Redis深度历险笔记

Redis深度历险笔记

基础与应用

Redis基础数据结构

  • 5种基础数据结构:stringlisthash(字典)、set(集合)、zset(有序集合)
  • 所有数据以唯一的key字符串作为名称,通过这个唯一的key获得value
  • 键值对操作
    • set key value:添加key:value键值对
    • get key:得到key对应的value
    • exists key:检查key是否存在
    • del key:删除key以及对应的value
    • mset key1 value1 key2 value2 ...:添加多个键值对
    • mget key1 key2 ...:获取多个键值对
    • expire key time:设置key有效时间,过期自动删除
    • setex key time valueset+expire,添加具有有效时间限制的键值对
    • setnx key value:如果key不存在,则创建键值对
    • incr value:如果value是整数,自增操作,增加1,最大不超过signed long
    • incrby value step:以step大小增减value,在signed long数值范围,否则报错
  • list(列表)
    • 相当于Java语言的LinkedList,双向链表,实际上其是一个“快速链表”(quicklist)的一个结构
      • 快速链表内部由一个个压缩列表(ziplist)串起来,压缩列表使用连续内存存储列表元素,这样就相当于结合了链表和数组
      • 优点:
        • 元素查找相比传统列表更快
        • 插入删除性能也没有收到太大的影响
        • 减轻内存碎片化
    • 通常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串,塞进Redis的列表,另一个线程从这个列表中轮询数据进行处理。
    • rpush key element1 element2 ...:在名为key的列表右端加入元素
    • lpush key element1 element2 ...
    • lpop key:从链表左端弹出元素,相当于先入先出FIFO
    • rpop key:从链表右端弹出元素,相当于先入后出,栈
    • lindex key index:从名为key的链表遍历到下标为index的元素
    • lrange key start end:提取key链表中[start,end]范围内的元素
    • ltrim key start end:去除key链表中[start,end]范围之外的两端元素,可以实现一个定长链表
  • hash(字典)
    • 相当于Java中的HashMap,无序字典,内部存储很多键值对
    • 结构上与Java一致,数组+链表,当产生Hash碰撞时,即会将碰撞的元素使用链表串接起来
    • 不同的是,Redis的字典值只能存取字符串,Javarehash是个耗时操作,需要一次性全部rehash(发生在扩容的时候),Redis为了高性能,不能阻塞服务,采用渐进式rehash策略,在rehash时保留新旧两个hash结构,查询时,同时查询两个结构,当rehash结束,采用用新的hash结构替代老的结构
    • hash结构可以用来存储用户信息,可以对用户结构中的每个字段单独存取
    • 缺点:存储消耗高于存储单个字符串
    • hset hash_name key "value":不加引号的话,如果alue中含有空格,会自动分隔成多个value
    • hgetall hash_name:获得hash_name字典名中的所有内容
    • hlen hash_name:获得hash_name字典中key的个数
    • hget hash_name key:获得字典中指定key下的元素
    • hmset hash_name key1 "value1" key2 "value2" ...:增加多个key及对应元素
    • hincrby hash_name key step:如果hash_namekey对应的value为数字,可以执行增减step操作
  • Set(集合)
    • Redis中的Set相当于Java中的HashSet,内部的键值对是无序的、唯一的,内部的实现相当于一个字典,字典中的所有value都是NULL
    • sadd key value1 value2 ...:为指定key添加1个或多个值
    • smembers key:列出key中的值
    • sismember key value:检测value是否在key对应的集合中
    • scard key:统计key集合中的value个数
    • spop key:随机弹出一个集合中的value
  • zset(有序列表)
    • 类似于Java中的SortedSetHashMap的结合体,内部实现用的是一种“跳跃列表”的数据结构
    • 为集合中的每个value赋予一个score,代表这个value的排序权重
    • zadd key score value:在key集合中添加权重为scorevalue
    • zrange key start end:根据score排序的结果(从小到大)
    • zrevrange key start end:根据score排序的结果(逆序,从大到小)
    • zcard key:统计key集合中的value个数
    • zscore key value:得到key集合中valuescore
    • zrank key value:得到key集合中valuerank
    • zrangebyscore key low_score high_scorekey集合中权重在[low_score,high_score]中的value,可以使用inf
    • zrem key value:删除key集合中的value
  • 通用规则
    • 容器型数据结构,1、不存在则创建;2、没有元素就自动删除
  • 过期时间
    • 过期时间以对象为单位,一个hash结构的过期是整个hash对象的过期
    • 设置完过期时间的对象,再次设置时可能改变过期设置

