前面我們介紹了通過springcloud的eureka服務注冊組件實現,并且實現了多機互備的HA,同時也將之前寫的springboot的服務注冊到了eureka上,今天我們主要來介紹下作為服務使用者如何去使用這些服務接口并且實現基于服務化的軟負載均衡。對于之前實現的springboot的rest接口服務,一般來說進行restful接口的接收和拆組包,可以通過apache的httpclient、jdk的URLConnection、okhttp等http請求庫,也可以通過spring提供的resttemplate,這里我們使用springcloud推薦的feign來進行報文解析來體會下它的優勢,為什么spring會大力推薦一個新的rest請求組件,比傳統的使用httpclient等有什么優勢。
1. POM增加Feign和Ribbon相關依賴庫
新建一個springboot工程,并添加cloud的依賴,本文中使用的是1.4.7版本,具體的代碼可以參見文后的源碼,這里需要在pom中添加對ribbon和feign的依賴。還需要加入對Zuul網關組件的依賴,如果沒有網關組件,在啟動的時候會報Hystrix的錯誤,“Caused by: java.lang.ClassNotFoundException: com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect。“
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2. Application啟動類增加Feign依賴
在application類中需要增加在類名前面增加@EnableFeignClients的注解,這個注解的意思就是在該應用被啟動的時候,會去所有類中搜索定義為FeignClient的接口,并自動注冊到spring ioc容器中,然后會對所有使用client實現類的對象進行自動依賴注入。這里還有EurekaClient的注解是因為后面定義FeignClient的時候需要定義接口的服務名而不是ip地址,需要定義EurekaClient才能發現相關服務,并且實現軟負載。
@SpringCloudApplication
@EnableFeignClients
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. 創建Feign Client接口
下面來到最重要的一步,就是定義Feign的Client接口,這里Client接口其實就是對提供服務的restful接口的本地定義,定義好后,在其他類中就可以像使用RPC本地接口類一樣使用遠程服務接口了,想想我們用httpclient、okhttp是怎么實現報文接收的功能的,首先我們需要定義httpclient對象,按照服務端的接口要求組合json報文,然后定義是get還是post,不同的傳輸方法還需要調用不同的方法(doGet、doPost),這才將一個請求發出去,然后從inputstream中循環讀取字節流或者從Reader中讀取字符流,再將字符流反序列化成對象,全部需要編碼實現,整個過程雖然結構很清晰但是很繁瑣,為什么之前RPC很火,就是因為使用RPC會讓client端調用遠程服務接口簡單化,就好像使用本地對象一樣簡單,但是使用RPC的最大的弊端就是每個client都需要維護一個服務端的client jar包,這個jar包中其實就是定義了接口,一旦服務端接口有變化,服務使用方就需要更新jar包,這就使得客戶端對服務端產生了強依賴,feign就很好的解決了這個問題,在client和server端沒有依賴的情況下,讓client使用服務就像使用本地接口一樣簡單,廢話不多少,下面來定義feign client的接口,這里服務端的接口就使用之前定義的getUser接口。
@FeignClient("usercenter-provider")
public interface UserFeignClient {
//Feign定義服務提供者接口
@RequestMapping(value = "/getUser", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
String getUser(@RequestBody String data);
}
@FeignClient("usercenter-provider")注解的意思是該接口里定義的方法全部是“usercenter-provider”這個serviceid提供的方法,注意這里的serviceid需要和eureka server里定義的服務提供方的名字一致,不然feign是無法找到相關服務的,feign內部集成了Ribbon所以通過serviceid找到服務后,會通過ribbon自動實現服務訪問的負載均衡,可以通過定義ribbon的負載均衡方法來實現自己想要的,默認得是隨機訪問方法也可以定義成順序、訪問權重或者自定義負載算法,這里就使用了默認的負載,默認負載已經能夠滿足大部分需求了。在接口的方法前面需要定義該方法訪問路徑和訪問方法和編碼格式等信息,對于請求參數也可以通過@requestBody或者@RequestParameter的value方法進行設置,這里就使用和入參名同名的參數設定。是不是很簡單,這樣就定義好了遠程服務端接口。
4. 遠程服務接口使用
上面定義好了Feign Client接口,后面使用就很簡單了,這里按照日常項目結構定義了service層和controller,將feign的接口調用放在了service的實現類中,在controller類中進行service的調用,這里需要注意一個問題,之前在測試的過程中自己遇到了,因為不小心寫錯了,犯了一個很低級的錯誤,找了1個小時才找到原因。。因為feign的client是通過@AutoWired啟動的時候自動注入的,這就使得如果client的調用是在service里的時候需要將service也放在spring的ioc中進行托管,不然就會報你的Service沒有定義,并且在Controller使用這個Service的時候也要通過ioc容器自動注入Service實現,不然會報client空指針的錯誤,如果調試過程中發現FeignClient調用的時候報nullpoint,基本就是由于ioc bean托管導致的問題,我就是因為寫錯了一行代碼導致了這個問題。
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserFeignClient userFeignClient;
@Override
public String getUSer(String data) {
return "Feign: " + userFeignClient.getUser(data);
}
}
UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/getUser", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public String getUser(@RequestBody String data){
return userService.getUSer(data);
}
}
5. application.yml參數設置
在配置中主要是要配置eureka server集群的地址,因為需要定義在哪個eureka集群中找到feign使用的serviceid。
spring:
application:
name: springcloud-feign-ribbon
server:
port: 8080
eureka:
client:
serviceUrl:
defaultZone: http://node1:8761/eureka/,http://node2:8762/eureka/
小結
最后我們啟動eureka server、usercenter-provider的服務,再啟動本次寫的應用。
下面通過httprequester來發起請求,地址欄輸入http://node1:8080/getUser,body欄輸入{"name":"feiweiwei"},可以看到返回了Feign {"name":"feiweiwei"}