一、常用的redis缓存处理流程
二、缓存穿透
当前端请求一个不存在的数据时,按照上述流程,会先从redis中查询,未查到数据,然后到后端数据库查询,也未查到数据,这时返回空数据(返回为空的数据不写入redis)。下次再查询此数据,由于redis中还是没有此数据,因此还会到后端数据库查询。像这样每次都穿过redis到后端数据库查询的情况,就叫缓存穿透。若有人恶意用此方式大量查询,则可能导致后端数据库崩溃。
解决方案
1、前端优化:参数验证(页面端、接口端等),从前端拦截无效的查询。
2、后端优化:从数据库中查询出为空时,将key-value对以key-null写入redis,这样大量查询时就从redis中查到,不会再穿透到后端数据库。同时,为key-null设置过期时间,一般为(30s~5m),设置太短,会有大量穿透请求到后端数据库。设置太长或不设置超时时间(该key会一直存在),后面没有恶意请求来时,该key依然占用内存。若这种key很多时,可能会导致内存溢出。
public object getOrderList() { int cacheTime = 30; String cacheKey = "orderList"; cacheValue = CacheHelper.Get(cacheKey); if (cacheValue != null) { return cacheValue; } else { cacheValue = getOrderListFromDB(); if(null == cacheValue){ cacheValue = ""; } CacheHelper.Add(cacheKey, cacheValue, cacheTime); //即使 return cacheValue; } }
三、缓存击穿
对于设置了过期时间的热点key(在某些时候会有很高的并发访问),若超高并发访问时,刚好热点key到期,这时会出现redis中该key数据查不到,而后端数据库能查到。
在这些并发访问线程中,第一个线程会去数据库中查询并将结果写入redis,在该操作执行期间,redis中还没有数据,这时其他线程在redis中查询不到数据就会到后端数据库去查,大量的请求可能导致后端数据库崩溃,这就叫缓存击穿。
解决方案
1、使用互斥锁
缓存失效时(redis中取出值为null时),加锁成功的线程去数据库查询,并放入redis,其他线程等待一段时间后重新从redis中获取。
public String get(key) { String value = redis.get(key); if (value == null) { //代表缓存值过期 //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功 value = db.get(key); redis.set(key, value, expire_secs); redis.del(key_mutex); } else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可 sleep(50); get(key); //重试 } } else { return value; } }
2、不设置过期时间
在redis的设置过期时间功能上不设置过期时间。
把过期时间设置在value里,发现如果过期了,通过异步线程去更新redis。
String get(final String key) { V v = redis.get(key); String value = v.getValue(); long timeout = v.getTimeout(); //获取时间 if (v.timeout <= System.currentTimeMillis()) { //发现过期了 // 异步更新后台异常执行 threadPool.execute(new Runnable() { //可能短时间内出现很多个线程 public void run() { String keyMutex = "mutex:" + key; if (redis.setnx(keyMutex, "1")) { //保证只有1个线程会执行查询后端数据库并更新redis的操作 // 3 min timeout to avoid mutex holder crash redis.expire(keyMutex, 3 * 60); String dbValue = db.get(key); redis.set(key, dbValue); redis.delete(keyMutex); } } }); } return value; }
四、缓存雪崩
大量redis数据同时到过期时间,即使做了防缓存击穿处理,由于到期的数据量过大,还是会有很多线程同时访问后端数据库,从而可能导致后端数据库崩溃。这就是缓存雪崩。
redis挂掉后,请求也会直接访问后端数据库,从而引起雪崩。
解决方案
1、缓存数据的过期时间设置随机(固定值+随机值的方式),防止同一时间大量过期。
2、最好采用redis集群,可以将热点数据分布在不同的节点上,防止部分redis挂掉后导致的雪崩问题。
3、对于热点数据,可以设置为永不过期,通过定时更新的方式来刷新缓存。
4、从系统上进行限流(服务限流、接口限流等),控制请求数量。