NoSQL数据库
Redis命令
Redis常用数据类型
String
Redis常用数据类型
List
![]()
![]()
Redis常用数据类型
Set
Redis常用数据类型
Hash
Redis常用数据类型
有序集合Zset
![]()
Redis数据类型
HyperLogLog
![]()
Redis数据类型
Geographic
![]()
![]()
![]()
Redis配置文件
protocted-mode no 设置为no表示支持远程访问
tcp-keepaliave 300 检测心跳时间周期
![]()
Redis发布订阅
Jedis
<dependencies> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.3</version> </dependency> </dependencies>
public class PhoneCode{ /*生成6位验证码*/ public static String getCode(){ Random random = new Random(); String code = ""; for(int i=0;i<6;i++){ int rand = random.nextInt(10); code += rand; } return code; } /*每个手机号每天只能发送3次验证码,验证码放到redis *设置过期时间*/ public static String verifyCode(String phone){ /*连接redis*/ Jedis jedis = new Jedis("10.10.5.252",6379); /*手机号发送次数key*/ String countKey = "VerifyCode"+phone+":count"; String count = jedis.get(countKey); if(count == null){ /*没有发送次数,第一次发送,设置发送次数为1*/ jedis.setex(countKey,24*60*60,"1"); }else if(Integer.parseInt(count) <= 3){ /*发送次数+1*/ jedis.incr(countKey); }else{ /*超过发送次数*/ System.out.println("超过每日发送次数"); jedis.close(); return null; } /*验证码key*/ String codeKey = "VerifyCode"+phone+":code"; String vcode = getCode(); jedis.setex(codeKey,120,vcode); jedis.close(); return vcode; } /*验证码校验*/ public static void getRedisCode(String phone,String code){ Jedis jedis = new Jedis("10.10.5.252",6379); /*验证码Key*/ String codeKey = "VerifyCode"+phone+":code"; String codeR = jedis.get(codeKey); if(codeR.equals(code)){ System.out.println("成功"); }else{ System.out.println("失败"); } } }
SpringBoot整合Redis
<!--Redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--Reids链接池--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>x.x.x</version> </dependency>
@RestController @RequestMapping("/redisTest") public class RedisTestController{ @Autowired private RedisTemplate<String,String> redisDemo; @GetMapping public String testRedis(){ /*设置key,value到redis*/ redisDemo.opsForValue().set("name","daMing"); String name = redisDemo.opsForValue().get("name"); return name; } }
Redis事务
事务的冲突问题
乐观锁命令
秒杀
/*uid:用户id *prodid:商品id*/ public static boolean doSecKill(String uid,String prodid){ /*uid,pid非空判断*/ if(uid == null || prodid == null){ return false; } /*链接redis*/ Jedis jedis = new Jedis("10.10.5.252",6379); /*拼接key *库存key *秒杀成功用户key*/ String kcKey = "kc:"+prodid+":qt"; String usersKey = "users:"+prodid+":user"; /*使用乐观锁监视库存情况,防止并发同时消耗库存*/ jedis.watch(kcKey); /*获取库存 *如果库存等于null,秒杀还没有开始*/ String kc = jedis.get(kcKey); if(kc == null){ System.out.println("秒杀未开始,请等候!"); jedis.close(); return false; } /*判断如果商品数量,库存数量小于等于0,秒杀结束*/ if(Integer.parseInt(kc) <= 0){ System.out.println("无库存,秒杀结束!"); jedis.close(); return false; } /*秒杀过程 *库存-1 *把秒杀成功用户添加到清单里面*/ /*需要使用事务机制对库存进行操作,防止并发时,只能有一个操作库存成功*/ Transactional multi = jedis.multi(); /*事务组队*/ multi.decr(kcKey); multi.sadd(userKey,uid); /*执行事务*/ List<Object> results = multi.exec(); if(results == null || results.size() == 0){ /*事务执行失败*/ System.out.println("秒杀失败!"); jedis.close(); return false; } /*事务执行成功*/ System.out.println("秒杀成功!"); jdeis.close(); return true; }
使用乐观锁容易造成库存遗留问题
500个商品,2000个都同时秒杀,只要有一个人进行操作redis,修改版本号时,其它人都会执行事务失败,导致秒杀失败,从而产生库存遗留。
Lua脚本:
local userid = KEYS[1]; local prodid = KEYS[2]; local qtkey = "sk:"..prodid..":qt"; local usersKey = "users:"..prodid..":user"; local userExists = redis.call("sismember",usersKey,userid); if tonumber(userExists)==1 then return 2; end local num = redis.call('get',qtkey); if tonumber(num)<=0 then return 0; else redis.call("decr",qtkey); redis.call("sadd",usersKey,userid); end return 1;
public static void main(String[] args) throws IOException{ /*链接redis*/ Jedis jedis = new Jedis("10.10.5.252",6379); String sha1 = jedis.scriptLoad(secKillScript); Object result = jedis.evalsha(sha1,2,userid,prodid); String reString = String.valueOf(result); if("0".equals(reString)){ System.out.println("已抢光!"); }else if("1".equals(reString)){ System.out.println("抢购成功!"); }else if("2".equals(reString)){ System.out.println("该用户已购买过"); }else{ System.out.println("抢购异常!!"); } jedis.close(); return true; } static String secKillScript="local userid=KEYS[1];\r\n" + “local prodid=KEYS[2];\r\n" + "local qtkey = 'Seckill:'..prodid..\":kc\";\r\n"; + "local usersKey='Seckill:'..prodid..\"users\";\r\n"; + "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + "if tonumber(userExists)==1 then \r\n" + " return 2; \r\n" + "end \r\n" + "local num = redis.call(\"Ret\",qtkey); \r\n" + "if tonumber(num) <= 0 then \r\n" + " return 0; \r\n" + "else \r\n" + " redis.call(\"decr\",qtkey); \r\n" + " redis.call(\"sadd\",usersKey,userid); \r\n" + "end\r\n" + "return 1";
ab测试工具
ab -n 1000 -c 100 http://xxx/xx
-n请求次数 -c 并发次数 -p post提交的参数的文件
ab -n 1000 -c 100 -p ./postfile -T 'application/x-www-form-urelencoded' http://xxx/xx
postfile内容 prodid=0101&
Redis持久化
RDB AOF
RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘
AOF
Redis主从复制
![]()
![]()
启动哨兵 redis-sentinel /etc/sentinel.conf
Redis集群
1.设置集群
2.链接集群
一个集群至少有3个节点。
在集群中一次进行多个添加时,要使用组的方式。如下:
集群的Jedis开发
/* *Redis集群操作*/ public class RedisClusterDemo{ public static void main(String[] args){ /*创建对象*/ HostAndPort hostPort = new HostAndPort("10.10.5.252",6379); JedisCluster jCluster = new JedisCluster(hostPort); /*操作*/ jCluster.set("b1","value1"); String value = jCluster.get("b1"); System.out.println("value : "+value); jCluster.close(); } }
集群优点:
实现扩容 分摊压力 无中心配置相对简单
集群的不足:
![]()
Redis集群分布式锁
基于Redis实现分布式锁
setnx users 20 设置锁
del users 删除锁
expire users 10 设置锁的过期时间
set users 10 nx ex 10 上锁的同时设置过期时间(推荐使用)
未加入uuid防误删代码
@RestController @RequestMapping("/redisTest") public class RedistestController{ @Autowired public RedisTemplate<String,String> redisDemo; @GetMapping("testLock") public void testLock(){ /*获取锁,相当于setnx *设置锁超时时间为3s,相当于expire或者set xx xx nx ex 3*/ Boolean lock = redisDemo.opsForValue().setIfAbsent("lock","111",3,TimeUnit.SECONDS); /*获取锁成功,查询num值*/ if(lock){ Object value = redisDemo.opsForValue().get("num"); /*判断num为空Return*/ if(StringUtils.isEmpty(value)){ return ; } /*类型转换*/ int num = Integer.parseInt(value+""); /*把redis的num加1*/ redisDemo.opsForValue().set("num",num+1+""); /*释放锁,相当于del*/ redisDemo.delete("lock"); }else{ /*获取锁失败,延时0.1s后重新获取*/ try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } } } }
加入uuid防误删代码
@RestController @RequestMapping("/redisTest") public class RedisTestCOntroller{ @GetMapping("testLock") public String testLock(){ /*获取uuid*/ String uuid = UUID.randomUUID().toString()+Thread.currentThread().getId(); /*获取锁,相当于setnx *设置锁超时时间为3s,相当于set key value nx ex 3*/ Boolean lock = redisDemo.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS); /*获取锁成功,查询num值*/ if(lock){ Object value = redisDemo.opsForValue().get("num"); /*判断num为空,直接Return*/ if(StringUtils.isEmpty(value)){ return ; } /*类型转换*/ int num = Integer.parseInt(value+""); /*把redis上num值加1*/ redisDemo.opsForValue().set("num",num+1+""); /*获取redis上lock的值*/ String lockCurUuid = redisDemo.opsForValue().get("lock"); /*判断比较uuid是否一致,如果一致释放锁 *如果不一致,不进行锁删除,防止误删*/ if(lockCurUuid.equals(uuid)){ /*释放锁,相当于del*/ redisDemo.delete("lock"); } }else{ /*获取锁失败,延时0.1s在获取*/ try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } } } }