java多线程合并请求_详解spring cloud hystrix 请求合并collapsing

在HystrixCommand之前可以使用请求合并器(HystrixCollapser就是一个抽象的父类)来把多个请求合并成一个然后对后端依赖系统发起调用。

下图显示了两种情况下线程的数量和网络的连接数的情况:第一种是不使用合并器,第二种是使用请求合并器(假设所有的链接都是在一个短的时间窗口内并行的,比如10ms内)。

d84d5ce6380e3af9745ce67de53a58bd.png

为什么要使用请求合并?

使用请求合并来减少执行并发HystrixCommand执行所需的线程数和网络连接数,请求合并是自动执行的,不会强制开发人员手动协调批处理请求。

全局上下文-global context(跨越所有Tomcat线程)

这种合并类型是在全局应用级别上完成的,因此任何Tomcat线程上的任何用户的请求都可以一起合并。

例如,如果您配置一个HystrixCommand支持任何用户请求依赖关系来检索电影评级,那么当同一个JVM中的任何用户线程发出这样的请求时,Hystrix会将其请求与任何其他请求一起添加到同一个已折叠网络通话。

用户请求上下文-request context(单个Tomcat线程)

如果你配置一个HystrixCommand仅仅为一个单个用户处理批量请求,Hystrix可以在一个Tomcat线程(请求)中合并请求。

例如,一个用户想要加载300个视频对象的书签,不是去执行300次网络请求,Hystrix能够将他们合并成为一个。

Hystrix默认是的就是request-scope,要使用request-scoped的功能(request caching,request collapsing, request log)你必须管理HystrixRequestContext的生命周期(或者实现一个可替代的HystrixConcurrencyStrategy)

这就意味你在执行一个请求之前需要执行以下的代码:

HystrixRequestContext  context=HystrixRequestContext.initializeContext();

并且在请求的结束位置执行:

context.shutdown();

在标准的JavaWeb应用中,你也可以使用一个Servlet过滤器来初始化这个生命周期

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中

HystrixRequestContextServletFilter

HystrixRequestContextServletFilter

com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter

HystrixRequestContextServletFilter

