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中使用,还是有一定的区别,日常开发中代码方式使用更多,更推荐将这些方法封装到工具类中。

posted @ 2022-05-18 21:35  钟小嘿  阅读(383)  评论(0编辑  收藏  举报