分布式锁

  • 多用户同时操作时可能出现数据冲突问题,因为读取和保存状态不是原子操作,不加控制的执行可能导致数据出错
  • setnx(set if not exists):设置锁,del删除锁
  • 死锁:如果设置完锁之后,在del之前出现异常,则会陷入死锁,锁永远得不到释放
  • 死锁处理办法:在拿到锁之后,为锁设定一个过期时间,保证出现异常之后,经过指定时间,锁也能得到释放
    • 如果设置过期时间时,服务器宕机,导致expire得不到执行,也会造成死锁
    • setnxexpire一起执行:set lockname value ex time nx
  • 超时问题:如果加锁和释放锁之间逻辑执行耗时超过为锁设定的过期时间,则为执行完的逻辑不能得到严格串行执行
    • 稍微安全的做法:set指定的value设定为一个随机数,释放锁时先匹配随机数是否一致,然后再删除key,可以确保当前线程占有的锁不会被其他线程释放,除非这个锁是因为过期而被服务器释放的
    • 但是匹配value和删除key不是一个原子操作,Lua脚本可以实现多个指令的原子性执行
    • 没有解决问题,如果真的超时了,当前逻辑没有执行完,其他线程也会乘虚而入
  • 可重入性:线程在持有锁的情况下,可以再次加锁

延时队列

  • 异步消息队列
    • 通过list实现,队列消息空了,需要让线程sleep,不然客户端会不断尝试pop数据,造成空轮询,消耗资源
  • 睡眠会导致消息延迟增大,缩短睡眠时间会降低延迟,但这样也就越来越接近空轮询
  • 阻塞读blpopbrpopb代表blocking,阻塞读在队列没有数据的时候会立即进入睡眠状态,一旦数据到来,则立刻醒过来。
  • 空闲连接自动断开:客户端闲置过久,服务器一般会主动断开连接,此时blpop/brpop抛出异常,需要对此进行处理
  • 锁冲突处理:加锁未成功怎么处理?
    • 直接抛出异常,通知用户稍后重试
    • sleep一会儿,然后再重试
    • 将强求转移至延时队列,过一会再试
  • 延时队列实现
    • 通过zset实现,将消息作为zsetvalue,到期处理时间未score,多线程轮询zset获取到期的任务进行处理(多线程保障可用性,某线程挂了,还有其他线程可以处理,多线程需要考虑并发争抢任务,使用zrem,根据删除的返回值确定是否拿到消息)
    • 使用lua进行原子化操作,避免争抢任务的浪费

位图

  • 不是特殊的数据结构,其内容是普通的字符串,byte数组,超过范围自动扩展
  • setbit key index 1/0:设置key对应valueindex位上的值
  • getbit key index:获得某个具体位置的值0/1
  • bitcount key:统计keyvalue的二进制中1的个数
  • bitcount key start end[start,end]范围的字符的二进制中1的个数
  • bitpos key 0:第一个0位
  • bitpos key 1:第一个1位
  • bitpos key 0/1 start end:从[start,end]范围的字符start开始,第一个0/1
  • bitfield key get u4 index:从keyvalue的第index+1位开始,取4个位,结果为无符号数
  • bitfield key get i4 index:从keyvalue的第index+1位开始,取4个位,结果为有符号数
  • bitfield key set u8 8 97:将keyvalue的第9位开始,将接下来的8位用无符号数97代替
  • bitfield key incrby u4 index 1:从第index+1位开始,对接下来的4位无符号数加1
    • incrby可能溢出,三种处理方式(wrap:折返,最大变最小;fail:失败;sat:饱和截断)

HyperLogLog

  • 提供不精确的去重计数方案,标准误差是0.81%
  • pfadd key value:与sadd用法一致,添加value
  • pfcount key:与scard用法一致,获取计数值
  • pfmerge:合并多个pf计数值
  • 缺点:需要额外占用12kb空间(与内部数学原理分桶有关)

布隆过滤器

  • 解决去重问题,节约空间,有一定的误判概率
  • 可以准确的确定元素是否不在集合中,但是部分不在集合中的元素会被认为已经加入集合(误判)
  • bf.add
  • bf.exists
  • bf.reserve显示创建布隆过滤器,可以设置keyerror_rateinitial_size
  • 原理:大型的位数组+几个不一样的无偏的hash函数

