前言
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件; 下面就了解的应用场景及其原理做个总结:
String类型
介绍
存储字符串类型的key-value
常用命令及应用场景
常见命令 | 命令介绍 | 应用场景 | 例 |
---|
set/get/setex | 存储字符串类型的key-value | 验证码、幂等性(重复提交判断)、token令牌、 序列化json对象存储 | set user:captcha:zhangsan 123456 ex 30/setex user:captcha:zhangsan 30 123456 (张三收到验证码为123456,30s后失效),set user:zhangsan 用户信息json串(用户张三的个人信息) | mset/mget | 批量设置key-value | 同set/get | mset user:name zhangsan user:age 20 (用户姓名为张三,年龄20) | incr | 对key对应的值进行加1操作,并返回新值 | 统计、计数器、发号器 | incr article:123:reading(对id为123的文章阅读量增加1) | incrby | 对key对应的值进行加increment操作,并返回新值 | 同incr | incr article:123:reading 10(对id为123的文章阅读量增加10) | setnx | key如果存在则返回0,不存在则返回1 | 分布式锁 | setnx addr 123(设置地址为123) | getset | 设置key的值,并返回key的旧值 | 状态变更(比如用户下单,可以修改下单状态) | getset addr 567(注意:返回为旧值123) |
原理
Redis的字符串是动态字符串,是可以修改的字符串,它的内部表示就是一个字符数组,内部结构的实现类似于Java的ArrayList,它的内部结构是一个带长度信息的字节数组
注意
- 值的长度不能超过512MB
- key的命名规范,不要过长,冒号分割, 业务名:表名:ID
List
介绍
常用命令及应用场景
常见命令 | 命令介绍 | 应用场景 | 例 |
---|
lpush/rpush | 将一个或多个值插入到列表头部(左边)/(右边) | 最新评论列表/非实时榜单(定时计算榜单,如手机日销榜单) | lpush phone:rank:daily iphone13 xiaomi12 huaweiP50 (手机日销榜 第一华为,第二小米12,第三苹果) | lpop/rpop | 移除并获取列表最左边/右边一个元素 | - | rpop phone:rank:daily (移除key phone:rank:daily最右边一个元素) | llen | 获取列表长度 | - | llen phone:rank:daily(获取key phone:rank:daily的列表长度) | lindex | 通过索引获取列表中元素 | - | lindex phone:rank:daily 0 (获取第一个元素) | lrange | 获取key对应的list指定下标范围的元素(-1表示所有元素) | - | lrange phone:rank:daily 0 -1 (获取列表所有元素) | brpop | 移除并获取列表最右边一个元素(如果没有元素则阻塞) | 简单队列 | brpop phone:rank:daily 30(监听key phone:rank:daily最右边一个元素,监听时间30S) | lrem | 移除元素指定个数 | - | lrem phone:rank:daily 1 iphone13(phone:rank:daily队列移除一个iphone13元素) |
原理
- 双向链表,插入删除时间复杂度O(1)快,查找为O(n)慢
注意
- 通常添加一个元素到列表的头部(左边)或者尾部(右边)。
- 存储的都是String字符串类型。
- 支持分页操作,高并发项目中,第一页数据都是来源list,第二页和更多信息则是通过数据库加载。
- 一个列表最多可以包含232-1个元素(最好不要超过1K)。
Hash
介绍
- 一个String类型的filed和value的映射表,hash特别适合用于存储对象。
常用命令及应用场景
常见命令 | 命令介绍 | 应用场景 | 例 |
---|
hset/hget/hgetall | 设置/得到 key指定的哈希集中指定字段值 | 用户个人信息、商品详情 | hset product:detail:1 title iphone13(设置id为1的商品详情 其中标题为iPhone13) | hmset/hmget | 批量设置/得到 key指定的哈希集中指定字段值 | 用户个人信息、商品详情 | hset product:detail:1 title iphone13 price 6999 stock 10(设置id为1的商品详情 其中标题为iPhone13,价格为6999,库存为10件) | hincrby | 增加key指定的hash集中指定字段数值 | 购物车增加或减少 | hincrby product:detail:1 stock 1(id为1的商品增加一件库存) | hdel | 从key指定的hash集中移除指定域 | - | hdel product:detail:1 stock(移除id为1的商品的库存信息) | hexists | 返回hash里边field是否存在(存在是1,不存在未0) | - | hexists product:detail:1 stock (id为1的商品库存信息是否存在) |
原理
如何解决hash冲突
- Redis 采用了渐进式 rehash 策略.这也是 hash 中最重要的部分。
- redis在扩容的时候执行 rehash 策略会保留新旧两个 hashtable 结构,查询时也会同时查询两个 hashtable.Redis会将旧 hashtable 中的内容一点一点的迁移到新的 hashtable 中,当迁移完成时,就会用新的 hashtable 取代之前的.当 hashtable 移除了最后一个元素之后,这个数据结构将会被删除。
- 正常情况下,当 hashtable 中元素的个数等于数组的长度时,就会开始扩容,扩容的新数组是原数组大小的 2 倍.如果 Redis 正在做 bgsave(持久化) 时,可能不会去扩容,因为要减少内存页的过多分离(Copy On Write).但是如果 hashtable 已经非常满了,元素的个数达到了数组长度的 5 倍时,Redis 会强制扩容。
- 当hashtable 中元素逐渐变少时,Redis 会进行缩容来减少空间占用,并且缩容不会受 bgsave 的影响,缩容条件是元素个数少于数组长度的 10%。
注意
- 每个hash可以存储232 -1键值对(40多亿)
Set
介绍
将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略
常用命令及应用场景
常见命令 | 命令介绍 | 应用场景 | 例 |
---|
sadd | 添加一个或多个元素到集合key中,如果已经在集合key中则忽略 | 去重、社交应用关注、粉丝、用户标签 | sadd user:1:tags:hangzhou(id为1的用户来自杭州) | scard | 返回集合存储key的基数 | 粉丝数量、统计网站PV、UV、IP数 | scard user:1:fans 用户id为1的粉丝数量为多少 | sdiff | 返回两个集合的差集 | 商品推荐 | sdiff user:1:tags user:2:tags(用户id为1的与用户id为2的标签差集) | sinter | 返回指定所有集合的成员交集 | 共同好友、共同关注 | sinter user:1:tags user:2:tags(用户id为1的与用户id为2的标签交集) | sismember | 判断是否是集合中成员 | - | sismember user:1:tags:hangzhou (user:1:tags中是否存在hangzhou标签) | srem | 移除key集合指定元素 | - | srem user:1:tags:hangzhou(移除user:1:tags中标签hangzhou) | sunion | 返回多个集合的并集 | - | sunion user:1:tags user:2:tags(用户id为1的与用户id为2的标签并集) |
原理
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
注意
SortedSet
介绍
- 用于将一个或多个元素及其分数值加入到有序集当中。
- 如果某个成员已存在,那么更新这个分值。
常用命令及应用场景
常见命令 | 命令介绍 | 应用场景 | 例 |
---|
zadd | 向有序集合添加一个或多个成员 | 实时排行榜(商品热销榜、积分榜) | zadd shop:rank 90 iphone 80 huawei(商品排行榜iPhone第一,华为第二) | zcard | 获取有序集合的成员数 | 榜单数量 | zcard shop:rank(获取集合shop:rank数量) | zcount | 计算有序集合中指定区间分数的成员数 | 统计数量 | zcount shop:rank 80 90(返回销量为80-90的品牌数) | zincrby | 有序集合中对指定成员的分数加上增量 | 实时增加销量 | zincrby shop:rank 10 huawei(华为手机销量增加10) | zrange/zrevrange | 通过索引区间返回有序集合指定区间内的成员,分数值递增(从小到大)/(从大到小) | 榜单排名 | zrange shop:rank 0 -1(返回商品榜单) | zrevrank/zrank | 返回集合中成员排第几(从大到小)/(从小到大) | 榜单具体排名情况 | zrevrank shop:rank iphone | zrem | 删除集合中一个或多个元素 | - | zrem shop:rank iphone huawei(移除商品排行榜中苹果和华为) | zscore | 返回集合中成员分数值 | 查看具体某个商品积分 | zscore shop:rank iphone(查看iPhone手机销量) |
原理
- 底层使用Ziplist压缩列表和"跳跃表"两种存储结构
注意
如果重复添加相同的数据,score的值将被反复覆盖,保留最后一次修改的结果。
补充
跳跃表简单介绍
举例: 比如找78 正常 1-> 4->7->13->16->25->57->78 8次 跳跃表如图 1->7->16->57->79(发现比78小,又没找到;往下一层走)->78 6次 如果数据量大速度提升则更明显
压缩表简单介绍
举例: 我们知道,数组要求每个元素的大小相同,如果我们要存储不同长度的字符串,那我们就需要用最大长度的字符串大小作为元素的大小(假设是20个字节)。存储小于 20 个字节长度的字符串的时候,便会浪费部分存储空间。
数组的优势占用一片连续的空间可以很好的利用CPU缓存访问数据。如果我们想要保留这种优势,又想节省存储空间我们可以对数组进行压缩。
但是这样有一个问题,我们在遍历它的时候由于不知道每个元素的大小是多少,因此也就无法计算出下一个节点的具体位置。这个时候我们可以给每个节点增加一个lenght的属性。
如此。我们在遍历节点的之后就知道每个节点的长度(占用内存的大小),就可以很容易计算出下一个节点再内存中的位置。这种结构就像一个简单的压缩列表了。
|