一、定义
zset 是 set 的一个升级版本,它在 set 的基础上增加了一个顺序属性, 它和 set 一样,zset也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double类型的 score。 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 】
集合中最大的成员数为 2的(32 - 1)次方 (4294967295, 每个集合可存储40多亿个成员)。 zset 最经典的应用场景就是排行榜。
二、ZSET的基本命令
1、ZADD key score1 member1 score2 member2 score3 member3
将一个或多个member元素及其score值加入到有序集key当中。
2、ZRANGE KEY start end WITHSCORES
返回 [start,end] 这个区间的所有值
本地:0>zadd lxh 1 a 2 b 3 c 4 d 5 e
"5"
本地:0>zrange lxh 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
本地:0>zrange lxh 0 -1 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
7) "d"
8) "4"
9) "e"
10) "5"
本地:0>zrevrange lxh 0 -1
1) "e"
2) "d"
3) "c"
4) "b"
5) "a"
本地:0>zrevrange lxh 0 -1 withscores
1) "e"
2) "5"
3) "d"
4) "4"
5) "c"
6) "3"
7) "b"
8) "2"
9) "a"
10) "1"
3、zrem key member1 member2 ...
移除有序集key中的一个或多个成员,不存在的成员将被忽略。
本地:0>zrange lxh 0 -1 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
7) "d"
8) "4"
9) "e"
10) "5"
本地:0>zrem lxh a
"1"
本地:0>zrange lxh 0 -1 withscores
1) "b"
2) "2"
3) "c"
4) "3"
5) "d"
6) "4"
7) "e"
8) "5"
4、zcard key
返回有序集key的基数。
本地:0>zrange lxh 0 -1 withscores
1) "b"
2) "2"
3) "c"
4) "3"
5) "d"
6) "4"
7) "e"
8) "5"
本地:0>zcard lxh
"4"
5、zcount key min max
返回有序集key中,score值在min和max之间(默认包括score值等于min或max)的成员。
本地:0>zrange lxh 0 -1 withscores
1) "b"
2) "2"
3) "c"
4) "3"
5) "d"
6) "4"
7) "e"
8) "5"
本地:0>zcount lxh 1 5
"4"
本地:0>zcount lxh 2 3
"2"
6、zscore key member
查找指定值,返回有序集key中,成员member的score值。
本地:0>zrange lxh 0 -1 withscores
1) "b"
2) "2"
3) "c"
4) "3"
5) "d"
6) "4"
7) "e"
8) "5"
本地:0>zscore lxh 2
null
本地:0>zscore lxh b
"2"
本地:0>zscore lxh e
"5"
7、zincrby key increment member
为有序集key的成员member的score值加上增量increment。
本地:0>zrange lxh 0 -1 withscores
1) "b"
2) "2"
3) "c"
4) "3"
5) "d"
6) "4"
7) "e"
8) "5"
本地:0>zincrby lxh 10 b
"12"
本地:0>zrange lxh 0 -1 withscores
1) "c"
2) "3"
3) "d"
4) "4"
5) "e"
6) "5"
7) "b"
8) "12"
8、zrevrange key start stop [WITHSCORES]
返回有序集key中,指定区间内的成员,降序。
zrange key start stop [WITHSCORES]
返回有序集key中,指定区间内的成员,升序。
本地:0>zrange lxh 0 -1 withscores
1) "c"
2) "3"
3) "d"
4) "4"
5) "e"
6) "5"
7) "b"
8) "12"
本地:0>zrevrange lxh 0 -1 withscores
1) "b"
2) "12"
3) "e"
4) "5"
5) "d"
6) "4"
7) "c"
8) "3"
9、zrangebyscore key min max limit offset count
返回指定集合【min ,max】区间的值,并进行分页-------返回值是升序排列
zrevrangebyscore key min max limit offset count
返回指定集合【min ,max】区间的值,并进行分页-------返回值是降序排列
本地:0>zrange lxh 0 -1 withscores
1) "c"
2) "3"
3) "d"
4) "4"
5) "e"
6) "5"
7) "b"
8) "12"
本地:0>zrangebyscore lxh 0 10 limit 0 10
1) "c"
2) "d"
3) "e"
-- 降序排列的话, 就不是【min,max】 而是 【max,min】
本地:0>zrevrangebyscore lxh 0 10 limit 0 10
本地:0>zrevrange lxh 0 -1 withscores
1) "b"
2) "12"
3) "e"
4) "5"
5) "d"
6) "4"
7) "c"
8) "3"
本地:0>zrevrangebyscore lxh 10 0 limit 0 10
1) "e"
2) "d"
3) "c"
-- 升序排列的话, 就不是【max,min】 而是 【min,max】
本地:0>zrangebyscore lxh 10 0 limit 0 10
10、zrank key member
查看这个member 在key集合里面排名(升序的排名)
本地:0>zrange lxh 0 -1 withscores
1) "c"
2) "3"
3) "d"
4) "4"
5) "e"
6) "5"
7) "b"
8) "12"
-- 排序最开始值是 0开始的
本地:0>zrank lxh b
"3"
zrevrank key member
查看这个member 在key集合里面排名(降序的排名)
本地:0>zrevrange lxh 0 -1 withscores
1) "b"
2) "12"
3) "e"
4) "5"
5) "d"
6) "4"
7) "c"
8) "3"
本地:0>zrevrank lxh b
"0"
11、zremrangebyrank key start end
移除key集合里面【start,end】这个排名区间的值
本地:0>zrange lxh 0 -1 withscores
1) "c"
2) "3"
3) "d"
4) "4"
5) "e"
6) "5"
7) "b"
8) "12"
本地:0>zremrangebyrank lxh 0 1
"2"
本地:0>zrange lxh 0 -1 withscores
1) "e"
2) "5"
3) "b"
4) "12"
12、zremrangebyscore key min max
移除key集合里面【min ,max】这个区间的值
本地:0>zadd lxh 1 a 2 b 3 c 10 d 100 e 500 f
"6"
本地:0>zrange lxh 0 -1 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
7) "d"
8) "10"
9) "e"
10) "100"
11) "f"
12) "500"
本地:0>zremrangebyscore lxh 1 100
"5"
本地:0>zrange lxh 0 -1 withscores
1) "f"
2) "500"
13、zinterstore key num key1 key2 key3
取key1,key2,key3的集合的交集放到key集合里面,由于需要取3个集合的交集,则num是3。返回值是交集的数量。也就是 key 集合的数量。
取交集的时候,会将相同的member的source值合并的。(求和)
本地:0>zrange lxh 0 -1 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "f"
6) "500"
本地:0>zrange lxh1 0 -1 withscores
1) "a"
2) "1"
3) "b"
4) "2"
本地:0>ZINTERSTORE lxh2 2 lxh lxh1
"2"
本地:0>zrange lxh2 0 -1 withscores
1) "a"
2) "2"
3) "b"
4) "4"
zinterstore lxh3 3 lxh lxh1 lxh2
"2"
14、zunionstore key key1 key2 key3
取key1,key2,key3的集合的并集放到key集合里面,,由于需要取3个集合的并集,则num是3。返回值是并集的数量,也就是 key 集合的数量。
取交集的时候,会将相同的member的source值合并的。(求和)
本地:0>zrange lxh 0 -1 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "dd"
6) "25"
7) "f"
8) "500"
本地:0>zrange lxh2 0 -1 withscores
1) "a"
2) "2"
3) "b"
4) "4"
本地:0>zrange lxh1 0 -1 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "dd"
6) "25"
本地:0>zunionstore lxh8 3 lxh lxh1 lxh2
"4"
本地:0>zrange lxh8 0 -1 withscores
1) "a"
2) "4"
3) "b"
4) "8"
5) "dd"
6) "50"
7) "f"
8) "500"
三、ZSET的代码实例
需求一:完成热搜排行帮查询功能。
实现步骤:
1、当前毫秒数 / (1000*60*60) =时间块
2、获取得到每个热搜的时间块-----(时间块,热搜热度,热搜id),热搜热度 = 点赞+转发+阅读。
3、每隔1个小时,将从现在开始得到的时间块,和前23个小时时间块的24个集合取交集,将交集的结果给 到一个新集合,这个新集合就每日热搜的集合。
核心代码:
初始化数据+刷新数据
package com.redis.zset;
/*
* 本类用来实现微博热搜
*
* 1、微博的榜单分为 小时榜,日榜,周榜,月榜
* 2、我们现在需要做的是两件事情,一个是造出微博的数据
* 3、查看指定榜单
*
*
* */
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("WB")
@Slf4j
public class WBRedisController {
private static final String REDIS_WB_HOUR = "redisWB_hour";
private static final String REDIS_WB_DAY = "redisWB_day";
private static final String REDIS_WB_WEEK = "redisWB_week";
private static final String REDIS_WB_MONTH = "redisWB_month";
@Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void init() {
new Thread(() -> {
try {
initWbData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@PostMapping("refreshWBData")
public String getDayWBData() {
// 每小时统计一次每天的数据
refreshDayData();
// 每小时统计一次每周的数据
refreshWeekData();
// 每小时统计一次每月的数据
refreshMonthData();
return "数据刷新成功";
}
private void initWbData() throws InterruptedException {
// 由于没有热搜事件,通过26个字母来代替热搜事件,同时存储每个小时每个热搜(字母)的数据
// 获取当前小时的时间块
long key = System.currentTimeMillis() / (1000 * 60 * 60);
// 获取当月的时间块,组装为key值
for (int i = 0; i < 24 * 30; i++) {
String redisKey = REDIS_WB_HOUR + (key - i);
for (int j = 0; j < 26; j++) {
int source = new Random().nextInt(10);
// 添加的是 key , member,source(member这个属性对应的数值--double类型)
redisTemplate.opsForZSet().add(redisKey, String.valueOf((char)(96+i)), source);
}
}
log.info("" +
"一个月内的数据初始化完成");
// 开始定时刷新数据
while (true) {
// 每隔5秒钟开始刷新数据
// 初始化数据完成之后,每隔5秒刷新一次数据,模拟用户的访问
Thread.sleep(5000);
refreshData();
log.info("数据刷新成功");
}
}
/*
* 刷新数据----统计每小时的数据之和
* 定时统计数据---每次要统计的单位是一个小时
* */
private void refreshDayData() {
long key = System.currentTimeMillis() / (1000 * 60 * 60);
List<String> list = new ArrayList<>();
// 获取当天内的所有的集合
for (int i = 0; i < 24; i++) {
String redisKey = REDIS_WB_HOUR + (key - i);
this.redisTemplate.expire(redisKey, 40, TimeUnit.DAYS);
list.add(redisKey);
}
redisTemplate.opsForZSet().unionAndStore(null, list, REDIS_WB_DAY);
}
/*
* 刷新数据----统计每周的数据之和
*
* */
private void refreshWeekData() {
long key = System.currentTimeMillis() / (1000 * 60 * 60);
List<String> list = new ArrayList<>();
// 获取当天内的所有的集合
for (int i = 0; i < 168; i++) {
String redisKey = REDIS_WB_HOUR + (key - i);
list.add(redisKey);
}
redisTemplate.opsForZSet().unionAndStore(null, list, REDIS_WB_WEEK);
}
/*
* 刷新数据----统计每月的数据之和
*
* */
private void refreshMonthData() {
long key = System.currentTimeMillis() / (1000 * 60 * 60);
List<String> list = new ArrayList<>();
// 获取当天内的所有的集合
for (int i = 0; i < (24 * 30); i++) {
String redisKey = REDIS_WB_HOUR + (key - i);
list.add(redisKey);
}
redisTemplate.opsForZSet().unionAndStore(null, list, REDIS_WB_MONTH);
}
/*
* 定时刷新数据---模拟用户的请求
* */
private void refreshData() throws InterruptedException {
long key = System.currentTimeMillis() / (1000 * 60 * 60);
for (int i = 0; i < 26; i++) {
// 添加的是 key , member,source(member这个属性对应的数值--double类型)
redisTemplate.opsForZSet().incrementScore(REDIS_WB_HOUR + key, String.valueOf((char)(96+i)), new Random().nextInt(5));
}
}
}
获取排行榜数据信息
package com.redis.zset;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Set;
/*
* 本类用来查询热搜排行榜的展示
*
* */
@RestController
@RequestMapping("WB")
@Slf4j
public class GetRdisWB {
private static final String REDIS_WB_HOUR = "redisWB_hour";
private static final String REDIS_WB_DAY = "redisWB_day";
private static final String REDIS_WB_WEEK = "redisWB_week";
private static final String REDIS_WB_MONTH = "redisWB_month";
@Autowired
private RedisTemplate redisTemplate;
@PostMapping("getHourWBData")
public Set getHourWBData() {
long hour = System.currentTimeMillis() / (1000 * 60 * 60);
Set set = redisTemplate.opsForZSet().rangeWithScores(REDIS_WB_HOUR+hour, 0, -1);
return set;
}
@PostMapping("getDayWBData")
public Set getDayWBData() {
long hour = System.currentTimeMillis() / (1000 * 60 * 60);
Set set = redisTemplate.opsForZSet().rangeWithScores(REDIS_WB_DAY, 0, -1);
return set;
}
@PostMapping("getWeekWBData")
public Set getWeekWBData() {
long hour = System.currentTimeMillis() / (1000 * 60 * 60);
Set set = redisTemplate.opsForZSet().rangeWithScores(REDIS_WB_WEEK, 0, -1);
return set;
}
@PostMapping("getMonthWBData")
public Set getMonthWBData() {
long hour = System.currentTimeMillis() / (1000 * 60 * 60);
Set set = redisTemplate.opsForZSet().rangeWithScores(REDIS_WB_MONTH, 0, -1);
return set;
}
}