Redis总结
用到了操作系统层面的NIO,同步非阻塞。epoll(sys_epoll)函数。IO多路复用。用到红黑树,用户与内核共享内存。
二进制安全
支持的数据类型:
1、string:动态字符串
string包含三个encoding,string、int、bitmap。
bitmap(使用场景,会员一年中登录的次数,可以通过redis的bitmap存储)。
2、list:
lpush:从左往右填入,lpop可以用作栈功能,先进后出。rpop则可以用作队列,先进先出。
list可描述的数据结构:
①、栈:先进后出(同向命令)
②、队列:先进先出(反向命令)
③、数组:下标获取元素
3、hash
键值对
4、set
①、无序去重
②、获取随机数据,如果是正数,可取出去重,如果是负数,可去除重复,填满需获取的数据。
5、sorted set:有序集合
①、物理内存左小右大,不随命令发生改变。zrang,zrevrang.
②、集合取并集,交集(权重/聚合命令)。
删除策略
数据删除策略:定时删除+定期删除+惰性删除
定时删除:创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
优点:节约内存,到时就删除,快速释放掉不必要的内存占用
缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
总结:用处理器性能换空间
定期删除:redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?假如redis存了几十万个key,每隔100ms就遍历所有的设置过期时间的key的话,就会给CPU带来很大的负载。
惰性删除:定期删除可能会导致很多过期key到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个key,才会被redis给删除掉。这就是所谓的惰性删除。expireIfNeeded(),检查数据是否过期,执行get的时候调用
优点:节约CPU性能,发现必须删除的时候才删除
缺点:内存压力很大,出现长期占用内存的数据
总结:用存储空间换取处理器性能(随机抽查,重点抽查)
优点 | 缺点 |
---|---|
定时删除 | 节约内存 |
定期删除 | 内存定期随机清理 |
惰性删除 | 内存占用严重 |
淘汰策略:基本会在allkeys-lru,volatile-lru二选一,如果是大部分数据是设置了过期时间用volatile-lru。
noeviction:不删除,返回错误
allkeys-lru:首先通过LRU算法驱逐最久没有使用的键
volatile-lru:首先从配置了过期时间的键集合中驱逐最久没有使用的键
allkeys-random:从所有key随机删除
volatile-random:从配置过期键的集合中随机驱逐
volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键
持久化
RDB:redis进程fork一个子进程,父进程与子进程数据共享,实际上是父进程快速fork子进程,子进程拥有父进程对数据的指针,此时由子进程对数据的写操作Copy on write内核机制。
父进程此时还需要对客户端的读写操作进行响应,到子进程复制写入文件为止的这段时间父进程的写出数据子进程是不能实时读取的。此为父子进程数据隔离。
save命令:手动触发,进程阻塞,使用场景,关机维护,先进性数据备份。
bgsave命令:fork子进程。
#配置文件 配置快照 save触发的是bgsave命令
save 900 1
save 300 10
save 60 10000
缺点:
1、不会像mysql备份那样进行版本的文件写入,不覆盖。而是每次都覆盖dump.rdb文件
2、宕机,可能会丢失部分数据。在写入文件之后的写入操作丢失。
优点:
类似于java中序列化恢复速度相对快。
AOF
优点:
1、丢失少
aof触发重写配置:
auto-aof-rewrite-percentage 100 -->比上次重写增加100%
auto-aof-rewrite-min-size 64mb -->最小大小64mb
4.0之前:重写(限定大小触发重写),删除抵消的命令,合并重复的命令。
4.0之后:重写(限定大小触发重写), 将老的数据RDB到aof文件中再将增量的以指令方式append到aof中,优点RDB恢复速度快,以及后来纪录的指令日志数据全。
AOF写入策略:
1.always,每执行一次数据修改命令就将其命令写入到磁盘日志文件上
2.everysec,每秒将命令写入到磁盘日志文件上
3.no,不主动设置,由os决定什么时候写入到磁盘日志文件上
单机存在如下问题,如何解决实现高可用
1、单点故障
2、容量有限
3、压力
基于上述问题redis的AKF架构:
X轴拆分:水平复制,就是讲单体系统多运行几个实例,做集群加负载均衡的模式,主主、主备、主从。
Y轴拆分:基于不同的业务拆分
Z轴拆分:基于数据拆分
主从复制:
1、数据一致性。
①、强一直性:所有节点阻塞直到数据全部一致。存在问题,破坏可用性。
②、弱一致性:主节点成功直接返回成功,从节点异步同步数据。
主从复制参数设置:
replica-read-only yes 从机只读模式
replica-serve-state-data yes 从机同步主机过程 从机的老数据是否可以访问
主从复制 需要维护主的故障问题。
哨兵-sentinel:
可以通过redis-server启动哨兵,如:redis-server ./26379.conf --sentinel
启动时设置哨兵参数。
哨兵配置文件设置redis master服务器即可:
哨兵启动后,如集群则哨兵会知道主、从以及所有哨兵的节点。
当主节点挂掉是,哨兵集群会通过选举方式选举出新的主节点,其他则设置为从节点。
高可用集群模式:客户端增多,服务器端造成链接压力,可以通过代理方式解决。
国美缓存服务器架构设计如下:
多种算法或者方式进行分区
1、key带有前缀的,如:ORDER:k1;PRODUCT:K1; 这样,代理层或者客户端根据前缀来映射不同的redis服务器。
2、环形hash,预分区的方式,比如:通过客户端或者代理进行hash之后 预留10台分区, 实际2台服务器,01234 模结果分配到 1服务器,56789分配到2服务器。
redis集群代理技术:
1、twemproxy推特开源的。
2、predixy,功能齐全及性能强大。
缓存击穿,缓存穿透,缓存雪崩。
1、缓存穿透
布隆过滤器解决。
2、缓存击穿
①、setnx,只有一个访问其他都等待,分布式锁。
②、过期时间
如果过期,还没有处理完,此时可以用两个线程,一个访问DB存redis,第二个访问redis,是否已经已经存了redis.类似于看门狗,可以延迟线程池做第二个线程。
3、雪崩
按照击穿①②步骤完成。随机过期,永久有效等。
主从复制步骤:
1、发送 psync 命令(psync ?-1)
2、主节点根据命令返回 FULLRESYNC
3、从节点记录主节点 runid 和 offset
4、主节点 bgsave() 并保存 RDB 到本地
5、主节点发送 RBD 文件到从节点
6、从节点收到 RDB 文件并加载到内存中
7、主节点在从节点接受数据的期间,将新数据保存到“复制客户端缓冲区”,当从节点加载 RDB 完毕,再发送过去。(如果从节点花费时间过长,将导致缓冲区溢出,最后全量同步失败)
8、从节点清空数据后加载 RDB 文件,如果 RDB 文件很大,这一步操作仍然耗时,如果此时客户端访问,将导致数据不一致,可以使用配置slave-server-stale-data 关闭.
9、从节点成功加载完 RBD 后,如果开启了 AOF,会立刻做 bgrewriteaof。
官网主从复制描述
在 Redis 复制的基础上,使用和配置主从复制非常简单,能使得从 Redis 服务器(下文称 slave)能精确得复制主 Redis 服务器(下文称 master)的内容。每次当 slave 和 master 之间的连接断开时, slave 会自动重连到 master 上,并且无论这期间 master 发生了什么, slave 都将尝试让自身成为 master 的精确副本。
这个系统的运行依靠三个主要的机制:
- 当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave , :包括客户端的写入、key 的过期或被逐出等等。
- 当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步:这意味着它会尝试只获取在断开连接期间内丢失的命令流。
- 当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。
问题:为什么redis速度快?
1、基于内存实现
2、高效的底层数据结构
3、读写单线程模型(对于 Redis 的持久化、集群数据同步、异步删除等都是其他线程执行)
4、I/O 多路复用模型
并发处理连接。采用了 epoll + 自己实现的简单的事件框架。Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。
5、Redis 全局 hash 字典
Redis 整体就是一个 哈希表来保存所有的键值对,无论数据类型是 5 种的任意一种。哈希表,本质就是一个数组,每个元素被叫做哈希桶,不管什么数据类型,每个桶里面的 entry 保存着实际具体值的指针。
而哈希表的时间复杂度是 O(1),只需要计算每个键的哈希值,便知道对应的哈希桶位置,定位桶里面的 entry 找到对应数据,这个也是 Redis 快的原因之一。
Redis 使用对象(redisObject)来表示数据库中的键值,当我们在 Redis 中创建一个键值对时,至少创建两个对象,一个对象是用做键值对的键对象,另一个是键值对的值对象。
也就是每个 entry 保存着 「键值对」的 redisObject 对象,通过 redisObject 的指针找到对应数据。
Redis 通过链式哈希 解决冲突:也就是同一个 桶里面的元素使用链表保存 。但是当链表过长就会导致查找性能变差可能,所以 Redis 为了追求快,使用了两个全局哈希表。用于 rehash 操作,增加现有的哈希桶数量,减少哈希冲突。
6、持久化方案
7、主从哨兵模式。
面试题:
ZSet为什么不用B+树
ZSet:压缩链表+跳跃表
跳跃表(SkipList)是一种能高效实现插入、删除、查找的内存数据结构,这些操作的期望复杂度都是O(logN)。
与红黑树以及其他的二分查找树相比,跳跃表的优势在于实现简单,而且在并发场景下加锁粒度更小,从而可以实现更高的并发性。
正因为这些优点,跳跃表广泛使用于KV数据库中,诸如Redis、LevelDB、HBase都把跳跃表作为一种维护有序数据集合的基础数据结构。
redis基于内存,设计是尽可能提高读写效率。插入性能搞,操作简单,无需维护B+树特征。
而B+树是mysql InnerDB引擎的数据存储结构,磁盘存储,可存储数据量更大,需减少磁盘IO,提高访问效率。B+树层数少,进而减少磁盘IO。
redis 大Key
redis中有常见的几种数据结构,每种结构对大key的定义不同,比如:
value是String类型时,size超过10KB
value是ZSET、Hash、List、Set等集合类型时,它的成员数量超过1w个
大key有什么影响
我们都知道,redis的一个典型特征就是:核心工作线程是单线程。
单线程中请求任务的处理是串行的,前面完不成,后面处理不了,同时也导致分布式架构中内存数据和CPU的不平衡。
执行大key命令的客户端本身,耗时明显增加,甚至超时
执行大key相关读取或者删除操作时,会严重占用带宽和CPU,影响其他客户端
大key本身的存储带来分布式系统中分片数据不平衡,CPU使用率也不平衡
大key有时候也是热key,读取操作频繁,影响面会很大
执行大key删除时,在低版本redis中可能阻塞线程
这样看来大key的影响还是很明显的,最典型的就是阻塞线程,并发量下降,导致客户端超时,服务端业务成功率下降。
大key的产生往往是业务方设计不合理,没有预见vaule的动态增长问题:
一直往value塞数据,没有删除机制,迟早要爆炸
数据没有合理做分片,将大key变成小key
如何找到大key
增加内存&流量&超时等指标监控
由于大key的value很大,执行读取时可能阻塞线程,这样Redis整体的qps会下降,并且客户端超时会增加,网络带宽会上涨,配置这些报警可以让我们发现大key的存在。
bigkeys命令
使用bigkeys命令以遍历的方式分析Redis实例中的所有Key,并返回整体统计信息与每个数据类型中Top1的大Key
redis-rdb-tools
使用redis-rdb-tools离线分析工具来扫描RDB持久化文件,虽然实时性略差,但是完全离线对性能无影响。
redis-rdb-tools是由Python写的用来分析Redis的rdb快照文件用的工具,它可以把rdb快照文件生成json文件或者生成报表用来分析Redis的使用详情。
压缩和拆分key
当vaule是string时,比较难拆分,则使用序列化、压缩算法将key的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗。
当value是string,压缩之后仍然是大key,则需要进行拆分,一个大key分为不同的部分,记录每个部分的key,使用multiget等操作实现事务读取。
当value是list/set等集合类型时,根据预估的数据规模来进行分片,不同的元素计算后分到不同的片,分配到不同的redis服务器中。
客户端请求路由
(1)moved重定向
客服端请求产生moved重定向的执行过程:
1.每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系。
2.客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与16383取余,计算自己的槽和对应节点 。
3.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端。
4.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常 。
5.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息。
6.客户端向目标节点发送命令,获取命令执行结果。
(2)ask重定向
在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移。当槽及槽中数据正在迁移时,客服端请求目标节点时,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转向给客户端。
当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息;客户端再向正确的节点发送命令时,如果此时正在进行集群扩展或者缩空操作,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制。如下图:
请求执行步骤:
1.当客户端向集群中某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应目标槽的节点信息。
2.客户端再向目标节点发送命令,目标节点中的槽已经迁移出别的节点上了,此时目标节点会返回ask重定向给客户端。
2.客户端向新的target节点发送Asking命令,然后再次向新节点发送请求请求命令。
3.新节点target执行命令,把命令执行结果返回给客户端。
moved和ask重定向的区别:
两者都是客户端重定向
moved异常:槽已经确定迁移,即槽已经不在当前节点
ask异常:槽还在迁移中
(3)smart智能客户端
当数据过多,集群节点较多时,客服端大多数请求都会发生重定向,每次重定向都会产生一次无用的请求,严重影响了redis的性能。如果客户端在请求时就知道由哪个节点负责管理哪个槽,再将请求打到对应的节点上,那就有效的解决了这个问题。
提高redis的性能,避免大部分请求发生重定向,可以使用智能客户端。智能客户端知道由哪个节点负责管理哪个槽,而且某个当节点与槽的映射关系发生改变时,客户端也会进行响应的更新,这是一种非常高效的请求方式。
Jedis为Redis Cluster提供了Smart客户端,也就是JedisCluster类。JedisCluster会从集群中选一个可运行的节点,使用 cluster slots 初始化槽和节点映射,将映射关系保存到本地,为每个节点创建JedisPool,相当于为每个redis节点都设置一个JedisPool,然后就可以进行数据读写操作。
smart智能客户端读写数据命令执行过程如下:
1.JedisCluster启动时,会从集群中选一个可运行的节点,使用 cluster slots 初始化槽和节点映射,将映射关系保存到本地。
2.smart 客户端将请求要操作的 key 发送到目标节点,如果请求成功,就得到响应,并返回结果。
3.如果目标节点出现连接出错(说明节点的slot->node的映射有更新),客户端将随机找个活跃节点,向其发送命令,大概率会得到 moved异常,然后根据moved响应更新 slot 和 node 的映射关系,再向新的目标节点发送命令。
如果这样的情况连续出现 5 次未找到目标节点,则抛出异常:Too many cluster redirection!。
总结:mart智能客户的目标:追求性能。避免了大量请求的moved重定向操作,在数据量和请求量大的环境下,极高的提升了redis性能。
Redis Cluster主从选举
当某个master挂掉后,在cluster集群仍然可用的前提下,由于某个master可能有多个slave,某个salve将提升为master节点,那么就会存在竞争,那么此时它们的选举机制是怎样的呢?
currentEpoch选举轮次标记
一个集群状态的相关概念,记录集群状态变更的递增版本号。集群中每发生一次master选举currentEpoch就加一,集群节点创建时,不管是 master还是slave,都置currentEpoch为0。 当前节点在接受其他节点发送的请求时,如果发送者的currentEpoch(消息头部会包含发送者的 currentEpoch)大于当前节点的currentEpoch,那么当前节点会更新currentEpoch。 因此,集群中所有节点的 currentEpoch最终会达成一致,相当于对集群状态的认知达成了一致。
master节点选举过程:
1.slave发现自己的master变为FAIL。
2.发起选举前,slave先给自己的epoch(即currentEpoch)加一,然后请求集群中其它master给自己投票,并广播信息给集群中其他节点。
3.slave发起投票后,会等待至少两倍NODE_TIMEOUT时长接收投票结果,不管NODE_TIMEOUT何值,也至少会等待2秒。
4.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送结果。
5.尝试选举的slave收集master返回的结果,收到超过半投票数master的统一后变成新Master,如果失败会发起第二次选举,选举轮次标记+1继续上面的流程。
6.选举成功后,广播Pong消息通知集群其它节点。
之所以强制延迟至少0.5秒选举,是为确保master的fail状态在整个集群内传开,否则可能只有小部分master知晓,而master只会给处于fail状态的master的slaves投票。 如果一个slave的master状态不是fail,则其它master不会给它投票,Redis通过八卦协议(即Gossip协议,也叫谣言协议)传播fail。 而在固定延迟上再加一个随机延迟,是为了避免多个slaves同时发起选举。
延迟计算公式:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。
每次痛苦的挣扎过程都是一次成长,坚持过去了就海阔天空;越优秀的人,越自律;越痛苦的时候,越要坚持,不忘初心!
参加:https://zhuanlan.zhihu.com/p/511199672