1.redis常用的数据类型及应用
Redis 的特性:
更丰富的数据类型
进程内与跨进程;单机与分布式
功能丰富:持久化机制、过期策略
支持多种编程语言
高可用,集群
Redis 一共有几种数据类型?(注意是数据类型不是数据结构)
String、Hash、Set、List、Zset、Hyperloglog、Geo、Streams
Redis 基本数据类型
String 字符串 (可以用来存储字符串、整数、浮点数 )
操作命令
设置多个值(批量操作,原子性)
mset qingshan 2673 jack 666
设置值,如果 key 存在,则不成功
setnx qingshan
基于此可实现分布式锁。用 del key 释放锁。 但如果释放锁的操作失败了,导致其他节点永远获取不到锁,怎么办? 加过期时间。单独用 expire 加过期,也失败了,无法保证原子性,怎么办?多参数
set key value [expiration EX seconds|PX milliseconds][NX|XX]
使用参数的方式
set lock1 1 EX 10 NX
(整数)值递增
incr qingshan incrby qingshan 100
(整数)值递减
decr qingshan decrby qingshan 100
浮点数增量
set f 2.6 incrbyfloat f 7.3
获取多个值
mget qingshan jack
获取值长度
strlen qingshan
字符串追加内容
append qingshan good
获取指定范围的字符
getrange qingshan 0 8
存储(实现)原理
数据模型
set hello word 为例,因为 Redis 是 KV 的数据库,它是通过 hashtable 实现的(我 们把这个叫做外层的哈希)。所以每个键值对都会有一个 dictEntry(源码位置:dict.h), 里面指向了 key 和 value 的指针。next 指向下一个 dictEntry
typedef struct dictEntry {void *key; /* key 关键字定义 */union {void *val; uint64_t u64; /* value 定义 */int64_t s64; double d;} v;struct dictEntry *next; /* 指向下一个键值对节点 */ } dictEntry;
key 是字符串,但是 Redis 没有直接使用 C 的字符数组,而是存储在自定义的 SDS 中。 value 既不是直接作为字符串存储,也不是直接存储在 SDS 中,而是存储在 redisObject 中。实际上五种常用的数据类型的任何一种,都是通过 redisObject 来存储 的。
redisObject
redisObject 定义在 src/server.h 文件中
typedef struct redisObject {unsigned type:4; /* 对象的类型,包括:OBJ_STRING、OBJ_LIST、OBJ_HASH、OBJ_SET、OBJ_ZSET */unsigned encoding:4; /* 具体的数据结构 */unsigned lru:LRU_BITS; /* 24 位,对象最后一次被命令程序访问的时间,与内存回收有关 */int refcount; /* 引用计数。当 refcount 为 0 的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了*/void *ptr; /* 指向对象实际的数据结构 */ } robj;
应用场景
缓存
String 类型
热点数据缓存(例如报表,明星出轨),对象缓存,全页缓存。 可以提升热点数据的访问速度。
数据共享分布式
String 类型,因为 Redis 是分布式的独立服务,可以在多个应用之间共享 ,
例如:分布式 Session ,只需要引入spring-session-data-redis的依赖即可使用
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId> </dependency>
分布式锁
String 类型 setnx 方法,只有不存在时才能添加成功,返回 true。
http://redisdoc.com/string/set.html 建议用参数的形式
SET key value [EX seconds] [PX milliseconds] [NX|XX]
- EX seconds : 将键的过期时间设置为 seconds 秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value 。
- PX milliseconds : 将键的过期时间设置为 milliseconds 毫秒。 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value 。
- NX : 只在键不存在时, 才对键进行设置操作。 执行 SET key value NX 的效果等同于执行 SETNX key value 。
- XX : 只在键已经存在时, 才对键进行设置操作。
//基于Jedis实现分布式锁,使用带参数的那种 public Boolean getLock(Object lockObject){jedisUtil = getJedisConnetion();//只用当设置成功后,才可以获取锁boolean flag = jedisUtil.setNX(lockObj, 1);if(flag){expire(locakObj,10);}return flag; } public void releaseLock(Object lockObject){del(lockObj); }
全局 ID
INT 类型,INCRBY,利用原子性
incrby userid 1000
计数器
int 类型,INCR 方法
incr onclick
例如:文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到 数据库。
限流
INT 类型,INCR 方法
以访问者的 IP 和其他信息作为 key,访问一次增加一次计数,超过次数则返回 false。
位统计
String 类型的 BITCOUNT(1.6.6 的 bitmap 数据结构介绍)。 字符是以 8 位二进制存储的。
set k1 a setbit k1 6 1 setbit k1 7 0 get k1
a 对应的 ASCII 码是 97,转换为二进制数据是 01100001
b 对应的 ASCII 码是 98,转换为二进制数据是 01100010
因为 bit 非常节省空间(1 MB=8388608 bit)大约830万,可以用来做大数据量的统计。
例如:在线用户统计,留存用户统计
setbit onlineusers 0 1 setbit onlineusers 1 1 setbit onlineusers 2 0
支持按位与、按位或等等操作。
BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。 BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。 BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。 BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
计算出 7 天都在线的用户
BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users"
如果一个对象的 value 有多个值的时候,怎么存储? 例如用一个 key 存储一张表的数据。
序列化?例如 JSON/Protobuf/XML,会增加序列化和反序列化的开销,并且不能 单独获取、修改一个值。可以通过 key 分层的方式来实现,例如:
mset student:1:sno GP16666 student:1:sname 沐风 student:1:company 腾讯
获取值的时候一次获取多个值:
mget student:1:sno student:1:sname student:1:company
缺点:key 太长,占用的空间太多。有没有更好的方式?
Hash 哈希(包含键值对的无序散列表。value 只能是字符串,不能嵌套其他类型 )
同样是存储字符串,Hash 与 String 的主要区别?
- 把所有相关的值聚集到一个 key 中,节省内存空间
- 只使用一个 key,减少 key 冲突
- 当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU 的消耗
Hash 不适合的场景:
- Field 不能单独设置过期时间
- 没有 bit 操作
- 需要考虑数据量分布的问题(value 值非常大的时候,无法分布到多个节点)
操作命令
hset h1 f 6
hset h1 e 5
hmset h1 a 1 b 2 c 3 d 4
hget h1 a
hmget h1 a b c d
hkeys h1
hvals h1
hgetall h1
key 操作
hget exists h1
hdel h1
hlen h1
存储(实现)原理
Redis 的 Hash 本身也是一个 KV 的结构,类似于 Java 中的 HashMap。 外层的哈希(Redis KV 的实现)只用到了 hashtable。当存储 hash 数据类型时, 我们把它叫做内层的哈希。内层的哈希底层可以使用两种数据结构实现: ziplist:OBJ_ENCODING_ZIPLIST(压缩列表) hashtable:OBJ_ENCODING_HT(哈希表)
ziplist 压缩列表
ziplist 是一个经过特殊编码的双向链表,它不存储指向上一个链表节点和指向下一 个链表节点的指针,而是存储上一个节点长度和当前节点长度,通过牺牲部分读写性能, 来换取高效的内存空间利用率,是一种时间换空间的思想。只用在字段个数少,字段值 小的场景里面
问题:什么时候使用 ziplist 存储?
- 所有的键值对的健和值的字符串长度都小于等于 64byte(一个英文字母 一个字节);
- 哈希对象保存的键值对数量小于 512 个。
应用场景
String
String 可以做的事情,Hash 都可以做。
存储对象类型的数据
比如对象或者一张表的数据,比 String 节省了更多 key 的空间,也更加便于集中管 理。
购物车
key:用户 id;field:商品 id;value:商品数量。
hset userId:0001 shopId:99999 5
List 列表
存储类型
存储有序的字符串(从左到右),元素可以重复。可以充当队列和栈的角色。
操作命令
lpush queue a //从左开始添加元素 lpush queue b c rpush queue d e //从右边开始添加元素 lpop queue //从左边弹出元素 rpop queue // 从右边弹出元素 blpop queue //阻塞式左边的弹出元素 发布订阅模式 如果当前队列中没有了元素,就会阻塞 brpop queue //阻塞式的右边弹出元素
lindex queue 0 lrange queue 0 -1
存储(实现)原理
在早期的版本中,数据量较小时用 ziplist 存储,达到临界值时转换为 linkedlist 进 行存储,分别对应 OBJ_ENCODING_ZIPLIST 和 OBJ_ENCODING_LINKEDLIST 。 3.2 版本之后,统一用 quicklist 来存储。quicklist 存储了一个双向链表,每个节点 都是一个 ziplist。
quicklist :
quicklist(快速列表)是 ziplist 和 linkedlist 的结合体。
quicklist中的head 和 tail 指向双向列表的表头和表尾
typedef struct quicklist {quicklistNode *head; /* 指向双向列表的表头 */quicklistNode *tail; /* 指向双向列表的表尾 */unsigned long count; /* 所有的 ziplist 中一共存了多少个元素 */unsigned long len; /* 双向链表的长度,node 的数量 */int fill : 16; /* fill factor for individual nodes */unsigned int compress : 16; /* 压缩深度,0:不压缩; */ } quicklist;
应用场景
用户消息时间线 timeline
因为 List 是有序的,可以用来做用户时间线
消息队列
List 提供了两个阻塞的弹出操作:BLPOP/BRPOP,可以设置超时时间。
BLPOP:BLPOP key1 timeout 移出并获取列表的第一个元素, 如果列表没有元素 会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOP:BRPOP key1 timeout 移出并获取列表的最后一个元素, 如果列表没有元 素会阻塞列表直到等待超时或发现可弹出元素为止。
队列:先进先出:rpush blpop,左头右尾,右边进入队列,左边出队列。 右边进左边出
栈:先进后出:rpush brpop 右边近右边出
tip: 左边是链表的头部
Set集合
存储类型
String 类型的无序集合,最大存储数量 2^32-1(40 亿左右)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QzGFJauR-1603368961704)(https://i.loli.net/2020/05/25/dlP8pwyEbjseLfS.png)]
操作命令
sadd myset a b c d e f g
smembers myset
scard myset
srandmember key
spop myset
srem myset d e f
sismember myset a
存储(实现)原理
Redis 用 intset 或 hashtable 存储 set。如果元素都是整数类型,就用 inset 存储。 如果不是整数类型,就用 hashtable(数组+链表的存来储结构)。 问题:KV 怎么存储 set 的元素?key 就是元素的值,value 为 null。 如果元素个数超过 512 个,也会用 hashtable 存储。
应用场景
抽奖
随机获取元素
spop myset
点赞、签到、打卡
这条微博的 ID 是 t1001,用户 ID 是 u3001。
用 like:t1001 来维护 t1001 这条微博的所有点赞用户。
点赞了这条微博:sadd like:t1001 u3001
取消点赞:srem like:t1001 u3001
是否点赞:sismember like:t1001 u3001
点赞的所有用户:smembers like:t1001
点赞数:scard like:t1001 比关系型数据库简单许多。
商品标签
用 tags:i5001 来维护商品所有的标签。
sadd tags:i5001 画面清晰细腻
sadd tags:i5001 真彩清晰显示屏
sadd tags:i5001 流畅至极
商品筛选
获取差集
sdiff set1 set2
获取交集(intersection )
sinter set1 set2
获取并集
sunion set1 set2
iPhone11 上市了。
sadd brand:apple iPhone11
sadd brand:ios iPhone11
sad screensize:6.0-6.24 iPhone11
sad screentype:lcd iPhone11
筛选商品,苹果的,iOS 的,屏幕在 6.0-6.24 之间的,屏幕材质是 LCD 屏幕
sinter brand:apple brand:ios screensize:6.0-6.24 screentype:lcd
用户关注、推荐模型
并集:A中有B中也有,去除重复的,
交集:A和B共同的部分才是交集
全集U={1,2,3,4,5} A={1,3,5} B={1,2,5} 。那么因为A和B中都有1,5,所以交集A∩B={1,5} 。再来看看,他们两个中含有1,2,3,5这些个元素,不管多少,反正不是你有,就是我有。那么说并集A∪B={1,2,3,5}
相互关注? (取并集)
sunion set1 set2
我(A)关注的人,他(B)也关注了?(取交集)
sinter set1 set2
可能认识的人? (取差集 SET1中有的 而set2中没有的)
sdiff set1 set2
共同关注/共同好友的人
sinterstore set5 set1 set2127.0.0.1:6379> smembers set1 //我关注了one 1) "two" 2) "one" 127.0.0.1:6379> smembers set2 //他也关注了one 1) "ghy" 2) "one" 127.0.0.1:6379> sinterstore set5 set1 set2 //我们两共同的共同关注的人 (integer) 1 127.0.0.1:6379> smembers set5 1) "one" //one
Redis实现关注关系
最近使用关系型数据库实现了用户之间的关注,于是思考换一种思路,使用Redis实现用户之间的关注关系。
综合考虑了一下Redis的几种数据结构后,觉得可以用集合实现一下。一、添加关注
添加关注分为两步:1、将对方id添加到自己的关注列表中;2、将自己的id添加到对方的粉丝列表中:
SADD 1:follow 2 SADD 2:fans 1
二、取消关注
取消关注同样分为两步:1、将对方id从自己的关注列表中移除;2、将自己的id从对方的粉丝列表中移除:
SREM 1:follow 2 SREM 2:fans 1
三、关注列表
查看我的关注列表:
SMEMBERS 1:follow
查看别人的把id换掉就可以
四、粉丝列表
查看我的粉丝列表:
SMEMBERS 2:fans
查看别人的把id换掉就可以
五、人物关系
5.1 我单向关注他
我单向关注他,要
同时满足
两个条件:1、我的关注列表中有
他(或他的粉丝列表中有
我);2、我的粉丝列表中没有
他(或他的关注列表中没有
我)。SISMEMBER 1:follow 2 #true SISMEMBER 1:fans 2 #false
5.2 他单向关注我
他单向关注我,要
同时满足
两个条件:1、我的关注列表中没有
他(或他的粉丝列表中没有
我);2、我的粉丝列表中有
他(或他的关注列表中有
我)。SISMEMBER 1:follow 2 #false SISMEMBER 1:fans 2 #true
5.3 我和某人是否互粉
我和某人是否互粉,要
同时满足
两个条件:1、我的关注列表中有他(或他的粉丝列表中有我);2、我的粉丝列表中有他(或他的关注列表中有我)。同时成立才为互粉。SISMEMBER 1:follow 2 #true SISMEMBER 1:fans 2 #true
互粉的关系是互相的,也可以反过来查。
六、我的互粉
查询和我互粉的人,实际是对我的关注和我的粉丝求交集
SINTER 1:follow 1:fans
七、共同关注
查询1和2的共同关注,实际是1的关注和2的关注求交集
SINTER 1:follow 2:follow
八、数量相关
8.1 我的关注数
SCARD 1:follow
8.2 我的粉丝数
SCARD 1:fans
九、问题
目前存在的问题是,我的关注列表 & 我的粉丝列表,无法做到按关注时间排序,终端下显示是结果按ID正序排列的。
考虑的解决方案是添加关注时同时存一份有序集合,关注时的时间戳是score。ZADD 1:follow 1457871625 2 ZADD 2:fans 1457871625 1
那么我的关注列表是:
ZREVRANGE 1:follow 0 -1
同时,ZREVRANGE查询时的索引可以作为分页游标,基本解决目前的问题。
粉丝列表同理。
ZSet有序集合
存储类型
sorted set,有序的 set,每个元素有个 score。
score 相同时,按照 key 的 ASCII 码排序。
数据结构对比:
数据结构 是否允许重复元素 是否有序 有序实现方式 列表list 是 是 索引下标 集合set 否 否 无 有序集合zset 否 是 分值score 操作命令
添加元素
zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python
获取全部元素
zrange myzset 0 -1 withscores zrevrange myzset 0 -1 withscores
根据分值区间获取元素
zrangebyscore myzset 20 30
移除元素 也可以根据 score rank 删除
zrem myzset php cpp
统计元素个数
zcard myzset
分值递增
zincrby myzset 5 python
根据分值统计个数
zcount myzset 20 60
获取元素 rank
zrank myzset java
获取元素 score
zsocre myzset java
存储(实现)原理
同时满足以下条件时使用 ziplist 编码:
元素数量小于 128 个
所有 member 的长度都小于 64 字节
在 ziplist 的内部,按照 score 排序递增来存储。插入的时候要移动之后的数据。
超过阈值之后,使用 skiplist+dict 存储。
什么是 skiplist?
我们先来看一下有序链表: 在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较, 直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。 也就是说,时间复杂度为 O(n)。同样,当我们要插入新数据的时候,也要经历同样的查 找过程,从而确定插入位置。
而二分查找法只适用于有序数组,不适用于链表。
假如我们每相邻两个节点增加一个指针(或者理解为有三个元素进入了第二层), 让指针指向下下个节点。
这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半 (上图中是 7, 19, 26)。在插入一个数据的时候,决定要放到那一层,取决于一个算法 (在 redis 中 t_zset.c 有一个 zslRandomLevel 这个方法)。
现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数 据大的节点时,再回到原来的链表中的下一层进行查找
在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行 比较了。需要比较的节点数大概只有原来的一半。这就是跳跃表。
为什么不用 AVL 树或者红黑树?因为 skiplist 更加简洁
应用场景
排行榜
id 为 6001 的新闻点击数加 1:zincrby hotNews:20190926 1 n6001
获取今天点击最多的 15 条:zrevrange hotNews:20190926 0 15 withscores
BitMaps
Bitmaps 是在字符串类型上面定义的位操作。一个字节由 8 个二进制位组成。
应用场景: 用户访问统计 在线用户统计
Hyperloglogs
Streams
支持多播1->N的可持久化的消息队列,用于实现发布订阅功能,借 鉴了 kafka 的设计。
**这样所有新增加的指针连成了一个新的链表**,但它包含的节点个数只有原来的一半 (上图中是 7, 19, 26)。在插入一个数据的时候,决定要放到那一层,取决于一个算法 (在 redis 中 t_zset.c 有一个 zslRandomLevel 这个方法)。 现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数 据大的节点时,再回到原来的链表中的下一层进行查找 在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行 比较了。需要比较的节点数大概只有原来的一半。这就是跳跃表。 为什么不用 AVL 树或者红黑树?因为 skiplist 更加简洁
应用场景
排行榜
id 为 6001 的新闻点击数加 1:zincrby hotNews:20190926 1 n6001
获取今天点击最多的 15 条:zrevrange hotNews:20190926 0 15 withscores
BitMaps
Bitmaps 是在字符串类型上面定义的位操作。一个字节由 8 个二进制位组成。
应用场景: 用户访问统计 在线用户统计
Hyperloglogs
Streams
支持多播1->N的可持久化的消息队列,用于实现发布订阅功能,借 鉴了 kafka 的设计。
1.redis常用的数据类型及应用相关推荐
- 一文带你详解Redis常用的数据类型以及面试常碰到的数据持久化机制原理
前言 关于Redis的知识点总结了一个思维导图分享给大家: 简介 Redis是一种面向"key-value"类型数据的分布式NoSQL数据库系统,支持五种数据类型格式:** Str ...
- Redis常用五大数据类型
1.String(字符串) string类型是二进制安全的.意思是redis的string可以包含任何数据.比如jpg图片或者序列化的对象 . string类型是Redis最基本的数据类型,一个red ...
- Redis 学习笔记-NoSQL数据库 常用五大数据类型 Redis配置文件介绍 Redis的发布和订阅 Redis_事务_锁机制_秒杀 Redis应用问题解决 分布式锁
1.NoSQL数据库 1.1 NoSQL数据库概述 NoSQL(NosQL = Not Only sQL ),意即"不仅仅是sQL",泛指非关系型的数据库.NoSQL不依赖业务逻辑 ...
- redis常用数据类型的场景,你真的用对了么?
关注微信公众号"虾米聊吧",每天更新一篇技术文章,文章内容涵盖架构师成长必经之路应掌握的技术,一起学习,一起交流. redis常用数据类型的场景,你真的用对了么? redis常用数 ...
- Redis面试常问-- Redis常用数据类型
Redis常用数据类型 http://www.redis.cn/
- 解析Redis操作五大数据类型常用命令
摘要:分享经常用到一些命令和使用场景总结,以及对Redis中五大数据类型如何使用cmd命令行的形式进行操作的方法. 本文分享自华为云社区<Redis操作五大数据类型常用命令解析>,作者:灰 ...
- Redis常用数据类型和事物以及并发
Redis数据类型 基本类型(String int): 如 set key value .get key 等 所有命令都是按照 key value keys * 可以将全部数据列出,其中后面的 &qu ...
- 【用户画像】Redis的常用五大数据类型和配置文件介绍
文章目录 一 常用五大数据类型简介 1 Redis键(key) 2 Redis字符串(String) 3 Redis列表(List) 4 Redis集合(Set) 5 Redis哈希(Hash) 6 ...
- Redis常用数据类型及使用场景
String(字符串) 常用命令 SET key value GET key GETRANGE key start end #返回[start, end]区间的字符串,key不存在时返回空字符串 MG ...
最新文章
- python进度条 pyqt_Python高级进阶#015 pyqt5进度条QProgressBar结合使用qbasictimer
- 瞄准千亿个护市场,纸业龙头们下半场战役已经打响
- 阿里云安全组——添加安全组规则(开放端口)
- 如何在Firefox 3中重新启用about:config警告消息
- 广度优先遍历_LeetCode | 广度优先遍历
- 动态规划经典题目_动态规划经典题目:鸡蛋掉落(附视频讲解)
- 小牛电动京东众筹活动中的违约行为记录
- Ubuntu下pycharm无法输入中文
- mysql省市区递归查询_mysql 递归查询
- Vue实现仿豆瓣电影
- qq for android 4.6能视频吗,安卓qq4.6使用评测
- Docker部署rabbitmq遇到的两个问题
- Unity Text字体花屏
- Facebook成为美国四大电视台争夺观众阵地
- 根椐图片,请猜一四字成语!有一点难度喔:)
- 六面蚂蚁金服,唬住了面试官要了 30K;其实 Java 面试也没那么难
- 从零开始之驱动发开、linux驱动(二十九、mmap原理)
- SQLite 使用问题记录(一)
- python爬取智联招聘_【原创源码】python 爬取智联招聘
- 美妆界现在是“李佳琦们”的内容营销时代
热门文章
- Zabbix4.0安装文档
- 性能工具gperftools使用说明
- 从防御者视角来看APT攻击
- 【宫水三叶的刷题日记】961. 在长度 2N 的数组中找出重复 N 次的元素
- tigase服务器推送消息,tigase,消息中心,概要设计(25页)-原创力文档
- h5调用本地摄像头和麦克风一
- 地表最强系列之Sqoop安装以及使用
- android图片资源加密解密,Android下资源图片的加密和解密
- libsodium linux,Linux安装libsodium失败解决办法~
- 如何在CentOS 6.7上配置和安装ZeroMQ(libsodium)?