1.谈谈你对Redis的理解?

Redis是 ANSI C 语言编写的一个基于内存的高性能键值对(key-value)的NoSQL数据库,一般用于架设在Java程序与数据库之间用作缓存层来弥补DB性能与Java程序之间的差距所带来的请求阻塞造成的响应缓慢以及DB并发吞吐跟不上系统并发量时避免请求直接落入DB从而起到保护DB的作用,而Redis一般除了缓存DB数据之外还可以利用它丰富的数据类型及指令来实现一些其他功能,比如:计数器、用户在线状态、排行榜、session存储等,同时Redis的性能也非常可观,通过官方给出的数据显示能够达到10w/s的QPS处理,但是在生产环境的实测结果大概读取QPS在7-9w/s,写入QPS在6-8w/s左右(注:与机器性能也有关),同时Redis也提供事务、持久化、高可用等一些机制的支持。

2.讲讲redis的基本数据类型以及你是在项目中怎么使用它们的?

Redis数据类型在之前是五种,但是现在的版本中存在九种,分别为:字符串(strings/string)、散列(hashes/hash)、列表(lists/list)、集合(sets/set)、有序集合(sorted sets/zset)以及后续的四种数据类型:bitmaps、hyperloglogs、地理空间( geospatial)、消息(Streams)

不过无论是哪种数据类型Redis都不会直接将它放在内存中存储,而是转而内部使用redisObject来存储以及表示所有类型的key-value:

redis 的qps redis的qps是什么_面试

Redis内部使用一个redisObject对象来表示所有的key和value,redisObject最主要的信息如上图所示:type表示一个value对象具体是何种数据类型,encoding是不同数据类型在redis内部的存储方式。比如:type=string表示value存储的是一个普通字符串,那么encoding可以是raw或者int,而关于其他数据类型的内部编码实现:

redis 的qps redis的qps是什么_redis 的qps_02

每种数据结构,redis都提供了不同的内部编码实现方式,以便适用不同的场景

2.1内部编码

String的内部编码:

int:8个字节的长整型,如果一个字符串保存的类型是整数值,并且这个值可以 用long类型来表示,name字符串对象会将整数值保存在字符串对象结构的ptr属性里面,并将字符串对象的编码设置为int。

embstr:小于等于39个字节的字符串,embstr编码是专门用于保存短字符串的优化编码方式。相比raw有如下好处:

  • embstr编码将创建字符串对象所需的空间分配的次数从raw编码的两次降低为一次。
  • 释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码对象的字符串对象需要调用两次内存释放函数
  • 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw编码的字符串对象能更好地利用缓存带来的优势。

raw:大于39个字节的字符串,用简单动态字符串(SDS)来保存,将这个字符串的对象编码设置为raw。

SDS相比传统字符串的优点:SDS 在属性中记录了 SDS 本身的长度,所以获取一个 SDS 长度的复杂度仅为O(1);杜绝缓冲区溢出;减少修改字符串时带来的内存重分配次数;

Hash的内部编码:

ziplist(压缩列表):当哈希类型元素小于has-max-ziplist-entries配置(默认512个),同事所有值都小于hash-max-ziplist-value配置(默认64个字节)时,redis会使用ziplist作为哈希的内部实现。ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。

hashtable(哈希表):当哈希类型无法满足ziplist的条件时,redis会使用hashtable作为哈希的内部实现。因为ziplist的读写效率会下降,而hashtable的读写时间复杂度为o(1)。

List的内部编码:

ziplist(压缩列表)

linkedlist(链表):当列表类型无法满足条件的时候,redis会使用linkedlist作为列表的内部实现。

Set的内部编码:

intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)是,redis会选intset作为集合的内部实现,从而减少内存使用。

hashtable:当集合元素无法满足intset的条件时,redis会使用hashtable作为集合的内部实现。

Zset有序集合的内部编码:

ziplist(压缩列表)

skiplist(跳跃表):当ziplist条件不满足的时候,有序集合会使用skiplist作为内部 实现,因为ziplist的读写效率会下降;

2.2redis的基本数据类型以及它们的应用场景:

String(字符串)

  • String类型是redis的最基础的数据结构,也是最经常使用到的类型。 而且其他的四种类型多多少少都是在字符串类型的基础上构建的,所以String类型是redis的基础。
  • String 类型的值最大能存储 512MB,这里的String类型可以是简单字符串、 复杂的xml/json的字符串、二进制图像或者音频的字符串、以及可以是数字的字符串

应用场景
1、缓存功能:String字符串是最常用的数据类型,不仅仅是redis,各个语言都是最基本类型,因此,利用redis作为缓存,配合其它数据库作为存储层,利用redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。

2、计数器:许多系统都会使用redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。

3、统计多单位的数量:eg,uid:gongming count:0 根据不同的uid更新count数量。

4、共享用户session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存cookie,这两种方式做有一定弊端,1)每次都重新登录效率低下 2)cookie保存在客户端,有安全隐患。这时可以利用redis将用户的session集中管理,在这种模式只需要保证redis的高可用,每次用户session的更新和获取都可以快速完成。大大提高效率。
List(列表)

  • list类型是用来存储多个有序的字符串的,列表当中的每一个字符看做一个元素
  • 一个列表当中可以存储有一个或者多个元素,redis的list支持存储2^32次方-1个元素。
  • redis可以从列表的两端进行插入(pubsh)和弹出(pop)元素,支持读取指定范围的元素集, 或者读取指定下标的元素等操作。redis列表是一种比较灵活的链表数据结构,它可以充当队列或者栈的角色。
  • redis列表是链表型的数据结构,所以它的元素是有序的,而且列表内的元素是可以重复的。 意味着它可以根据链表的下标获取指定的元素和某个范围内的元素集。

