缓存的用途
高性能
用户直接访问数据库时,需要从硬盘上读取数据,速度较慢。如果将用户查询数据库中的数据存在缓存中,下次可以直接从内存的缓存中读取,性能更高。
高并发
直接操作缓存能承受的请求远远大于直接访问数据库,将数据库中的数据存放在缓存中能显著提高系统的并发能力
常见数据类型
Redis常见的数据结构包括:String、List、Set、SortedSet、Hash、HyperLogLog
Redis支持的数据结构可以与Java中对应的类来理解,String对应Object类,因为任意对象都可以以string的形式来存储。List数据结构对应java.util.List接口的实现类LinkedList,Set数据结构对应HashSet类,SortedSet对应SortedSet接口,Hash数据结构对应HashMap类。
string:最基础的数据类型,Redis中String类型的value最多可容纳数据长度为512M。适用于value较小、模型简单的value。
List:按插入顺序排序的字符串链表。可以在头部(left)和尾部(right)添加新元素。头尾添加元素效率很高,中间插入效率较低。适用于新闻列表、评论列表等列表类型数据存储。
Set:无序字符串集合,不允许出现重复元素,多次添加相同元素,Set只保存一份数据。适用于存放唯一性数据,如统计访问IP
SortedSet:与Set相似,都是不重复字符串集合。SortedSet中每个成员都有一个分数(score)与之关联,Redis根据分数为集合中的元素进行从小到大的排序。尽管SortedSet中的成员必须是唯一的,但分数却可以重复。在SortedSet中添加、删除或更新元素的事件复杂度为O(logn)。适用于积分排行榜等需要排序的场景,SortedSet中元素个数不要超过1w。
Hash:用于存储值对象数据,如User对象的Username、Password、Age等属性,可以部分更新、获取,提高效率。
HyperLogLog:用于基数统计,可以使用少量固定大小的内存统计集合中唯一元素的数量(每个HyperLogLog占用12KB内存,可统计$2^{64}$个不同元素。HyperLogLog得到的基数统计不是精确值,而是一个带有0.81%标准差(standard error)的近似值。适用于统计精确度要求不高的场景,如网站的uv。
Redis和memcached的区别
数据类型
memcached仅支持字符串类型,redis支持五种常见数据结构
数据持久化
redis支持两种持久化策略:RDB快照和AOF日志,memcached不支持持久化
分布式
memcached不支持分布式,只能通过在客户端使用一致性hash来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点
redis cluster实现了分布式的支持
内存管理机制
redis中并不是所有数据都一直存储在内存中,可以将一些很久没用的value交换到磁盘,memcached的数据会一直在内存中
memcached将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。这种方式会导致内存利用率不高
性能
redis只使用单核,memcached可以使用多核,平均每个核上存储小数据时redis比memcached性能更好,在100k以上的数据中,memecached性能高于redis。
持久化机制
Redis支持两种数据持久化方式:RDB快照和AOF日志。两种持久化方式可以单独使用,但通常会将两者结合起来。
RDB持久化
将Redis在内存的数据库记录定时dump到磁盘上的RDB的持久化。指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF(append only file)持久化
将redis的操作日志以追加的方式写入文件,以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本方式记录,可以打开文件看到详细的操作记录。
过期时间
设置过期时间后,redis通过两种手段对过期key进行删除:定期删除和惰性删除
定期删除:redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。
惰性删除:定期删除可能导致很多过期key到了时间并没有被删除掉,所以就有了惰性删除。惰性删除是在查询时检查key是否已过期,如果过期才删除。
淘汰机制
由于过期时间的定期删除和惰性删除并不能保证删除所有过期数据,如果大量过期key堆积在内存中,会导致内存很快耗尽,这时需要redis的淘汰机制。
redis提供了6种淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(最常用)
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-eviction:禁止驱逐数据,当内存不足以容纳新写入数据时,新写入操作会报错
4.0版本后增加以下两种:
volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
缓存问题解决
缓存雪崩
缓存同一时间大面积失效或部分redis机器宕机,导致所有请求都去查数据库,进而导致数据库CPU和内存负载过高,甚至宕机。为了解决缓存雪崩的问题,可以采取以下几种方法:
采用高可用缓存
缓存层设计成高可用,防止缓存大面积故障。即使个别节点、个别机器甚至机房宕掉,依然可以提供服务。例如Redis Sentinel和Redis Cluster都实现了高可用。
缓存降级
使用ehcache等本地缓存存放部分数据,或对源服务进行限流、熔断、降级等,保证系统核心服务可用
备份和预热
redis数据备份和恢复,快速预热缓存,设置随机过期时间
缓存穿透
查询一个不存在的数据,如redis没有命中,需要从mysql数据库查询,查不到数据则不写入缓存,将导致这个不存在的数据每次请求都到数据库去查,造成缓存穿透。为了解决缓存穿透,可以设置一个默认值到缓存,第二次从缓存中查询数据就有值了,不用访问数据库。设置一个过期时间或者有值的时候将缓存中的值替换掉即可。另外可以在查询前过滤不符合规则的查询。
数据一致性
只读缓存一般没有什么问题,一旦涉及数据更新,就可能出现缓存和数据库间的数据一致性问题。
可以通过设置过期时间保证最终一致性。所有写操作以数据库为准,只要达到缓存过期时间,后面的读请求会从数据库中读取然后写入缓存。
使用binlog同步的方式,先将全部数据一次性写入redis,然后mysql发生增删改时通过binlog对redis中的数据进行更新
线程模型
redis是纯内存操作,CPU不是redis的瓶颈,它的瓶颈最可能是机器内存的大小或网络带宽。既然单线程容易实现并且CPU不会成为瓶颈,就顺理成章地采用单线程方案了。
采用单线程的好处
- 不需要各种锁的性能开销,在单线程场景下,不用考虑加锁释放锁的操作,也不用考虑可能出现死锁导致的性能消耗
- 采用单线程避免了不必要的上下文切换和竞争条件,也不存在多进程或多线程导致的切换而消耗CPU。需要发挥多核CPU性能时可以通过在单机开多个Redis实例。
redis内部使用文件事件处理器file event handler,这个事件处理器是单线程的,所以redis才叫做单线程的模型。它采用IO多路复用机制,同时监听多个socket,将产生的socket压入内存队列中,事件分派器根据socket上的事件类型来选择对应的事件处理器进行处理。文件事件处理器包含4个部分:
- 多个socket
- IO多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但IO多路复用程序会监听多个socket,将产生事件的scoket放入队列中排队,事件分派处理器每次从队列中取出一个socket,根据socket的事件类型交给对应的事件处理器进行处理。
redis 高可用
Redis 中实现高可用的手段包括持久化、复制、哨兵和集群。
持久化:持久化是最简单的高可用方法,它的作用是数据备份,即将数据存在硬盘上,保证数据不会因为进程退出而丢失
复制:复制是高可用 Redis 的基础,哨兵和集群都是在复制的基础上实现高可用的。复制主要实现了数据的多机备份以及对读操作的负载均衡和简单的故障恢复。缺陷是故障恢复无法自动化、写操作无法负载均衡、存储能力受到单机限制
哨兵:在复制基础上,哨兵实现了自动化的故障恢复,缺陷是写操作无法负载均衡,存储能力受到单机限制
集群:通过集群,Redis 解决了写操作无法负载均衡以及存储能力受到单机限制的问题,实现了较为完善的高可用方案
Redis Sentinel 是 Redis 高可用的实现方案,Sentinel 是一个管理多个 redis 实例的工具,可以实现对 Redis 的监控、通知、自动故障转移。Redis Sentinel 的基本概念如下:
基本名词说明:
基本名词 | 逻辑结构 | 物理结构 |
---|---|---|
Redis数据节点 | 主节点和从节点 | 主节点和从节点的进程 |
主节点(master) | Redis主数据库 | 一个独立的Redis进程 |
从节点(slave) | Redis从数据库 | 一个独立的Redis进程 |
Sentinel节点 | 监控Redis数据节点 | 一个独立的Sentinel进程 |
Sentinel节点集合 | 若干Sentinel节点的抽象组合 | 若干Sentinel节点进程 |
Redis Sentinel | Redis高可用实现方案 | Sentinel节点集合和Redis数据节点进程 |
应用客户端 | 泛指一个或多个客户端 | 一个或者多个客户端进程或者线程 |
Redis Sentinel主要功能
Sentinel 的主要功能包括主节点存活检测、主从运行情况检测、自动故障转移(failover)、主从切换,sentinel 最小配置为一主一从。sentinel 系统可以管理多个 Redis 服务器并执行以下任务:
监控:不断检查主服务器和从服务器是否正常运行
通知:当被监控的某个 Redis 服务器出现问题时,sentinel 通过 API 脚本向管理员或其它应用程序发送通知
故障自动转移:当从节点不能正常工作时,sentinel 会开始一次自动的故障转移操作,将与主节点是主从关系的其中一个从节点升级为新的主节点,并将其它从节点指向新的主节点
配置提供者:Redis sentinel 模式下,客户端应用在初始化时连接的是 sentinel 节点集合,从中获取主节点信息
主观下线和客观下线
默认情况下,每个 sentinel 节点以每秒一次的频率对 redis 节点和其它 sentinel 节点发送 ping 命令,并通过节点的回复来判断节点是否在线
主观下线
主观下线适用于所有的主节点和从节点,如果在 down-after-milliseconds 毫秒内,sentinel 没有收到目标节点的有效回复,会判定该节点为主观下线
客观下线
客观下线只适用于主节点,如果主节点出现故障,sentinel 节点会通过 sentinel is-master-down-by-addr 命令,向其它 sentinel 节点询问对该节点的状态判断。如果超过
常见问题
1. 热点 Key
Redis 缓存中可能存在一些访问量非常大的数据,流量超过数据所在主机的网卡上限,导致缓存分片服务被打垮。再有请求参数时,会打到 DB 上将 DB 压垮。
热点数据发现:
- 对一个周期内数据访问进行统计,将达到一定请求量级的数据当做热点数据。
- 根据业务场景,如大促商品数据为热点数据
- Redis自带命令查询:Redis4.0.4版本提供了
redis-cli –hotkeys
就能找出热点Key
如果要用Redis自带命令查询时,要注意需要先把内存逐出策略设置为allkeys-lfu或者volatile-lfu,否则会返回错误。进入Redis中使用
config set maxmemory-policy allkeys-lfu
即可。
解决方案:
- 将热点数据缓存到服务端内存中(可采用 redis 事件监听机制保证一致性)
- 将热点 key + 随机数,均匀分配到Redis 其它主机上