redis基本数据类型和对象的实际应用场景
MySQL端口号3306,redis6379
为什么redis可以是单线程?
单线程的epoll可能会被卡在数据库查询、写日志这种耗时的操作上。
redis是工作在内存上,纯内存的不需要考虑有没有数据库、日志、磁盘操作、网络操作、外界请求啥的。
redis是内存数据库、KV数据库、数据结构数据库。它之所以快是因为它是基于内存的,有高效优化好的数据结构,单线程没有上下文切换,采用多路IO复用技术。
基本高效的数据结构如下。
(1)SDS的结构体属性有len/free/buf,这样就能快速找到字符串的长度;同时free属性避免了对字符串进行扩展等操作时的内存空间溢出问题;空间预分配策略减少了修改字符的时候带来的内存重分配次数(系统调用耗时);字符串里的字符可以是空格,保证了二进制安全;最后一个是'\0'可以兼容部分的C字符串函数。
有自己的空间预分配策略。长度小于1M,free变成一倍。长度大于1M,free变成1M。
free还实现了惰性空间释放,等待将来使用,避免字符串缩短所需的内存重分配操作。
(2)链表list结构包含head/tail/len,还包含dup/free/match三个函数。
void*实现了多态,使得链表可以用于保存不同类型值。
(3)字典底层是哈希表。哈希表包含table数组(存储哈希节点),size/sizemask/used。其中,哈希节点包含键和值。
dict的结构体属性包含type/privdata/ht(hashtable里面有上述的)/rehashidx。
采用的是链地址法处理哈希冲突,所以哈希表数组元素是一条条的链表。
补充:处理哈希冲突的方法有开放定址法(线性探测/二次探测/伪随机数探测)、再哈希法、链地址法、公共溢出区。
(4)跳跃表zskiplist包含header/tail/level/length这些属性。每一个节点是zskiplistNode又包含层(前进指针和跨度)、后退指针、分值、成员对象。其中成员对象必须唯一,分值可以重复。每个跳跃表节点的层高都是1到32之间的随机数。
(5)整数集合的结构体包含encoding/length/contents[]元素数组,这三个属性。其中encoding可以选择INTSEC_ENC_INT16/32/64。当我们要把一个32的加到前面都是16的时候,要进行内存升级,先把他们都变成32,然后再插入。升级的好处就是能够提高整数集合的灵活性,同时尽可能节约内存。整数集合不支持降级的操作,一旦对数组进行升级,就会一直保持升级的状态。
(7)压缩列表的结构体包含zlbytes压缩列表总长/zltail表尾指针偏移量(起始地址加上这个就可以找到表尾节点)/zllen压缩列表一共有多少节点。列表上的每个节点的结构体包含previous_entry_length第几个/encoding/content。压缩列表从表尾向表头节点进行遍历的操作就是使用previous_entry_length这个属性实现的。content可以是一个字符数组"hello"也可以是一个整数。
如果前一个节点的长度小于254字节,那个previous_entry_length属性需要1字节长的空间来保存这个长度值。如果大于,需要5字节。因此,本来前面254都是用1字节,突然在开始插入,就需要进行空间重分配操作,就是连锁更新。
redis的每个键值对的键是一个对象,值是一个对象。
对象类型有字符串、列表、哈希、集合、有序集合。
对于redis数据库保存的键值对来说,键总是一个字符串对象,值可以是上述任意一个。
比如set msg "hello world",键是字符串对象msg,值是字符串对象"hello world"。
rpush number 1 3 5,列表对象。
hmset profile name Tom age 26 career programmer,哈希对象。
sadd fruits apple banana cherry,集合对象。
zadd price 8.5 apple 5.0 banana,有序集合对象。
类型和编码方式的对应如下。
REDIS_STRING: REDIS_ENCODING_INT / REDIS_ENCODING_EMBSTR / REDIS_ENCODING_RAW
REDIS_LIST: REDIS_ENCODING_ZIPLIST / REDIS_ENCODING_LINKEDLIST
REDIS_HASH: REDIS_ENCODING_ZIPLIST / REDIS_ENCODING_HT
REDIS_SET: REDIS_ENCODING_INTSET / REDIS_ENCODING_HT
REDIS_ZSET: REDIS_ENCODING_ZIPLIST / REDIS_ENCODING_SKIPLIST
数据结构是因为k对应的v的数据结构可以是string(安全的,/0也无法阻断)/list(双端队列/插入有序)/hash(两个字段确定一个value)/set(共同好友 )/zset有序集合(做排行榜)。
setnx设置不存在的kv对,没有teacher键的时候才会设置成功返回1,否则设置失败返回0。
(1)string
int是字符串长度小于等于20且能转换成整数;raw是字符串长度大于44,embstr是字符串长度小于等于44。
embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构。但raw编码会调用两次内存分配函数来分别创建那两个结构,而embstr编码则会通过调用一次内存分配函数分别创建上述两个结构。
1 2 3 4 5 6 7 8 9 10 11 12 | set:设置key的值,如果key存在,覆盖旧值,成功返回ok。 get:获取key的值,如果key不存在,返回nil。 mset:和set一致,批量设置键值对,减少网络开销。 mget:和get一致,批量获取键值对,减少网络开销。 incr:key+1,不存在则先set再incr。 incrby:和incr一致,多出一个指定数字增量值。 decr:减法。 decrby:减法。 strlen :获取长度len,如果不存在返回0。 setnx:设置key值,如果key不存在则返回1,否则返回0. setex:设置key值和过期时间。 append:对key值进行末尾追加数据,返回值是字符串长度。 |
string应用:
对象存储即缓存基础数据(key val的json对)。
累加器即计数器(统计某个页面的访问数据量,利用INCR counter, counter没有设置等于0,现在累加1,直接通过返回值直到key所对应的value是多少)。
限制请求次数,使用incr+expire结合,对某些用户或者IP地址进行限流请求。
分布式锁(锁的本质也是一个资源,value为string来存储锁。setnx加锁,del key解锁)。
位运算(利用二进制安全字符串的特性,1到31号看哪一天签到了,setbit sign:10001:2202 1 1, getbit, bitcount)。
其中sign:10001:202106 1 1,10001是用户id,202106是2021年6月。第1天是1。
(2)list(不唯一,有序)
linklist是双向链表;ziplist是压缩列表。当列表对象保存的所有字符串元素长度都小于64字节,或者列表对象保存的元素数量小于512个的时候,列表对象使用ziplist编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 | lpush:从左边插入元素。 rpush:从右边插入元素。 linsert:向某个元素前/后插入元素。 lrange:获取列表中指定范围内的元素列表。 lindex:获取列表指定索引下标的元素。 llen:获取列表长度。 lpop:从列表左侧弹出元素并返回头部元素。 rpop:从列表右侧弹出元素并返回尾部元素。 lrem:从列表中找到等于value的元素进行删除。 ltrim:按照索引范围修建列表,相当于切片操作。 lset:修改指定下标的元素的值。 blpop:移除并获取列表的第一个元素,如果列表没有元素会阻塞列表等待超时或发现可弹出元素为止。 brpop:移除并获取列表的最后一个元素。 |
list应用:栈、队列、异步消息队列。
阻塞队列(多个server,一个server去redis查询一个key发现没有,要阻塞等待其他server生产出来。例如一个server输入BRPOP list 0;另一个server输入lpush list aaa)。
获取固定窗口记录(需要最近50条的战绩,插入有序)
列表是简单的字符串链表,按照插入顺序进行排序。可以实现简单的点对点消息队列。可做定时计算排行榜,实时不行。
朋友圈的点赞列表评论列表是按时间最新的。
人气榜单+热点新闻。
比如每周都要重新计算生成一份活动排行榜的列表进行展示。可以使用lpush+lrange来实现这个排行榜。
假如我们有一个抽奖活动,展示最近20个抽中奖励的人。可以使用lpush+ltrim+lrange来实现。
如果这些数据是实时更新的,就不可以使用列表对象,要变成有序集合对象实时计算榜单数据。
消息队列:通常使用rpush+lpop来做队列,rpush生产消息,lpop消费消息,然后开启多进程去跑脚本处理队列。
延迟队列:订单超时取消、游戏超时检测。通过push队列数据的时候,给消息体增加一个delay到期的时间,每次pop出来,解析数据拿到delay跟当前时间戳进行对比,判断是否到期,到期则根据对应逻辑处理,否则重新丢到队列尾部进行消费。我们只需要保证消息队列能不阻塞,能正常消费完就可以。
(3)hash(唯一,无序)
dict是字典,节点数量大于512或字符串长度大于64;
ziplist是压缩列表,节点数量小于等于512,且字符串长度小于等于64。
ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象的时候,程序会将保存了键的压缩列表节点推入到压缩列表的末尾,然后再将保存了值得压缩列表节点推入到压缩列表表尾。因此,保存了同一键值对的两个节点总是紧紧挨在一起,保存键的节点在前,保存值得节点在后。
hash应用:存储订单等对象、购物车(对象经常修改属性字段用hash,如果不经常修改用string)
hmset hash:10001 name aaa age 18 sex male
hset hash:10001 age 30修改
(4)set(唯一,无序)
intset是整数数组,元素都为整数且节点数量小于等于512;dict是字典,元素一个不为整数或者数量大于512。
如果使用hashtable字典编码,字典的每个键都是一个字符串对象,即包含另一个集合元素,而字典值全部都是NULL。
当集合对象保存的所有元素都是整数值,并且元素数量不超过512个的时候才能使用intset编码。
set应用:交并集合,关系型的一些应用,抽奖、共同关注、推荐好友
(5)zset(唯一,有序)
skiplist是跳表,数量大于128或有一个字符串长度大于64;
ziplist是压缩列表,子节点数量小于等于128,且字符串长度小于等于64。
ziplist编码的有序集合中,每个集合元素使用两个紧挨在一起的压缩列表节点进行保存。
第一个节点保存元素成员,第二个分值。
skiplist编码的有序集合对象使用的不只是跳跃表,而是跳跃表和字典结合的zset结构。这两种数据结构会通过指针来共享相同元素的成员和分值,所以同时使用两种底层数据结构不会产生任何重复成员或者分值,不会因此浪费内存。
zset应用:分布式应用很多,百度热榜(排行榜数据,zadd rank 100 aaa, zadd rank 101 bbb, zadd rank 99 bbb。zrange rank 0 -1按照分数从小到大排序;zrange rank 0 -1 with score)、延时队列(具体任务,按照时间戳进行排序,5秒后再执行任务。生产者生产任务给zset,然后消费者去zset拿任务)、分布式定时器(考虑数据安全性,考虑redis节点宕机的高可用解决方案)、时间窗口限流。
有序集合用在排行榜,一开始有个用户user1他的点赞数目是1,就是zadd weibo 1 user,然后用户每次点赞都加一,zincryby weibo 1 user,取消点赞就是减一。zincryby weibo -1 user。取前十名,zrevrange weibo 0 9。
ip也是递增的,也可以存到有序集合里面去。
实现按照时间淘汰的算法,把活跃用户用时间戳存储。
缓存击穿,一个键被瞬间大量访问,导致数据库支撑不住。
缓存穿透,一直去请求缓存和数据库都不存在的,就会一直去查数据库。
缓存雪崩,缓存同一时间大面积失效,请求全部数据库。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek “源神”启动!「GitHub 热点速览」
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器