A_6_07.Redis设计与实现
2021-04-14 17:07:17 0 举报
AI智能生成
全面、高效的知识图谱:A_6_07.Redis设计与实现!! 全面又深度的提升认知,达到实际应用的目的! 建议先纵观全局,掌握好大方向。 再根据自己的需要,针对性的学习某一个点,最后做到逐步由点及面。
作者其他创作
大纲/内容
Redis
运维
监控
2)命令平均耗时使用info Commandstats命令获取,包含每个命令调用次数,总耗时,平均耗时,单位微秒。
主从
Redis的复制功能分为同步(sync)和命令传播(command propagate)两个操作:
同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态;
命令传播操作则用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态。
读写分离
读写分离适合大访问(大到单台redis感觉很慢了),并且写操作远小于读操作的情况。
如果读请求数量远超写请求,导致集群数据copy成本远小于读请求成本。 同时能一定程度上接受数据的不一致性,那可以做读写分离的。
redis cluster
特性
2、节点的fail是通过集群中超过半数的节点检测失效时才生效。
cluster addslots {slot_index1} {slot_index 2} {slot_index 3}
5、Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
每个Redis实例都知道其他节点的存在
无法保证强一致性
1、你的客户端写给主服务器节点 B2、主服务器节点B向您的客户端回复确认。3、主服务器节点B将写入传播到它的从服务器B1,B2和B3。
容错
故障转移
1. 下线的主节点的所有从节点里面,会进行选举,选举出一个新的主节点。2. 被选中的从节点会执行 slave no one命令,成为新的主节点。3. 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽指派给自己。4. 新的主节点向集群广播一条pong消息,这条pong消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点处理的槽。5.新的主节点开始接受和自己负责处理的槽有关的命令请求,故障转移操作完成。
主从选举
1. 当从节点发现自己复制的主节点进入已下线时,从节点(这里发出请求的从节点可能会有多个)会向集群广播一条cluster_type_failover_auth_request的消息,要求有投票权(负责处理槽)的主节点向这个节点进行投票。2.收到cluster_type_failover_auth_request消息的主节点,根据自身条件(发起投票节点的current epoch不低于投票节点的current epoch)判断是否赞成该从节点成为新的主节点,若赞成则返回一条cluster_type_failover_auth_ack消息。3. 从节点接收到cluster_type_failover_auth_ack消息,会将选票数加1。4.如果某个从节点的选票大于等于集群中主节点的一半时(大于等于N/2 + 1),这个节点就会成为新的主节点。如果在一个配置周期内,没有一个从节点获得足够多的选票,那么集群中会进入新的配置周期,并在此进行选举,知道选出新的主节点为止。
可能选不出来
局限
1.目前只支持同一个槽上的key的批量操作;2.目前只支持同一个槽上的key事务;3.只能使用数据库0(每个redis实例有16个数据库,可通过select {index}命令来切换);4.不能将一个大的key(如hash、list)映射到不同的节点上;5.目前集群主从复制只支持一层,不支持嵌套树状架构;
扩容时
步骤
1.对目标节点发送cluster setslot {slot_index} importing {source_node_id}2.对源节点发送cluster setslot {slot_index} migrating {target_node_id}3.源节点循环执行cluster getkeysinslot {slot_index} {count(key个数)}4.源节点执行,把key通过流水线(pipeline)迁移到目标节点migrate {target_ip} {target_port} "" 0 {timeout} keys {key1} {key2} {key3}5.重复3、4步骤6.向集群中所有主节点发送通知cluster setslot {slot_index} node {target_nodeid}
每个节点都知道每个槽对应的 cluster node .
Codis
分支主题
在Codis中,Codis会把所有的key分成1024个槽,这1024个槽对应着的就是Redis的集群,这个在Codis中是会在内存中维护着这1024个槽与Redis实例的映射关系。这个槽是可以配置,可以设置成 2048 或者是4096个。看你的Redis的节点数量有多少,偏多的话,可以设置槽多一些。
当Codis的Codis Dashbord 改变槽位的信息的时候,其他的Codis节点会监听到ZooKeeper的槽位变化,会及时同步过来。如图:
zk负责同步槽位信息.
优先级队列
sorted set
list
使用多个队列实现.优先级队列.不同的优先级任务进入到不同的队列
阻塞似
启动多个消费者就是启动多个 client取数据
消息队列
pubsub
使用
风险
数据可靠性的无法保障
无法保证至少一次
扩展不灵活,没法通过多加consumer来加快消费的进度
1、当给定列表内没有任何元素可供弹出的时候,连接将被 BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止。 2、当给定多个key参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的尾部元素。另外,BRPOP 除了弹出元素的位置和 BLPOP 不同之外,其他表现一致。
如果list中没有任务时, 该连接将会被阻塞连接的阻塞有一个超时时间 , 当超时时间设置为0时 , 即可无线等待, 直到弹出消息
使用 pubsub 也可以通知消费者可以去 list中消费了
ack实现
维护两个队列: pending队列和doing表(hash表)。
workers定义为ThreadPool。由pending队列出队后,workers分配一个线程(单个worker)去处理消息——给目标消息append一个当前时间戳和当前线程名称,将其写入doing表,然后该worker去消费消息,完成后自行在doing表擦除信息。
启用一个定时任务,每隔一段时间去扫描doing队列,检查每隔元素的时间戳,如果超时,则由worker的ThreadPoolExecutor去检查线程是否存在,如果存在则取消当前任务执行,并把事务rollback。最后把该任务从doing队列中pop出,再重新push进pending队列。
可以使用zset 作排序.
扫码回复【脑图】下载 120 张超清晰脑图源文件
缓存一致性问题
(1)缓存刚好失效(2)请求A查询数据库,得一个旧值(3)请求B将新值写入数据库(4)请求B删除缓存(5)请求A将查到的旧值写入缓存
以上会发生脏数据
先删缓存再写数据库
(1)先淘汰缓存(2)再写数据库(这两步和原来一样)(3)休眠1秒,再次淘汰缓存
使用异步双删除
基本操作
常见操作
自由主题
数据类型
Key
从一个redis实例迁移到另一个redis实例
Set
Sdiff 获取集合不存在的元素 SDiff a b 相当于a-b
Sdiff Store 将差集存储到一个key中
SInter 取交集 SInterStore 将交集写入到一个key中
List
可指定从若干个队列中获取值
阻塞操作可以保证公平性
是否可实现公平的分布式锁
有序集合
返回有序集合中某key的排名
可以获取有序集合的某一范围的值; 也可以指定分数的最小最大值分页查询该范围的集合
迭代集合
Transaction
watch
由于WATCH命令的作用只是当被监控的键值被修改后阻止之后一个事务的执行,而不能保证其他客户端不修改这一键值,所以在一般的情况下我们需要在EXEC执行失败后重新执行整个函数。用循环保证
即使事务中有某条/某些命令执行失败了, 事务队列中的其他命令仍然会继续执行 —— Redis 不会停止执行事务中的命令。
从Mutil命令开始到exec 命令中执行的命令都会被入到事务队列
Hash
递增hash 某key的值 如果 该key不存在就设置其为0+ incre value
数据结构坑点
分布式锁
单节点方案(非红锁)
获取锁
hash 到一个 cluster 节点
加锁是 value 设置 client id
解锁
如果不设置过期时间.会导致出现死锁问题
a) 一旦redis发生主从切换,可能会丢失一些锁,
和zk 比较
Zookeeper实现的分布式锁其实存在一个缺点,那就是性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。并发问题,可能存在网络抖动,客户端和ZK集群的session连接断了,zk集群以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了
Redis很难实现公平性
redlock
在算法的分布式版本中,我们假设我们有N个Redis主机。这些节点完全独立,因此我们不使用复制或任何其他隐式协调系统。我们已经描述了如何在单个实例中安全地获取和释放锁。我们理所当然地认为算法将使用此方法在单个实例中获取和释放锁。在我们的示例中,我们设置N = 5,这是一个合理的值,因此我们需要在不同的计算机或虚拟机上运行5个Redis主服务器,以确保它们以大多数独立的方式失败。为了获取锁,客户端执行以下操作:1. 它以毫秒为单位获取当前时间。2. 它尝试按顺序获取所有N个实例中的锁,在所有实例中使用相同的键名和随机值。在步骤2期间,当在每个实例中设置锁定时,客户端使用与总锁定自动释放时间相比较小的超时以获取它。例如,如果自动释放时间是10秒,则超时可以在~5-50毫秒范围内。这可以防止客户端长时间保持阻塞状态,试图与Redis节点通话失败:如果实例不可用,我们应该尝试尽快与下一个实例通话。3. 客户端通过从当前时间中减去在步骤1中获得的时间戳来计算获取锁定所需的时间。当且仅当客户端能够在大多数实例中获取锁定时(至少3个)并且获取锁定所经过的总时间小于锁定有效时间,认为锁定被获取。4. 如果获得了锁,则其有效时间被认为是初始有效时间减去经过的时间,如步骤3中计算的。5. 如果客户端由于某种原因(无法锁定N / 2 + 1实例或有效时间为负)未能获取锁定,它将尝试解锁所有实例(即使它认为不是能够锁定)。
缓存
失效
数据结构的实现
应用场景
zset
记录用户帖子id 列表.快速显示
hash
记录帖子的相关文章列表.根据内容推荐相关帖子
bitmap
HyperLogLog
用于粗略计算去重之后的统计值.
布隆过滤器
用于去重.获取某个元素是否存在于列表中
考虑一定给用户推荐未访问过得资源
考虑不重复多次使用资源.
不存在一定不存在!!!!
只有add和 exist(可查多个)命令
Redis-cell
项目不大的,维护成本不高的话,可以采用 直接使用 redsi-cell ,否则可以考虑细粒度的控制到每个服务节点去限流,配合相应的负载均衡策略去实现
CL.THROTTLE test 100 400 60 3
test key 容量100(最大并发) 60s内最多400次. 本次申请3个容量
1: 是否成功,0:成功,1:拒绝2: 令牌桶的容量,大小为初始值+13: 当前令牌桶中可用的令牌4: 若请求被拒绝,这个值表示多久后才漏斗桶中会重新添加令牌,单位:秒,可以作为重试时间5: 表示多久后令牌桶中的令牌会存满
漏斗算法
特殊的数据结构
压缩链表
为了尽可能节约内存设计出来的双向链表
存储字符串或整数
在细节上节省内存,对于值的存储采用了变长的编码方式
每一项会有单独的位数标记该项的数据长度和类型
hash、list、zset底层数据结构实现
特点
1) 内部表现为数据紧凑排列的一块连续内存数组。
2) 可以模拟双向链表结构,以O(1)时间复杂度入队和出队。
3) 新增删除操作涉及内存重新分配或释放,加大了操作的复杂性。
4) 读写操作涉及复杂的指针移动,最坏时间复杂度为O(n2)。
5) 适合存储小对象和长度有限的数据。
1)针对性能要求较高的场景使用ziplist,建议长度不要超过1000,每个元素大小控制在512字节以内
跳跃表
跳表平均支持 O(logN),最坏O(N)复杂度的查找
\b跳跃表的实现很简单就能达到 O(logN)的级别
快速链表
每一个ziplist 默认是8k.(可配)
0 条评论
下一页