/*

如果你是springboot开发的话代码如下:

@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

//这个是必须的,否则filter无效

@ServletComponentScan

public class Application {

public static void main(String[] args) {

new SpringApplicationBuilder(Application.class).web(true).run(args);

}

}

请求合并的成本是多少?

启用请求合并的成本是在执行实际命令之前的延迟。最大的成本是批处理窗口的大小,默认是10ms。

如果你有一个命令需要花费5ms去执行并且有一个10ms的批处理窗口,执行的时间最坏的情况是15ms,一般情况下,请求不会在批处理窗口刚打开的时候发生,所以时间窗口的中间值是时间窗口的一半,在这种情况下是5ms。

这个成本是否值得取决于正在执行的命令,高延迟命令不会受到少量附加平均延迟的影响。而且,给定命令的并发量也是关键:如果很少有超过1个或2个请求被组合在一起,那么这个成本就是不值得的。事实上,在一个单线程的顺序迭代请求合并将会是一个主要的性能瓶颈,每一次迭代都会等待10ms的窗口等待时间。

但是,如果一个特定的命令同时被大量使用,并且可以同时批量打几十个甚至几百个呼叫,那么成本通常远远超过所达到的吞吐量的增加,因为Hystrix减少了它所需的线程数量,依赖。(这段话不太好理解,其实就是说如果并发比较高,这个成本是值得的,因为hystrix可以节省很多线程和连接资源)。

请求合并的流程(如下图)

87febeca97ed601e0ca9922b1dcb1ff7.png

理论知识已经讲完了,下面来看看例子,下面的例子集成了eureka+feign+hystrix,完整的例子请查看:https://github.com/jingangwang/micro-service

实体类

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();

}

}

服务提供者代码

@RestController

@RequestMapping("user")

public class UserController {

@RequestMapping("getUser")

public User getUser(Integer id) {

return new User(id, "test", 29);

}

@RequestMapping("getAllUser")

public List 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());

}

}

消费者代码

UserFeignClient

@FeignClient(name = "eureka-provider",configuration = FeignConfiguration.class)

public interface UserFeignClient {

/**

* 根据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 findAllUser(@RequestParam("ids") String ids);

}

UserService(设置为全局上下文)

@Service

public class UserService {

@Autowired

private UserFeignClient userFeignClient;

/**

* maxRequestsInBatch 该属性设置批量处理的最大请求数量,默认值为Integer.MAX_VALUE

* timerDelayInMilliseconds 该属性设置多长时间之内算一次批处理,默认为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 find(Integer id){

return null;

}

@HystrixCommand(commandKey = "findAllUser")

public List findAllUser(List 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的线程的请求都可以合并),合并的时间窗口为5s(每一次请求都得等5s才发起请求),最大合并数为5。我们在postman中,5s之内发起两次请求,用户id不一样。

localhost:8082/user/findUser.json?id=123189891

localhost:8082/user/findUser.json?id=222222

结果如下图所示,两次请求合并为一次请求批量请求。

b792f21ba2a6d0b5854b83b34023ca5c.png

我们再来测试一下请求上下文(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(设置为请求上下文)

@Service

public class UserService {

@Autowired

private UserFeignClient userFeignClient;

/**

* maxRequestsInBatch 该属性设置批量处理的最大请求数量,默认值为Integer.MAX_VALUE

* timerDelayInMilliseconds 该属性设置多长时间之内算一次批处理,默认为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 find(Integer id){

return null;

}

@HystrixCommand(commandKey = "findAllUser")

public List findAllUser(List ids){

return userFeignClient.findAllUser(StringUtils.join(ids,","));

}

}

FeignCollapser2Controller

@RequestMapping("user")

@RestController

public class FeignCollapser2Controller {

@Autowired

private UserService userService;

@RequestMapping("findUser2")

public List getUser() throws ExecutionException, InterruptedException {

Future user1 = userService.find(1989);

Future user2= userService.find(1990);

List users = new ArrayList<>();

users.add(user1.get());

users.add(user2.get());

return users;

}

}

我们在postman中输入:localhost:8082/user/findUser2.json

ce50d2f471bfffe497a5b18be3075954.png

可以看到一个请求内的两次连续调用被合并了。这个地方要注意,不能直接使用userServer.find(1989).get(),否则直接按同步执行处理,不会合并。如果两个tab页同时调用上述地址,发现发起了两次批量请求,说明作用域是request范围。

参考资料如下:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud Hystrix是一种用于构建容错和弹性系统的开源库,它允许开发者在分布式系统中处理延迟和故障,并在这些系统之间提供后备选项。Hystrix的原理可以概括为以下几点。 首先,Hystrix通过使用线程池隔离和信号量隔离的方式,为每个依赖服务创建了一个独立的执行环境。这意味着每个服务的执行都是在独立的线程或信号量中完成的,这样可以防止由于依赖服务的故障而导致整个系统崩溃。 其次,Hystrix使用了断路器模式来防止故障的扩散。当一个依赖服务的失败率超过预定义的阈值时,断路器会打开,进而停止请求该服务,而是直接返回预定义的fallback值。这个过程可以有效地保护系统免受失败服务的影响,并且在服务恢复正常后,断路器会逐渐闭合,重新允许对该服务的请求。 另外,Hystrix还提供了实时监控和报告功能。通过使用Hystrix Dashboard,开发者可以实时监控每个依赖服务的运行状态,包括请求的成功率、失败率、响应时间等指标,以及断路器的状态。这对于故障诊断和性能优化非常有帮助。 最后,Hystrix还支持请求缓存和请求合并等功能,以进一步提升系统的性能和容错能力。通过对重复的请求进行合并和缓存,可以减少网络开销和服务的负载,同时也减少了对依赖服务的请求次数,降低了故障的风险。 总的来说,Spring Cloud Hystrix通过使用断路器、隔离和容错等机制,能够保护系统免受依赖服务的故障影响,并提供实时监控和报告,以及其他增强性能的功能,从而提高了分布式系统的可靠性和弹性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值