本文主要介紹SpringCloud中三種服務呼叫方式:
- Spring DiscoveryClient
- 支援Ribbon的RestTemplate
- Feign客戶端
搭建服務測試環境
測試中,服務發現層採用Netflix的Eureka搭建。
主要步驟如下:
1.引入Eureka所需依賴
<!--eureka服務端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!--客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
2.修改配置檔案
服務端:
eureka:
instance:
hostname: eureka9001.com #eureka服務端的例項名稱
instance-id: eureka9001
client:
register-with-eureka: false #false表示不向註冊中心註冊自己
fetch-registry: false # #false 表示自己就是註冊中心,職責就是維護服務例項,並不需要去檢索服務
service-url:
defaulteZone: http://127.0.0.1:9001
客戶端1:
server:
port: 8002
spring:
application:
name: licensingservice
eureka:
instance:
instance-id: licensing-service-8002
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://127.0.0.1:9001/eureka/,
客戶端2:
server:
port: 8002
spring:
application:
name: licensingservice
eureka:
instance:
instance-id: licensing-service-8002
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://127.0.0.1:9001/eureka/,
一組微服務的不同例項採服務名相同,不同的例項Id區分,分別對應,spring.application.name
和eureka.instance.instance-id
。
3.啟動服務
服務端:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerPort9001_App {
public static void main(String[] args) {
SpringApplication.run(EurekaServerPort9001_App.class,args);
}
}
客戶端:
@SpringBootApplication
@RefreshScope
@EnableEurekaClient
public class LicenseApplication_8002 {
public static void main(String[] args) {
SpringApplication.run(LicenseApplication_8002.class, args);
}
}
4.測試
進入到Eureka服務端地址,我這是127.0.0.1:9001
,可以檢視註冊到註冊中心的服務。
如圖:
注意事項:Eureka透過三次心跳檢測均透過,服務才會成功註冊到註冊中心,預設每次間隔10s,及初次啟動服務需等待30s才能在Eureka中看到註冊服務。
消費者搭建
1.Discover Client方式
微服務中服務既是消費者也可以是呼叫者,因此消費者配置和上面服務配置大體一致,依賴及配置參考上面服務端搭建方式。啟動主類新增EnableEurekaClient
註釋:
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class ConsumerApplication_7002 {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication_7002.class, args);
}
}
核心配置類:
@Component
public class ConsumerDiscoveryClient {
@Autowired
private DiscoveryClient discoveryClient;
public ServiceInstance getServiceInstance() {
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("licensingservice");
if (serviceInstances.size() == 0) {
return null;
}
return serviceInstances.get(0);
}
public String getUrl(String url) {
ServiceInstance serviceInstance=this.getServiceInstance();
if (serviceInstance==null)
throw new RuntimeException("404 ,NOT FOUND");
String urlR=String.format(url,serviceInstance.getUri().toString());
return urlR;
}
}
透過DiscoveryClient
從Eureka中獲取licensingservice
服務的例項陣列,並返回第一個例項。
測試Controller
@RestController
@RequestMapping("test")
public class TestController {
//注意必須new,否則會被ribbon攔截器攔截,改變URL行為
private RestTemplate restTemplate=new RestTemplate();
@Autowired
private ConsumerDiscoveryClient consumerDiscoveryClient;
@RequestMapping("/getAllEmp")
public List<Emp> getAllLicense(){
String url=consumerDiscoveryClient.getUrl("%s/test/getAllEmp");
return restTemplate.getForObject(url,List.class);
}
}
測試:
- 除錯資訊
從該圖可以直觀看到licensingservice
,擁有兩個服務例項,並可以檢視例項資訊。
- 頁面返回資訊
成功查詢到資料庫儲存資訊。
2.Ribbon功能的Spring RestTemplate方式
依賴同上。
核心配置類:
//指明負載均衡演算法
@Bean
public IRule iRule() {
return new RoundRobinRule();
}
@Bean
@LoadBalanced //告訴Spring建立一個支援Ribbon負載均衡的RestTemplate
public RestTemplate restTemplate() {
return new RestTemplate();
}
設定負載均衡方式為輪詢方式。
測試類:
@RestController
@RequestMapping("rest")
public class ConsumerRestController {
@Autowired
private RestTemplate restTemplate;
private final static String SERVICE_URL_PREFIX = "http://LICENSINGSERVICE";
@RequestMapping("/getById")
public Emp getById(Long id) {
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("id", id);
return restTemplate.postForObject(SERVICE_URL_PREFIX + "/test/getById", paramMap, Emp.class);
}
}
測試結果:
- 第一次:
- 第二次:
- 第三次:
因為採用輪詢負載均衡方式分別呼叫不同服務例項,未區別,將name做出了一定更改。
以上兩種方式對比,Ribbon方式是對第一種方式的封裝且內建不同的負載演算法,支援自定義。使用更加簡單,但此兩次均需編寫RestTemplate的請求方法,較為繁瑣且容易出錯,第三種方式Feign客戶端則極大的降低了開發難度和提升速度。
3.feign客戶端方式
引入依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
主要程式碼:
@FeignClient(value = "LICENSINGSERVICE",fallbackFactory = ServiceImp.class)
public interface ServiceInterface {
@RequestMapping("/test/getById")
Emp getById(@RequestParam("id") Long id);
@RequestMapping("/test/getLicenseById")
License getLicenseById(@RequestParam("id") Long id);
@RequestMapping("/test/getAllEmp")
List<Emp> getAllLicense();
}
@Component
public class ServiceImp implements FallbackFactory<ServiceInterface> {
@Override
public ServiceInterface create(Throwable throwable) {
return new ServiceInterface() {
@Override
public Emp getById(Long id) {
Emp emp = new Emp();
emp.setName("i am feign fallback create");
return emp;
}
@Override
public License getLicenseById(Long id) {
return null;
}
@Override
public List<Emp> getAllLicense() {
return null;
}
};
}
}
採用介面模式開發,透過註解指明服務名以及後備方法,在服務表現不佳時,方便返回預設的結果,而不是一個不友好的錯誤。
主啟動類:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class Consumer_feign_Application_7004 {
public static void main(String[] args) {
SpringApplication.run(Consumer_feign_Application_7004.class, args);
}
}
測試類:
@RestController
@RequestMapping("rest")
public class ConsumerRestController {
@Autowired
private ServiceInterface serviceInterface;
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/getById")
public Emp getById(Long id) {
return serviceInterface.getById(id);
}
}
測試結果:
- 正常測試:
- 關閉兩個服務例項,模擬服務例項死亡:
Feign除了能簡化服務呼叫,也可以實現當呼叫的服務失敗時,友好的反饋資訊。
此三種呼叫方式,由低至上,從不同層次實現了SpringCloud中的微服務互相呼叫。