Redis常用数据结构有string,hash,list,set,zset,本文将就五种数据结构常用操作进行说明,并就典型应用场景进行举例。
1、Redis数据类型-String
String类型的数据存储是最简单的key-value存储,存储元素类型包含字符串(String)、数值(Number)、二进制(bit)三种类型。
-
1.1、字符串(String)
-
针对字符串常见操作:
SET key value; 存入字符串键
SETNX key value; 存入一个不存在的字符串,若存在key则存储不成功
GET key;获取一个字符串键
MSET key value [key value...];批量存储字符串键
MGET key [key...];批量获取字符串键
- 应用场景1:分布式全局锁
分布式系统对共享资源进行访问,需要互斥来防止彼此干扰来保证一致性时,要对共享资源添加分布式锁。方法之一是借助Redis setnx命令,首先执行setNX(lockObj, 1),lockObj不存在时set成功,紧接着设置过期时间expire(lockObj, 10),事务处理完毕del(lockObj)进行解锁。另外在Redis的分布式环境中,Redis提供了RedLock 的算法来实现一个分布式锁。
//获取锁
boolean getLock(lockObj) {
redisUtils = getRedisConnection();
boolean flag = redisUtils.setNX(lockObj, 1);
if(flag) {
expire(lockObj, 10);
}
return flag;
}
//释放锁
releaseLock(lockObj) {
del(lockObj);
}
- 1.2、数值(Number)
- 针对数字常见操作:
INCRBY key increment;对数字key进行{increment}的增加
DECRBY key decrement;对数字key进行{decrement}的减少
INCR key;对数字key自增1
DECR key;对数字key自减1
- 应用场景1:Redis实现计数器,防止刷单
计数器是Redis的原子性自增操作可实现的最直观的模式,每当某个操作发生时向Redis发送一个INCR命令。比如在一个web应用程序中,如果想知道用户在一年中每天的点击量,那么只要将用户ID以及相关的日期信息作为键,并在每次用户点击页面时,执行一次自增操作即可(INCR wangpf::20200408)。防止刷单需要在接口请求上做一下并发限制的处理,或者做一个防止刷单的安全拦截,比如显示接口请求每秒最多200次。
String redisKey = "wx_health_temp";
// 记录接口请求次数
int count = incr(redisKey);
if (count == 1) {
//设置1s后失效
expire($redisKey,1);
}
//1s请求大于200次则进行拦截等待
if (count > 200) {
return false;
}
- 应用场景2:分布式全局ID生成
依赖于Redis的单进程单线程模型,可以生成全局唯一的ID,用Redis的原子操作INCR和INCRBY来实现。
- 1.3、二进制(bit)
- 针对bits常见操作
GETBIT key offset;获取指定偏移量上位(bit)值
SETBIT key offset value;设置或替换指定偏移量上的位(bit)值
BITCOUNT key [start] [end];统计从start到end被设置为1的bit位的数量
- 应用场景1:在线用户统计
自增主键ID作为bite[]数组的下标,从而可以将用户信息和字节数据bite[]位一一对应,用户在线则将对应位置1(SETBIT userOnline 100 1),否则置0,通过BITCOUNT userOnline统计当前在线人数。
2、Redis数据类型-Hash
Redis中Hash表存储数据比较类似数据库中表的一条记录。例如存储user表中一条数据:
HMSET user 101::teacherNo 10086 101::name wangpf 101::age 18 101::email wangpf2011@163.com
- 针对HASH操作:
HSET key field value;存储一个散列值
HSETNX key field value;存储一个不存在的散列值
HMSET key field value [field value...];在一个key中存储多个field
HGET key field;获取key field的散列键值
HMGET key field [field...];批量获取key中多个field的值
HDEL key field [field...];删除散列键key中field的值
- 针对数字操作:
HINCRBY key field increment;对key散列中field进行数字操作{increment}
Hash键意义及不使用场景
Hash键意义:Hash键可以将信息凝聚在一起,便于管理;避免键名冲突、误操作;减少内存/IO/CPU消耗。
不适合使用Hash键的情况:过期键功能的使用,过期功能只能使用在key上;二进制操作命令,如SETBIT、GETBIT;需要考虑数据量分布的问题。
对于数据量分布问题,这里普及下Redis Cluster预分配Hash槽(Hash slot)相关知识:
Redis Cluster中有一个16384长度的slot槽,编号分别为0、1、2、3……16382、16383。正常工作的时候,Redis Cluster中的每个Master节点都会负责一部分的slot槽,当有某个key被映射到某个Master负责的slot槽,那么这个Master负责为这个key提供服务,至于哪个Master节点负责哪个槽,这是可以由用户指定的,也可以在初始化的时候自动生成(redis-trib.rb脚本)。在Redis Cluster中,只有Master才拥有slot槽的所有权,如果是某个Master的slave,这个slave只负责slot槽的使用,但是没有所有权。
存储在Redis Cluster中的所有的键都会被映射到这些slot槽中。数据库中的每个键都属于这16384个哈希槽的其中一个,集群使用公式CRC16(key) % 16384来计算键key属于哪个slot槽。
- 应用场景1:服务端购物车
将购物车信息存放在Redis中,用户唯一userID作为key,将不同商品Hash形式存储,HSET shoppingCar:{userID} {goodsID} {count}
3、Redis数据类型-List
List数据结构是双向链表结构,可以在链表左右两边分别操作。也可以把List看成一种队列,所以在有些场景可以用redis用作消息队列,常见应用案例有时间轴数据、评论列表、消息传递等。
- List常见操作:
LPUSH key value [value...];往key的列表键中左边放入一个元素,key不存在则新建
RPUSH key value [value...];往key的列表键中右边放入一个元素,key不存在则新建
LPOP key;从key的列表键最左端弹出一个元素,并且从List中删除
RPOP key;从key的列表键最右端弹出一个元素,并且从List中删除
LRANGE key start stop;获取列表键从start下标到stop下标的元素
例如LRANGE key 0 -1;获取key列表中所有元素
BLPOP key [key...] timeout;阻塞的从key的列表键最左端弹出一个元素,若列表键中不存在元素,阻塞等待{timeout}秒,若{timeout}=0一直阻塞
BRPOP key [key...] timeout;阻塞的从key的列表键最右端弹出一个元素,若列表键中不存在元素,阻塞等待{timeout}秒,若{timeout}=0一直阻塞
- 应用场景1:阻塞消息队列
借助BLPOP和BRPOP阻塞特性,实现阻塞消息队列。
- 应用场景2:Pub/Sub模型
Redis通过publish和subscribe命令实现订阅和发布的功能。订阅者可以通过subscribe向redis server订阅自己感兴趣的消息类型。redis将信息类型称为通道(channel)。当发布者通过publish命令向redis server发送特定类型的信息时,订阅该消息类型的全部订阅者都会收到此消息。
Pubish username wangpf // 发布消息
SUBSCRIBE username // 订阅
UNSUBSCRIBE username // 取消订阅
4、Redis数据类型-Set
利用Redis提供的Set数据结构,可以存储一些唯一无序数据。比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。
- 4.1、Set常见操作
SADD key member [member...];往集合键key中存放元素,若key不存在则新建
SREM key member [member...];从集合键key中删除元素
SMEMBERS key;获取集合键key中所有元素
SCARD key;获取集合键key的元素个数
SISMEMBER key member;判断{member}元素是否存在于集合键key中
SRANDMEMBER key [count];从集合键key中选出{count}元素,不从集合键中删除
SPOP key [count];从集合键key中选出{count}元素,并且从集合键key中删除
- 应用场景1:点赞/签到/打卡
用户user0001点赞1000帖子 SADD like::1000 user0001
用户取消点赞 SREM like::1000 user0001
检查user0001用户是否点赞过 SISMEMBER like::1000 user0001
获取1000帖子点赞的用户SMEMBERS like::1000
获取1000帖子点赞用户数 SCARD like::1000
- 4.2、Set集合的运算操作
交集运算:
SINTER key [key...]
SINTERSTORE destination key [key...]
并集运算:
SUNION key [key...]
SUNIONSTORE destination key [key...]
差集运算:
SDIFF key [key...]
SDIFFSTORE destination key [key...]
- 应用场景1:商品筛选
每个商品入库的时候即会建立它的今天标签列表,如品牌、屏幕尺寸、处理器、内存大小。
SADD brand::huawei P30
SADD screenSize::5.6 P30
SADD os::android P30
SADD screenType::FHD P30
SINTER brand::huawei screenSize::5.6 os::android screentype::FHD ---> P30
5、Redis数据类型-Zset(Sorted Sets)
zset是set的一个升级版本,在set的基础上增加了一个顺序属性(权重),这一属性在添加修改元素的时候可以指定,每次指定后zset会自动重新按新的值调整顺序。
- Zset常见操作:
ZADD key score element [...];往有序集合键key中存放元素,若key不存在则新建
ZREM key element [element...];从有序集合key中删除元素
ZSCORE key element;获取有序集合key中{element}元素的score值
ZINCRBY key increment element;给有序集合key中{element}元素进行score值操作,若key不存在则新建,然后进行score值操作
ZCARD key;获取有序集合key中元素个数
ZRANGE key start stop [WITHSCORES];正序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES];倒序获取有序集合key从start下标到stop下标的元素
- 集合运算操作:
ZUNIONSTORE destkey numkeys key [key...];并集计算
ZINTERSTORE deskey numkeys key [key..];交集计算
- 应用场景1:排行榜
ZINCREBY hotNews::20200408 1 {newsId};
ZREVRANGE hotNews::20200408 0 10 WITHSCORES