应用场景
1、消息队列:reids的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。

2、文章列表或者数据分页展示的应用。比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。

Set(集合)

  • redis集合(set)类型和list列表类型类似,都可以用来存储多个字符串元素的集合。
  • 但是和list不同的是set集合当中不允许重复的元素。而且set集合当中元素是没有顺序的,不存在元素下标。
  • redis的set类型是使用哈希表构造的,因此复杂度是O(1),它支持集合内的增删改查, 并且支持多个集合间的交集、并集、差集操作。可以利用这些集合操作,解决程序开发过程当中很多数据集合间的问题。

应用场景
1、标签:比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并。

2、共同好友功能,共同喜好,或者可以引申到二度好友之类的扩展应用。

3、统计网站的独立IP。利用set集合当中元素不唯一性,可以快速实时统计访问网站的独立IP。

数据结构

set的底层结构相对复杂写,使用了intset和hashtable两种数据结构存储,intset可以理解为数组。
sorted set(有序集合)

  • redis有序集合也是集合类型的一部分,所以它保留了集合中元素不能重复的特性,但是不同的是,有序集合给每个元素多设置了一个分数,利用该分数作为排序的依据。。

应用场景
1、 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。

2、用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
hash(哈希)

  • Redis hash数据结构 是一个键值对(key-value)集合,它是一个 string 类型的 field 和 value

的映射表, redis本身就是一个key-value型数据库,因此hash数据结构相当于在value中又套了一层key-value型数据。

  • 所以redis中hash数据结构特别适合存储关系型对象

应用场景
1、由于hash数据类型的key-value的特性,用来存储关系型数据库中表记录,是redis中哈希类型最常用的场景。一条记录作为一个key-value,把每列属性值对应成field-value存储在哈希表当中,然后通过key值来区分表当中的主键。

2、经常被用来存储用户相关信息。优化用户信息的获取,不需要重复从数据库当中读取,提高系统性能。
BitMap

  • Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,时间复杂度为O(1)。

由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。

应用场景

Bitmap 类型非常适合二值状态统计的场景,这里的二值状态就是指集合元素的取值就只有 0 和 1 两种,在记录海量数据时,Bitmap 能够有效地节省内存空间。

签到统计

在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。

签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型。

HyperLogLog

Redis HyperLogLog 是 Redis 2.8.9 版本新增的数据类型,是一种用于「统计基数」的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog 是统计规则是基于概率完成的,不是非常准确,标准误算率是 0.81%。

所以,简单来说 HyperLogLog 提供不精确的去重计数。

HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的内存空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。

这什么概念?举个例子给大家对比一下。

用 Java 语言来说,一般 long 类型占用 8 字节,而 1 字节有 8 位,即:1 byte = 8 bit,即 long 数据类型最大可以表示的数是:2^63-1。对应上面的2^64个数,假设此时有2^63-1这么多个数,从 0 ~ 2^63-1,按照long以及1k = 1024 字节的规则来计算内存总数,就是:((2^63-1) * 8/1024)K,这是很庞大的一个数,存储空间远远超过12K,而 HyperLogLog 却可以用 12K 就能统计完。

应用场景

百万级网页 UV 计数

Redis HyperLogLog 优势在于只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。

所以,非常适合统计百万级以上的网页 UV 的场景。

geospatial

Redis GEO 是 Redis 3.2 版本新增的数据类型,主要用于存储地理位置信息,并对存储的信息进行操作。

在日常生活中,我们越来越依赖搜索“附近的餐馆”、在打车软件上叫车,这些都离不开基于位置信息服务(Location-Based Service,LBS)的应用。LBS 应用访问的数据是和人或物关联的一组经纬度信息,而且要能查询相邻的经纬度范围,GEO 就非常适合应用在 LBS 服务的场景中。

Stream

Redis Stream 是 Redis 5.0 版本新增加的数据类型,Redis 专门为消息队列设计的数据类型。

在 Redis 5.0 Stream 没出来之前,消息队列的实现方式都有着各自的缺陷,例如:

  • 发布订阅模式,不能持久化也就无法可靠的保存消息,并且对于离线重连的客户端不能读取历史消息的缺陷;
  • List 实现消息队列的方式不能重复消费,一个消息消费完就会被删除,而且生产者需要自行实现全局唯一 ID。

基于以上问题,Redis 5.0 便推出了 Stream 类型也是此版本最重要的功能,用于完美地实现消息队列,它支持消息的持久化、支持自动生成全局唯一 ID、支持 ack 确认消息的模式、支持消费组模式等,让消息队列更加的稳定和可靠。

3.Redis缓存及一致性、雪崩、击穿与穿透问题

3.1Redis与MySQL之间的数据一致性问题

关于redis与MySQL之间的数据一致性问题其实也考虑过很多方案,比如先删后改,延时双删等等很多方案,但是在高并发情况下还是会造成数据的不一致性,所以关于DB与缓存之间的强一致性一定要保证的话那么就对于这部分数据不要做缓存,操作直接走DB,但是如果这个数据比较热点的话那么还是会给DB造成很大的压力,所以在我们的项目中还是采用先删再改+过期的方案来做的,虽然也会存在数据的不一致,但是勉强也能接受,因为毕竟使用缓存访问快的同时也能减轻DB压力,而且本身采用缓存就需要接受一定的数据延迟性和短暂的不一致性,我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。合适的策略包括合适的缓存更新策略,合适的缓存淘汰策略,更新数据库后及时更新缓存、缓存失败时增加重试机制等。

3.2redis为什么快?

一、Redis完全基于内存

