在日常工作中,使用Redis有什么需要注意的?
- 设置合适的过期时间。
- 尽量避免大key问题,避免用字符串存储过大的数据;避免集合的数据量太大,要定期清除。
常用的数据结构有哪些?用在什么地方?
按照使用的频率排序。
- 字符串类型,用作常规的缓存,比如缓存token;存储点赞数、库存等需要增减的数字类型,自带自增自减API。
- zset类型,支持去重和排序,可以用来实现排行榜,使用热度作为分数值,每次插入数据的时候记得删除排行榜之外的数据,还可以随机获取数据。
- 哈希类型,对于符合对象结构类型的数据,可以考虑使用哈希类型存储。例如一个班有多个学生。可以用班级id作为哈希的key,使用学员id作为键,存储学生信息JSON数据。
- set类型,需要去重又不需要排序的时候可以使用set结构,速度比zset要快一点。
- list类型,存储集合数据,支持随机取值。
如何保证数据库和缓存的一致性?
首先明确一点,使用了缓存了,就基本会存在延迟,因此在能接受一点延迟的情况下才使用缓存。
需要保持数据库和缓存一致性的数据都会设置合适的过期时间,这个过期时间是保持一致性兜底。
更新缓存方案
该方案就是在数据更新完成之后就去更新缓存。如果事务尚未提交就更新,可能事务回滚了导致缓存和数据库不一致。事务提交之后更新缓存失败,也会导致缓存和数据库不一致。
但也有优势,特别是现在很多项目的数据库都采用主从架构,主动更新可以直接拿到最新的数据。
如果缓存的过期时间设置得比较短,并且可以接受短时间不一致,可以考虑使用该方案。
删除缓存方案
该方案就是在数据更新完成之后就去删除缓存。下次查询的时候检测到没有缓存就会去数据库查缓存。
存在缓存删除失败问题。还有一个更致命的问题,因为更新缓存的时候一般查的是从库,有可能主从同步尚未完成就将旧值更新到了缓存导致缓存和数据库不一致。
延时双删方案
该方案是基于上述的方案的改进版,先删除缓存,延时之后再删除一次。这样可以防止第一次删除失败,也可以防止主从同步完成之前将旧值更新到缓存。
但也会引入新的问题,延时时间难以精准控制。不同数据库的主从复制时间差不同。
并且延时需要引入新的中间件,增加了不确定性,可能中间件故障导致消息丢失而第二次删除失败。
容易引发缓存击穿,两次删除缓存会导致大量请求进入数据库。
实际企业开发中使用较少。
订阅binlog方案
通过订阅binlog来更新缓存,该方案可以确保更新的是最新变更的数据。大厂一般用该方案。
通过监听binlog的变化来异步更新缓存,该方案防止了读取到旧数据的问题。
分布式锁+版本号方案
该方式通过对比版本号来防止更新到旧数据。在数据库存储数据的版本号,并且在缓存缓存数据的版本号。在更新的时候加分布式锁防止并发更新,查询缓存中的版本号和查到的数据的版本号进行对比,如果缓存的版本号小于数据库的版本号则更新吗,这样也可以防止更新旧数据到缓存。
什么是Redis的大key问题?
Redis的大key问题本质上是Redis的key对应的值太大了,比如字符串类型的值到了5M或者集合类型数量集合内元素量达到了5000.
大key问题有什么影响?
- 内存占用高,容易引发OOM;
- 大key数据操作延迟高,像持久化、迁移等;
- 读取的大key时候会阻塞其他操作。
出现Redis大key问题该如何处理?
处理Redis大key问题可以分成两部分,一部分是处理已经产生的大key,一部分是预防。
对于已经产生的大key,首先可以通过一些工具来找出这些大key,比如使用redis-cli --bigkeys结合MEMORY USAGE key可以查看某个key对应的值的大小。
- 拆分大key,将大key的值拆分来降低单个值的大小。字符串类型可以通过固定_后缀拆分成子key存储,哈希类型可以按照字段哈希进行分片,集合类型可以按照数量进行分片。
- 拆分之后的值可能还是大,可以存储值之前使用压缩算法压缩值,进一步降低值的大小。
- 采用异步删除来删除key,这样可以防止阻塞,采用unlink,不要采用del。
- 设置合适的过期时间,特别是临时性大key
- 针对集合类型的数据结构,记得要清除无用数据,避免集合越来越大。
预防方面,在设计初期就预测key的大小,如果可能发展成大key的键,在设计上可以直接采用上述设计。