Hystrix 请求合并
- Hystrix 请求合并,是把接口相同、参数不同的请求封装成一个请求,比如 A 、B 、C 三个请求调用一个接口,相隔时间 200 毫秒,那完全可一起发送,提供发送效率
(1)请求命令方式
- 先在 provider 中提供一个接口:
@RestController
public class UserController {
@GetMapping("/user/{ids}")
public List<User> getUserByIds(@PathVariable String ids){
System.out.println(ids);
String[] split = ids.split(",");
List<User> users = new ArrayList<>();
for (int i = 0; i < split.length; i++) {
User u = new User();
u.setId(Integer.parseInt(split[i]));
users.add(u);
}
return users;
}
}
- 这个接口,可以处理多个请求,也可以处理一个请求(List 中只有一个 User 对象)
- 定义
UserService
,构造方法封装请求需要的内容
@Service
public class UserService {
@Autowired
RestTemplate restTemplate;
public List<User> getUserByIds(List<Integer> ids){
String idsStr = StringUtils.join(ids, ",");
System.out.println(idsStr);
User[] forObject = restTemplate.getForObject("http://provider/user/{1}", User[].class, idsStr);
return Arrays.asList(forObject);
}
}
- 定义普通的请求命令类
UserBatchCommand
/*请求命令的command*/
public class UserBatchCommand extends HystrixCommand<List<User>> {
private List<Integer> ids;
private UserService userService;
public UserBatchCommand( List<Integer> ids, UserService userService) {
super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("batchCommand")).
andCommandKey(HystrixCommandKey.Factory.asKey("batchKey")));
this.ids = ids;
this.userService = userService;
}
@Override
protected List<User> run() throws Exception {
return userService.getUserByIds(ids);
}
}
- 到这里只是,普通的请求命令方式,接下来定义请求和并的
UserCollapseCommand
public class UserCollapseCommand extends HystrixCollapser<List<User>, User, Integer> {
private UserService userService;
private Integer id;
public UserCollapseCommand( UserService userService, Integer id) {
super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("userCollapseCommand"))
.andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(200)));
this.userService = userService;
this.id = id;
}
/*请求中的 id 参数*/
@Override
public Integer getRequestArgument() {
return id;
}
/*请求合并方法*/
@Override
protected HystrixCommand<List<User>> createCommand(Collection<CollapsedRequest<User, Integer>> collection) {
List<Integer> ids = new ArrayList<>(collection.size());
for (CollapsedRequest<User, Integer> userIntegerCollapsedRequest : collection) {
ids.add(userIntegerCollapsedRequest.getArgument());
}
return new UserBatchCommand(ids,userService);
}
/*请求结果分发*/
@Override
protected void mapResponseToRequests(List<User> users, Collection<CollapsedRequest<User, Integer>> collection) {
int count = 0;
for (CollapsedRequest<User, Integer> request : collection) {
request.setResponse(users.get(count++));
}
}
}
- Setter 中定义
withTimerDelayInMilliseconds(200)
,意思是相隔 200 毫秒内的请求,如果请求的接口相同,那么可以一起发送,然后等 200 毫秒,如果还有相同的接口,那么合并稍后一起发送,然后再等 200 毫秒,依此类推,如果 200 毫秒内没有相同请求,那么就合并一起发送 - 继承类的参数
HystrixCollapser<BatchReturnType, ResponseType, RequestArgumentType>
,BatchReturnType 是 provider 中接口的返回类型,ResponseType 是每个请求没有合并前,单独返回给前台的数据类型,RequestArgumentType 是每个请求的的参数类型 createCommand
是请求合并的方法,返回的HystrixCommand
类型,就是普通请求命令方式的类,Collection<CollapsedRequest<User, Integer>>
封装的是每个请求,从请求中处理参数mapResponseToRequests
是请求结果分发的方法,Collection<CollapsedRequest<User, Integer>>
封装的是每个请求,把请求的结果分发给每个请求- 定义调用方法,注意要用 HystrixRequestContext 的 initializeContext() 、close()方法包围起来
@GetMapping("/hello5")
public void hello5() throws ExecutionException, InterruptedException {
HystrixRequestContext cxt = HystrixRequestContext.initializeContext();
UserCollapseCommand command1 = new UserCollapseCommand(userService, 99);
UserCollapseCommand command2 = new UserCollapseCommand(userService, 98);
UserCollapseCommand command3 = new UserCollapseCommand(userService, 97);
Future<User> q1 = command1.queue();
Future<User> q2 = command2.queue();
Future<User> q3 = command3.queue();
User u1 = q1.get();
User u2 = q2.get();
User u3 = q3.get();
Thread.sleep(2000);
UserCollapseCommand command4 = new UserCollapseCommand(userService, 96);
Future<User> q4 = command4.queue();
User u4 = q4.get();
System.out.println(u1);
System.out.println(u2);
System.out.println(u3);
System.out.println(u4);
cxt.close();
}
-
启动服务
-
调用 hello5 请求,,查看 provider 中的日志:
-
前三次的请求被封装成一个请求,请求了一次,而后线程暂停了 2 秒,第 4 个请求独立发送,一共请求了两次。
(2)注解方式
- 注解方式真简单,定义 getUserById 方法
@Service
public class UserService {
@Autowired
RestTemplate restTemplate;
@HystrixCollapser(batchMethod = "getUserByIds",collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value = "200")})
public Future<User> getUserById(Integer id){
return null;
}
@HystrixCommand
public List<User> getUserByIds(List<Integer> ids){
String idsStr = StringUtils.join(ids, ",");
System.out.println(idsStr);
User[] forObject = restTemplate.getForObject("http://provider/user/{1}", User[].class, idsStr);
return Arrays.asList(forObject);
}
}
- 注意返回值是
Future<User>
,参考请求命令方式调用的queue()
入队方法;
@HystrixCollapser(batchMethod = "getUserByIds",collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value = "200")})
的意思是把调用这个方法的请求都合并一下,合并完调用getUserByIds
方法,请求等待的是时间间隔 200 毫秒,getUserByIds
是我们定义一般调用接口的方式,即调用 provider 服务的方式,这个方法上要加上@HystrixCommand
注解 - 定义出发注解来处理请求合并的请求 hello6 ,注意要用 HystrixRequestContext 的 initializeContext() 、close()方法包围起来
@GetMapping("hello6")
public void hello6() throws ExecutionException, InterruptedException {
HystrixRequestContext cxt = HystrixRequestContext.initializeContext();
Future<User> q1 = userService.getUserById(99);
Future<User> q2 = userService.getUserById(98);
Future<User> q3 = userService.getUserById(97);
User u1 = q1.get();
User u2 = q2.get();
User u3 = q3.get();
Thread.sleep(2000);
Future<User> q4 = userService.getUserById(96);
User u4 = q4.get();
System.out.println(u1);
System.out.println(u2);
System.out.println(u3);
System.out.println(u4);
cxt.close();
}
- 重启服务,访问 hello6 接口,查看 provider 服务的控制台日志
- 与请求命令的结果一样
总结一下
至此,两种方式实现请求合并就完成了,推荐使用注解方式,因为目前使用来看,注解的代码量少,功能比请求命令要完善,强大。推荐优先使用注解方式