一、请求合并适用的场景

在服务提供者提供了返回单个对象和多个对象的查询接口,并且单个对象的查询并发数很高,服务提供者负载较高的时候,我们就可以使用请求合并来降低服务提供者的负载。
实现请求合并
1、传统方式
首先在服务提供者的GetRequestController中添加两个接口,用于打印是哪个方法被调用

/**     * 为Hystrix请求合并提供的接口     */
    @GetMapping("/users/{id}")    public User getUserById(@PathVariable Long id){
        logger.info("=========getUserById方法:入参ids:"+id);        return new User("one"+id, "女", "110-"+id);
    }    @GetMapping("/users")    public List<User> getUsersByIds(@RequestParam("ids") List<Long> ids){
        List<User> userList = new ArrayList<>();
        User user;
        logger.info("=========getUsersByIds方法:入参ids:"+ids);        for(Long id : ids){
            user = new User("person"+id ,"男","123-"+id);
            userList.add(user);
        }
        System.out.println(userList);        return userList;
    }

在消费者(RibbonConsumHystrix)项目中的RibbonController中实现简单的调用上边的两个接口

/**     * 单个请求处理     * @param id     */
    @GetMapping("/users/{id}")    public User findOne(@PathVariable Long id){
        LOGGER.debug("=============/hystrix/users/{} 执行了", id);
        User user = service.findOne(id);        return user;
    }    /**     * 多个请求处理     * @param ids id串,使用逗号分隔     */
    @GetMapping("/users")    public List<User> findAll(@RequestParam List<Long> ids){
        LOGGER.debug("=============/hystrix/users?ids={} 执行了", ids);        return service.findAll(ids);
    }

扩充RibbonService,添加两个方法,分别调用上述两个接口,主要是为了分层明确

/**请求合并使用到的测试方法**/

    /**     * 查一个User对象     */
    public User findOne(Long id){
        LOGGER.info("findOne方法执行了,id= "+id);        return restTemplate.getForObject("http://eureka-service/users/{1}", User.class, id);
    }    /**     * 查多个对象     *     * 注意: 这里用的是数组,作为结果的接收,因为restTemplate.getForObject方法在这里受限     *         如果尽如《SpringCloud微服务实战》一书中指定类型为List.class,会返回一个List<LinkedHashMap>类型的集合     *         为了避坑这里我们使用数组的方式接收结果     */
    public List<User> findAll(List<Long> ids){
        LOGGER.info("findAll方法执行了,ids= "+ids);
        User[] users = restTemplate.getForObject("http://eureka-service/users?ids={1}", User[].class, StringUtils.join(ids, ","));        return Arrays.asList(users);
    }

我们还需要一个将请求合并的类,在hystrix包下创建UserCollapseCommand

package com.cnblogs.hellxz.hystrix;import com.cnblogs.hellxz.entity.User;import com.cnblogs.hellxz.servcie.RibbonService;import com.netflix.hystrix.HystrixCollapser;import com.netflix.hystrix.HystrixCollapserProperties;import com.netflix.hystrix.HystrixCommand;import java.util.ArrayList;import java.util.Collection;import java.util.List;import java.util.stream.Collectors;//注意这个asKey方法不是HystrixCommandKey.Factory.asKeyimport static com.netflix.hystrix.HystrixCollapserKey.Factory.asKey;/** * @Author : Hellxz * @Description: 继承HystrixCollapser的请求合并器 * @Date : 2018/5/5 11:42 */public class UserCollapseCommand extends HystrixCollapser<List<User>,User,Long> {    private RibbonService service;    private Long userId;    /**     * 构造方法,主要用来设置这个合并器的时间,意为每多少毫秒就会合并一次     * @param ribbonService 调用的服务     * @param userId 单个请求传入的参数     */
    public UserCollapseCommand(RibbonService ribbonService, Long userId){        super(Setter.withCollapserKey(asKey("userCollapseCommand")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(100)));        this.service = ribbonService;        this.userId = userId;
    }    /**     * 获取请求中的参数     */
    @Override
    public Long getRequestArgument() {        return userId;
    }    /**     * 创建命令,执行批量操作     */
    @Override
    public HystrixCommand<List<User>> createCommand(Collection<CollapsedRequest<User, Long>> collapsedRequests) {        //按请求数声名UserId的集合
        List<Long> userIds = new ArrayList<>(collapsedRequests.size());        //通过请求将100毫秒中的请求参数取出来装进集合中
        userIds.addAll(collapsedRequests.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList()));        //返回UserBatchCommand对象,自动执行UserBatchCommand的run方法
        return new UserBatchCommand(service, userIds);
    }    /**     * 将返回的结果匹配回请求中     * @param batchResponse 批量操作的结果     * @param collapsedRequests 合在一起的请求     */
    @Override
    protected void mapResponseToRequests(List<User> batchResponse, Collection<CollapsedRequest<User, Long>> collapsedRequests) {        int count = 0 ;        for(CollapsedRequest<User,Long> collapsedRequest : collapsedRequests){            //从批响应集合中按顺序取出结果
            User user = batchResponse.get(count++);            //将结果放回原Request的响应体内
            collapsedRequest.setResponse(user);
        }
    }
}

