redis
Posted yanwu0527
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis相关的知识,希望对你有一定的参考价值。
简介
redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库
其具有以下特点:开源、多种数据结构、基于键值的存储服务器、高性能、功能服务
优点:数据类型丰富、效率高、支持集群、支持持久化
缺点:单进程单线程,长命令可能会导致redis阻塞、集群下多key同时操作无法使用
数据类型和命令
string:K-V
值可以是字符串、数字(整数和浮点)、二进制【其中,整数的取值范围与系统的长整型取值一致[32位系统是32位、64位系统是64位];浮点数的精度为double】
命令 | 说明 | 时间复杂度 |
---|---|---|
GET K | 获取K对应的V | O(1) |
MGET K1 K2 K3 ... | 批量获取K对应的V,原子性操作 | O(N) |
GETSET K V | 设置K-V并返回旧的V | O(1) |
GETRANGE K start end | 获取K对应V字符串的指定下标所有的值 | O(1) |
SET K V | 设置K-V,K存在修改K对应的V,K不存在新增K-V | O(1) |
SETNX K V | K不存在时才设置 | O(1) |
SETRANGE K index V | 设置K对应V字符串的执行下标所对应的值 | O(1) |
APPEND K V | 将V追加到K对应的旧V后 | O(1) |
MSET K1 V1 K2 V2 K3 V3 ... | 批量设置K-V | O(1) |
DEL K | 删除K-V | O(1) |
INCR K | K自增1,如果K不存在,自增后GET K = 1 | O(1) |
DECR K | K自减1,如果K不存在,自减后GET K = -1 | O(1) |
INCRBY K I | K自增I,如果K不存在,自增后GET K = I | O(1) |
DECRBY K I | K自减I,如果K不存在,自减后GET K = -I | O(1) |
INCRBYFLOAT K F | K自增F,如果K不存在,自增后GET K = F | O(1) |
STRLEN K | 返回字符串长度(UTF8下中文占3个字符) | O(1) |
hash:K-[F-V]
在使用hash的HGETALL指令时应当谨慎小心,因为redis是单线程的,此命令可能需要比较久的处理时间,可能会导致redis阻塞
命令 | 说明 | 时间复杂度 |
---|---|---|
HGET K F | 获取K下的F对应的V | O(1) |
HMGET K F1 F2 F3 ... | 批量获取K下F1、F2、F3的V | O(N) |
HGETALL K | 批量获取K下所有的F-V | O(N) |
HKEYS K | 返回K对应的所有的F | O(N) |
HVALS K | 返回K对应的所有F的V | O(N) |
HSET K F V | 设置K下对应的F-V | O(1) |
HMSET K F1 V1 F2 V2 F3 V3 ... | 批量设置K下的F1-V1、F2-V2、F3-V3 | O(1) |
HSETNX K F V | 设置K-F-V,当F已存在时则设置失败 | O(1) |
HEXISTE K F | 判断K下是否有F | O(1) |
HLEN K | 获取K下F的数量 | O(1) |
HINCRBY K F I | K下的F对应的V自增I | O(1) |
HINCRBYFLOAT K F F | K下的F对应的V自增F | O(1) |
list:K-[V]
有序,不唯一,链表实现,左右两边都可以执行插入和弹出操作,容量是2的32次方减1个元素
- LPUSH - LPOP >> STACK
- LPUSH - RPOP >> QUEUE
- LPUSH - LTRIM >> CAPPED COLLECTION
- LPUSH - BRPOP >> MESSAGE QUEUE
命令 | 说明 | 时间复杂度 |
---|---|---|
RPUSH K V1 V2 V3 ... | 从列表右端依次插入V1、V2、V3 | O(N) |
LPUSH K V1 V2 V3 ... | 从列表左端依次插入V1、V2、V3 | O(N) |
LINSERT K [before/after] V NV | 在K指定的V[前/后]插入NV | O(N) |
LPOP K | 从列表的左端弹出一个V | O(1) |
BLOPO K timeoout | LPOP阻塞版本,timeout为阻塞时间,timeout为0时永远阻塞 | O(1) |
RPOP K | 从列表的右端弹出一个V | O(1) |
BRPOP K | RPOP阻塞版本,timeout为阻塞时间,timeout为0时永远阻塞 | O(1) |
LREM K count V | 1)count>0,从左到右删除最多count个value相等的项 2)count<0,从右到左删除最多count个value相等的项 3)count=0,删除所有value相等的项 |
O(N) |
LTRIM K start end | 按照索引范围修建列表 | O(N) |
LRANGE K start end | 获取列表指定索引范围内所有的V | O(N) |
LINDEX K index | 获取列表指定索引的V | O(1) |
LLEN K | 获取列表的长度 | O(1) |
LSET K index V | 设置列表指定索引的值为V | 0(N) |
set:K-[V]
无序,唯一,哈希表实现
命令 | 说明 | 时间复杂度 |
---|---|---|
SADD K V | 向集合K中添加V,如果V已经存在,则添加失败 | O(1) |
SREM K V | 将集合K中的V移除 | O(1) |
SCARD K | 计算集合的大小 | O(1) |
SISMEMBER K V | 判断集合K中是否存在V | O(1) |
SRANDMEMBER K count | 从集合K中随机挑选count个V,不会改变集合 | O(1) |
SPOP K | 从集合K中随机弹出一个V,会改变集合 | O(1) |
SMEMBERS K | 获取集合K中所有的V,大数据量场景可能会导致redis阻塞 | O(1) |
SDIFF K1 K2 | 求K1和K2两个集合的差集 | O(1) |
SINTER K1 K2 | 求K1和K2两个集合的交集 | O(1) |
SUNION K1 K2 | 求K1和K2两个集合的并集 | O(1) |
[SDIFF/SINTER/SUNION] K1 K2 store destK | 将K1和K2的[差集、交集、并集]的结果保存到destK中 | O(1) |
zset:K-[V]
有序,唯一,每个元素都会关联一个double类型的分数,通过分数来为集合中的元素进行从小到大的排序,分数可以重复
命令 | 说明 | 时间复杂度 |
---|---|---|
ZADD K S V | 向集合K中添加分数为S的V | O(logN) |
ZREM K V1 V2 V3 ... | 删除集合K中的V1、V2、V3 | O(1) |
ZSCORE K V | 返回集合K中V的分数 | O(1) |
ZINCRBY K increScore V | 将集合K中的V分数自增或自减increScore | O(1) |
ZCARD K | 返回集合K中的V总个数 | O(1) |
ZRANK K menber | 返回集合K中元素的排名 | O(1) |
ZRANGE K start end | 返回集合K中指定索引范围内的升序元素 | O(logN+M) |
ZRANGEBYSCORE K minS maxS | 返回集合K中指定分数范围内的升序元素 | O(logN+M) |
ZCOUNT K minS maxS | 返回集合K中指定分数范围内的元素的个数 | O(logN+M) |
ZREMRANGEBYRANK K start end | 删除集合K中指定排名内的元素 | O(logN+M) |
ZREMRANGEBYSCORE K minS maxS | 删除集合K中指定分数内的元素 | O(logN+M) |
ZINTERSTORE destZ numkeys K1 K2 K3 ... | 计算集合K1 K2 K3的交集,将结果存储在新的destZ有序集合中 | |
ZUNIONSTORE dtstZ numneys K1 K2 K3 ... | 计算集合K1 K2 K3的并集,将结果存储在新的destZ有序集合中 |
geo:K-[M LON LAT]
地理信息定位,存储经纬度,计算两地距离,范围计算等
命令 | 说明 |
---|---|
GEOADD K LON LAT member [LON1 LON2 member ...] | 向K中添加地理位置,命名为member LAT(维度):取值为[-85.05112878 ~ 85.05112878] |
GEOPOS K menber [member1 ...] | 获取K中的menber地理位置信息 |
GEODIST K member1 member2 [unit] | 获取两个地理坐标之间的距离,默认为m unit:[m(米)、km(千米)、mi(英里)、ft(英尺)] |
GEORADIUS K LON LAT radius unit [withcoord] [withdist] [withhash] [asc/desc] [count n] | 以给定的经纬度为中心,返回K包含的位置元素当中与中心的距离不超错给定最大距离的所有位置元素 unit:[m(米)、km(千米)、mi(英里)、ft(英尺)] withcoord:将位置元素的经纬度一起返回 withdist:在返回元素的同时,将位置与中心位置之间的距离也一起返回,距离的单位和给定的范围单位一致 asc/desc:根据中心位置,按照[从近到远/从远到近]的方式返回位置元素,默认不排序 count n:获取n个匹配的元素 |
GEORADIUSBYMEMBER K menber radius unit [withcoord] [withdist] [withhash] [asc/desc] [count n] | 同上,区别是GEORADIUS以给定的经纬度为中心,GEORADIUSBYMEMBER以给定的member为中心 |
GEOHASH K member [member1 ...] | 将二维经纬度转化为一维字符串,字符串越长表明位置越精确,两个字符串越相似表示距离越近 |
ZREM K member | GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除 |
穿透 & 击穿 & 雪崩
穿透
穿透指K对应的数据在数据库中并不存在,每次针对K的请求从缓存中取不到,请求都会到数据库,从而压垮数据库。比如使用id为负数去获取信息,不论是缓存还是数据库都没有,所以每次都会去数据库里查一次,缓存失去意义。
解决方案
- 布隆过滤器
将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免数据库的压力。 - 另一种方式
如果一个条件查询返回的结果为空(不论是数据不存在还是系统故障),我们仍然把这个空结果进行缓存,然后把它的过期时间设置短一些。
击穿
击穿指K对应的数据非常热点,访问非常频繁,当这个K过期的瞬间,此时如果有大量的请求过来,这些请求发现缓存没有数就会从数据库中查询数据并回设到缓存,这个时候大并发的请求可能会瞬间把数据库压垮。
解决方案
- 使用互斥锁(mutex),就是在缓存失效的时候(判断拿出来的值为空),不是立即去load DB,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load DB的操作并回设缓存;否则,就重试整个get缓存的方法。
- 将热点数据设置为永不过期
雪崩
雪崩指缓存服务器重启或缓存中大量数据在某一时间段过期,然后系统涌入大量的查询请求,因为大部分数据在缓存中已经失效,请求渗透到数据库,引起数据库压力造成查询堵塞甚至宕机。
解决方案
- 将缓存失效时间分散,比如每个K的过去时间是随机的,让缓存失效的时间点尽量均匀,防止同一时间大量数据过期的现象发生;
- 如果缓存和数据库是分布式部署,将热点数据均匀的分布在不同的redis和数据库中,分担压力;
- 做二级缓存或者双缓存策略(A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期)
- 最简单粗暴的方式是让redis数据永不过期
事务
MULTI & EXEC(非互斥,原子执行)
被multi和exec命令包围的的所有命令会一个一个的执行,知道所有的命令都执行完成。该命令是原子执行,其它客户端仍可能会修改正在操作的数据,在输入命令时如果中间有一条命令有语法错误,那么该命令之后所有的命令都不会执行,但之前的命令会被执行不会被回滚。
WATCH & UNWATCH(乐观锁,原子执行)
在事务开启前watch了某个K,在事务提交的时候会检查K的值与watch时候的值是否一致,如果不一致,说明数据被其他客户端修改,那么事务的命令队列不会被执行。
如果使用unwatch命令,那么之前的对所有的K的监控全部取消,哪怕之前检测到watch的K的V发生变化,也不会对事务产生影响。
SETNX(排他锁)
如果不存在,则设置一个键值对,它是一个原子性的操作
持久化
RDB(redis database,全量模式)
RDB是redis内存到硬盘的快照,用于持久化,有save(同步)、bgsave(异步)和自动配置三种方式触发
命令 | 同步/异步 | 是否阻塞 | 复杂度 | 优点 | 缺点 |
---|---|---|---|---|---|
save | 同步 | 是 | O(N) | 不会消耗额外的内存 | 阻塞客户端的命令 |
bgsave | 异步 | 是(阻塞发生在fork) | O(N) | 不阻塞客户端的命令 | 需要fork,消耗内存 |
AOF(append only file,增量模式)
AOF是以文件的形式进行持久化,类似于mysql的binlog,它会将每个客户端的每一次写操作都记录到AOF文件中,然后通过载入AOF文件进行数据恢复
策略 | 说明 | 优点 | 缺点 |
---|---|---|---|
always | redis将写命令刷新到缓冲区,然后把每条命令从缓冲区fsync到硬盘的AOF文件 | 不丢失数据 | IO开销大 |
everysec | redis将写命令刷新到缓冲区,然后每秒从缓冲区fsync到硬盘的AOF文件 | 每秒一次fsync | 丢失一秒的数据 |
no | redis将写命令刷新到缓冲区,由OS决定时机执行fsync到硬盘的AOF文件 | 不用管 | 不可控 |
AOF的配置于触发方式
配置:
- auto-aof-rewrite-min-zise >> AOF文件重写需要的尺寸
- auto-aof-rewrite-precentage >> AOF文件增长率
统计:
- aof_current_size >> AOF当前尺寸(单位:字节)
- aof_base_size >> AOF上次启动和重写的储存(单位:字节)
当同时满足以下条件时,自动触发AOF策略,执行AOF流程
aof_current_size > auto-aof-rewrite-min-zise && (aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-precentage
RDB和AOF相比较
命令 | 启动优先级 | 体积 | 恢复速度 | 数据安全性 | 轻重 |
---|---|---|---|---|---|
RDB | 低 | 小 | 快 | 丢数据 | 重 |
AOF | 高 | 大 | 慢 | 根据策略决定 | 轻 |
过期策略
当K expires超时时,怎么处理超时的K,有三种过期策略,redis中同时使用了惰性过期和定期过期两种过期策略
定时删除:
在设置K的过期时间的同时,为该K创建一个定时器,让定时器在K的过期时间来临时对K进行删除
优点:
保证内存被尽快释放
缺点:
- 如果过期的K太多,删除这些K会占用很多CPU时间,可能会导致redis阻塞
- 定时器的创建耗时,要为每一个设置过期的K创建一个定时器,严重影响性能
惰性删除
K过期时不删除,每次从redis获取K的时候区检查是否过期,若果已经过期,删除该K,返回NULL
优点:
删除操作只发生在从数据库取数据的时候,而且只对该K进行操作,所以对CPU的占比较小,而且此时的删除是已经到了非删不可得地步(如果不删除,那么就会获取到过期得K,违背过期策略)
缺点:
如果大量得K过期后,很长的时间都没有去获取过,那么可能发生内存泄漏(无用的垃圾占用了大量的内存)
定期删除
每隔一段时间执行一次删除K的操作
难点:
根据服务器运行情况合理的设置执行时长和执行频率
优点:
1)通过限制删除操作的时长和频率,来减少删除操作对CPU的时间占用(解决定时删除的缺点)
2)定期删除过期K(解决惰性删除的缺点)
缺点:
在内存友好方面不如定时删除,在CPU时间友好方面不如惰性删除
内存淘汰策略
redis的内存淘汰策略是指在redis的用于缓存的内存不足时,怎么处理需要写入且申请额外空间的数据
策略 | 说明 |
---|---|
noeviction | 当内存不足以容纳新写入的数据时,新写入操作会报错 |
allkeys-lru | 当内存不足以容纳新写入的数据时,在键空间中,移除最近最少使用的K |
allkeys-random | 当内存不足以容纳新写入的数据时,在键空间中,随机移除K |
volatile-lru | 当内存不足以容纳新写入的数据时,在设置了过期时间的键空间中,移除最近最少使用的K |
volatile-random | 当内存不足以容纳新写入的数据时,在是指了过期时间的键空间中,随机移除K |
volatile-ttl | 当内存不足以容纳新写入的数据时,在设置了过期时间的键空间中,优先移除更早过期时间的K |
主从复制、哨兵、集群
主从复制
所有节点间的数据是同步的,一个master,多个slave,其中slave不能写只能读,只有master能读,在部署好slave后,客户端可以向任意的slave发送读请求,而不必总是把读请求发送给master,达到负载均衡的效果,master节点挂掉后,redis就不能对外提供写服务了,因为剩下的slave不能成为master这个缺点影响很大,所以一般生产环境不会单单只用主从复制模式,而是使用sentinel哨兵模式。
如果有多个slave节点并发的向master发送sync命令,企图建立主从关系,只要第二个slave的sync命令发生在master完成bgsave操作之前,第二个slave将受到和第一个slave相同地快照和后续backlog;否则,第二个slave的sync命令会导致master的第二次bgsave。
特点:
- master可以拥有多个slave
- 多个slave可以连接同一个master,还可以连接到其它的slave
- 主从复制不会阻塞master
- 可以提高系统的伸缩性
过程:
- slave和master建立连接,发送sync同步命令
- master接收到指令后,会开启一个后台进程,将数据库快照保存到文件中(RDB方式,bgsave指令),同时master主进程会开始收集新的写命令并缓存到backlog队列
- 后台进程完成保存后,将文件发送到slave
- slave会丢弃所有的旧数据,开始载入master发过来的快照文件
- master向slave发送存储在backlog队列中的写命令,发送完毕后,每执行一个写命令,就向slave发送相同地写命令(异步复制)
- slave执行master发来的所有存储在缓冲区的写命令,并从现在开始,接受并执行master传来的每个写命令
哨兵(sentinel)
主从模式中,所有节点间的数据是同步的,当master节点挂掉后,slave节点不能自动选举出一个master出来,此时会导致redis服务的写操作不可用,我们可以通过一个或多个sentinel来处理这种情况,当sentinel发现master节点挂掉后,sentinel就会从slave中重新选举一个master出来。
sentinel是建立在主从模式的基础上的,所以如果只有一个redis节点,那么sentinel则没有任何意义
主从模式配置密码时,sentinel也会同步的将配置信息修改到配置问文件中
当master挂了以后,sentinel会在slave中选择一个作为新的master,并修改它和其它所有slave节点的配置文件,当旧的master重启后,它将不再是master而是以slave的身份接受新的master节点的同步数据
sentinel因为也是一个进程有挂掉的可能,所以一般也会启动多个sentinel形成sentinel集群,一个sentinel或sentinel集群可以管理多个主从redis
当使用sentinel模式的时候,客户端就不需要直接连接redis,而是应该通过sentinel来操作redis服务,由sentinel来提供具体的可提供服务的redis实现
原理:
- sentinel集群通过给定的配置文件发现master,启动时会监控master,通过向master发送info信息获得该服务器下的所有从服务器
- sentinel集群通过命令连接向被监视的主从服务器发送hello信息(每秒一次),该信息包括sentinel自身的IP、PORT、ID等内容,以此来向其它sentinel宣告自身的存在
- sentinel集群通过订阅连接接受其它sentinel发送的hello信息,以此来发现监视同一个主服务器的其它的sentinel;集群之间会相互创建命令连接用于通信,因为已经有主从服务操作为发送和接受hello信息的中介,sentinel之间不会创建订阅连接
- sentinel集群使用ping命令来检测示例的状态,如果在指定的时间内没有回复或返回错误的会粗,那么该实例会被判定为下线
- 当failover主备切换被触发后,failover并不会马上进行,还需要sentinel中的大多数sentinel授权后才可以进行failover,即进行failover的sentinel会去获得指定quorum个的sentinel授权,成功后进入ODOWN状态(如5个sentinel中配置了两个quorum,等到两个sentinel认为master死了才会执行failover)
- sentinel向选为master的slave发送SLAVEOF NO ONE命令,选择slave的条件是sentinel会首先根据slages的优先级来进行排序,优先级越小排名越靠前;如果优先级相同,则查看复制的下标,那个从master接收的复制最多,那个就靠前;如果优先级和小标都相同,就选择进程ID比较小的那个
- sentinel被授权后,它将会获得宕掉的master的一份最新配置版本号,当failover执行结束后,这个版本号将会被用于朱新的配置,通过广播的形式通知其它sentinel,其它sentinel则更新对应master的配置
原理 | 机制 |
---|---|
1 2 3 |
是自动发现机制:以10秒一次的频率,向被监视的master发送info命令,根据回复获取master当前信息;以每秒一次的频率向所有redis服务器和sentinel发送PING命令,通过回复判断服务是否在线;以2秒一次的频率向所有被监视的master、slave发送当前sentinel、master的信息。 |
4 | 是检测机制 |
5 6 |
是failover机制 |
7 | 是更新配置机制 |
职责:
- 监控:sentinel节点会定期检测redis数据节点和其它sentinel节点是否可达
- 通知:sentinel节点会将故障转移通知给应用方
- 主节点故障转移:实现slave晋升为master并维护后续正确的主从关系
- 配置提供:在redis sentinel结构中,客户端在初始化的时候连接的是sentinel节点集合,从中获取主节点信息
集群(cluster):
redis集群中数据是分片存储的,集群的出现是为了解决单机redis容量有限的问题,将redis的数据根据一定的规则分配到多台机器。
cluster可以说是sentinel和主从复制的结合体,通过cluster可以实现主从和master重选操作,cluster配置至少需要3个主节点(每个主节点对应一个从节点,所以需要6个实例)。
因为redis的数据是根据一定的规则分配到cluster不同机器的,当数据量过大时,可以新增机器进行横向扩容,这种模式适合数据量巨大的缓存要求,通常情况下使用sentinel模式就可以了。
redis-cluster通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通知,cluster也可以继续处理命令的请求。
好处:
- 数据自动切分到多个节点,当cluster中一部分节点不可用时,仍然可以继续处理命令请求的能力
- 不同的master存放不同的数据,所有的master数据并集是所有的数据(master和slave数据是一致的)
数据分片:
HASH映射(不是一致性HASH,而是HASH槽):[ HASH_SLOT = CRC16(K) mod 16384 ] (16384=214)
在redis官方给出的集群方案中,数据分配是根据槽位来进行分配的,每一个数据的键被哈希函数映射到一个槽位,redis-3.0规定一共有214个槽位,槽位数可以进行配置,当用户GET或者PUT一个数据时,首先会查找数据对应的槽位,然后查找对应的节点,最后把数据放到节点内。这样就做到了把数据均匀的分配到cluster中的每个节点上,从而做到了每一个节点的负载均衡。
计算K字符串对应的映射值,redis 采用了crc16 函数然后与0x3FFF取低16位的方法。crc16以及md5都是比较常用的根据K均匀的分配的函数,就这样,用户传入的一个K我们就映射到一个槽上,然后经过gossip 协议,周期性的和集群中的其他节点交换信息,最终整个集群都会知道K在哪一个槽上。
Redis 集群有16384 个哈希槽,每个K通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分HASH槽,举个例子,比如当前集群有3个节点,那么:
- 节点A 包含0 到5500 号哈希槽.
- 节点B 包含5501 到11000 号哈希槽.
- 节点C 包含11001 到16384 号哈希槽.
这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点A, B, C中得部分槽到D上. 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.
位序列结构: Master节点维护着一个16384/8字节的位序列,Master节点用bit来标识对于某个槽自己是否拥有。比如对于编号为1的槽,Master只要判断序列的第二位(索引从0 开始)是不是为1即可。
范围映射
范围映射通常选择K本身而非K的函数计算值来作为数据分布的条件,且每个节点存放的K值域是一段连续的范围。
K的值域时通过业务决定的,业务层需要清楚每个区间的范围和redis实例数量,才能完整的描述数据分布,这使业务层的K值域与系统层的实例数耦合,数据分片无法在纯系统层实现,需要和业务层结合。
HASH和范围映射结合
首先对K进行hash计算,得到值域有限的hash值,再对hash值做范围映射,确定该K对应的业务数据存放的具体实例,这种方式的优势是节点新增或者退出时,涉及的数据迁移量小(变更节点上涉及的数据只需要和相邻的节点发生迁移关系)
- 哈希标签:
是一种可以让用户指定将一批K都能够被存放到同一个槽位中的实现方法,用户唯一要做的就是按照既定的规则生成K即可。redis在计算槽位的时候只会获取{}之间的字符串进行槽位编号计算,我们只需要保证多个K的{}内的字符串是相同的,就可以将他们分配到同一个槽位中:【abc{yanwu12138}def 和 ghi{yanwu12138}jkl 两个K会被分配到同一个槽位,因为{}中的字符串都是yanwu12138】 - 节点通信(gossip):
redis-cluster节点间通过gossip协议来进行通信。gossip协议简单来说就是集群中每个节点会由于网络分化、节点抖动等原因而具有不同的cluster全局视图,节点间通过gossip协议进行节点信息共享,这是业界比较流行的去中心化方案。gossip协议由MEET、PING、PONG三种消息实现,这三种消息实现的正文都是由两个clusterMsgDataGossip结构组成。
节点间共享的关键信息有以下几点:- 数据分片和节点的对应关系
- cluster中每个节点的可用状态
- cluster结构发生变化时,通过一定的协议对配置信息达成一致
- phb/sub功能在cluster的内部实现所需要交互的信息
集群的主从选举:
redis-cluster重用了sentinel的选举代码,cluster中每个节点都会定期的向其它节点发送PING消息,以此来交换各个节点的状态信息,节点分为三种状态:【在线、疑似下线、下线】
故障转移:
- 从下线master的所有slave中选择一个slave
- 被选中的slave执行SLAVE NO NOE命令,成为新的master
- 新master会撤销所有对已下线master的槽指派,并将这些槽都指派给自己
- 新maser对cluster广播PONG消息,告知其他节点自己已经成为新的master
- 新master开始接受和处理槽相关的请求
功能限制:redis-cluster相对于单机在功能上有一定的局限性
- K批量操作的支持有限
- K事务操作支持有限,支持多个K在同一个节点上的事务,不支持分布在多个节点的事务功能
- K作为数据区分的最小粒度,所以不能将一个大的键值对象映射到不同的节点(HASH、LIST)
- 不支持多数据库空间,单机的redis支持16个数据库空间,集群下只能使用db0一个数据库空间
- 复制结构只能复制一层,不支持嵌套树状复制结构
源码
线程模型-单线程,对于命令处理是单线程的,在IO层面同时面向多个客户端并发的提供服务,IO多路复用。
redis单线程快的原因:
- 纯内存,不需要操作磁盘
- 非阻塞IO
- 避免线程切换和竞态消耗
拒绝慢命令:
- KEYS
- FLUSHALL
- FLUSHDB
- SHOW LUA SCREIPT
- MUTIL/EXEC
- OPERATE BIG VALUE
RedisObject:
typedef struct redisObject {
/* 4位type表示具体的数据类型,redis中一共有5中数据类型,24足以表示所有的类型 */
unsingned type:4;
/* 4位encoding表示该类型的物理编码方式,同一种数据类型可能有不同的编码方式,目前redis主要有8种编码方式 */
unsingned encoding:2;
/* 表示当内存超限时采用LRU算法清除内存中的对象 */
unsingned lru:REDIS_LRU_BITS;
/* refcount表示对象的引用计数 */
int refcount;
/* ptr指针指向真正的存储结构 */
void *ptr;
} robj;
编码方式 | 说明 | 数据类型 |
---|---|---|
define REDIS_ENCODING_RAW 0 | Raw representation | 【STRING】 |
define REDIS_ENCODING_INT 1 | Encoded as integer | 【STRING】 |
define REDIS_ENCODING_HT 2 | Encoded as hash table | 【HASH & SET】 |
define REDIS_ENCODING_ZIPMAP 3 | Encoded as zipmap | 【】 |
define REDIS_ENCODING_LINKEDLIST 4 | Encoded as regular linked list | 【LIST】 |
define REDIS_ENCODING_ZIPLIST 5 | Encoded as ziplist | 【HASH & LIST & ZSET】 |
define REDIS_ENCODING_INTSET 6 | Encoded as intset | 【SET】 |
define REDIS_ENCODING_SKIPLIST 7 | Encoded as skiplist | 【ZSET】 |
以上是关于redis的主要内容,如果未能解决你的问题,请参考以下文章