0、前提引入:
视频讲解:
5.2-点赞功能-结合多线程定时持久化到数据库-应用redis的scan方法_哔哩哔哩_bilibili
项目前身:
5-点赞功能-定时持久化到数据库-pipeline+lua-优化ByScan_哔哩哔哩_bilibili
https://www.bilibili.com/video/BV1Gs4y1676q
blogLike_schedule/like05 · xin麒/XinQiUtilsOrDemo - 码云 - 开源中国 (gitee.com)
本项目地址:
本项目like05_02是like05的优化:
blogLike_schedule/like05_02 · xin麒/XinQiUtilsOrDemo - 码云 - 开源中国 (gitee.com)
https://gitee.com/flowers-bloom-is-the-sea/XinQiUtilsOrDemo/tree/master/blogLike_schedule/like05_02
数据表库等注意的点:
https://gitee.com/flowers-bloom-is-the-sea/XinQiUtilsOrDemo/tree/master/blogLike_schedule
其他需要注意的点:
测试时一定要将executeInternal
里面的内容注释掉,主要是防止定时任务的干扰:
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// 要执行的内容就写在这个函数中
// blogService.updateAllLikeListToDatabase();
// blogService.updateAllLikeListToDatabaseByThreads();
}
1、先造一些假user数据吧:
现在是20230715-9:50
@Slf4j
@SpringBootTest
public class DataConstructTest {
@Resource
private BlogServiceImpl blogService;
@Autowired
private UserService userService;
@Test//获取Redis里的数据,得到的是Map<String, String>
public void addSomeUser(){
for (int i = 0; i < 10000; i++) {
User user = new User();
user.setName("xinqi" + i);
Long phone = 10000000000L + i;
user.setPhone(phone.toString());
userService.save(user);
}
}
}
1.2先造假的博客数据:
@Test//这里不知道为什么不行
public void addSomeBlogByBatch() {
long userId = 0;
int addCount = 10000;
int preCount = 100;
int connection = addCount / preCount;
for (int i = 0; i < connection; i++) {
ArrayList<Blog> blogs = new ArrayList<>(preCount);
for (int j = 0; j < preCount; j++) {
Blog blog = new Blog();
blog.setTitle("title" + userId);
blog.setUserId(userId++);
}
System.out.println("now userId is" + userId);
blogService.saveBatch(blogs);
}
}
不知道为什么不行博客假数据的添加01.
思考:应该是mybatisPlus没有配置saveBatch相关的。
将就用:
@Test
public void addSomeBlog() {
long userId = 0;
for (int i = 0; i < 10000; i++) {
Blog blog = new Blog();
blog.setTitle("title" + userId);
blog.setUserId(userId++);
System.out.println("now userId is" + userId);
blogService.save(blog);
}
}
1.3win10查看、关闭和开启mysql服务:
看这里吧:https://blog.csdn.net/ws_please/article/details/131736814
2、先查看下redis和java服务器最大承载的内存和数据量大概是多少?
这个到底要不要关心,这里还是要去关系的,具体可以看gitee项目里的readme文档。
2.2往redis里装载数据
@Test
public void likeBlog() {
long userId = 0;
for (int i = 4; i < 10000; i++) {
blogService.likeBlog(userId,userId++);
}
}
补充单线程的操作:
@Override
public Result updateAllLikeListToDatabaseByNoThreads() {
int totalCount = 0;
String prefix = "BLOG_LIKED_KEY";
Jedis jedis = null;
ScanParams scanParams = new ScanParams();
try {
jedis = jedisPool.getResource();
String cursor = "0";
do {
// 扫描并获取一部分key
ScanResult<String> result = jedis.scan(cursor, scanParams.match(prefix.concat("*")));//https://www.cnblogs.com/xubiao/p/8489522.html
// 记录cursor
cursor = result.getCursor();
List<String> list = result.getResult();
totalCount += list.size();
if (list == null || list.isEmpty()) {
break;
}
// 遍历
for (String key : list) {
log.debug("key is {}", key);//这里可以看到有哪些key
Map<String, String> map = queryAllBlogLikesUserIdWithScoresByRedisKey(key);
Long blogId = Long.parseLong(key.substring(prefix.length(), key.length()));
durableToDatabaseByBlogIdAndMap(map, blogId);
}
} while (!cursor.equals("0"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) jedis.close();
}
log.debug("totalCount is {}", totalCount);
return Result.ok("some like_lists had bean durable in database from cache");
}
接下来要来实现多线程的优化了,有2种优化方式,都可以,各有各自的应用场景。
为什么可以用多线程来优化?redis服务器是有一个容器可以装载命令的,发多个命令过去可以减少网络通讯的时间,MySQL也一样。
3.1优化1-多线程-等待方式
循环{
通过scan扫描一部分key(如果扫描的游标归0,说明已经扫描结束)
开启线程来执行:{
通过这部分key在redis找到博客点赞信息再存储到数据库里面。
}
等待这些线程都执行完毕后再进入下一次循环。
}
1、多线程-等待-的代码:
@GetMapping("/updateAllLikeListToDatabaseByThreads")
@ApiOperation("测试多线程持久化接口")
public Result updateAllLikeListToDatabaseByThreads(){
return blogService.updateAllLikeListToDatabaseByThreads();
}
@Override
public Result updateAllLikeListToDatabaseByThreads() {
int totalCount = 0;
String prefix = "BLOG_LIKED_KEY";
Jedis jedis = null;
ScanParams scanParams = new ScanParams();
try {
jedis = jedisPool.getResource();
String cursor = "0";
do {
// 扫描并获取一部分key
ScanResult<String> result = jedis.scan(cursor, scanParams.match(prefix.concat("*")));//https://www.cnblogs.com/xubiao/p/8489522.html
// 记录cursor
cursor = result.getCursor();
List<String> list = result.getResult();
totalCount += list.size();
if (list == null || list.isEmpty()) {
break;
}
// List<Thread> threads = new Vector<>(list.size());//对于线程的添加,这里不需要用Vector,因为临界区仅仅包括线程里面的内容
List<Thread> threads = new ArrayList<>(list.size());//用ArrayList足以
// 遍历
for (String key : list) {
log.debug("key is {}", key);//这里可以看到有哪些key
Thread t = new Thread(() -> {
Map<String, String> map = queryAllBlogLikesUserIdWithScoresByRedisKey(key);
Long blogId = Long.parseLong(key.substring(prefix.length(), key.length()));
durableToDatabaseByBlogIdAndMap(map, blogId);
});//这里是和like05不一样的地方 TODO postman已经测试通过了,还没有对jemeter进行测试
// 判断key的类型
t.start();
threads.add(t);
}
threads.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threads = null;
} while (!cursor.equals("0"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) jedis.close();
}
log.debug("totalCount is {}", totalCount);
return Result.ok("some like_lists had bean durable in database from cache");
}
也就是用了join来等待一下,好处是如果风险某些线程没办法执行完毕,那么可以通过threads这个ArrayList集合来拿到抛出异常对象做一些异常处理已来做一些兜底或者防护或者是补救的措施,当然应该也可以执行将执行失败的线程再次通过其他方法让他重新执行直到执行成功(本项目目前就没写这些内容了,可以自己优化)。
3.2多线程-异步
这里是因为线程创建一般默认是非守护线程,因此不用join时,主线程执行完毕,哪怕守护线程没执行完毕,后续也会继续执行下去。
//异步
@Override
public Result updateAllLikeListToDatabaseByThreadsWithNoJoin() {
int totalCount = 0;
String prefix = "BLOG_LIKED_KEY";
Jedis jedis = null;
ScanParams scanParams = new ScanParams();
try {
jedis = jedisPool.getResource();
String cursor = "0";
do {
// 扫描并获取一部分key
ScanResult<String> result = jedis.scan(cursor, scanParams.match(prefix.concat("*")));//https://www.cnblogs.com/xubiao/p/8489522.html
// 记录cursor
cursor = result.getCursor();
List<String> list = result.getResult();
totalCount += list.size();
if (list == null || list.isEmpty()) {
break;
}
// 遍历
for (String key : list) {
log.debug("key is {}", key);//这里可以看到有哪些key
new Thread(() -> {
Map<String, String> map = queryAllBlogLikesUserIdWithScoresByRedisKey(key);
Long blogId = Long.parseLong(key.substring(prefix.length(), key.length()));
durableToDatabaseByBlogIdAndMap(map, blogId);
}).start();//这里是和like05不一样的地方 TODO postman已经测试通过了,还没有对jemeter进行测试
// 判断key的类型
}
} while (!cursor.equals("0"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) jedis.close();
}
log.debug("totalCount is {}", totalCount);
return Result.ok("some like_lists had bean durable in database from cache");
}
就这样了,其他的测试过程使用到了postman和jmeter这些工具,具体可以看项目的README文档。
4、其他
肯定是还有其他优化空间的,当然也不一定这个项目的代码没有瑕疵,如果有问题可以在gitee或B站评论区提问。