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服务器中。