詳解spring cloud hystrix 請求合并collapsing
在HystrixCommand之前可以使用請求合并器(HystrixCollapser就是一個(gè)抽象的父類)來把多個(gè)請求合并成一個(gè)然后對后端依賴系統(tǒng)發(fā)起調(diào)用。
下圖顯示了兩種情況下線程的數(shù)量和網(wǎng)絡(luò)的連接數(shù)的情況:第一種是不使用合并器,第二種是使用請求合并器(假設(shè)所有的鏈接都是在一個(gè)短的時(shí)間窗口內(nèi)并行的,比如10ms內(nèi))。

為什么要使用請求合并?
使用請求合并來減少執(zhí)行并發(fā)HystrixCommand執(zhí)行所需的線程數(shù)和網(wǎng)絡(luò)連接數(shù),請求合并是自動(dòng)執(zhí)行的,不會(huì)強(qiáng)制開發(fā)人員手動(dòng)協(xié)調(diào)批處理請求。
全局上下文-global context(跨越所有Tomcat線程)
這種合并類型是在全局應(yīng)用級(jí)別上完成的,因此任何Tomcat線程上的任何用戶的請求都可以一起合并。
例如,如果您配置一個(gè)HystrixCommand支持任何用戶請求依賴關(guān)系來檢索電影評級(jí),那么當(dāng)同一個(gè)JVM中的任何用戶線程發(fā)出這樣的請求時(shí),Hystrix會(huì)將其請求與任何其他請求一起添加到同一個(gè)已折疊網(wǎng)絡(luò)通話。
用戶請求上下文-request context(單個(gè)Tomcat線程)
如果你配置一個(gè)HystrixCommand僅僅為一個(gè)單個(gè)用戶處理批量請求,Hystrix可以在一個(gè)Tomcat線程(請求)中合并請求。
例如,一個(gè)用戶想要加載300個(gè)視頻對象的書簽,不是去執(zhí)行300次網(wǎng)絡(luò)請求,Hystrix能夠?qū)⑺麄兒喜⒊蔀橐粋€(gè)。
Hystrix默認(rèn)是的就是request-scope,要使用request-scoped的功能(request caching,request collapsing, request log)你必須管理HystrixRequestContext的生命周期(或者實(shí)現(xiàn)一個(gè)可替代的HystrixConcurrencyStrategy)
這就意味你在執(zhí)行一個(gè)請求之前需要執(zhí)行以下的代碼:
并且在請求的結(jié)束位置執(zhí)行:
context.shutdown();
在標(biāo)準(zhǔn)的JavaWeb應(yīng)用中,你也可以使用一個(gè)Servlet過濾器來初始化這個(gè)生命周期
public class HystrixRequestContextServletFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
}
}
然后將它配置在web.xml中
<filter> <display-name>HystrixRequestContextServletFilter</display-name> <filter-name>HystrixRequestContextServletFilter</filter-name> <filter-class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter</filter-class> </filter> <filter-mapping> <filter-name>HystrixRequestContextServletFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
如果你是springboot開發(fā)的話代碼如下:
@WebFilter(filterName = "hystrixRequestContextServletFilter",urlPatterns = "/*")
public class HystrixRequestContextServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try{
filterChain.doFilter(servletRequest,servletResponse);
}finally {
context.shutdown();
}
}
@Override
public void destroy() {
}
}
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
//這個(gè)是必須的,否則filter無效
@ServletComponentScan
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
請求合并的成本是多少?
啟用請求合并的成本是在執(zhí)行實(shí)際命令之前的延遲。最大的成本是批處理窗口的大小,默認(rèn)是10ms。
如果你有一個(gè)命令需要花費(fèi)5ms去執(zhí)行并且有一個(gè)10ms的批處理窗口,執(zhí)行的時(shí)間最壞的情況是15ms,一般情況下,請求不會(huì)在批處理窗口剛打開的時(shí)候發(fā)生,所以時(shí)間窗口的中間值是時(shí)間窗口的一半,在這種情況下是5ms。
這個(gè)成本是否值得取決于正在執(zhí)行的命令,高延遲命令不會(huì)受到少量附加平均延遲的影響。而且,給定命令的并發(fā)量也是關(guān)鍵:如果很少有超過1個(gè)或2個(gè)請求被組合在一起,那么這個(gè)成本就是不值得的。事實(shí)上,在一個(gè)單線程的順序迭代請求合并將會(huì)是一個(gè)主要的性能瓶頸,每一次迭代都會(huì)等待10ms的窗口等待時(shí)間。
但是,如果一個(gè)特定的命令同時(shí)被大量使用,并且可以同時(shí)批量打幾十個(gè)甚至幾百個(gè)呼叫,那么成本通常遠(yuǎn)遠(yuǎn)超過所達(dá)到的吞吐量的增加,因?yàn)镠ystrix減少了它所需的線程數(shù)量,依賴。(這段話不太好理解,其實(shí)就是說如果并發(fā)比較高,這個(gè)成本是值得的,因?yàn)閔ystrix可以節(jié)省很多線程和連接資源)。
請求合并的流程(如下圖)

