Redis开发与运维笔记-缓存设计

网友投稿 938 2022-10-02

本站部分文章、图片属于网络上可搜索到的公开信息,均用于学习和交流用途,不能代表睿象云的观点、立场或意见。我们接受网民的监督,如发现任何违法内容或侵犯了您的权益,请第一时间联系小编邮箱jiasou666@gmail.com 处理。

Redis开发与运维笔记-缓存设计

缓存设计

缓存更新策略

LRU/LFU/FIFO算法剔除

清理数据由算法决定,一致性无法得到保障。算法无需开发人员实现,根据业务场景选择合适算法即可。

超时剔除

通过给键设置过期时间,过期期间内数据可能存在数据不一致问题。

主动更新

对数据一致性要求高,需要在真实数据更新后通过消息或其他方式通知立即更新缓存。一致性最高,但如果主动更新发生了问题,那么这条数据很可能很长时间不会更新,所以建议结合超时剔除一起使用效果会更好。维护成本会比较高,开发者需要自己来完成更新,并保证更新操作的正确性。

策略一致性维护成本
LRU/LFU/FIFO算法剔除最差
超时剔除较差较低
主动更新

最佳实践

•低一致性业务建议配置最大内存和淘汰策略的方式使用•高一致性业务可以结合使用超时剔除和主动更新,这样即使主动更新出了问题,也能保证数据过期时间后删除脏数据

缓存粒度控制

缓存粒度需要在空间以及时间上进行取舍,决定时需要综合数据通用性、空间占用比、代码维护性三点进行取舍

穿透优化

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层。

缓存空对象

存储层不命中时,仍然将空对象保存到缓存层。这样下次查询会命中缓存层。这样解决会导致以下两个问题:

•空值进行缓存,浪费了内存

通常要设置一个较短的过期时间,过期后剔除缓存。

•数据不一致,过期时间内,当前缓存与储存层存在数据不一致现象

存储层修改时,主动更新缓存层数据。

布隆过滤器

有关布隆过滤器的相关知识,可以参考:https://en.wikipedia.org/wiki/Bloom_filter可以利用Redis的Bitmaps实现布 隆过滤器,GitHub上已经开源了类似的方案,读者可以进行参 考:https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter。

这种方法适用于数据命中不高、数据相对固定、实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少

无底洞优化

分布式服务节点过多时,数据分布到的节点数更多,一次批量操作的数据可能分布在n个节点上,操作的时间为n*网络+n*命令,常见的优化思路如下:

串行命令

执行n次get命令,Jedis客户端示例如下

List serialMGet(List keys) { // 结果集 List values = new ArrayList(); // n次串行get for (String key : keys) { String value = jedisCluster.get(key); values.add(value); } return values;}

串行IO

使用Smart客户端,将映射到相同槽(节点)的key合并为一个集合,之后对每个节点执行mget或pipeline命令。它的操作时间=node次网络时间+n次命令时间,如果节点数过多,还是存在性能问题。Jedis客户端示例如下:

Map serialIOMget(List keys) { // 结果集 Map keyValueMap = new HashMap(); // 属于各个节点的key列表,JedisPool要提供基于ip和port的hashcode方法 Map> nodeKeyListMap = new HashMap>(); // 遍历所有的key for (String key : keys) { // 使用CRC16本地计算每个key的slot int slot = JedisClusterCRC16.getSlot(key); // 通过jedisCluster本地slot->node映射获取slot对应的node JedisPool jedisPool = jedisCluster.getConnectionHandler().getJedisPoolFromSlot(slot); // 归档 if (nodeKeyListMap.containsKey(jedisPool)) { nodeKeyListMap.get(jedisPool).add(key); } else { List list = new ArrayList(); list.add(key); nodeKeyListMap.put(jedisPool, list); } } // 从每个节点上批量获取,这里使用mget也可以使用pipeline for (Entry> entry : nodeKeyListMap.entrySet()) { JedisPool jedisPool = entry.getKey(); List nodeKeyList = entry.getValue(); // 列表变为数组 String[] nodeKeyArray = nodeKeyList.toArray(new String[nodeKeyList.size()]); // 批量获取,可以使用mget或者Pipeline List nodeValueList = jedisPool.getResource().mget(nodeKeyArray); // 归档 for (int i = 0; i < nodeKeyList.size(); i++) { keyValueMap.put(nodeKeyList.get(i), nodeValueList.get(i)); } } return keyValueMap;}

并行IO

将方案2中的改为多线程执行。由于改为多线程,网络时间变为O(1),这种方案会增加编程的复杂度。

hash_tag

hash_tag可以将多个key强制分配到同一个槽(节点)上,它的操作时间=1次网络时间+n次命令时间

四种方案对比

方案优点缺点网络IO
串行命令1)编程简单
2)如果少量keys,性能可以满足要求
大量keys请求延迟严重O(keys)
串行IO1)编程简单
2)少量节点,性能满足要求
大量node延迟严重O(nodes)
并行IO利用并行特性,延迟取决于最慢的节点1)编程复杂
2)多线程,定位问题比较难
O(max_slow(nodes))
hash_tag性能最高1)业务维护成本较高
2)容易出现数据倾斜
O(1)