二、Redis整个结构类似于HashMap,查找和操作复杂度为O(1),不需要和MySQL查找数据一样需要产生随机磁盘IO或者全表

三、Redis对于客户端的处理是单线程的,采用单线程处理所有客户端请求,避免了多线程的上下文切换和线程竞争造成的开销

四、底层采用select/epoll多路复用的高效非阻塞IO模型

五、客户端通信协议采用RESP,简单易读,避免了复杂请求的解析开销

3.3缓存雪崩

缓存雪崩造成的原因是因为我们在做缓存时为了保证内存利用率,一般在写入数据时都会给定一个过期时间,而就是因为过期时间的设置有可能导致大量的热点key在同一时间内全部失效,此时来了大量请求访问这些key,而redis中却没有这些数据,从而导致所有请求直接落入DB查询,造成DB出现瓶颈或者直接被打宕导致雪崩情况的发生。

关于解决方案的的话也可以从多个维度来考虑:

一、设置热点数据永不过期,避免热点数据的失效导致大量的相同请求落入DB

二、错开过期时间的设置,根据业务以及线上情况合理的设置失效时间

三、使用分布式锁或者MQ队列使得请求串行化,从而避免同一时间请求大量落入DB(性能会受到很大的影响)

3.4缓存穿透

缓存穿透这个问题是由于请求参数不合理导致的,比如对外暴露了一个接口getUser?userID=xxx,而数据库中的userID是从1开始的,当有黑客通过这个接口携带不存在的ID请求时,比如:getUser?userID=-1,请求会先来到Redis中查询缓存,但是发现没有对应的数据从而转向DB查询,但是DB中也无此值, 所以也无法写入数据到缓存,而黑客就通过这一点利用“肉鸡”等手段疯狂请求这个接口,导致出现大量redis不存在数据的请求落入DB,从而导致DB出现瓶颈或者直接被打宕机,整个系统陷入瘫痪。

解决方案也有好几种呢:

一、做IP限流与黑名单,避免同一IP一瞬间发送大量请求

二、对于请求做非法校验,对于携带非法参数的请求直接过滤

三、对于DB中查询不存在的数据写入Redis中“Not Data”并设置短暂的过期时间,下次请求能够直接被拦截在redis而不会落入DB

四、布隆过滤器

3.5缓存击穿

缓存击穿和缓存雪崩有点类似,都是由于请求的key过期导致的问题,但是不同点在于失效key的数量,对于雪崩而言指的是大量的key失效导致大量请求落入DB,而对于击穿而言,指的是某一个热点key突然过期,而这个时候又突然又大量的请求来查询它,但是在redis中却并没有查询到结果从而导致所有请求全部打向DB,导致在这个时刻DB直接被打穿。解决方案的话也是有多种:

一、设置热点key永不过期

二、做好redis监控,请求串行化访问(性能较差)使用mutex。

简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法

4.Redis的淘汰策略与删除策略

4.1淘汰策略

redis在5.0之前为我们提供了六种淘汰策略,而5.0为我们提供了八种,但是大体上来说这些lru、lfu、random、ttl四种类型,如下:

一、在Redis中,数据有一部分访问频率较高,其余部分访问频率较低,或者无法预测数据的使用频率时,设置allkeys-lru是比较合适的。

二、如果所有数据访问概率大致相等时,可以选择allkeys-random。

三、如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略。

四、如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时,选择volatile-lru或volatile-random都是比较不错的。

五、由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru 策略,这样就可以不再设置过期时间,高效利用内存了。

maxmemory-policy:参数配置淘汰策略。maxmemory:限制内存大小。

4.2删除策略

redis删除Key的策略策略有三种:

定时删除:在设置键的过期时间的同时,设置一个定时器,当键过期了,定时器马上把该键删除。(定时删除对内存来说是友好的,因为它可以及时清理过期键;但对CPU是不友好的,如果过期键太多,删除操作会消耗过多的资源。)

惰性删除:key过期后任然留在内存中不做处理,当有请求操作这个key的时候,会检查这个key是否过期,如果过期则删除,否则返回key对应的数据信息。(惰性删除对CPU是友好的,因为只有在读取的时候检测到过期了才会将其删除。但对内存是不友好,如果过期键后续不被访问,那么这些过期键将积累在缓存中,对内存消耗是比较大的。)

定期删除:redis数据库默认每隔100ms就会进行随机抽取一些设置过期时间的key进行检测,过期则删除。(定期删除是定时删除和惰性删除的一个折中方案。可以根据实际场景自定义这个间隔时间,在CPU资源和内存资源上作出权衡。)

Redis默认采用定期+惰性删除策略。

5.Redis持久化机制

5.1RDB持久化

Redis持久化机制由三种,在4.X版本之前Redis只支持AOF以及RDB两种形式持久化,但是因为AOF与RDB都存在各自的缺陷,而在4.x版本之后redis还提供一种新的持久化机制:混合型持久化(但是最终生成的文件还是AOF)。

RDB 持久化把内存中当前进程的数据生成快照(.rdb)文件保存到硬盘的过程,有手动触发和自动触发:

save:阻塞当前 Redis,直到 RDB 持久化过程完成为止,若内存实例比较大,会造成长时间阻塞,线上环境不建议用它

bgsave:redis 进程执行 fork 操作创建子进程,由子进程完成持久化,阻塞时间很短(微秒级),是 save 的优化

手动触发:

在执行 redis-cli shutdown 关闭 redis服务时或执行 flushall 命令时,如果没有开启 AOF 持久化,自动执行bgsave,而且RDB 是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,重启时加载这个文件达到数据恢复。

自动触发:

edis RDB持久化默认开启,save 900 1,900s内,存在1个写操作, save 300 10,300s内,存在10个写操作, save 60 10000 -- 60s内,存在10000个写操作,如上是RDB的自动触发的默认配置,当操作满足如上条件时会被触发。

5.2AOF持久化

AOF持久化方式能很好的解决RDB持久化方式造成的数据丢失,AOF持久化到硬盘中的并不是内存中的数据快照,而是和MySQL的belog日志一样记录写入命令

优点:根据不同的fsync策略可以保证数据丢失风险降到最低,数据能够保证是最新的,fsync是后台线程在处理,所以对于处理客户端请求的线程并不影响。

缺点:文件体积由于保存的是所有命令会比RDB大上很多,而且数据恢复时也需要重新执行指令,在重启时恢复数据的时间往往会慢很多。虽然fsync并不是共用处理客户端请求线程的资源来处理的,但是这两个线程还是在共享同一台机器的资源,所以在高并发场景下也会一定受到影响。

AOF的持久化策略也有三种:

appendfsync always:同步持久化形式,每次发生数据更改都将命令追加到AOF文件,因为每次写入时都记录会产生大量磁盘IO,从而性能会受到影响,但是数据最安全。

appendfsync everysec:redis开启AOF后的缺省配置,异步操作,每秒将写入命令追加到AOF文件,如果在刚持久化之后的一秒内宕机,会造成1S的数据丢失。

appendfsync no:redis并不直接调用文件同步,而是交给操作系统来处理,操作系统可以根据 buffer填充情况/通道空闲时间等择机触发同步;这是一种普通的文件操作方式。性能较好,在物理服务器故障时,数据丢失量会因 OS 配置有关。

AOF机制重写:

随着redis在线上运行的时间越来越久,客户端执行的命令越来越多,AOF的文件也会越来越大,当AOF达到一定程度大小之后再通过AOF文件恢复数据是异常缓慢的,那么对于这种情况redis在开启AOF持久化机制的时候会存在AOF文件的重写,缺省配置是当AOF文件比上一次重写时的文件大小增长100%并且文件大小不小于64MB时会对整个AOF文件进行重写从而达到“减肥”的目的(这里的100%和64MB可以通过 auto-aof-rewrite-percentage 100 与 auto-aof-rewrite-min-size 64mb 来调整)。

而AOF rewrite 操作就是“压缩”AOF 文件的过程,当然 redis 并没有采用“基于原 aof 文件”来重写的方式,而是采取了类似 snapshot 的方式:基于 copy-on-write,全量遍历内存中数据,然后逐个序列到 aof 文件中。因此 AOF rewrite 能够正确反应当前内存数据的状态,这正是我们所需要的;

*rewrite 过程中,对于新的变更操作将仍然被写入到原 AOF 文件中,同时这些新的变更操作也会被 redis 收集起来(buffer,copy-on-write 方式下,最极端的可能是所有的 key 都在此期间被修改,将会耗费 2 倍内存)。

当内存数据被全部写入到新的 aof 文件之后,收集的新的变更操作也将会一并追加到新的 aof 文件中,此后将会重命名新的 aof 文件为 appendonly.aof, 此后所有的操作都将被写入新的 aof 文件。如果在 rewrite 过程中,出现故障,将不会影响原 AOF 文件的正常工作,只有当 rewrite 完成之后才会切换文件,因为 rewrite 过程是比较可靠的,触发 rewrite 的时机可以通过配置文件来声明,同时 redis 中可以通过 bgrewriteaof 指令人工干预。

5.3项目中redis采用的是那种持久化方式呢?

在项目中考虑到了redis中不仅仅只是用来做缓存,其中还存储着一些MySQL中不存在的数据,所以数据的安全性要求比较高,而RDB因为并不是实时的持久化,会出现数据丢失,但是采用AOF形式在重启、灾备、迁移的时候过程异常耗时,也并不理想,所以在我们线上是同时采用两种形式的

当然在redis4.x之后推出了混合型持久化机制,因为RDB虽然加载快但是存在数据丢失,AOF数据安全但是加载缓慢,redis为了解决这个问题,带来了一个新的持久化选项——混合持久化。

将RDB文件的内容和增量的AOF日志文件存在一起。这里的AOF日志不再是全量 的日志,而是自持久化开始到持久化结束的这段时间发生的增量AOF日志,通常这部分AOF日志很小。redis重启的时候,可以先加载RDB的内容,然后再重放增量AOF日志,就可以完全替代之前的AOF全量文件重放,恢复效率因此大幅得到提升(混合型持久化最终生成的文件后缀是.aof,可以通过redis.conf文件中aof-use-rdb-preamble yes配置开启)。

混合型持久化优点:结合了RDB和AOF的优点,使得数据恢复的效率大幅提升

混合型持久化缺点:兼容性不好,redis-4.x新增,虽然最终的文件也是.aof格式的文件,但在4.0之前版本都不识别该aof文件,同时由于前部分是RDB格式,阅读性较差

6.Redis的事务机制

redis是数据库,那么它支不支持事务呢?

redis作为数据库当然是支持事务的,只不过redis的事务机制是弱事务,相对来说比较鸡肋,官方给出如下几个指令来进行redis的事务控制:

MULTI:标记一个事务块的开始

DISCARD:取消事务,放弃执行事务块内的所有命令

EXEC:执行所有事务块内的命令

UNWATCH:取消 WATCH 命令对所有 key 的监视

WATCH key [key ...]:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

7.Redis内存模型及内存划分

7.1内存模型

Redis的内存模型我们可以通过客户端连接之后使用内存统计命令info memory去查看,如下:

used_memory(单位:字节): Redis分配器分配的内存总量,包括使用的虚拟内存

