本次和大家分享的是怎麼來消費服務,上篇文章講了使用Feign來消費,本篇來使用rest+ribbon消費服務,並且通過輪詢方式來自定義了個簡易消費組件,本文分享的宗旨是:自定義消費服務的思路;思路如果有可取之處還請“贊”一下:
Rest+Ribbon實現消費服務
做為服務消費方准確的來說進行了兩種主流程區分1)獲取可以服務2)調用服務,那麼又是如何獲取服務的並且又是通過什麼來調用服務的,下面我們來看一副手工圖:
手工圖上能夠看出消費方先獲取了服務方的真實接口地址,然後再通過地址去調用接口;然後對於微服務架構來說獲取某一個類ip或端口然後去調用接口肯定是不可取的,因此微服務中產生了一種serviceid的概念;簡單流程介紹完了,下面通過實例來分析;首先添加依賴如:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId></dependency>
再來我們通過上篇文章搭建的eureka_server(服務中心),eureka_provider(服務提供者)來做測試用例,這裡我重新定義eureka_consumer_ribbon模塊做為消費服務;先創建service層類和代碼:
@Servicepublic class UserService implements UserInterface { @Autowired protected RestTemplate restTemplate; @Override public MoRp<List<MoUser>> getUsers(MoRq rq) { return null; } @Override public String getMsg() { String str = restTemplate.getForObject("http://EUREKA-PROVIDER/msg", String.class); return str; }}主要用到了RestTemplate的restTemplate.getForObject 函數,然後需要定義個Controller來吧獲取到的數據響應到頁面上,為了簡單這裡僅僅只拿getMsg服務接口測試:
@RestControllerpublic class UserController { @Autowired private UserService userService; @GetMapping("/msg") public String getMsg(){ return userService.getMsg(); }}最後我們在啟動類添加入下代碼,注意@LoadBalanced標記必須加,因為咋們引入的eureka依賴裡麵包含了ribbon(Dalston.RELEASE版本),ribbon封裝了負載均衡的算法,如果不加這個註解,那後面rest方法的url就必須是可用的url路徑了,當然這裡加了註解就可以使用上面說的serviceId:
@SpringBootApplication@EnableDiscoveryClient //消費客戶端public class EurekaConsumerRibbonApplication { @Bean @LoadBalanced //負載均衡RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(EurekaConsumerRibbonApplication.class, args); }}下面來消費方顯示的效果:
Rest+輪詢自定義簡易消費組件
自定義消費組件原來和麵手工圖差不多,就是先想法獲取服務提供端真實的接口地址,然後通過rest去調用這個url,得到相應的結果輸出;這裡自定義了一個ShenniuBanlance的組件類:
/** * Created by shenniu on 2018/6 * <p> * rest+eureka+自定義client端*/@Componentpublic class ShenniuBanlance { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; /** * 服務真實地址ConcurrentHashMap<"服務應用名稱", ("真實接口ip", 被訪問次數)> */ public static ConcurrentHashMap<String, List<MoService>> sericesMap = new ConcurrentHashMap<>(); /** * 設置服務提供者信息到map */ public void setServicesMap() { //獲取所有服務提供者applicationName List<String> appNames = discoveryClient.getServices(); //存儲真實地址到map for (String appName : appNames) { //獲取某個服務提供者信息List<ServiceInstance> instanceInfos = discoveryClient.getInstances(appName); if (instanceInfos.isEmpty()) { continue; } List<MoService> services = new ArrayList<>(); instanceInfos.forEach(b -> { MoService service = new MoService(); //被訪問次數service.setWatch(0L); //真實接口地址service.setUrl(b.getUri().toString()); services.add(service); }); //如果存在就更新sericesMap.put(appName.toLowerCase(), services); } } /** * 根據app獲取輪詢方式選中後的service * * @param appName * @return */ public MoService choiceServiceByAppName(String appName) throws Exception { appName = appName.toLowerCase(); //某種app的服務service集合List<MoService> serviceMap = sericesMap.get(appName); if (serviceMap == null) { //初始化所有app服務setServicesMap(); serviceMap = sericesMap.get(appName); if (serviceMap == null) { throw new Exception("未能找到" + appName + "相關服務"); } } //篩選出被訪問量最小的service 輪詢的方式MoService moService = serviceMap.stream().min( Comparator.comparing(MoService::getWatch) ).get(); //負載記錄+1 moService.setWatch(moService.getWatch() + 1); return moService; } /** * 自動刷新服務提供者信息到map */ @Scheduled(fixedDelay = 1000 * 10) public void refreshServicesMap() { setServicesMap(); } /** * get請求服務獲取返回數據* * @param appName 應用名稱ApplicationName * @param serviceName 服務名稱ServiceName * @param map url上請求參數* @param tClass 返回類型* @param <T> * @return */ public <T> T getServiceData( String appName, String serviceName, Map<String, ?> map, Class<T> tClass) { T result = null; try { //篩選獲取真實Service MoService service = choiceServiceByAppName(appName); //請求該service的url String apiUrl = service.getUrl() + "/" + serviceName; System.out.println(apiUrl); result = map != null ? restTemplate.getForObject(apiUrl, tClass, map) : restTemplate.getForObject(apiUrl, tClass); } catch (Exception ex) { ex.printStackTrace(); } return result; } /** * Service信息*/ public class MoService { /** * 負載次數記錄數*/ private Long watch; /** * 真實接口地址: http://xxx.com/api/add */ private String url; public Long getWatch() { return watch; } public void setWatch(Long watch) { this.watch = watch; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }}以上就是主要的實現代碼,代碼邏輯:設置服務提供者信息到map-》根據app獲取輪詢方式選中後的service-》請求服務獲取返回數據;輪詢實現的原理是使用了一個負載記錄數,每次被請求後自動+1,當要獲取某個服務提供者時,通過記錄數篩選出最小值的一個實例,裡面存儲有真實接口地址url;調用只需要這樣(當然可以弄成註解來調用):
@Override public String getMsg() { String str = banlance.getServiceData( "EUREKA-PROVIDER", "msg", null, String.class ); return str; }這裡需要注意由於我們在前面RestTemplate使用加入了註解@LoadBalanced ,這樣使得rest請求時必須用非ip的訪問方式(也就是必須serviceid)才能正常響應,不然會提示錯誤如:
簡單來說就是不用再使用ip了,因為有負載均衡機制;當我們去掉這個註解後,我們自定義的組件就能運行成功,效果圖和實例1一樣就不貼圖了;
使用Scheduled刷新服務提供者信息
在微服務架構中,如果某台服務掛了之後,必須要及時更新client端的服務緩存信息,不然就可能請求到down的url去,基於這種考慮我這裡採用了EnableSched標記來做定時刷新;首先在啟動類增加@EnableScheduling ,然後定義一個刷行服務信息的服務如:
/** * 自動刷新服務提供者信息到map */ @Scheduled(fixedDelay = 1000 * 10) public void refreshServicesMap() { setServicesMap(); }為了方便看測試效果,我們在server,provider(2個),consumer已經啟動的情況下,再啟動一個端口為2005的provider服務;然後刷新consumer接口看下效果:
這個時候能夠看到調用2005端口的接口成功了,通過@Scheduled定時服務吧最新或者失效的服務加入|移除掉,就達到了咋們的需求了;如果你覺得該篇內容對你有幫助,不防贊一下,謝謝。希望對大家的學習有所幫助,也希望大家多多支持武林網。