做网站 郑州公司哪家好,哪个购物网站最便宜,怎么以公司名义注册邮箱,网站开发的api文章目录1. Redis1.1 Redis可以用来做什么#xff1f;1.2 Redis和传统的关系型数据库有什么不同#xff1f;1.3 Redis有哪些数据类型#xff1f;1.4 Redis是单线程的#xff0c;为什么还能这么快#xff1f;1.5 Redis在持久化时fork出一个子进程#xff0c;这时已经有两个…
文章目录1. Redis1.1 Redis可以用来做什么1.2 Redis和传统的关系型数据库有什么不同1.3 Redis有哪些数据类型1.4 Redis是单线程的为什么还能这么快1.5 Redis在持久化时fork出一个子进程这时已经有两个进程了怎么能说是单线程呢1.6 set和zset有什么区别1.7 说一下Redis中的watch命令1.8 说说Redis中List结构的相关操作1.9 你要如何设计Redis的过期时间1.10 Redis中sexnx命令的返回值是什么如何使用该命令实现分布式锁1.11 说一说Redis的持久化策略1.12 如何实现Redis的高可用1.13 Redis的主从同步是如何实现的1.14 Redis为什么存的快内存断电数据怎么恢复1.15 说一说Redis的缓存淘汰策略1.16 请介绍一下Redis的过期策略1.17 缓存穿透、缓存击穿、缓存雪崩有什么区别该如何解决1.18 如何保证缓存与数据库的双写一致性1.19 请介绍Redis集群的实现方案1.20 说一说Redis集群的分片机制1.21 说一说Redis集群的应用和优劣势1.22 说一说hash类型底层的数据结构1.23 介绍一下zset类型底层的数据结构1.24 如何利用Redis实现分布式Session1.25 如何利用Redis实现一个分布式锁1.26 说一说你对布隆过滤器的理解1.27 多台Redis抗高并发访问该怎么设计1.28 如果并发量超过30万怎么设计Redis架构1.27 多台Redis抗高并发访问该怎么设计1.28 如果并发量超过30万怎么设计Redis架构1. Redis
1.1 Redis可以用来做什么
参考答案
Redis最常用来做缓存是实现分布式缓存的首先中间件Redis可以作为数据库实现诸如点赞、关注、排行等对性能要求极高的互联网需求Redis可以作为计算工具能用很小的代价统计诸如PV/UV、用户在线天数等数据Redis还有很多其他的使用场景例如可以实现分布式锁可以作为消息队列使用。
1.2 Redis和传统的关系型数据库有什么不同
参考答案
Redis是一种基于键值对的NoSQL数据库而键值对的值是由多种数据结构和算法组成的。Redis的数据都存储于内存中因此它的速度惊人读写性能可达10万/秒远超关系型数据库。
关系型数据库是基于二维数据表来存储数据的它的数据格式更为严谨并支持关系查询。关系型数据库的数据存储于磁盘上可以存放海量的数据但性能远不如Redis。
1.3 Redis有哪些数据类型
参考答案
Redis支持5种核心的数据类型分别是字符串、哈希、列表、集合、有序集合Redis还提供了Bitmap、HyperLogLog、Geo类型但这些类型都是基于上述核心数据类型实现的Redis在5.0新增加了Streams数据类型它是一个功能强大的、支持多播的、可持久化的消息队列。
1.4 Redis是单线程的为什么还能这么快
参考答案
对服务端程序来说线程切换和锁通常是性能杀手而单线程避免了线程切换和竞争所产生的消耗Redis的大部分操作是在内存上完成的这是它实现高性能的一个重要原因Redis采用了IO多路复用机制使其在网络IO操作中能并发处理大量的客户端请求实现高吞吐率。
关于Redis的单线程架构实现如下图 1.5 Redis在持久化时fork出一个子进程这时已经有两个进程了怎么能说是单线程呢
参考答案
Redis是单线程的主要是指Redis的网络IO和键值对读写是由一个线程来完成的。而Redis的其他功能如持久化、异步删除、集群数据同步等则是依赖其他线程来执行的。所以说Redis是单线程的只是一种习惯的说法事实上它的底层不是单线程的。
1.6 set和zset有什么区别
参考答案
set
集合中的元素是无序、不可重复的一个集合最多能存储232-1个元素集合除了支持对元素的增删改查之外还支持对多个集合取交集、并集、差集。
zset
有序集合保留了集合元素不能重复的特点有序集合会给每个元素设置一个分数并以此作为排序的依据有序集合不能包含相同的元素但是不同元素的分数可以相同。
1.7 说一下Redis中的watch命令
参考答案
很多时候要确保事务中的数据没有被其他客户端修改才执行该事务。Redis提供了watch命令来解决这类问题这是一种乐观锁的机制。客户端通过watch命令要求服务器对一个或多个key进行监视如果在客户端执行事务之前这些key发生了变化则服务器将拒绝执行客户端提交的事务并向它返回一个空值。
1.8 说说Redis中List结构的相关操作
参考答案
列表是线性有序的数据结构它内部的元素是可以重复的并且一个列表最多能存储2^32-1个元素。列表包含如下的常用命令
lpush/rpush从列表的左侧/右侧添加数据lrange指定索引范围并返回这个范围内的数据lindex返回指定索引处的数据lpop/rpop从列表的左侧/右侧弹出一个数据blpop/brpop从列表的左侧/右侧弹出一个数据若列表为空则进入阻塞状态。
1.9 你要如何设计Redis的过期时间
参考答案
热点数据不设置过期时间使其达到“物理”上的永不过期可以避免缓存击穿问题在设置过期时间时可以附加一个随机数避免大量的key同时过期导致缓存雪崩。
1.10 Redis中sexnx命令的返回值是什么如何使用该命令实现分布式锁
参考答案
setnx命令返回整数值当返回1时表示设置值成果当返回0时表示设置值失败key已存在。
一般我们不建议直接使用setnx命令来实现分布式锁因为为了避免出现死锁我们要给锁设置一个自动过期时间。而setnx命令和设置过期时间的命令不是原子的可能加锁成果而设置过期时间失败依然存在死锁的隐患。对于这种情况Redis改进了set命令给它增加了nx选项启用该选项时set命令的效果就会setnx一样了。
采用Redis实现分布式锁就是在Redis里存一份代表锁的数据通常用字符串即可。采用改进后的setnx命令即set...nx...命令实现分布式锁的思路以及优化的过程如下
加锁
第一版这种方式的缺点是容易产生死锁因为客户端有可能忘记解锁或者解锁失败。
setnx key value第二版给锁增加了过期时间避免出现死锁。但这两个命令不是原子的第二步可能会失败依然无法避免死锁问题。
setnx key valueexpire key seconds第三版通过“set…nx…”命令将加锁、过期命令编排到一起它们是原子操作了可以避免死锁。
set key value nx ex seconds解锁
解锁就是删除代表锁的那份数据。
del key问题
看起来已经很完美了但实际上还有隐患如下图。进程A在任务没有执行完毕时锁已经到期被释放了。等进程A的任务执行结束后它依然会尝试释放锁因为它的代码逻辑就是任务结束后释放锁。但是它的锁早已自动释放过了它此时释放的可能是其他线程的锁。 想要解决这个问题我们需要解决两件事情
在加锁时就要给锁设置一个标识进程要记住这个标识。当进程解锁的时候要进行判断是自己持有的锁才能释放否则不能释放。可以为key赋一个随机值来充当进程的标识。解锁时要先判断、再释放这两步需要保证原子性否则第二步失败的话就会出现死锁。而获取和删除命令不是原子的这就需要采用Lua脚本通过Lua脚本将两个命令编排在一起而整个Lua脚本的执行是原子的。
按照以上思路优化后的命令如下
# 加锁
set key random-value nx ex seconds # 解锁
if redis.call(get,KEYS[1]) ARGV[1] thenreturn redis.call(del,KEYS[1])
elsereturn 0
end1.11 说一说Redis的持久化策略
参考答案
Redis支持RDB持久化、AOF持久化、RDB-AOF混合持久化这三种持久化方式。
RDB
RDB(Redis Database)是Redis默认采用的持久化方式它以快照的形式将进程数据持久化到硬盘中。RDB会创建一个经过压缩的二进制文件文件以“.rdb”结尾内部存储了各个数据库的键值对数据等信息。RDB持久化的触发方式有两种
手动触发通过SAVE或BGSAVE命令触发RDB持久化操作创建“.rdb”文件自动触发通过配置选项让服务器在满足指定条件时自动执行BGSAVE命令。
其中SAVE命令执行期间Redis服务器将阻塞直到“.rdb”文件创建完毕为止。而BGSAVE命令是异步版本的SAVE命令它会使用Redis服务器进程的子进程创建“.rdb”文件。BGSAVE命令在创建子进程时会存在短暂的阻塞之后服务器便可以继续处理其他客户端的请求。总之BGSAVE命令是针对SAVE阻塞问题做的优化Redis内部所有涉及RDB的操作都采用BGSAVE的方式而SAVE命令已经废弃
BGSAVE命令的执行流程如下图 BGSAVE命令的原理如下图 RDB持久化的优缺点如下 优点RDB生成紧凑压缩的二进制文件体积小使用该文件恢复数据的速度非常快 缺点BGSAVE每次运行都要执行fork操作创建子进程属于重量级操作不宜频繁执行 所以RDB持久化没办法做到实时的持久化。
AOF
AOFAppend Only File解决了数据持久化的实时性是目前Redis持久化的主流方式。AOF以独立日志的方式记录了每次写入命令重启时再重新执行AOF文件中的命令来恢复数据。AOF的工作流程包括命令写入append、文件同步sync、文件重写rewrite、重启加载load如下图 AOF默认不开启需要修改配置项来启用它
appendonly yes # 启用AOF
appendfilename appendonly.aof # 设置文件名AOF以文本协议格式写入命令如
*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n文本协议格式具有如下的优点
文本协议具有很好的兼容性直接采用文本协议格式可以避免二次处理的开销文本协议具有可读性方便直接修改和处理。
AOF持久化的文件同步机制
为了提高程序的写入性能现代操作系统会把针对硬盘的多次写操作优化为一次写操作。
当程序调用write对文件写入时系统不会直接把书记写入硬盘而是先将数据写入内存的缓冲区中当达到特定的时间周期或缓冲区写满时系统才会执行flush操作将缓冲区中的数据冲洗至硬盘中
这种优化机制虽然提高了性能但也给程序的写入操作带来了不确定性。
对于AOF这样的持久化功能来说冲洗机制将直接影响AOF持久化的安全性为了消除上述机制的不确定性Redis向用户提供了appendfsync选项来控制系统冲洗AOF的频率Linux的glibc提供了fsync函数可以将指定文件强制从缓冲区刷到硬盘上述选项正是基于此函数。
appendfsync选项的取值和含义如下 AOF持久化的优缺点如下
优点与RDB持久化可能丢失大量的数据相比AOF持久化的安全性要高很多。通过使用everysec选项用户可以将数据丢失的时间窗口限制在1秒之内。缺点AOF文件存储的是协议文本它的体积要比二进制格式的”.rdb”文件大很多。AOF需要通过执行AOF文件中的命令来恢复数据库其恢复速度比RDB慢很多。AOF在进行重写时也需要创建子进程在数据库体积较大时将占用大量资源会导致服务器的短暂阻塞。
RDB-AOF混合持久化
Redis从4.0开始引入RDB-AOF混合持久化模式这种模式是基于AOF持久化构建而来的。用户可以通过配置文件中的“aof-use-rdb-preamble yes”配置项开启AOF混合持久化。Redis服务器在执行AOF重写操作时会按照如下原则处理数据
像执行BGSAVE命令一样根据数据库当前的状态生成相应的RDB数据并将其写入AOF文件中对于重写之后执行的Redis命令则以协议文本的方式追加到AOF文件的末尾即RDB数据之后。
通过使用RDB-AOF混合持久化用户可以同时获得RDB持久化和AOF持久化的优点服务器既可以通过AOF文件包含的RDB数据来实现快速的数据恢复操作又可以通过AOF文件包含的AOF数据来将丢失数据的时间窗口限制在1s之内。
1.12 如何实现Redis的高可用
参考答案
实现Redis的高可用主要有哨兵和集群两种方式。
哨兵
Redis Sentinel哨兵是一个分布式架构它包含若干个哨兵节点和数据节点。每个哨兵节点会对数据节点和其余的哨兵节点进行监控当发现节点不可达时会对节点做下线标识。如果被标识的是主节点它就会与其他的哨兵节点进行协商当多数哨兵节点都认为主节点不可达时它们便会选举出一个哨兵节点来完成自动故障转移的工作同时还会将这个变化实时地通知给应用方。整个过程是自动的不需要人工介入有效地解决了Redis的高可用问题
一组哨兵可以监控一个主节点也可以同时监控多个主节点两种情况的拓扑结构如下图 哨兵节点包含如下的特征
哨兵节点会定期监控数据节点其他哨兵节点是否可达哨兵节点会将故障转移的结果通知给应用方哨兵节点可以将从节点晋升为主节点并维护后续正确的主从关系哨兵模式下客户端连接的是哨兵节点集合从中获取主节点信息节点的故障判断是由多个哨兵节点共同完成的可有效地防止误判哨兵节点集合是由多个哨兵节点组成的即使个别哨兵节点不可用整个集合依然是健壮的哨兵节点也是独立的Redis节点是特殊的Redis节点它们不存储数据只支持部分命令。
集群
Redis集群采用虚拟槽分区来实现数据分片它把所有的键根据哈希函数映射到0-16383整数槽内计算公式为slotCRC16(key)16383每一个节点负责维护一部分槽以及槽所映射的键值数据。虚拟槽分区具有如下特点
解耦数据和节点之间的关系简化了节点扩容和收缩的难度节点自身维护槽的映射关系不需要客户端或者代理服务维护槽分区元数据支持节点、槽、键之间的映射查询用于数据路由在线伸缩等场景。
Redis集群中数据的分片逻辑如下图 1.13 Redis的主从同步是如何实现的
参考答案
从2.8版本开始Redis使用psync命令完成主从数据同步同步过程分为全量复制和部分复制。全量复制一般用于初次复制的场景部分复制则用于处理因网络中断等原因造成数据丢失的场景。psync命令需要以下参数的支持
复制偏移量主节点处理写命令后会把命令长度做累加记录从节点在接收到写命令后也会做累加记录从节点会每秒钟上报一次自身的复制偏移量给主节点而主节点则会保存从节点的复制偏移量。积压缓冲区保存在主节点上的一个固定长度的队列默认大小为1M当主节点有连接的从节点时被创建主节点处理写命令时不但会把命令发送给从节点还会写入积压缓冲区缓冲区是先进先出的队列可以保存最近已复制的数据用于部分复制和命令丢失的数据补救。主节点运行ID每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID如果使用IP和端口的方式标识主节点那么主节点重启变更了数据集RDB/AOF从节点再基于复制偏移量复制数据将是不安全的因此当主节点的运行ID变化后从节点将做全量复制。
psync命令的执行过程以及返回结果如下图 全量复制的过程如下图 部分复制的过程如下图 1.14 Redis为什么存的快内存断电数据怎么恢复
参考答案
Redis存的快是因为它的数据都存放在内存里并且为了保证数据的安全性Redis还提供了三种数据的持久化机制即RDB持久化、AOF持久化、RDB-AOF混合持久化。若服务器断电那么我们可以利用持久化文件对数据进行恢复。理论上来说AOF/RDB-AOF持久化可以将丢失数据的窗口控制在1S之内。
1.15 说一说Redis的缓存淘汰策略
参考答案
当写入数据将导致超出maxmemory限制时Redis会采用maxmemory-policy所指定的策略进行数据淘汰该策略一共包含如下8种选项
策略描述版本noeviction直接返回错误volatile-ttl从设置了过期时间的键中选择过期时间最小的键进行淘汰volatile-random从设置了过期时间的键中随机选择键进行淘汰volatile-lru从设置了过期时间的键中使用LRU算法选择键进行淘汰volatile-lfu从设置了过期时间的键中使用LFU算法选择键进行淘汰4.0allleys-random从所有的键中随机选择键进行淘汰allkeys-lru从所有的键中使用LRU算法选择键进行淘汰allkeys-lfu从所有的键中使用LFU算法选择键进行淘汰4.0
其中volatile前缀代表从设置了过期时间的键中淘汰数据allkeys前缀代表从所有的键中淘汰数据。关于后缀ttl代表选择过期时间最小的键random代表随机选择键需要我们额外关注的是lru和lfu后缀它们分别代表采用lru算法和lfu算法来淘汰数据。
LRULeast Recently Used是按照最近最少使用原则来筛选数据即最不常用的数据会被筛选出来
标准LRU把所有的数据组成一个链表表头和表尾分别表示MRU和LRU端即最常使用端和最少使用端。刚被访问的数据会被移动到MRU端而新增的数据也是刚被访问的数据也会被移动到MRU端。当链表的空间被占满时它会删除LRU端的数据。近似LRURedis会记录每个数据的最近一次访问的时间戳LRU。Redis执行写入操作时若发现内存超出maxmemory就会执行一次近似LRU淘汰算法。近似LRU会随机采样N个key然后淘汰掉最旧的key若淘汰后内存依然超出限制则继续采样淘汰。可以通过maxmemory_samples配置项设置近似LRU每次采样的数据个数该配置项的默认值为5。
LRU算法的不足之处在于若一个key很少被访问只是刚刚偶尔被访问了一次则它就被认为是热点数据短时间内不会被淘汰。
LFU算法正式用于解决上述问题LFULeast Frequently Used是Redis4新增的淘汰策略它根据key的最近访问频率进行淘汰。LFU在LRU的基础上为每个数据增加了一个计数器来统计这个数据的访问次数。当使用LFU策略淘汰数据时首先会根据数据的访问次数进行筛选把访问次数最低的数据淘汰出内存。如果两个数据的访问次数相同LFU再比较这两个数据的访问时间把访问时间更早的数据淘汰出内存。
1.16 请介绍一下Redis的过期策略
参考答案
Redis支持如下两种过期策略
惰性删除客户端访问一个key的时候Redis会先检查它的过期时间如果发现过期就立刻删除这个key。
定期删除Redis会将设置了过期时间的key放到一个独立的字典中并对该字典进行每秒10次的过期扫描
过期扫描不会遍历字典中所有的key而是采用了一种简单的贪心策略。该策略的删除逻辑如下
从过期字典中随机选择20个key删除这20个key中已过期的key如果已过期key的比例超过25%则重复步骤1。
1.17 缓存穿透、缓存击穿、缓存雪崩有什么区别该如何解决
参考答案
缓存穿透
问题描述
客户端查询根本不存在的数据使得请求直达存储层导致其负载过大甚至宕机。出现这种情况的原因可能是业务层误将缓存和库中的数据删除了也可能是有人恶意攻击专门访问库中不存在的数据。
解决方案
缓存空对象存储层未命中后仍然将空值存入缓存层客户端再次访问数据时缓存层会直接返回空值。布隆过滤器将数据存入布隆过滤器访问缓存之前以过滤器拦截若请求的数据不存在则直接返回空值。
缓存击穿
问题描述
一份热点数据它的访问量非常大。在其缓存失效的瞬间大量请求直达存储层导致服务崩溃。
解决方案
永不过期热点数据不设置过期时间所以不会出现上述问题这是“物理”上的永不过期。或者为每个数据设置逻辑过期时间当发现该数据逻辑过期时使用单独的线程重建缓存。加互斥锁对数据的访问加互斥锁当一个线程访问该数据时其他线程只能等待。这个线程访问过后缓存中的数据将被重建届时其他线程就可以直接从缓存中取值。
缓存雪崩
问题描述
在某一时刻缓存层无法继续提供服务导致所有的请求直达存储层造成数据库宕机。可能是缓存中有大量数据同时过期也可能是Redis节点发生故障导致大量请求无法得到处理。
解决方案
避免数据同时过期设置过期时间时附加一个随机数避免大量的key同时过期。启用降级和熔断措施在发生雪崩时若应用访问的不是核心数据则直接返回预定义信息/空值/错误信息。或者在发生雪崩时对于访问缓存接口的请求客户端并不会把请求发给Redis而是直接返回。构建高可用的Redis服务采用哨兵或集群模式部署多个Redis实例个别节点宕机依然可以保持服务的整体可用。
1.18 如何保证缓存与数据库的双写一致性
参考答案
四种同步策略
想要保证缓存与数据库的双写一致一共有4种方式即4种同步策略
先更新缓存再更新数据库先更新数据库再更新缓存先删除缓存再更新数据库先更新数据库再删除缓存。
从这4种同步策略中我们需要作出比较的是
更新缓存与删除缓存哪种方式更合适应该先操作数据库还是先操作缓存
更新缓存还是删除缓存
下面我们来分析一下应该采用更新缓存还是删除缓存的方式。 更新缓存 优点每次数据变化都及时更新缓存所以查询时不容易出现未命中的情况。 缺点更新缓存的消耗比较大。如果数据需要经过复杂的计算再写入缓存那么频繁的更新缓存就会影响服务器的性能。如果是写入数据频繁的业务场景那么可能频繁的更新缓存时却没有业务读取该数据。 删除缓存 优点操作简单无论更新操作是否复杂都是将缓存中的数据直接删除。 缺点删除缓存后下一次查询缓存会出现未命中这时需要重新读取一次数据库。
从上面的比较来看一般情况下删除缓存是更优的方案。
先操作数据库还是缓存
下面我们再来分析一下应该先操作数据库还是先操作缓存。
首先我们将先删除缓存与先更新数据库在出现失败时进行一个对比 如上图是先删除缓存再更新数据库在出现失败时可能出现的问题
进程A删除缓存成功进程A更新数据库失败进程B从缓存中读取数据由于缓存被删进程B无法从缓存中得到数据进而从数据库读取数据进程B从数据库成功获取数据然后将数据更新到了缓存。
最终缓存和数据库的数据是一致的但仍然是旧的数据。而我们的期望是二者数据一致并且是新的数据。 如上图是先更新数据库再删除缓存在出现失败时可能出现的问题
进程A更新数据库成功进程A删除缓存失败进程B读取缓存成功由于缓存删除失败所以进程B读取到的是旧的数据。
最终缓存和数据库的数据是不一致的。
经过上面的比较我们发现在出现失败的时候是无法明确分辨出先删缓存和先更新数据库哪个方式更好以为它们都存在问题。后面我们会进一步对这两种方式进行比较但是在这里我们先探讨一下上述场景出现的问题应该如何解决呢
实际上无论上面我们采用哪种方式去同步缓存与数据库在第二步出现失败的时候都建议采用重试机制解决因为最终我们是要解决掉这个错误的。而为了避免重试机制影响主要业务的执行一般建议重试机制采用异步的方式执行如下图 这里我们按照先更新数据库再删除缓存的方式来说明重试机制的主要步骤
更新数据库成功删除缓存失败将此数据加入消息队列业务代码消费这条消息业务代码根据这条消息的内容发起重试机制即从缓存中删除这条记录。
好了下面我们再将先删缓存与先更新数据库在没有出现失败时进行对比 如上图是先删除缓存再更新数据库在没有出现失败时可能出现的问题
进程A删除缓存成功进程B读取缓存失败进程B读取数据库成功得到旧的数据进程B将旧的数据成功地更新到了缓存进程A将新的数据成功地更新到数据库。
可见进程A的两步操作均成功但由于存在并发在这两步之间进程B访问了缓存。最终结果是缓存中存储了旧的数据而数据库中存储了新的数据二者数据不一致。 如上图是先更新数据库再删除缓存再没有出现失败时可能出现的问题
进程A更新数据库成功进程B读取缓存成功进程A更新数据库成功。
可见最终缓存与数据库的数据是一致的并且都是最新的数据。但进程B在这个过程里读到了旧的数据可能还有其他进程也像进程B一样在这两步之间读到了缓存中旧的数据但因为这两步的执行速度会比较快所以影响不大。对于这两步之后其他进程再读取缓存数据的时候就不会出现类似于进程B的问题了。
最终结论
经过对比你会发现先更新数据库、再删除缓存是影响更小的方案。如果第二步出现失败的情况则可以采用重试机制解决问题。
扩展阅读
延时双删
上面我们提到如果是先删缓存、再更新数据库在没有出现失败时可能会导致数据的不一致。如果在实际的应用中出于某些考虑我们需要选择这种方式那有办法解决这个问题吗答案是有的那就是采用延时双删的策略延时双删的基本思路如下
删除缓存更新数据库sleep N毫秒再次删除缓存。
阻塞一段时间之后再次删除缓存就可以把这个过程中缓存中不一致的数据删除掉。而具体的时间要评估你这项业务的大致时间按照这个时间来设定即可。
采用读写分离的架构怎么办
如果数据库采用的是读写分离的架构那么又会出现新的问题如下图 进程A先删除缓存再更新主数据库然后主库将数据同步到从库。而在主从数据库同步之前可能会有进程B访问了缓存发现数据不存在进而它去访问从库获取到旧的数据然后同步到缓存。这样最终也会导致缓存与数据库的数据不一致。这个问题的解决方案依然是采用延时双删的策略但是在评估延长时间的时候要考虑到主从数据库同步的时间。
第二次删除失败了怎么办
如果第二次删除依然失败则可以增加重试的次数但是这个次数要有限制当超出一定的次数时要采取报错、记日志、发邮件提醒等措施。
1.19 请介绍Redis集群的实现方案
参考答案
Redis集群的分区方案
Redis集群采用虚拟槽分区来实现数据分片它把所有的键根据哈希函数映射到0-16383整数槽内计算公式为slotCRC16(key)16383每一个节点负责维护一部分槽以及槽所映射的键值数据。虚拟槽分区具有如下特点
解耦数据和节点之间的关系简化了节点扩容和收缩的难度节点自身维护槽的映射关系不需要客户端或者代理服务维护槽分区元数据支持节点、槽、键之间的映射查询用于数据路由在线伸缩等场景。
Redis集群中数据的分片逻辑如下图 Redis集群的功能限制
Redis集群方案在扩展了Redis处理能力的同时也带来了一些使用上的限制
key批量操作支持有限。如mset、mget目前只支持具有相同slot值的key执行批量操作。对于映射为不同slot值的key由于执行mset、mget等操作可能存在于多个节点上所以不被支持。key事务操作支持有限。同理只支持多key在同一节点上的事务操作当多个key分布在不同的节点上时无法使用事务功能。key作为数据分区的最小粒度因此不能将一个大的键值对象如hash、list等映射到不同的节点。不支持多数据库空间。单机下的Redis可以支持16个数据库集群模式下只能使用一个数据库空间即DB0。复制结构只支持一层从节点只能复制主节点不支持嵌套树状复制结构。
Redis集群的通信方案
在分布式存储中需要提供维护节点元数据信息的机制所谓元数据是指节点负责哪些数据是否出现故障等状态信息。常见的元数据维护方式分为集中式和P2P方式。
Redis集群采用P2P的Gossip流言协议Gossip协议的工作原理就是节点彼此不断通信交换信息一段时间后所有的节点都会知道集群完整的信息这种方式类似流言传播。通信的大致过程如下
集群中每个节点都会单独开辟一个TCP通道用于节点之间彼此通信通信端口号在基础端口号上加10000每个节点再固定周期内通过特定规则选择几个节点发送ping消息接收ping消息的节点用pong消息作为响应。
其中Gossip协议的主要职责就是信息交换而信息交换的载体就是节点彼此发送的Gossip消息Gossip消息分为meet消息、ping消息、pong消息、fail消息等。
meet消息用于通知新节点加入消息发送者通知接受者加入到当前集群。meet消息通信正常完成后接收节点会加入到集群中并进行周期性的ping、pong消息交换。ping消息集群内交换最频繁的消息集群内每个节点每秒向多个其他节点发送ping消息用于检测节点是否在线和交换彼此状态信息。ping消息封装了自身节点和一部分其他节点的状态数据。pong消息当接收到meet、ping消息时作为响应消息回复给发送方确认消息正常通信。pong消息内封装了自身状态数据节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。fail消息当节点判定集群内另一个节点下线时会向集群内广播一个fail消息其他节点接收到fail消息之后把对应节点更新为下线状态。
虽然Gossip协议的信息交换机制具有天然的分布式特性但它是有成本的。因为Redis集群内部需要频繁地进行节点信息交换而ping/pong消息会携带当前节点和部分其他节点的状态数据势必会加重带宽和计算的负担。所以Redis集群的Gossip协议需要兼顾信息交换的实时性和成本的开销。
集群里的每个节点默认每隔一秒钟就会从已知节点列表中随机选出五个节点然后对这五个节点中最长时间没有发送过PING消息的节点发送PING消息以此来检测被选中的节点是否在线。如果节点A最后一次收到节点B发送的PONG消息的时间距离当前时间已经超过了节点A的超时选项设置时长的一半cluster-node-timeout/2那么节点A也会向节点B发送PING消息这可以防止节点A因为长时间没有随机选中节点B作为PING消息的发送对象而导致对节点B的信息更新滞后。每个消息主要的数据占用slots槽数组2KB和整个集群1/10的状态数据10个节点状态数据约1KB。
1.20 说一说Redis集群的分片机制
参考答案
Redis集群采用虚拟槽分区来实现数据分片它把所有的键根据哈希函数映射到0-16383整数槽内计算公式为slotCRC16(key)16383每一个节点负责维护一部分槽以及槽所映射的键值数据。虚拟槽分区具有如下特点
解耦数据和节点之间的关系简化了节点扩容和收缩的难度节点自身维护槽的映射关系不需要客户端或者代理服务维护槽分区元数据支持节点、槽、键之间的映射查询用于数据路由在线伸缩等场景。
Redis集群中数据的分片逻辑如下图 1.21 说一说Redis集群的应用和优劣势
参考答案
优势
Redis Cluster是Redis的分布式解决方案在3.0版本正式推出有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时可以采用Cluster架构方案达到负载均衡的目的。
劣势
Redis集群方案在扩展了Redis处理能力的同时也带来了一些使用上的限制
key批量操作支持有限。如mset、mget目前只支持具有相同slot值的key执行批量操作。对于映射为不同slot值的key由于执行mset、mget等操作可能存在于多个节点上所以不被支持。key事务操作支持有限。同理只支持多key在同一节点上的事务操作当多个key分布在不同的节点上时无法使用事务功能。key作为数据分区的最小粒度因此不能将一个大的键值对象如hash、list等映射到不同的节点。不支持多数据库空间。单机下的Redis可以支持16个数据库集群模式下只能使用一个数据库空间即DB0。复制结构只支持一层从节点只能复制主节点不支持嵌套树状复制结构。
1.22 说一说hash类型底层的数据结构
参考答案
哈希对象有两种编码方案当同时满足以下条件时哈希对象采用ziplist编码否则采用hashtable编码
哈希对象保存的键值对数量小于512个哈希对象保存的所有键值对中的键和值其字符串长度都小于64字节。
其中ziplist编码采用压缩列表作为底层实现而hashtable编码采用字典作为底层实现。
压缩列表
压缩列表ziplist是Redis为了节约内存而设计的一种线性数据结构它是由一系列具有特殊编码的连续内存块构成的。一个压缩列表可以包含任意多个节点每个节点可以保存一个字节数组或一个整数值。
压缩列表的结构如下图所示 该结构当中的字段含义如下表所示
属性类型长度说明zlbytesuint32_t4字节压缩列表占用的内存字节数zltailuint32_t4字节压缩列表表尾节点距离列表起始地址的偏移量单位字节zllenuint16_t2字节压缩列表包含的节点数量等于UINT16_MAX时需遍历列表计算真实数量entryX列表节点不固定压缩列表包含的节点节点的长度由节点所保存的内容决定zlenduint8_t1字节压缩列表的结尾标识是一个固定值0xFF
其中压缩列表的节点由以下字段构成 previous_entry_lengthpel属性以字节为单位记录当前节点的前一节点的长度其自身占据1字节或5字节
如果前一节点的长度小于254字节则“pel”属性的长度为1字节前一节点的长度就保存在这一个字节内如果前一节点的长度达到254字节则“pel”属性的长度为5字节其中第一个字节被设置为0xFE之后的四个字节用来保存前一节点的长度
基于“pel”属性程序便可以通过指针运算根据当前节点的起始地址计算出前一节点的起始地址从而实现从表尾向表头的遍历操作。
content属性负责保存节点的值字节数组或整数其类型和长度则由encoding属性决定它们的关系如下
encoding长度content00 xxxxxx1字节最大长度为26 -1的字节数组01 xxxxxx bbbbbbbb2字节最大长度为214-1的字节数组10 __ bbbbbbbb … … …5字节最大长度为232-1的字节数组11 0000001字节int16_t类型的整数11 0100001字节int32_t类型的整数11 1000001字节int64_t类型的整数11 1100001字节24位有符号整数11 1111101字节8位有符号整数11 11xxxx1字节没有content属性xxxx直接存[0,12]范围的整数值
字典
字典dict又称为散列表是一种用来存储键值对的数据结构。C语言没有内置这种数据结构所以Redis构建了自己的字典实现。
Redis字典的实现主要涉及三个结构体字典、哈希表、哈希表节点。其中每个哈希表节点保存一个键值对每个哈希表由多个哈希表节点构成而字典则是对哈希表的进一步封装。这三个结构体的关系如下图所示 其中dict代表字典dictht代表哈希表dictEntry代表哈希表节点。可以看出dictEntry是一个数组这很好理解因为一个哈希表里要包含多个哈希表节点。而dict里包含2个dictht多出的哈希表用于REHASH。当哈希表保存的键值对数量过多或过少时需要对哈希表的大小进行扩展或收缩操作在Redis中扩展和收缩哈希表是通过REHASH实现的执行REHASH的大致步骤如下 为字典的ht[1]哈希表分配内存空间 如果执行的是扩展操作则ht[1]的大小为第1个大于等于ht[0].used*2的2n。如果执行的是收缩操作则ht[1]的大小为第1个大于等于ht[0].used的2n。 将存储在ht[0]中的数据迁移到ht[1]上 重新计算键的哈希值和索引值然后将键值对放置到ht[1]哈希表的指定位置上。 将字典的ht[1]哈希表晋升为默认哈希表 迁移完成后清空ht[0]再交换ht[0]和ht[1]的值为下一次REHASH做准备。
当满足以下任何一个条件时程序会自动开始对哈希表执行扩展操作
服务器目前没有执行bgsave或bgrewriteof命令并且哈希表的负载因子大于等于1服务器目前正在执行bgsave或bgrewriteof命令并且哈希表的负载因子大于等于5。
为了避免REHASH对服务器性能造成影响REHASH操作不是一次性地完成的而是分多次、渐进式地完成的。渐进式REHASH的详细过程如下
为ht[1]分配空间让字典同时持有ht[0]和ht[1]两个哈希表在字典中的索引计数器rehashidx设置为0表示REHASH操作正式开始在REHASH期间每次对字典执行添加、删除、修改、查找操作时程序除了执行指定的操作外还会顺带将ht[0]中位于rehashidx上的所有键值对迁移到ht[1]中再将rehashidx的值加1随着字典不断被访问最终在某个时刻ht[0]上的所有键值对都被迁移到ht[1]上此时程序将rehashidx属性值设置为-1标识REHASH操作完成。
REHSH期间字典同时持有两个哈希表此时的访问将按照如下原则处理
新添加的键值对一律被保存到ht[1]中删除、修改、查找等其他操作会在两个哈希表上进行即程序先尝试去ht[0]中访问要操作的数据若不存在则到ht[1]中访问再对访问到的数据做相应的处理。
1.23 介绍一下zset类型底层的数据结构
参考答案
有序集合对象有2种编码方案当同时满足以下条件时集合对象采用ziplist编码否则采用skiplist编码
有序集合保存的元素数量不超过128个有序集合保存的所有元素的成员长度都小于64字节。
其中ziplist编码的有序集合采用压缩列表作为底层实现skiplist编码的有序集合采用zset结构作为底层实现。
其中zset是一个复合结构它的内部采用字典和跳跃表来实现其源码如下。其中dict保存了从成员到分支的映射关系zsl则按分值由小到大保存了所有的集合元素。这样当按照成员来访问有序集合时可以直接从dict中取值当按照分值的范围访问有序集合时可以直接从zsl中取值采用了空间换时间的策略以提高访问效率。
typedef struct zset {dict *dict; // 字典保存了从成员到分值的映射关系zskiplist *zsl; // 跳跃表按分值由小到大保存所有集合元素
} zset;综上zset对象的底层数据结构包括压缩列表、字典、跳跃表。
压缩列表
压缩列表ziplist是Redis为了节约内存而设计的一种线性数据结构它是由一系列具有特殊编码的连续内存块构成的。一个压缩列表可以包含任意多个节点每个节点可以保存一个字节数组或一个整数值。
压缩列表的结构如下图所示 该结构当中的字段含义如下表所示
属性类型长度说明zlbytesuint32_t4字节压缩列表占用的内存字节数zltailuint32_t4字节压缩列表表尾节点距离列表起始地址的偏移量单位字节zllenuint16_t2字节压缩列表包含的节点数量等于UINT16_MAX时需遍历列表计算真实数量entryX列表节点不固定压缩列表包含的节点节点的长度由节点所保存的内容决定zlenduint8_t1字节压缩列表的结尾标识是一个固定值0xFF
其中压缩列表的节点由以下字段构成 previous_entry_lengthpel属性以字节为单位记录当前节点的前一节点的长度其自身占据1字节或5字节
如果前一节点的长度小于254字节则“pel”属性的长度为1字节前一节点的长度就保存在这一个字节内如果前一节点的长度达到254字节则“pel”属性的长度为5字节其中第一个字节被设置为0xFE之后的四个字节用来保存前一节点的长度
基于“pel”属性程序便可以通过指针运算根据当前节点的起始地址计算出前一节点的起始地址从而实现从表尾向表头的遍历操作。
content属性负责保存节点的值字节数组或整数其类型和长度则由encoding属性决定它们的关系如下
encoding长度content00 xxxxxx1字节最大长度为26 -1的字节数组01 xxxxxx bbbbbbbb2字节最大长度为214-1的字节数组10 __ bbbbbbbb … … …5字节最大长度为232-1的字节数组11 0000001字节int16_t类型的整数11 0100001字节int32_t类型的整数11 1000001字节int64_t类型的整数11 1100001字节24位有符号整数11 1111101字节8位有符号整数11 11xxxx1字节没有content属性xxxx直接存[0,12]范围的整数值
字典
字典dict又称为散列表是一种用来存储键值对的数据结构。C语言没有内置这种数据结构所以Redis构建了自己的字典实现。
Redis字典的实现主要涉及三个结构体字典、哈希表、哈希表节点。其中每个哈希表节点保存一个键值对每个哈希表由多个哈希表节点构成而字典则是对哈希表的进一步封装。这三个结构体的关系如下图所示 其中dict代表字典dictht代表哈希表dictEntry代表哈希表节点。可以看出dictEntry是一个数组这很好理解因为一个哈希表里要包含多个哈希表节点。而dict里包含2个dictht多出的哈希表用于REHASH。当哈希表保存的键值对数量过多或过少时需要对哈希表的大小进行扩展或收缩操作在Redis中扩展和收缩哈希表是通过REHASH实现的执行REHASH的大致步骤如下 为字典的ht[1]哈希表分配内存空间 如果执行的是扩展操作则ht[1]的大小为第1个大于等于ht[0].used*2的2n。如果执行的是收缩操作则ht[1]的大小为第1个大于等于ht[0].used的2n。 将存储在ht[0]中的数据迁移到ht[1]上 重新计算键的哈希值和索引值然后将键值对放置到ht[1]哈希表的指定位置上。 将字典的ht[1]哈希表晋升为默认哈希表 迁移完成后清空ht[0]再交换ht[0]和ht[1]的值为下一次REHASH做准备。
当满足以下任何一个条件时程序会自动开始对哈希表执行扩展操作
服务器目前没有执行bgsave或bgrewriteof命令并且哈希表的负载因子大于等于1服务器目前正在执行bgsave或bgrewriteof命令并且哈希表的负载因子大于等于5。
为了避免REHASH对服务器性能造成影响REHASH操作不是一次性地完成的而是分多次、渐进式地完成的。渐进式REHASH的详细过程如下
为ht[1]分配空间让字典同时持有ht[0]和ht[1]两个哈希表在字典中的索引计数器rehashidx设置为0表示REHASH操作正式开始在REHASH期间每次对字典执行添加、删除、修改、查找操作时程序除了执行指定的操作外还会顺带将ht[0]中位于rehashidx上的所有键值对迁移到ht[1]中再将rehashidx的值加1随着字典不断被访问最终在某个时刻ht[0]上的所有键值对都被迁移到ht[1]上此时程序将rehashidx属性值设置为-1标识REHASH操作完成。
REHSH期间字典同时持有两个哈希表此时的访问将按照如下原则处理
新添加的键值对一律被保存到ht[1]中删除、修改、查找等其他操作会在两个哈希表上进行即程序先尝试去ht[0]中访问要操作的数据若不存在则到ht[1]中访问再对访问到的数据做相应的处理。
跳跃表
跳跃表的查找复杂度为平均O(logN)最坏O(N)效率堪比红黑树却远比红黑树实现简单。跳跃表是在链表的基础上通过增加索引来提高查找效率的。
有序链表插入、删除的复杂度为O(1)而查找的复杂度为O(N)。例若要查找值为60的元素需要从第1个元素依次向后比较共需比较6次才行如下图 跳跃表是从有序链表中选取部分节点组成一个新链表并以此作为原始链表的一级索引。再从一级索引中选取部分节点组成一个新链表并以此作为原始链表的二级索引。以此类推可以有多级索引如下图 跳跃表在查找时优先从高层开始查找若next节点值大于目标值或next指针指向NULL则从当前节点下降一层继续向后查找这样便可以提高查找的效率了。
跳跃表的实现主要涉及2个结构体zskiplist、zskiplistNode它们的关系如下图所示 其中蓝色的表格代表zskiplist红色的表格代表zskiplistNode。zskiplist有指向头尾节点的指针以及列表的长度列表中最高的层级。zskiplistNode的头节点是空的它不存储任何真实的数据它拥有最高的层级但这个层级不记录在zskiplist之内。
1.24 如何利用Redis实现分布式Session
参考答案
在web开发中我们会把用户的登录信息存储在session里。而session是依赖于cookie的即服务器创建session时会给它分配一个唯一的ID并且在响应时创建一个cookie用于存储这个SESSIONID。当客户端收到这个cookie之后就会自动保存这个SESSIONID并且在下次访问时自动携带这个SESSIONID届时服务器就可以通过这个SESSIONID得到与之对应的session从而识别用户的身。如下图 现在的互联网应用基本都是采用分布式部署方式即将应用程序部署在多台服务器上并通过nginx做统一的请求分发。而服务器与服务器之间是隔离的它们的session是不共享的这就存在session同步的问题了如下图 如果客户端第一次访问服务器请求被分发到了服务器A上则服务器A会为该客户端创建session。如果客户端再次访问服务器请求被分发到服务器B上则由于服务器B中没有这个session所以用户的身份无法得到验证从而产生了不一致的问题。
解决这个问题的办法有很多比如可以协调多个服务器让他们的session保持同步。也可以在分发请求时做绑定处理即将某一个IP固定分配给同一个服务器。但这些方式都比较麻烦而且性能上也有一定的消耗。更合理的方式就是采用类似于Redis这样的高性能缓存服务器来实现分布式session。
从上面的叙述可知我们使用session保存用户的身份信息本质上是要做两件事情。第一是保存用户的身份信息第二是验证用户的身份信息。如果利用其它手段实现这两个目标那么就可以不用session或者说我们使用的是广义上的session了。
具体实现的思路如下图我们在服务端增加两段程序
第一是创建令牌的程序就是在用户初次访问服务器时给它创建一个唯一的身份标识并且使用cookie封装这个标识再发送给客户端。那么当客户端下次再访问服务器时就会自动携带这个身份标识了这和SESSIONID的道理是一样的只是改由我们自己来实现了。另外在返回令牌之前我们需要将它存储起来以便于后续的验证。而这个令牌是不能保存在服务器本地的因为其他服务器无法访问它。因此我们可以将其存储在服务器之外的一个地方那么Redis便是一个理想的场所。
第二是验证令牌的程序就是在用户再次访问服务器时我们获取到了它之前的身份标识那么我们就要验证一下这个标识是否存在了。验证的过程很简单我们从Redis中尝试获取一下就可以知道结果。 1.25 如何利用Redis实现一个分布式锁
参考答案
何时需要分布式锁
在分布式的环境下当多个server并发修改同一个资源时为了避免竞争就需要使用分布式锁。那为什么不能使用Java自带的锁呢因为Java中的锁是面向多线程设计的它只局限于当前的JRE环境。而多个server实际上是多进程是不同的JRE环境所以Java自带的锁机制在这个场景下是无效的。
如何实现分布式锁
采用Redis实现分布式锁就是在Redis里存一份代表锁的数据通常用字符串即可。实现分布式锁的思路以及优化的过程如下 加锁 第一版这种方式的缺点是容易产生死锁因为客户端有可能忘记解锁或者解锁失败。 setnx key value第二版给锁增加了过期时间避免出现死锁。但这两个命令不是原子的第二步可能会失败依然无法避免死锁问题。 setnx key value
expire key seconds第三版通过“set…nx…”命令将加锁、过期命令编排到一起它们是原子操作了可以避免死锁。 set key value nx ex seconds 解锁 解锁就是删除代表锁的那份数据。 del key问题 看起来已经很完美了但实际上还有隐患如下图。进程A在任务没有执行完毕时锁已经到期被释放了。等进程A的任务执行结束后它依然会尝试释放锁因为它的代码逻辑就是任务结束后释放锁。但是它的锁早已自动释放过了它此时释放的可能是其他线程的锁。
想要解决这个问题我们需要解决两件事情
在加锁时就要给锁设置一个标识进程要记住这个标识。当进程解锁的时候要进行判断是自己持有的锁才能释放否则不能释放。可以为key赋一个随机值来充当进程的标识。解锁时要先判断、再释放这两步需要保证原子性否则第二步失败的话就会出现死锁。而获取和删除命令不是原子的这就需要采用Lua脚本通过Lua脚本将两个命令编排在一起而整个Lua脚本的执行是原子的。
按照以上思路优化后的命令如下
# 加锁
set key random-value nx ex seconds # 解锁
if redis.call(get,KEYS[1]) ARGV[1] thenreturn redis.call(del,KEYS[1])
elsereturn 0
end基于RedLock算法的分布式锁
上述分布式锁的实现方案是建立在单个主节点之上的。它的潜在问题如下图所示如果进程A在主节点上加锁成功然后这个主节点宕机了则从节点将会晋升为主节点。若此时进程B在新的主节点上加锁成果之后原主节点重启成为了从节点系统中将同时出现两把锁这是违背锁的唯一性原则的。 总之就是在单个主节点的架构上实现分布式锁是无法保证高可用的。若要保证分布式锁的高可用则可以采用多个节点的实现方案。这种方案有很多而Redis的官方给出的建议是采用RedLock算法的实现方案。该算法基于多个Redis节点它的基本逻辑如下
这些节点相互独立不存在主从复制或者集群协调机制加锁以相同的KEY向N个实例加锁只要超过一半节点成功则认定加锁成功解锁向所有的实例发送DEL命令进行解锁
RedLock算法的示意图如下我们可以自己实现该算法也可以直接使用Redisson框架。 1.26 说一说你对布隆过滤器的理解
参考答案
布隆过滤器可以用很低的代价估算出数据是否真实存在。例如给用户推荐新闻时要去掉重复的新闻就可以利用布隆过滤器判断该新闻是否已经推荐过。
布隆过滤器的核心包括两部分
一个大型的位数组若干个不一样的哈希函数每个哈希函数都能将哈希值算的比较均匀。
布隆过滤器的工作原理
添加key时每个哈希函数都利用这个key计算出一个哈希值再根据哈希值计算一个位置并将位数组中这个位置的值设置为1。询问key时每个哈希函数都利用这个key计算出一个哈希值再根据哈希值计算一个位置。然后对比这些哈希函数在位数组中对应位置的数值 如果这几个位置中有一个位置的值是0就说明这个布隆过滤器中不存在这个key。如果这几个位置中所有位置的值都是1就说明这个布隆过滤器中极有可能存在这个key。之所以不是百分之百确定是因为也可能是其他的key运算导致该位置为1。
1.27 多台Redis抗高并发访问该怎么设计
参考答案
Redis Cluster是Redis的分布式解决方案在3.0版本正式推出有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时可以采用Cluster架构方案达到负载均衡的目的。
Redis集群采用虚拟槽分区来实现数据分片它把所有的键根据哈希函数映射到0-16383整数槽内计算公式为slotCRC16(key)16383每一个节点负责维护一部分槽以及槽所映射的键值数据。虚拟槽分区具有如下特点
解耦数据和节点之间的关系简化了节点扩容和收缩的难度节点自身维护槽的映射关系不需要客户端或者代理服务维护槽分区元数据支持节点、槽、键之间的映射查询用于数据路由在线伸缩等场景。
Redis集群中数据的分片逻辑如下图 1.28 如果并发量超过30万怎么设计Redis架构
参考答案
Redis Cluster是Redis的分布式解决方案在3.0版本正式推出有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时可以采用Cluster架构方案达到负载均衡的目的。
Redis集群采用虚拟槽分区来实现数据分片它把所有的键根据哈希函数映射到0-16383整数槽内计算公式为slotCRC16(key)16383每一个节点负责维护一部分槽以及槽所映射的键值数据。虚拟槽分区具有如下特点
解耦数据和节点之间的关系简化了节点扩容和收缩的难度节点自身维护槽的映射关系不需要客户端或者代理服务维护槽分区元数据支持节点、槽、键之间的映射查询用于数据路由在线伸缩等场景。
Redis集群中数据的分片逻辑如下图
就说明这个布隆过滤器中不存在这个key。
如果这几个位置中所有位置的值都是1就说明这个布隆过滤器中极有可能存在这个key。之所以不是百分之百确定是因为也可能是其他的key运算导致该位置为1。
1.27 多台Redis抗高并发访问该怎么设计
参考答案
Redis Cluster是Redis的分布式解决方案在3.0版本正式推出有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时可以采用Cluster架构方案达到负载均衡的目的。
Redis集群采用虚拟槽分区来实现数据分片它把所有的键根据哈希函数映射到0-16383整数槽内计算公式为slotCRC16(key)16383每一个节点负责维护一部分槽以及槽所映射的键值数据。虚拟槽分区具有如下特点
解耦数据和节点之间的关系简化了节点扩容和收缩的难度节点自身维护槽的映射关系不需要客户端或者代理服务维护槽分区元数据支持节点、槽、键之间的映射查询用于数据路由在线伸缩等场景。
Redis集群中数据的分片逻辑如下图
[外链图片转存中…(img-W0cUgnt5-1641471982130)]
1.28 如果并发量超过30万怎么设计Redis架构
参考答案
Redis Cluster是Redis的分布式解决方案在3.0版本正式推出有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时可以采用Cluster架构方案达到负载均衡的目的。
Redis集群采用虚拟槽分区来实现数据分片它把所有的键根据哈希函数映射到0-16383整数槽内计算公式为slotCRC16(key)16383每一个节点负责维护一部分槽以及槽所映射的键值数据。虚拟槽分区具有如下特点
解耦数据和节点之间的关系简化了节点扩容和收缩的难度节点自身维护槽的映射关系不需要客户端或者代理服务维护槽分区元数据支持节点、槽、键之间的映射查询用于数据路由在线伸缩等场景。
Redis集群中数据的分片逻辑如下图