javascript
SpringCloud(第二部分)
文章目錄
- 6.負載均衡Ribbon
- 6.1.啟動兩個服務實例
- 6.2.開啟負載均衡
- 6.3.負載均衡策略
- 7.Hystrix
- 7.1.簡介
- 7.2.雪崩問題
- 7.3.線程隔離,服務降級
- 7.3.1.原理
- 7.3.2.測試
- 7.3.2.1.引入依賴
- 7.3.2.2.開啟熔斷
- 7.3.2.3.編寫降級邏輯
- 7.3.2.4.默認FallBack
- 7.3.2.5.設置超時
- 7.4.服務熔斷
- 7.4.2.測試
- 8.Feign
- 8.1.簡介
- 8.2.快速入門
- 8.2.1.導入依賴
- 8.2.2.開啟feign的功能
- 8.2.3.創建Feign的客戶端
- 8.2.4.測試
- 8.3.負載均衡
- 8.4.Hystrix支持
- 9.Zuul網關
- 9.1.簡介
- 9.2.Zuul加入后的架構
- 9.3.快速入門
- 8.3.1.新建工程
- 8.3.2.編寫配置
- 8.3.3.編寫引導類
- 8.3.4.編寫路由規則
- 8.3.5.啟動測試
- 8.4.面向服務的路由
- 8.4.1.添加Eureka客戶端依賴
- 8.4.2.添加Eureka配置,獲取服務信息
- 8.4.3.開啟Eureka客戶端發現功能
- 8.4.4.修改映射配置
- 8.5.過濾器
- 8.5.1.ZuulFilter
- 8.5.2.過濾器執行生命周期
- 8.5.3.使用場景
- 8.6.自定義過濾器
6.負載均衡Ribbon
在剛才的案例中,我們啟動了一個service-provider,然后通過DiscoveryClient來獲取服務實例信息,然后獲取ip和端口來訪問。
但是實際環境中,往往會開啟很多個itcast-service-provider的集群。此時獲取的服務列表中就會有多個,到底該訪問哪一個呢?
一般這種情況下我們就需要編寫負載均衡算法,在多個實例列表中進行選擇。
不過Eureka中已經幫我們集成了負載均衡組件:Ribbon,簡單修改代碼即可使用。
什么是Ribbon:
6.1.啟動兩個服務實例
首先啟動兩個service-provider實例 一個8081 一個8082
6.2.開啟負載均衡
因為Eureka中已經集成了Ribbon,所以我們無需引入新的依賴,直接修改代碼。
修改service-consumer的引導類,在RestTemplate的配置方法上添加@LoadBalanced注解:
@Bean@LoadBalanced //開啟負載均衡public RestTemplate restTemplate(){return new RestTemplate();}修改調用方式,不再手動獲取ip和端口,而是直接通過服務名稱調用:
@Controller @RequestMapping("consumer/user") public class UserController {@Autowiredprivate RestTemplate restTemplate; // @Autowired // private DiscoveryClient discoveryClient; // eureka客戶端,可以獲取到eureka中服務的信息@ResponseBody@GetMappingpublic User queryUserById(@RequestParam Long id){//根據服務名稱獲取服務實例,有可能是集群,所以是service集合 // List<ServiceInstance> instances = discoveryClient.getInstances("service-provider");//因為只有一個service-provider 所以獲取第一個實例 // ServiceInstance serviceInstance = instances.get(0);//獲取ip和端口信息,拼接成服務地址String baseUrl = "http://service-provider/user/" + id;User user = this.restTemplate.getForObject(baseUrl, User.class);return user;} }6.3.負載均衡策略
Ribbon默認的負載均衡策略是簡單的輪詢
測試
SpringBoot幫我們提供了修改負載均衡規則的配置入口,在service-consumer的application.yml中添加如下配置:
格式是:{服務名稱}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的實現類。
再次測試,就變為了隨機
7.Hystrix
7.1.簡介
Hystrix,英文意思是豪豬,全身是刺,是一種保護機制。
Hystrix也是Netflix公司的一款組件。
主頁:https://github.com/Netflix/Hystrix/
Hystix是Netflix開源的一個延遲和容錯庫,用于隔離訪問遠程服務、第三方庫,防止出現級聯失敗。
7.2.雪崩問題
微服務中,服務間調用關系錯綜復雜,一個請求,可能需要調用多個微服務接口才能實現,會形成非常復雜的調用鏈路:
如圖,一次業務請求,需要調用A、P、H、I四個服務,這四個服務又可能調用其它服務。
如果此時,某個服務出現異常:
例如微服務I發生異常,請求阻塞,用戶不會得到響應,則tomcat的這個線程不會釋放,于是越來越多的用戶請求到來,越來越多的線程會阻塞:
服務器支持的線程和并發數有限,請求一直阻塞,會導致服務器資源耗盡,從而導致所有其它服務都不可用,形成雪崩效應。
Hystix解決雪崩問題的手段有兩個:
- 線程隔離
- 服務熔斷
7.3.線程隔離,服務降級
7.3.1.原理
線程隔離示意圖:
解讀:
Hystrix為每個依賴服務調用分配一個小的線程池,如果線程池已滿調用將被立即拒絕,默認不采用排隊.加速失敗判定時間。
用戶的請求將不再直接訪問服務,而是通過線程池中的空閑線程來訪問服務,如果線程池已滿,或者請求超時,則會進行降級處理.
服務降級:優先保證核心服務,而非核心服務不可用或弱可用。
用戶的請求故障時,不會被阻塞,更不會無休止的等待或者看到系統崩潰,至少可以看到一個執行結果(例如返回友好的提示信息) 。
服務降級雖然會導致請求失敗,但是不會導致阻塞,而且最多會影響這個依賴服務對應的線程池中的資源,對其它服務沒有響應。
觸發Hystix服務降級的情況:
- 線程池已滿
- 請求超時
7.3.2.測試
7.3.2.1.引入依賴
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>7.3.2.2.開啟熔斷
@SpringCloudApplication 包括了上述三種注解。
可以使用這個組合注解來代替之前的3個注解。
7.3.2.3.編寫降級邏輯
改造service-consumer,當目標服務的調用出現故障,希望快速失敗,給用戶一個友好提示。因此需要提前編寫好失敗時的降級處理邏輯,要使用HystixCommond來完成:
@Controller @RequestMapping("consumer/user") public class UserController {@Autowiredprivate RestTemplate restTemplate;@GetMapping@ResponseBody@HystrixCommand(fallbackMethod = "queryUserByIdFallBack")public String queryUserById(@RequestParam("id") Long id) {String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);return user;}public String queryUserByIdFallBack(Long id){return "請求繁忙,請稍后再試!";} }因為熔斷的降級邏輯方法必須跟正常邏輯方法保證:相同的參數列表和返回值聲明。失敗邏輯中返回User對象沒有太大意義,一般會返回友好提示。所以把queryById的方法改造為返回String,反正也是Json數據。這樣失敗邏輯中返回一個錯誤說明,會比較方便。
說明:
- @HystrixCommand(fallbackMethod = “queryByIdFallBack”):用來聲明一個降級邏輯的方法
測試:
當service-provder正常提供服務時,訪問與以前一致。但是當我們將service-provider停機時,會發現頁面返回了降級處理信息:
7.3.2.4.默認FallBack
可以把Fallback配置加在類上,實現默認fallback:
@Controller @RequestMapping("consumer/user") @DefaultProperties(defaultFallback = "queryUserByIdFallback") //指定一個全局的熔斷方法 public class UserController {@Autowiredprivate RestTemplate restTemplate;@ResponseBody@GetMapping@HystrixCommand //標記該方法需要熔斷public String queryUserById(@RequestParam Long id){String baseUrl = "http://service-provider/user/" + id;String user = this.restTemplate.getForObject(baseUrl, String.class);return user;}//熔斷方法,返回值要和被熔斷的方法一致public String queryUserByIdFallback(){return "服務繁忙,請稍后再試";} }- @DefaultProperties(defaultFallback = “defaultFallBack”):在類上指明統一的失敗降級方法
- @HystrixCommand:在方法上直接使用該注解,使用默認的剪輯方法。
- defaultFallback:默認降級方法,不用任何參數,以匹配更多方法,但是返回值一定一致
7.3.2.5.設置超時
可以通過hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds來設置Hystrix超時時間。該配置沒有提示。
hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 6000 # 設置hystrix的超時時間為6000ms改造服務提供者
改造服務提供者的UserController接口,隨機休眠一段時間,以觸發熔斷:
@GetMapping("{id}") public User queryUserById(@PathVariable("id") Long id) {try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}return this.userService.queryUserById(id); }7.4.服務熔斷
熔斷器,也叫斷路器,其英文單詞為:Circuit Breaker
熔斷狀態機3個狀態:
- Closed:關閉狀態,所有請求都正常訪問。
- Open:打開狀態,所有請求都會被降級。Hystix會對請求情況計數,當一定時間內失敗請求百分比達到閾值,則觸發熔斷,斷路器會完全打開。默認失敗比例的閾值是50%,請求次數最少不低于20次。
- Half Open:半開狀態,open狀態不是永久的,打開后會進入休眠時間(默認是5S)。隨后斷路器會自動進入半開狀態。此時會釋放部分請求通過,若這些請求都是健康的,則會完全關閉斷路器,否則繼續保持打開,再次進行休眠計時
7.4.2.測試
為了能夠精確控制請求的成功或失敗,在consumer的調用業務中加入一段邏輯:
@ResponseBody@GetMapping@HystrixCommand //標記該方法需要熔斷public String queryUserById(@RequestParam Long id){if(id == 1){throw new RuntimeException("服務繁忙,請稍后再試");}String baseUrl = "http://service-provider/user/" + id;String user = this.restTemplate.getForObject(baseUrl, String.class);return user;}如果參數是id為1,一定失敗,其它情況都成功。
準備兩個請求窗口:
- 一個請求:http://localhost/consumer/user/1,注定失敗
- 一個請求:http://localhost/consumer/user/2,肯定成功
當瘋狂訪問id為1的請求時(超過20次),就會觸發熔斷。斷路器會斷開,一切請求都會被降級處理。
此時訪問id為2的請求,會發現返回的也是失敗,而且失敗時間很短,只有幾毫秒左右:
8.Feign
8.1.簡介
- Feign是Netflix開發的聲明式、模板化的HTTP客戶端,其靈感來自Retrofit、JAXRS-2.0以及WebSocket。Feign可幫助我們更加便捷、優雅地調用HTTP API。
- 在Spring Cloud中,使用Feign非常簡單–創建一個接口,并在接口上添加一些注解,代碼就完成了。Feign支持多種注解,例如Feign自帶的注解或者JAX-RS注解等。
- Spring Cloud對Feign進行了增強,使Feign支持了Spring MVC注解,并整合了Ribbon
和Eureka,從而讓Feign的使用更加方便。
8.2.快速入門
8.2.1.導入依賴
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>8.2.2.開啟feign的功能
添加注解,開啟Feign功能
@SpringCloudApplication @EnableFeignClients //開啟feign客戶端 public class ServiceConsumerApplication {@Bean@LoadBalanced //開啟負載均衡public RestTemplate restTemplate(){return new RestTemplate();}public static void main(String[] args) {SpringApplication.run(ServiceConsumerApplication.class, args);}}8.2.3.創建Feign的客戶端
添加一個UserClient接口
@FeignClient(value = "service-provider") //標注該類是一個feign接口 public interface UserClient {@GetMapping("user/{id}")User queryById(@PathVariable Long id); }- 首先這是一個接口,Feign會通過動態代理,幫我們生成實現類。這點跟mybatis的mapper很像
- @FeignClient,聲明這是一個Feign客戶端,類似@Mapper注解。同時通過value屬性指定服務名稱
- 接口中的定義方法,完全采用SpringMVC的注解,Feign會根據注解幫我們生成URL,并訪問獲取結果
改造原來的調用邏輯,調用UserClient接口:
@Controller @RequestMapping("consumer/user") public class UserController {@Autowiredprivate UserClient userClient;@ResponseBody@GetMappingpublic User queryUserById(@RequestParam Long id){return this.userClient.queryById(id);}}8.2.4.測試
8.3.負載均衡
Feign中本身已經集成了Ribbon依賴和自動配置:
8.4.Hystrix支持
開啟Hystrix的集成:
feign:hystrix:enabled: true1.定義一個類UserClientFallback,實現剛才編寫的UserClient,作為fallback的處理類
@Component public class UserClientFallback implements UserClient{@Overridepublic User queryById(Long id) {User user = new User();user.setName("服務器繁忙");return user;} }2.然后在UserFeignClient中,指定剛才編寫的實現類
@FeignClient(value = "service-provider",fallback = UserClientFallback.class) //標注該類是一個feign接口 public interface UserClient {@GetMapping("user/{id}")User queryById(@PathVariable Long id); }3.測試
9.Zuul網關
使用Spring Cloud實現微服務的架構
我們使用Spring Cloud Netflix中的Eureka實現了服務注冊中心以及服務注冊與發現;而服務間通過Ribbon或Feign實現服務的消費以及均衡負載。為了使得服務集群更為健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引起的故障蔓延。
在該架構中,我們的服務集群包含:內部服務Service A和Service B,他們都會注冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,通過均衡負載公開至服務調用方。我們把焦點聚集在對外服務這塊,直接暴露我們的服務地址,這樣的實現是否合理,或者是否有更好的實現方式呢?
這樣架構的不足
-
破壞了服務無狀態特點。
為了保證對外服務的安全性,我們需要實現對服務訪問的權限控制,而開放服務的權限控制機制將會貫穿并污染整個開放服務的業務邏輯,這會帶來的最直接問題是,破壞了服務集群中REST API無狀態的特點。
從具體開發和測試的角度來說,在工作中除了要考慮實際的業務邏輯之外,還需要額外考慮對接口訪問的控制處理。
-
無法直接復用既有接口。
當我們需要對一個即有的集群內訪問接口,實現外部服務訪問時,我們不得不通過在原有接口上增加校驗邏輯,或增加一個代理調用來實現權限控制,無法直接復用原有的接口。
為了解決上面這些問題,我們需要將權限控制這樣的東西從我們的服務單元中抽離出去,而最適合這些邏輯的地方就是處于對外訪問最前端的地方,我們需要一個更強大一些的均衡負載器的 服務網關。
服務網關是微服務架構中一個不可或缺的部分。通過服務網關統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集群主體能夠具備更高的可復用性和可測試性。
9.1.簡介
9.2.Zuul加入后的架構
不管是來自于客戶端(PC或移動端)的請求,還是服務內部調用。一切對服務的請求都會經過Zuul這個網關,然后再由網關來實現 鑒權、動態路由等等操作。Zuul就是我們服務的統一入口。
9.3.快速入門
8.3.1.新建工程
添加Zuul依賴:
8.3.2.編寫配置
server:port: 10000 #服務端口 spring:application:name: api-gateway #指定服務名8.3.3.編寫引導類
通過@EnableZuulProxy注解開啟Zuul的功能:
@SpringBootApplication @EnableZuulProxy //開啟網關功能 public class ZhZuulApplication {public static void main(String[] args) {SpringApplication.run(ZhZuulApplication.class, args);}}8.3.4.編寫路由規則
server:port: 10010 #服務端口 spring:application:name: api-gateway #指定服務名 zuul:routes:service-provider: # 這里是路由id,隨意寫path: /service-provider/** # 這里是映射路徑url: http://127.0.0.1:8081 # 映射路徑對應的實際url地址將符合path 規則的一切請求,都代理到 url參數指定的地址
本例中,將 /service-provider/**開頭的請求,代理到http://127.0.0.1:8081
8.3.5.啟動測試
訪問的路徑中需要加上配置規則的映射路徑,我們訪問:http://127.0.0.1:10010/service-provider/user/1
8.4.面向服務的路由
在剛才的路由規則中,把路徑對應的服務地址寫死了!如果同一服務有多個實例的話,這樣做顯然就不合理了。應該根據服務的名稱,去Eureka注冊中心查找服務對應的所有實例列表,然后進行動態路由才對
8.4.1.添加Eureka客戶端依賴
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>8.4.2.添加Eureka配置,獲取服務信息
eureka:client:registry-fetch-interval-seconds: 5 # 獲取服務列表的周期:5sservice-url:defaultZone: http://127.0.0.1:10086/eureka8.4.3.開啟Eureka客戶端發現功能
@SpringBootApplication @EnableZuulProxy // 開啟Zuul的網關功能 @EnableDiscoveryClient public class ZuulDemoApplication {public static void main(String[] args) {SpringApplication.run(ZuulDemoApplication.class, args);} }8.4.4.修改映射配置
zuul:routes:service-provider: /provider/** #路由id,隨意寫service-consumer: /consumer/**prefix: /api #添加路有前綴通過zuul.prefix=/api來指定了路由的前綴,這樣在發起請求時,路徑就要以/api開頭。
在使用Zuul的過程中,上面講述的規則已經大大的簡化了配置項。但是當服務較多時,配置也是比較繁瑣的。因此Zuul就指定了默認的路由規則:
- 默認情況下,一切服務的映射路徑就是服務名本身。例如服務名為:service-provider,則默認的映射路徑就 是:/service-provider/**
也就是說,剛才的映射規則我們完全不配置也是可以的
8.5.過濾器
Zuul作為網關的其中一個重要功能,就是實現請求的鑒權。往往是通過Zuul提供的過濾器來實現的。
8.5.1.ZuulFilter
ZuulFilter是過濾器的頂級父類。其中定義的4個最重要的方法:
public abstract ZuulFilter implements IZuulFilter{abstract public String filterType();abstract public int filterOrder();boolean shouldFilter();// 來自IZuulFilterObject run() throws ZuulException;// IZuulFilter }- shouldFilter:返回一個Boolean值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。
- run:過濾器的具體業務邏輯。
- filterType:返回字符串,代表過濾器的類型。包含以下4種:
- pre:請求在被路由之前執行
- route:在路由請求時調用
- post:在route和errror過濾器之后調用
- error:處理請求時發生錯誤調用
- filterOrder:通過返回的int值來定義過濾器的執行順序,數字越小優先級越高。
8.5.2.過濾器執行生命周期
正常流程:
- 請求到達首先會經過pre類型過濾器,而后到達route類型,進行路由,請求就到達真正的服務提供者,執行請求,返回結果后,會到達post過濾器。而后返回響應。
異常流程:
- 整個過程中,pre或者route過濾器出現異常,都會直接進入error過濾器,在error處理完畢后,會將請求交給POST過濾器,最后返回給用戶。
- 如果是error過濾器自己出現異常,最終也會進入POST過濾器,將最終結果返回給請求客戶端。
- 如果是POST過濾器出現異常,會跳轉到error過濾器,但是與pre和route不同的是,請求不會再到達POST過濾器了。
8.5.3.使用場景
- 請求鑒權:一般放在pre類型,如果發現沒有訪問權限,直接就攔截了
- 異常處理:一般會在error類型和post類型過濾器中結合來處理。
- 服務調用時長統計:pre和post結合使用。
8.6.自定義過濾器
@Component public class LoginFilter extends ZuulFilter {/*** 過濾器類型,前置過濾器* @return*/@Overridepublic String filterType() {return "pre";}/*** 過濾器執行順序* @return*/@Overridepublic int filterOrder() {return 10;}/*** 該過濾器是否生效,true為生效* @return*/@Overridepublic boolean shouldFilter() {return true;}/*** 業務邏輯* @return* @throws ZuulException*/@Overridepublic Object run() throws ZuulException {// 獲取zuul提供的上下文對象RequestContext context = RequestContext.getCurrentContext();// 從上下文對象中獲取請求對象HttpServletRequest request = context.getRequest();// 獲取token信息String token = request.getParameter("token");// 判斷if (StringUtils.isBlank(token)){// 過濾該請求,不對其進行路由context.setSendZuulResponse(false);// 設置響應狀態碼,401context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);// 設置響應信息context.setResponseBody("request error");}// 校驗通過,把登陸信息放入上下文信息,繼續向后執行context.set("token",token);return null;} }測試
沒有傳入token
傳入token
總結
以上是生活随笔為你收集整理的SpringCloud(第二部分)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端模板引擎Thymeleaf快速入门
- 下一篇: SpringCloud(第一部分)