架构师系列文:通过Spring Cloud组件Hystrix合并请求
在前文里,我们讲述了通过Hystrix进行容错处理的方式,这里我们将讲述通过Hystrix合并请求的方式
哪怕一个URL请求调用的功能再简单,Web应用服务都至少会开启一个线程来提供服务,换句话说,有效降低URL请求数能很大程度上降低系统的负载。通过Hystrix提供的“合并请求”机制,我们能有效地降低请求数量。
在如下的HystrixMergeDemo.java里,我们将收集2秒内到达的所有“查询订单”的请求,并把它们合并到一个对象中传输给后台,后台则是根据多个请求参数统一返回查询结果,这种基于合并的做法将比每次只处理一个请求的方式要高效得多,代码比较长,我们按类来说明。
1 //省略必要的package和import的代码
2 class OrderDetail{ //订单业务类,其中包含2个属性
3 private String orderId;
4 private String orderOwner;
5 //省略针对orderId和orderOwner的get和set方法
6 //重写toString方法,方便输出
7 public String toString() {
8 return "orderId: " + orderId + ", orderOwner: " + orderOwner ;
9 }
10 }
11 //合并订单请求的处理器
12 class OrderHystrixCollapser extends HystrixCollapser<Map<String, OrderDetail>, OrderDetail, String>
13 {
14 String orderId;
15 //在构造函数里传入请求参数
16 public OrderHystrixCollapser(String orderId)
17 { this.orderId = orderId;}
18 //指定根据orderId去请求OrderDetail
19 public String getRequestArgument()
20 { return orderId; }
21 //创建请求命令
22 protected HystrixCommand<Map<String, OrderDetail>> createCommand(
23 Collection<CollapsedRequest<OrderDetail, String>> requests)
24 { return new MergerCommand(requests); }
25 //把请求得到的结果和请求关联到一起
26 protected void mapResponseToRequests(Map<String, OrderDetail> batchResponse,
27 Collection<CollapsedRequest<OrderDetail, String>> requests) {
28 for (CollapsedRequest<OrderDetail, String> request : requests)
29 {
30 // 请注意这里是得到单个请求的结果
31 OrderDetail oneOrderDetail = batchResponse.get(request.getArgument());
32 // 把结果关联到请求中
33 request.setResponse(oneOrderDetail);
34 }
35 }
36 }
在第2行,我们定义了OrderDetail类,这里,我们将合并针对该类对象的请求。
在第12行,我们定义了合并订单的处理器OrderHystrixCollapser类, 它继承(extends)了HystrixCollapser<Map<String, OrderDetail>, OrderDetail, String>类,而HystrixCollapser泛型中包含了3个参数,其中第一个参数Map<String, OrderDetail>表示该合并处理器合并请求后返回的结果类型,第二个参数表示是合并OrderDetail类型的对象,第三个参数则表示是根据String类型的请求参数来合并对象。
在第19行里,我们指定了是根据String类型的OrderId参数来请求OrderDetail对象,在第22行的createCommand方法里,我们指定了是调用MergerCommand方法来请求多个OrderDetail,在第26行的mapResponseToRequests方法里,我们是用第28行的for循环,依次把batchResponse对象中包含的多个的查询结果设置到request对象里,由于request是参数requests里的元素,所以执行完第28行的for循环后,requests对象就能关联到合并后的查询结果。
37 class MergerCommand extends HystrixCommand<Map<String, OrderDetail>> {
38 //用orderDB模拟数据库中的数据
39 static HashMap<String,String> orderDB = new HashMap<String,String> ();
40 static {
41 orderDB.put("1","Peter");
42 orderDB.put("2","Tom");
43 orderDB.put("3","Mike");
44 }
45 Collection<CollapsedRequest<OrderDetail, String>> requests;
46 public MergerCommand(Collection<CollapsedRequest<OrderDetail, String>> requests) {
47 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
48 .asKey("mergeDemo")));
49 this.requests = requests;
50 }
51 //在run方法里根据请求参数返回结果
52 protected Map<String, OrderDetail> run() throws Exception {
53 List<String> orderIds = new ArrayList<String>();
54 //通过for循环,整合参数
55 for(CollapsedRequest<OrderDetail, String> request : requests)
56 { orderIds.add(request.getArgument()); }
57 // 调用服务,根据多个订单Id获得多个订单对象
58 Map<String, OrderDetail> ordersHM = getOrdersFromDB(orderIds);
59 return ordersHM;
60 }
61 //用HashMap模拟数据库,从数据库中获得对象
62 private Map<String, OrderDetail> getOrdersFromDB(List<String> orderIds) {
63 Map<String, OrderDetail> result = new HashMap<String, OrderDetail>();
64 for(String orderId : orderIds) {
65 OrderDetail order = new OrderDetail();
66 //这个本该是从数据库里得到,但为了模拟,仅从HashMap里取数据
67 order.setOrderId(orderId);
68 order.setOrderOwner(orderDB.get(orderId) );
69 result.put(orderId, order);
70 }
71 return result;
72 }
73 }
在MergerCommand类的第38到44行里,我们用了orderDB对象来模拟数据库里存储的订单数据。在第46行的构造函数里,我们用传入的requests对象来构建本类里的同名对象,在这个传入的requests对象里,已经包含了合并后的请求。
在第52行的run方法里,我们通过第55行的for循环,依次遍历requests对象,并组装包含请求参数集合的orderIds对象,随后在第58行里,通过getOrdersFromDB方法,根据List类型的orderIds参数,模拟地从数据库里读取数据。
74 public class HystrixMergeDemo{
75 public static void main(String[] args){
76 // 收集2秒内发生的请求,合并为一个命令执行
77 ConfigurationManager.getConfigInstance().setProperty( "hystrix.collapser.default.timerDelayInMilliseconds", 2000);
78 // 初始化请求上下文
79 HystrixRequestContext context = HystrixRequestContext .initializeContext();
80 // 创建3个请求合并处理器
81 OrderHystrixCollapser collapser1 = new OrderHystrixCollapser("1");
82 OrderHystrixCollapser collapser2 = new OrderHystrixCollapser("2");
83 OrderHystrixCollapser collapser3 = new OrderHystrixCollapser("3");
84 // 异步执行
85 Future<OrderDetail> future1 = collapser1.queue();
86 Future<OrderDetail> future2 = collapser2.queue();
87 Future<OrderDetail> future3 = collapser3.queue();
88 try {
89 System.out.println(future1.get());
90 System.out.println(future2.get());
91 System.out.println(future3.get());
92 } catch (InterruptedException e) {
93 e.printStackTrace();
94 } catch (ExecutionException e) {
95 e.printStackTrace();
96 }
97 /关闭请求上下文
98 context.shutdown();
99 }
100 }
第74行定义的HystrixMergeDemo类里包含着main方法,在第77行里,我们设置了合并请求的窗口时间是2秒,在第81到83行,创建了3个合并处理器对象,从第85到87行,我们是通过queue 方法,以异步的方式启动了三个处理器,并在第89到91行里,输出了三个处理器返回的结果。这个程序的运行结果如下。
1 orderId: 1, orderOwner: Peter
2 orderId: 2, orderOwner: Tom
3 orderId: 3, orderOwner: Mike
虽然在main方法里,我们发起了3次调用,但由于这些调用是发生在2秒内的,所以会被合并处理,下面我们结合上述针对类和方法的说明,归纳下合并处理3个请求的流程。
步骤一,在代码的81到83行里,我们是通过OrderHystrixCollapser类型的collapser1等三个对象来传入待合并处理的请求,OrderHystrixCollapser类会通过第16行的构造函数,分别接收三个对象传入的orderId参数,并通过第22行的createCommand方法,调用MergerCommand类的方法执行“根据订单Id查订单”的业务。
这里说明下,由于在OrderHystrixCollapser内第16行的getRequestArgument方法里,我们指定了查询参数名是orderId,所以createCommand方法的requests参数,会用orderId来设置查询请求,同时,MergerCommand类中的相关方法也会用该对象来查询OrderDetail信息。
步骤二,由于在createCommand方法里,调用了MergerCommand类的构造函数,所以会触发该类第52行的run方法,在这个方法里,通过第55行和第56行的for循环,把request请求中包含的多个Argument(也就是OrderId)放入到orderIds这个List类型的对象中,随后通过第58行的getOrdersFromDB方法,根据这些orderIds去找对应的OrderDetail对象。
步骤三,在getORdersFromDB方法里,找到对应的多个OrderDetail对象,并组装成Map<String, OrderDetail>类型的result对象返回,然后按调用链的关系,层层返回给OrderHystrixCollapser类。
步骤四,在OrderHystrixCollapser类的mapResponseToRequests方法里,通过for循环,把多次请求的结果组装到requests对象中。由于requests对象是Collection<CollapsedRequest<OrderDetail, String>>类型的,其中用String类型的OrderId关联到了一个OrderDetail对象,所以这里会把合并查询的结果再拆散给3次请求,具体而言,会把3个OrderDetail对象对应地返回给第85行到第87行通过queue调用的3个请求。
这里请注意,虽然通过合并请求的处理方法能降低URL请求的数量,但如果合并后的URL请求数过多,会撑爆掉合并处理器(这里是OrderHystrixCollapser类)的缓存。比如在某项 目里,虽然只设置了合并5秒内的请求,但正好赶上秒杀活动,在这个窗口期内的请求数过万,那么就有可能出问题。
所以一般会在上线前,先通过测试确定合并处理器的缓存容量,随后再预估下平均每秒的可能访问数,然后再据此设置合并的窗口时间。
本人之前写的和本文有关的Spring Cloud其它相关文章。
架构师入门:Spring Cloud系列,Hystrix与Eureka的整合
架构师系列文:通过Spring Cloud组件Hystrix合并请求的更多相关文章
- 阿里高级架构师教你使用Spring Cloud Sleuth跟踪微服务
随着微服务数量不断增长,需要跟踪一个请求从一个微服务到下一个微服务的传播过程,Spring Cloud Sleuth 正是解决这个问题,它在日志中引入唯一ID,以保证微服务调用之间的一致性,这样你就能 ...
- Spring Cloud 组件 —— hystrix
作用与功能 ① 资源隔离, 每个依赖配备单独的线程池,为每个依赖提供一个小的线程池(或信号),如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间.② 依赖超时,可配置依赖调用超时时间,超 ...
- 从架构演进的角度聊聊Spring Cloud都做了些什么?
Spring Cloud作为一套微服务治理的框架,几乎考虑到了微服务治理的方方面面,之前也写过一些关于Spring Cloud文章,主要偏重各组件的使用,本次分享主要解答这两个问题:Spring Cl ...
- SpringCloud升级之路2020.0.x版-9.如何理解并定制一个Spring Cloud组件
本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ 我们实现的 S ...
- 从架构演进的角度聊聊Spring Cloud都做了些什么
1.从架构演进的角度聊聊Spring Cloud都做了些什么?2.中小型互联网公司微服务实践-经验和教训3.Spring Cloud在国内中小型公司能用起来吗?
- App架构师实践指南三之基础组件
App架构师实践指南三之基础组件 1.基础组件库随着时间的增长,代码量的逐渐积累,新旧项目之间有太多可以服用的代码.下面是整理的公共代码库. 2.关于加密密钥的保护以及网络传输安全是移动应用安全最关键 ...
- 跟我学SpringCloud | 第十九章:Spring Cloud 组件 Docker 化
前面的文章<跟我学SpringCloud | 第十八篇:微服务 Docker 化之基础环境>我们介绍了基础环境系统和 JRE 的容器化,这一节我们介绍 Spring Cloud 组件的容器 ...
- Spring Cloud中Hystrix、Ribbon及Feign的熔断关系是什么?
导读 今天和大家聊一聊在Spring Cloud微服务框架实践中,比较核心但是又很容易把人搞得稀里糊涂的一个问题,那就是在Spring Cloud中Hystrix.Ribbon以及Feign它们三者之 ...
- Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失问题分析
最近spring boot项目中由于使用了spring cloud 的hystrix 导致了threadLocal中数据丢失,其实具体也没有使用hystrix,但是显示的把他打开了,导致了此问题. 导 ...
随机推荐
- vue.js小总结
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统; 指令带有前缀 v-,以表示它们是 Vue 提供的特殊特性; v-for 指令可以绑定数组的数据来渲染一个项目列 ...
- 理解Go Context机制
1 什么是Context 最近在公司分析gRPC源码,proto文件生成的代码,接口函数第一个参数统一是ctx context.Context接口,公司不少同事都不了解这样设计的出发点是什么,其实我也 ...
- 【bzoj1758】[Wc2010]重建计划
Description Input 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案, ...
- BZOJ_3589_动态树_容斥原理+树链剖分
BZOJ_3589_动态树_容斥原理+树链剖分 题意: 维护一棵树,支持1.子树内点权加上一个数 2.给出k条链,求路径上的点权和(重复的计算一次) (k<=5) 分析: 可以用树剖+线段树解 ...
- BZOJ_[JSOI2010]Group 部落划分 Group_kruskal
BZOJ_[JSOI2010]Group 部落划分 Group_kruskal Description 聪聪研究发现,荒岛野人总是过着群居的生活,但是,并不是整个荒岛上的所有野人都属于同一个部落,野人 ...
- 处理php出现default timezone抖动的问题
懒癌发作1年多,再次回来写随笔.(上次是16年,再上次是13年,好像懒的没救了) 这回遇到一个系统前端展现的时间在无规律抖动的问题: 前端php环境是5.3,运行于apache上,php.ini中已经 ...
- Jmeter----A接口response中body的某一个参数传递给B接口request的body中使用(参数的传递)
示例:将接口"获取待办列表"response中body的id值传递给接口"删除待办"request的body中使用: 操作步骤如下: 第一步:给"获取 ...
- sql server 任务调度与CPU
一. 概述 我们知道在操作系统看来, sql server产品与其它应用程序一样,没有特别对待.但内存,硬盘,cpu又是数据库系统最重要的核心资源,所以在sql server 2005及以后出现了SQ ...
- jdk源码阅读笔记-HashMap
文章出处:[noblogs-it技术博客网站]的博客:jdk1.8源码分析 在Java语言中使用的最多的数据结构大概右两种,第一种是数组,比如Array,ArrayList,第二种链表,比如Array ...
- python接口自动化(十三)--cookie绕过验证码登录(详解)
简介 有些登录的接口会有验证码:短信验证码,图形验证码等,这种登录的话验证码参数可以从后台获取的(或者查数据库最直接).获取不到也没关系,可以通过添加cookie的方式绕过验证码.(注意:并不是所有的 ...