一、請求合併適用的場景
在服務提供者提供了返回單個對象和多個對象的查詢接口,並且單個對象的查詢併發數很高,服務提供者負載較高的時候,我們就可以使用請求合併來降低服務提供者的負載。
實現請求合併
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;
}