1. Redis 是单线程的, 为什么快?
- 单线程没有线程上下文切换问题, 没有资源竞争
- 采用IO多路复用的方式并发处理任务
- 纯内存操作, 没有磁盘IO限制
- CPU 不是 Redis 的使用瓶颈, 一般内存和网络带宽才可能是.
2. Redis 和其他 key-value 存储有什么不同
- 所有命令都是原子性操作
- 数据结构多样, 且对使用者透明
- 运行在内存中, 可以持久化到磁盘
3. Redis 数据结构有哪些?
- string
- hash 表, H开头的命令
- list
- set
- zset 有序集合
4. Redis 慢怎么办?
延迟- 查看慢日志
1
2
3
4config set slowlog-log-slower-than 0 # 设置慢日志门槛, 到过这个值就会记录到慢日志中, 单位微秒
config set slowlog-man-len 100 # 存储的慢日志数量
slowlog show - 检查最大内存策略导致
对大对象进行删除, 过期和淘汰操作 (由于最大内存策略导致) 都属于阻塞操作
如果经常对大对象进行删除, 过期和淘汰操作, 可尝试把大对象拆分成多个小对象.1
config get maxmemory-policy
5. Redis 分布式锁
- 加锁
SET $resource_id $token EX $expired_time NX
resource_id: 锁名称, 用于标识资源的唯一ID
token: 随机值, 如果锁需要重入, 或者手动释放锁, 这个值必须保持一致.
expired_time: 锁自动过期时间
- 解锁
不使用 DEL 命令来释放锁, 而是发送一个 Lua 脚本, 这个脚本只在客户端传入的值和键的口令串相匹配时, 才对键进行删除.
比较 token 是否一致, 如果一致则可以解锁, 如果不一致, 说明锁已经被其他客户端获取了, 不能再操作.
1 | if redis.call("get",KEYS[1]) == ARGV[1] |
这个脚本可以通过 EVAL ...script... 1 resource_id token 命令来调用.
6. 常见问题
6.1. 缓存穿透
缓存穿透是指查询一定不存在的数据, 由于缓存没有命中时需要查询数据库, 查不到数据则不写入缓存, 这将导致这个不存在的数据每次请求都需要去数据库查询, 造成缓存穿透.
解决办法:
- 对所有可能查询的参数以hash形式存储, 在控制层先进行校验, 不符合则丢弃.
- 采用布隆过滤器, 将所有可能存在的数据哈希到一个足够大的bitmap中, 一个一定不存在的数据会被这个bitmap拦截掉, 从而避免了对底层存储系统的查询压力.
- 也可以采用一个更为简单粗暴的方法, 如果一个查询返回的数据为空 (不管是数 据不存在, 还是系统故障) , 我们仍然把这个空结果进行缓存, 但它的过期时间会很短, 最长不超过五分钟.
6.2. 缓存雪崩
如果缓存集中在一段时间内失效, 发生大量的缓存穿透, 所有的查询都落在数据库上, 造成了缓存雪崩.
这个没有完美解决办法, 但可以分析用户行为, 尽量让失效时间点均匀分布.大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程 (进程) 写, 从而避免失效时大量的并发请求落到底层存储系统上.
解决办法:
- 在并发量不高的情况下, 可以使用加锁排队, 但是这样会影响系统吞吐量.
- 提前更新过期时间, 如果发现缓存即将失效, 则重新reload, 当然需要控制reload的数量
- 不同的key, 设置不同的过期时间, 让缓存失效的时间点尽量均匀
6.3. 缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据 (一般是缓存时间到期) , 这时由于并发用户特别多, 同时读缓存没读到数据, 又同时去数据库去取数据, 引起数据库压力瞬间增大, 造成过大压力
- 设置热点数据永远不过期.
- 查询时加互斥锁, 保证同一时间只有一个线程区查询; 没有获取到锁的线程则轮询等待获取数据
6.4. 缓存一致性
场景: 在分布式系统中, 有一个频繁更新的数据要做缓存, 该怎么处理?
方案: 先更新数据库, 之后直接淘汰内存. 那么其他线程查询缓存时, 发现没有命中, 则通过查询数据库获取并缓存结果.
- 使用直接淘汰, 而不是更新缓存, 是为了防止高并发下更新缓存的顺序问题; 而如果要保证更新的顺序性, 需要有更新数据的版本, data + version 同时更新, 会有原子性的问题.
- 查询数据库需要考虑缓存击穿问题. 通过分布式锁保证只有一个线程去查询, 其他线程自旋等待.
- 如果要考虑数据更新完后, 还没来得及淘汰缓存, 就服务崩溃的问题. 也就是说不能等到下次更新数据时更新缓存. 那么这种依赖事务的数据不要放在缓存中. 如果要保证缓存一致性, 需要保证业务能幂等重试.
6.5. 什么样的数据能放在缓存中
- 业务完全依赖缓存中的数据, 如果缓存中没有数据则无法工作, 或者能够通过查询数据库恢复的数据.
- 缓存数据不一致不会影响业务.