老鱼开篇-跟我学redis

605 阅读11分钟

Redis 内容整理

1、redis 数据类型及使用场景

类型名称常见使用场景常用命令
String计数器、共享存储:sessionset get
List文章评论列表、高性能lrange分页、粉丝列表等lpush lpop rpush rpop lrange
Hash简单爆款商品属性、地理位置存储hset hget hmset hmget
Set共同好友sadd srem sinter sunion sdiff
Zset排行榜、热搜等zadd zrem zrank zrevrank zrange
HyperLoglog
BitMap注册IP数、在线人数、连续或者不连续签到统计等bitset bitget bitop and/not/or
Geo附件的人geoadd geodist georadius
Stream实时通信

2、redis 内存划分

内存名称内存特点内存类型
数据内存分配器:jemalloc划分的used_memory
缓冲内存内存分配器:jemalloc划分的used_memory
进程操作系统分配used_memory_rss
内存碎片操作系统分配used_memory_rss

内存碎片率mem_fragmentation_ratio = used_memory_rss / used_memory, mem_fragmentation_ratio 一般大于1,且该值越大,内存碎片比例越大,在1.03左右是比较健康的状态(对于jemalloc来说);刚开始的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据,Redis进程本身运行的内存使得used_memory_rss 比used_memory大得多。

3、redis 数据编码类型及特点

数据类型编码类型特点
Stringint,embstr, raw当数值全部为整数类型时,采用int编码类型,8字节长度;当key的字节数key<=44时并且为非整型采用embstr, >44 采用raw编码类型; 44=为redisObject的长度16+sds的长度4+字符串长度=16+4+44=64,内存分配器可以一次性分配64字节空间
Listziplist linkedlist一个列表可以存储2^32-1个元素。3.0之前使用,3.2之后采用的是quicklist。ziplist: 连续内存块组成的顺序型数据结构, 用于元素个数少、元素长度小的场景; linkedlist:由一个list结构和多个listNode结构组成,链表中每个节点指向的是type为字符串的redisObject。quicklist: 由quicklistNode 和ziplist构成,每个ziplist都能存多个元素
Hashziplist hashtablehashtable:一个hashtable由1个dict结构、2个dictht结构、1个dictEntry指针数组(称为bucket)和多个dictEntry结构组成。所有的数据都是存在放dict的ht[0]中,ht[1]只在rehash的时候使用。dict进行rehash操作的时候,将ht[0]中的所有数据rehash到ht[1]中。然后将ht[1]赋值给ht[0],并清空ht[1]。Redis中的哈希之所以在dictht和dictEntry结构之外还有一个dict结构,一方面是为了适应不同类型的键值对,另一方面是为了rehash。dictEntry数组(bucket)是一个数组,数组的每个元素都是指向dictEntry结构的指针。redis中bucket数组的大小计算规则如下:大于dictEntry的、最小的2^n:1000个dictEntry,那么bucket大小为1024;如果有1500个dictEntry,则bucket大小为2048。在64位系统中,一个dictEntry对象占24字节(key/val/next各占8字节);如前所述,Redis中内层的哈希既可能使用哈希表,也可能使用压缩列表。只有同时满足下面两个条件时,才会使用压缩列表:哈希中元素数量小于512个;哈希中所有键值对的键和值字符串长度都小于64字节。
Set整数集intset 哈希表hashtable集合在使用哈希表时,值全部被置为null。只有同时满足下面两个条件时,集合才会使用整数集合:集合中元素数量小于512个;集合中所有元素都是整数值。且编码只可能由整数集合转化为哈希表,反方向则不可能
Zsetziplist skiplist跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点Redis的目的。跳跃表实现由zskiplistzskiplistNode两个结构组成:前者用于保存跳跃表信息(如头结点、尾节点、长度等),后者用于表示跳跃表节点。只有同时满足下面两个条件时,才会使用压缩列表:有序集合中元素数量小于128个;有序集合中所有成员长度都不足64字节。如果有一个条件不满足,则使用跳跃表;且编码只可能由压缩列表转化为跳跃表,反方向则不可能

4、redis 内存优化方案

方法描述
jemalloc分配特性估算key值大小分布,选择合适的长度,可以大幅节省空间;由于分配时按照2^n进行,所以17和16差别很大
使用整型此类型占用为8字节,使用int类型(8字节)存储来代替字符串
共享对象默认初始化时会有0-9999 个共享对象,可以减少对象的创建,调整参数:REDIS_SHARED_INTEGERS
缩短键值对的存储长度长度和性能成反比,越长的话编码越复杂,内存使用消耗更大,性能也越低,触发内存淘汰机制也更频繁,所以某些场景有必要对数据进行序列化和压缩

5、redis 数据持久化

持久化方式特点触发机制
RDB缓存快照文件备份,缓存时主进程通过调用操作系统fork函数复制当前进程的一个副本(子进程)来完成内存的备份快照文件写入,主进程可以继续对外提供服务,然后将写命令写入内存缓冲区中,等待下一次快照文件;子进程将内存中的数据写入临时文件,写完后用此临时文件替换就的RDB文件;服务器停机或者异常退出时会造成备份快照后的数据发生丢失RDB 可以最大化 Redis 的性能配置文件设定备份规则;执行save或者bgsave命令:"save 900 1" 表示15分钟(900秒钟)内至少1个键被更改则进行快照;调用flushall;执行主从复制操作
AOFapend only file 文件模式,按照指定规则每发生一次写命令,就会将命令写入AOF文件中,默认和RDB文件保存目录相同。发生更新操作时,由于命令存在缓存中,没有实时写入硬盘AOF文件中,需要通过缓存刷新机制来完成保存到硬盘的文件中;AOF在文件变得过大时,可以自动重写,将多条命令进行优化,减小存储内容,而且是有序的保存了所有的写操作命令,易于解析可配置参数为appendfsync always; appendfsync everysec;appendfsync no
混合持久化模式 (4.0增加,5.0默认开启)首先将内存数据以RDB的形式写入文件的开头,然后采用AOF的形式继续写入后续的操作命令,这样既能加快redis启动速度,又能保证数据不会发丢失查询: config get aof-use-rdb-preamble; 命令行设置:config set aof-use-rdb-preamble yes;配置文件设置

