redis:
redis是一种基于键值对key、value的NoSQL内存数据库,同时它会把内存的数据利用快照和日志的形式保存到硬盘上,即使发生断电,内存中的数据也不会完全丢失,内部基于C语言(string,hash,list,set,zset)的单线程实现
1.键过期的缓存机制
2.发布订阅的消息机制
3.pinpline的流水线功能,一批命令一次传输
4.持久化AOF和RDB
5.redis cluster的分布式实现
6.单线程实现,请求放队列,底层epoll模型,连接,读写,关闭转化为事件,减少了多线程切换资源消耗
存储结构:
Redis存储结构:redisServer(默认一个服务器创建16个元空间) -》是redisDB,一个元空间(dict,expires)-》Dict(hash链表)-》ht(两个数组(entry))-》value(redisObject)-》value具体的底层数据结构类型(list,str,zset等)
key,value都是redisObject对象存储,type指定类型,encoding执行编码实现方式(类型的实现方式),ptr指向实现的指针
字符串(SDS,动态扩容,记录长度,二进制)
三种编码:int(8);embstr编码的简单动态字符串(申请一次内存,redisObject和sdshdr结构连续),row动态字符串(申请两次内存,redisObject和sdshdr结构分开)
编码转换,对key改变值,能从int或embstr转换为row,embstr对外只是可读,如果想改变会先变成row然后修改
对应命令:set,setnx(不存在才设置成功,用于插入), setxx(存在才设置成功,用于修改)),setex(设置过期时间),get key, mset(批量设置,一次网络,多个命令),incr(自增),append(值追加),get range(范围查找),getset(设置并返回原值)
Hash:有点类似的key是字符串,value是键值对
基本命令:hset key field value,hget key field, hdel key field,hexist key field;获取所有value: hval key;获取所有field: hkeys key;获取所有field-value: hgetall key
两种编码:ziplist(个数512,value 64字节),hashTable
List(可用于消息队列):内部存储有序字符串元素,对列表两边都可操作,充当栈和队列的作用
命令:向某个元素前或者后插入元素: linsert key before | after pivot value;从右边插入元素, rpush key value; lrange key start end;blpop、 brpop是lpop和rpop的阻塞版本, blpop key timeout
内部编码:有两种,压缩列表(ziplist(元素大小不等的连续空间,作为zset实现有序,其他无序))和链表(linkedlist)。新版quicklist(双向链表,节点是ziplist(内部顺序存储保存了多个entry))
lpush+lpop的组合就是栈, lpush+rpop的组合就是队列,列表同时具备栈和队列的功能。
set命令过程:初始化时候,初始化hash表,数据集等。定时事件,IO事件绑定注册,Loop循环监听selector。get请求建立连接,监听到链接时间accept创建socket监听读写,set命令传输,读就绪,select返回,读事件读取set命令,寻找命令集。,命令入队;命令出队执行,ok放入缓存,注册写事件,写就绪缓存写入传输回客户端响应。
Redis的内存回收主要分为过期删除策略和内存淘汰策略两部分。
过期删除:
1.过期删除策略:定时删除(有过期时间的设置定时器,过期删除,耗时过久影响性能),惰性删除(执行命令时删除,可能造成大量积压),定期删除(每隔一段时间删除一部分已过期的,通过定时间隔和执行耗时找到最优)redis使用定期删除和惰性删除相结合
2.过期删除流程:
redisDB的expires是个Dict数组,当对象设置过期时间会被放到这个数组中,监听事件有IO事件和时间事件,每次select循环执行,会主动清理部分key,同时定时任务也会每次清理部分key(从expires随机获取20个,判断是否过期,过期则清理,超过5个接着清理,但是执行时间不能过长25%);同时执行命令也会判断清理当前key
del命令可以异步延迟删除(unlink,交给后台线程)或立即删除;同样过期key删除也是根据配置看是否异步
内存淘汰策略:执行时,检测到内存满了,触发淘汰策略
淘汰策略(6种):
所有的key:LRU(默认),任意淘汰,禁止驱逐数据
设置了过期时间的key:LRU淘汰(从LRU计数中随机找到5个,其中LRU计数最大的),TTL将要过期淘汰(从过期时间表中随机找到5个,其中TTL最大的),随机淘汰
LRU实现:有一个全局的LRU时钟,定时更新数值,key对象有LRU属性,淘汰时候从某个节点开始连续获取5个对象,和eviction pool中idle(lru最久)时间最小对象比较,比最小的小的话放入到eviction pool中,最后清理掉最小的对象(时钟粒度是毫秒级,样本5个)
过期命令:SETEX(用于字符串),EXPIRE,PERSIST(移除过期时间),TTL返回多久过期
删除对持久化和复制的影响:
主服务器执行删除操作,命令追加到aof缓存,从库执行删除
过期数据的持久化:
rdb的save和aof的rewrite对于过期数据不会持久化,load启动读入对于过期数据不会读入,从库RDB数据还会读入。get从库正常返回过期key,只有主库删除从库才删除,保证统一(新版本从库key过期也会返回null)
持久化:
RDB:保存的二进制文件,服务器上的所有数据库键值。可设置每隔一段时间进行bgsave操作,存入单一大文件。bgsave启动子进程进行写入操作,save会阻塞客户端命令。可设置多久持久化一次,redisServer保存了持久化的配置(每隔900s,修改操作至少一次等),定时时间事件每次执行都会查看上次修改时间和当前累计计数,达到条件进行持久化,重置时间和计数。写入之后替换掉原来的RDB文件。服务启动rdb文件读入。
AOF:保存的是修改命令。每次set操作等,执行命令,同时命令追加到aof缓冲区,根据系统配置(always:每次修改都写入并同步aof,everysec:每次写入,距离上次超过一秒进行同步,no:只写入,同步由系统决定),set命令协议超文本格式存入AOF。(默认everysec,下次文件事件触发查看是否超过一秒,然后同步aof缓冲区到AOF文件)。在文件到达一定大小,会进行重写操作,重写期间交给子进程重写(把当前服务器的数据命令方式写入,对于比较大的数据,比如list超过64个,缓冲区大小固定,会分为多个命令写入),重写期间新的命令写入重写缓冲区,同时aof缓冲区也在写入同步,重写结束通知主进程信号驱动,主进程把重写缓冲区数据写入AOF,同时替换旧的AOF文件(缓冲区重写期间AOF期间客户端阻塞)。
重写之后aof文件大小会小很多
对比:RDB数据写入频率低,效率更高,但是每次写入耗时比较久,是全部数据的持久化,因为定时持久,丢失的话比较多。AOF每隔一秒写入,相对来说丢失少,但是效率可能不如RDB。启动时候RDB相对来说比AOF快(RDB体积比AOF小),RDB单一的二进制,方便备份。
主从复制:
操作从服务器命令salveof指定主服务器。建立连接,ping是否通。slave发送Psync操作,主服务器bgSave生成RDB文件,文件传输到从服务器(包括主服务器的服务Id)。传输过程中新的命令放入缓冲区,从服务器读取rdb文件结束,主服务器把缓冲区的数据发送给从服务器。整个复制过程中,主服务器会记录当前复制的偏移量,复制缓冲区队列。当从服务器命令丢失(断线重连等),会返回主服务器Id和偏移量,主服务器Id判断是之前同步的slave,根据偏移量从复制缓冲区找到对应偏移量记录命令开始传输部分同步,如果队列不存在,进行重新同步
Cluster(每个节点只有0号数据库):
哨兵实现选举和故障转移(主从复制)
clusterNode(节点名称,创建时间,IP等,clusterLink,分配的节点槽数组(二进制数组),处理的槽数量),clusterLink(缓冲区,指向的关联node),clusterState(集群是否上线,集群关联的节点map,)
CLUSTER MEET命令会把节点加入集群,同时创建对应的clusterNode信息,clusterstate.
分配的槽记录在clusterNode,clusterState记录信息:分配的槽,跳跃表(记录了槽对应的所有key,方便直接查询所属槽下所有key)。
重分片:从源节点获取指定槽下所有节点,循环让源节点转移至目标节点,通知集群其他节点。
客户端get,根据key找到slot对应节点,发现key不存在,查找迁移列表,发现key存在,返回客户端迁移的节点,客户端转向迁移节点接着查
故障转移通过选举从节点替换主节点实现,通过Gossip协议进行消息传播(选定一部分节点进行消息传播,收到的节点接着重复步骤,最终都同步)
跳跃表(zset):
多层有序链表,所属层级随机产生。适合查找,同时对增删影响也不大
zskipList,zskipNode。zset结构通过dict(hashtable)+zskipList结构(当数据少的时候用的ziplist,元素256,元素大小64)实现,通过span实现排名(跨越有序链表节点数),节点按score排序,最底层level0有前继节点。后继节点根据所属层级指定层级及以下节点。分数查找时,根据span查找跨度最大的,分数区间内再查找span低一级,一步步查找。zskipList(sore/rank和数据的关联),dict:根据数据查找分数。score可以相等,value不能相等
发布与订阅
事务:
multi开始,事务状态的命令被放到服务端队列,执行exec等方法会执行队列的命令一个个返回。出错不会回滚会接着执行
watch命令会监听一部分key,在执行事务时会检查是否已被修改,如果被执行事务会拒绝
流程:watch命令会在服务器端有key和client的对应关系,执行修改操作,服务器遍历watch查看对应键如果是被监视的,client事务状态改变。execu执行事务会查看客户端的事务状态,被破坏会拒绝。
Redis 的事务总是具有ACID中的原子性(不能回滚)、一致性和隔离性,当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性。
消息队列:
list实现,发布订阅,丢失不能找回等问题
缓存穿透(不存在的key频繁打到数据库):布隆过滤器,加空值放入缓存等
缓存雪崩(同时失效):过期时间尽量分散比如过期时间加随机数,或做二级缓存。
缓存击穿(热点数据过期并发大):分级缓存等(L1,L2)