本次和大家分享的是怎麼來消費服務,上篇文章講了使用Feign來消費,本篇來使用rest+ribbon消費服務,並且通過輪詢方式來自定義了個簡易消費元件,本文分享的宗旨是:自定義消費服務的思路;思路如果有可取之處還請“贊”一下:
- Rest+Ribbon實現消費服務
- Rest+輪詢自定義簡易消費元件
- 使用Scheduled重新整理服務提供者資訊
Rest+Ribbon實現消費服務
做為服務消費方準確的來說進行了兩種主流程區分1)獲取可以服務2)呼叫服務,那麼又是如何獲取服務的並且又是通過什麼來呼叫服務的,下面我們來看一副手工圖:
手工圖上能夠看出消費方先獲取了服務方的真實介面地址,然後再通過地址去呼叫介面;然後對於微服務架構來說獲取某一個類ip或埠然後去呼叫介面肯定是不可取的,因此微服務中產生了一種serviceid的概念;簡單流程介紹完了,下面通過例項來分析;首先新增依賴如:
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-web</artifactId> 4 </dependency> 5 <dependency> 6 <groupId>org.springframework.cloud</groupId> 7 <artifactId>spring-cloud-starter-eureka</artifactId> 8 </dependency>
再來我們通過上篇文章搭建的eureka_server(服務中心),eureka_provider(服務提供者)來做測試用例,這裡我重新定義eureka_consumer_ribbon模組做為消費服務;先建立service層類和程式碼:
1 @Service 2 public class UserService implements UserInterface { 3 4 @Autowired 5 protected RestTemplate restTemplate; 6 7 @Override 8 public MoRp<List<MoUser>> getUsers(MoRq rq) { 9 return null; 10 } 11 12 @Override 13 public String getMsg() { 14 15 String str = restTemplate.getForObject("http://EUREKA-PROVIDER/msg", String.class); 16 return str; 17 } 18 }
主要用到了RestTemplate的restTemplate.getForObject函式,然後需要定義個Controller來吧獲取到的資料響應到頁面上,為了簡單這裡僅僅只拿getMsg服務介面測試:
1 @RestController 2 public class UserController { 3 4 @Autowired 5 private UserService userService; 6 7 @GetMapping("/msg") 8 public String getMsg(){ 9 10 return userService.getMsg(); 11 } 12 }
最後我們在啟動類新增入下程式碼,注意@LoadBalanced標記必須加,因為咋們引入的eureka依賴裡面包含了ribbon(Dalston.RELEASE版本),ribbon封裝了負載均衡的演算法,如果不加這個註解,那後面rest方法的url就必須是可用的url路徑了,當然這裡加了註解就可以使用上面說的serviceId:
1 @SpringBootApplication 2 @EnableDiscoveryClient //消費客戶端 3 public class EurekaConsumerRibbonApplication { 4 5 @Bean 6 @LoadBalanced //負載均衡 7 RestTemplate restTemplate(){ 8 return new RestTemplate(); 9 } 10 11 public static void main(String[] args) { 12 SpringApplication.run(EurekaConsumerRibbonApplication.class, args); 13 } 14 }
下面來消費方顯示的效果:
Rest+輪詢自定義簡易消費元件
自定義消費元件原來和麵手工圖差不多,就是先想法獲取服務提供端真實的介面地址,然後通過rest去呼叫這個url,得到相應的結果輸出;這裡自定義了一個ShenniuBanlance的元件類:
1 /** 2 * Created by shenniu on 2018/6 3 * <p> 4 * rest+eureka+自定義client端 5 */ 6 @Component 7 public class ShenniuBanlance { 8 9 @Autowired 10 private RestTemplate restTemplate; 11 12 @Autowired 13 private DiscoveryClient discoveryClient; 14 15 /** 16 * 服務真實地址 ConcurrentHashMap<"服務應用名稱", ("真實介面ip", 被訪問次數)> 17 */ 18 public static ConcurrentHashMap<String, List<MoService>> sericesMap = new ConcurrentHashMap<>(); 19 20 /** 21 * 設定服務提供者資訊到map 22 */ 23 public void setServicesMap() { 24 //獲取所有服務提供者applicationName 25 List<String> appNames = discoveryClient.getServices(); 26 27 //儲存真實地址到map 28 for (String appName : 29 appNames) { 30 //獲取某個服務提供者資訊 31 List<ServiceInstance> instanceInfos = discoveryClient.getInstances(appName); 32 if (instanceInfos.isEmpty()) { 33 continue; 34 } 35 36 List<MoService> services = new ArrayList<>(); 37 instanceInfos.forEach(b -> { 38 MoService service = new MoService(); 39 //被訪問次數 40 service.setWatch(0L); 41 //真實介面地址 42 service.setUrl(b.getUri().toString()); 43 services.add(service); 44 }); 45 46 //如果存在就更新 47 sericesMap.put(appName.toLowerCase(), services); 48 } 49 } 50 51 /** 52 * 根據app獲取輪詢方式選中後的service 53 * 54 * @param appName 55 * @return 56 */ 57 public MoService choiceServiceByAppName(String appName) throws Exception { 58 appName = appName.toLowerCase(); 59 //某種app的服務service集合 60 List<MoService> serviceMap = sericesMap.get(appName); 61 if (serviceMap == null) { 62 //初始化所有app服務 63 setServicesMap(); 64 serviceMap = sericesMap.get(appName); 65 if (serviceMap == null) { 66 throw new Exception("未能找到" + appName + "相關服務"); 67 } 68 } 69 70 //篩選出被訪問量最小的service 輪詢的方式 71 MoService moService = serviceMap.stream().min( 72 Comparator.comparing(MoService::getWatch) 73 ).get(); 74 75 //負載記錄+1 76 moService.setWatch(moService.getWatch() + 1); 77 return moService; 78 } 79 80 /** 81 * 自動重新整理 服務提供者資訊到map 82 */ 83 @Scheduled(fixedDelay = 1000 * 10) 84 public void refreshServicesMap() { 85 setServicesMap(); 86 } 87 88 /** 89 * get請求服務獲取返回資料 90 * 91 * @param appName 應用名稱 ApplicationName 92 * @param serviceName 服務名稱 ServiceName 93 * @param map url上請求引數 94 * @param tClass 返回型別 95 * @param <T> 96 * @return 97 */ 98 public <T> T getServiceData( 99 String appName, String serviceName, 100 Map<String, ?> map, 101 Class<T> tClass) { 102 T result = null; 103 try { 104 //篩選獲取真實Service 105 MoService service = choiceServiceByAppName(appName); 106 107 //請求該service的url 108 String apiUrl = service.getUrl() + "/" + serviceName; 109 System.out.println(apiUrl); 110 result = map != null ? 111 restTemplate.getForObject(apiUrl, tClass, map) : 112 restTemplate.getForObject(apiUrl, tClass); 113 } catch (Exception ex) { 114 ex.printStackTrace(); 115 } 116 return result; 117 } 118 119 /** 120 * Service資訊 121 */ 122 public class MoService { 123 /** 124 * 負載次數記錄數 125 */ 126 private Long watch; 127 /** 128 * 真實介面地址: http://xxx.com/api/add 129 */ 130 private String url; 131 132 public Long getWatch() { 133 return watch; 134 } 135 136 public void setWatch(Long watch) { 137 this.watch = watch; 138 } 139 140 public String getUrl() { 141 return url; 142 } 143 144 public void setUrl(String url) { 145 this.url = url; 146 } 147 } 148 }
以上就是主要的實現程式碼,程式碼邏輯:設定服務提供者資訊到map-》根據app獲取輪詢方式選中後的service-》請求服務獲取返回資料;輪詢實現的原理是使用了一個負載記錄數,每次被請求後自動+1,當要獲取某個服務提供者時,通過記錄數篩選出最小值的一個例項,裡面儲存有真實介面地址url;呼叫只需要這樣(當然可以弄成註解來呼叫):
1 @Override 2 public String getMsg() { 3 4 String str = banlance.getServiceData( 5 "EUREKA-PROVIDER", "msg", 6 null, 7 String.class 8 ); 9 return str; 10 }
這裡需要注意由於我們在前面RestTemplate使用加入了註解@LoadBalanced,這樣使得rest請求時必須用非ip的訪問方式(也就是必須serviceid)才能正常響應,不然會提示錯誤如:
簡單來說就是不用再使用ip了,因為有負載均衡機制;當我們去掉這個註解後,我們自定義的元件就能執行成功,效果圖和例項1一樣就不貼圖了;
使用Scheduled重新整理服務提供者資訊
在微服務架構中,如果某臺服務掛了之後,必須要及時更新client端的服務快取資訊,不然就可能請求到down的url去,基於這種考慮我這裡採用了EnableSched標記來做定時重新整理;首先在啟動類增加 @EnableScheduling ,然後定義一個刷行服務資訊的服務如:
1 /** 2 * 自動重新整理 服務提供者資訊到map 3 */ 4 @Scheduled(fixedDelay = 1000 * 10) 5 public void refreshServicesMap() { 6 setServicesMap(); 7 }
為了方便看測試效果,我們在server,provider(2個),consumer已經啟動的情況下,再啟動一個埠為2005的provider服務;然後重新整理consumer介面看下效果:
這個時候能夠看到呼叫2005埠的介面成功了,通過@Scheduled定時服務吧最新或者失效的服務加入|移除掉,就達到了咋們的需求了;如果你覺得該篇內容對你有幫助,不防贊一下,謝謝。