7-1:ZSetOperations重要api讲解(sortedSet)
1、排行榜:
排行榜功能是一个很普遍的需求。使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择。
一般排行榜都是有实效性的,比如“用户积分榜”,游戏中活跃度排行榜,游戏装备排行榜等。
面临问题:
数据库设计复杂,并发数较高,数据要求实时性高
2、redis实现排行榜api讲解
排行榜对应redis的sortSet。
步骤:
1.redisService 查看现成的方法。
/**
* 有序集合添加
*
* @param key
* @param value
* @param scoure
*/
public void zAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
}
参照这个:输入的是三个参数 第一个是有序集合的名称 第二个是值 第三个是排行的名次
2.创建RangService拷贝redisService 方法
3.写controller
跑一个结果:
http://localhost:8080/addScore?uid=1&score=15
http://localhost:8080/addScore?uid=2&score=5
http://localhost:8080/addScore?uid=3&score=10
增加分数:
http://localhost:8080/increScore?uid=3&score=10
源码对应:
public Double incrementScore(K key, V value, double delta) {
byte[] rawKey = this.rawKey(key);
byte[] rawValue = this.rawValue(value);
return (Double)this.execute((connection) -> {
return connection.zIncrBy(rawKey, delta, rawValue);
}, true);
}
底层的方法:
public void incrementScore(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.incrementScore(key, value, scoure);
}
获取排名
http://localhost:8080/rank?uid=3
底层的方法:
public Long zRank(String key, Object value) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rank(key,value);
}
分数小排名高。
http://localhost:8080/scoreByRange?start=0&end=-1
底层的方法:
public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start,long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key,start,end);
return ret;
}
对应这个方法。
7-2:浅谈mysql数据库表设计过程中几个关键要点
数据库的表:score_flow积分流水表 用于查top100 查询用户的排名
user_score用户积分表 特定用户的排名,通过用户的id看排名
1、表设计过程中应该注意的点即数据类型
1)更小的通常更好 :控制字节长度
2)使用合适的数据类型:如tinyint只占8个位,(3)100以内的数值。
char(死的)与varchar(可变长的,多一位存字节的长度255上限)的对比,用于定长数据的存储,如uuid,不需要额外一位存长度。
CREATE TABLE `score_flow` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`score` bigint(19) unsigned(非负整数) NOT NULL COMMENT '用户积分流水',
`user_id` int(11) unsigned NOT NULL COMMENT '用户主键id',
`user_name` varchar(30) NOT NULL DEFAULT '' COMMENT '用户姓名',
PRIMARY KEY (`id`),
KEY `idx_userid` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4;
3)尽量避免NULL NOT NULL DEFAULT ”“/0
NULL的列会让索引统计和值比较都更复杂。可为NULL的列会占据更多的磁盘空间,在Mysql中也需要更多复杂的处理程序,会使所索引失效或者命中率变低。
2、索引设计过程中应该注意的点
1)选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录
唯一索引的场景:就是首先你不加的话你在程序中要先查,再加就会效率很慢。
唯一索引的话就会快速定位。
保证物理唯一。
2)为经常需要排序、分组和联合操作的字段建立索引
经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间
3)常作为查询条件的字段建立索引
如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度
4)数据少的地方不必建立索引,指的是数据的种类少如state只有0和1 索引是很占内存的
3、sql优化,explain查看执行计划
1) 能够用BETWEEN的就不要用IN
2) 能够用DISTINCT的就不用GROUP BY
3) 避免数据强转 where id=1和where id='1'功能是一样的
4) 学会采用explain查看执行计划
7-3:sql优化,explain查看执行计划(扫描行数会影响CPU运行,占用大量内存)
续:org.mybatis.generator配置讲解
引入:
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<scope>test</scope>
<version>1.3.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
最后弄了个神奇的东西类似于代码生成器,这个用的时候再看。
7-4:用户实现排行榜的三大接口?
1、添加用户积分
2、获取top N 排行 redisService新增方法reverseRangeWithScores() 索引查看有序数组小的在前面
3、根据用户ID获取排行 zset.rank(key,value),key为set的名称,value为用户id id查看排名
使用场景:
---------------------------------------
添加积分:
/**
* 添加用户积分
*
* @param uid
* @param score
*/
public void increSaleSocre(String uid, Integer score) {
User user = userMapper.find(uid);
if (user == null) {
return;
}
int uidInt = Integer.parseInt(uid);
long socreLong = Long.parseLong(score + "");
String name = user.getUserName();
String key = uid + ":" + name;
// 每次的积分增加
scoreFlowMapper.insertSelective(new ScoreFlow(socreLong, uidInt, name));
// 积分增加总数
userScoreMapper.insertSelective(new UserScore(uidInt, socreLong, name));
redisService.incrementScore(SALESCORE, key, score);//只在redis更新分数不存数据库
}
这个是在数据库添加积分在redis也添加积分。
第一个是set的id就是哪个set 第二个是set的key 第三个是set的积分
------------------------
查询用户在排行榜里面的积分:
这个直接在redis里面查不需要数据库里面查。
这方法注意了。
// 查询排名 分数小的在前面
public Map<String, Object> userRank(String uid, String name) {
Map<String, Object> retMap = new LinkedHashMap<>();
String key = uid + ":" + name;
Integer rank = redisService.zRank(SALESCORE, key).intValue();//rank查询排行,0开始,小的在前面。
Long score = redisService.zSetScore(SALESCORE, key).longValue();//获得分数
retMap.put("userId", uid);
retMap.put("score", score);
retMap.put("rank", rank);
return retMap;
}
------------------------------
获取top,根据排名查询
public List<Map<String, Object>> reverseZRankWithRank(long start, long end) {
Set<ZSetOperations.TypedTuple<Object>> setObj = redisService.reverseZRankWithRank(SALESCORE, start, end);//传的是积分
List<Map<String, Object>> mapList = setObj.stream().map(objectTypedTuple -> {
Map<String, Object> map = new LinkedHashMap<>();
map.put("userId", objectTypedTuple.getValue().toString().split(":")[0]);
map.put("userName", objectTypedTuple.getValue().toString().split(":")[1]);
map.put("score", objectTypedTuple.getScore());
return map;
}).collect(Collectors.toList());
return mapList;
}
-----------------------------------------
测试三个方法。
http://localhost:8080/sale/increScore?uid=1&score=5 uid是用户的id score是分数
http://localhost:8080/sale/increScore?uid=2&score=6
http://localhost:8080/sale/increScore?uid=3&score=7
http://localhost:8080/sale/increScore?uid=4&score=8
http://localhost:8080/sale/increScore?uid=5&score=9
http://localhost:8080/sale/increScore?uid=6&score=10
redis存的是用户id:用户姓名 积分的加和是在redis里面加的。
redis和数据库存的值
查某个人的积分
http://localhost:8080/sale/userScore?uid=1&name=one
获取排行榜的前几名的用户
http://localhost:8080/sale/top?start=0&end=10
7-5 :redis项目实战之排行榜实现--重点
疑问?redis数据丢失的时候怎么办?
初始化加载做同步。实现将1000万用户load到缓存,用户请求判断该用户是不是缓存你的用户。
4、springboot项目初始化加载讲解?
场景:将一千万用户白名单load缓存,用户请求的时候判断该用户是否是缓存里面的用户。
1、springboot实现初始化加载配置(实现缓存预热)。
public void rankSaleAdd() {
UserScoreExample example = new UserScoreExample();
example.setOrderByClause("id desc");
List<UserScore> userScores = userScoreMapper.selectByExample(example);
userScores.forEach(userScore -> {
String key = userScore.getUserId() + ":" + userScore.getName();
redisService.zAdd(SALESCORE, key, userScore.getUserScore());
});
}
数据预热的代码。很简单就是遍历下。
2、采用实现springboot ApplicationRunner,里面只有一个run方法。
这个是shangguigu的
该方法仅在SpringApplication.run(…)完成之前调用,此时外部可以访问了,都放在容器中,然后去遍历调用的。服务已经对外了执行重写方法的时候。
解释:https://blog.csdn.net/jdd92/article/details/81053404
3.采用实现InitializingBean,里面只有一个方法。
InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet()方法。
在spring初始化bean的时候,如果bean实现了InitializingBean接口,
在对象的所有属性被初始化后之后才会调用afterPropertiesSet()方法,还没启动完毕。
在执行重写的方法的时候所有的服务还没有对外。
解释:https://www.cnblogs.com/weiqihome/p/8922937.html
初始化同步redis数据
初始化完成再放入请求
具体做法:将
数据load到缓存中。
1.写方法
2..首先会清空redis中所有的数据,手动的清空。
3..重启springboot
用另一个接口:
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("======enter init bean=======");
this.rankSaleAdd();
}
两个方法的区别:
第二种方法,在初始化预热的过程中用户请求是进不来的。
第一种方法,在进行初始化预热的时候是可以请求通的。
-----------------------------------
总结:存的是userId,和userName,加上score。