目录
Redis的基本数据类型及使用场景
Redis线程模型、Redis是单线程但查询快的原因
常用的内存策略
Redis的持久化策略有哪些
缓存过期策略
常见的缓存淘汰算法
缓存雪崩、缓存穿透、缓存击穿
布隆过滤器原理,优缺点
如何保证数据库与缓存的一致性
分布式缓存寻址算法
redis集群方案(高可用方案)
如何实现缓存的高可用
redis主从同步
redis主从同步的核心原理
谈谈对RedisCluster的理解
redis分布式锁实现
简述Redis事务实现
Redis常见数据类型及底层实现
- string:简单动态字符串(SDS)包括int(能转整数的)、embstr(小于等于44字节)、raw(大于44字节)
- List是个有序(按加入的时序排序)的数据结构,Redis采用quicklist (双端链表) 和ziplist作为List的底层实现。(没有ref和next指针,指针大小各占8byte浪费空间)
可以通过设置每个ziplist的最大容量,quicklist的数据压缩范围,提升数据存取效率
list- max- ziplist-size -2 //(默认-2)单个ziplist节点最大能存储8kb ,超过则进行分裂将数据存储在新的ziplist节点中
list- compress-depth 0 //0代表所有点,都不进行压缩,1代表从头节点往后走一个,尾节点往前走一个不用压缩。其他的全部压缩,2. 3. 4 ..以此类推
- Hash数据结构底层实现为个字典dict),也是RedisBb用来存储K-V的数据结构,当数据量比
较小,或者单个元素比较小时,底层用ziplist存储,数据大小和元素数量阈值可以通过如下参
数设置。数据大时涉及到元素的拷贝比较耗性能,所以转为了hashtable
hash-max- ziplist-entries 512 // ziplist 元素个数超过512,将改为hashtable编码
hash-max- ziplist-value 64 //单个元素大小超过64 byte时, 将改为NaShe编码
string和hash的区别:
|
string |
hash |
优点 |
可以部分更新 |
节省空间,支持Hashtable和Ziplist, 比String节约空间5倍 |
|
|
可以部分更新 |
缺点 |
内存占用较大 |
编程稍微复杂 |
|
key较为分散 |
ttl不好控制,不能单独设置某个值的过期时间 |
具体使用哪种数据结构,其实是需要看你要存储的数据以及使用场景。
如果存储的都是比较结构化的数据,比如用户数据缓存,或者经常需要操作数据的一个或者几个,特别是如果一个数据中如果filed比较多,但是每次只需要使用其中的一个或者少数的几个,使用hash是一个好的选择,因为它提供了hget 和 hmget,而无需取出所有数据再在代码中处理。
反之,如果数据差异较大,操作时常常需要把所有数据都读取出来再处理,使用string是一个好的选择。
- Set为无序的,自动去重的集合数据类型,Set 数据结构底层实现为一个value为null的字典
(dict),当数据可以用整形表示时,Set集合将被编码为intset数据结构。两个条件任意满足时
Set将用hashtable存储数据(1元素个数大于set-max-intset-entries,2元素无法用整形表示)
set-max-intset-entries 512 I1 intset能存储的最大元素个数,超过则用hasHtabe编码
整数集合intset是一个有序的,存储整型数据的结构。整型集合在Redis中可以保存int16_ t,int32_ t,int64_ t类型的整型数据,并且可以保证集合中不会出现重复数据。
- ZSet为有序的,自动去重的集合数据类型,ZSet 数据结构底层实现为字典(dict) +跳表(skiplist)。当数据比较少时,用ziplist编码结构存储。
zset-max-ziplist-entries 128 //元素个数超过128,将用skiplist编码
zset-max-ziplist-value 64 //单个元素大小超过64 byte,将用skiplist编码
Redis的基本数据类型及使用场景
String:用户凭证信息,分布式锁,验证码, (点赞,点踩、数字),bitmap
hash:可以快速定位,需要存储信息,且这个信息需要频繁被修改时,就可以采用这个结构 购物车
list:队列和栈双向链表(秒杀,保存待抢购的商品列表)
set:无序唯一 (唯一,秒杀,保存抢购到商品的幸运用户,保证每个人每件商品只能抢购一件)
zset:可排序特性分数(数值,可排序,如排行榜)
redis新特性:多线程读写(读写不能同时进行)、客户端缓存、ACL(access control list)权限控制 、SSL数据加密
Redis线程模型、Redis是单线程但查询快的原因
Redis基于Reactor(响应式)模式开发了网络事件处理器,这个处理器叫做文件事件处理器file event handler.因为这个文件事件处理器,它是单线程的,操作命令的线程是单线程的。所以Redis才叫做单线程的模型Redis本身并不是单线程的,它采用IO多路复用机制来同时监听多个Socket, 根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了Redis 内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket. 10多路复用程序、文件事件分派器以及事件处理器(命令请求、处理器、命令回复处理器、连接应答处理器等)。
多个Socket可能并发的产生不同的操作,每个操作对应不同的文件事件,但是I0多路复用程序会监听多个、Socket,会将Socket放入一个队列中排队,每次从队列中取出一个Socket给事件分派器,事件分派器把Socket、给对应的事件处理器。然后一个Socket的事件处理完之后,I0多路复用程序才会将队列中的下一个Socket给事件分派器。文件事件分、派器会根据每个Socket当前产生的事件,来选择对应的事件处理器来处理。
- Redis启动初始化时,将连接应答处理器跟AE_ READABLE事件(可读事件)关联。
- 若一个宫户端发起连接,会产生一个AE. READABLE事件, 然后由连接应答处理器负责和客户端建立连接,创建客户端对应的socket,同时将这个socket的AE READABLE事件和命令请求处理路关联,使得客户端可以向主服务器发送命令请求。
- 当客户端向Redis发请求时 (不管读还是弓请求)。客户端sockel都会产生一 个AE READABLE事件,触发命令请求处理器。处理器读取客户端的命令内容,然后传给相关程序执行。
- 当Redis服务 器准备好给客户端的响应数据后,会将socket的AE. WRITABLE事件和命令回复处理器关联,当客户端准备好读取响应数据时,会在socketr产生一个AE_ WRITABLE事件,由对应命令回复处理器处理,即将准备好的响应数据写入socket,供客户端读取。
- 命令回复处理器全部写完到socket后,就会删除该socket的AE. WRITABLE事件和命令回复处理器的映射。
单线程快的原因:
- 纯内存操作 操作快
- 核心是基于非阻塞的I0多路复用机制 一个线程对应多个Socket事件同时NIO是非阻塞的。
- 单线程反而避免了多线程的频繁上下文切换带来的性能问题 (因为多线程交出CPU时需要上下文的切换)
常用的内存策略
设置有效期,到期了之后不是立马回收,占据着内存
没有设置有效期,就一直存在,命中率很低的资源,占据内存
内存的资源没有被合理利用
如何回收:
- 采用定时回收的方式10次/S
- 惰性回收,执行get指令刚好这个key已经过期了,那么这个时候会自动删除
- 具体的回收算法
- TTL (ALL, random) ,根据是过期时间
- LRU (最近最少使用) , 根据是缓存的命中率 不回收(默认行为),满了就只能读,不能写
- 推荐: LRU 细节: 精确度和性能的权衡 采用采样的方式来筛选
Redis的持久化策略有哪些
意义: 当我们的redis服务器发生故障,可以通过重启快速恢复数据,从而保护数据库。否则,重启将是一个空壳。
具体有什么样的持久化机制?
1、RDB(Redis DataBase)快照的形式,产出二进制文件,默认开启的方式 save 900 60 在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork-个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储保存的是数据。
手动触发
- save命令,使Redis处于阳塞状态,直到RDB持久化完成,才会响应其他客户端发来的命令(因为单线程),所以在生产环境一定要慎用
- bgsave命令, fork出一个子进程执行持久化,主进程只在fork过程中有短暂的阳塞, 子进程创建之后,主进程就可以响应客户端请求了 (此时主进程如果有写操作,则是COW写时拷贝,有写命令时先生成副本写,写完再更新过去)
自动触发:
- save m n:在m秒内,如果有n个键发生改变,则自动触发持久化,通过bgsave执行, 如果设置多个、只要满足其一就会触发,配置文件有默认配置(可以注释掉)
- flushall: 用于清空redis所有的数据库(一共有11号数据库),flushdb清空当前redis所在库数据(默认是0号数据库),会清空RDB文件,同时也会生成dump.rdb.内容为空 慎用
- 主从同步:全量同步时会自动触发bgsave命令,生成rdb发送给从节点
触发持久化,并非实时的方式,适合做全量备份
优点:
- 整个Redis数据库将只包含一个文件dump.rdb,方便持久化。
- 容灾性好,方便备份。
- 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是l0最大化。使用单独子进程来进行 持久化,主进程不会进行任何IO操作,保证了redis 的高性能
- 相对于数据集大时,比AOF的启动效率更高。
缺点:
- 数据安全性低。RDB是间隔一段时间进行持久化, 如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
- 由于RDB是通过fork子进程来协助完成数据持久化工作的占用CPU,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
2、AOF:(Append Only File只追加文件)以日志的形式记录服务器所处理的每一个写、 删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录,调操作系统命令进程刷盘 保存的是命令
- 所有的写命令会追加到AOF缓冲中。
- AOF 缓冲区根据对应的策略向硬盘进行同步操作。
- 随着AOF文件越来越大,需要定期对AOF文件进行重写(命令的优化),达到压缩的目的。
- 当Redis重启时,可以加载AOF文件进行数据恢复。
同步策略(缓冲区):
- 每秒同步:异步完成,效率非常高,一旦系统出现宕机现象,那么这一 秒钟之内修改的数据将会丢失
- 每修改同步:同步持久化,每次发生的数据变化都会被立即记录到磁盘中,最多丢一 条
- 不同步(redis不进行操作):由操作系统控制,可能丢失较多数据
AOF, 产出日志文件,默认不开启
触发时机:每秒(推荐),每次操作
细节: aof.log setk1 v1 set k2 v2 ... set k1 value1 set k1 newValue
目标:只保留有效的指令
实现:
- 假设: key值唯一 效率低,不采用
- 设置触发重写日志文件的时机 如:到64M的时候重写(依然不做比较,直按将当前内存的数据写到日志文件。完成覆盖)
以日志的形式记录服务器所处理的每一个写删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录
●优点:
- 数据安全, Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是- -旦 系统出现宕机现象,那么这一秒钟之内修改的数据将会失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。
- 通过append模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过redis-check-aof工具解决数据一致性问题。
- AOF 机制的rewrite模式。(将一些重复的命令合并)定期对AOF文件进行重写,以达到压缩的目的
●缺点:
- AOF文件比RDB文件大有大量的操作记录,且恢复速度慢。
- 数据集大的时候,比rdb启动效率低。
- 运行效率没有RDB高
AOF文件比RDB更新频率高,优先使用AOF还原数据。 AOF比RDB更安全更大;RDB性能比AOF好;如果两种都配置了,优先加载AOF
缓存过期策略
Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存 的key过期了,Redis如何处理。
- 定时过期:每个设置过期时间的key部需要创建一个定时器, 到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好:但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐最(redis中没有采用该策略)
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
- 定期过期:每隔-定的时间,会扫描一定数量的数据库的expires字典中一 定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案(既不扫描所有的又不每时每刻扫描)。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中, key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。) Redis中同时使用了惰性过期和定期过期两种过期策略。
常见的缓存淘汰算法
缓存空间满了的情况下已经再放不下数据时
- FIFO (First In First Out,先进先出) , 根据缓存被存储的时间,离当前最远的数据优先被淘汰;
- LRU (Least Recently Used,最近最少使用),根据最近被使用的时间,离当前最远的数据优先被淘汰;
- LFU (Least Frequently Used,最不经常使用),在一段时间内,缓存数据被使用次数最少的会被淘汰。
缓存雪崩、缓存穿透、缓存击穿
缓存雪崩是指缓存同一时间大面积的失效(如重启时宕机时等),所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:
- 缓存数据的过期时间设置随机,防止同-时间大量数据过期现象发生。
- 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。比较耗性能需要总是判断
- 缓存预热 针对缓存重启可以先把热点数据放到缓存中
- 互斥锁 缓存失效需要去查数据库,这时加一个锁(就是查数据库的时候把缓存的这个键锁起了查完数据库再放到缓存中,再释放锁,这样避免大量的请求对同一个键进行大量操作)
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 一般是来自于网络攻击
解决方案:
- 接口层增加校验、做校验从业务层面就判断出数据没有,如用户鉴权校验,id做基础校验,id<=0的直接拦截:
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null, 缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底只存储系统的查询压力
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬问增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据, 缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
- 设置热点数据永远不过期。
- 加互斥锁 只允许一个线程去查数据库
布隆过滤器原理,优缺点
先了解一下位图: 一个int[10], 每个int类型的整效是4*8-32个bit,则int[10]- 共有320 bit,每个bit位非0即1,初始化时都是0 添加数据时,将数据进行hash得到hash值, 对应到bit位, 将该bit改为1, hash函数可以定义多个,则一个数据涿加会将多个(hash函数个数) bit改为1,多个(具体个数自定义)hash函数的目的是减少hash碰撞的概率查询数据: hash函数计算得到hash值, 对应到bit中, 如果有一个为0,则说明数据不在bit中,不在bit中缓存中肯定没有,就没必要再查缓存了,如果都为1,则该数据可能在bit中。在bit中就去缓存查
优点:
- 占用内存小
- 增加和查询元索的时间复杂度为: O(K), (K为哈希函数的个数,-般比较小),与数据量大小无关
- 哈希函数相互之间没有关系,方便硬件并行运算
- 布降过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
- 数据量很大时,布降过滤器可以表示全集
- 使用同一组散列函数的布降过滤器可以进行交、并、差集运算
缺点:
- 误判率,即存在假阳性(False Position),不能准确判断元素是否在集合中但可以通过两个hash算法组合进行判断,同一个元素用两个不同hash算法定位
- 不能获取元素本身 (不可以逆向运算。散列运算)
- 一般情况下不能从布降过滤器中删除元素(可能影响到其他的key)
如何保证数据库与缓存的一致性
由于缓存和数据库是分开的,无法做到原子性的同时进行数据修改,可能出现缓存更新失败,或者数据库更新失败的情况,这时候会出现数据不一致,影响前端业务
- 先更新数据库,再更新缓存。缓存可能更新失败,读到老数据 (业务允许可以选择)
- 先删缓存,再更新数据库。并发时,读操作可能还是会将旧数据读回缓存(写的操作还没来得及写)
- 先更新数据库,再删缓存。也存在缓存删除失败的可能
最经典的缓存+数据库读写的模式,Cache Aside Pattern.读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。史新的时候,先更新数据库,然后再删除缓存。
为什么是删除而不是更新? 删除更加轻量,延迟加载的一种实现,更新可能涉及多个表比较耗时
延时双删:先删除缓存,再更新数据库,休眠1s、 再次删除缓存。写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据,并发还是可能读到旧值覆盖缓存
終极方案:
将访问操作串行化
- 先删缓存,将更新数据库的操作放进有序队列中
- 从缓存查不到的查询操作,都进入有序队列
会面临的问题:
- 读请求积压,大量超时,导致数据库的压力:限流。熔断
- 如何避免大量请求积压:将队列水平拆分,提高并行度,根据业务拆分。
- 保证相同请求路由正确,读写操作是针对相同key的。
分布式缓存寻址算法
- hash算法:根据key进行hash函数运算、结果对分片数取模,确定分片。适合固定分片数的场景。扩展分片或者减少分片时,所有数据都需要重新计算分片、存储
- 一致性hash: 将整个hash值得区间组织成一个闭合的圆环, 计算每台服务路的hash值、映射到圆环中。使用 相同的hash算法计算数据的hash值,映射到圆环,顺时针寻找,找到的第一个服务 器就是数据存储的服务器。新增及减少节点时只会影响节点到他逆时针最近的一个服务器之间的值存在hash环倾斜的问题,即服务器分布不均匀,可以通过虚拟节点解决
- hash slot:将数据与服务器隔离开,数据与slot映射,slot与服务器映射, 数据进行hash决定存放的slot 新增及删除节点时,将slot进行迁移即可
redis集群方案(高可用方案)
主从、哨兵
哨兵模式:sentinel,哨兵是redis集群中非常重要的一个组件,主要有以下功能:
- 集群监控:负责监控redis master和slave进程是否正常工作。
- 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果master node挂掉了,会自动转移到slave node上。
- 配置中心:如果故障转移发生了,通知client客户端新的master地址。
哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
- 故障转移时,判断一个master node是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
- 哨兵通常需要3个实例,来保证自己的健壮性。
- 哨兵+ redis主从的部署架构,是不保证数据零丢失的,只能保证redis集群的高可用性。
- 对于哨兵+ redis主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
Redis Cluster是一种服务端Sharding技术每个节点互为主从分担16384个槽位, 3.0版本开始正式提供。采用slot(槽)的概念分布在每一个节点上,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行
方案说明
- 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据服务端分片,默认分配了16384个槽位
- 每份数据分片会存储在多个互为主从的多节点上
- 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
- 同一分片多个节点间的数据不保持强一致性
- 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
- 扩容时需要需要把旧节点的数据迁移一部分到新节点
在redis cluster架构下,每个redis要放开两个端口号,比如一个是6379,另外一个就是加1w的端口号,比如16379. 16379端口号是用来进行节点间通信的,也就是cluster bus的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus用了另外-种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间
优点
- 无中心架构多主多从,支持动态扩容,对业务透明
- 具备Sentinel的监控和自动Failover(故障转移)能力
- 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- 高性能,客户端直连redis服务, 免去了proxy代理的损耗
缺点
- 运维也很复杂(导入导出槽),数据迁移需要人工干预
- 只能使用0号数据库
- 不支持批量操作(pipeline管道操作)
- 分布式逻辑和存储模块耦合等
Redis Sharding是Redis Cluster出来之前是客户端分片,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法将 Redis数据的key进行散列,通过hash函数, 特定的key会映射到特定的Redis节点上。Java redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool .
优点
优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器-样运行, 非常容易线性扩展,系统的灵活性很强
缺点
由于sharding处理放到客户端,规模进一步扩 大时给运维带来挑战。
客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。连接不能共享,当应用规模增大时,资源浪费制约优化
如何实现缓存的高可用
原因: Redis的作用如此重要,如果在系统中属于单点的情况,那么一旦发生故障, 将对系统造成很大的影响。于是,我们需要保证Redis的高可用,避免单点故障。
实现:
一主多从的模式, 同时有哨兵这个服务来监控主从节点的健康状态。
当主节点发生了宕机,此时哨兵服务具体要做什么呢?
- 哨兵要选择一台从机做为新主机, 这里面有一个关键指标(参照依据 ), 就是偏移量,这个是来看从机跟主机的数据同步状态,执行指令: slaveof no master
- 其他的从机要重新建立跟新主机的关系,slaveof newip newport
- 宫户端是连接哨兵服务的,所以哨兵服务会给客户端返回新的主机地址
redis主从同步
- 从节点执行slaveof masterlp port(可配置可执行给从节点指定主节点),保存主节点信息.
- 从节点中的定时任务发现主节点信息,建立和主节点的socket连接
- 从节点发送ping命令信号,主节点返回,两边能互相通信
- 权限校验
- 连接建立后,主节点将所有数据发送给从节点(数据同步)
- 主节点把当前的数据同步给从节点后,便完成了复制过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。
runld:每个redis节点启动都会生成唯- -的uuid, 每次redis重启后, runld都会发生变化。
offset:主从节点各自维护自己的复制偏移量offset,当主节点有写入命令时, offset=offsel+命令的字节长度。从节点在收到主节点发送的命令后,也会增加自己的offset, 并把自己的ffset发送给主节点。主节点同时保存自己的offset和从节点的offset,通过对比fset来判断主从节点数据是否致。
repl_ backlog_size:保存在主节点上的一个固定长度的先进先出队列,默认大小是1MB。
redis主从同步的核心原理
通过执行slaveof命令或设置slaveof选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作,当写操作导致数据交化时会自动将数据同步给从数据库。而从数据库一般是只读的, 并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主 数据库。
全量复制:
- 从节点发送psync命令,psync runid offset (由于是第一次, runid为?,offset为-1)
- 主节点返回FULLRESYNC runld offset, runld是主节点的runld,offset是主节点目前的offset。 从节点保存信息
- 主节点通过bgsave 命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制、硬盘I0的
- 主节点通过网络将RDB文件发送给从节点,到从节点加载数据完成之前,写命令写入缓冲区,对主从节点的带宽都会带来很大的消耗
- 从节点清空老数据、载入新RDB文件的过程是阻塞的, 无法响应客户端的命令;如果从节点执行 bgrewriteaof,也会带来额外的消耗 如果开启了AOF就会重写AOF
部分复制(增量复制):
- 复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
- 复制积压缓冲区:主节点内部维护了-个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区(避免了操作硬盘),当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
- 服务器运行ID(runid):每个Redis节点, 都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。从节点Redis断开重连的时候, 就是根据运行ID来判断同步的进度:
- 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
- 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。
谈谈对RedisCluster的理解
redis3.0之后的版本提供的特性
特点: .
- 没有中间层,客户端直按跟redisCluster的节 点进行连接
- 每个主节点都支持读写操作,分担了写的压力,后期我们继续增加新的主机,然后做hash迁移,新主机就可以分担压力
- 因为每个节点负责的区域是不同的,所以,往里面保存数据的时候,比如set key value 这是时候,要根据key来做crc16算法,从而得到一个数值,数值%1 6384=(0-1 8383).从而确定存储到哪个节点
- 为了保证每个节点的高可用,所以,每个节点可以做主从
- 后期还支持新增主节点和从节点,需要做数据迁移,将之前的主节点迁移部分数据到新的主节点分单压力
redis分布式锁实现
setnx+setex:存在设置超时时间失败的情况,导致死锁 setex设置key及给key设置过期时间
set(key,value,nx.px):将setnx+setex变成原子操作
存在问题:
- 任务超时,锁自动释放,导致并发问题。使用redisson解决(看门狗监听, 自动续期)
- 以及加锁和释放锁不是同一个线程的问题。 在value中存入uuid(线程唯一标识), 删除锁时判断该标识(使用 lua保证原子操作)
- 不可重入,使用redisson解决(实现机制类似AQS,计数)
- 异步复制可能造成锁丢失,使用redLock解决 redission中也有相应的实现
- 顺序向五个节点请求加锁
- 根据-定的超时时间来推断县不是跳过该节点
- 三个节点加锁成功并且花费时间小于锁的有效期
- 认定加锁成功
1.首先利用setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁
2.然后还要利用lua脚本来保证多个redis操作的原子性
3.同时还要考虑到过期,所以需要额外的一个看门狗定时任务 来监听锁足否需要续约
4.同时还要考虑到redis节点挂掉后的情况,所以需要采用红锁的方式来同时向N/2+1个节点申请锁,都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到
Redis做分布式锁死锁有哪些情况,如何解决?
情况1:加锁,没有释放锁。需要加释放锁的操作。比如delete key。
情况2:加锁后,程序还没有执行释放锁,程序挂了。需要用的key的过期机制。
ZooKeeper和Reids做分布式锁的区别?
Reids:
1. Redis只保证最终一致性,副本间的数据复制是异步进行(Set是写,Get是读,Reids集群一 般是读写分离架构,存在主从同步延迟情况),主从切换之后可能有部分数据没有复制过去可能会「丢失锁」情况,故强一致性要求的业务不推荐使用Reids,推荐使用zk。
2. Redis集群各方法的响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升(公网集群影响因素偏大), 但是极限qps可以达到最大且基本无异常
ZooKeeper:
1.使用ZooKeeper集群,锁原理是使用ZooKeeper的临时顺序节点,临时顺序节点的生命周期在Client与集群的Session结束时结束。因此如果某个Client节点存在网络问题,与ZooKeeper集群断开连接, Session超时同样会导致锁被错误的释放(导致被其他线程错误地持有),因此ZooKeeper也无法保证完全一致。
2. ZK具有较好的稳定性;响应时间抖动很小,没有出现异常。但是随着并发星和业务数星的提升其响应时间和qps会明显下降(因为zk创建若干个几点,要保证强一致性是需要时间去把节点进行同步)。
总结:
1. Zookeeper每次进行锁操作前都要创建若干节点,完成后要释放节点,会浪费很多时间(节点的同步时间);
2.而Redis只是简单的数据操作,没有这个问题。
简述Redis事务实现
1、事务开始
MULTI命令的执行,标识着一个事 务的开始。MULTI命 令会将客户端状态的f1ags属性中打开REDIS_ MULTI标识来完成的。.
2.命令入队
当一个客户端切换到事务状态之后,服务器会根据这个客户端发送来的命令来执行不同的操作。如果客户端发送的 命令为MULTI. EXEC、 WATCH. DISCARD中的一个, 立即执行这个命令,否则将命令放入一个事务队列里面,然后 向客户端返回QUEUED回复
- 如果客户端发送的命令为EXEC、 DISCARD、 WATCH、MULTI四个命令的其中一个,那么服务器立即执行这 个命令。
- 如果客户端发送的是四个命令以外的其他命令, 那么服务器并不立即执行这个命令。首先检查(检查语法 )此命令的格式是否正确,如果不正确,服务器会在客户端状态(redisClient) 的flags属性关闭 REDIS_ _MULTI标识,并且返回错误信息给客户端。如果正确,将这个命令放入一个事务队列里面,然后向客户端返回QUEUED回复 事务队列是按照FIFO的方式保存入队的命令
3、事务执行
- 客户端发送EXEC命令,服务器执行EXEC命令逻辑。
- 如果客户端状态的flags属性不包含REDIS_ MULTI标识,或者包含REDIS_ DIRTY_ CAS或者REDIS_ DIRTY_ EXEC标识,那么就直接取消事务的执行。
- 否则客户端处于事务状态(flags 有REDIS_ MULTI 标识), 服务器会遍历客户端的事务队列,然后执行事务队 列中的所有命令,最后将返回结果全部返回给客户端;
redis不支持事务回滚机制,但是它会检查每一个事务 中的命令是否错误。
Redis事务不支持检查那些程序员自己逻辑错误。例如对String类型的数据库键执行对HashMap类型的操作!
- WATCH 命令是-一个乐观锁,可以为Redis事务提供check-and-set (CAS) 行为。可以监控一个或多 个键,一旦其中有一个键被修改(或删除), 之后的事务就不会执行,监控-直持续到EXEC命令。
- MULTI命令用于开启一个事务, 它总是返回OK。MULTI执行之后, 客户端可以继续向服务器发送任意多条命 令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC 命令被调用时,所有队列中的命令才会被执行。
- EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被 打断时,返回空值nil。
- 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出。
- UNWATCH命令可以取消watch对所有key的监控。