简单限流

  • 限制一段时间用户的访问量,用时间窗口,通过zset实现,以时间为score,每次更新删除有效score的范围之外的访问,并计算当前的访问量,如果当前的访问量已经达到最大允许,则不允许新的访问

漏斗限流

  • 根据漏斗模型,如果漏斗嘴流水速率小于灌水的速率,就要等待漏斗具有足够的空间再进行灌水
  • 将漏斗模型中需要的字段(比如流水速率,剩余空间,上一次放水时间等)放入Hash结构中,单数hash结构取值和回填无法保证原子性,需要适当的加锁控制,但是加锁也有可能失败,失败的话又要选择重试或者放弃。
  • Redis-Cell:限流Redis模块,使用漏斗算法,提供原子的限流指令
  • cl.throttle key capacity rate [need-elem]
  • 例如cl.throttle laoqian:reply 15 30 60 1:用户老钱回复行为的频率为每60s最多30词,速率分成了2个参数,漏斗初始的容量为15,至少有1条回复

GeoHash

  • 地理位置Geo模块
  • 应对问题:高并发场景下,在数据库中查询矩形区域算法(根据坐标查找附近矩形区域的目标)性能有限
  • 地理位置距离排序算法GeoHash,其将二维数据映射到一维,一维上相近的点,空间距离也近
    • 将二维平面切分(可以分成四块,确定目标数据所在的方块,进行编码,例如00(左上)01(右上)10(左下)11(右下)),不断往下切分,切分的块将越来越接近目标,拼接得到的编码可以用来计算距离
  • geohash内部采用普通的zset结构实现
  • geoadd set-name 经度 纬度 地址名
  • geodist set-name 地址1 地址2 距离单位
  • geopos set-name 地址:返回的结果与输入有些许误差,因为输入时二维转一维损失了部分精度
  • geohash setname 地址:获取地址对应的经纬度转换后,在集合中保存的编码字符串
  • georadiusbymember set-name 地址 距离 距离单位 返回结果的数量限制 结果排序方式:查询指定元素附近的其他元素
    • withcoord
    • withdist:显示距离
    • withhash
  • georadius:根据坐标查询附近的元素

scan

  • 如何从海量的key中找出满足特定前缀的key列表
  • keys 正则表达式字符串:根据正则表达式的要求,过滤出满足条件的key
    • 没有offsetlimit参数,满足条件的key过多,将会全部输出
    • 是一种遍历算法,复杂度O(n),因为Redis单线程程序,顺序执行所有指令,如果key非常多,则非常耗时,导致服务卡顿
  • scan相比keys优点
    • 复杂度依然O(n),但是其通过游标分步进行,不会阻塞线程
    • 提供limit参数,可以控制每次返回结果的最大条数,limit只是一个hint,返回的结果可多可少
    • keys一样,提供模式匹配功能
    • 服务器不需要为游标保存状态,游标的唯一状态就是scan返回给客户端的游标整数
    • 遍历的过程中,如果有数据修改,改动后的数据能不能遍历到是不确定的
    • 单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零
  • scan 游标 match 正则字符串 count 数量
  • scan的遍历顺序不是从第一维的散列数组的第0位开始,而是采用高位进位加法的方式,根据位掩膜遍历所有的槽
  • 高位进位加法的遍历顺序保证扩容缩容的重复遍历(缩容时可能在某个槽位上的元素重复遍历)
  • 由于渐进式rehashscan需要同时扫描新旧槽位,将结果融合后返回
  • 针对指定容器的遍历:zscanhscansscan
  • 大key扫描
    • 如果Redis实例中形成了很大的对象,这个大key将会导致数据迁移卡顿,扩容、删除等操作也会造成卡顿
    • 尽量避免大key的产生
    • redis-cli中提供 --bigkeys 扫描

原理