used_memory_rss(单位:字节): Redis进程占据操作系统的内存;除了分配器分配的内存之外,used_memory_rss还包括进程运行本身需要的内存、内存碎片等,但是不包括虚拟内存

说明: used_memory是从Redis角度得到的量,used_memory_rss是从操作系统角度得到的量。二者之所以有所不同,一方面是因为内存碎片和Redis进程运行需要占用内存,使得used_memory_rss可能更大;另一方面虚拟内存的存在,使得used_memory可能更大

mem_fragmentation_ratio: 内存碎片比率,该值是used_memory_rss / used_memory;一般大于1,且该值越大,内存碎片比例越大。而小于1,说明Redis使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等;一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc分配器来说),由于在实际应用中,Redis的数据量会比较大,此时进程运行占用的内存与Redis数据量和内存碎片相比,都会小得多,mem_fragmentation_ratio便成了衡量Redis内存碎片率的参数

mem_allocator: Redis使用的内存分配器,在编译时指定;可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc

7.2内存划分

Redis作为内存数据库,在内存中存储的内容主要是数据,但除了数据以外,Redis的其他部分也会占用内存。Redis的内存占用可以划分为以下几个部分:

数据: 作为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory中

进程本身运行需要的内存: Redis主进程本身运行肯定需要占用内存,如代码、常量池等等,这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory中。除了主进程外,Redis创建的子进程运行也会占用内存,如Redis执行AOF、RDB重写时创建的子进程。当然,这部分内存不属于Redis进程,也不会统计在used_memory和used_memory_rss中。

缓冲内存: 缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。

内存碎片:内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统 计在used_memory中。

内存碎片的产生与对数据进行的操作、数据的特点等都有关;此外,与使用的内存分配器也有关系:如果内存分配器设计合理,可以尽可能的减少内存碎片的产生。如果Redis服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为重启之后,Redis重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。

7.3共享对象

在RedisObject对象中有一个refcount,refcount记录的是该对象被引用的次数,类型为整型。

refcount的作用,主要在于对象的引用计数和内存回收。当创建新对象时,refcount初始化为1;当有新程序使用该对象时,refcount加1;当对象不再被一个新程序使用时,refcount减1;当refcount变为0时,对象占用的内存会被释放。

Redis中被多次使用的对象(refcount>1),称为共享对象。Redis为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。这个被重复使用的对象,就是共享对象。目前共享对象仅支持整数值的字符串对象。

共享对象的具体实现: Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。 虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。 就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0-9999的整数值;当Redis需要使用值为0-9999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值进行改变。 共享对象的引用次数可以通过object refcount命令查看。

8.Redis虚拟内存

首先说明下redis的虚拟内存与操作系统虚拟内存不是一码事,但是思路和目的都是相冋的。

就是暂时把不经常访问的数据从內存交换到磁盘中,从而腾出宝贵的内存空间。对于redis这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个redis实例以外。另外的能够提高数据库容量的办法就是使用虚拟内存技术把那些不经常访问的数据交换到磁盘上。如果我们存储的数据总是有少部分数据被经常访问,大部分数据很少被访问,对于网站来说确实总是只有少量用户经常活跃。当少量数据被经常访问时,使用虚拟内存不但能提高单台 redis数据库服务器的容量,而且也不会对性能造成太多影响Redis没有使用操作系统提供的虚拟内存机制而是自己在用户态实现了自己的虚拟内存机制。

主要的理由有以下两点:

一、操作系统的虚拟内存是以4k/页为最小单位进行交换的。而redis的大多数对象都远小于4k,所以一个操作系统页上可能有多个 redis对象。另外 redis的集合对象类型如list,set可能行在于多个操作系统页上。最终可能造成只有10%的key被经常访问,但是所有操作系统页都会被操作系统认为是活跃的,这样只有内存真正耗尽时操作系统才会进行页的交换

二、相比操作系统的交换方式,redis可以将被交换到磁盘的对象进行压缩,保存到磁盘的对象可以去除指针和对象元数据信息。一般压缩后的对象会比内存中的对象小10倍。这样redis的虛拟内存会比操作系统的虚拟内存少做很多I0操作

关于Redis虚拟内存的配置也存在于redis.conf文件中,如下:

  • vm-enabled ves:#开启虚拟内存功能
  • vm-swap-file ../redis.swap:#交换出来value保存的文件路径
  • Vm-max-memory 268435456:# redis使用的最大内存上限(256MB),超过上限后 redis开始交换value到磁盘swap文件中。建议设置为系统空闲内存的60%-80%
  • vm-page-size 32:#每个 redis页的大小32个字节
  • vm-pages 134217728:#最多在文件中使用多少个页,交换文件的大小
  • vm-max-threads 8:#用于执行 value对象换入换出的工作线程数量,0 表示不使用工作线程

redis的虚拟内存在设计上为了保证key的查询速度,只会将value交换到swap文件。如果是由于太多key很小的vaue造成的内存问题,那么redis的虚拟内存并不能解决问题。和操作系统一样 redis也是按页来交换对象的。redis规定同一个页只能保存一个对象。但是一个对象可以保存在多个页中。在redis使用的内存没超过vm-max-memory之前是不会交换任何value的。

当超过最大内存限制后,redis会选择把较老的对象交换到swap文件中去。如果两个对象一样老,会优先交换比较大的对象,精确的交换计算公式swappability=age*1og(size_Inmemory)。对于vm-page-size的设置应该根据自己应用将页的大小设置为可以容纳大多数对象的尺寸。太大了会浪费磁盘空间,太小了会造成交换文件出现过多碎片。对于交换文件中的每个页, redis会在内存中用一个1bit值来对应记录页的空闲状态。所以像上面配置中页数量( vm pages134217728)会占用16MB内存用来记录页的空內状态。vm-max-threads表示用做交换任务的工作线程数量。如果大于0推荐设为服务器的cpu的核心数。如果是0则交换过程在上线程进行。具体工作模式如下:

