Redis
什么是Redis?
Redis,英文全称是Remote Dictionary Server(远程字典服务),是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
作用
与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度非常快,每秒可以处理超过10万次读写操作。因此redis被广泛应用于缓存,另外,Redis也经常用来做分布式锁。除此之外,Redis支持事务、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。
Redis的基本数据结构类型
五种基本类型:
String(字符串)
String是Redis最基础的数据结构类型,它是二进制安全的,可以存储图片或者序列化的对象,值最大存储为512M
数据缓存、共享session、分布式锁,计数器、限流
Hash(哈希)
Hash在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构
内部编码:ziplist(压缩列表) 、hashtable(哈希表)
应用场景:缓存用户信息等。
List(列表)
List列表类型是用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素。
内部编码:ziplist(压缩列表)、linkedlist(链表)
应用场景:消息队列,文章列表。
Set(集合)
Set集合类型也是用来保存多个的字符串元素,但是不允许重复元素。
内部编码:intset(整数集合)、hashtable(哈希表)
应用场景:用户标签、生成随机数抽奖、社交需求(共同好友等)。
Zset(有序集合)
Zset是有序的字符串集合,同时元素不能重复。
内部编码:ziplist(压缩列表)、skiplist(跳跃表)
应用场景:排行榜,社交需求(如用户点赞)。
Redis6三种新的数据结构类型:
- Geospatial (地理信息)
- Hyperloglog (基数统计)
- Bitmaps (二进制位图)
Redis的过期策略
定时过期:
expire key 60:指定60s后过期
每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
Redis采用的是两种折中方案:定期过期和惰性过期两种过期策略
定期过期
- 每隔一定的时间(默认100ms),会随机抽取一些expires字典中的key,并清除其中已过期的key。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
惰性过期
- 定期过期会导致一部分数据在过期后不能被立即删除,这种情况依靠惰性过期来删除数据
- 访问一个key时,先判断该key是否已过期,过期则清除。
- 该策略可以最大化地节省CPU资源
Redis过期策略
(定期过期和惰性过期)的问题:
大量已经过期的键没有被扫描到,仍然保存在内存中
这些键如果长时间不被访问,就不会触发惰性过期,长时间占用内存
为了解决问题,Redis提供了内存淘汰机制
Radis缓存淘汰机制
Redis服务器繁忙时,有大量信息要保存
如果Redis服务器内存全满,再要往Redis中保存新的数据,就需要淘汰老数据,才能保存新数据
Radis缓存淘汰机制:
- noeviction:返回错误**(默认)**
- allkeys-random:所有数据中随机删除数据
- volatile-random:有过期时间的数据库中随机删除数据
- volatile-ttl:删除剩余有效时间最少的数据
- allkeys-lru:所有数据中删除上次使用时间最久的数据
- volatile-lru:有过期时间的数据中删除上次使用时间最久的数据
- allkeys-lfu:所有数据中删除使用频率最少的
- volatile-lfu:有过期时间的数据中删除使用频率最少的
- LRU(Least Recently Used)最近最少使用算法
随机挑选被访问的数据,添加到5个长度(redis默认)的数组中 - LFU(Least Frequently Used)最不经常使用算法
Redis的LRU, LFU和 TTL算法都不是精确的淘汰算法,而是近似算法,为了节省内存。所以redis默认会随机选择5个key,然后从中选择使用最少用的key来移除。 设置5个是比较合适的,10个接近真LRU但是非常消耗CPU,3个很快但不是非常精确
Redis持久化
Redis提供了RDB和AOF两种持久化机制
RDB(Redis Database),
就是把内存数据以快照的形式保存到磁盘上
优点:适合大规模的数据恢复场景,如备份,全量复制等,恢复速度快
缺点:没办法做到实时持久化/秒级持久化,可能丢失一部分新数据。新老版本存在RDB格式兼容问题
AOF(append only file) 持久化
采用日志的形式来记录每个写操作,追加到文件中,重启时再重放AOF文件中的命令来恢复数据
优点:数据的一致性和完整性更高
缺点:AOF记录的内容越多,文件越大,数据恢复越慢
Redis官方建议,采用RDB+AOF混合模式
配置:
aof-use-rdb-preamble yes # redis5.0后默认开启
需要先开启AOF模式: appendonly yes
优点:
RDB保存全量数据
AOF时时持久化避免丢失数据,以增量的方式记录数据操作指令
使用RDB快速批量恢复数据,使用AOF来恢复增量数据
redis的缓存击穿/穿透/雪崩
缓存穿透
数据在Redis缓存和数据库中都不存在。大量访问这种数据时,数据库频繁查找数据,造成压力过大产生故障
- 非正常数据访问
- 黑客攻击
解决方案
- 对不存在的数据缓存空值
- 设置白名单
- 可访问的数据id作为偏移值存入bitmaps
- 访问时先检查bitmaps
- 使用布隆过滤器
- 对黑客攻击进行实时监控
缓存击穿
突发热点访问时,热点数据在Redis缓存中不存在或已过期。大量的对热点数据的访问,都直接访问数据库,造成数据库访问压力短时激增造成故障
解决方案
- 提前预设热门数据缓存
- 实时调整过期时间
- 使用锁缓存数据不存在时,把数据库数据放入缓存
缓存雪崩
大量key集中过期,数据库短时访问量激增
解决方案
- 多级缓存架构(Nginx-本地缓存(ehcache/guava)-Redis)
- 锁或队列对并发访问进行序列化
- Key设置过期标志,对即将过期数据提前进行更新,自动续期
- 数据的过期时间用随机值,分散过期时间
redis热key
- 访问频率高的key,称为热点key
- 某些热点key的请求量特别大,可能会导致Redis服务器资源不足,甚至宕机,从而影响正常的服务。
产生热Key问题的场景:
- 用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
- 请求分片集中,超过单Redis服务器的性能,比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题。
解决热key问题:
- Redis集群扩容:增加分片、增加副本(从机),均衡读流量
- 将热key分散到不同的服务器中
- 使用多级缓存,使用本地缓存,减少Redis的读请求
如何实现Redis的高可用
- 主从模式
- 哨兵模式
- 集群模式
主从模式
- 主从模式中,主机负责读写操作,从机只负责读操作
- 数据添加到主机后,会自动复制到从机
- 全量复制当从机连接到主机时,执行全量复制。主机将RDB文件发送到从机进行数据导入
- 增量复制全量复制后,主机数据再发生变化时就会触发增量复制。主机每一次增删改操作都会将指令发送到从机,从机执行相同的操作
哨兵模式
由来:
主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。显然,多数业务场景都不能接受这种故障处理方式。Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。
- 一般由多个Sentinel实例组成的Sentinel系统
- 它可以监视所有的Redis主节点和从节点
- 主节点下线时,由Sentinel对主节点下线状态进行确认。
- 自动将某个从节点升级为新的主节点
工作机制
- 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他Sentinel实例发送一个 PING命令。
- 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel标记为主观下线。
- 如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
- 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线。
- 在一般情况下, 每个 Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
- 当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
- 若没有足够数量的 Sentinel同意Master已经下线, Master的客观下线状态就会被移除;若Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
Cluster集群模式
-负载均衡,故障切换,主从复制
Redis Cluster要求至少需要3个master才能组成一个集群,同时每个master至少需要有一个slave节点。各个节点之间保持TCP通信。当master发生了宕机, Redis Cluster自动会将对应的slave节点提拔为master,来重新对外提供服务。
Cluster集群模式 – 哈希槽
Redis集群采用哈希槽算法,哈希槽槽位从0到16383,共16384个槽位
每个Redis主机覆盖一段哈希槽
添加key时,首先用哈希槽算法计算该key存放的哈希槽,然后存放到对应的主机
- 一致性哈希算法
- 哈希槽算法
Redis实现分布式锁?
分布式锁
- 我们的系统都是分布式部署的,日常开发中,秒杀下单、抢购商品等等业务场景,为了防库存超卖,都需要用到分布式锁。
- 分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
常见的分布式锁实现,一般有这3种方式:
- 基于数据库实现的分布式锁
- 基于Redis实现的分布式锁
- 基于Zookeeper实现的分布式锁
使用Redis实现分布式锁的原理
- 用Redis实现分布式锁,就像在图书馆占座
- 加锁:在Redis中放一个数据占位
- 解锁:从Redis删除占位数据
Redis实现分布式锁的七种方案
方案1-setnx+expire
方案2-SET的扩展命令(SET key value NX EX|PX time)
方案3-SETNX + value(值是系统时间+过期时间)
方案4-SET EX PX NX + 校验唯一随机值,再释放锁
方案5-使用Lua脚本(包含值的比对和删除)
方案6-多机实现的分布式锁Redlock
方案7-开源框架~Redisson
setnx+expire
加锁:
- setnx key value
- 添加数据成功即加锁成功(占位)
- 添加数据失败即加锁失败
解锁:
- del key
- 删除占位数据即解锁
死锁:
- 长时间不解锁,或忘记解锁就会发生死锁
死锁的解决方法:
- 设置锁的过期时间expire key seconds
SET的扩展命令(SET key value NX EX|PX time)
方案1中为了防止死锁,加锁后要设置过期时间:
- 加锁
- 设置锁的过期时间
可以将两步操作合并为一步,例如:
set mylock 1 nx ex 10 如果锁数据不存在则添加锁,并设置过期时间为10秒
参数含义:nx不存在才能添加成功,ex秒,px毫秒
SETNX + value(值是系统时间+过期时间)
- 在方案1和2中对锁设置了过期时间
- 也可以不在Redis中设置过期,而是在客户端加锁失败时检查过期,其过程如下:
- 加锁失败
- 获取该锁的过期时间
- 如果判断该锁已过期,则先删除锁,再重新加锁
这种方案还可以防止误删除其他会话的锁
在什么情况下会出现误删:
- 加锁成功
- 执行数据操作
- 发生阻塞…
- 锁超时,自动删除
- 阻塞结束后执行解锁,删除锁
在删除之前可以先检查值是不是自己设置的值,如果不是,则不能执行删除
SET EX PX NX + 校验唯一随机值,再释放锁
与方案3相同,可以避免删除其他会话的锁
锁的值使用随机字符串,例如uuid
设置自动过期
使用Lua脚本(包含值的比对和删除)
- 在方案3和4中,删除锁时,需要先比对值一致才能删除,这是两步操作:
获取值,进行比对 - 删除
这两步数据操作,可能会出现在两步执行的中间,锁自动过期,进而删除其他会话的锁的情况:
多机实现的分布式锁(Redlock)
Redis一般会做主从复制来提高可用性
当主机中添加了锁,但是还没同步到从机。恰好这时,主机发生故障,一个从机就会升级为主机。但是,新的主机上不存在锁,那么其他会话就可以添加锁。
为了解决这个问题,Redis作者antirez提出一种高级的分布式锁算法:Redlock。Redlock核心思想是这样的:
用多个Redis master部署,以保证它们不会同时宕机。并且这些master节点是完全相互独立的,相互之间不存在数据同步。同时,需要确保在这多个master实例上,是与在Redis单实例,使用相同方法来获取和释放锁。
假设当前有5个Redis master节点
- 按顺序向5个master节点请求加锁
- 根据设置的超时时间来判断,是不是要跳过该master节点。
- 如果大于等于3个节点加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。
- 如果获取锁失败,解锁!
开源框架~Redisson
Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发
Redisson 提供了多种分布式锁实现:
- 可重入锁
- 公平锁
- 联锁
- 红锁
- 读写锁
Redis与数据库数据一致策略
延迟双删策略
- 删除缓存
- 修改数据库数据
- 延迟3到5秒
,并且使用的时间小于锁的有效期,即可认定加锁成功啦。
- 如果获取锁失败,解锁!
开源框架~Redisson
Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发
Redisson 提供了多种分布式锁实现:
- 可重入锁
- 公平锁
- 联锁
- 红锁
- 读写锁
Redis与数据库数据一致策略
延迟双删策略
- 删除缓存
- 修改数据库数据
- 延迟3到5秒
- 再次删除缓存