Redis缓存设计和问题处理

摘要:
Redis已在工作中的所有项目中使用,并总结了设计思路和问题处理。过多的bigkey容易导致Redis拥塞和网络拥塞。如果没有Redis4或更高版本的异步删除机制,当发生过期删除时,bigkey也可能导致阻塞。Redis键值对集中失效,导致对存储层的大量请求,容易导致存储层负载过大,导致失败。解决方案是根据一定的基数随机设置键值的过期时间,以避免集中故障。1) 缓存设计应合理,减少大密钥的出现,并提前估计业务和数据量。

工作中做的所有项目都用到了redis,对其设计思路和问题处理做个总结。

key设计:可读性高,定义简洁,不包含特殊字符,一般使用:分隔,比如user:info:1000001,表示id为1000001的缓存key

value设计:字符串不宜过长,字符串最大是512M,一般来说超过10k我们就认为他是bigkey,集合,有序集合,哈希,个数不宜太多,比如存储百万级别的数据。具体大小应该根据读写频率进行评估。bigkey太多容易造成redis阻塞和网络阻塞。过期删除时,如果没有redis4以上的异步删除机制,bigkey也容易造成阻塞。bigkey要注意拆解成多个key-value,如果不可避免,尽量不要用一次性全部取出的命令。key值要控制生命周期,避免存储过多无用的数据。

命令:禁用keys,fslushall,flushdb等危险命令,适量使用批量命令可以提高效率,O(n)复杂度的命令要关注n的个数。部分遍历的需求可以使用scan相关的命令渐进式遍历。

什么是缓存击穿?redis键值对集中失效,导致大量的请求到存储层,容易引起存储层负载过高导致故障。比如电商网站可能同时商家多个热卖商品,活动时间两小时,两小时之后就会集中失效。解决办法就是将key值基于某个基数随机设置过期时间,避免集中失效。类似下面的伪代码:

public String get(String key) {
    // 从缓存中取数据
    String cacheData = cache.get(key);
    if (StringUtils.isNotBlank(cacheData)) {
         // 缓存非空,返回数据
         return cacheData;
    } 
    // 缓存为空,从存储层获取数据
    String storageValue = storage.get(key);
    cache.set(key, storageValue);
    // 随机设置过期时间
    int expireTime = new Random().nextInt(500)  + 500;
    return storageValue;     
}

什么是缓存穿透?一个不存在的数据,缓存和数据库都查不到,导致每次都会到数据库查询,失去了缓存的意义。这种一般是代码逻辑出问题或者被恶意攻击导致的。

最直接的解决办法是缓存空数据且设置过期时间,这样还是可以在缓存层拦截下来,缺点时如果缓存了很多空对象,key-value会占用较多redis的空间。

还有一种思路,使用布隆过滤器来解决,这是一种去重的思想,并不属于redis,redisson客户端进行比较好的实现,可以拿来使用。可以将布隆过滤器理解为一个非常大的bit数组,初始都为0,将key进行多次hash散列之后,放到指定的位置标记为1。具体判定方法为:如果过滤器中存在,这个key实际并不一定存在。如果过滤器中不存在,这个key一定不存在。缺点是需要提前构建过滤器,会有一定的误差率,不能删除,新增key时要在布隆过滤器中设置。

什么是缓存雪崩?缓存层顶住了绝大多数请求,但是由于种种原因,比如宕机、缓存设计不够好、超大并发导致缓存扛不住等问题,大量的请求直接流向存储层,容易造成存储层故障。  

1)缓存设计要合理,减少bigkey的出现,对业务和数据量提前进行预估。
2)应该保证缓存层的高可用,现在更多的使用集群模式。
3)限流、熔断、降级,并不是服务器能顶住一百万的请求,就随时让请求全部进来,限流处理还是很有必要的,要留有一定的空间。如果顶不住流量的压力对于非核心数据可以降级返回,对于核心数据仍然可以从缓存以及数据库中获取

缓存和数据库不一致?并发条件下,读写缓存和读写数据库的执行过程是不可预估的,多个线程执行的顺序是外部很难看清楚,可能会出现缓存数据库不一致问题。比如A线程写入数据库,然后B线程此时写入数据库且写入缓存,由于各种原因,比如网络等,即使A线程先执行业务,也有可能会后写入缓存,这就可能出现不一致。