换出:当主线程检测到使用内存超过最大上限,会将选中要互换的对象信息放到一个队列中,给工作线程后台处理,主线程会继续处理客户端请求

换入:如果有客户端请求的key已终被换出了,主线程会先阻塞发出命令的客户端,然后将加载对象的信息放到一个队列中,让工作线程去加载。加载完毕后工作线程通知主线程。主线程再执行客户端的命令。这种方式只阻塞请求的value是已经被换出key的客户端,总的来说阻塞方式的性能会好些,因为不需要线程同步、创建线程和恢复被阻塞的客户端等开销。但是也相应的牺牡了响应性。工作线程方式主线程不会阳塞在磁盘1O上,所以响应性更好。如果我们的应用不太经常发生换入换出,而且也不太在意有点延迟的话推荐使用阻塞方式

换出:主线程定期检査发现内存超出最大上限后,会直接以阻塞的方式,将选中的对象保存到swap文件中,并释放对象占用的内存空间,此过程会一直重复直到下面条件满足:

  • 内存使用降到最大限制以下
  • swap文件满了
  • 几乎全部的对象都被交换到磁盘了
  • 阻塞模式(vm-max-threads等于0)
  • 非阻塞模式(vm-max-threads大于0)

换入:当有客户端请求已经被换出的value时,主线程会以阳塞的方式从swap文件中加载对应的value对象,加载时此时会阻塞所客户端。然后处理该客户端的请求

9.Redis客户端通信RESP协议

RESP是Redis序列化协议,Redis客户端RESP协议与Redis服务器通信。RESP协议在Redis 1.2中引入,但在Redis 2.0中成为与Redis服务器通信的标准方式。这个通信方式就是Redis客户端实现的协议。RESP实际上是一个序列化协议,它支持以下数据类型:简单字符串、错误、整数、大容量字符串和数组。当我们在客户端中像Redis发送操作命令时,比如:


set name 枫桥夜泊


这条命令,不会直接以这种格式的形式发送到Redis Server,而是经过RESP的序列化之后再发送给Redis执行,而AOF持久化机制持久化之后生成的AOF文件中也并不是存储

这个指令,而是存储RESP序列化之后的指令,RESP的特点如下:

  • 实现简单
  • 能被计算机快速地解析
  • 可读性好能够被人工解析

10.Redis高可用机制:主从复制、哨兵、代理式/分片式集群

10.1主从复制

Reedis提供了主从、哨兵、代理集群与分片集群的高可用机制来保证出现单点问题时,能够及时的切换机器以保障整个系统不受到影响。但是后续的三种高可用机制都是基于主从的基础上来实现的。

虽然我们之前讲到过持久化机制可以保证数据重启情况下也不丢失,但是由于是存在于一台服务器上的,如果机器磁盘坏了、机房出问题等也会导致数据丢失,而主从复制可以将数据同步到多台不同机器,也能够保证在主节点宕机时仍然对外提供服务,还可以做到通过读写分离的形式提升整体缓存业务群吞吐量。一般在线上环境时我们去搭建主从环境时,为了保证数据一致性,从节点是不允许写的,而是通过复制主节点数据的形式保障数据同步。所以在整个redis节点群中只能同时运行存在一台主,其他的全为从节点

主从数据同步的过程

redis2.8之前使用 sync[runId][offset] 同步命令,redis2.8之后使用 psync[runId][offset] 命令。

两者不同在于,sync命令仅支持全量复制过程,psync支持全量和部分复制。介绍同步之前,先介绍几个概念:

  • runId:每个redis节点启动都会生成唯一的uuid,每次redis重启后,runId都会发生变化
  • offset:主节点和从节点都各自维护自己的主从复制偏移量offset,当主节点有写入命令时,offset=offset+命令的字节长度。从节点在收到主节点发送的命令后,也会增加自己的offset,并把自己的offset发送给主节点。这样,主节点同时保存自己的offset和从节点的offset,通过对比offset来判断主从节点数据是否一致
  • repl_back_buffer:复制缓冲区,用来存储增量数据命令

主从复制全量数据同步过程:

redis 的qps redis的qps是什么_数据_03

当然psync命令除了支持全量复制之外还支持部分复制,因为在做主从数据同步时会导致主从机器网络带宽开销非常大,而在2.8之前redis仅支持全量复制,这样非常容易导致redis在线上出现网络瓶颈,而在2.8之后的部分复制,用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当slave再次连上master后,如果条件允许,master会补发丢失数据给slave。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。

主从复制部分复制过程:

redis 的qps redis的qps是什么_java_04

psyncrunid命令三种返回值:

  • FULLRESYNC:第一次连接,进行全量复制
  • CONTINUE:进行部分复制
  • ERR:不支持psync命令,进行全量复制

主从机制其实也是为后续的一些高可用机制打下了基础,但是本身也存在一些缺陷,当然在后续的高可用机制中得到了解决,如下:

  • 全量数据同步时如果数据量比较大,在之前会导致线上短暂性的卡顿
  • 一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预
  • 写入的QPS性能受到主节点限制,虽然主从复制能够通过读写分离来提升整体性能,但是只有从节点能够做到线性扩容升吞吐,写入的性能还是受到主节点限制
  • 木桶效应,整个redis节点群能够存储的数据容量受到所有节点中内存最小的那台限制,比如一主两从架构:主(32GB)、从1(32GB)、从2(16GB),那么整个redis节点群能够存储的最大容量为16GB
  • 能够为后续的高可用机制打下基础
  • 在持久化的基础上能够将数据同步到其他机器,在极端情况下做到灾备的效果
  • 能够通过主写从读的形式实现读写分离提升redis整体吞吐,并且读的性能可以通过对从节点进行线性扩容无限提升