线程I/O模型

  • Redis是单线程程序
  • 所有的数据都在内存中,所有的运算都是内存级别的运算
  • 高并发实现:多路复用(IO多路转接
    • 使用非阻塞IO
    • 事件轮询(selectpollepollkqueueJava中的NIO
    • 指令队列:客户端的指令先到先服务,顺序处理
    • 响应队列:将指令的返回结果回复给客户端
    • 定时任务:定时任务记录在一个“最小堆”结构中,将最快要执行的任务排在堆的最上方,将最快要执行的任务还需要的时间记录下来,这个时间就是select系统调用中的timeout参数(也就是执行系统调用时,将这部分耗时与定时任务结合起来)

通信协议

  • Redis作者认为数据库系统的瓶颈一般不在于网络流量,而在于数据库自身内部的逻辑处理上
  • RESPRedis Serialization Protocol):Redis序列化协议
    • 实现简单,解析性好
    • 单元结束统一加上\r\n
    • 单行“+”开头、多行“$”开头,后跟字符串长度
    • 整数值以":"开头,后跟整数的字符串形式
    • 错误消息以“-”开头
    • 数组以“*”开头,后跟数组长度:\*3\r\n:1\r\n:2\r\n:3\r\n
    • NULL$-1\r\n
    • 空行:$0\r\n\r\n(两个\r\n之间隔的是空行)
  • 客户端->服务器:发送多行字符串数组
    • 例如:*3\r\n$3\r\nset\r\n$6\r\nauthor\r\n$8\r\ncodehole\r\n
      • set author codehole
  • 服务器->客户端:多种响应的组合

持久化

  • 防止突然宕机,内存中的数据丢失
  • 两种持久化方式:快照、AOF日志
  • 快照
    • 一次数据全量备份,二进制序列化形式,存储上非常紧凑
    • 使用多进程的写时拷贝COWcopy on write)机制实现持久化,所以父进程的数据改变,持久化的内容也会相应改变
    • 子进程做数据持久化,不会修改现有内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中
    • 当父进程对数据段的一个页面进行修改时,会将被共享的页面复制一份出来
  • AOF日志
    • 连续的增量备份,记录的是内存数据修改的指令记录文本,长期运行AOF会变得无比巨大,需要定期重写,瘦身
    • 通过对AOF指令重放,恢复内存数据结构的状态
  • AOF重写
    • Redis提供了bgrewriteaof指令用于对AOF日志文件进行瘦身
    • 原理就是开辟一个子进程对内存进行遍历,转换成一系列Redis的操作指令,序列化到一个新的AOF日志文件中,序列化完毕之后,再将新的增量AOF日志追加到新的AOF文件
  • fsync
    • 写操作的内容是写到内存缓冲中的,将数据刷回磁盘的过程中,服务器宕机会造成日志丢失
    • fsync指定文件fd的内容强制刷到磁盘,也就是冲洗,IO操作是一个耗时操作,所以Redis通常默认是每个1s执行一次fsync
  • 运维
    • 通常有Redis的从节点执行持久化操作,这样不增加主节点的负担
    • 如果从节点长期连不上主节点,就会出现数据不一致的情况
      • 需要实时监控
      • 多增加从节点,降低网络分区(造成无法访问主节点)的概率
  • Redis混合持久化
    • 很少用rdb(快照)来恢复内存状态,因为会丢失大量数据
    • 通常使用AOF日志重放
    • 混合:rdb文件的内容和增量的AOF日志文件组合,AOF不再是全量的日志,而是持久化开始到持久化结束这段时间发生的增量AOF日志

管道

  • 技术本质上由Redis客户端提供,跟服务器没有什么直接的关系
  • 管道将多个操作合并,消耗一次网络,节省IO时间

事务

  • 确保连续多个操作的原子性
  • begincommitrollback分别对应Redismultiexecdiscard
  • 所有的指令在exec之前不执行,而是缓存在服务器的一个事务队列中
  • Redis的事务不具备原子性,仅仅满足事务的“隔离性”中的串行化
  • watch:乐观锁
    • 分布式锁:悲观锁
    • Redis会检查关键变量自watch之后是否被修改了,如果被修改,则返回NULL,告知事务执行失败
    • watch不能在multiexec之间

PubSub

  • Redis消息队列的不足:不支持消息的多播机制
  • PubSubpublisher subscriber发布者/订阅者模式)
  • 消费者使用listen阻塞监听消息
  • 模式订阅Pattern Subscribe:订阅多个主题
  • 缺点:
    • 生产者传递来一个消息,Redis会立刻将消息向消费者发送,如果没有消费者,那么这个消息直接被丢弃
    • Redis停机重启,PubSub消息无法恢复