6、redis 主从复制

方式特点
全量同步在从服务器启动第一次连上主节点的时候,会触发主节点数据的全量同步;主从复制不会阻塞 master ,在同步数据时, master 可以继续处理 client 请求;步骤如下:1、同步快照:Master创建快照并发送给Slave, Slave导入并解析快照文件,Master同时会将后续命令保存到缓冲区中;2、同步缓冲:Master 向 Slave 同步存储在缓冲区的写操作命令;3、同步增量:Master 向 Slave 同步写操作命令
增量同步在从服务器完成全量同步后,从节点正常提供服务后,后续主节点新的写命令将通过循环方式以增量模式进行数据同步写入;断线重连有可能触发全量同步也有可能是增量同步( master 判断 runid 是否一致)

7、redis数据结构及内存占用

dictEntry:

Redis是Key-Value数据库,因此对每个键值对都会有一个dictEntry,里面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。共计 24 字节

struct dictEntry {
	void* key
	void* value,
	struct dictEntry* next
}

SDS:

简单动态字符串,占用空间为:free所占长度4+len所占长度4+ buf数组的长度(free+length+1) = free+len+9

struct sdshdr{ 
    //记录buf数组中已使用字节的数量 
    //等于 SDS 保存字符串的长度 
    int len; // 4字节
    //记录 buf 数组中未使用字节的数量 
    int free; // 4字节
    //字节数组,用于保存字符串 
    char buf[];
}

redisObject:

无论是哪种类型,Redis都不会直接存储,而是通过redisObject对象进行存储

struct robj { 
    unsigned type:4; //类型 五种对象类型 
    unsigned encoding:4;//编码 
    void *ptr;//指向底层实现数据结构的指针
    int refcount;//引用计数 
    unsigned lru:24;//记录最后一次被命令程序访问的时间 
}

type:代表5种数据类型(String, List等),占用 4bit

encoding: 对象内部编码,占用 4bit, 如:int 、embstr、raw、ziplist 、hashtable、linkedlist、skiplist等

lru:对象最后一次被访问的时间;(2.6版本占22bit,4.0版本占24bit)

refcount: 记录该对象被引用的次数,主要在对象的引用计数和内存回收使用,占用 4 Byte;Redis中被多次使用的对象(refcount>1),称

​ 为共享对象。共享对象的引用次数可以通过object refcount命令查看

ptr: 指向具体对象数据的指针,指向SDS,占用8Byte;

综上:一个redisObjet对象大小=4bit+4bit+24bit+4Byte+8Byte = 16Byte

8、redis集群模式

模式特点
主从+哨兵哨兵的作用:定时监控,报警,故障判定,自动故障迁移。redis主从的Leader的选举流程;定时监控:哨兵每10秒向主节点发送info命令,获取主从结构拓扑图信息;哨兵节点每2秒向其他哨兵节点或者redis节点发送当前哨兵信息及当前哨兵对于主节点的判定信息(通过redis publish/subscribe机制);每隔1秒每隔哨兵节点会向主节点、从节点、其余哨兵节点发送一次ping命令做心跳检测,是判定节点是否正常的重要依据。当主观下线SDOWN的节点是主节点时,此时该哨兵3节点会通过指令sentinel is-masterdown-by-addr寻求其它哨兵节点对主节点的判断,如果其他的哨兵也认为主节点主观线下了,则当认为主观下线的票数超过了quorum(选举)个数,此时哨兵节点则认为该主节点确实有问题,这样就客观下线了,大部分哨兵节点都同意下线操作,也就说是客观下线 ODOWN。
cluster所有的redis节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽,节点的fail是通过集群中超过半数的节点检测失效时才生效,客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可,redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value。集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过**(cluster-node-timeout)**,认为该master节点挂掉。如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

9、redis分布式应用场景及常见问题解决方案

场景/问题优缺点/解决方案
分布式锁秒杀、幂等判定等场景使用。不适合在强一致性场景下使用,如转账等
缓存雪崩大量key在同一时间失效,导致缓存中都没数据,查询直接命中数据库;key设置不同失效时间、多层缓存
缓存击穿某一个缓存热点key在过期时被大量访问,导致查询命中数据库;使用redis.setnx判定,只允许一个线程查库写入缓存,其余的等待
缓存穿透对不存在的key进行高并发访问,直接命中数据库;对查询结果为空的数据也进行缓存,时间设置短些;对一定不存在的key进行过滤,bitmap, 布隆表达式。
缓存双写一致性建议先更新数据库,再删除缓存。(延迟双删,第一步删除后sleep后再删除),使用消息队列。

10、redis常见面试题

  1. redis常见数据结构及使用场景
  2. redis 持久化方式和同步机制
  3. redis内存淘汰策略
  4. 哨兵作用及选举流程
  5. 集群作用和选举流程
  6. redis分布式锁实现方案
  7. redis缓存雪崩,击穿,穿透的含义及解决方案
  8. 缓存数据库一致性如何保证