高并发场景-请求合并(一)SpringCloud中Hystrix请求合并
背景
在互联网的高并发场景下,请求会非常多,但是数据库连接池比较少,或者说需要减少CPU压力,减少处理逻辑的,需要把单个查询,用某些手段,改为批量查询多个后返回。
如:支付宝中,查询“个人信息”,用户只会触发一次请求,查询自己的信息,但是多个人同时这样做就会产生多次数据库连接。为了减少连接,需要在JAVA服务端进行合并请求,把多个“个人信息”查询接口,合并为批量查询多个“个人信息”接口,然后以个人信息在数据库的id作为Key返回给上游系统或者页面URL等调用方。
目的
- 减少访问数据库的次数
- 单位时间内的多个请求,合并为一个请求。让业务逻辑层把单个查询的sql,改为批量查询的sql。或者逻辑里面需要调用redis,那批量逻辑里面就可以用redis的pipeline去实现。
点赞再看,关注公众号:【地藏思维】给大家分享互联网场景设计与架构设计方案
掘金:地藏Kelvin https://juejin.im/user/5d67da8d6fb9a06aff5e85f7
主要解决手段
- SpringCloud的Hystrix的自定义HystrixCollapse和HystrixCommand
- SpringCloud的Hystrix注解方式。
- 没有服务治理框架时,利用JDK队列、定时任务线程池处理。
鉴于现在大部分都有SpringCloud,所以先说第2种的注解方式,后续再说第3种,不用第1种是因为注解方式比较方便。
交互流程
- 主思路是接收请求后,从上一次计数开始累计等待200ms
- 一次过处理200ms内的接口入参
- 然后以id为key,批量查询多个id的结果
- 批量查询完后,以id为key,返回给上游系统的单个查询
测试手段
- Postman
- 在本地系统创建单元测试方式,调用自己启动的服务
- 建立上游系统工程来调用
- 手动在页面请求多次
- Jmeter生成多线程请求
选其一种。建议1、4、5
开发
本文主要使用Hystrix注解的方式去实现,还有另外一种办法实现的就是编码自定义HystrixCollapser,那种方法是建立两个类,一个继承HystrixCollapser,另一个继承HystrixCommand,这个方法比较显式的编码声明有助于理解,但是不够Hystrix方式便捷。
自定义HystrixCollapser方式和Hystrix注解方式实现请求合并的优劣
虽然Hystrix注解方式比较快,但是不能做到实时更改等待的单位时间,那个超时时间是放在注解上,如果要更改单位时间,其实都需要重启服务或者重新编译打包。
用自定义HystrixCollapser比较好的地方就是可以在运行过程中,读字典表去更改单位时间,这样线上出问题了就不用重启了。
但是自定义HystrixCollapser方式缺点还是有的,因为绑定一个批量方法就要建立一个HystrixCommand类,如果有多个请求合并的情况,就只能建立多个HystrixCommand类了。
1. 添加POM
声明springboot 和springcloud版本
我以前做的工程使用了1.4.7.RELEASE,Camden.SR2。
其实大家可以用新版本的,只是新版本的eureka、Feign依赖的artifactId改变了,但是后续使用方式是一样的。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.7.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
添加关键依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>
2. 启动注解
除了SpringCloud客户端所基本需要的@SpringBootApplication @EnableEurekaClient,主要加上@EnableCircuitBreaker。因为使用到hystrix的都必须声明这个注解,为了启动断路器的意思,如熔断的时候也会使用,熔断也是通过hystrix来实现的。
这个比较关键,不启动的话,后续编码怎么弄都不生效的
@SpringBootApplication
@EnableDiscoveryClient
//使用hystrix必须增加
@EnableCircuitBreaker
@EnableEurekaClient
@EnableSwagger2
public class ProviderRequestMergeApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderRequestMergeApplication.class, args);
}
}
3. 请求接口Controller
编写两个接口,user方法是没有经过合并请求的样例,在本案例实际没有作用,只是用于校验合并与不合并的效果。
userbyMerge方法在合并请求的方法,其作为请求接口入口,合并请求的逻辑,并不需要在Controller里面实现,使得Controller只作为请求这一层,不耦合其他功能。
/**
*
* @author kelvin.cai
*
*/
@RestController
public class UserController {
@Autowired
private UserBatchServiceImpl userBatchServiceImpl;
@RequestMapping(method = RequestMethod.POST,value = "/user/{id}")
public User user(@PathVariable Long id) {
User book = new User( 55L, "姚雪垠2");
return book;
}
@RequestMapping(method = RequestMethod.GET,value = "/userbyMerge/{id}")
public User userbyMerge(@PathVariable Long id) throws InterruptedException, ExecutionException {
Future<User> userFu = this.userBatchServiceImpl.getUserById(id);
User user = userFu.get();
return user;
}
}
4. 编写请求合并逻辑
/**
*
* @author kelvin.cai
*
*/
@Component
public class UserBatchServiceImpl {
@HystrixCollapser(batchMethod = "getUserBatchById",scope=Scope.GLOBAL,
collapserProperties = {@HystrixProperty(name ="timerDelayInMilliseconds",value = "2000")})
public Future<User> getUserById(Long id) {
throw new RuntimeException("This method body should not be executed");
}
@HystrixCommand
public List<User> getUserBatchById(List<Long> ids) {
System.out.println("进入批量处理方法"+ids);
List<User> ps = new ArrayList<User>();
for (Long id : ids) {
User p = new User();
p.setId(id);
p.setUsername("dizang"+ids);
ps.add(p);
}
return ps;
}
}
这里有几个关键点(如果没生效可以看看)
- @HystrixCollapser参数batchMethod 的值为批量处理的方法的名字,批量处理方法必须在同一个类中。
- 单个处理方法和批量处理方法必须要同一个基本类型,只是批量方法需要使用List去包裹
- 单个处理方法,建议用Future,这个是jdk线程异步获取的那个类,用于异步获取结果。其实有另外的返回类型,让调用getUserById使用同步阻塞的方式去使用,但是不是很建议。
- scope有两个值一个是Scope.REQUEST,意思就是当次请求接口内调用UserBatchServiceImpl.getUserById多次才会合并。想想看,如果我一个接口内,调用多次单个插叙,为何不直接使用一个批量查询呢?我没想到有什么场景会需要这个值。
scope有另外一个值Scope.GLOBAL,就是样例所示的值,意思就是,所有请求接口进来都合并。大家回顾一下需求目的,就比较符合要求了,如多个支付宝用户查询自己的信息时就是合并全局请求。 - @HystrixProperty填合并请求的单位时间,debug时可以把他设置为5秒,比较好测试。
这里有个包路径的建议
这个合并请求类UserBatchServiceImpl 不建议放在业务逻辑层,为了保持业务逻辑service层代码是干净的只保留业务逻辑,所以这个UserBatchServiceImpl 类建议放在另外一个包collapser下,让这个包路径只是用于处理请求合并的事情。
因为这个类是利用springcloud框架实现,万一以后不用springcloud来做合并请求而用原始队列加线程池怎么办?
而且有些工程设计时,是建立server工程只做请求和服务治理,搞另外一个工程专门写domain领域下的东西,不包含其他框架的,这样为了第三个工程叫job定时任务工程可以直接使用domain工程的依赖。
这个领域驱动设计,请看我之前的文章。
测试方法
1. 触发测试
swagger-ui
如果你有添加swagger,那你打开http://localhost:7902/swagger-ui.html,对接口填一下参数请求两次。
2. 结果输出
下图中,console日志已经输出了两次请求的入参
3. Jmeter
Postman不能测试并发请求,为了试验并发,要么用上面的办法,要么下载Jmeter来做测试。
总结
到这里相信大家都已经完成了合并请求了,其实原理还是基于原始做法,利用队里存入参,然后利用线程池定时的获取队列的入参,再批量处理,利用线程的Future,异步返回结果。大致流程是这样的就不再描述了,如果有空会继续弄原始方法的请求合并。
大家还可以去看看Hystrix合并请求的其他参数,搜索相关信息来扩展hystrix功能。
本文Demo
都在我springcloud的demo里面了,看provider-hystrix-request-merge这个工程下的内容。
https://gitee.com/kelvin-cai/spring-cloud-demo
欢迎关注公众号,文章更快一步
我的公众号 :地藏思维
掘金:地藏Kelvin
简书:地藏Kelvin
我的Gitee: 地藏Kelvin https://gitee.com/kelvin-cai
高并发场景-请求合并(一)SpringCloud中Hystrix请求合并的更多相关文章
- 高并发场景-请求合并(二)揭秘HystrixCollapser-利用Queue和线程池异步实现
背景 在互联网的高并发场景下,请求会非常多,但是数据库连接池比较少,或者说需要减少CPU压力,减少处理逻辑的,需要把单个查询,用某些手段,改为批量查询多个后返回. 如:支付宝中,查询"个人信 ...
- Spring-cloud (九) Hystrix请求合并的使用
前言: 承接上一篇文章,两文本来可以一起写的,但是发现RestTemplate使用普通的调用返回包装类型会出现一些问题,也正是这个问题,两文没有合成一文,本文篇幅不会太长,会说一下使用和适应的场景. ...
- HttpClient在高并发场景下的优化实战
在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...
- MySQL在大数据、高并发场景下的SQL语句优化和"最佳实践"
本文主要针对中小型应用或网站,重点探讨日常程序开发中SQL语句的优化问题,所谓“大数据”.“高并发”仅针对中小型应用而言,专业的数据库运维大神请无视.以下实践为个人在实际开发工作中,针对相对“大数据” ...
- C++高并发场景下读多写少的解决方案
C++高并发场景下读多写少的解决方案 概述 一谈到高并发的解决方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也 ...
- C++高并发场景下读多写少的优化方案
概述 一谈到高并发的优化方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读 ...
- 高并发场景之RabbitMQ篇
上次我们介绍了在单机.集群下高并发场景可以选择的一些方案,传送门:高并发场景之一般解决方案 但是也发现了一些问题,比如集群下使用ConcurrentQueue或加锁都不能解决问题,后来采用Redis队 ...
- Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S
Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...
- 【转】记录PHP、MySQL在高并发场景下产生的一次事故
看了一篇网友日志,感觉工作中值得借鉴,原文如下: 事故描述 在一次项目中,上线了一新功能之后,陆陆续续的有客服向我们反应,有用户的个别道具数量高达42亿,但是当时一直没有到证据表示这是,确实存在,并且 ...
随机推荐
- 基于ABP 底层代码生成器
ABPBuilder.Tools 基于ABP 底层代码生成器 界面预览: 生成结果: 界面很简单,输入数据库连接字符串,选择要生成的表后,就能在桌面生成底层代码,然后复制到项目里即可. 做这个生成器的 ...
- Android开发之 当前日期String类型转date类型 java代码中实现方法
/** * 获取当前时间 * * @return */ public Date getDate(String str) { try { java.text.SimpleDateFormat forma ...
- jsBridge
jsBridge https://www.dazhuanlan.com/2019/12/05/5de7eb50739df/ JSBridge的原理 https://juejin.im/post/5ab ...
- 1090 Highest Price in Supply Chain (25 分)(模拟建树,找树的深度)牛客网过,pat没过
A supply chain is a network of retailers(零售商), distributors(经销商), and suppliers(供应商)-- everyone invo ...
- Codeforces1393 题解(A-D)
AC代码 A. Rainbow Dash, Fluttershy and Chess Coloring 可以推导出\(f_1 = 1, f_2 = 2, ..., f_n = f_{n - 2} + ...
- 推荐一个IT老鸟肝了2月有余的免费开源WPF企业级开发框架
一个新学WPF的IT老鸟,肝了2个月做了这么一个WPF企业级开发框架,站长clone学习,觉得甚是不错.这是一个使用了Prism搭建的插件式框架,封装了DataGrid的使用,使整个框架子模块简单易学 ...
- Java实现获取命令行中获取指定数据
执行ipconfig /all获取主机所有网卡信息并分析这些字符串,提取出有效网卡(网卡名称,mac地址,ipv4地址,掩码,网关,dns)将网卡插入HashMap中,key是网卡的名称,value是 ...
- Java中String.strip()和String.trim()方法
strip和trim String.trim() 可以去除字符串前后的"半角"空白字符 String.strip() 可以去除字符串前后的"全角和半角"空白字符 ...
- ckeditor4.0以上使用行间距插件lineheight报错修改
①从百度上下载一个 ckeditor 行距包,解压放到ckeditor/plugins目录下. ②在config.js 中添加 config.extraPlugins += (config.extra ...
- appium多线程自动化
基于上篇讲述的appium自动启动停止.测试服务.对controller文件进行相应的修改 1.首先对start_server函数,应采用多线程模式启动多个server,如下 其中启动的每个线程函数s ...