小对象压缩

  • 如果使用32bit编译,内部所有的数据结构所使用的指针空间占用会少一半
  • 小对象压缩存储(ziplist
    • ziplist是一个紧凑的字节数组结构
  • 内存回收机制
    • 操作系统以页作为单位回收数据,但是Rediskey可能是保存在多个页上的,而由于页上还有其他key,所以系统不会立刻回收资源
    • flushdb删除所有数据,系统基本会立刻回收(emmm,这操作。。。)
    • 虽然系统没有回收数据,但是Redis可以使用它删除了的内容占有的位置
  • 内存分配算法
    • 使用第三方库jemallocfacebook)来管理内存,也可以切换到tcmallocgoogle

集群

主从同步

  • 从节点作为主节点宕机的备用
  • CAP原理
    • C:Consistent 一致性
    • A:Avaliabilty 可用性
    • P:Partition tolerance 分区容忍性
    • 当网络分区发生时(也就是不同节点的网络通信被阻断),一致性和可用性两难全
  • 最终一致
    • Redis主从数据是异步同步的,所以分布式Redis不满足一致性要求
    • 即使主从节点断开,Redis依然对外提供修改服务,所以Redis满足可用性
    • Redis保证最终一致性,从节点努力追赶主节点
  • 主从同步和从从同步
  • 增量同步
    • 将指令流发送给从节点,从节点一边接收指令一边执行,将同步的进度(偏移量)进行反馈
    • 为了节约指令流缓存占用的内存,内部使用环形数组保存待发送的指令流,如果数组满了,从数组的头部开始覆盖,如果主从网络出现问题或者从节点执行太慢,从节点未执行的指令被覆盖,则无法再通过指令流同步
  • 快照同步
    • 主节点使用快照备份数据,将数据发送给从节点,从节点接收并加载数据,之后进行增量同步
    • 由于快照同步过程中,主节点的复制buffer还在不断向前移动,如果快照同步时间过长或者复制buffer太小,则增量指令可能会被覆盖,导致从节点开启增量同步失败,又重新快照同步,造成快照同步死循环
  • 增加从节点
  • 无盘复制
    • 直接通过套接字将快照内容发送到从节点,生成快照是一个遍历的过程,主节点一边遍历内存,一边将序列化的内容发送到从节点
  • wait指令
    • 让异步复制变身同步复制,确保系统的强一致性(不严格)
    • wait 节点数 等待时间:如果等待时间为0,则一直等待

