redis-01–理论总结
一、 简介
1.1、功能
1.1.1、高性能(目前已知性能最快)
1. 读速度:110000 次 /s
2. 写速度:81000 次 /s
1.1.2、key-value 类型的内存数据库
单个value的最大限制是1GB
1.1.3、Redis运行在内存中,读写性能快
1.1.4、支持持久化
1. 周期性的把更新的数据 写入磁盘(RDB)
2. 周期性的把更新的数据 写入追加的记录文件(AOF)。
1.1.5、支持的数据类型
1. string(字符串)
2. list(链表)
3. set(集合)
4. zset(sorted set --有序集合)
5. hash(哈希类型)
6. Bitmaps(位运算)
7. HyperLogLog(基数操作)
8. Geospatial(经纬度操作)
1.1.6、原子性
1. 单个操作是原子性的。
2. 多个操作也支持事务,即原子性
1.1.7、支持事务(原子性)
1. 这里的事物是指多个操作放到一个队列,按照顺序执行。和数据库的事物不是一回事。
2. 执行阶段,事务中任意命令执行失败,其余命令依然被执行
1. Redis 事务不保证原子性,也不支持回滚(带验证)
3. 事务中的多条命令被一次性发送给服务器,服务器在执行命令期间,不会去执行其他客户端的命令请求。
1.1.8、支持各种不同方式的排序
1.1.9、支持数据备份
master-slave(主从)模式的数据同步
1.2、适用场景
1. 适用在较小数据量的高性能操作和运算上
2. 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写
1.2.1、消息队列服务
使用List来做FIFO双向链表(前一个元素和后一个元素),实现一个轻量级的高性能消息队列服务
1.2.2、排行榜 top n
使用zset 的有序集合
1.2.3、配合关系型数据库做高速缓存
1. 高频次,热门访问的数据,降低数据库IO
1. 将用户基本信息放到redis中,减少对数据库的访问,直接做法就是建立用户的内存模型。
2. 分布式架构,做session共享
1.2.4、发布订阅消息系统
pub/sub 模式
1.2.5、计数器、秒杀
原子性,利用自增方法INCR,DECT
1.2.6、时效性数据,手机验证码
expire 过期
1.2.7、去除大量数据的重复数据
set集合
1.2.8、最新N个数据
通过list实现按照时间排序的数据
二、过期键的策略
2.1、定时删除
缓存过期时间到就删除,创建timer耗CPU
2.2、惰性删除
获取的时候检查,不获取一直留在内存,对内存不友好
2.3、定期删除
CPU和内存的折中方案
三、淘汰策略
3.1、从已经设置过期时间的数据集中
volatile-lru: 挑选最近最少使用的数据淘汰
volatile-ttl: 挑选即将要过期的数据淘汰
volatile-random: 随机挑选数据淘汰
3.2从所有的数据集中
allkeys-lru: 挑选最近最少使用的数据淘汰
allkeys-random:,随机挑选数据淘汰
3.3 no-enviction:禁止淘汰数据
四、数据类型
4.1、String
- String类型是最基本的数据类型,一个key对应一个value。
- String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。
- String类型value最多可以是512MB
4.2、List
- 单键多值
- Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
- 元素是可重复的
- 它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标操作中间的节点性能会较差。
- 适用做消息队列或最新消息排行等功能。
4.3、Hash
-
是一个键值对(key - value)集合。
-
是一个 string 类型的 key 和 value 的映射表
-
特别适合用于存储对象
-
可以对对象某一项属性值进行存储、读取、修改等操作。
通过 key(用户ID) + field(属性标签) 操作对应属性数据
4.4、Zset
- Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。
- 集合的成员是唯一的,但是评分可以是重复了 。
- 因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
- 访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
- 可用作排行榜等场景。
4.5、Bitmaps
- Bitmaps可以实现对位的操作(1位=8字节)
- Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value), 但是它可以对字符串value的位进行操作。可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
4.6、HyperLogLog
- Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
- 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
4.7、Geospatial
- 经纬度类型
- 提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。
五、Redis高可用架构
5.1、持久化
5.1.1、RDB
- 定期将存储的数据生成快照并存储到磁盘上,
- 可以将快照复制到其他服务器从而创建具有相同数据的服务器副本。
- 系统故障,会丢失最后一次创建快照之后的数据。
- 数据量大,保存快照的时间会很长。
5.1.2、AOF
- 将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再执行一遍,就可以实现数据恢复。
- 将写命令添加到 AOF 文件(append only file)末尾。
- 对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。
- AOF有重写的特性,去除AOF文件中的冗余写命令。
- redis重启的话,则会优先采用AOF方式来进行数据恢复(aop数据更完整)
- 需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。
5.1.2.1、同步选项
always
- 每个写命令都同步
- 会严重减低服务器的性能
eyerysec
- 每秒同步一次
- 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,且每秒执行一次同步对服务器几乎没有任何影响。
no
- 让操作系统来决定何时同步
- 并不能给服务器性能带来多大的提升
- 会增加系统崩溃时数据丢失的数量
5.2、复制
为了解决单点数据库问题(主从和主备两种模式),会把数据复制多个副本部署到其他节点上, 实现Redis的高可用性,保证数据和服务的高度可靠性。
主备(keepalived)模式
主机备机对外提供同一个虚拟IP,客户端通过虚拟IP进行数据操作,正常期间主机一直对外提供服务,宕机后VIP自动漂移到备机上。
主从模式
当Master宕机后,通过选举算法从slave中选举出新Master继续对外提供服务,主机恢复后以slave的身份重新加入,此模式下可以使用读写分离,如果数据量比较大,不希望过多浪费机器,还希望在宕机后,做一些自定义的措施,比如报警、记日志、数据迁移等操作,推荐使用主从方式,因为和主从搭配的一般还有个管理监控中心(哨兵)。
5.3、 数据通信过程
5.3.1 、Redis2.8之前同步
①从数据库向主数据库发送sync(数据同步)命令。
②主数据库接收同步命令后,会保存快照,创建一个RDB文件。
③当主数据库执行完保持快照后,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。
④主数据库将缓冲区的所有写命令发给从服务器执行。
⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。可以同步发送也可以异步发送,同步发送可以不用每台都同步,可以配置一台master,一台slave,同时这台salve又作为其他slave的master。异步方式无法保证数据的完整性,比如在异步同步过程中主机突然宕机了,也称这种方式为数据弱一致性。
注意:在Redis2.8之后,主从断开重连后会根据断开之前最新的命令偏移量进行增量复制。
5.3.2 、 Redis2.8之后同步
5.4 、哨兵
当主节点出现故障时,由哨兵自动完成故障发现和转移,并通知应用方,实现高可用性。
5.4.1 、Redis哨兵主要功能
- 集群监控:负责监控Redis master和slave进程是否正常工作
- 消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
- 故障转移:如果master node挂掉了,会自动转移到slave node上
- 配置中心:如果故障转移发生了,通知client客户端新的master地址
5.4.2 、Redis哨兵的高可用
原理
- 哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。
- 同时哨兵节点之间也互相通信,交换对主从节点的监控状况。
哨兵用来判断节点是否正常的依据
每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。
主节点down掉依据(主观下线和客观下线)
主观下线
一个哨兵节点判定主节点down掉是主观下线
客观下线
只有半数哨兵节点都判定主观下线,就会会判定主节点客观下线
补充
基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。
5.5 、 集群
高可用性:在主机挂掉后,自动故障转移,使前端服务对用户无影响。
读写分离:将主机读压力分流到从机上。
写压力分摊:将写压力分流到多个主机上。
5.5.1、小数据到大数据过程
问题1
缓存数据量不断增加时,单机内存不够使用
解决方案
把数据切分不同部分,分布到多台服务器上。 可在客户端对数据进行分片,数据分片算法详见一致性Hash详解、虚拟桶分片。
问题2
数据量持续增加时,越来越多的客户端直接访问Redis服务器难以管理,而造成风险
解决方案
根据不同场景下的业务申请对应的分布式集群。
加入了代理服务(Codis和Twemproxy),通过代理访问真实的Redis服务器进行读写
代理服务(Codis和Twemproxy)
在代理层做安全措施,比如限流、授权、分片,避免客户端越来越多的逻辑代码,不但臃肿升级还比较麻烦。
代理层无状态,可任意扩展节点,对于客户端来说,访问代理跟访问单机Redis一样。
5.5.2、Redis官网的集群架构
5.5.2.1、原理
客户端与Redis(Master)节点直连,不需要中间Proxy层,根据公式HASH_SLOT=CRC16(key) mod 16384,计算出映射到哪个分片上,然后Redis会去相应的节点进行操作
5.5.2.2、优点
- 无需Sentinel哨兵监控,如果Master挂了,自动将Slave切换Master
- 可以进行水平扩容
- 支持自动化迁移
- 当出现某个Slave宕机了,那么就只有Master了,这时候的高可用性就无法很好的保证了,万一Master也宕机了,咋办呢? 针对这种情况,如果说其他Master有多余的Slave ,集群自动把多余的Slave迁移到没有Slave的Master 中。
5.5.2.3、缺点
- 批量操作是个坑,不同的key会划分到不同的slot中,因此直接使用mset或者mget等操作是行不通的。
- 解决方案:使用Hashtag保证这些key映射到同一台Redis节点上。
- 资源隔离性较差,容易出现相互影响的情况。
六、Redis高并发及热key解决之道
6.1、并发设置key
问题
集群环境下的定时任务,存在A服务器执行任务t1,B服务器也执行了任务t1,如果t1是创建订单,那么就会出现重复订单。
解决方案
1. 分布式锁
2. 使用消息队列,把并行读写进行串行化。
6.2、缓存击穿(热key问题)
问题
瞬间有几十万的请求去访问某个固定的key,从而压垮缓存服务的情况
在缓存失效的瞬间,有大量线程来重建缓存 ( 如下图),造成后端负载加大,甚至可能会让应用崩溃。
热key判断依据
- 凭借业务经验,进行预估哪些是热key
- 在客户端进行收集
- 在Proxy层做收集
- 用redis自带命令(monitor命令、hotkeys参数)
解决方案:
-
利用二级缓存,比如一个HashMap。在你发现热key以后,把热key加载到系统的JVM中。查找的时候先去本地jvm查询,查不到再去redis查询
-
备份热key,不要让key走到同一台redis上。我们把这个key,在多个redis上都存一份。可以用HOTKEY加上一个随机数(N,集群分片数)组成一个新key。
-
热点数据尽量不要设置过期时间,在数据变更时同步写缓存,防止高并发下重建缓存的资源损耗。
-
预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
-
实时调整:现场监控哪些数据热门,实时调整key的过期时长
-
使用分布式锁
- 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
- 先使用SETNX 命令 去set一个mutex key
- 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
- 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。
6.3、缓存穿透
指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层。这样每次请求都要到存储层去查询,从而可能压垮数据库
6.3.1、影响
将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。
6.3.2、缓存穿透原因
- 业务自身代码或者数据出现问题,
- 一些恶意攻击、爬虫等造成大量空命中
6.3.3、解决缓存穿透方案
1)缓存空对象
缓存空对象会有两个问题
- 空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
- 缓存层和存储层的数据会有一段时间窗口的不一致,例如过期时间设置为1分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,更新数据的时候清除掉缓存层中的空对象。
2)布隆过滤器拦截
如下图所示,在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截。如果布隆过滤器认为key不存在,那么就不会访问存储层,在一定程度保护了存储层。
布隆过滤器
https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FBloom_filter
可以利用 Redis 的 Bitmaps 实现布隆过滤器
https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Ferikdubbelboer%2FRedis-Lua-scaling-bloom-filter
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
3)设置可访问的名单(白名单)
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
4)进行实时监控
当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
6.3.4、缓存空对象和布隆过滤器方案对比
6.4、缓存雪崩
6.4.1、现象
- 查询不走缓存,所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。
- 缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,缓存击穿则是某一个key
6.4.2、原因
- 数据未加载到缓存中
- 缓存同一时间大面积的失效
6.4.3、解决方案
6.4.3.1、保证缓存层服务高可用性
缓存层设计成高可用的,即使个别节点,依然可以提供服务,比如 Redis Sentinel 和 Redis Cluster 都实现了高可用。
6.4.3.2、Redis备份和快速预热
- Redis备份:保证master出问题切换为slave迅速能够承担线上实际流量
- 快速预热:保证缓存及时被写入缓存,防止穿透到库。
6.4.3.3、依赖隔离组件为后端限流并降级
作为并发量较大的系统,假如有一个资源(redis)不可用,可能会造成线程全部瘫痪,造成整个系统不可用。
降级在高并发系统中是非常正常的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面是开天窗。
在实际项目中,我们需要对重要的资源 ( 例如 Redis、 MySQL、 Hbase、外部接口 ) 都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。这里推荐一个 Java 依赖隔离工具Hystrix(https://github.com/Netflix/Hystrix)
6.4.3.4、提前演练
在项目上线前,演练缓存层宕掉后,应用或者后端的负载情况,在此基础上做一些预案设定。
6.4.3.5、缓存预热
将相关的缓存数据直接加载到缓存系统。避免上线后在大量用户请求的时候,直接访问数据库
6.4.3.6、构建多级缓存架构
nginx缓存 + redis缓存 +其他缓存(ehcache等)
6.4.3.7、使用锁或队列
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
6.4.3.8、设置过期标志更新缓存:
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
6.4.3.9、将缓存失效时间分散开:
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。