前言
需要统计某个商家商品的实时销量排行,可以使用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-plus
和redis
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
使用前先注入redisTemplate
和MallMapper
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
的排行榜功能实现,码字不易,觉得有用的话点个赞