原文连接java
能坚持别人不能坚持的,才能拥有别人不曾拥有的。
关注编程大道
公众号,让咱们一同坚持心中所想,一块儿成长!!web
使用Redis作K-V存储,必定要注意过时时间的把控,任何K-V的存储都要设置过时时间,无论多长时间。通常在封装Redis操做工具类时提供默认使用系统公共超时时间的操做API,避免新手在使用时不设置过时时间,致使内存的浪费。另外,经过链接池 Jedis jedis = JedisPool.getResource();
这样获取Redis链接最好使用try/finally
块,而且在finally块中调用 jedis.close()
; 将链接归还给链接池,不然将会一直持有链接,颇有可能致使在未来的某一时刻报拿不到链接的错。这也是以前某一个同事犯过的错致使生产bug!面试
你觉得Redis作缓存就万无一失吗?就单纯的遵循那种经典操做吗?(即:请求来了,先看缓存有没有,有直接返回,没有就查数据库,数据库有的话先存缓存,而后返回,数据库没有就返回空)这样就是Redis缓存的正确姿式吗?若是你这样作,极可能疏忽一点,那就是缓存穿透。如以前在项目中作的一个需求-页面广告可配置化自动上下线(我在以前专门写过一篇文章介绍这个需求的一步步演进过程,对Redis新手颇有帮助,感兴趣的能够去看看),简单的提一下吧,就是好比在支付完成的页面你们都应该见过吧,好比支付完成后的结果页,可能会弹出来一个红包什么的,页面下方的广告位等,就是相似的这样一个需求。由于这个页面访问量很大,进这个页面就查这个广告位的数据,当运营最近不想配置广告了,这边查到的是否是就是是空啊?数据库也是空的,缓存也没有数据,那不少请求都来,这样就无缘无故的形成了数据库的压力呀,多么的浪费!若是是别的其余业务,黑客钻了空子,专门请求你系统根本不存在的数据,请求多了,都打到数据库,是颇有可能把你数据库打死的。若是你在作需求的时候没想到这一点,那后续出了问题,你就等着背锅了。redis
怎么避免呢?算法
好办,能够将数据库也不存在的数据存个null值或一个空json(总之你本身约定好就行),也给放到Redis里,设置个较短的过时时间,下次再来取的时候看到是空就直接返回。另外,可使用布隆过滤器作一层系统级的防御,专门去拦截系统中根本不存在的key。数据库
刚说完缓存穿透,再聊聊缓存雪崩。好比你将用户数据放到缓存里,当某一时刻这些数据所有都过时了,大量请求都过来,发现缓存没法命中,不就都去数据库了吗,数据库一会儿来这么多请求不就搞挂了吗?解决办法就是尽可能是key的过时时间分散开,不要集中。在一个固定的过时时间上+一个随机值,好比你设置的过时时间是5小时,你能够加一个0-600秒的随机值。编程
缓存失效时多个请求同时请求同一个key,都发现缓存中空了,都去查数据库,这不是浪费吗,正常一个去查就好了,查完放缓存别的请求直接从缓存拿就好了。这就是缓存并发问题。当请求很是的多的时候,会对数据库形成很大的冲击,也是有可能把数据库搞挂的吧?怎么解决,能够对更新缓存的操做加锁,使用synchronized吗?不行,由于生产上是分布式部署的,须要使用redis分布式锁。json
例如,当缓存数据失效的时候,某一线程使用资源ID做为key尝试加分布式锁,加锁成功的线程执行更新缓存的操做将查到的数据放入缓存缓存中,其余线程就能够直接使用缓存数据了。缓存
正如上面所说,在集群部署的状况下synchronized就失效了,因此分布式锁就派上用场了。常见的分布式锁的实现方式有三种:基于数据库,基于Redis,基于Zookeeper。架构
Redis分布式锁须要特别注意的点就是锁的过时时间,如,使用redis的setnx命令,设置成功即表示拿到锁,而后设置过时时间,命令执行失败的线程表示获取锁失败。必定要注意锁的过时时间的设置,有加锁的操做,也要有解锁的操做。如以前咱们项目的一个临时性的一个组团竞走的活动,10人成团竞走PK的活动,在组团阶段,用户能够邀请朋友加入本身的团。咱们的团数据是存放在Redis中的,包括每一个团的人数。当用户发起入团操做时,后台逻辑会从redis取该团的现有成员数,若是小于10才能继续走下面的逻辑。当并发场景下,如团长分享给不少人入团邀请,这些人的入团请求并发执行的状况下颇有可能能形成组团人数超过10人的状况。由于在并发场景下,执行获取当前团成员数的这行代码会被多个请求获取到,好比临界的时候,团成员已经有了9个,同时来了俩入团请求,若是不加控制,同时执行读取现有团成员个数时都读到的是9,而后都执行入团操做,就会形成团成员超过10人的bug。
因此在入团请求的逻辑上,要加分布式锁,获取到锁才能执行后续逻辑。由于获取锁的操做是使用setnx命令,并无等待锁的机制,咱们须要在获取锁的逻辑加一个自旋,每隔必定时间尝试一次获取,超过必定时间后返回加锁失败。
public boolean tryLock(String lockKey,long expireTime){
long waitTime = 0;
//setIfAbsent使用的是redis的setnx方法
boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey,"jingzouLock",
expireTime,TimeUnit.MILLISECONDS);
if(success==true){
return success;
}else{
while(success==false && waitTime <50000L){
success = redisTemplate.opsForValue().setIfAbsent(lockKey,"jingzouLock",
expireTime, TimeUnit.MILLISECONDS);
try{
Thread.sleep(100);
}catch(Exception e){}
waitTime+=100L;
}
}
return success;
}
另外,还须要遵循“解铃还须系铃人”的原则,谁加的锁谁解,否则本身加的锁,被别人解了也是会形成问题的。例如,用户A,请求入团,拿到分布式锁,若是A由于某些缘由在锁超时时间内没有执行完代码,锁就过时自动释放了,若是此时B请求加入同一个团,拿到了分布式锁,若是此时A请求执行完了,释放锁了,可是释放的是B的锁,这样也有可能形成团人数超过10的bug。因此,设置分布式锁时的value能够设置成不一样的值,如A请求是用户ID为12的用户,设置分布式锁的时候就value就能够用这个惟一的元素,当解锁的时候再验证value是12时才能执行解锁操做。
如上加锁代码,咱们增长一个参数String value传入动态值,在上述场景中能够用用户ID,代替咱们写死的"jingzouLock"。而后在释放锁的方法里,咱们先判断value值,相同再执行删除。
public void releaseLock(String lockKey,String value){
String valueInRedis = redisTemplate.get(lockKey);
if(value.equals(valueInRedis)){
redisTemplate.delete(lockKey);
}
}
还有一种场景须要考虑。当Redis master发生故障,主备切换时每每会形成数据丢失,包括分布式锁的Key-Value。这样就会致使锁间接的被释放了,假如操做还没执行完,锁被其余请求拿到了,分布式锁就起不到做用了。
考虑到这方面的问题,Redis官方提供了Redlock算法,以及相应的开源实现Redisson。用到分布式锁的场景,你们能够直接使用 Redisson,很是方便,后期可能会写一写Redisson的技术干货。
原文地址:https://mp.weixin.qq.com/s/2sXEkRsrC9b9RBJns3fnnw
另外,若是系统对可靠性要求很高,如需用到分布式锁,建议使用分布式锁的另外实现方式,如:Zookeeper,etcd等。
好了,今天就分享到这。若是感受本文对您有帮助,有劳点下在看,把知识分享给更多的人哦
以为好看,请点赞哦~
关注公众号 编程大道 ,第一时间获文章推送。
以为好看,请 点赞、关注、转发 哦~