高并发场景-请求合并(二)揭秘HystrixCollapser-利用Queue和线程池异步实现
背景
在互联网的高并发场景下,请求会非常多,但是数据库连接池比较少,或者说需要减少CPU压力,减少处理逻辑的,需要把单个查询,用某些手段,改为批量查询多个后返回。
如:支付宝中,查询“个人信息”,用户只会触发一次请求,查询自己的信息,但是多个人同时这样做就会产生多次数据库连接。为了减少连接,需要在JAVA服务端进行合并请求,把多个“个人信息”查询接口,合并为批量查询多个“个人信息”接口,然后以个人信息在数据库的id作为Key返回给上游系统或者页面URL等调用方。
目的
- 减少访问数据库的次数
- 单位时间内的多个请求,合并为一个请求。让业务逻辑层把单个查询的sql,改为批量查询的sql。或者逻辑里面需要调用redis,那批量逻辑里面就可以用redis的pipeline去实现。
- 本次需要使用JDK原生手段来实现请求合并,因为大家不一定会有Hystrix,所以用原生办法实现,并解析HystrixCollapser里面是如何实现的。
点赞再看,关注公众号:【地藏思维】给大家分享互联网场景设计与架构设计方案
掘金:地藏Kelvin https://juejin.im/user/5d67da8d6fb9a06aff5e85f7
主要解决手段
- SpringCloud的Hystrix的自定义HystrixCollapse和HystrixCommand
- SpringCloud的Hystrix注解方式。
- 没有服务治理框架时,利用JDK队列、定时任务线程池处理。
在上一章已经说了第一二种,鉴于有同学没有SpringCloud,所以使用第3种来做请求合并,并一起分析请求合并的原理。
建议先看第一章,第二章相当于为HystrixCollapser的内部原理描述
高并发场景-请求合并(一)SpringCloud中Hystrix请求合并
交互流程
开发
本章节为利用JDK原生包开发,所以没有SpringCloud那么多东西要配置,编写代码只有一个类。
1. 创建请求层
只需要暴露单个查询的接口,业务逻辑层里做请求合并的逻辑。
@RestController
public class UserController {
@Autowired
private UserBatchWithFutureServiceImpl userBatchWithFutureServiceImpl;
@RequestMapping(method = RequestMethod.GET,value = "/userbyMergeWithFuture/{id}")
public User userbyMergeWithFuture(@PathVariable Long id) throws InterruptedException, ExecutionException {
User user = this.userBatchWithFutureServiceImpl.getUserById(id);
return user;
}
}
2. 请求合并逻辑层
- 创建请求合并逻辑入口
- 创建阻塞队列,用于累计多个请求参数
- 创建CompletableFuture类,为了本条线程阻塞,等批量查询处理完后,异步获取当前id对应的User结果信息。
- 执行CompletableFuture.get方法等待异步结果通知。
@Component
public class UserBatchWithFutureServiceImpl {
/** 积攒请求的阻塞队列 */
private LinkedBlockingDeque<UserQueryDto> requestQueue = new LinkedBlockingDeque<>();
public User getUserById(Long id) throws InterruptedException, ExecutionException {
UserQueryDto userQueryDto = new UserQueryDto();
userQueryDto.setId(id);
CompletableFuture<User> completedFuture = new CompletableFuture<>();
userQueryDto.setCompletedFuture(completedFuture);
requestQueue.add(userQueryDto);
User user = completedFuture.get();
return user;
}
HystrixCollapser也是利用这种办法来做异步通知的手段,让请求接口主线程在获得真正结果前阻塞等待。
3. 定时任务
在相同的类下创建定时任务,利用@PostConstruct让当前类的Bean构造完后执行该方法,生成一个5秒定时任务。
大家可以设定定时的时间,我为了比较方便测试,而用了5秒。
/** 线程池数量 */
private int threadNum = 1;
/** 定时间隔时长 */
private long period = 5000;
@PostConstruct
public void init() {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(createDeviceMergeNum);
// 每5秒执行一次
scheduledExecutorService.scheduleAtFixedRate(new UserBatchThread(), 0, createDeviceMergePeriod,
TimeUnit.MILLISECONDS);
}
HystrixCollapser的每隔n毫秒就会处理一次执行单个方法转批量方法,也是通过这类来实现的。
4. 在UserBatchWithFutureServiceImpl 类下创建内部类
创建内部类为了定时任务执行此逻辑,并且为了代码整洁,不在创建线程池时编写大方法块的代码。
在内部类里面主要逻辑:
- 从存放请求接口参数的requestQueue 队列中,获取所有成员,并放入当此触发任务逻辑的局部变量中
- 并且取出关键的请求参数id放入局部变量List中。
- 只要获取出变量,则进行批量查询
- 最后利用CompletedFuture异步通知并唤醒getUserById方法等待的线程。
public class UserBatchThread implements Runnable {
@Override
public void run() {
List<UserQueryDto> requestQueueTmp = new ArrayList<>();
// 存放批量查询的入参
List<Long> requestId = new ArrayList<>();
// 把出请求层放入的消息queue的元素取出来
int size = requestQueue.size();
for (int i = 0; i < size; i++) {
UserQueryDto request = requestQueue.poll();
if (Objects.nonNull(request)) {
requestQueueTmp.add(request);
requestId.add(request.getId());
}
}
if (!requestId.isEmpty()) {
try {
List<User> response = getUserBatchById(requestId);
Map<Long, User> collect = response.stream().collect(
Collectors.toMap(detail -> detail.getId(), Function.identity(), (key1, key2) -> key2));
// 通知请求的线程
for (UserQueryDto request : requestQueueTmp) {
request.getCompletedFuture().complete(collect.get(request.getId()));
}
} catch (Exception e) {
// 通知请求的线程-异常
requestQueueTmp.forEach(request -> request.getCompletedFuture().obtrudeException(e));
}
}
}
}
public List<User> getUserBatchById(List<Long> ids) {
System.out.println("进入批量处理方法" + ids);
List<User> ps = new ArrayList<>();
for (Long id : ids) {
User p = new User();
p.setId(id);
p.setUsername("dizang" + id);
ps.add(p);
}
return ps;
}
请求接口中入队列的元素,就会从这里取出,HystrixCollasper也是利用这种poll方法原子性的获取队列里面元素,不会被定时任务的多次触发而重复的获取,只要满足有至少一个都会做批量查询,所以HystrixCollasper合并请求时,即使n毫秒内只有一个请求,也会去处理。
测试验证
- 同上一章一样触发Swagger-ui页面
- 请求两次不同的参数
- 结果如下图中,console日志已经输出了两次请求的入参
总结
到这里相信大家都已经完成了合并请求了。这次没有依赖框架,基于原生做法,利用队列存查询所需的入参,然后利用线程池定时地获取队列的入参,再批量处理,利用线程的Future做异步返回结果。这样我们就理解了SpringCloud的HystrixCollasper的内部流程了。希望能够帮助没有框架的项目,或者公司技术栈不合适的情况下的同学。
本文Demo
都在我springcloud的demo里面了,看provider-hystrix-request-merge这个工程下的内容,在UserBatchWithFutureServiceImpl类中。
https://gitee.com/kelvin-cai/spring-cloud-demo
欢迎关注公众号,文章更快一步
我的公众号 :地藏思维
掘金:地藏Kelvin
简书:地藏Kelvin
我的Gitee: 地藏Kelvin https://gitee.com/kelvin-cai
高并发场景-请求合并(二)揭秘HystrixCollapser-利用Queue和线程池异步实现的更多相关文章
- 高并发场景-请求合并(一)SpringCloud中Hystrix请求合并
背景 在互联网的高并发场景下,请求会非常多,但是数据库连接池比较少,或者说需要减少CPU压力,减少处理逻辑的,需要把单个查询,用某些手段,改为批量查询多个后返回. 如:支付宝中,查询"个人信 ...
- 【高并发】通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程
核心逻辑概述 ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态. ThreadPoolExecu ...
- 高并发场景之RabbitMQ篇
上次我们介绍了在单机.集群下高并发场景可以选择的一些方案,传送门:高并发场景之一般解决方案 但是也发现了一些问题,比如集群下使用ConcurrentQueue或加锁都不能解决问题,后来采用Redis队 ...
- 高并发场景之RabbitMQ
高并发场景之RabbitMQ 上次我们介绍了在单机.集群下高并发场景可以选择的一些方案,传送门:高并发场景之一般解决方案 但是也发现了一些问题,比如集群下使用ConcurrentQueue或加锁都不能 ...
- Java电商项目,秒杀,抢购等高并发场景的具体场景和一些概念以及处理思路
这里我借鉴了网上其他大佬的观点: 一:高并发带来的挑战 原因:秒杀抢购会经常会带来每秒几万的高并发场景,为了更快的返回结果给用户. 吞吐量指标QPS(每秒处理请求数),假设一个业务请求响应耗时为100 ...
- 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存
原文:http://blog.csdn.net/heyewu4107/article/details/71009712 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存 问 ...
- HttpClient在高并发场景下的优化实战
在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...
- PHP如何解决网站大流量与高并发的问题(二)
转载:https://zhyunfe.github.io/2017/10/02/php-interview-prepare-hc-2/ 数据库缓存 相关概念 什么是数据库缓存? 为什么使用缓存 使用M ...
- MySQL在大数据、高并发场景下的SQL语句优化和"最佳实践"
本文主要针对中小型应用或网站,重点探讨日常程序开发中SQL语句的优化问题,所谓“大数据”.“高并发”仅针对中小型应用而言,专业的数据库运维大神请无视.以下实践为个人在实际开发工作中,针对相对“大数据” ...
随机推荐
- 程序员小哥教你秋招拿大厂offer
快要到秋招了,对于应届生来说,秋招是一个特别重要的机会.对于社招同学来说,金九银十也是一个很好的跳槽窗口. 而我呢,因为是从上海到广州工作,就没有提前先把工作定下来.刚好也趁这个机会出去旅游了两个月. ...
- 总结删除文件或文件夹的7种方法-JAVA IO基础总结第4篇
本文是Java IO总结系列篇的第4篇,前篇的访问地址如下: 总结java中创建并写文件的5种方式-JAVA IO基础总结第一篇 总结java从文件中读取数据的6种方法-JAVA IO基础总结第二篇 ...
- 冒泡排序的优化方案BubbleSort
<?php /** * 冒泡排序 * * ------------------------------------------------------------- * 思路分析:就是像冒泡一样 ...
- 分布式事务和分布式hash
分布式事务是什么? 分布式事务就是保证各个微服务之间数据一致,本质上就是保证不同数据库的数据一致性.一致性状态包含 强一致性,任何时刻,所有节点中数据都是一样的 弱一致性,数据更新后,只能访问到部分节 ...
- 1.异常页面: /File/ApplyFileForm.aspx 2.异常信息: 基类包括字段“PageOfficeCtrl1”,但其类型(PageOffice.PageOfficeCtrl)与控件(PageOffice.PageOfficeCtrl)的类型不兼容。
出现页面报错的解决如下: 1. 在 .aspx 页面报错的字段 ID 名称更改: 2. 在没有重复 ID控件,先实行步骤1, 生成项目是ok的,然后,还原,原来的ID名称,再去浏览页面运行, ...
- Mac Item2自动远程连接服务器
Mac Item2自动远程连接服务器 1.编写脚本 vi test #!/usr/bin/expect set PORT 端口 set HOST ip set USER root set PASSWO ...
- Kubernetes 存活、就绪探针
在设计关键任务.高可用应用程序时,弹性是要考虑的最重要因素之一. 当应用程序可以快速从故障中恢复时,它便具有弹性. 云原生应用程序通常设计为使用微服务架构,其中每个组件都位于容器中.为了确保Kuber ...
- 吴恩达《深度学习》-第五门课 序列模型(Sequence Models)-第一周 循环序列模型(Recurrent Neural Networks) -课程笔记
第一周 循环序列模型(Recurrent Neural Networks) 1.1 为什么选择序列模型?(Why Sequence Models?) 1.2 数学符号(Notation) 这个输入数据 ...
- Java基础之HashMap原理分析(put、get、resize)
在分析HashMap之前,先看下图,理解一下HashMap的结构 我手画了一个图,简单描述一下HashMap的结构,数组+链表构成一个HashMap,当我们调用put方法的时候增加一个新的 key-v ...
- 国产化之路-麒麟V10操作系统安装.net core 3.1 sdk
随着芯片国产化,操作系统国产化,软件国产化的声浪越来越高,公司也已经把开发项目国产化提上了日程,最近搞来了台长城的国产化电脑主机,用来搞试验,安装的是麒麟V10的操作系统,国产化折腾之路就此开始,用的 ...