其中将多个参数封装成一个List,将参数交给UserBatchCommand类执行

创建测试接口:
在这里用了类比方法,分别是同步方法和异步方法

/**     * 合并请求测试     * 说明:这个测试本应在findOne方法中new一个UserCollapseCommand对象进行测试     *         苦于没有好的办法做并发实验,这里就放在一个Controller中了     *         我们看到,在这个方法中用了三个UserCollapseCommand对象进行模拟高并发     */
    @GetMapping("/collapse")    public List<User> collapseTest(){
        LOGGER.info("==========>collapseTest方法执行了");
        List<User> userList = new ArrayList<>();
        Future<User> queue1 = new UserCollapseCommand(service, 1L).queue();
        Future<User> queue2 = new UserCollapseCommand(service, 2L).queue();
        Future<User> queue3 = new UserCollapseCommand(service, 3L).queue();        try {
            User user1 = queue1.get();
            User user2 = queue2.get();
            User user3 = queue3.get();
            userList.add(user1);
            userList.add(user2);
            userList.add(user3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }        return userList;
    }    /**     * 同步方法测试合并请求     *     * 说明:这个方法是用来与上面的方法做类比的,通过这个实验我们发现如果使用同步方法,     *         那么这个请求合并的作用就没有了,这会给findAll方法造成性能浪费     */
    @GetMapping("synccollapse")    public List<User> syncCollapseTest(){
        LOGGER.info("==========>syncCollapseTest方法执行了");
        List<User> userList = new ArrayList<>();
        User user1 = new UserCollapseCommand(service, 1L).execute();
        User user2 = new UserCollapseCommand(service, 2L).execute();
        User user3 = new UserCollapseCommand(service, 3L).execute();
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);        return userList;
    }

2、注解方式
扩充RibbonService

/**注解方式实现请求合并**/

    /**     * 被合并请求的方法     * 注意是timerDelayInMilliseconds,注意拼写     */
    @HystrixCollapser(batchMethod = "findAllByAnnotation",collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "100")})    public Future<User> findOneByAnnotation(Long id){        //你会发现根本不会进入这个方法体
        LOGGER.info("findOne方法执行了,ids= "+id);        return null;
    }    /**     * 真正执行的方法     */
    @HystrixCommand
    public List<User> findAllByAnnotation(List<Long> ids){
        LOGGER.info("findAll方法执行了,ids= "+ids);
        User[] users = restTemplate.getForObject("http://eureka-service/users?ids={1}", User[].class, StringUtils.join(ids, ","));        return Arrays.asList(users);
    }

扩充RibbonController调用findOneByAnnotation()

@GetMapping("/collapsebyannotation/{id}")    public User collapseByAnnotation(@PathVariable Long id) throws ExecutionException, InterruptedException {
        Future<User> one = service.findOneByAnnotation(id);
        User user = one.get();      
          return user;
    }