概述
redis 是一个内存的缓存数据库,用于做缓存的。为各个服务做集中缓存服务。支持多种缓存数据结构。如 string、hash、list、set、sortset,也支持简单的发布订阅、和消息队列、lua 脚本自己支持的缓存服务和事务等一些其它服务。
redis 架构模式
单机到集群
-
单机模式
适合于不重要的缓存数据,可以快速重建,失效对系统影响不大的数据。缺点:单点,数据容易丢失。
-
主从模式
能实用于大多的应用场景。主失败后需要人工介入切入从,或者走keepalive ip 漂移。缺点:主redis服务中断,切换时间内redis服务不可用,缓存容量不可用扩展。
-
哨兵模式
1.Redis哨兵主要功能
(1)集群监控:负责监控Redis master和slave进程是否正常工作
(2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
(3)故障转移:如果master node挂掉了,会自动转移到slave node上
(4)配置中心:如果故障转移发生了,通知client客户端新的master地址
2.Redis哨兵的高可用
原理:当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。同时哨兵节点之间也互相通信,交换对主从节点的监控状况。每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。这个就是哨兵用来判断节点是否正常的重要依据,涉及两个新的概念:主观下线和客观下线。一个哨兵节点判定主节点down掉是主观下线,只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。
-
cluster集群模式
集群模式有多种,有的用代理模式加哨兵主从模式,proxy 对请求进行分布。有的也用redis 新的cluster模式,client 用一致性hash + 2 20次方的虚拟节点的哈希分布方式。
数据持久化
Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。Redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。
-
rdb
简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本。如果系统发生故障,将会丢失最后一次创建快照之后的数据。如果数据量大,保存快照的时间会很长
2.AOF
换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把 这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。将写命令添加到 AOF 文件(append only file)末尾
3.配置选项说明
使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。选项同步频率always每个写命令都同步,eyerysec每秒同步一次,no让操作系统来决定何时同步,always 选项会严重减低服务器的性能,everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器几乎没有任何影响。no 选项并不能给服务器性能带来多大的提升,而且会增加系统崩溃时数据丢失的数量。随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。如果你没有数据持久化的需求,也完全可以关闭RDB和AOF方式,这样的话,redis将变成一个纯内存数据库。
常见问题
1.并发设置key及分布式锁
Redis是一种单线程机制的nosql数据库,基于key-value,数据可持久化落盘。由于单线程所以Redis本身并没有锁的概念,多个客户端连接并不存在竞争关系,但是利用jedis等客户端对Redis进行并发访问时会出现问题。比如多客户端同时并发写一个key,一个key的值是1,本来按顺序修改为2,3,4,最后是4,但是顺序变成了4,3,2,最后变成了2。使用分布式锁防止并发设置Key的原理及代码见:使用Redis实现分布式锁及其优化,另外一种方式是使用消息队列,把并行读写进行串行化。
2.热key问题
热key问题说来也很简单,就是瞬间有几十万的请求去访问redis上某个固定的key,从而压垮缓存服务的情情况。其实生活中也是有不少这样的例子。比如XX明星结婚。那么关于XX明星的Key就会瞬间增大,就会出现热数据问题。那么如何发现热KEY呢:
1.凭借业务经验,进行预估哪些是热key
2.在客户端进行收集
3.在Proxy层做收集
4.用redis自带命令(monitor命令、hotkeys参数)
5.自己抓包评估
解决方案:
1.利用二级缓存,比如利用ehcache,或者一个HashMap都可以。在你发现热key以后,把热key加载到系统的JVM中。
2.备份热key,不要让key走到同一台redis上。我们把这个key,在多个redis上都存一份。可以用HOTKEY加上一个随机数(N,集群分片数)组成一个新key。
3.热点数据尽量不要设置过期时间,在数据变更时同步写缓存,防止高并发下重建缓存的资源损耗。可以用setnx做分布式锁保证只有一个线程在重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。
3.缓存穿透
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。造成缓存穿透的基本有两个。第一,业务自身代码或者数据出现问题,第二,一些恶意攻击、爬虫等造成大量空命中,下面我们来看一下如何解决缓存穿透问题。解决缓存穿透的两种方案:
1)缓存空对象
缓存空对象会有两个问题:
第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5 分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
2)布隆过滤器拦截
如下图所示,在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截。如果布隆过滤器认为该用户 ID 不存在,那么就不会访问存储层,在一定程度保护了存储层。有关布隆过滤器的相关知识,可以参考: 布隆过滤器,可以利用 Redis 的 Bitmaps 实现布隆过滤器,GitHub 上已经开源了类似的方案,读者可以进行参考:redis bitmaps实现布隆过滤器
缓存空对象和布隆过滤器方案对比
4.缓存雪崩
数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。
可以从以下几个方面防止缓存雪崩:
1)保证缓存层服务高可用性
和飞机都有多个引擎一样,如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如前面介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。
2)Redis备份和快速预热
Redis备份保证master出问题切换为slave迅速能够承担线上实际流量,快速预热保证缓存及时被写入缓存,防止穿透到库。
3)依赖隔离组件为后端限流并降级
无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang 在这个资源上,造成整个系统不可用。降级在高并发系统中是非常正常的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面是开天窗。
在实际项目中,我们需要对重要的资源 ( 例如 Redis、 MySQL、 Hbase、外部接口 ) 都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。但是线程池如何管理,比如如何关闭资源池,开启资源池,资源池阀值管理,这些做起来还是相当复杂的,这里推荐一个 Java 依赖隔离工具Hystrix(https://github.com/Netflix/Hystrix),如下图所示。
4)提前演练
在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,在此基础上做一些预案设定。
5.缓存预热
缓存预热就是系统上线前,将相关的缓存数据直接加载到缓存系统。这样就可以避免上线后在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
六.Redis内存模型
**Redis为什么这么快?一文深入了解Redis内存模型!
**
参考文章: