一、 简介
- 高性能(目前已知性能最快)
- 读速度:110000 次 /s
- 写速度:81000 次 /s
- key-value(单个value的最大限制是1GB)类型的内存数据库
- 数据库在内存中进行操作
- 支持数据持久化
- 定期异步操作将数据库数据flush到硬盘上
- 支持string,list,set,sorted set,hash
- 操作都是原子性
- 支持事务
- 对数据的更改要么全部执行,要么全部不执行
- 事务中任意命令执行失败,其余命令依然被执行(Redis 事务不保证原子性,也不支持回滚)
- 事务中的多条命令被一次性发送给服务器,服务器在执行命令期间,不会去执行其他客户端的命令请求
- 支持数据备份( master - slave 模式的数据备份)
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写
- 适合的场景局限在较小数据量的高性能操作和运算上
二、 可实现的功能
2.1 消息队列服务
用List来做FIFO双向链表(前一个元素和后一个元素),实现一个轻量级的高性能消息队列服务
2.2 tag系统
Set可以做高性能的tag系统
2.3 缓存
高并发情况下,将用户基本信息放到redis中,减少对数据库的访问,直接做法就是建立用户的内存模型
存储用户会话缓存信息
2.4 消息(支持 publish/subscribe 通知)
2.5 排行榜/计数器
计数器:Redis在内存中对数字进行递增或递减的操作实现的非常好。
排行榜:有序集合(Sorted Set)通过分数来实现
三、淘汰策略
3.1从已经设置过期时间的数据集中
volatile-lru: 挑选最近最少使用的数据淘汰
volatile-ttl: 挑选即将要过期的数据淘汰
volatile-random: 随机挑选数据淘汰
3.2从所有的数据集中
allkeys-lru: 挑选最近最少使用的数据淘汰
allkeys-random:,随机挑选数据淘汰
3.3 no-enviction:禁止淘汰数据
四、过期键的策略
4.1定时删除
缓存过期时间到就删除,创建timer耗CPU
4.2惰性删除
获取的时候检查,不获取一直留在内存,对内存不友好
4.3定期删除
CPU和内存的折中方案
五、数据类型分析
5.1 string
最基本的数据类型。
二进制安全的,可以包含任何数据。
最大能存储 512 MB。
5.2 hash
- 一个键值对(key - value)集合。
- 一个 string 类型的 key 和 value 的映射表,
- 适合用于存储对象
- 可以对对象某一项属性值进行存储、读取、修改等操作。
5.3 list
- 是简单的字符串列表(集合)。
- 按照插入顺序排序。我们可以网列表的左边或者右边添加元素。
- 元素是可重复的。
- 适用做消息队列或最新消息排行等功能。
5.4 set
- 无序
- 通过哈希表实现,因此添加、删除、查找的复杂度都是 O(1)。
- 是一个 key 对应着多个字符串类型的 value的集合
- 集合元素不能重复
- 可以统计访问网站的所有独立ip。
5.5 Zset
- 和set 一样
- 不同的是zset 每个元素都会关联一个 double 类型的分数,通过分数来为集合中的成员进行从小到大的排序。
- value元素是唯一的,但是分数(score)却可以重复。
- 可用作排行榜等场景。
六、Redis高可用架构
6.1、持久化
6.1.1、RDB
- 定期将存储的数据生成快照并存储到磁盘上,
- 可以将快照复制到其他服务器从而创建具有相同数据的服务器副本。
- 系统故障,会丢失最后一次创建快照之后的数据。
- 数据量大,保存快照的时间会很长。
6.1.2、AOF
- 将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再执行一遍,就可以实现数据恢复。
- 将写命令添加到 AOF 文件(append only file)末尾。
- 对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。
- AOF有重写的特性,去除AOF文件中的冗余写命令。
- redis重启的话,则会优先采用AOF方式来进行数据恢复(aop数据更完整)
- 需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。
6.1.2.1、同步选项
always
- 每个写命令都同步
- 会严重减低服务器的性能
eyerysec
- 每秒同步一次
- 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,且每秒执行一次同步对服务器几乎没有任何影响。
no
- 让操作系统来决定何时同步
- 并不能给服务器性能带来多大的提升
- 会增加系统崩溃时数据丢失的数量
6.2、复制
为了解决单点数据库问题(主从和主备两种模式),会把数据复制多个副本部署到其他节点上, 实现Redis的高可用性, 保证数据和服务的高度可靠性。
主备(keepalived)模式
主机备机对外提供同一个虚拟IP,客户端通过虚拟IP进行数据操作,正常期间主机一直对外提供服务,宕机后VIP自动漂移到备机上。
主从模式
当Master宕机后,通过选举算法从slave中选举出新Master继续对外提供服务,主机恢复后以slave的身份重新加入,此模式下可以使用读写分离,如果数据量比较大,不希望过多浪费机器,还希望在宕机后,做一些自定义的措施,比如报警、记日志、数据迁移等操作,推荐使用主从方式,因为和主从搭配的一般还有个管理监控中心(哨兵)。
6.3、 数据通过过程
6.3.1 、Redis2.8之前同步
- ①从数据库向主数据库发送sync(数据同步)命令。
- ②主数据库接收同步命令后,会保存快照,创建一个RDB文件。
- ③当主数据库执行完保持快照后,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。
- ④主数据库将缓冲区的所有写命令发给从服务器执行。
- ⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。可以同步发送也可以异步发送,同步发送可以不用每台都同步,可以配置一台master,一台slave,同时这台salve又作为其他slave的master。异步方式无法保证数据的完整性,比如在异步同步过程中主机突然宕机了,也称这种方式为数据弱一致性。
- 注意:在Redis2.8之后,主从断开重连后会根据断开之前最新的命令偏移量进行增量复制。
6.3.2 、 Redis2.8之后同步
6.4 、哨兵
当主节点出现故障时,由哨兵自动完成故障发现和转移,并通知应用方,实现高可用性。
6.4.1 、Redis哨兵主要功能
集群监控:负责监控Redis master和slave进程是否正常工作
消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
故障转移:如果master node挂掉了,会自动转移到slave node上
配置中心:如果故障转移发生了,通知client客户端新的master地址
6.4.2 、Redis哨兵的高可用
原理
- 哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。
- 同时哨兵节点之间也互相通信,交换对主从节点的监控状况。
哨兵用来判断节点是否正常的依据
每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。
主节点down掉依据(主观下线和客观下线)
主观下线
一个哨兵节点判定主节点down掉是主观下线
客观下线
只有半数哨兵节点都判定主观下线,就会会判定主节点客观下线
补充
基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。
6.5 、 集群
高可用性:在主机挂掉后,自动故障转移,使前端服务对用户无影响。
读写分离:将主机读压力分流到从机上。
6.5.1、小数据到大数据过程
问题
缓存数据量不断增加时,单机内存不够使用
解决方案
把数据切分不同部分,分布到多台服务器上。 可在客户端对数据进行分片,数据分片算法详见一致性Hash详解、虚拟桶分片。
问题
数据量持续增加时,越来越多的客户端直接访问Redis服务器难以管理,而造成风险
解决方案
根据不同场景下的业务申请对应的分布式集群。
加入了代理服务(Codis和Twemproxy),通过代理访问真实的Redis服务器进行读写
代理服务(Codis和Twemproxy)
在代理层做安全措施,比如限流、授权、分片,避免客户端越来越多的逻辑代码,不但臃肿升级还比较麻烦。
代理层无状态,可任意扩展节点,对于客户端来说,访问代理跟访问单机Redis一样。
6.5.2、Redis官网的集群架构
6.5.2.1、原理
客户端与Redis(Master)节点直连,不需要中间Proxy层,根据公式HASH_SLOT=CRC16(key) mod 16384,计算出映射到哪个分片上,然后Redis会去相应的节点进行操作
6.5.2.2、优点
- 无需Sentinel哨兵监控,如果Master挂了,自动将Slave切换Master
- 可以进行水平扩容
- 支持自动化迁移
- 当出现某个Slave宕机了,那么就只有Master了,这时候的高可用性就无法很好的保证了,万一Master也宕机了,咋办呢? 针对这种情况,如果说其他Master有多余的Slave ,集群自动把多余的Slave迁移到没有Slave的Master 中。
6.5.2.3、缺点
- 批量操作是个坑,不同的key会划分到不同的slot中,因此直接使用mset或者mget等操作是行不通的。
- 解决方案:使用Hashtag保证这些key映射到同一台Redis节点上。
- 资源隔离性较差,容易出现相互影响的情况。
七、Redis高并发及热key解决之道
7.1、并发设置key及分布式锁
问题
集群环境下的定时任务,存在A服务器执行任务t1,B服务器也执行了任务t1,如果t1是创建订单,那么就会出现重复订单。
解决方案
- 分布式锁
- 使用消息队列,把并行读写进行串行化。
7.2、热key问题
问题
瞬间有几十万的请求去访问某个固定的key,从而压垮缓存服务的情况
热key判断依据
- 凭借业务经验,进行预估哪些是热key
- 在客户端进行收集
- 在Proxy层做收集
- 用redis自带命令(monitor命令、hotkeys参数)
解决方案:
- 利用二级缓存,比如一个HashMap。在你发现热key以后,把热key加载到系统的JVM中。查找的时候先去本地jvm查询,查不到再去redis查询
- 备份热key,不要让key走到同一台redis上。我们把这个key,在多个redis上都存一份。可以用HOTKEY加上一个随机数(N,集群分片数)组成一个新key。
- 热点数据尽量不要设置过期时间,在数据变更时同步写缓存,防止高并发下重建缓存的资源损耗。
7.3、缓存穿透
指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层。
7.3.1、影响
将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。
7.3.2、缓存穿透原因
业务自身代码或者数据出现问题,
一些恶意攻击、爬虫等造成大量空命中
7.3.3、解决缓存穿透方案
1)缓存空对象
缓存空对象会有两个问题
- 空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
- 缓存层和存储层的数据会有一段时间窗口的不一致,例如过期时间设置为1分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,更新数据的时候清除掉缓存层中的空对象。
2)布隆过滤器拦截
如下图所示,在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截。如果布隆过滤器认为key不存在,那么就不会访问存储层,在一定程度保护了存储层。
可以参考: 布隆过滤器,可以利用 Redis 的 Bitmaps 实现布隆过滤器redis bitmaps实现布隆过滤器
7.3.4、缓存空对象和布隆过滤器方案对比