如果问题并发量很小,就很难出现上述问题,或者缓存和数据库可以容忍出现不一致情况,我们给缓存设置过期时间就行,让缓存隔一段时间刷新一次数据。如果我们对于缓存和数据库的一致性有严格的要求,可以加读写锁来实现,读读相当于无锁,效率还是比较高的。引用外部的工具也可以处理,比如使用阿里开源的一款工具:canal,监听数据库binlog近实时更新缓存,且和我们的业务代码解耦,缺点就是系统引入了新的中间件,会增加系统复杂性。

对热点key重建时的优化:在某些场景下,一些缓存的访问量极大,比如微博热搜、头条热点此类,缓存失效时,会涌入大量的请求尝试将数据写入缓存,建缓存可能设计很多复杂的逻辑,比如多次查询数据库、多次IO等,不能短时间内完成。如果每个请求都做这些操作,会给服务端应用造成压力。对于这种情况,我们可以使用互斥锁,可以参考redis分布式锁,同一时间只有一个线程重建缓存,其他线程只需要等待重建完成从缓存查询数据即可。看下面的伪代码:

String get(String key) {
    // 从缓存中获取数据
    String cacheValue = redis.get(key);
    // 如果cacheValue为空, 重构缓存
    if (StringUtil.isBlank(cacheValue)) {
        // 推荐使用redisson的分布式锁
        if (redisson.tryLock(lockKey)) {
            cacheValue = storage.get(key);
            // 写入缓存
            redis.setex(key, timeout, cacheValue);
            // 释放锁
            redisson.unlock();
        }else {
       // 没有拿到所的线程,休眠一段时间直接再尝试从缓存中读取数据
            Thread.sleep(50);
            get(key);
        }
    }
    return cacheValue;
}

对于并发量很高的系统,而且对应用的稳定性有要求的系统,上面的很多情况可能都要思考在内。 

免责声明:文章转载自《Redis缓存设计和问题处理》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇c++中的.hpp文件IIS-7.5 第一次加载慢的 解决办法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

dedecms 空间迁移步骤

1.在新空间重新安装一次原版本的DEDECMS,然后把旧站的所有数据,这里的数据指的是文件,即除了根目录下文件夹include下的配置文件config_base.php外的所有文件覆盖到新空间下2.在旧网站后台系统管理-数据备份哪里备份数据,这里的数据指的是DEDECMS自己后台所采取的备份文件,类似用phpmyadmin等工具所作的备份。备份完成后,DE...

Spring Boot页面中select选项绑定数据库数据

  在一个select框中,option往往是写好的,这样难以适应数据库中项目的动态变化,因此需要将option中的项目与数据库数据进行绑定,本项目使用Spring Boot进行开发,下面演示绑定方法。   首先在前端定义一个基本select框,在这里把第一项写好了,并显示为select框的默认项。 <select id="selectshijuan...

【转】MySQL各版本的区别

来源:https://www.cnblogs.com/langtianya/p/5185601.html MySQL各版本的区别: 1. MySQL Community Server 社区版本,开源免费,但不提供官方技术支持。2. MySQL Enterprise Edition 企业版本,需付费,可以试用30天。3. MySQL Cluster 集群版,...

sqlserver跨服务器数据库sql语句

1、启用Ad Hoc Distributed Queries:exec sp_configure 'show advanced options',1reconfigureexec sp_configure 'Ad Hoc Distributed Queries',1reconfigure2、sql语句insert into datatable(id)sel...

Linux搭建Java环境(JDK+Tomcat+MySQL)

目录 一、项目环境: 二、安装JDK1.8 三、安装Tomcat8.5 四、安装MySQL数据库 五、配置JAVA项目 一、项目环境: 开发环境 生产环境 测试环境 硬件环境: web服务器:cpu:intel i7,8G内存,金士顿1T固态硬盘,万兆网卡 数据库服务器:cpu:intel i7,8G内存,金士顿1T固态硬盘,万兆网卡...

你必须知道的ADO.NET(八) 深入理解DataAdapter(上)

摘要 ADO.NET有两个核心组件:基于连接的Data Provider组件以及基于非连接的DataSet组件。基于连接的Data Provider组件常用于实时地从数据库中检索数据。而基于非连接的DataSet,似乎与数据库没有直接联系,仅仅用于在本地内存中存储Data Provider提供的数据表或集合。这一切似乎很微妙,此时,你是否在想:这两大组件...