10.2哨兵机制

 哨兵机制的确能够解决之前主从存在的一些问题

redis 的qps redis的qps是什么_redis_05

上图所示是目前企业中常用的redis架构,一主两从三哨兵架构,Redis Sentinel(哨兵)主要功能包括主节点存活检测、主从运行情况检测、自动故障转移、主从切换。Redis Sentinel最小配置是一主一从。Redis的Sentinel系统可以用来管理多个Redis节点,该系统可以执行以下四个任务:

  • 监控:不断检查主服务器和从服务器是否正常运行
  • 通知:当被监控的某个redis服务器出现问题,Sentinel通过API脚本向管理员或者其他应用程序发出通知
  • 自动故障转移:当主节点不能正常工作时,Sentinel会开始一次自动的故障转移操作,它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点,这样就不需要人工干预进行主从切换
  • 配置提供者:在Sentinel模式下,客户端应用在初始化时连接的是Sentinel节点集合,从中获取主节点的信息

哨兵的工作原理如下:

  • 主观下线:单个哨兵节点认为某个节点故障时出现的情况,一般出现主观下线的节点为从节点时,不需要与其他哨兵协商,当前哨兵可直接对改节点完成下线操作
  • 客观下线:当一个节点被哨兵判定为主观下线时,这个节点是主节点,那么会和其他哨兵协商完成下线操作的情况被称为客观下线(客观下线只存在于主节点)
  • 每个在线的哨兵节点都可以成为领导者,当它确认主节点下线时,会向其它哨兵发is-master-down-by-addr命令,征求判断并要求将自己设置为领导者,由领导者处理故障转移
  • 当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者
  • 如果征求投票的哨兵发现自己在选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续重复选举…………
  • 过滤掉主观下线的节点
  • 选择slave-priority最高的节点,如果有则返回没有就继续选择
  • 选择出复制偏移量最大的系节点,因为复制便宜量越大则数据复制的越完整,如果有就返回了,没有就继续下一步
  • 选择run_id最小的节点
  • 通过slaveof no one命令,让选出来的从节点成为主节点;并通过slaveof命令让其他节点成为其从节点
  • 将已下线的主节点设置成新的主节点的从节点,当其回复正常时,复制新的主节点,变成新的主节点的从节点,同理,当已下线的服务重新上线时,sentinel会向其发送slaveof命令,让其成为新主的从

一、每个哨兵节点每10秒会向主节点和从节点发送info命令获取最新级联结构图,哨兵配置时只要配置对主节点的监控即可,通过向主节点发送info,获取从节点的信息,并当有新的从节点加入时可以马上感知到

二、每个哨兵节点每隔2秒会向redis数据节点的指定频道上发送该哨兵节点对于主节点的判断以及当前哨兵节点的信息,同时每个哨兵节点也会订阅该频道,来了解其它哨兵节点的信息及对主节点的判断,其实就是通过消息publish和subscribe来完成的

三、隔1秒每个哨兵根据自己info获取的级联结构信息,会向主节点、从节点及其余哨兵节点发送一次ping命令做一次心跳检测,这个也是哨兵用来判断节点是否正常的重要依据

四、sentinel会以每秒一次的频率向所有与其建立了命令连接的实例(master,从服务,其他sentinel)发ping命令,通过判断ping回复是有效回复还是无效回复来判断实例是否在线/存活(对该sentinel来说是“主观在线”),sentinel配置文件中的down-after-milliseconds设置了判断主观下线的时间长度,如果实例在down-after-milliseconds毫秒内,返回的都是无效回复,那么sentinel回认为该实例已(主观)下线,修改其flags状态为SRI_S_DOWN。如果多个sentinel监视一个服务,有可能存在多个sentinel的down-after-milliseconds配置不同,这个在实际生产中要注意(主观下线:所谓主观下线,就是单个sentinel认为某个实例下线(有可能是接收不到订阅,之间的网络不通等等原因))

五、当主观下线的节点是主节点时,此时该哨兵3节点会通过指令sentinel is-masterdown-by-addr寻求其它哨兵节点对主节点的判断,如果其他的哨兵也认为主节点主观下线了,则当认为主观下线的票数超过了quorum(选举)个数,此时哨兵节点则认为该主节点确实有问题,这样就客观下线了,大部分哨兵节点都同意下线操作,也就说是客观下线,一般情况下,每个Sentinel会以每10秒一次的频率向它已知的所有主服务器和从服务器发送INFO命令,当一个主服务器被标记为客观下线时,Sentinel向下线主服务器的所有从服务器发送INFO命令的频率,会从10秒一次改为每秒一次

六、Sentinel和其他Sentinel协商客观下线的主节点的状态,如果处于SDOWN状态,则自动选出新的主节点,将剩余从节点指向新的主节点进行数据复制

哨兵机制缺点:

  • 全量数据同步仍然会导致线上出现短暂卡顿
  • 写入QPS仍然受到主节点单机限制,对于写入并发较高的项目无法满足需求
  • 仍然存在主从复制时的木桶效应问题,存储容量受到节点群中最小内存机器限制
  • 解决了之前主从切换需要人工干预问题,保证了一定意义上的高可用

10.3. 代理式集群

之前的哨兵并不算真正意义上的集群,只解决了人工切换问题,如果需要大规模的写入支持,或者缓存数据量巨大的情况下只能够通过加机器内存的形式来解决,但是长此已久并不是一个好的方案,而在Redis3.0之前官方却并没有相对应的解决方案,不过在Redis3.0之前却有很多其他的解决方案的提出以及落地,比如:

TwemProxy

Twemproxy是一种代理分片机制,由Twitter开源。Twemproxy作为代理, 可接受来自多个程序的访问,按照路由规则,转发给后台的各个Redis服务器,再原路返回。这个方案顺理成章地解决了单个Redis实例承载能力的问题。当然,Twemproxy本身也是单点,需要用Keepalived做高可用方案。这么些年来,Twemproxy是应用范围最广、稳定性最高、 最久经考验的分布式中间件。

只是,他还有诸多不方便之处。Twemproxy最大的痛点在于,无法平滑地扩容/缩容。这样增加了运维难度:业务量突增,需增加Redis服务器; 业务量菱缩,需要减少Redis服务器。但对Twemproxy而言,基本上都很难操作。或者说,Twemproxy更加像服务器端静态sharding.有时为了规避业务量突增导致的扩容需求,甚至被迫新开一个基于Twemproxy的Redis集群。 Twemproxy另- 个痛点是,运维不友好,甚至没有控制面板。当然,由于使用了中间件代理,相比客户端直接连服务器方式,性能上有所损耗,实测结果降低了20%左右。

Codis

Codis由豌豆英于2014年11月开源,基于Go和C开发,是近期涌现的、国人开发的优秀开源软件之一。现已广泛用于豌豆英的各种Redis业务场景,从各种压力测试来看,稳定性符合高效运维的要求。性能更是改善很多,最初比Twemproxy慢20%;现在比Twemproxy快近100% (条件:多实例,-般Value长度)。Codis具有可视化运维管理界面。Codis无疑是 为解决Twemproxy缺点而出的新解决方案。因此综合方面会由于Twemproxy很多。

目前也越来越多公司选择Codis.Codis引入了Group的概念,每个Group包括1个Master及至少1个Slave,这是和Twemproxy的区别之一。这样做的好处是,如果当前Master有问题,则运维人员可通过Dashboard“自助式”切换到Slave,而不需要小心翼翼地修改程序配置文件。为支持数据热迁移(AutoRebalance),出品方修改了RedisServer源码,并称之为Codis Server.Codis采用预先分片(Pre-Sharding)机制,事先规定好了,分成1024个slots (也就是说,最多能支持后端1024个CodisServer),这些路由信息保存在ZooKeeper中。 不足之处有对redis源码进行了修改,以及代理实现本身会有的问题。

实则代理分片的原理也很简单,类似于代理式的分库分表的实现,之前我们是直接连接Redis,然后对Redis进行读写操作,现在则是连接代理,读写操作全部交由代理来处理分发到具体的Redis实例,而集群的组成就很好的打破了之前的一主多从架构,形成了多主多从的模式,每个节点由一个个主从来构建,每个节点存储不同的数据,每个节点都能够提供读写服务,从而做到真正意义上的高可用

使用代理之后能够去解决哨兵存在的问题,但是凡事有利必有弊,代理式集群具体情况如下:

  • 由于使用了代理层来打破之前的架构模型,代理层需要承担所有工作
  • 代理层需要维护,保证高可用
  • 代理层需要实现服务动态感知、注册与监听
  • 代理层需要承载所有客户端流量
  • 代理层需要处理所有分发请求
  • 由于数据并不存在与同一台机器,Redis的很多命令不再完美支持,如set的交集、并集、差集等
  • 打破了传统的一主多从模型,允许多主存在,写入QPS不再受到单机限制
  • 数据分片存储,每个节点存储的数据都不同,解决之前主从架构存在的存容问题
  • 每个节点都是独立的主从,数据同步并不是真正的“全量”,每个节点同步数据时都只是同步该节点上master负责的一部分数据

10.4Redis去中心化分片式集群

redis3.x之后的redis-cluster去中心化分片式集群,reids-cluster在redis3.0中推出,支持Redis分布式集群部署模式。

采用无中心分布式架构。所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽,节点的fail是通过集群中超过半数的节点检测失效时才生效。客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点连接集群中任何一个可用节点即可,减少了代理层,大大提高了性能。redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责维护node<-> slot<-> key之间的关系。目前Jedis已经支持Redis-cluster。从计算架构或者性能方面无疑Redis-cluster是最佳的选择方案。

redisCluster集群的原理

Redis Cluster在设计中没有使用一致性哈希(ConsistencyHashing),而是使用数据分片(Sharding)引入哈希槽(hashSlot)来实现;一个RedisCluster包含16384(0~16383)个哈希槽,存储在RedisCluster中的所有键都会被映射到这些slot中,集群中的每个键都属于这16384个哈希槽中的一个,集群使用公式slot=CRC16(key)%16384来计算key属于哪个槽,其中CRC16(key)语句用于计算key的CRC16 校验和。

集群中的每个主节点(Master)都负责处理16384个哈希槽中的一部分,当集群处于稳定状态时,每个哈希槽都只由一个主节点进行处理,每个主节点可以有一个到N个从节点(Slave),当主节点出现宕机或网络断线等不可用时,从节点能自动提升为主节点进行处理。

redis 的qps redis的qps是什么_java_06

假设我此时向Redis发送一条命令:CRC16(name)

那么Redis会使用CRC16算法计算KEY值,类似于一个HASH函数,完成后会得到一个数字,假设此时计算完name后得到的结果是26384,那么会拿着这个计算完成之后的结果%总槽数,26384%16384得到结果为10000,那么key=name的这个值应该被放入负责10000这个HashSlot存储,如上图中,会被放入到第三个节点存储,当再次get这个缓存时同理。