佬们求解高并发场景的问题

接手了同事一个高并发的场景问题,理论上峰值一秒要处理500次请求,
之前他和领导是讨论开发了这么一个方案:用redis做消息队列存储初始请求,然后写了一个定时任务定时扫描线程池里队列空闲的数量,如果队列有空闲值,从redis消息队列里消费数据,压到线程池里执行业务操作,以此循环。

大概代码过程

定时任务A {
    1.判断线程池是否有空闲队列
    2.从redis中获取消息
    3. 线程池操作调用方法B
       executor.execute(() -> {
          方法B(xxx);
       });
}

方法B进行了一些业务操作,然后调用了一个查询表数据的方法

方法B(xxx):{
    long time1 = System.currentTimeMillis();
	log.info("业务【" + xxx + "】time1 " + time1);
	
	Data = 方法C(xxx);
	
	long time5 = System.currentTimeMillis();
	log.info("业务【" + xxx + "】 time5 " + time5);
	
	long endTime = System.currentTimeMillis();
	log.info("工单【" + relId + "】查询耗时"+(endTime-time1)+"ms");
}

方法C查询了一条表数据并返回

方法C(xxx){
        long time2 = System.currentTimeMillis();
        log.info("业务【" + xxx + "】 time2 " + time2);

        StringBuffer sb = new StringBuffer("select * from 表名 where id=?");
        List<Map<String, Object>> list = jdbcTemplate.queryForList(sb.toString(), xxx);
		
        long time3 = System.currentTimeMillis();
        log.info("业务【" + xxx + "】 time3 " + time3);

		Map<String, Object> data = new HashMap<>;
        if(list!=null&&list.size()>0){
            data = list.get(0);
            
        }
        long time4 = System.currentTimeMillis();
        log.info("业务【" + xxx + "】 time4 " + time4);

        return data;
}

之前同事线程池设置核心线程数设置100,最大线程数200,池队列200,定时任务A每12秒执行一次,一次取100条数据,看了看日志,在执行的中间会有这种场景

2025-01-21 11:39:50.626  INFO 1077868 --- [ool-6-thread-17] .i.c.m.b.c. : 业务【xxx】time1 1737430790626

2025-01-21 11:40:05.240  INFO 1077868 --- [ool-6-thread-17] c.i.c.e.i.s.i.  : 业务【xxx】 time2 1737430805240
2025-01-21 11:40:05.242  INFO 1077868 --- [ool-6-thread-17] c.i.c.e.i.s.i.  : 业务【xxx】 time3 1737430805242
2025-01-21 11:40:05.242  INFO 1077868 --- [ool-6-thread-17] c.i.c.e.i.s.i.  : 业务【xxx】 time4 1737430805242
2025-01-21 11:40:05.243  INFO 1077868 --- [ool-6-thread-17] .i.c.m.b.c. : 业务【xxx】time5 1737430805243
2025-01-21 11:40:05.243  INFO 1077868 --- [ool-6-thread-17] .i.c.m.b.c. : 业务【xxx】查询耗时14617ms

time1和time2之间会相差15秒

其他设置和尝试:

  1. 减小了线程池的大小和定时任务间隔,线程池核心线程数改成16(服务器16核),队列大小改成了32;定时任务A改成1秒执行一次。
    结果是:总时间差不多还是五六分钟处理500条,但是time2和time3间隔差的短了点,在4s左右,还是不满足。
  2. 服务的数据库链接数设置了 200,定时服务运行时,数据库连接数没有超过数据库上限,且工具里查询同样的sql很快,应该不是数据库瓶颈
  3. 定时任务运行的时候看了下服务器top指令,cpu占用率最高每个核心30%,没有跑满;服务占用内存设置了最大2G;不知道有没有影响,看top里也没有占满只有1.7G左右
  4. 把方法C 抽出来直接放在方法B里实现查询,呈现出来就是jdbcTemplate执行的前后两个时间差距变大。

目前没定位到这个time2和1之间为什么会有这么久的间隔,有没有佬给分析一下,感谢!

12 个赞

首先,如果是我,我会考虑这里的业务逻辑是否可以放在redis中,因为数据库需要链接然后查询、返回、断开。

第二,我想知道业务具体做什么,比如说抢票还是说其他的,如果是抢票,我的想法是,将票号初始化到redis做hash,然后做请求进入做redis队列进入,消耗队列消息放在票号对应的value中,如果满了就结束了,最后放在数据库里

这里相当于做个排队,用mq也可以,但是需要考虑消息丢失问题

是公司一个业务场景,和原有框架高度耦合,这个表是一个工单的业务数据,在redis压入消息的同时就存到数据库里了

1 个赞

time2 和 time3 之间只做了一件事就是查库啊,
那就研究数据库,看下实际跑的库和你测试用的库是不是一个,
1.表量级大不大,如果大没有索引,索引有没有生效,
2.毕竟是并发跑的也上队列了,反正都要查,看看不能不批量查一下,查库可能用不了多久,但是每次链接数据库时间可能会长
3.是不是 tmd 网络问题???哲就涉及到我芝士盲区了-,-

而且我测下来感觉瓶颈不是在数据库连接或者数据库性能上,定时任务并发的时候,我用工具查数据库速度都正常

加油佬友,有结论了圈我一下,我来学习学习

db总数据量多大,id是不是索引

数据库不太适合 这么高频的查询吧,如果同时有其他业务写表或者有事务会锁住表的

我是做学校信息化的,我们选课系统瞬时并发就很大,所以不推荐高并发下直接操作数据库,线程如果没释放或者释放慢也可能会出现问题

(帖子已被作者删除)

刚发现写的有点问题,我列出来这个代码的日志问题显示的应该是time1和time2之间差距过大

2025-01-21 11:39:50.626  INFO 1077868 --- [ool-6-thread-17] .i.c.m.b.c. : 业务【xxx】time1 1737430790626
2025-01-21 11:40:05.240  INFO 1077868 --- [ool-6-thread-17] c.i.c.e.i.s.i.  : 业务【xxx】 time2 1737430805240
2025-01-21 11:40:05.242  INFO 1077868 --- [ool-6-thread-17] c.i.c.e.i.s.i.  : 业务【xxx】 time3 1737430805242
2025-01-21 11:40:05.242  INFO 1077868 --- [ool-6-thread-17] c.i.c.e.i.s.i.  : 业务【xxx】 time4 1737430805242
2025-01-21 11:40:05.243  INFO 1077868 --- [ool-6-thread-17] .i.c.m.b.c. : 业务【xxx】time5 1737430805243
2025-01-21 11:40:05.243  INFO 1077868 --- [ool-6-thread-17] .i.c.m.b.c. : 业务【xxx】查询耗时14617ms

还有个可能就是你系统 db 连接池的量不够,然后查询需要等待

索引设置了,而且并发的时候工具查询速度还可以,另一个并发的时候看了下数据库连接数,上限设置的1500,实际最高只有770左右,最低到600,也基本符合200左右的并发处理

看你代码没看出来time1 time2之间有啥呢

是不是方法c有什么异步的注解

有一个点要做下检查,看看线程池的代码里面有没有同步代码,之前写test的时候没注意,输出的时候有个地方偷懒写System.out.println(println里面有重锁,检查一圈才发现)导致没有出现并发的效果

2 个赞

看不出来啊,这就是方法里面调方法,咋会 15 秒呢,这玩意超过0.1秒都不应该啊…

1 个赞

是的,就是进了一个C方法,但是卡在这里,就很怪

我的意思是连接池等待链接,是java应用的配置,而不是数据库的配置