美文网首页
Redis深度历险笔记

Redis深度历险笔记

作者: yuq329 | 来源:发表于2020-07-12 15:29 被阅读0次

    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深度历险笔记

          本文链接:https://www.haomeiwen.com/subject/bjagcktx.html