springcloud之自定義簡易消費服務組件
本次和大家分享的是怎么來消費服務,上篇文章講了使用Feign來消費,本篇來使用rest+ribbon消費服務,并且通過輪詢方式來自定義了個簡易消費組件,本文分享的宗旨是:自定義消費服務的思路;思路如果有可取之處還請“贊”一下:
- Rest+Ribbon實現消費服務
- Rest+輪詢自定義簡易消費組件
- 使用Scheduled刷新服務提供者信息
Rest+Ribbon實現消費服務
做為服務消費方準確的來說進行了兩種主流程區(qū)分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模塊做為消費服務;先創(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 函數,然后需要定義個Controller來吧獲取到的數據響應到頁面上,為了簡單這里僅僅只拿getMsg服務接口測試:
@RestController
public 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端
*/
@Component
public 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定時服務吧最新或者失效的服務加入|移除掉,就達到了咋們的需求了;如果你覺得該篇內容對你有幫助,不防贊一下,謝謝。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
SpringBoot?項目的創(chuàng)建與啟動步驟詳解
這篇文章主要介紹了SpringBoot?項目的創(chuàng)建與啟動,本文分步驟給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
Java實現監(jiān)聽UDP協議的指定端口并收到數據按照十六進制輸出方式
這篇文章主要介紹了Java實現監(jiān)聽UDP協議的指定端口并收到數據按照十六進制輸出方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04

