上兩章節,介紹了下關於註冊中心-Eureka的使用及高可用的配置示例,本章節開始,來介紹下服務和服務之間如何進行服務調用的,同時會講解下幾種不一樣方式的服務調用。html
在SpringCloud
體系中,咱們知道服務之間的調用是經過http
協議進行調用的。而註冊中心的主要目的就是維護這些服務的服務列表。咱們知道,在Spring
中,提供了RestTemplate
。RestTemplate
是Spring
提供的用於訪問Rest服務的客戶端。而在SpringCloud
中也是使用此服務進行服務調用的。java
同時在微服務中,通常上服務都不會進行單點部署的,都會至少部署2臺及以上的。如今咱們有了註冊中心進行服務列表的維護,就須要一個客戶端負載均衡來進行動態服務的調用。git
因此開始示例前,咱們先來大體瞭解下關於負載均衡
和RestTemplate
的相關知識點。其實後面實例的Ribbon
和Feign
最後的調用都是基於RestTemplate
的。使用比較簡單~github
負載均衡(Load Balance)是分佈式系統架構設計中必須考慮的因素之一,它一般是指,將請求/數據**【均勻】分攤**到多個操做單元上執行,負載均衡的關鍵在於【均勻】。web
實現負載均衡的方式有不少種,這裏簡單介紹下幾種方式,並未過多深刻。算法
注意:如下部份內容轉至幾種負載均衡技術的實現。spring
1.HTTP重定向負載均衡編程
根據用戶的http請求計算出一個真實的web服務器地址,並將該web服務器地址寫入http重定向響應中返回給瀏覽器,由瀏覽器從新進行訪問設計模式
優缺點:實現起來很簡單,而缺點也顯而易見了:請求兩次才能完成一次訪問;性能差;重定向服務器會成爲瓶頸api
2.DNS域名解析負載均衡
在DNS服務器上配置多個域名對應IP的記錄。例如一個域名
www.baidu.com
對應一組web服務器IP地址,域名解析時通過DNS服務器的算法將一個域名請求分配到合適的真實服務器上。
優缺點:加快訪問速度,改善性能。同時因爲DNS解析是多級解析,每一級DNS均可能化緩存記錄A,當某一服務器下線後,該服務器對應的DNS記錄A可能仍然存在,致使分配到該服務器的用戶訪問失敗,並且DNS負載均衡採用的是簡單的輪詢算法,不能區分服務器之間的差別,不能反映服務器當前運行狀態。
3.反向代理負載均衡
反向代理處於web服務器這邊,反向代理服務器提供負載均衡的功能,同時管理一組web服務器,它根據負載均衡算法將請求的瀏覽器訪問轉發到不一樣的web服務器處理,處理結果通過反向服務器返回給瀏覽器。
優缺點:實現簡單,可利用反向代理緩存資源(這是最經常使用的了)及改善網站性能。同時由於是全部請求和響應的中轉站,因此反向代理服務器可能成爲瓶頸。
以上僅僅是部分實現方式,還有好比IP負載均衡
、數據鏈路層負載均衡
等等,這些可能涉及到相關網絡方面的知識點了,不是很瞭解,你們有興趣能夠自行搜索下吧。
實現負載均衡也又區分客戶端和服務端之分,Ribbon
就是基於客戶端的負載均衡。 客戶端負載均衡:
服務端負載均衡:
服務端實現負載均衡方式有不少,好比:硬件F5
、Nginx
、HA Proxy
等等,這些應該實施相關人員應該比較熟悉了,本人可能也就對Nginx
瞭解下,⊙﹏⊙‖∣
RestTemplate
是Spring
提供的用於訪問Rest服務
的客戶端,RestTemplate
提供了多種便捷訪問遠程Http服務的方法,可以大大提升客戶端的編寫效率。
簡單來講,RestTemplate
採用了模版設計
的設計模式,將過程當中與特定實現相關的部分委託給接口,而這個接口的不一樣實現定義了接口的不一樣行爲,因此能夠很容易的使用不一樣的第三方http服務,如okHttp
、httpclient
等。
RestTemplate
定義了不少的與REST資源交互,這裏簡單介紹下一些經常使用的請求方式的使用。
在URL上執行特定的HTTP方法,返回包含對象的ResponseEntity
。其餘的如GET
、POST
等方法底層都是基於此方法的。
如:
RequestEntity requestEntity = RequestEntity.get(new URI(uri)).build(); ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
RequestEntity<User> requestEntity = RequestEntity.post(new URI(uri)).body(user); ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
get請求能夠分爲兩類:
getForEntity()
和getForObject()
.
// 1-getForObject() User user1 = this.restTemplate.getForObject(uri, User.class); // 2-getForEntity() ResponseEntity<User> responseEntity1 = this.restTemplate.getForEntity(uri, User.class); HttpStatus statusCode = responseEntity1.getStatusCode(); HttpHeaders header = responseEntity1.getHeaders(); User user2 = responseEntity1.getBody();
其餘的方法都大同小異了,能夠根據實際的業務需求進行調用。
簡單示例:
// 1-postForObject() User user1 = this.restTemplate.postForObject(uri, user, User.class); // 2-postForEntity() ResponseEntity<User> responseEntity1 = this.restTemplate.postForEntity(uri, user, User.class);
關於postForLocation()
,用的比較少,做用是返回新建立資源的URI,前面介紹的二者是返回資源自己,也就是結果集了。
關於其餘的請求類型相關用法,這裏就不詳細闡述了,都是相似的。能夠查看下此文章:詳解 RestTemplate 操做,講的蠻詳細了。
特別說明:系列教程爲了方便,github上分別建立了一個單體的Eureka
註冊中心和高可用的Eureka
註冊中心,無特殊說明,都是使用單體的Eureka
註冊中心進行服務註冊與發現的,工程名爲:spring-cloud-eureka-server
,端口號爲:1000。服務提供方工程名爲:spring-cloud-eureka-client
,應用名稱爲:eureka-client
,端口號爲:2000,提供了一個接口:http://127.0.0.1:2000/hello
spring-cloud-eureka-server示例:spring-cloud-eureka-server
spring-cloud-eureka-client示例:spring-cloud-eureka-client
此類是實現客戶端負載均衡的關鍵。自己它是個接口類,位於spring-cloud-commons
包下,此包包含了大量的服務治理相關的抽象接口,好比已經介紹過的DiscoveryClient
、ServiceRegistry
以及LoadBalancerClient實例
等等。
首先,咱們使用最原生的方式去獲取調用服務接口。
建立個工程:spring-cloud-eureka-consumer
0.引入pom文件依賴。
<!-- 客戶端依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
1.配置文件添加相關注冊中心等信息。
## 服務名稱 spring.application.name=eureka-consumer ## 端口號 server.port=8008 #指定註冊中心地址 eureka.client.service-url.defaultZone=http://127.0.0.1:1000/eureka # 啓用ip配置 這樣在註冊中心列表中看見的是以ip+端口呈現的 eureka.instance.prefer-ip-address=true # 實例名稱 最後呈現地址:ip:2000 eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
2.編寫啓動類,加入@EnableDiscoveryClient
,申明爲一個客戶端應用,同時申明一個RestTemplate
,最後是使用RestTemplate
來完成rest服務調用的。
@SpringBootApplication @EnableDiscoveryClient @Slf4j public class EurekaConsumerApplication { public static void main(String[] args) throws Exception { SpringApplication.run(EurekaConsumerApplication.class, args); log.info("spring-cloud-eureka-consumer啓動!"); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
3.編寫一個調用類,調用spring-cloud-eureka-client
服務提供者提供的服務。
/** * 訪問客戶端示例 * @author oKong * */ @RestController @Slf4j public class DemoController { @Autowired LoadBalancerClient loadBalancerClient; @Autowired RestTemplate restTemplate; @GetMapping("/hello") public String hello(String name) { ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client"); String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/hello?name=" + name; log.info("url地址爲:{}", url); return restTemplate.getForObject(url, String.class); } }
4.啓動應用,訪問:http://127.0.0.1:8008/hell0?name=oKong ,能夠看見控制檯輸出了利用LoadBalancerClient
的choose
方法,獲取到了對應eureka-client
服務ID的服務地址。
最後經過範圍對應的http地址進行服務請求:
最後瀏覽器上能夠看見,進行了正確的訪問了:
此時,切換到服務提供者spring-cloud-eureka-client
控制檯,能夠看見日誌輸出:
此時咱們已經調用成功了,經過LoadBalancerClient
獲取到了服務提供者實際服務地址,最後進行調用。
你們能夠建立多個的spring-cloud-eureka-client
服務提供者,再去調用下,能夠看見會調用不一樣的服務地址的。
Spring Cloud Ribbon
是一個基於Http和TCP的客服端負載均衡工具,它是基於Netflix Ribbon
實現的。與Eureka
配合使用時,Ribbon
可自動從Eureka Server (註冊中心)
獲取服務提供者地址列表,並基於負載均衡
算法,經過在客戶端中配置ribbonServerList
來設置服務端列表去輪詢訪問以達到均衡負載的做用。
上小節,簡單的使用LoadBalancerClient
進行了服務實例獲取最後調用,也說了其實LoadBalancerClient
是個接口類。而Ribbon
實現了此接口,對應實現類爲:RibbonLoadBalancerClient
.
如今咱們來看下,使用Ribbon
的方式如何進行更加優雅的方式進行服務調用。
建立一個工程:spring-cloud-eureka-consumer-ribbon
(其實這個工程和spring-cloud-eureka-consumer
是差很少的,只是有些許不一樣。)
0.加入pom依賴
<!-- 客戶端依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
1.配置文件修改,添加註冊中心等相關信息。
spring.application.name=eureka-consumer-ribbon server.port=8018 #指定註冊中心地址 eureka.client.service-url.defaultZone=http://127.0.0.1:1000/eureka # 啓用ip配置 這樣在註冊中心列表中看見的是以ip+端口呈現的 eureka.instance.prefer-ip-address=true # 實例名稱 最後呈現地址:ip:2000 eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
2.編寫啓動類,加入@EnableDiscoveryClient
,同時申明一個RestTemplate
,這裏和原先不一樣,就在於加入了@LoadBalanced
註解進行修飾RestTemplate
類,稍後會大體講解下是如何進行實現的。
@SpringBootApplication @EnableDiscoveryClient @Slf4j public class EurekaConsumerRibbonApplication { public static void main(String[] args) throws Exception { SpringApplication.run(EurekaConsumerRibbonApplication.class, args); log.info("spring-cloud-eureka-consumer-ribbon啓動!"); } //添加 @LoadBalanced 使其具有了使用LoadBalancerClient 進行負載均衡的能力 @Bean @LoadBalanced public RestTemplate restTemplage() { return new RestTemplate(); } }
3.編寫測試類,進行服務調用。
/** * ribbon訪問客戶端示例 * @author oKong * */ @RestController @Slf4j public class DemoController { @Autowired RestTemplate restTemplate; @GetMapping("/hello") public String hello(String name) { //直接使用服務名進行訪問 log.info("請求參數name:{}", name); return restTemplate.getForObject("http://eureka-client/hello?name=" + name, String.class); } }
能夠看見,能夠直接注入RestTemplate
,經過服務名直接調用.
4.啓動應用,訪問:http://127.0.0.1:8018/hello?name=oKong ,能夠看見調用成功:
控制檯輸出:
能夠從以上示例中,能夠看出,咱們就加了一個
@LoadBalanced
註解修飾RestTemplate
bean類,就實現了服務的調用。如今來簡單看看具體是如何實現的。
首先,咱們看看此註解的代碼說明:
從註釋能夠看出,該註解用來給RestTemplate作標記,以使用負載均衡的客戶端LoadBalancerClient
。
如今來看一眼相同包下的類的狀況,能夠看到有個LoadBalancerAutoConfiguration
,字面意思能夠知道這是一個自動配置類,此類就是咱們要找的關鍵類了。
LoadBalancerAutoConfiguration
,此類不長,一百來行,這裏就不貼了。
簡單說明下: 首先,此類生效的條件是
@ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class)
RestTemplate
類必須存在於當前工程的環境中。LoadBalancerClient
的實現Bean。該自動化配置類中,主要作了幾件事情:
@LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList();
同時爲其每一個對象經過調用RestTemplateCustomizer
添加了一個LoadBalancerInterceptor
和RetryLoadBalancerInterceptor
攔截器(有生效條件),其爲ClientHttpRequestInterceptor
接口的實現類,ClientHttpRequestInterceptor
是RestTemplate
的請求攔截器
RetryLoadBalancerInterceptor攔截器
LoadBalancerInterceptor攔截器
咱們主要看下LoadBalancerInterceptor
:
能夠看見,最後是實現了ClientHttpRequestInterceptor
接口的實現類執行execute
方法進行.
從繼承關係裏,此實現類就是RibbonLoadBalancerClient
類了。
RibbonLoadBalancerClient
類:
簡單來講:最後仍是經過loadBalancerClient.choose()
獲取到服務實例,最經過拼湊http地址來進行最後的服務調用。
整體來講,就是經過爲加入@LoadBalanced
註解的RestTemplate
添加一個請求攔截器,在請求前經過攔截器獲取真正的請求地址,最後進行服務調用。
裏面的細節就不闡述了,畢竟源碼分析不是很在行呀,你們能夠跟蹤進去一探究竟吧。
友情提醒:若被@LoadBalanced
註解的RestTemplate
訪問正常的服務地址,如http://127.0.0.1:8080/hello
時,是會提示沒法找到此服務的。
具體緣由:serverid
必須是咱們訪問的服務名稱
,當咱們直接輸入ip
的時候獲取的server
是null
,就會拋出異常。
此時,如果須要調用非註冊中心的服務,能夠建立一個不被@LoadBalanced
註解的RestTemplate
,同時指定bean的名稱,使用時,使用@Qualifier
指定name注入此RestTemplate
。
@Bean("normalRestTemplage") public RestTemplate normalRestTemplage() { return new RestTemplate(); } //使用 @Autowired @Qualifier("normalRestTemplage") RestTemplate normalRestTemplate; @GetMapping("/ip") public String ip(String name) { //直接使用服務名進行訪問 log.info("使用ip請求,請求參數name:{}", name); return normalRestTemplate.getForObject("http://127.0.0.1:2000/hello?name=" + name, String.class); }
目前還未進行過自定義負載均衡,這裏就簡單的舉例下,上次整理ppt時有講過一些,但未深刻了解過⊙﹏⊙‖∣,
能夠從繼承關係看出,是經過繼承IRule
來實現的。
可繼承ClientConfigEnabledRoundRobinRule,來實現本身負載均衡策略。
從上一章節,咱們知道,當咱們要調用一個服務時,須要知道服務名和api地址,這樣才能進行服務調用,服務少時,這樣寫以爲沒有什麼問題,但當服務一多,接口參數不少時,上面的寫法就顯得不夠優雅了。因此,接下來,來講說一種更好更優雅的調用服務的方式:Feign。
Feign
是Netflix
開發的聲明式、模塊化的HTTP客戶端。Feign
可幫助咱們更好更快的便捷、優雅地調用HTTP API
。
在Spring Cloud
中,使用Feign
很是簡單——建立一個接口,並在接口上添加一些註解。Feign
支持多種註釋,例如Feign自帶的註解或者JAX-RS註解等 Spring Cloud對Feign進行了加強,使Feign支持了Spring MVC註解,並整合了Ribbon和 Eureka,從而讓Feign 的使用更加方便。只須要經過建立接口並用註解來配置它既可完成對Web服務接口的綁定。
建立個spring-cloud-eureka-consumer-ribbon
工程項目。
0.加入feigin
依賴
<!-- feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- eureka客戶端依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- rest api --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
1.配置文件
spring.application.name=eureka-consumer-feign server.port=8028 #指定註冊中心地址 eureka.client.service-url.defaultZone=http://127.0.0.1:1000/eureka # 啓用ip配置 這樣在註冊中心列表中看見的是以ip+端口呈現的 eureka.instance.prefer-ip-address=true # 實例名稱 最後呈現地址:ip:2000 eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
2.建立啓動類,加入註解@EnableFeignClients
,開啓feign
支持。
@SpringBootApplication @EnableFeignClients @Slf4j public class EurekaConsumerFeignApplication { public static void main(String[] args) throws Exception { SpringApplication.run(EurekaConsumerFeignApplication.class, args); log.info("spring-cloud-eureka-consumer-feign啓動"); } }
3.建立一個接口類IHelloClient
,加入註解@FeignClient
來指定這個接口所要調用的服務名稱。
@FeignClient(name="eureka-client") public interface IHelloClient { /** * 定義接口 * @param name * @return */ @RequestMapping(value="/hello", method=RequestMethod.GET) public String hello(@RequestParam("name") String name); }
4.建立一個demo控制層,引入此接口類。
/** * feign 示例 * @author OKong * */ @RestController @Slf4j public class DemoController { @Autowired IHelloClient helloClient; @GetMapping("/hello") public String hello(String name) { log.info("使用feign調用服務,參數name:{}", name); return helloClient.hello(name); } }
5.啓動應用,訪問:http://127.0.0.1:8028/hello?name=oKong-feign
是否是很簡單,和調用本地服務是同樣的了!
Feign
支持繼承,但不支持多繼承。使用繼承,可將一些公共操做分組到一些父類接口中,從而簡化Feign的開發。
因此在實際開發中,調用服務接口時,可直接按接口類和實現類進行編寫,調用方引入接口依賴,繼承一個本地接口,這樣接口方法默認都是定義好的,也少了不少編碼量。用起來就更爽了,就是有點依賴性,對方服務修改後須要同步更新下,但這個團隊內部約定下問題不大的
這裏簡單實例下,建立一個spring-cloud-eureka-client-api
工程。
0.加入依賴,注意此依賴的做用範圍:
<!--api接口依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>provided</scope> </dependency>
1.編寫一個接口類IHellpApi
:
public interface IHelloApi { //定義提供者服務名 public static final String SERVICE_NAME = "eureka-client"; /** * 定義接口 * @param name * @return */ @RequestMapping(value="/hello", method=RequestMethod.GET) public String hello(@RequestParam("name") String name); }
修改spring-cloud-eureka-client
工程
0.引入api依賴
<!-- 導入接口依賴 --> <dependency> <groupId>cn.lqdev.learning</groupId> <artifactId>spring-cloud-eureka-client-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
1.建立一個HelloApiImpl
類,實現IHelloApi
:
/** * 使用接口方式進行接口編寫 * @author oKong * */ @RestController @Slf4j public class HelloApiImpl implements IHelloApi { @Override public String helloApi(@RequestParam("name") String name) { log.info("[spring-cloud-eureka-client]服務[helloApi]被調用,參數name值爲:{}", name); return name + ",helloApi調用!"; } }
此時,HelloApiImpl
是個控制層也是個接口實現類了。
修改spring-cloud-eureka-consumer-feign
工程。 0.引入api依賴
<!-- 導入接口依賴 --> <dependency> <groupId>cn.lqdev.learning</groupId> <artifactId>spring-cloud-eureka-client-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
1.一樣建立一個接口,使其繼承IHelloApi
:
/** * 直接繼承接口 * @author Okong * */ @FeignClient(name = IHelloApi.SERVICE_NAME) public interface HelloApi extends IHelloApi{ }
小技巧:能夠在IHelloApi
定義一個服務名變量,如:SERVICE_NAME,這樣讓提供者進行變量的賦值,能夠避免一些沒必要要的交流成本的,如有變化,服務調用方也無需關心的。一切都是約定編程!
2.修改下DemoController
類,注入HelloApi
:
@Autowired HelloApi helloApi; @GetMapping("hello2") public String hello2(String name) { log.info("使用feign繼承方式調用服務,參數name:{}", name); return helloApi.helloApi(name); }
3.分別啓動各服務,訪問:http://127.0.0.1:8028/hello2?name=oKong-api
使用起來沒啥差異的,同樣的調用,但對於調用方而言,能夠無需去理會具體細節了,照着接口方法去傳參就行了。
這種方式,和原來的dubbo
調用的方式是相似的,簡單方便。你們能夠把接口和實體放入一個包中,調用者和提供者都進行依賴便可。
在使用Feign
時,會遇見一些問題,爲了不沒必要要的錯誤,如下這些須要額外注意下。
本章節主要講解了下服務消費者如何利用原生、ribbon、fegin三種方式進行服務調用的,其實每種調用方式都是使用
ribbon
來進行調用的,只是有些進行了加強,是的使用起來更簡單高效而已。對於其原理的實現,本文未進行詳細闡述,你們能夠谷歌想相關知識,跟蹤下源碼瞭解下,本人也還沒有深刻研究過,仍是停留在使用階段,以後有時間了看一看,有啥心得再來分享吧。此時若服務上線下線,調用者調用可能會出現短暫的調用異常,最多見的就是找不到服務,此時服務容錯保護就排上用場了,因此下一章節,就來講說關於服務容錯保護
相關知識點~
目前互聯網上大佬都有分享
SpringCloud
系列教程,內容可能會相似,望多多包涵了。原創不易,碼字不易,還但願你們多多支持。若文中有錯誤之處,還望提出,謝謝。
499452441
lqdevOps
我的博客:http://blog.lqdev.cn
源碼示例:https://github.com/xie19900123/spring-cloud-learning
原文地址:http://blog.lqdev.cn/2018/09/21/SpringCloud/chapter-four/