背景

在互联网的高并发场景下,请求会非常多,但是数据库连接池比较少,或者说需要减少CPU压力,减少处理逻辑的,需要把单个查询,用某些手段,改为批量查询多个后返回。

如:支付宝中,查询“个人信息”,用户只会触发一次请求,查询自己的信息,但是多个人同时这样做就会产生多次数据库连接。为了减少连接,需要在JAVA服务端进行合并请求,把多个“个人信息”查询接口,合并为批量查询多个“个人信息”接口,然后以个人信息在数据库的id作为Key返回给上游系统或者页面URL等调用方。

目的

  1. 减少访问数据库的次数
  2. 单位时间内的多个请求,合并为一个请求。让业务逻辑层把单个查询的sql,改为批量查询的sql。或者逻辑里面需要调用redis,那批量逻辑里面就可以用redis的pipeline去实现。
  3. 本次需要使用JDK原生手段来实现请求合并,因为大家不一定会有Hystrix,所以用原生办法实现,并解析HystrixCollapser里面是如何实现的。

点赞再看,关注公众号:【地藏思维】给大家分享互联网场景设计与架构设计方案

掘金:地藏Kelvin https://juejin.im/user/5d67da8d6fb9a06aff5e85f7

主要解决手段

  1. SpringCloud的Hystrix的自定义HystrixCollapse和HystrixCommand
  2. SpringCloud的Hystrix注解方式。
  3. 没有服务治理框架时,利用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. 请求合并逻辑层

  1. 创建请求合并逻辑入口
  2. 创建阻塞队列,用于累计多个请求参数
  3. 创建CompletableFuture类,为了本条线程阻塞,等批量查询处理完后,异步获取当前id对应的User结果信息。
  4. 执行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 类下创建内部类

创建内部类为了定时任务执行此逻辑,并且为了代码整洁,不在创建线程池时编写大方法块的代码。

在内部类里面主要逻辑:

  1. 从存放请求接口参数的requestQueue 队列中,获取所有成员,并放入当此触发任务逻辑的局部变量中
  2. 并且取出关键的请求参数id放入局部变量List中。
  3. 只要获取出变量,则进行批量查询
  4. 最后利用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毫秒内只有一个请求,也会去处理。

测试验证

  1. 同上一章一样触发Swagger-ui页面
  2. 请求两次不同的参数
  3. 结果如下图中,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和线程池异步实现的更多相关文章

  1. 高并发场景-请求合并(一)SpringCloud中Hystrix请求合并

    背景 在互联网的高并发场景下,请求会非常多,但是数据库连接池比较少,或者说需要减少CPU压力,减少处理逻辑的,需要把单个查询,用某些手段,改为批量查询多个后返回. 如:支付宝中,查询"个人信 ...

  2. 【高并发】通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程

    核心逻辑概述 ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态. ThreadPoolExecu ...

  3. 高并发场景之RabbitMQ篇

    上次我们介绍了在单机.集群下高并发场景可以选择的一些方案,传送门:高并发场景之一般解决方案 但是也发现了一些问题,比如集群下使用ConcurrentQueue或加锁都不能解决问题,后来采用Redis队 ...

  4. 高并发场景之RabbitMQ

    高并发场景之RabbitMQ 上次我们介绍了在单机.集群下高并发场景可以选择的一些方案,传送门:高并发场景之一般解决方案 但是也发现了一些问题,比如集群下使用ConcurrentQueue或加锁都不能 ...

  5. Java电商项目,秒杀,抢购等高并发场景的具体场景和一些概念以及处理思路

    这里我借鉴了网上其他大佬的观点: 一:高并发带来的挑战 原因:秒杀抢购会经常会带来每秒几万的高并发场景,为了更快的返回结果给用户. 吞吐量指标QPS(每秒处理请求数),假设一个业务请求响应耗时为100 ...

  6. 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存

    原文:http://blog.csdn.net/heyewu4107/article/details/71009712 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存 问 ...

  7. HttpClient在高并发场景下的优化实战

    在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...

  8. PHP如何解决网站大流量与高并发的问题(二)

    转载:https://zhyunfe.github.io/2017/10/02/php-interview-prepare-hc-2/ 数据库缓存 相关概念 什么是数据库缓存? 为什么使用缓存 使用M ...

  9. MySQL在大数据、高并发场景下的SQL语句优化和"最佳实践"

    本文主要针对中小型应用或网站,重点探讨日常程序开发中SQL语句的优化问题,所谓“大数据”.“高并发”仅针对中小型应用而言,专业的数据库运维大神请无视.以下实践为个人在实际开发工作中,针对相对“大数据” ...

随机推荐

  1. 你竟然不知道Java中可以用 :: 吗?

    简介 Java8中方法引用分为三种,方法引用通过一对双冒号:: 来表示,方法引用是一种函数式接口的另一种书写方式 静态方法引用,通过类名::静态方法名, 如 Integer::parseInt 实例方 ...

  2. 神舟zx6-ct5da装黑苹果Macos 10.15.6记录

    可能是一时脑子抽风,突然就想体验一把mac系统.以前就了解过,给非苹果电脑装macos叫黑苹果,emmmmm.好吧,给我的神船也整一个. 看了很多个视频,整理一下装黑苹果过程.本人电脑系统是win10 ...

  3. el-dialog“闪动”解决办法

    问题描述:el-dialog关闭的时候总是出现两次弹窗 解决思路:既然是el-dialog产生的那就直接杀掉el-dialog 代码实践:在el-dialog上添加上一个v-if,值就是用闭窗的值,促 ...

  4. Java HashMap源码

    http://blog.csdn.net/qq_27093465/article/details/52207135 http://blog.csdn.net/qq_27093465/article/d ...

  5. 【C#】Random类中构造方法、时间种子与随机数序列的关系

    Random类 构造函数 1) Random random = new Random(); // 无参数构造函数使用系统时钟生成其种子值 然而,系统时钟取值范围有限,因此在小规模计算中,可能无法使用不 ...

  6. I - 乓 (BFS+邻接表)

    USTC campus network is a huge network. There is a bi-directional link between every pair of computer ...

  7. Django 入门介绍

    Django介绍 Django框架是PythonWeb三大主流框架之一,以其功能强大全面而受到众多开发者追捧,现如今Django已经更新到3版本,但是并不推荐使用,更多建议使用1版本. Django版 ...

  8. Cocos Creator 性能优化:DrawCall

    前言 在游戏开发中,DrawCall 作为一个非常重要的性能指标,直接影响游戏的整体性能表现. 无论是 Cocos Creator.Unity.Unreal 还是其他游戏引擎,只要说到游戏性能优化,D ...

  9. 安装JDK的攻略

    打开浏览器搜索JDK官网 单击Oracle下的JDK Download. 如果英语不好就直接打开浏览器自带翻译功能,根据自己的系统以及相应版本,下载对应的JDK 根据安装提示进行安装,路径自己选择. ...

  10. MySQL查询point类型类型的坐标,返回经度纬度

    location字段为point类型的空间坐标 SELECT id, name, address, x(location) as 经度, Y(location) as 纬度, ROUND( 6378. ...