为什么要使用redis?
- 数据量增长,读写压力大
- 数据从内存直接读取很块
- 所以把热数据(经常访问的数据)存到内存中(redis),冷数据放在redis中
- 读场景,先去redis找,找不到再找redis
- 写场景:直接写到mysql,监听binlog再写入到reids中,或者同时进行写
redis基本原理
- 数据从内存中读写
- 数据保存到硬盘上防止重启数据丢失
- 增量数据保存到AOF文件
- redis-client通过RESP协议写入服务进程,然后读写内存
- appendonly.aof,写入日志,服务器宕机,redis也能读取出来
Redis 除了做缓存,还能做什么?
- 分布式锁 : 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。
- 限流 :一般是通过 Redis + Lua 脚本的方式来实现限流
- 消息队列 :Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
- 复杂业务场景 :通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。
Redis使用注意事项
大Key、热Key
- 大Key:Value大于10KB就是大Key,使用大Key将导致Redis系统不稳定
消除大Key的方法
- 拆分 将大key拆分为小key。例如一个String拆分成多个String 2 压缩 将value压缩后写入redis,读取时解压后再使用。压缩算法可以是gzip、snappy、lz4等。通常情况下,一个压缩算法压缩率高、则解压耗时就长。需要对实际数据进行测试后,选择一个合适的算法。如果存储的是JSON字符串,可以考虑使用MessagePack进行序列化。
- 集合类结构hash、list、set、set (1)拆分:可以用hash取余、位掩码的方式决定放在哪个key中 (2)区分冷热:如榜单列表场景使用zset,只缓存前10页数据,后续数据走db
- 热Key:一个Key的QPS特别高,将导致Redis实例出现负载突增,负责均衡流量不均的情况。导致单实例故障
解决热Key的方法
- 设置Localcache 在访问Redis前,在业务服务侧设置Localcache,降低访问Redis的QPS。LocalCache中缓存过期或未命中,则从Redis中将数据更新到LocalCache。Java的Guava、Golang的Bigcache就是这类LocalCache
- 拆分 将key:value这一个热Key复制写入多份,例如key1.value, key2value,访问的时候访问多个key,但value是同一个.以此将qps分散到不同实例上,降低负载。代价是,更新时需要更新多个key,存在数据短暂不一致的风险
- 使用Redis代理的热Key承载能力 字节跳动的Redis访问代理就具备热Key承载能力。本质上是结合了"热Key发现"、"LocalCache"两个功能
慢查询
- 慢查询:大Key、热Kye的读写;一次操作过多的Key(mset/hmset/sadd/zadd);导致redis查询慢
- 一次性传入过多的key/value(pipiline)一次最好不要超过100个
- zset命令,大小超过5k以上,也可能慢查询
- 操作的单个value过大
- 对dakey的delete/expire也可能导致慢查询
缓存穿透、缓存雪崩
- 缓存穿透:热点数据查询绕过缓存,直接查询数据库
- 缓存雪崩:大量缓存同时过期
缓存穿透:
- 缓存空值 如一个不存在的userlD。这个id在缓存和数据库中都不存在。则可以缓存一个空值,下次再查缓存直接反空值。
- 布隆过滤器 通过bloom filter算法来存储合法Key,得益于该算法超高的压缩率,只需占用极小的空间就能存储大量key值
缓存雪崩:
- 缓存空值 将缓存失效时间分散开,比如在原有的失效时间基础上增加一个随机值,例如不同Key过期时间, 可以设置为10分1秒过期,10分23秒过期,10分8秒过期。单位秒部分就是随机时间,这样过期时间就分散了。对于热点数据,过期时间尽量设置得长一些,冷门的数据可以相对设置过期时间短一些。
- 使用缓存集群,避免单机宕机造成的缓存雪崩。