欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

redis实现商品销量排行榜

程序员文章站 2022-06-19 15:15:01
前言需要统计某个商家商品的实时销量排行,可以使用SQL语句,根据销量字段排序,但是这个方法需要进行全表扫描,当数据量非常大的时候,效率很低redis自带的数据结构zset是有序列表,可以结合redis更加高效的得到实时排行数据数据准备1. 表准备CREATE TABLE `mall` ( `id` bigint(20) NOT NULL, `name` varchar(20) DEFAULT NULL COMMENT '商品名称', `stock` bigint(20) DEFAULT...

前言

需要统计某个商家商品的实时销量排行,可以使用SQL语句,根据销量字段排序,但是这个方法需要进行全表扫描,当数据量非常大的时候,效率很低

redis自带的数据结构zset是有序列表,可以结合redis更加高效的得到实时排行数据

数据准备

1. 表准备

CREATE TABLE `mall` (
  `id` bigint(20) NOT NULL,
  `name` varchar(20) DEFAULT NULL COMMENT '商品名称',
  `stock` bigint(20) DEFAULT '0' COMMENT '库存',
  `shop_id` varchar(32) DEFAULT NULL COMMENT '商家id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. 测试数据

INSERT INTO `mall` VALUES (1, '红豆奶茶', 1000, '1001');
INSERT INTO `mall` VALUES (2, '原味奶茶', 1000, '1001');
INSERT INTO `mall` VALUES (3, '巧克力奶茶', 1000, '1001');
INSERT INTO `mall` VALUES (4, '柠檬水', 1000, '1001');
INSERT INTO `mall` VALUES (5, '双皮奶', 1000, '1001');
INSERT INTO `mall` VALUES (6, '茉莉雪顶', 1000, '1001');
INSERT INTO `mall` VALUES (7, '相思奶茶', 1000, '1001');
INSERT INTO `mall` VALUES (8, '城市恋人', 1000, '1001');
INSERT INTO `mall` VALUES (9, '冰可乐', 1000, '1001');
INSERT INTO `mall` VALUES (10, '心动之芒', 1000, '1001');
INSERT INTO `mall` VALUES (11, '藿香正气水', 1000, '1001');

工程搭建

我这里使用springboot工程,集成mybatis-plusredis

1. 相关依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.28</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>
</dependencies>

2. 配置文件

server:
  port: 8001
  context-path: /
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=true
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    jedis:
      pool:
        max-active: 20
        max-wait: 1
        max-idle: 10
        min-idle: 0
    timeout: 1000
mybatis-plus:
  type-aliases-package: com.weilc.demo.entity

3. 映射文件

使用逆向工程生成实体类和mapper接口,因为这里用的mybatis-plus,所以单表操作可以不生成xml文件

逆向工程没有的也可以使用我写的这个:mybatis-plus逆向工程工具

功能测试

我这里直接使用springboot自带的redisTemplate操作redis

使用前先注入redisTemplateMallMapper

private static final String SALE_KEY = "SALE_KEY";
@Autowired
private MallMapper mallMapper;
@Autowired
private StringRedisTemplate redisTemplate;

1. 新增销量

@GetMapping("/increment/{id}/{num}")
public String increment(@PathVariable("id") String id, @PathVariable("num")double num) {
    Mall mall = mallMapper.selectById(id);
    log.info("售出-" + mall.getName());
    String key = SALE_KEY + "_" + mall.getShopId();
    redisTemplate.opsForZSet().incrementScore(key, mall.getName(), num);
    return "ok";
}
@GetMapping("/add/{id}/{num}")
public String add(@PathVariable("id") String id, @PathVariable("num")double num) {
    Mall mall = mallMapper.selectById(id);
    log.info("售出-" + mall.getName());
    String key = SALE_KEY + "_" + mall.getShopId();
    redisTemplate.opsForZSet().add(key, mall.getName(), num);
    return "ok";
}

因为实际我们数据库里面应该是有很多不同的商家,所以我们这里的key使用静态常量加上shoId代表该商家的唯一标识

这里有两个接口,都可以实现新增数据的功能,区别就是第二个接口的方法add,会覆盖掉原来key的销量的值,相当于更新操作,而第一个接口的incrementScore方法,会在原有的基础上进行累加

我们在这里传入不同的id,模拟每个商品的销售情况

2. 获取指定范围的排行(根据销量倒序)

@GetMapping("/rank/{shopId}/{start}/{end}")
public Set rank(@PathVariable("shopId")String shopId,@PathVariable("start")long start,@PathVariable("end")long end) {
    String key = SALE_KEY + "_" + shopId;
    Set<String> set = redisTemplate.opsForZSet().reverseRange(key, start, end);
    return set;
}

如:获取该商家销量前10的商品列表:http://127.0.0.1:8001/mall/rank/1001/0/10

返回数据:

["城市恋人","红豆奶茶","柠檬水","相思奶茶","茉莉雪顶","巧克力奶茶","双皮奶","藿香正气水","心动之芒","原味奶茶","冰可乐"]

3. 获取指定范围的排行和销量

@GetMapping("/rankWithScore/{shopId}/{start}/{end}")
public Set rankWithScore(@PathVariable("shopId")String shopId,@PathVariable("start")long start,@PathVariable("end")long end) {
    String key = SALE_KEY + "_" + shopId;
    Set<ZSetOperations.TypedTuple<String>> set = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
    return set;
}

调用接口:http://127.0.0.1:8001/mall/rankWithScore/1001/0/10

返回数据:

[{"score":20.0,"value":"城市恋人"},{"score":16.0,"value":"红豆奶茶"},{"score":15.0,"value":"柠檬水"},{"score":14.0,"value":"相思奶茶"},{"score":13.0,"value":"茉莉雪顶"},{"score":11.0,"value":"巧克力奶茶"},{"score":10.0,"value":"双皮奶"},{"score":9.0,"value":"藿香正气水"},{"score":6.0,"value":"心动之芒"},{"score":3.0,"value":"原味奶茶"},{"score":2.0,"value":"冰可乐"}]

4. 查找某个商品的销量排行名次

@GetMapping("/findRank/{id}")
public Long findRank(@PathVariable String id) {
    Mall mall = mallMapper.selectById(id);
    String key = SALE_KEY + "_" + mall.getShopId();
    Long rankNum = redisTemplate.opsForZSet().reverseRank(key, mall.getName());
    return rankNum;
}

调用接口:http://127.0.0.1:8001/mall/findRank/3

返回数据:5

5. 查找某个商品的销量数字

@GetMapping("/findScore/{id}")
public Double findScore(@PathVariable String id) {
    Mall mall = mallMapper.selectById(id);
    String key = SALE_KEY + "_" + mall.getShopId();
    Double score = redisTemplate.opsForZSet().score(key, mall.getName());
    return score;
}

调用接口:http://127.0.0.1:8001/mall/findScore/3

返回数据:11.0

6. 统计某个销量区间内有多少商品

@GetMapping("/count/{shopId}/{start}/{end}")
public Long count(@PathVariable("shopId") String shopId, @PathVariable("start") long start, @PathVariable("end") long end) {
    String key = SALE_KEY + "_" + shopId;
    Long count = redisTemplate.opsForZSet().count(key, start, end);
    return count;
}

调用接口:http://127.0.0.1:8001/mall/count/1001/5/100

返回数据:9

7. 获取集合的基数(数量大小)

@GetMapping("/zCard/{shopId}")
public Long zCard(@PathVariable String shopId) {
    String key = SALE_KEY + "_" + shopId;
    Long aLong = redisTemplate.opsForZSet().zCard(key);
    return aLong;
}

调用接口:http://127.0.0.1:8001/mall/zCard/1001

返回数据:11

8. 删除指定区间排行的数据

@GetMapping("/removeRange/{shopId}/{start}/{end}")
public void clear(@PathVariable("shopId") String shopId, @PathVariable("start") long start, @PathVariable("end") long end) {
    String key = SALE_KEY + "_" + shopId;
    redisTemplate.opsForZSet().removeRange(key, start, end);
}

删掉之后,剩下的数据会重新排序

总结归纳

在上述测试中,我们了解了使用redisTemplate操作zset的添加,查询,删除等功能

1. 新增更新

//单个新增or更新
Boolean add(K key, V value, double score);
//批量新增or更新
Long add(K key, Set<TypedTuple<V>> tuples);
//使用加法操作分数
Double incrementScore(K key, V value, double delta);

2. 查询

查询分为正序和逆序,逆序只需要在下面的方法前面加上reverse即可,redis默认的是从小到大排序

列表查询

//通过排名区间获取列表值集合
Set<V> range(K key, long start, long end);
//通过排名区间获取列表值和分数集合
Set<TypedTuple<V>> rangeWithScores(K key, long start, long end);
//通过分数区间获取列表值集合
Set<V> rangeByScore(K key, double min, double max);
//通过分数区间获取列表值和分数集合
Set<TypedTuple<V>> rangeByScoreWithScores(K key, double min, double max);
//通过Range对象删选再获取集合排行
Set<V> rangeByLex(K key, Range range);
//通过Range对象删选再获取limit数量的集合排行
Set<V> rangeByLex(K key, Range range, Limit limit);

单人查询

//获取个人排行
Long rank(K key, Object o);
//获取个人分数
Double score(K key, Object o);

3. 删除

//通过key/value删除
Long remove(K key, Object... values);
//通过排名区间删除
Long removeRange(K key, long start, long end);
//通过分数区间删除
Long removeRangeByScore(K key, double min, double max);

4. 统计

//统计分数区间的人数
Long count(K key, double min, double max);
//统计集合基数
Long zCard(K key);

以上就是redis的排行榜功能实现,码字不易,觉得有用的话点个赞

本文地址:https://blog.csdn.net/w139074301/article/details/112687954