Sentinel(哨兵)

  • Redis Sentinel集群可以看作一个Zookeeper集群,是一个集群高可用心脏
  • 负责监控从节点的健康,当主节点宕机,自动选择一个最优的从节点切换成主节点
  • 客户端连接集群时,首先连接Sentinel,通过Sentinel查询主节点的地址
  • 消息丢失
    • 主节点宕机时,从节点可能没有收到全部同步消息,未同步的消息就丢失了
  • Sentinel用法
    • discover_xxx发现主从地址(discover_master
    • xxx_for从连接池拿出一个连接使用

Codis

  • 单个Redis的内存不宜过大,内存太大导致rdb文件过大
  • Redis集群方案将众多小内存的Redis实例整合起来,完成海量数据的存储和高并发读写操作
  • Codis负责将客户发来的指令转发到后面的Redis实例来运行
  • Codis是无状态的,是一个转发代理中间件,可以启动多个Codis供客户端使用,每个Codis节点是对等的
  • Codis分片原理
    • 将所有的key划分为1024个槽位(可以设置)
    • 多客户端传来的key进行crc32运算计算hash值
    • hash值对1024进行取模运算,得到的余数就是对应的槽位
    • 每个槽位都会唯一的映射到后面的Redis实例Codis在内存中维护槽位和Redis实例的映射关系
  • 不同的Codis实例之间的槽位关系如何同步
    • 如果槽位映射关系只存储在内存中,则不同的Codis实例之间的槽位关系就无法得到同步,需要持久化槽位关系
    • Codis将槽位关系存储在Zookeeper中,提供一个Dashboard观察和修改槽位关系,Codis Proxy监听变化,并重新同步槽位关系
  • 扩容
    • 一开始1024个槽全部指向同一个Redis,如果Redis内存不足,则增加Redis实例,这时候需要将一半槽位划分到新节点,需要对这一半的槽位对应的所有key进行迁移
    • CodisRedis进行了改造,增加了SLOTSSCAN指令,可以遍历指定slot下的所有key
    • 迁移过程中,如果接收到新的请求(正在迁移的服务器),会立即强制对当前请求的key进行迁移,迁移完成后,将请求转发到新的Redis实例
  • 自我均衡
    • 自动均衡会在系统比较空闲的时候观察每个Redis实例对应的slot数量,如果不平衡,自动进行迁移
  • Codis代价
    • 因为所有key分散在不同的Redis实例中,所以不再支持事务,事务只能在单个实例中完成
    • 单个keyvalue不宜过大
    • 增加Proxy作为中转层,网络开销要比单个Redis
    • 集群配置中心使用zookeeper实现,增加了zookeeper运维代价
  • 优点
    • 比官方集群简单
  • mget:批量获取多个key

Cluster

  • 将所有数据划分为16384个槽位,每个节点负责其中一部分槽位
  • 槽位的信息存储与每个节点中,不像Codis,不需要另外的分布式存储空间来存储节点槽位信息
  • Redis Cluster的客户端来连接集群时,也会得到一份集群的槽位配置信息。当客户要查找某个key时,可以直接定位到目标节点
  • 槽位定位算法
    • 默认对key使用crc16算法进行hash,对16384取模得到具体槽位
    • 允许强制将某个key放到特定槽位上
  • 跳转
    • 当客户端像一个错误的节点发出了指令后,该节点向客户端发送一个特殊的跳转指令,告诉客户端去指定节点获取数据
    • MOVED 槽位编号 目标节点地址
  • 迁移
    • redis-trib可以让运维人员手动调整槽位的分配情况
    • 提供自动化平衡槽位工具
    • Redis迁移的单位是槽,当一个槽正在迁移时,这个槽处于中间过渡状态
      • 源节点状态为migrating
      • 目标节点状态为importing
      • redis-trib首先设置两节点的状态,然后一次性获取源节点槽位的所有key列表,在挨个key进行迁移,迁移结束,源节点删除对应key内容
      • 迁移过程是同步的,源节点处于阻塞状态,知道key删除
  • 容错
    • 为主节点设置若干从节点
  • 网络抖动
  • 可能下线与确定下线
    • 去中心化,一个节点认为某个节点失联了并不代表所有节点都认为它失联了,集群需要一次协商的过程
  • 槽位迁移感知
    • MOVED
    • ASKING
  • 集群变更感知

拓展篇

Stream

  • 支持多播的可持久化消息队列
  • 简述
    • Stream消息队列实现了可持久化,消息内容比较安全。其上的消息通过链表的结构串接起来,使用消息消费组去消费消息,消费组内部的消费组之间存在竞争关系,消费组只会消费消息一次,具体内部由哪个消费者消费,只会让最快响应的消费者处理消息。另外,消费组从哪一个消息位置消费,需要利用last_delivered_id进行标记。还有,抢到消息的消费者有可能突然宕机,没能处理消息或者没能返回ack(确认信息),那么我们不能确保这个消息已经被正常消费,需要将这个未能确认的状态记录下来,这里使用PEL,确保客户端至少消费消息一次
  • 每个Stream具有唯一的名称,就是Redis中的key,首次使用xadd指令时自动创建
  • 每个Stream都可以挂多个消费组,每个消费组会有个游标last_delivered_id,表示当前消费组已经消费到哪条消息了
  • 每个消费组都有一个Stream内的唯一的名称,消费组不会自动创建,需要单独的指令xgroup create创建,指定从Stream中的某个消息ID开始消费
  • 消费组之间的状态独立,同一份Stream内部的消息会被每个消费组都消费到
  • 同一个消费组可以挂接多个消费者,这些消费者之间是竞争关系,任何一个消费者读取了消息都会使游标往前移动
  • 消费者内部有一个状态变量pending_ids,记录当前已经被客户端读取,但是还没有ack的消息,如果没有ack,这些状态加入PELpending entries list),确保客户端至少消费了消息一次
  • 消息ID:可以服务器自动生成,也可以由客户端指定,必须是“整数-整数”,后面加入的消息比前面的大
  • 消息内容:键值对
  • 增删改查
    • xadd
    • xdel
    • xrange
    • xlen
    • del
  • 独立消费 xread
  • 创建消费组 xgroup create
  • 消费
  • Stream消息过多:可以设置定长Stream功能
  • 消息如果忘记ackPEL列表将会不断增长
  • PEL如何避免消息丢失:PEL记录了已经发送的ID,根据ID重发消息
  • Stream高可用:在SentinelCluster集群下,Stream支持高可用。但由于Redis的指令复制是异步的,在failover发生时,可能丢失极小部分数据
  • 分区partion
    • 不支持原生分区能力,通过分配多个Stream模拟

