前言:
在高并发的场景下,Spring Cloud通过Hystrix的请求缓存和请求合并来减轻高并发时的请求线程消耗、降低请求响应时间的效果。
这里以注解的方式进行请求合并的演示。
请求合并:将多个单个请求合并成一个请求,去调用服务提供者提供的服务接口,再遍历合并的结果为每个合并前的单个请求设置返回结果。
进行演示的前提是已经具备了注册中心eureka-server、服务提供者hello-service、服务消费者ribbon-consume。
一、在服务提供者新增一个UserController,实现/getUser/{id}接口和/getUsers接口。
/**
* @author HDN
* @date 2019/6/22 10:36
*/
@RestController
public class UserController {
private Logger logger = Logger.getLogger(String.valueOf(UserController.class));
//根据id获得单个User
@RequestMapping(value = "/getUser/{id}")
public User getUserById(@PathVariable String id){
logger.info("getUserById,它的id:"+id);
return new User(id,"hdn",22);
}
//获得多个user
@RequestMapping(value = "/getUsers")
public List<User> getUsers(@RequestParam("ids") List<String> ids){
logger.info("getUsers,它的ids:"+ids);
List<User> users = new ArrayList<>();
User user = null;
for(String id:ids){
logger.info("getUsers,它的id:"+id);
user = new User(id,"hdn",24);
users.add(user);
}
return users;
}
}
二、在服务消费者中新增UserService,添加find方法(请求合并)和findAll方法(合并之后调用的):
/**
* @author HDN
* @date 2019/6/22 10:50
*/
@Service
public class UserService {
private Logger logger = Logger.getLogger(String.valueOf(UserService.class));
@Autowired
RestTemplate restTemplate;
//请求合并的方法
@HystrixCollapser(batchMethod = "findAll",collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "3000")})
public Future<User> find(String id){
//这里本应该访问服务提供者提供的/getUser/{id}接口
logger.info("======执行了find方法========");
return null;
}
//合并请求之后调用的方法
@HystrixCommand
public List<User> findAll(List<String> ids){
logger.info("======执行了findAll方法========"+ids);
return restTemplate.getForObject("http://hello-service/getUsers?ids={1}", List.class, StringUtils.join(ids, ","));
}
}
三、在服务消费者中新增UserController,实现/findUser/{id}接口:
/**
* @author HDN
* @date 2019/6/22 11:08
*/
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping(value = "/findUser/{id}")
public String findUser(@PathVariable String id) throws ExecutionException, InterruptedException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
Future<User> user1 = userService.find(id);
Future<User> defaultUser = userService.find("888");
System.out.println(user1.get());
System.out.println(defaultUser.get());
context.close();
return "SUCCESS";
}
}
测试1:访问http://localhost:9000/findUser/1,查看ribbon-consume控制台,可以根本没有进入到find方法体,把/findUser/{id}中两次访问find的请求合并为一个服务调用请求去访问findAll方法。
测试2:在3000毫秒之内访问http://localhost:9000/findUser/1和http://localhost:9000/findUser/2,相当于有4个请求,来看控制台的结果,这里并没有将4个服务调用全部进行合并,而是根据不同线程的服务调用进行合并的:
这里,可以通过设置@HystrixCollapser注解的scope属性来将两次客户端的请求合并为一次服务调用:
- scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL:将所有线程中多次服务调用进行合并
- scope = com.netflix.hystrix.HystrixCollapser.Scope.REQUEST:对一次请求的多次服务调用进行合并
四、将@HystrixCollapser注解设置scope属性为GLOBAL,再次测试测试2的情况:
/**
* @author HDN
* @date 2019/6/22 10:50
*/
@Service
public class UserService {
private Logger logger = Logger.getLogger(String.valueOf(UserService.class));
@Autowired
RestTemplate restTemplate;
//请求合并的方法
@HystrixCollapser(batchMethod = "findAll",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "3000")})
//@HystrixCollapser(batchMethod = "findAll",collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "3000")})
public Future<User> find(String id){
//这里本应该访问服务提供者提供的/getUser/{id}接口
logger.info("======执行了find方法========");
return null;
}
//合并请求之后调用的方法
@HystrixCommand
public List<User> findAll(List<String> ids){
logger.info("======执行了findAll方法========"+ids);
return restTemplate.getForObject("http://hello-service/getUsers?ids={1}", List.class, StringUtils.join(ids, ","));
}
}
测试3:在3000毫秒之内访问http://localhost:9000/findUser/1和http://localhost:9000/findUser/2,相当于有4个请求,来看控制台的结果,这里将所有的服务调用都进行了合并:
---------------------------------------------------总结---------------------------------------------------------
- UserService中单个请求的方法要返回Future包装的对象,实现异步请求。如果返回User对象,这个是同步请求,不会进行请求合并。
- 通过@HystrixCollapser注解的scope属性合并请求的作用于。