Redis存储结构及应用场景
1.存储结构
对于下文中key使用:进行分隔,其实是一种分目录的方式,使用冒号分隔,形成目录。如article:readcount:223的目录如下图(使用Another Redis):
通过分目录,一目了然有分类,相同的分类则归纳在同一个目录下,而不至于看的是所有的key,找起来就特别麻烦。
2.字符串string应用场景
String类型,键和值都是在字符串。
2.1计数器
在redis中有一个命令incr,可以将 key 中储存的数字值增一。故此功能可用于记录文章的阅读数。只要打开一次文章,阅读数就加1。
例如对id为29的文章记录阅读数
incr article:readcount:{文章id}
命令如下:
> incr article:readcount:222 1 > incr article:readcount:222 2 > incr article:readcount:222 3
可使用get命令查看最新的值。
java代码如下:(以基本方式说明,下同)
String key1 = "article:readcount:222"; //+1操作 stringRedisTemplate.boundValueOps(key1).increment(); stringRedisTemplate.boundValueOps(key1).increment(); stringRedisTemplate.boundValueOps(key1).increment(); String s = stringRedisTemplate.boundValueOps(key1).get(); System.out.println(s);
3.哈希hash应用场景
hash是一种key-value形式,而其vaule又是key-value的形式,故相当于给指定的key存入一个map,而map的字段分别是field和value。
3.1购物车案例
对于常见的购物车,会涉及到添加商品、删除商品、查看商品总数、查看所有的商品信息。使用hash进行存储非常合适。
以用户的id为key,商品的id作为值的key,商品数量作为值的value,假设用户id是666,三个商品id分别是1005,1008,1009:
1)模拟添加3个商品
> hset cart:666 1005 1 1 > hset cart:666 1008 1 1 > hset cart:666 1009 1 1
三条命令,也就是说需要三次添加到购物车
2)对第一个商品增加1
> hincrby cart:666 1005 1 2
3)查看商品总数
> hlen cart:666 3
查看的商品总数只是统计有多少种商品,但不会统计出所有商品的个数,这是其无法做到的。
4)删除第2个商品
> hdel cart:666 1008 1
5)查看购物车所有的商品
> hgetall cart:666 1005 2 1009 1
java代码如下:
String key = "cart:666"; String k1 = "1005"; String k2 = "1008"; String k3 = "1009"; stringRedisTemplate.boundHashOps(key).put(k1, "1");//添加商品 stringRedisTemplate.boundHashOps(key).put(k2, "1"); stringRedisTemplate.boundHashOps(key).put(k3, "1"); stringRedisTemplate.boundHashOps(key).increment(k1, 1);//商品数量+1 Long size = stringRedisTemplate.boundHashOps(key).size();//商品总数 System.out.println(size); stringRedisTemplate.boundHashOps(key).delete(k2);//删除商品 Map<Object, Object> entries = stringRedisTemplate.boundHashOps(key).entries();//获取所有元素 System.out.println(entries);
4.列表list应用场景
有序,可重复。
4.1公众号消息
在公众号消息中,最新发的消息会推送到最前面,使得用户第一时间看到最新消息,可利用lpush+lrange特性。
以用户id作为key,消息id作为item:
1)发布3条消息
> lpush msg:666 20221 1 > lpush msg:666 20222 2 > lpush msg:666 20223
2)获取最新的消息,即最新的消息排在最前面
> lrange msg:666 0 -1 20223 20222 20221
java代码:
String key = "msg:666"; stringRedisTemplate.boundListOps(key).leftPush("20221"); stringRedisTemplate.boundListOps(key).leftPush("20222"); stringRedisTemplate.boundListOps(key).leftPush("20223"); List<String> range = stringRedisTemplate.boundListOps(key).range(0, -1); System.out.println(range);
4.2强制修改密码
很多场景中当用户在修改密码时,不能使用最近5次使用过的密码,防止因密码被盗造成的风险。
可使用lpush+lrange+rpop特性,从左边存入最近修改的密码,从右边删除旧的密码,保证redis中只保留最近5次的密码。
以java代码说明:
public Boolean updatePwd(String p) {
String key = "zhangsan";
//获取所有值
List<String> list = stringRedisTemplate.boundListOps(key).range(0, -1);
System.out.println(list);
List<String> pwd;
Integer len = 5;
//获取最新的5个密码
if (list.size() < len) {
pwd = list;
} else {
pwd = list.subList(0, len);
}
if (pwd.contains(p)) {
System.out.println("新密码不能和最近" + len + "次的密码相同");
return false;
} else {
stringRedisTemplate.boundListOps(key).leftPush(p);
//删除多余的密码
for (int i = list.size(); i >= len; i--) {
stringRedisTemplate.boundListOps(key).rightPop();
}
return true;
}
}
5.集合set应用场景
无序,不能重复
5.1抽奖活动(等级奖品)
对于某些抽奖活动,有很多人参与,但中奖者都是按奖品个数随机抽中的。比如以活动编号是1022的抽奖为例,一共有3个奖品,10个人参与抽奖
1)模拟10人抽奖
> sadd prize:1022 201 1 > sadd prize:1022 202 1 > sadd prize:1022 203 1 > sadd prize:1022 204 1 > sadd prize:1022 205 1 > sadd prize:1022 206 1 > sadd prize:1022 207 1 > sadd prize:1022 208 1 > sadd prize:1022 209 1 > sadd prize:1022 210 1
查看全部抽奖的人
> smembers prize:1022 201 202 203 204 205 206 207 208 209 210
当然也可看参与抽奖的人数
> scard prize:1022 10
2)从参与者中随机抽取3人作为中奖者
> srandmember prize:1022 3 208 206 201
在获取随机元素时,指定获取的个数即可。
java代码:
String key = "prize:1022"; stringRedisTemplate.boundSetOps(key).add("201","202","203","204","205","206","207","208","209","210");//可变的,设置元素 Set<String> members = stringRedisTemplate.boundSetOps(key).members();//获取所有元素 Long size = stringRedisTemplate.boundSetOps(key).size();//大小 List<String> list = stringRedisTemplate.boundSetOps(key).randomMembers(3);//随机抽取3个
5.2抽奖活动(不等级奖品)
对于某些抽奖活动,有很多人参与,但中奖者都是按奖品个数随机抽中的。比如以活动编号是1023的抽奖为例,一共有3个奖品(1个一等奖,2个二等奖),10个人参与抽奖
1)模拟10人抽奖
> sadd prize:1023 201 1 > sadd prize:1023 202 1 > sadd prize:1023 203 1 > sadd prize:1023 204 1 > sadd prize:1023 205 1 > sadd prize:1023 206 1 > sadd prize:1023 207 1 > sadd prize:1023 208 1 > sadd prize:1023 209 1 > sadd prize:1023 210 1
查看全部抽奖的人
> smembers prize:1023 201 202 203 204 205 206 207 208 209 210
2)从参与者中随机抽取3人作为中奖者
首先抽取一等奖1个
> spop prize:1023 1 206
再抽取二等奖2个
> spop prize:1023 2 207 209
spop用来随机删除元素,默认只删除一个,可指定多个。
java代码:
String key = "prize:1022"; stringRedisTemplate.boundSetOps(key).add("201","202","203","204","205","206","207","208","209","210");//可变的,设置元素 Set<String> members = stringRedisTemplate.boundSetOps(key).members();//获取所有元素 Long size = stringRedisTemplate.boundSetOps(key).size();//大小 String pop1 = stringRedisTemplate.boundSetOps(key).pop();//随机删除 String pop2 = stringRedisTemplate.boundSetOps(key).pop();//随机删除 String pop3 = stringRedisTemplate.boundSetOps(key).pop();//随机删除 System.out.println(pop1); System.out.println(pop2); System.out.println(pop3);
6.有序集合Zset应用场景
不可重复,元素根据分数有序。
6.1 朋友圈点赞
在看谁对我点赞时,肯定是按先后顺序排列的。可按照点赞时的时间戳作为分值。
这里的时间戳是临时生成的,从前往后分别如下:
1653442642389 1653442642400 1653442642402 1653442642404 1653442642406
模拟对用户为1001的朋友圈点赞,用户分别是201,202,203,205,206.
> zadd py:1001 1653442642389 201 1 > zadd py:1001 1653442642406 202 1 > zadd py:1001 1653442642404 203 1 > zadd py:1001 1653442642402 205 1 > zadd py:1001 1653442642400 206 1
按先后顺序查看所有点赞的人
> zrange py:1001 0 -1 201 206 205 203 202
用户205取消点赞
> zrem py:1001 205 1
以上就简单实现了朋友圈点赞功能。其中java代码如下:
String key = "py:1001"; stringRedisTemplate.boundZSetOps(key).add("201", 1653442642389D); stringRedisTemplate.boundZSetOps(key).add("202", 1653442642406D); stringRedisTemplate.boundZSetOps(key).add("203", 1653442642404D); stringRedisTemplate.boundZSetOps(key).add("205", 1653442642402D); stringRedisTemplate.boundZSetOps(key).add("206", 1653442642400D); Set<String> range = stringRedisTemplate.boundZSetOps(key).range(0, -1); System.out.println(range); stringRedisTemplate.boundZSetOps(key).remove("205");
6.2实时热点排行榜
访问量越高的热点信息会排名越靠前,就是所谓的热搜。可按一段时间内阅读量作为分值。默认文章的阅读量为0,每阅读一次,阅读量+1.
假设有3篇文章,id分别为001,002,003:
设置初始阅读量为0
> zadd article:readcount 0 001 1 > zadd article:readcount 0 002 1 > zadd article:readcount 0 003 1
文章001被3个人阅读了
> zincrby article:readcount 1 001 1 > zincrby article:readcount 1 001 2 > zincrby article:readcount 1 001 3
文章002被5个人阅读了
> zincrby article:readcount 1 002 1 > zincrby article:readcount 1 002 2 > zincrby article:readcount 1 002 3 > zincrby article:readcount 1 002 4 > zincrby article:readcount 1 002 5
文章003被1个人阅读了
> zincrby article:readcount 1 003 1
查看所有文章排行榜(升序),不看分数
> zrange article:readcount 0 -1 003 001 002
查看所有文件及分数
> zrevrange article:readcount 0 -1 withscores 002 5 001 3 003 1
从这里可以看出,zrange按升序查询,那么对于排行榜,则实际业务处理中可按照查询的结果进行倒序展示。对于zrevrange则可按阅读量的高低直接展示,但由于其包含分数,查询的数据就会多一些。可根据实际需求对二者进行选择使用。
以上就简单实现了实时热点排行榜功能。其中java代码如下:
String key = "article:readcount"; stringRedisTemplate.boundZSetOps(key).add("001", 0); stringRedisTemplate.boundZSetOps(key).add("002", 0); stringRedisTemplate.boundZSetOps(key).add("003", 0);
stringRedisTemplate.boundZSetOps(key).incrementScore("001", 1);
stringRedisTemplate.boundZSetOps(key).incrementScore("001", 1); stringRedisTemplate.boundZSetOps(key).incrementScore("001", 1);
stringRedisTemplate.boundZSetOps(key).incrementScore("002", 1);
stringRedisTemplate.boundZSetOps(key).incrementScore("002", 1);
stringRedisTemplate.boundZSetOps(key).incrementScore("002", 1); stringRedisTemplate.boundZSetOps(key).incrementScore("002", 1);
stringRedisTemplate.boundZSetOps(key).incrementScore("002", 1);
stringRedisTemplate.boundZSetOps(key).incrementScore("003", 1); Set<String> range = stringRedisTemplate.boundZSetOps(key).range(0, -1);//升序 System.out.println(range); Set<String> range2 = stringRedisTemplate.boundZSetOps(key).reverseRange(0, -1);//倒序 System.out.println(range2); Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.boundZSetOps(key).rangeWithScores(0, -1);//升序,有分数 typedTuples.forEach(item -> { System.out.println(item.getValue() + ":" + item.getScore()); });
使用命令和在java中使用,还是有一定的区别,日常开发中代码方式使用更多,更推荐将这些方法封装到工具类中。