微服務架構是一種新型的系統架構。其設計思路是,將單體架構系統拆分爲多個能夠相互調用、配合的獨立運行的小程序。這每一個小程序對總體系統所提供的功能就稱爲微服務。html
因爲每一個微服務都具備獨立運行的,因此每一個微服務都獨立佔用一個進程。微服務間採用輕量級的HTTP RESTFUL協議通訊。每一個微服務程序不受編程語言的限制,整個系統關心的是微服務程序所提供的具體服務,並不關心其具體的實現。每一個微服務能夠有本身獨立的數據庫。便可以操做本身的獨立數據,也能夠操做總體系統的數據庫。java
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的開發便利性巧妙地簡化了分佈式系統基礎設施的開發,如服務發現註冊、配置中心、消息總線、負載均衡、斷路器、數據監控等,均可以用Spring Boot的開發風格作到一鍵啓動和部署。Spring Cloud並無重複製造輪子,它只是將各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來,經過Spring Boot風格進行再封裝屏蔽了複雜的配置和實現原理,最終給開發者流出了一套簡單易懂、易部署和易維護的分佈式系統開發工具包。mysql
https://www.springcloud.cc/git
http://www.springcloud.cn/github
本示例使用Spring的RestTemplate實現消費者對提供者的調用,並未使用到Spring Cloud,但其爲後續Spring Cloud的運行測試環境。使用MySql數據庫,使用Spring Data JPA做爲持久層技術。web
pom.xml算法
<!--Druid依賴--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency>
Controller處理器方法的返回值是做爲JSON數據響應給瀏覽器的;這個數據轉換工做是由SpringMvc的HttpMessageConverter接口完成的。spring
注意,默認狀況下,Hibernate對全部對象的查詢採用了延遲加載策略,這裏要添加@JsonIgnoreProperties註解,將延遲加載及相關的屬性忽略,即不採用延遲加載策略。若須要延遲加載,可在spring boot配置文件中專門配置。sql
Depart.java數據庫
package com.cyb.provider.bean; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Data @Entity(name = "t_depart") //實體類和"t_depart"映射關係;不寫表明實體類和同名錶映射 @JsonIgnoreProperties({"hibernateLazyInitializer","handler","fieldHandler"}) //延遲加載;第一個參數,延遲加載初始化器;第二、3,處理屬性和字段 public class Depart { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) //自動生成數據庫,自增ID private Integer id; private String name; private String dbase; }
DepartRepository.java
package com.cyb.provider.repository; import com.cyb.provider.bean.Depart; import org.springframework.data.jpa.repository.JpaRepository; //泛型:第一個指明操做實體類是誰;第二個當前數據表的自增列類型 public interface DepartRepository extends JpaRepository<Depart,Integer> { }
注意:定義的是接口
DepartService.java(業務接口)
package com.cyb.provider.Service; import com.cyb.provider.bean.Depart; import java.util.List; /** * 業務接口 */ public interface DepartService { /** * 增長 * @param depart * @return */ boolean saveDepart(Depart depart); /** * 刪除 * @param id * @return */ boolean removeDepartById(int id); /** * 修改 * @param depart * @return */ boolean modifyDepart(Depart depart); /** * 查詢id * @param id * @return */ Depart getDepartById(int id); /** * 查詢全部 * @return */ List<Depart> listAllDeparts(); }
DepartServiceImpl.java(業務接口實現類)
package com.cyb.provider.Service; import com.cyb.provider.bean.Depart; import com.cyb.provider.repository.DepartRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class DepartServiceImpl implements DepartService { @Autowired private DepartRepository repository; @Override public boolean saveDepart(Depart depart) { //返回結果有值,操做成功;沒值失敗,操做失敗 return repository.save(depart) == null ? false : true; } @Override public boolean removeDepartById(int id) { //對於deleteById方法,若DB中存在該id,必定能刪除;不存在該id,拋異常 if (repository.existsById(id)) { repository.deleteById(id); return true; } return false; } @Override public boolean modifyDepart(Depart depart) { //返回結果有值,操做成功;沒值失敗,操做失敗 return repository.save(depart) == null ? false : true; } @Override public Depart getDepartById(int id) { //getOne()方法:若其指定的id不存在,該方法將拋出異常 if (repository.existsById(id)){ return repository.getOne(id); } Depart depart=new Depart(); depart.setName("not this depart"); return depart; } @Override public List<Depart> listAllDeparts() { return repository.findAll(); } }
DepartController.java
package com.cyb.provider.controller; import com.cyb.provider.Service.DepartService; import com.cyb.provider.bean.Depart; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RequestMapping("/provider/depart") @RestController public class DepartController { @Autowired private DepartService service; @PostMapping("/save") public boolean saveHandle(@RequestBody Depart depart) { return service.saveDepart(depart); } @DeleteMapping("/del/{id}") public boolean deleteHandle(@PathVariable("id") int id) { return service.removeDepartById(id); } @PutMapping("/update") public boolean updateHandle(@RequestBody Depart depart) { return service.modifyDepart(depart); } @GetMapping("/get/{id}") public Depart getHandle(@PathVariable("id") int id) { return service.getDepartById(id); } @GetMapping("/list") public List<Depart> listHandle() { return service.listAllDeparts(); } }
補充:
1、@PathVariable:獲取請求路徑中的佔位符 二、@RestController=@ResponseBody + @Controller 2.1 若是隻是使用@RestController註解Controller,則Controller中的方法沒法返回jsp頁面,或者html,配置的視圖解析器 InternalResourceViewResolver不起做用,返回的內容就是Return 裏的內容。 2.2 若是須要返回到指定頁面,則須要用 @Controller配合視圖解析器InternalResourceViewResolver才行。 若是須要返回JSON,XML或自定義mediaType內容到頁面,則須要在對應的方法上加上@ResponseBody註解。
application.properties
# 端口號 server.port=8081 # 應用啓動是否自動建立表,默認爲false spring.jpa.generate-ddl=true # 是否在控制檯顯示sql語句,默認爲false spring.jpa.show-sql=true # 應用啓動時設置不從新建表 spring.jpa.hibernate.ddl-auto=none # 數據類型 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=root # 設置日誌輸出格式 logging.pattern.console=level-%level%msg%n # Spring Boot啓動時的日誌級別 logging.level.root=info # hibernate運行時的日誌級別 logging.level.org.hibernate=info # 在show-sql爲true時顯示sql中的動態參數值 logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace # 在show-sql爲true時顯示查詢結果 logging.level.org.hibernate.type.descriptor.sql.BasicExtractor=trace # 控制本身代碼運行時顯示的日誌級別 logging.level.com.cyb.provider=debug
Depart.java
package com.com.consumer.bean; import lombok.Data; @Data public class Depart { private Integer id; private String name; private String dbase; }
DepartCodeConfig.java
package com.com.consumer.codeconfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class DepartCodeConfig { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
DepartController.java
package com.com.consumer.controller; import com.com.consumer.bean.Depart; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import java.util.List; @RestController @RequestMapping("/consumer/depart") public class DepartController { @Autowired private RestTemplate restTemplate; @PostMapping("/save") public boolean saveHandle(Depart depart) { String url="http://localhost:8081/provider/depart/save"; return restTemplate.postForObject(url,depart,Boolean.class); } @DeleteMapping("/del/{id}") public void deleteHandle(@PathVariable("id") int id) { String url="http://localhost:8081/provider/depart/del/"+id; restTemplate.delete(url); } @PutMapping("/update") public void updateHandle(@RequestBody Depart depart) { String url="http://localhost:8081/provider/depart/update"; restTemplate.put(url,depart); } @GetMapping("/get/{id}") public Depart getHandle(@PathVariable("id") int id) { String url="http://localhost:8081/provider/depart/get/"+id; return restTemplate.getForObject(url,Depart.class); } @GetMapping("/list") public List<Depart> listHandle() { String url="http://localhost:8081/provider/depart/list"; return restTemplate.getForObject(url,List.class); } }
安裝教程:點我直達
注意,這裏要導入的依賴並不是Spring Cloud工程直接的依賴。而是由Eureka Server所依賴的,JDK9以前包含其所須要的依賴,JDK9以後,Eureka所需的依賴被踢出了,須要單獨添加依賴。JDK9以前的不須要如下依賴,我這邊演示用的JDK13,因此須要添加如下依賴。
pom.xml
<!--Eureka添加依賴開始--> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> <!--Eureka添加依賴結束-->
application.properties
server.port=8083
# 配置Eureka,開始
# 配置Eureka主機名
eureka.instance.hostname=localhost
# 指定當前主機是否須要向註冊中心註冊(不用,由於當前主機是Server,不是Client)
eureka.client.register-with-eureka=false
# 指定當前主機是否須要獲取註冊信息(不用,由於當前主機是Server,不是Client)
eureka.client.fetch-registry=false
# ${eureka.instance.hostname}和${server.port},動態引入變量的值
# 暴露服務中心地址
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
拷貝一份:01-provider-8081,重命名爲:02-provider-8081
<dependencies> <!--Eureka客戶端依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.2.RELEASE</version> </dependency> </dependencies> <!-- Eureka依賴管理模塊 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement>
能夠看出,點擊微服務狀態的超連接,能夠看到404錯誤頁,是由於在提供者配置文件中,未設置actuator的info監控終端所致。
<!-- actuator依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
這裏須要修改提供者2裏的pom.xml的版本(我已經將上面的版本修改過,這裏可忽略),因爲依賴問題致使的,解決方法,請看我另一篇博客:點我直達,這裏咱們只須要此處的版本號便可
完整pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.cyb</groupId> <artifactId>02-provider-8081</artifactId> <version>0.0.1-SNAPSHOT</version> <name>02-provider-8081</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- actuator依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--Druid依賴--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!--Eureka客戶端依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.2.RELEASE</version> <!-- 以前版本 --> <!-- <version>2.0.2.RELEASE</version> --> </dependency> </dependencies> <!-- Eureka依賴管理模塊 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
消費者將使用提供者暴露的服務名稱(spring.application.name)來消費服務。
複製01-consumer-8082,重複名爲02-consumer-8082,具體步驟,詳見上面提供者建立工程方式。
pom.xml
<!--Eureka客戶端依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.2.RELEASE</version> </dependency> <!-- Eureka依賴管理模塊 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement>
<!-- 若配置info,需添加如下依賴,不配置可忽略,案例中我是加了!!! --> <!-- actuator依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
server.port=8082
# 指定當前微服務對外(消費者)暴露的名稱
spring.application.name=cyb-consumer-depart
# 指定Eureka註冊中心
eureka.client.service-url.defaultZone=http://localhost:8083/eureka
即經過「服務發現客戶端」讀取EurekaServer中的服務列表,獲取指定名稱的微服務詳情。
在任何微服務的提供者或消費者處理器中,只要獲取到「服務發現Client」,便可讀取到Eureka Server的微服務列表。案例中修改02-provider-8081中的處理器類
@GetMapping("/discovery") public Object discoveryHandle(){ // 獲取服務註冊列表中全部的微服務名稱 List<String> springApplicationNames = client.getServices(); for (String name:springApplicationNames){ // 獲取提供指定微服務名稱的全部提供者主機 List<ServiceInstance> instances = client.getInstances(name); for (ServiceInstance instance:instances){ String host = instance.getHost(); int port = instance.getPort(); System.out.println(MessageFormat.format("host:{0},port:{1}",host,port)); } } return springApplicationNames; }
單個EurekaServer 不只吞吐量有限,還存在單點問題,因此咱們會使用EurekaServer集羣,這裏要搭建的EurekaServer集羣中包含3個EurekaServer節點,其端口號分別爲8123,8456,8789
因爲這些Eureka 在這裏都是運行在當前的這一臺主機,而Eureka管理頁面中顯示的僅僅是Eureka主機的域名,不顯示端口號,因此爲了在Eureka管理頁面能夠區分Eureka集羣中各個主機,咱們這裏先爲每個Eureka節點設置一個不一樣的域名。
須要修改host文件,爲了節點時間,不會的童鞋,請看我另外一篇博客有講解到如何設置host文件:點我直達
複製3份01-eurekaserver-8083,並重命名,分別爲:02-eurekaserver-8123;02-eurekaserver-8456;02-eurekaserver-8789;
注:「,」隔開的中間不能有空格!!!集羣中3個項目都要相應修改!!!
這裏無需修改提供者工程,只需修改消費者工程便可。
複製02-consumer-8082,並重命名爲03-consumer-feign-8082
pom.xml
<!-- openfeign依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.2.RELEASE</version> </dependency>
package com.com.consumer.service; import com.com.consumer.bean.Depart; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 業務接口 */ // 指定當前Service所綁定的提供者微服務名稱 @FeignClient("cyb-provider-depart") @RequestMapping("/provider/depart") public interface DepartService { /** * 增長 * @param depart * @return */ @PostMapping("/save") boolean saveDepart(Depart depart); /** * 刪除 * @param id * @return */ @DeleteMapping("/del/{id}") boolean removeDepartById(@PathVariable("id") int id); /** * 修改 * @param depart * @return */ @PutMapping("/update") boolean modifyDepart(Depart depart); /** * 查詢id * @param id * @return */ @GetMapping("/get/{id}") Depart getDepartById(@PathVariable("id") int id); /** * 查詢全部 * @return */ @GetMapping("/list") List<Depart> listAllDeparts(); }
package com.com.consumer.controller; import com.com.consumer.bean.Depart; import com.com.consumer.service.DepartService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/consumer/depart") public class DepartController { @Autowired(required = false) private DepartService service; @PostMapping("/save") public boolean saveHandle(Depart depart) { return service.saveDepart(depart); } @DeleteMapping("/del/{id}") public boolean deleteHandle(@PathVariable("id") int id) { return service.removeDepartById(id); } @PutMapping("/update") public boolean updateHandle(@RequestBody Depart depart) { return service.modifyDepart(depart); } @GetMapping("/get/{id}") public Depart getHandle(@PathVariable("id") int id) { return service.getDepartById(id); } @GetMapping("/list") public List<Depart> listHandle() { return service.listAllDeparts(); } }
這裏爲了演示方便,不用eureka集羣了,效果是同樣的
分別啓動:01-eurekaserver-8083;02-provider-8081(需修改eureka註冊中心地址);03-consumer-feign-8082(需修改eureka註冊中心地址)
上個例子經過OpenFeign接口來消費微服務,但沒體現負載均衡的功能。
負載均衡須要搭建出多個服務提供者,搭建系統以下:一個微服務由3個提供者提供,而消費者使用Ribbon對這3個提供者進行負載均衡訪問。Ribbon首先會選擇同一區域訪問量較少的EurekaService,而後再從該EurekaServer中獲取到服務列表,而後再根據用戶指定的負載均衡策略選擇一個服務提供者。
分別爲:demo1;demo2;demo3
三個庫中,三個表,3條數據
複製02-provider-8081,並重命名:02-provider-8091;02-provider-8092;02-provider-8093,修改相應端口號,鏈接的數據庫等信息
啓動依次啓動:01-eurekaserver-8083;02-provider-8091;02-provider-8092;02-provider-8093;03-consumer-feign-8082
咱們發現調用消費者的時候,消費者依次調用提供者一、提供者二、提供者3,這是由於默認採用負載均衡算法是輪詢,他還支持其餘的算法。
Ribbon提供了多種負載均衡策略算法,例如輪詢算法、隨機算法、響應時間加權算法等。默認採用的是輪詢算法,也能夠指定Ribbon默認算法。
Ribbon的負載均衡算法須要實現IRule接口,而該接口中的核心方法即choose()方法,即對提供者的選擇方式就是在該方法中體現的。
Ribbon的內置可用負載均衡算法有七種。
一、RoundRobinRule
輪詢策略。Ribbon默認採用的策略
二、BestAvailableRule
選擇併發量最小的provider,即鏈接的消費者數量最少的provider。其會遍歷服務列表中的每個provider,選擇當前鏈接數量minimalConcurrentConnections最小的provider。
三、AvailabilityFilteringRule
過濾掉因爲連續鏈接或讀故障而處於短路器跳閘狀態的provider,或已經超過鏈接極限的provider,對剩餘provider採用輪詢策略。
四、ZoneAvoidanceRule
複合判斷provider所在區域的性能及provider的可用性選擇服務器。
五、RandomRule
隨機策略,從全部可用的provider中隨機選一個。
六、RetryRule
先按照RoundRobinRule策略獲取provider,若後去失敗,則在指定的時限內重試。默認的時限爲500毫秒。
七、WeightedResponseTimeRule
權重響應時間策略,根據每一個provider的平均響應時間計算其權重,響應時間越快權重越大,被選中的概率就越高,在剛啓動時採用輪詢策略,後面就會根據權重從新進行選擇。
Ribbon默認採用的是RoundRobinRule,即輪詢策略。只須要在啓動類中添加以下代碼便可
該負載均衡策略的思路是:從全部可用的provider中排出掉指定端口號的provider,剩餘provider進行隨機選擇。
CustomRule.java
package com.com.consumer.irule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.Server; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * 自定義負載均衡算法 * 從全部可用provider中排出掉指定端口號的provider,剩餘provider進行隨機選擇 */ public class CustomRule implements IRule { private ILoadBalancer lb; /** * 要排除提供者端口號集合 */ private List<Integer> excludePorts; public CustomRule() { } public CustomRule(List<Integer> excludePorts) { this.excludePorts = excludePorts; } @Override public Server choose(Object key) { // 獲取全部可用的提供者 List<Server> servers = lb.getReachableServers(); // 獲取全部排出了指定端口號的提供者 List<Server> availableServers = this.getAvailableServers(servers); // 從剩餘的提供者中隨機獲取可用的提供者 return this.getAvailableRandomServers(availableServers); } // 獲取全部排出了指定端口號的提供者 private List<Server> getAvailableServers(List<Server> servers) { // 沒有要排除的Server,則直接將全部可用Servers返回 if (excludePorts == null || excludePorts.size() == 0) return servers; // 定義一個集合,用於存放排出了指定端口號的Server List<Server> aservers = new ArrayList<>(); boolean flag; for (Server server : servers) { flag = true; for (Integer port : excludePorts) { if (server.getPort() == port) { flag = false; break; } } // 若flag爲false,說明上面的for循環執行了break,說明當前遍歷的Server是要排除掉的 if (flag) aservers.add(server); } return aservers; } // 從剩餘的提供者中隨機獲取可用的提供者 private Server getAvailableRandomServers(List<Server> availableServers) { // 獲取一個[0,availableServers.size()]的隨機整數 int index = new Random().nextInt(availableServers.size()); return availableServers.get(index); } @Override public void setLoadBalancer(ILoadBalancer lb) { this.lb = lb; } @Override public ILoadBalancer getLoadBalancer() { return lb; } }
若要了解服務熔斷,須要先了解雪崩效應與服務雪崩。
分佈式系統中很容易出現雪崩效應
在IO型服務中,假設服務A依賴服務B和服務C,而B服務和C服務有可能依賴其餘的服務,繼續下去會使得調用鏈路過長,技術上稱1->N扇出。
若是在A的鏈路上某個或幾個被調用的子服務不可用或延遲較高,則會致使調用A服務的請求被堵住。
堵住的A請求會消耗佔用系統的進程、IO等資源,當對A服務的請求愈來愈多,佔用的計算機資源愈來愈多,會致使系統瓶頸出現,形成其餘的請求一樣不可用,最終致使業務系統崩潰,這種現象稱爲雪崩效應。
例如一個汽車生產線,生產不一樣的汽車,須要使用不一樣的零件。若是某個零件由於種種緣由沒法及時供給,而沒有該零件,則後續的好多已經到貨的零件也沒法安裝。一個零件的缺失形成整臺車沒法裝配,陷入等待零件的狀態,直到零件到位,才能繼續組裝。
此時若是有不少個車型都須要這個零件,那麼整個工廠都將陷入等待的狀態,而前述已經生成好多的汽車部件,暫不能安裝的其餘零件,將因爲等待而佔用大量資金、場地等資源。
一個零件最終致使全部生產陷入癱瘓,這就是雪崩效應。
雪崩效應發生在分佈式SOA(Service-Oriented Architecture,面向服務的架構)系統中,則稱爲服務雪崩。
大量用戶請求出現異常所有陷入阻塞的狀況,即服務發生雪崩的狀況。
舉個例子,一個依賴30個微服務的系統,每一個服務99.99%可用。則整個系統的可用性爲99.99%的30次方,約爲99.7%。爲何是30次方呢?若系統依賴於2個微服務,一個微服務的可用率爲99.99%,那麼,兩個微服務的組合的可用率爲99.99%*99.99%,同理,30個微服務,每一個微服務的可用率爲99.99%,則這30個微服務組合後的可用性爲99.99%的30次方。
也就是說,整個系統會存在0.3%的失敗率。若存在一億次請求,那麼將會有30萬次失敗。隨着服務依賴數量的增多,服務不穩定的機率會成指數升高。
熔斷機制是服務雪崩的一種有效解決方案。當服務消費者所請求的提供者暫不能提供服務時,消費者會被阻塞,且長時間佔用請求鏈路。爲了防止這種狀況的發生,當在設定閾值限制到達時,仍未得到提供者的服務,則系統將經過斷路器直接將此請求鏈路斷開。這種像熔斷「保險絲」同樣的解決方案稱爲熔斷機制。
https://github.com/Netflix/Hystrix
在訪問分佈式系統中,常常會發生如下兩種狀況:
一、當整個微服務架構總體的負載超出了預設的上限閾值,或即將到來的流量預計將會超過預設的閾值時,爲了保證重要或基本的服務能正常運行,咱們能夠將一些不重要或不緊急的服務進行延遲使用或暫停使用。這就是服務熔斷,相似於主動拉電閘的服務熔斷。此時,如有消費者消費這些延遲/暫停使用的服務則會出現阻塞,等待提供者的響應。
二、當消費者訪問某微服務時,因爲網絡或其餘緣由,提供者向消費者響應過慢,出現服務超時或根本就沒有響應時,這也是一種服務熔斷,相似於保險絲自動熔斷的服務熔斷。此時消費者會被迫阻塞,等待提供者的響應。
在發生服務熔斷時,不只用戶體驗不好,其還佔用了大量的系統資源。爲了解決這個問題,在編寫消費者端代碼時就設置了預案:在消費者端給出一種默認的、臨時的預處理方案,可以給出消費者一個能夠接受的結果。即,對於用戶(指的是人,並不是指消費者端)來講,其所消費的服務並不是由應當提供服務的提供者端給出,而是由服務消費者臨時給出,服務質量降級了。提供者端的「服務熔斷」與消費者端的「本地服務」,共同構成了「服務降級」。
簡單來講服務降級指的是,當服務的提供者沒法正常提供服務時,爲了增長用戶體驗,保證真個系統可以正常運行,由服務消費者端調用本地操做,暫時給出用戶效應結果的狀況。
一、建立工程
複製02-consumer-8082,並重命名04-consumer-hystrix-8082
二、添加依賴
<!-- hystrix依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.2.RELEASE</version> </dependency>
三、修改處理器
注:實際中一個方法對應一個處理函數
四、啓動類上添加註解
爲了演示方法,只開啓eureka註解中心和消費者
複製03-consumer-feign-8082並重命名04-consumer-feign-hystrix-8082
降級處理類須要實現FallbackFactory接口,該接口的泛型爲Feign接口。該類能夠定義在任意包下,不過,通常會與Feign解口定義在同一包下。
該類須要使用@Component註解,表示要將其交給Spring容器來管理。
在配置文件中添加以下內容,沒有自動提示
注:方法級別的優先級小於類級別的優先級
https://github.com/Netflix/zuul
Zuul主要提供了對請求的路由有過濾功能。路由功能主要指,將外部請求轉發到具體的微服務實例上,是外部訪問微服務的統一入口。過濾功能主要指,對請求的處理過程進行干預,對請求進行校驗、服務聚合等處理。
Zuul與Eureka進行整合,將Zuul自身註冊爲Eureka服務治理下的應用,從Eureka Server中獲取到其餘微服務信息,使外部對於微服務的訪問都是經過Zull進行轉發的。
上面測試,咱們發現,直接將服務名稱暴露給了消費者,爲了保護和隱藏服務名稱,能夠爲其配置一個映射路徑,將這個映射路徑暴露給消費者便可。
server.port=9000
# 指定Eureka註冊中心
eureka.client.service-url.defaultZone=http://localhost:8083/eureka
spring.application.name=cyb-zuul-depart
# zuul:設置zuul路由規則
# somedepart.service-id:指定要替換的微服務名稱
zuul.routes.somedepart.service-id=cyb-consumer-depart
# 指定替換使用的路徑
zuul.routes.somedepart.path=/cyb/**
對於該配置須要注意如下幾點:
設置過zuul路由規則後,兩種方式,同樣能夠訪問。
以上配置雖然可使用映射路徑訪問微服務,可是經過原來的服務名稱仍能夠訪問到微服務,即以上配置並無隱藏和保護了原來的微服務名稱。能夠在配置文件中設置忽略微服務屬性,替換原有的微服務名稱使用。兩種方式:一、忽略指定微服務;二、忽略全部微服務
在配置文件中指定要忽略的微服務
此時經過微服務名稱已沒法訪問到微服務了,但經過映射路徑是能夠正常訪問的
注:效果和上面忽略指定的微服務是同樣的!
通常狀況下咱們會在映射路徑前添加一個前綴用於表示模塊信息或公司名稱等,而該前綴對於各個微服務來講通常都是須要的,因此咱們能夠爲映射路徑統一配置前綴。
server.port=9000
# 指定Eureka註冊中心
eureka.client.service-url.defaultZone=http://localhost:8083/eureka
spring.application.name=cyb-zuul-depart
# zuul:設置zuul路由規則
# somedepart.service-id:指定要替換的微服務名稱
zuul.routes.somedepart.service-id=cyb-consumer-depart
# 指定替換使用的路徑
zuul.routes.somedepart.path=/cyb/**
# 指定要忽略的微服務
# zuul.ignored-services=cyb-consumer-depart
# 忽略全部的微服務
zuul.ignored-services=*
# 指定訪問的統一前綴
zuul.prefix=/test
百度雲盤
連接:https://pan.baidu.com/s/1OYwtq9O-3dF5fEuADNcIhA 密碼:pj5a