这是我参与「第五届青训营 」笔记创作活动的第14天。
引言
今天的课程中老师首先讲述了什么是Redis、为什么需要Redis、Redis的基本工作原理和其如何保证持久化,之后老师通过6个Redis的应用案例,来讲述如何使用Redis中的常见数据结构和命令,并进而分析对应结构的底层原理。
一、本堂课重点内容
本堂课的知识点
- Redis是什么
- Redis应用案例
- Redis使用注意事项
二、详细知识点介绍
Redis是什么?
Redis 全称 Remote Dictionary Server(即远程字典服务),它是一个基于内存实现的键值型非关系(NoSQL)数据库,由意大利人 Salvatore Sanfilippo 使用 C 语言编写。
Redis体系架构主要分为两个部分:
- Redis服务端
- Redis客户端
Redis 服务端的默认端口号为 6379
Redis的作者在自己的一篇博文中解释了为什么选用 6379 作为默认端口,因为 6379 在手机按键上 MERZ 对应的号码,而 MERZ 取自意大利女歌手 Alessia Merz 的名字
redis 是单线程 , 基于内存操作
cpu不是Redis的性能瓶颈 ,redis的瓶颈是根据机器的内存和网络带宽
Redis如何实现持久化?
数据的持久化存储是 Redis 的重要特性之一,它能够将内存中的数据保存到本地磁盘中,实现对数据的持久存储。这样即使在服务器发生故障之后,也能通过本地磁盘对数据进行恢复。
实现持久化的技术:AOF持久化、RDB持久化和混合持久化
RDB 即快照模式,它是 Redis 默认的数据持久化方式,它会将数据库的快照保存在 dump.rdb 这个二进制文件中。
所谓“快照”就是将内存数据以二进制文件的形式保存起来。
AOF 被称为追加模式,或日志模式,是 Redis 提供的另一种持久化策略,它能够存储 Redis 服务器已经执行过的的命令,并且只记录对内存有过修改的命令(类似MySQL中的binlog,记录对数据库执行的写入型操作命令),这种数据记录方法,被叫做“增量复制”,其默认存储文件为appendonly.aof。
五大基本数据类型(针对value)
1️⃣String类型
在redis中实现字符串类型,是自定义了一个属于特殊结构 SDS(Simple Dynamic String)即简单动态字符串),这是一个可以修改的内部结构。
struct sdshdr{
//记录buf数组中已使用字符的数量,等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用的字符数量
int free;
//字符数组,用于保存字符串
char buf[];
2️⃣Hash类型
底层有两种实现方式
第一种:当存储的数据量较少的时,hash 采用 ziplist 作为底层存储结构
当hash中存储的元素较少时,Redis 会使用一块连续的内存来存储这些元素,这个连续的结构被称为 ziplist(压缩列表) ,它将所有的元素紧挨着一起存储。
此时要求符合以下两个条件:
- 哈希对象保存的所有键值对(键和值)的字符串长度总和小于 64 个字节。
- 哈希对象保存的键值对数量要小于 512 个。
第二种:当无法满足上述条件时,hash 就会采用第二种方式来存储数据,也就是 dict(字典结构),该结构类似于 Java 的 HashMap,是一个无序的字典,并采用了数组和链表相结合的方式存储数据。在 Redis 中,dict 是基于哈希表算法实现的,因此其查找性能非常高效,其时间复杂度为 O(1)。
处理哈希冲突:再散列,拉链法
rehash操作
当hash中底层实现中大量key使用hash算法后映射到同一个位置(dict中的槽位)时,也即有大量的哈希冲突时,采用拉链法,但这样会导致利用key进行查询value的效率大大降低。
在以上场景中,redis会重新利用一个hash算法,在dict中增加更多的槽位,需要把之前数据拷贝到现在的hash容器中,如果直接针对之前的hash容器,进行数据迁移,会导致明显的阻塞用户请求。
采用渐进式rehash,每次用户访问时都只会迁移少量数据,将迁移平摊到多次请求中
3️⃣List类型
底层采用双向链表和listpack结合实现
在链表中一个节点可能存储多个数据(并不是只存储一个数据),从而增大节点的数据密度
4️⃣Set类型
底层类似hash类型
5️⃣SorteredSet(zset)类型
底层数据结构zskiplist,即跳表
大key和热key
解决 : 数据压缩,冷热数据区分,key拆分
三、实践练习例子
连续签到
掘金平台 有一个用户签到功能(每天只能签到一次),会显示用户累计签到次数,连续签到次数。当用户当天未签到(断签)时,连续签到次数会清零(归0),所以每天必须在23.59.59前签到
如何实现:key为cc_uid_用户id,value用户连续签到次数,expireAt:后天的0点
消息通知
使用list类型作为消息队列
在掘金平台上,用户发布了一篇文章,发布后将文章部分信息推送给ES,这样使用搜索功能就能查询到这篇文章了。
计数
在掘金平台的用户主页会显示用户的文章所获点赞数,文章所获总浏览量,粉丝数,关注用户数,收藏数....等。
如果一个数量对应一个redis中的key,这是十分不可取的,且增加了和redis的交互次数,因此可以利用redis中hash类型进行存储
{
"got_digg_count": 10693,
"got_view_count": 2238438,
"followee_count": 176,
"follower_count": 9895,
"follow_collect_set_count": 0,
"subscribe_tag_count": 95
},
排行榜
当用户的积分变化时,排行榜要实时变化
利用redis中的zset数据结构
限流
要求接口1秒内放行的请求数最大为N,超过N则禁止访问
redis中增加key:comment_query_limit_时间戳 ,value 0
一个请求在访问接口前,调用上述key 执行incr(加1) 得到value大于N则禁止
分布式锁
并发场景下,要求一次只能有一个协程执行,执行完成后,等待中的协程才能执行
因为redis是单线程的,所以获取redis执行操作是串行的
利用redis中的setnx实现,即setnx keyxx value 只有此key不存在,才能执行成功。
四、课后个人总结
今天学习兼复习了许多redis的知识,进一步了解了五大基本数据类型的底层实现,也从6个实际使用的案例中学习到如何去使用redis,后期根据项目的实际情况,进一步通过实践去加深对redis的理解。
五、引用参考
[1] 青训营课程资料 【后端专场 学习资料七】第五届字节跳动青训营 - 掘金 (juejin.cn)
[2] 课件 第五届青训-Redis-大厂程序员是怎么用的.pptx - 飞书云文档 (feishu.cn)