5_2-点赞功能-结合(多线程)or(多线程兼异步)定时持久化到数据库-应用redis的scan方法

该项目介绍了如何优化点赞功能的持久化过程,通过多线程和Redis的Scan方法提高效率。首先,创建假用户和博客数据,然后使用Redis存储点赞信息。文章对比了单线程和多线程(等待与异步)两种持久化策略,讨论了它们的适用场景和优缺点。
摘要由CSDN通过智能技术生成

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站评论区提问。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值