多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 一、Redis 键(Key) 构成 ```C typedef struct redisObject { unsigned type:4;//类型 五种对象类型 REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。 unsigned encoding:4; //编码 4表示位数 void *ptr;//指向底层实现数据结构的指针,指向具体数据 //... int refcount;//引用计数 //... unsigned lru:LRU_BITS; //LRU_BITS为24bit 记录最后一次被命令程序访问的时间        //高16位存储一个分钟数级别的时间戳; 低8位存储访问计数; lfu; 最近访问次数; //... } robj; ``` 1. 查看对象类型 `type key` :-: ![](https://img.kancloud.cn/e2/24/e224a038150723629b35b7ee13966fec_322x70.png) 2. 获取value具体编码 `object encoding key` :-: ![](https://img.kancloud.cn/be/72/be726c092b7c2587c02f0a84f854ec63_704x210.png) > 可以看出,value为string类型的,编码可以有3种:`embstr`,`int`,`raw`;当字符串长度大于44(整个sds总体超过64)时,就用raw存储。sds结构自身占用19个字节,字符串结尾标识"\\0"占一个字节,所以**64 - 19 -1 = 44** 3. 查看key的结构信息 `debug object key` :-: ![](https://img.kancloud.cn/e0/ce/e0ce3560159b4f3105f486306c1e50d3_831x122.png) ## 二、Redis二进制安全 1. 什么是二进制安全? 二进制安全就是指,在传输数据时,保证二进制数据的信息安全,即不被篡改、破译等。简单来说,就是只关心二进制化的字符串,不关心具体格式,只会严格的按照二进制数据存取,不会妄图以某种特殊格式解析数据。例如:在C语言中,就以"\\0"来判定字符串的结尾。 2. Redis是如何保证二进制安全的? ~~~c struct sdshdr{        int len;//buf数组中已经使用的字节的数量,也就是SDS字符串长度        int  free;//buf数组中未使用的字节的数量        char buf[];//字节数组,字符串就保存在这里面 }; ~~~ Redis 通过定义上述结构体的方式,扩展了C语言底层字符串的缺点;不再像C语言那样,以"\\0"作为字符串的结尾,而是使用了独立的`len`字段来表示字符串的长度。这样就避免了如果字符串中出现"\\0"而被截取忽略后面字符串的问题;保证了二进制安全。 > 基于此,Redis的string可以支持各种类型(图片、视频、文本等);而C语言的字符串就只能存储文本格式的数据。 :-: ![](https://img.kancloud.cn/98/c0/98c03487d8feec3f54d5a9dfff2bac87_709x245.png) 3. Redis的简单动态字符串SDS对比C语言中的字符串char,有什么优势? * 可以在O(1)的时间复杂度得到字符串的长度 (sds结构中,len字段直接记录了长度) * 二进制安全 * 可以高效的追加字符串操作 原理:sds会判断当前字符串空余(free)的长度与需要追加的字符串长度;如果空余大于需要追加,就会直接追加,减少了重新分配内存的操作;如果空余小于需要追加,那么就先对sds进行扩展,然后再追加;只是这里扩展内存是按照一定的机制进行的,扩展后多余的空间不释放,方便下次追加字符串,会造成一定的内存浪费,但是在频繁追加操作下,这种机制就很高效。 4. 1. SDS 内存扩展机制 在已经分配的内存低于1M时,每次扩容都是以现有内存2倍的方式扩容;当超过1M时,每次只扩容1M ## 三、5种基本数据类型 ### 1、字符串—string 1). 常见命令 ~~~ set get | mset mget | setrange getrange | getset | append | strlen ​ incr decr | incr by decrby | ​ setbit getbit | bitcount | bitop | bitpos ~~~ 2). 使用场景 * 计数器。如:文章浏览数、帖子点赞数等 ~~~ incr article:100.view # ID为100的文章,每打开一次,浏览次数增加1 ~~~ * 分布式锁。如:秒杀活动中,商品库存问题 ~~~ set goods:100 1 EX 30 NX # 当key goods:100 不存在时拿到锁,并设置过期时间为30s ~~~ * 缓存。如果:热点数据的缓存 ~~~ set hotgoods:100 '{"id":100, "price": 998, "title": "鸟哥笔记"}' # 缓存热卖书籍基本信息 ~~~ * 活跃用户数统计 ~~~ # 假如:某东 618 活动,需要为用户准备礼品;现需要统计最近三天活跃用户,用于备货 # 通过bitmap,以日期为key记录用户的登录,然后拿对应日期做与运算,就很容易实现 ​ setbit 20210615 10 1  # 6月15号,ID为10的用户登录 setbit 20210615 8 1   # 6月15号,ID为8的用户登录 ​ setbit 20210616 10 1  # 6月16号,ID为10的用户登录 setbit 20210616 15 1  # 6月16号,ID为15的用户登录 setbit 20210616 5 1   # 6月16号,ID为5的用户登录 ​ setbit 20210617 15 1  # 6月17号,ID为15的用户登录 setbit 20210617 13 1  # 6月17号,ID为13的用户登录 ​ # 可以看出15、16、17号这三天,总共有5位用户活跃 bitop or mau 20210615 20210616 20210617 # 会将计算结果以mau作为key存储 bitcount mau # 结果为5 ~~~ :-: ![](https://img.kancloud.cn/ce/ae/ceaee83a946a59a1f204e4a75ea3ce26_734x389.png) * 用户登录天数统计 ```bash # 需求:需要统计某个用户某个时间段的登录天数 # ID为88的用户,1月份中有登陆行为的天数;以1月1号为第1天,12月31号为第365天 setbit login:88 0 1 # 第一天有登陆 setbit login:88 8 1 # 第8天有登陆 setbit login:88 12 1 # 第12天有登陆 setbit login:88 13 1 # 第13天有登陆 setbit login:88 20 1 # 第20天有登陆 setbit login:88 25 1 # 第25天有登陆 setbit login:88 27 1 # 第27天有登陆 bitcount login:88 # 结果为:7 ``` ### 2、列表—list > List, 元素可以重复,可以按照添加的先后保证顺序 1)、常用命令 ~~~ lpush rpush | lpop rpop | blpop brpop | lrem linsert | llen | lrange | lindex ~~~ 2)、使用场景 * 栈 ~~~ lpush lpop ~~~ * 队列 ~~~ lpush rpop ~~~ * 阻塞MQ ~~~ lpush brpop ~~~ :-: ![](https://img.kancloud.cn/6e/3e/6e3e5c27a740366ec7737453e423d8ee_463x319.png) ### 3、哈希—hash 1)、常用命令 ~~~ hset hget | hmset | hmget | hstrlen | hgetall | hlen | hincrby | hincrbyfloat | hexists | hdel | hkeys ~~~ 2)、使用场景 * 存储对象信息 ~~~ # 存储用户信息;如姓名,年龄,地址等 hmset uid:101 name zhangsan age 18 addr 北京 ~~~ ### 4、集合—set > 集合中的元素会去重,保证不重复 1)、常用命令 ~~~ sadd srem spop | sinter sdiff sunion| sinterstore sdiffstore sunionstore | smembers | sismember | srandmember | scard ~~~ 2)、使用场景 > 抽奖和关注模型两大类 * 抽奖程序 ~~~ # 100张购物卡,共30人参与抽奖 # 奖品大于抽奖人 sadd k1 tom xxoo xoox xoxo oxxo ooxx oxox # 将30个参与抽奖的人添加到集合 srandmember k1 -100 # 这里-100,会返回100个元素,随机重复 ​ # 公司年会抽奖,一等奖1 二等奖3 三等奖 5 中奖者会从参与者中剔除,不再参与其他奖项 sadd k1 tom xxoo xoox xoxo oxxo ooxx oxox # 将参与抽奖员工添加到集合 spop k1 1 # 一等奖 spop k1 3 # 二等奖 spop k1 5 # 三等奖 ~~~ * 共同关注 ~~~ # 小王关注了:刘德华、罗大佑、郭富城、黎明 # 小李关注了:黎明、张曼玉、马龙、张继科 # 求小王和小李共同关注的人 sadd k1 刘德华 罗大佑 郭富城 黎明 sadd k2 黎明 张曼玉 马龙 张继科 sinter k1 k2 # 结果为黎明 ~~~ * 猜你喜欢 ~~~ # 我关注了:刘德华、罗大佑、郭富城、黎明 # 小王关注了:黎明、张曼玉、马龙、张继科 # 当我进入小王的主页后,可以推荐我关注 张曼玉、马龙、张继科 sadd k1 刘德华 罗大佑 郭富城 黎明 sadd k2 黎明 张曼玉 马龙 张继科 sdiff k2 k1 # 张曼玉 马龙 张继科 ~~~ ### 5、有序集合—sort\_set > 集合中数据去重,并可以按照给出的规则(一定的分值)排序 1)、常用命令 ~~~ zadd zrange zrangebyscore zcount | zincrby | zinterstore zunionstore | zrevrange zrevrangebyscore | zremrangebyscore ~~~ 2)、使用场景 * 排行榜 ~~~ # 热点新闻,每点击一次,热搜值加1 zincrby hotnews:20210822 1 汪峰开演唱会 ​ # 展示当天排行榜前十的热搜 zrevrange hotnews:20210822 0 9 withscores ​ # 计算最近3日热搜榜 zunionstore unkey 3 hotnews:20210820 hotnews:20210821 hotnews:20210822 ​ # 从上述3日热搜榜中,展示前3 zrevrange unkey 0 2 withscores ~~~ * 带有权重的队列 ## 四、跳表(skip list) 跳表是一个特殊的链表,相比一般的链表有更高的查找效率;Redis中的有序列表就使用了这种结构 :-: ![](https://img.kancloud.cn/29/60/2960b00b89e5b5ca355f966e311955c8_615x246.png) 1. 查找 (例如:查找40) * 第一步拿目标40跟三级索引第一个节点18比较;由于40 大于 18,则跟18的下一个节点45进行比较,而40小于45 * 通过第一步,可以知道目标40介于三级所以第一个节点和下一个节点之间,所以直接来到三级索引第一个节点18的二级索引 * 此时在二级索引,40大于18的下一个节点36,则移动指针到36;由于40小于36的下一个节点45,所以将指针移动到二级索引36的上级原始链表 * 此时发现,原始链表36的下一个节点复合条件 2. 插入节点 * 新插入的节点和各级索引节点逐个比较,确定原始链表的位置(同上述查找过程) * 把新的数据插入到原始链表 * 利用抛硬币的随机方式,决定是否将新的节点提升为上一级索引 3. 删除节点 * 自上而下,查找第一次出现节点的索引,并逐层找到每一层对应的节点 * 删除每一层找到的节点,如果该层只剩下一个节点,则删除整个层(原始链表除外) ## 五、管道(Pipeline) 当有多个命令(command)需要被及时提交,并且这些命令对相应结果没有互相依赖、对响应也无需立即获得;那么就可以使用管道来实现这种批处理。这在一定程度上可以提升性能,原因主要是TCP连接中减少了交互往返时间。假如有三条命令,单个提交就得需要3次往返;而使用管道批处理,只需要一次往返。Redis server 收到Pipeline发送过来的数据后,会以队列的形式放在内存中后,开始一条条的执行。 :-: ![](https://img.kancloud.cn/20/78/20780b90bee0983ba05a1d7a18678668_692x266.png) 1. 注意事项 * 可以为Pipeline操作新建Client连接,让其与其他正常操作隔离开在不同的Client连接中(由于Pipeline是独占连接的,所以在此期间是不可以进行其他操作的) * Pipeline所能容忍的操作数,和socket缓冲区大小有很大的关系,受限于server的物理内存和网络接口的缓冲能力 * Pipeline只是让一批命令按顺序执行,不能保证原子性 2. 使用场景 将数据库中的一批数据,一次性批量的存入Redis;可以考虑采用Pipeline实现 ## 六、发布/订阅 (Pub/Sub) 1. 常用命令 ~~~ publish subscribe unsubscribe | psubscribe punsubscribe ~~~ 2. 使用场景 * 普通的实时聊天、群聊功能 * 网站某一模块更新后,推送消息给到订阅者 3. 与Redis通过list结构实现消息队列的区别: list 消息队列,再有多个消费者的情况下,一条消息只会有一个消费者获取消费; 而发布订阅者,监听同一发布者同一频道的消息,多个订阅者都会收到消息 > 假如,现在需要开发一个聊天软件,需要实现: > > 1、好友可以接收消息 > > 2、可以查询聊天信息(考虑3天以内查询频次高,3天以外基本不会查询) > > 请给出架构方案 :-: ![](https://img.kancloud.cn/d6/f0/d6f0e92efdcfdec757250a721fceda57_516x466.png) 上述架构方案,重点关注红色虚线的部分。Client负责将消息Pub出去,这时候同一个频道的好友就会收到消息;同时还需负责将数据存入DB和sort\_set以备满足不同时间的查询(红色虚线部分),这种情况下很容易出现当发生网络错误时,DB和sort\_set丢数据。 针对上述问题,可以将保存DB和sort\_set的部分,也以订阅者身份去监听Client的消息发布,然后通过监听的服务负责保存数据。 :-: ![](https://img.kancloud.cn/b5/a2/b5a27c32bafe574cf43ad4c81c4a19a1_693x418.png)