雪崩优化

缓存层由于某些原因不能提供服务或大量键同时过期,导致大量的请求发送到存储层,造成级联宕机的情况。

保证缓存层高可用

使用Redis Sentinel 或 Redis Cluster架构增强可用性

使用隔离组件为后端限流并降级

对重要资源进行隔离,让每种资源都运行在各自的线程池中,避免因个别服务出现问题导致的全体服务不可用。这部分可以查看Hystrix[1],注意的是Hystrix只适用于Java。

提前演练

上线前对各种情况进行演练,包括但不限于,数据库宕机,缓存不可用,大量并发涌入系统等。

热点key重建优化

当一个热点key的并发量非常大,并且更新缓存不能在短时间内完成时,缓存失效到重建期间,大量请求的话,会导致大量线程重建缓存,增加后端负载。要解决这个问题可以减少重建缓存的次数。

互斥锁

只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,从缓存中获取数据即可。使用redis的setnx实现如下:

String get(String key) { // 从Redis中获取数据 String value = redis.get(key); // 如果value为空,则开始重构缓存 if (value == null) { // 只允许一个线程重构缓存,使用nx,并设置过期时间ex String mutexKey = "mutext:key:" + key; if (redis.set(mutexKey, "1", "ex 180", "nx")) { // 从数据源获取数据 value = db.get(key); // 回写Redis,并设置过期时间 redis.setex(key, timeout, value); // 删除key_mutex redis.delete(mutexKey); } // 其他线程休息50毫秒后重试 else { Thread.sleep(50); get(key); } } return value;}

永远不过期

缓存中不设置过期时间,过期时间由业务系统来维护。业务系统发现缓存过期后,通过单独的线程去重建缓存。此种方法在重建缓存期间会存在数据不一致情况。

本章重点回顾

1.缓存的使用带来的收益是能够加速读写,降低后端存储负载。2.缓存的使用带来的成本是缓存和存储数据不一致性,代码维护成本增大,架构复杂度增大。3.比较推荐的缓存更新策略是结合剔除、超时、主动更新三种方案共同完成。4.穿透问题:使用缓存空对象和布隆过滤器来解决,注意它们各自的使用场景和局限性。5.无底洞问题:分布式缓存中,有更多的机器不保证有更高的性能。有四种批量操作方式:串行命令、串行IO、并行IO、hash_tag。6.雪崩问题:缓存层高可用、客户端降级、提前演练是解决雪崩问题的重要方法。7.热点key问题:互斥锁、“永远不过期”能够在一定程度上解决热点key问题,开发人员在使用时要了解它们各自的使用成本。

References

[1] Hystrix: https://github.com/netflix/hystrix

上一篇:尴尬的面试现场:说说你们系统有多大QPS?系统到底怎么抗住高并发的?【石杉的架构笔记】
下一篇:Oracle RAC 重建 OCR 和 Voting disk(上篇)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~