理論知識(shí)已經(jīng)講完了,下面來看看例子,下面的例子集成了eureka+feign+hystrix,完整的例子請查看:https://github.com/jingangwang/micro-service
實(shí)體類
public class User {
private Integer id;
private String username;
private Integer age;
public User() {
}
public User(Integer id, String username, Integer age) {
this.id = id;
this.username = username;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("User{");
sb.append("id=").append(id);
sb.append(", username='").append(username).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
}
服務(wù)提供者代碼
@RestController
@RequestMapping("user")
public class UserController {
@RequestMapping("getUser")
public User getUser(Integer id) {
return new User(id, "test", 29);
}
@RequestMapping("getAllUser")
public List<User> getAllUser(String ids){
String[] split = ids.split(",");
return Arrays.asList(split)
.stream()
.map(id -> new User(Integer.valueOf(id),"test"+id,30))
.collect(Collectors.toList());
}
}
消費(fèi)者代碼
UserFeignClient
@FeignClient(name = "eureka-provider",configuration = FeignConfiguration.class)
public interface UserFeignClient {
/**
* 根據(jù)id查找用戶
* @param id 用戶id
* @return User
*/
@RequestMapping(value = "user/getUser.json",method = RequestMethod.GET)
User findUserById(@RequestParam("id") Integer id);
/**
* 超找用戶列表
* @param ids id列表
* @return 用戶的集合
*/
@RequestMapping(value = "user/getAllUser.json",method = RequestMethod.GET)
List<User> findAllUser(@RequestParam("ids") String ids);
}
UserService(設(shè)置為全局上下文)
@Service
public class UserService {
@Autowired
private UserFeignClient userFeignClient;
/**
* maxRequestsInBatch 該屬性設(shè)置批量處理的最大請求數(shù)量,默認(rèn)值為Integer.MAX_VALUE
* timerDelayInMilliseconds 該屬性設(shè)置多長時(shí)間之內(nèi)算一次批處理,默認(rèn)為10ms
* @param id
* @return
*/
@HystrixCollapser(collapserKey = "findCollapserKey",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,batchMethod = "findAllUser",collapserProperties = {
@HystrixProperty(name = "timerDelayInMilliseconds",value = "5000" ),
@HystrixProperty(name = "maxRequestsInBatch",value = "5" )
})
public Future<User> find(Integer id){
return null;
}
@HystrixCommand(commandKey = "findAllUser")
public List<User> findAllUser(List<Integer> ids){
return userFeignClient.findAllUser(StringUtils.join(ids,","));
}
}
FeignCollapserController
@RequestMapping("user")
@RestController
public class FeignCollapserController {
@Autowired
private UserService userService;
@RequestMapping("findUser")
public User getUser(Integer id) throws ExecutionException, InterruptedException {
return userService.find(id).get();
}
上面的代碼我們這是的是全局上下文(所有tomcat的線程的請求都可以合并),合并的時(shí)間窗口為5s(每一次請求都得等5s才發(fā)起請求),最大合并數(shù)為5。我們在postman中,5s之內(nèi)發(fā)起兩次請求,用戶id不一樣。
localhost:8082/user/findUser.json?id=123189891
localhost:8082/user/findUser.json?id=222222
結(jié)果如下圖所示,兩次請求合并為一次請求批量請求。

我們再來測試一下請求上下文(Request-Scope)的情況,加入上面所提到的HystrixRequestContextServletFilter,并修改UserService
HystrixRequestContextServletFilter
/**
* @author wjg
* @date 2017/12/22 15:15
*/
@WebFilter(filterName = "hystrixRequestContextServletFilter",urlPatterns = "/*")
public class HystrixRequestContextServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try{
filterChain.doFilter(servletRequest,servletResponse);
}finally {
context.shutdown();
}
}
@Override
public void destroy() {
}
}
UserService(設(shè)置為請求上下文)
@Service
public class UserService {
@Autowired
private UserFeignClient userFeignClient;
/**
* maxRequestsInBatch 該屬性設(shè)置批量處理的最大請求數(shù)量,默認(rèn)值為Integer.MAX_VALUE
* timerDelayInMilliseconds 該屬性設(shè)置多長時(shí)間之內(nèi)算一次批處理,默認(rèn)為10ms
* @param id
* @return
*/
@HystrixCollapser(collapserKey = "findCollapserKey",scope = com.netflix.hystrix.HystrixCollapser.Scope.REQUEST,batchMethod = "findAllUser",collapserProperties = {
@HystrixProperty(name = "timerDelayInMilliseconds",value = "5000" ),
@HystrixProperty(name = "maxRequestsInBatch",value = "5" )
})
public Future<User> find(Integer id){
return null;
}
@HystrixCommand(commandKey = "findAllUser")
public List<User> findAllUser(List<Integer> ids){
return userFeignClient.findAllUser(StringUtils.join(ids,","));
}
}
FeignCollapser2Controller
@RequestMapping("user")
@RestController
public class FeignCollapser2Controller {
@Autowired
private UserService userService;
@RequestMapping("findUser2")
public List<User> getUser() throws ExecutionException, InterruptedException {
Future<User> user1 = userService.find(1989);
Future<User> user2= userService.find(1990);
List<User> users = new ArrayList<>();
users.add(user1.get());
users.add(user2.get());
return users;
}
}
我們在postman中輸入:localhost:8082/user/findUser2.json
可以看到一個(gè)請求內(nèi)的兩次連續(xù)調(diào)用被合并了。這個(gè)地方要注意,不能直接使用userServer.find(1989).get(),否則直接按同步執(zhí)行處理,不會(huì)合并。如果兩個(gè)tab頁同時(shí)調(diào)用上述地址,發(fā)現(xiàn)發(fā)起了兩次批量請求,說明作用域是request范圍。
參考資料如下:
https://github.com/Netflix/Hystrix/wiki/How-To-Use
http://www.dbjr.com.cn/article/140530.htm
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用@value注解取不到application.xml配置文件中的值問題
這篇文章主要介紹了使用@value注解取不到application.xml配置文件中的值問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
JAVA中使用openoffice將Excel轉(zhuǎn)PDF再轉(zhuǎn)圖片功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了JAVA中使用openoffice將Excel轉(zhuǎn)PDF再轉(zhuǎn)圖片功能實(shí)現(xiàn),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
初學(xué)者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)
下面小編就為大家?guī)硪黄鯇W(xué)者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10
實(shí)例解析Java設(shè)計(jì)模式編程中的適配器模式使用
本篇文章主要通過實(shí)例對適配器模式進(jìn)行了詳解,需要的朋友可以參考下2017-04-04
springboot配置請求超時(shí)時(shí)間(Http會(huì)話和接口訪問)
本文主要介紹了springboot配置請求超時(shí)時(shí)間,包含Http會(huì)話和接口訪問兩種,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07

