springcloud之自定義簡易消費(fèi)服務(wù)組件
本次和大家分享的是怎么來消費(fèi)服務(wù),上篇文章講了使用Feign來消費(fèi),本篇來使用rest+ribbon消費(fèi)服務(wù),并且通過輪詢方式來自定義了個(gè)簡易消費(fèi)組件,本文分享的宗旨是:自定義消費(fèi)服務(wù)的思路;思路如果有可取之處還請“贊”一下:
- Rest+Ribbon實(shí)現(xiàn)消費(fèi)服務(wù)
- Rest+輪詢自定義簡易消費(fèi)組件
- 使用Scheduled刷新服務(wù)提供者信息
Rest+Ribbon實(shí)現(xiàn)消費(fèi)服務(wù)
做為服務(wù)消費(fèi)方準(zhǔn)確的來說進(jìn)行了兩種主流程區(qū)分1)獲取可以服務(wù)2)調(diào)用服務(wù),那么又是如何獲取服務(wù)的并且又是通過什么來調(diào)用服務(wù)的,下面我們來看一副手工圖:
手工圖上能夠看出消費(fèi)方先獲取了服務(wù)方的真實(shí)接口地址,然后再通過地址去調(diào)用接口;然后對于微服務(wù)架構(gòu)來說獲取某一個(gè)類ip或端口然后去調(diào)用接口肯定是不可取的,因此微服務(wù)中產(chǎn)生了一種serviceid的概念;簡單流程介紹完了,下面通過實(shí)例來分析;首先添加依賴如:
<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(服務(wù)中心),eureka_provider(服務(wù)提供者)來做測試用例,這里我重新定義eureka_consumer_ribbon模塊做為消費(fèi)服務(wù);先創(chuàng)建service層類和代碼:
@Service public 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 函數(shù),然后需要定義個(gè)Controller來吧獲取到的數(shù)據(jù)響應(yīng)到頁面上,為了簡單這里僅僅只拿getMsg服務(wù)接口測試:
@RestController public class UserController { @Autowired private UserService userService; @GetMapping("/msg") public String getMsg(){ return userService.getMsg(); } }
最后我們在啟動類添加入下代碼,注意 @LoadBalanced 標(biāo)記必須加,因?yàn)檎円氲膃ureka依賴?yán)锩姘藃ibbon(Dalston.RELEASE版本),ribbon封裝了負(fù)載均衡的算法,如果不加這個(gè)注解,那后面rest方法的url就必須是可用的url路徑了,當(dāng)然這里加了注解就可以使用上面說的serviceId:
@SpringBootApplication @EnableDiscoveryClient //消費(fèi)客戶端 public class EurekaConsumerRibbonApplication { @Bean @LoadBalanced //負(fù)載均衡 RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(EurekaConsumerRibbonApplication.class, args); } }
下面來消費(fèi)方顯示的效果:
Rest+輪詢自定義簡易消費(fèi)組件
自定義消費(fèi)組件原來和面手工圖差不多,就是先想法獲取服務(wù)提供端真實(shí)的接口地址,然后通過rest去調(diào)用這個(gè)url,得到相應(yīng)的結(jié)果輸出;這里自定義了一個(gè)ShenniuBanlance的組件類:
/** * Created by shenniu on 2018/6 * <p> * rest+eureka+自定義client端 */ @Component public class ShenniuBanlance { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; /** * 服務(wù)真實(shí)地址 ConcurrentHashMap<"服務(wù)應(yīng)用名稱", ("真實(shí)接口ip", 被訪問次數(shù))> */ public static ConcurrentHashMap<String, List<MoService>> sericesMap = new ConcurrentHashMap<>(); /** * 設(shè)置服務(wù)提供者信息到map */ public void setServicesMap() { //獲取所有服務(wù)提供者applicationName List<String> appNames = discoveryClient.getServices(); //存儲真實(shí)地址到map for (String appName : appNames) { //獲取某個(gè)服務(wù)提供者信息 List<ServiceInstance> instanceInfos = discoveryClient.getInstances(appName); if (instanceInfos.isEmpty()) { continue; } List<MoService> services = new ArrayList<>(); instanceInfos.forEach(b -> { MoService service = new MoService(); //被訪問次數(shù) service.setWatch(0L); //真實(shí)接口地址 service.setUrl(b.getUri().toString()); services.add(service); }); //如果存在就更新 sericesMap.put(appName.toLowerCase(), services); } } /** * 根據(jù)app獲取輪詢方式選中后的service * * @param appName * @return */ public MoService choiceServiceByAppName(String appName) throws Exception { appName = appName.toLowerCase(); //某種app的服務(wù)service集合 List<MoService> serviceMap = sericesMap.get(appName); if (serviceMap == null) { //初始化所有app服務(wù) setServicesMap(); serviceMap = sericesMap.get(appName); if (serviceMap == null) { throw new Exception("未能找到" + appName + "相關(guān)服務(wù)"); } } //篩選出被訪問量最小的service 輪詢的方式 MoService moService = serviceMap.stream().min( Comparator.comparing(MoService::getWatch) ).get(); //負(fù)載記錄+1 moService.setWatch(moService.getWatch() + 1); return moService; } /** * 自動刷新 服務(wù)提供者信息到map */ @Scheduled(fixedDelay = 1000 * 10) public void refreshServicesMap() { setServicesMap(); } /** * get請求服務(wù)獲取返回?cái)?shù)據(jù) * * @param appName 應(yīng)用名稱 ApplicationName * @param serviceName 服務(wù)名稱 ServiceName * @param map url上請求參數(shù) * @param tClass 返回類型 * @param <T> * @return */ public <T> T getServiceData( String appName, String serviceName, Map<String, ?> map, Class<T> tClass) { T result = null; try { //篩選獲取真實(shí)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 { /** * 負(fù)載次數(shù)記錄數(shù) */ private Long watch; /** * 真實(shí)接口地址: 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; } } }
以上就是主要的實(shí)現(xiàn)代碼,代碼邏輯:設(shè)置服務(wù)提供者信息到map-》根據(jù)app獲取輪詢方式選中后的service-》請求服務(wù)獲取返回?cái)?shù)據(jù);輪詢實(shí)現(xiàn)的原理是使用了一個(gè)負(fù)載記錄數(shù),每次被請求后自動+1,當(dāng)要獲取某個(gè)服務(wù)提供者時(shí),通過記錄數(shù)篩選出最小值的一個(gè)實(shí)例,里面存儲有真實(shí)接口地址url;調(diào)用只需要這樣(當(dāng)然可以弄成注解來調(diào)用):
@Override public String getMsg() { String str = banlance.getServiceData( "EUREKA-PROVIDER", "msg", null, String.class ); return str; }
這里需要注意由于我們在前面RestTemplate使用加入了注解 @LoadBalanced ,這樣使得rest請求時(shí)必須用非ip的訪問方式(也就是必須serviceid)才能正常響應(yīng),不然會提示錯(cuò)誤如:
簡單來說就是不用再使用ip了,因?yàn)橛胸?fù)載均衡機(jī)制;當(dāng)我們?nèi)サ暨@個(gè)注解后,我們自定義的組件就能運(yùn)行成功,效果圖和實(shí)例1一樣就不貼圖了;
使用Scheduled刷新服務(wù)提供者信息
在微服務(wù)架構(gòu)中,如果某臺服務(wù)掛了之后,必須要及時(shí)更新client端的服務(wù)緩存信息,不然就可能請求到down的url去,基于這種考慮我這里采用了EnableSched標(biāo)記來做定時(shí)刷新;首先在啟動類增加 @EnableScheduling ,然后定義一個(gè)刷行服務(wù)信息的服務(wù)如:
/** * 自動刷新 服務(wù)提供者信息到map */ @Scheduled(fixedDelay = 1000 * 10) public void refreshServicesMap() { setServicesMap(); }
為了方便看測試效果,我們在server,provider(2個(gè)),consumer已經(jīng)啟動的情況下,再啟動一個(gè)端口為2005的provider服務(wù);然后刷新consumer接口看下效果:
這個(gè)時(shí)候能夠看到調(diào)用2005端口的接口成功了,通過@Scheduled定時(shí)服務(wù)吧最新或者失效的服務(wù)加入|移除掉,就達(dá)到了咋們的需求了;如果你覺得該篇內(nèi)容對你有幫助,不防贊一下,謝謝。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springcloud gateway聚合swagger2的方法示例
這篇文章主要介紹了springcloud gateway聚合swagger2的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04java long轉(zhuǎn)String +Codeforces110A案例
這篇文章主要介紹了java long轉(zhuǎn)String +Codeforces110A案例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09SpringBoot通過@MatrixVariable進(jìn)行傳參詳解
這篇文章主要介紹了SpringBoot使用@MatrixVariable傳參,文章圍繞@MatrixVariable展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-06-06使用springBoot中的info等級通過druid打印sql
這篇文章主要介紹了使用springBoot中的info等級通過druid打印sql,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09