info指令

  • 显示Redis状态
  • Server
  • Clients:客户端相关信息
  • Memory:服务器运行内存统计数据
  • Persistence:持久化信息
  • Stats:通用统计数据
  • Replication:主从复制相关信息
  • CPU
  • Cluster:集群信息
  • KeySpace:键值对统计数量信息

再谈分布式锁

  • 集群中的分布式锁,如果主机点申请成功了一把锁,但这把锁还没有同步到从节点,此时主节点宕机,从节点变成主节点,丢失了锁
  • 解决:使用Redlock算法,加锁时,将超过一半的节点加锁成功,此时才算加锁成功,释放锁时,向所有节点发送del指令(大多数机制,投票机制)

过期策略

  • 过期的key集合
    • 将过期的key放入一个独立的字典中,定时遍历这个字典来删除到期的key,还利用惰性删除(只要下一次访问已经删除的元素,就立即删除)
  • 定时扫描
  • 从节点的过期策略
    • 主节点在key到期时,会在AOF文件里增加一条del指令,同步到所有从节点,从节点通过执行del指令来删除过期的key

LRU

  • Redis内存超过物理内存限制,内存数据会开始与磁盘产生频繁的交换,降低Redis性能
  • maxmemory限制最大内存
  • 当内存超过maxmemory
    • noeviction:不允许继续服务写请求,读请求继续进行
    • volatile-lru:尝试淘汰设置了过期时间的key,最少使用的key优先被淘汰
    • volatile-ttl:与上面一样,但是淘汰策略不是lru,而是比较key的剩余寿命ttl的值,越小越先淘汰(优先淘汰最快要过期的key
    • volatile-random:随机淘汰设置了过期时间的key
    • allkeys-lru:最少使用的key优先被淘汰(针对全体key,没有设置过期时间的key也会被淘汰)
    • allkeys-random:随机淘汰key
  • Redis使用近似LRU算法
    • 采用随机采样法来淘汰元素,在现有字段上增加了最后一次被访问的时间戳字段
    • 使用懒惰处理,当内存超过maxmemory,执行LRU,直到内存满足要求
    • 例如随机采样5个key(可以设置),淘汰掉最旧的key(不同设置,采样集合不同)

懒惰删除

  • Redis为什么采用懒惰删除?
    • 如果单个key是个非常大的对象,删除操作会导致单线程卡顿
    • 使用unlink key,对删除操作懒处理,丢给后台线程来异步回收内存
  • flush
    • Redis提供的flushdbflushall指令,用来清空数据库
    • flushall async:异步处理清除指令,由后台线程慢慢处理
  • 异步队列
    • 主线程将对象的引用从“大树“中摘除后,将这个key的内存回收操作包装成一个任务,塞进异步任务队列,后台从这个队列中取任务
    • 必须是一个线程安全的队列
  • AOF Sync也很慢
    • AOF Sync操作的线程是一个独立的异步线程,和懒惰删除线程不是一个线程
  • 更多异步删除点
    • 很多删除点也可以使用异步删除机制

Jedis

  • Jedis连接池-JedisPool
  • Jedis不是线程安全的

保护Redis

  • 指令安全
    • Redis在配置文件中提供了rename-command指令将某些危险的指令修改成特别的名称,用来避免人为误操作。
    • 将指令rename”“,将导致这条指令无法在Redis中执行了
  • 端口安全
    • Redis默认监听6379,如果当前服务器有外网ipRedis服务将会直接暴露在公网上
    • 可以在配置文件中指定监听的IP地址
    • 可以增加Redis密码访问机制
  • Lua脚本安全
  • SSL代理
    • spiped

Redis安全通信

  • Redis本身并不支持SSL安全连接,需要借助SSL代理软件,让通信数据得到加密
  • spiped

源码篇

字符串

字典

压缩列表

快速列表

紧凑列表

基数树

LFU与LRU

懒惰删除的巨大牺牲

深入字典遍历

你可能感兴趣的:(Redis深度历险笔记)