Redis缓存的主要异常及解决方案

Redis 是当前最流行的 NoSQL数据库。Redis主要用来做缓存使用,在提高数据查询效率、保护数据库等方面起到了关键性的作用,很大程度上提高系统的性能。当然在使用过程中,也会出现一些异常情景,导致Redis失去缓存作用。

异常类型

异常主要有 缓存雪崩 缓存穿透 缓存击穿。

缓存雪崩

现象

缓存雪崩是指大量请求在缓存中没有查到数据,直接访问数据库,导致数据库压力增大,最终导致数据库崩溃,从而波及整个系统不可用,好像雪崩一样。

异常原因

  • 缓存服务不可用。

  • 缓存服务可用,但是大量KEY同时失效。

解决方案

1.缓存服务不可用

redis的部署方式主要有单机、主从、哨兵和 cluster模式。

  • 单机
    只有一台机器,所有数据都存在这台机器上,当机器出现异常时,redis将失效,可能会导致redis缓存雪崩。

  • 主从
    主从其实就是一台机器做主,一个或多个机器做从,从节点从主节点复制数据,可以实现读写分离,主节点做写,从节点做读。
    优点:当某个从节点异常时,不影响使用。
    缺点:当主节点异常时,服务将不可用。

  • 哨兵
    哨兵模式也是一种主从,只不过增加了哨兵的功能,用于监控主节点的状态,当主节点宕机之后会进行投票在从节点中重新选出主节点。
    优点:高可用,当主节点异常时,自动在从节点当中选择一个主节点。
    缺点:只有一个主节点,当数据比较多时,主节点压力会很大。

  • cluster模式
    集群采用了多主多从,按照一定的规则进行分片,将数据分别存储,一定程度上解决了哨兵模式下单机存储有限的问题。
    优点:高可用,配置了多主多从,可以使数据分区,去中心化,减小了单台机子的负担.
    缺点:机器资源使用比较多,配置复杂。

  • 小结
    从高可用得角度考虑,使用哨兵模式和cluster模式可以防止因为redis不可用导致的缓存雪崩问题。

2.大量KEY同时失效

可以通过设置永不失效、设置不同失效时间、使用二级缓存和定时更新缓存失效时间

  • 设置永不失效
    如果所有的key都设置不失效,不就不会出现因为KEY失效导致的缓存雪崩问题了。redis设置key永远有效的命令如下:
    PERSIST key
    缺点:会导致redis的空间资源需求变大。

  • 设置随机失效时间
    如果key的失效时间不相同,就不会在同一时刻失效,这样就不会出现大量访问数据库的情况。
    redis设置key有效时间命令如下:
    Expire key
    示例代码如下,通过RedisClient实现

/**
* 随机设置小于30分钟的失效时间
* @paramredisKey
* @paramvalue
*/privatevoidsetRandomTimeForReidsKey(String redisKey,String value){
//随机函数Random rand = newRandom();
//随机获取30分钟内(30*60)的随机数
int times = rand.nextInt(1800);
//设置缓存时间(缓存的key,缓存的值,失效时间:单位秒)
redisClient.setNxEx(redisKey,value,times);
}
  • 使用二级缓存
    二级缓存是使用两组缓存,1级缓存和2级缓存,同一个Key在两组缓存里都保存,但是他们的失效时间不同,这样1级缓存没有查到数据时,可以在二级缓存里查询,不会直接访问数据库。
    示例代码如下:

publicstaticvoidmain(String[] args) {
CacheTest test = new CacheTest();
//从1级缓存中获取数据
String value = test.queryByOneCacheKey("key");
//如果1级缓存中没有数据,再二级缓存中查找if(StringUtils.isBlank(value)){
value = test.queryBySecondCacheKey("key");
//如果二级缓存中没有,从数据库中查找if(StringUtils.isBlank(value)){
value =test.getFromDb();
//如果数据库中也没有,就返回空if(StringUtils.isBlank(value)){
System.out.println("数据不存在!");
}else{
//二级缓存中保存数据
test.secondCacheSave("key",value);
//一级缓存中保存数据
test.oneCacheSave("key",value);
System.out.println("数据库中返回数据!");
}
}else{
//一级缓存中保存数据
test.oneCacheSave("key",value);
System.out.println("二级缓存中返回数据!");
}
}else {
System.out.println("一级缓存中返回数据!");
}
}
  • 异步更新缓存时间
    每次访问缓存时,启动一个线程或者建立一个异步任务来,更新缓存时间。
    示例代码如下:

publicclassCacheRunnableimplementsRunnable {

privateClusterRedisClientAdapter redisClient;
/**
* 要更新的key
*/publicString key;

publicCacheRunnable(String key){
this.key =key;
}

@Overridepublicvoidrun() {
//更细缓存时间
redisClient.expire(this.getKey(),1800);
}

publicStringgetKey() {
return key;
}

publicvoidsetKey(String key) {
this.key = key;
}
}
publicstaticvoidmain(String[] args) {
CacheTest test = newCacheTest();
//从缓存中获取数据String value = test.getFromCache("key");
if(StringUtils.isBlank(value)){
//从数据库中获取数据
value = test.getFromDb("key");
//将数据放在缓存中
test.oneCacheSave("key",value);
//返回数据System.out.println("返回数据");
}else{
//异步任务更新缓存CacheRunnable runnable = newCacheRunnable("key");
runnable.run();
//返回数据System.out.println("返回数据");
}
}

小结

上面从服务不可用和key大面积失效两个方面,列举了几种解决方案,上面的代码只是提供一些思路,具体实施还要考虑到现实情况。当然也有其他的解决方案,我这里举例是比较常用的。毕竟现实情况,千变万化,没有最好的方案,只有最适用的方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盈梓的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值