SpringCloud微服務基礎

lu_s發表於2018-09-29

單點系統架構

傳統專案架構

傳統專案分為三層架構,將業務邏輯層、資料庫訪問層、控制層放入在一個專案中。

優點:適合於個人或者小團隊開發,不適合大團隊開發。

分散式專案架構

根據業務需求進行拆分成N個子系統,多個子系統相互協作才能完成業務流程子系統之間通訊使用RPC遠端通訊技術。

優點:

1.把模組拆分,使用介面通訊,降低模組之間的耦合度。

2.把專案拆分成若干個子專案,不同的團隊負責不同的子專案。

3.增加功能時只需要再增加一個子專案,呼叫其它系統的介面就可以。

4.可以靈活的進行分散式部署。

有優點就有缺點,缺點如下:

1.系統之間互動需要使用遠端通訊,介面開發增加工作量。

2.各個模組有一些通用的業務邏輯無法共用。

為了解決上面分散式架構的缺點,我們引入了soa架構,SOA:Service Oriented Architecture面向服務的架構。也就是把工程拆分成服務層、表現層兩個工程。服務層中包含業務邏輯,只需要對外提供服務即可。表現層只需要處理和頁面的互動,業務邏輯都是呼叫服務層的服務來實現。

什麼是專案叢集

多臺伺服器部署相同應用構成一個叢集

作用:通過負載均衡裝置共同對外提供服務

RPC遠端呼叫

RPC 的全稱是 Remote Procedure Call 是一種程式間通訊方式。
它允許程式呼叫另一個地址空間(通常是共享網路的另一臺機器上)的過程或函式,而不用程式設計師顯式編碼這個遠端呼叫的細節。即無論是呼叫本地介面/服務的還是遠端的介面/服務,本質上編寫的呼叫程式碼基本相同。
比如兩臺伺服器A,B,一個應用部署在A伺服器上,想要呼叫B伺服器上應用提供的函式或者方法,由於不在一個記憶體空間,不能直接呼叫,這時候需要通過就可以應用RPC框架的實現來解決

restful、soap、rpc

(1)restful是一種架構設計風格,提供了設計原則和約束條件,而不是架構。而滿足這些約束條件和原則的應用程式或設計就是 RESTful架構或服務。
(2)soap象訪問協議是一種資料交換協議規範,
是一種輕量的、簡單的、基於XML的協議的規範。SOAP協議和HTTP協議一樣,都是底層的通訊協議,只是請求包的格式不同而已,SOAP包是XML格式的。
soap

基於xml並封裝成了符合http協議,因此,它符合任何路由器、 防火牆或代理伺服器的要求。
soap可以使用任何語言來完成,只要傳送正確的soap請求即可,基於soap的服務可以在任何平臺無需修改即可正常使用。
(3)RPC就是從一臺機器(客戶端)上通過引數傳遞的方式呼叫另一臺機器(伺服器)上的一個函式或方法(可以統稱為服務)並得到返回的結果。
RPC 會隱藏底層的通訊細節(不需要直接處理Socket通訊或Http通訊)
RPC 是一個請求響應模型。客戶端發起請求,伺服器返回響應(類似於Http的工作方式)
RPC 在使用形式上像呼叫本地函式(或方法)一樣去呼叫遠端的函式(或方法)。

rpc遠端呼叫框架

幾種比較典型的RPC的實現和呼叫框架。
(1)RMI實現,利用java.rmi包實現,基於Java遠端方法協議(Java Remote Method Protocol)
和java的原生序列化。
(2)Hessian,是一個輕量級的remoting onhttp工具,使用簡單的方法提供了RMI的功能。 基於HTTP協議,採用二進位制編解碼。
(3)thrift是一種可伸縮的跨語言服務的軟體框架。thrift允許你定義一個描述檔案,描述資料型別和服務介面。依據該檔案,編譯器方便地生成RPC客戶端和伺服器通訊程式碼。

(4)SpringCloud 為開發人員提供了快速構建分散式系統的一些工具,包括配置管理、服務發現、斷路器、路由、微代理、事件匯流排、全域性鎖、決策競選、分散式會話等等。

(4) Dubbo是阿里巴巴公司開源的一個高效能優秀的服務框架,使得應用可通過高效能的 RPC 實現服務的輸出和輸入功能,可以和 Spring框架無縫整合。

面向於服務架構

什麼是SOA

業務系統分解為多個元件,讓每個元件都獨立提供離散,自治,可複用的服務能力

通過服務的組合和編排來實現上層的業務流程

作用:簡化維護,降低整體風險,伸縮靈活

微服務架構

什麼是微服務架構

架構設計概念,各服務間隔離(分散式也是隔離),自治(分散式依賴整體組合)其它特性(單一職責,邊界,非同步通訊,獨立部署)是分散式概念的跟嚴格執行

SOA到微服務架構的演進過程

作用:各服務可獨立應用,組合服務也可系統應用(巨石應用[monolith]的簡化實現策略-平臺思想)

SOA架構與微服務架構區別

SOA架構主要針對企業級、採用ESB服務(ESB企業服務匯流排),非常重,需要序列化和反序列化,採用XML格式傳輸。

微服務架構主要網際網路公司,輕量級、小巧,獨立執行,基於Http+Rest+JSON格式傳輸。

ESB也可以說是傳統中介軟體技術與XML、Web服務等技術相互結合的產物。

SpringCloud

SpringCloud 為開發人員提供了快速構建分散式系統的一些工具,包括配置管理、服務發現、斷路器、路由、負載均衡、微代理、事件匯流排、全域性鎖、決策競選、分散式會話等等。它執行環境簡單,可以在開發人員的電腦上跑。另外說明spring cloud是基於Springboot的,所以需要開發中對Springboot有一定的瞭解,如果不瞭解的話可以瞭解一下咕泡學院或者加895244712這個群學習有關spring boot的相關知識,都是免費的教程。

服務提供者與消費關係

服務提供者:提供服務被人呼叫

消費者:呼叫被人服務

服務的註冊與發現(Eureka )

在這裡,我們需要用的的元件上Spring Cloud Netflix的Eureka ,eureka是一個服務註冊和發現模組。

什麼是Eureka

官方的介紹在這裡Eureka wiki。Eureka是Netflix開源的一個RESTful服務,主要用於服務的註冊發現。Eureka由兩個元件組成:Eureka伺服器和Eureka客戶端。Eureka伺服器用作服務註冊伺服器。Eureka客戶端是一個java客戶端,用來簡化與伺服器的互動、作為輪詢負載均衡器,並提供服務的故障切換支援。Netflix在其生產環境中使用的是另外的客戶端,它提供基於流量、資源利用率以及出錯狀態的加權負載均衡。
在我看來,Eureka的吸引力來源於以下幾點:

開源:大家可以對實現一探究竟,甚至修改原始碼。

可靠:經過Netflix多年的生產環境考驗,使用應該比較靠譜省心

功能齊全:不但提供了完整的註冊發現服務,還有Ribbon等可以配合使用的服務。

基於Java:對於Java程式設計師來說,使用起來,心裡比較有底。

spring cloud可以使用Spring Cloud, 與Eureka進行了很好的整合,使用起來非常方便。

實現服務註冊

建立EureKaserver 專案

Maven依賴

  <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
 
	<dependencies>
		<!--eureka server -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka-server</artifactId>
		</dependency>
		<!-- spring boot test -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.RC1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
複製程式碼

配置application.yml

server:
  port: 8888
eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
複製程式碼

啟動EurekaServer

@SpringBootApplication
@EnableEurekaServer
public class App {
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}
複製程式碼

開啟eureka server 介面的

http://localhost:8761 ,介面如下:

No application available 沒有服務被發現 ……^_^
因為沒有註冊服務當然不可能有服務被發現了。

實現案例訂單服務呼叫會員服務查詢使用者資訊

服務提供者

建立一個服務提供者 會員服務工程 (eurekaMember),提供會員查詢服務資訊

建立專案service-member

Maven依賴

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.RC1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
複製程式碼

application.yml配置

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8888/eureka/
server:
  port: 8762
spring:
  application:
    name: service-member
複製程式碼

服務介面

@RestController
public class MemberController {
 
	@RequestMapping("/getUserList")
	public List<String> getUserList() {
		List<String> listUser = new ArrayList<String>();
		listUser.add("zhangsan");
		listUser.add("lisi");
		listUser.add("yushengjun");
		return listUser;
	}
 
}
複製程式碼

釋出服務

通過註解@EnableEurekaClient 表明自己是一個eurekaclient.

 
@SpringBootApplication
 
@EnableEurekaClient
 
public class AppMember {
 
 
 
public static void main(String[] args) {
 
SpringApplication.run(AppMember.class, args);
 
}
 
 
 
}
複製程式碼

演示效果

需要指明spring.application.name,這個很重要,這在以後的服務與服務之間相互呼叫一般都是根據這個name 。
啟動工程,開啟127.0.0.1:8888 ,即eureka server 的網址:

你會發現一個服務已經註冊在服務中了,服務名為SERVICE-HI ,埠為7862

這時開啟 http://127.0.0.1:8762/getUserList ,你會在瀏覽器上看到 :

["zhangsan","lisi","yushengjun"]

服務消費者

建立專案sercice-order

Maven依賴

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
 
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
 
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-ribbon</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
 
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
 
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.RC1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
 
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
 
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
複製程式碼

application.yml配置

 
eureka:
 
  client:
 
    serviceUrl:
 
      defaultZone: http://localhost:8888/eureka/
 
server:
 
  port: 8764
 
spring:
 
  application:
 
    name: service-order
複製程式碼

編寫service,呼叫service-member

 
@SuppressWarnings("unchecked")
 
@Service
 
public class MemberService {
 
@Autowired
 
RestTemplate restTemplate;
 
 
 
public List<String> getOrderByUserList() {
 
return restTemplate.getForObject("http://service-member/getUserList", List.class);
 
}
 
}
複製程式碼

演示效果

 
@EnableEurekaClient
 
@SpringBootApplication
 
public class AppOrder {
 
 
 
public static void main(String[] args) {
 
SpringApplication.run(AppOrder.class, args);
 
}
 
 
 
@Bean
 
@LoadBalanced
 
RestTemplate restTemplate() {
 
return new RestTemplate();
 
}
 
 
 
}
複製程式碼

在工程的啟動類中,通過@EnableDiscoveryClient向服務中心註冊;並且向程式的ioc注入一個bean: restTemplate;並通過@LoadBalanced註解表明這個restRemplate開啟負載均衡的功能。

使用ribbon實現負載均衡

啟動兩個會員服務工程,埠號分別為8762、8763,訂單服務 使用負載均衡策略輪訓到會員服務介面。

什麼是ribbon

ribbon是一個負載均衡客戶端 類似nginx反向代理,可以很好的控制htt和tcp的一些行為。Feign預設整合了ribbon。

修改會員服務工程程式碼區分埠專案

	@Value("${server.port}")
	private String serverPort;
 
	@RequestMapping("/getUserList")
	public List<String> getUserList() {
		List<String> listUser = new ArrayList<String>();
		listUser.add("zhangsan");
		listUser.add("lisi");
		listUser.add("yushengjun");
		listUser.add("埠號:"+serverPort);
		return listUser;
	}
複製程式碼

開啟ribbon

@LoadBalanced註解表明這個restRemplate開啟負載均衡的功能。

服務消費者(Feign)

什麼是Feign

Feign是一個宣告式的偽Http客戶端,它使得寫Http客戶端變得更簡單。使用Feign,只需要建立一個介面並註解。它具有可插拔的註解特性,可使用Feign 註解和JAX-RS註解。Feign支援可插拔的編碼器和解碼器。Feign預設整合了Ribbon,並和Eureka結合,預設實現了負載均衡的效果。

簡而言之:

  • Feign 採用的是基於介面的註解
  • Feign 整合了ribbon

建立service-order-feign工程

Maven依賴

 
<parent>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-parent</artifactId>
 
<version>1.5.2.RELEASE</version>
 
<relativePath /> <!-- lookup parent from repository -->
 
</parent>
 
 
 
<properties>
 
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 
<java.version>1.8</java.version>
 
</properties>
 
 
 
<dependencies>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-starter-eureka</artifactId>
 
</dependency>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-starter-ribbon</artifactId>
 
</dependency>
 
<dependency>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-web</artifactId>
 
</dependency>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-starter-feign</artifactId>
 
</dependency>
 
<dependency>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-test</artifactId>
 
<scope>test</scope>
 
</dependency>
 
</dependencies>
 
 
 
<dependencyManagement>
 
<dependencies>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-dependencies</artifactId>
 
<version>Dalston.RC1</version>
 
<type>pom</type>
 
<scope>import</scope>
 
</dependency>
 
</dependencies>
 
</dependencyManagement>
 
 
 
<build>
 
<plugins>
 
<plugin>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-maven-plugin</artifactId>
 
</plugin>
 
</plugins>
 
</build>
 
 
 
<repositories>
 
<repository>
 
<id>spring-milestones</id>
 
<name>Spring Milestones</name>
 
<url>https://repo.spring.io/milestone</url>
 
<snapshots>
 
<enabled>false</enabled>
 
</snapshots>
 
</repository>
 
</repositories>
複製程式碼

application.yml配置

 
eureka:
 
  client:
 
    serviceUrl:
 
      defaultZone: http://localhost:8888/eureka/
 
server:
 
  port: 8765
 
spring:
 
  application:
 
    name: service-order-feign
複製程式碼

編寫service,呼叫service-member

 
@FeignClient("service-member")
 
public interface MemberFeign {
 
@RequestMapping("/getUserList")
 
public List<String> getOrderByUserList();
 
}
複製程式碼

演示效果@FeignClient 需要呼叫服務名稱,@RequestMapping服務請求名稱

 
@SpringBootApplication
 
@EnableEurekaClient
 
@EnableFeignClients
 
public class OrderFeignApp {
 
 
 
public static void main(String[] args) {
 
SpringApplication.run(OrderFeignApp.class, args);
 
}
 
 
 
}
複製程式碼

路由閘道器(zuul)

什麼是閘道器

Zuul的主要功能是路由轉發和過濾器。路由功能是微服務的一部分,比如/api/user轉發到到user服務,/api/shop轉發到到shop服務。zuul預設和Ribbon結合實現了負載均衡的功能, 類似於nginx轉發。

搭建SpringCloud閘道器

建立工程service-zuul

Maven依賴

建立工程service-zuul

 
<parent>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-parent</artifactId>
 
<version>1.5.2.RELEASE</version>
 
<relativePath /> <!-- lookup parent from repository -->
 
</parent>
 
 
 
<properties>
 
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 
<java.version>1.8</java.version>
 
</properties>
 
 
 
<dependencies>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-starter-eureka</artifactId>
 
</dependency>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-starter-zuul</artifactId>
 
</dependency>
 
<dependency>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-web</artifactId>
 
</dependency>
 
 
 
<dependency>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-test</artifactId>
 
<scope>test</scope>
 
</dependency>
 
</dependencies>
 
 
 
<dependencyManagement>
 
<dependencies>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-dependencies</artifactId>
 
<version>Dalston.RC1</version>
 
<type>pom</type>
 
<scope>import</scope>
 
</dependency>
 
</dependencies>
 
</dependencyManagement>
 
 
 
<build>
 
<plugins>
 
<plugin>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-maven-plugin</artifactId>
 
</plugin>
 
</plugins>
 
</build>
 
 
 
<repositories>
 
<repository>
 
<id>spring-milestones</id>
 
<name>Spring Milestones</name>
 
<url>https://repo.spring.io/milestone</url>
 
<snapshots>
 
<enabled>false</enabled>
 
</snapshots>
 
</repository>
 
</repositories>
複製程式碼

application.yml配置

 
eureka:
 
  client:
 
    serviceUrl:
 
      defaultZone: http://localhost:8888/eureka/
 
server:
 
  port: 8769
 
spring:
 
  application:
 
    name: service-zuul
 
zuul:
 
  routes:
 
    api-a:
 
      path: /api-member/**
      service-id: service-member
    api-b:
      path: /api-order/**
      service-id: service-order
複製程式碼

傳送請求http://127.0.0.1:8769/api-member/getMemberAll

轉發到http://127.0.0.1:8762/getMemberAll

開啟閘道器 @EnableZuulProxy

服務過濾

 
@Component
 
public class MyFilter extends ZuulFilter {
 
 
 
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
 
 
 
@Override
 
public String filterType() {
 
return "pre";
 
}
 
 
 
@Override
 
public int filterOrder() {
 
return 0;
 
}
 
 
 
public boolean shouldFilter() {
 
return true;
 
}
 
 
 
public Object run() {
 
RequestContext ctx = RequestContext.getCurrentContext();
 
HttpServletRequest request = ctx.getRequest();
 
log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
 
Object accessToken = request.getParameter("token");
 
if (accessToken != null) {
 
return null;
 
}
 
log.warn("token is empty");
 
ctx.setSendZuulResponse(false);
 
ctx.setResponseStatusCode(401);
 
try {
 
ctx.getResponse().getWriter().write("token is empty");
 
} catch (Exception e) {
 
}
 
return null;
 
 
 
}
 
}
複製程式碼

如果請求引數中沒有傳入token引數 直接返回報錯資訊

傳入token引數可以正常訪問請求

斷路器(Hystrix)

為什麼需要 Hystrix?

在微服務架構中,我們將業務拆分成一個個的服務,服務與服務之間可以相互呼叫(RPC)。為了保證其高可用,單個服務又必須叢集部署。由於網路原因或者自身的原因,服務並不能保證服務的100%可用,如果單個服務出現問題,呼叫這個服務就會出現網路延遲,此時若有大量的網路湧入,會形成任務累計,導致服務癱瘓,甚至導致服務“雪崩”。為了解決這個問題,就出現斷路器模型。大家可以加895244712這個群獲取專門的PDF版本的詳盡資料。

Hystrix 是一個幫助解決分散式系統互動時超時處理和容錯的類庫, 它同樣擁有保護系統的能力.什麼是服務雪崩

分散式系統中經常會出現某個基礎服務不可用造成整個系統不可用的情況, 這種現象被稱為服務雪崩效應. 為了應對服務雪崩, 一種常見的做法是手動服務降級. 而Hystrix的出現,給我們提供了另一種選擇.

服務雪崩應對策略

針對造成服務雪崩的不同原因, 可以使用不同的應對策略:

  1. 流量控制
  2. 改進快取模式
  3. 服務自動擴容
  4. 服務呼叫者降級服務

流量控制 的具體措施包括:

  • 閘道器限流
  • 使用者互動限流
  • 關閉重試

服務雪崩解決辦法

1.服務雪崩的原因

(1)某幾個機器故障:例如機器的硬驅動引起的錯誤,或者一些特定的機器上出現一些的bug(如,記憶體中斷或者死鎖)。

(2)伺服器負載發生變化:某些時候服務會因為使用者行為造成請求無法及時處理從而導致雪崩,例如阿里的雙十一活動,若沒有提前增加機器預估流量則會造伺服器壓力會驟然增大二掛掉。

(3)人為因素:比如程式碼中的路徑在某個時候出現bug

2.解決或緩解服務雪崩的方案

一般情況對於服務依賴的保護主要有3中解決方案:

(1)熔斷模式:這種模式主要是參考電路熔斷,如果一條線路電壓過高,保險絲會熔斷,防止火災。放到我們的系統中,如果某個目標服務呼叫慢或者有大量超時,此時,熔斷該服務的呼叫,對於後續呼叫請求,不在繼續呼叫目標服務,直接返回,快速釋放資源。如果目標服務情況好轉則恢復呼叫。

(2)隔離模式:這種模式就像對系統請求按型別劃分成一個個小島的一樣,當某個小島被火少光了,不會影響到其他的小島。例如可以對不同型別的請求使用執行緒池來資源隔離,每種型別的請求互不影響,如果一種型別的請求執行緒資源耗盡,則對後續的該型別請求直接返回,不再呼叫後續資源。這種模式使用場景非常多,例如將一個服務拆開,對於重要的服務使用單獨伺服器來部署,再或者公司最近推廣的多中心。

(3)限流模式:上述的熔斷模式和隔離模式都屬於出錯後的容錯處理機制,而限流模式則可以稱為預防模式。限流模式主要是提前對各個型別的請求設定最高的QPS閾值,若高於設定的閾值則對該請求直接返回,不再呼叫後續資源。這種模式不能解決服務依賴的問題,只能解決系統整體資源分配問題,因為沒有被限流的請求依然有可能造成雪崩效應。

3.熔斷設計

在熔斷的設計主要參考了hystrix的做法。其中最重要的是三個模組:熔斷請求判斷演算法、熔斷恢復機制、熔斷報警

(1)熔斷請求判斷機制演算法:使用無鎖迴圈佇列計數,每個熔斷器預設維護10個bucket,每1秒一個bucket,每個blucket記錄請求的成功、失敗、超時、拒絕的狀態,預設錯誤超過50%且10秒內超過20個請求進行中斷攔截。

(2)熔斷恢復:對於被熔斷的請求,每隔5s允許部分請求通過,若請求都是健康的(RT<250ms)則對請求健康恢復。

(3)熔斷報警:對於熔斷的請求打日誌,異常請求超過某些設定則報警

4.隔離設計

隔離的方式一般使用兩種

(1)執行緒池隔離模式:使用一個執行緒池來儲存當前的請求,執行緒池對請求作處理,設定任務返回處理超時時間,堆積的請求堆積入執行緒池佇列。這種方式需要為每個依賴的服務申請執行緒池,有一定的資源消耗,好處是可以應對突發流量(流量洪峰來臨時,處理不完可將資料儲存到執行緒池隊裡慢慢處理)

(2)訊號量隔離模式:使用一個原子計數器(或訊號量)來記錄當前有多少個執行緒在執行,請求來先判斷計數器的數值,若超過設定的最大執行緒個數則丟棄改型別的新請求,若不超過則執行計數操作請求來計數器+1,請求返回計數器-1。這種方式是嚴格的控制執行緒且立即返回模式,無法應對突發流量(流量洪峰來臨時,處理的執行緒超過數量,其他的請求會直接返回,不繼續去請求依賴的服務)

5 超時機制設計

超時分兩種,一種是請求的等待超時,一種是請求執行超時。

等待超時:在任務入佇列時設定任務入佇列時間,並判斷隊頭的任務入佇列時間是否大於超時時間,超過則丟棄任務。

執行超時:直接可使用執行緒池提供的get方法

什麼是熔斷機制

熔斷機制,就是下游服務出現問題後,為保證整個系統正常執行下去,而提供一種降級服務的機制,通過返回快取資料或者既定資料,避免出現系統整體雪崩效應。在springcloud中,該功能可通過配置的方式加入到專案中。

Hystrix作用

1.斷路器機制

斷路器很好理解, 當Hystrix Command請求後端服務失敗數量超過一定比例(預設50%), 斷路器會切換到開路狀態(Open). 這時所有請求會直接失敗而不會傳送到後端服務. 斷路器保持在開路狀態一段時間後(預設5秒), 自動切換到半開路狀態(HALF-OPEN). 這時會判斷下一次請求的返回情況, 如果請求成功, 斷路器切回閉路狀態(CLOSED), 否則重新切換到開路狀態(OPEN). Hystrix的斷路器就像我們家庭電路中的保險絲, 一旦後端服務不可用, 斷路器會直接切斷請求鏈, 避免傳送大量無效請求影響系統吞吐量, 並且斷路器有自我檢測並恢復的能力.

2.Fallback

Fallback相當於是降級操作. 對於查詢操作, 我們可以實現一個fallback方法, 當請求後端服務出現異常的時候, 可以使用fallback方法返回的值. fallback方法的返回值一般是設定的預設值或者來自快取.

3.資源隔離

在Hystrix中, 主要通過執行緒池來實現資源隔離. 通常在使用的時候我們會根據呼叫的遠端服務劃分出多個執行緒池. 例如呼叫產品服務的Command放入A執行緒池, 呼叫賬戶服務的Command放入B執行緒池. 這樣做的主要優點是執行環境被隔離開了. 這樣就算呼叫服務的程式碼存在bug或者由於其他原因導致自己所線上程池被耗盡時, 不會對系統的其他服務造成影響. 但是帶來的代價就是維護多個執行緒池會對系統帶來額外的效能開銷. 如果是對效能有嚴格要求而且確信自己呼叫服務的客戶端程式碼不會出問題的話, 可以使用Hystrix的訊號模式(Semaphores)來隔離資源.

服務的降級

什麼是服務降級

所有的RPC技術裡面服務降級是一個最為重要的話題,所謂的降級指的是當服務的提供方不可使用的時候,程式不會出現異常,而會出現本地的操作調

service-order工程新增Maven依賴

<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-starter-hystrix</artifactId>
 
</dependency>
複製程式碼

Rest方式使用斷路器

 
Rest請求方式介面改造
 
@HystrixCommand(fallbackMethod = "orderError")
 
public List<String> getOrderUserAll() {
 
return restTemplate.getForObject("http://service-member/getMemberAll", List.class);
 
}
 
 
 
public List<String> orderError() {
 
List<String> listUser = new ArrayList<String>();
 
listUser.add("not orderUser list");
 
return listUser;
 
}
 
啟動方式
 
@EnableEurekaClient
 
@EnableHystrix
 
@SpringBootApplication
 
public class OrderApp {
 
 
 
public static void main(String[] args) {
 
SpringApplication.run(OrderApp.class, args);
 
}
 
 
 
@Bean
 
@LoadBalanced
 
RestTemplate restTemplate() {
 
return new RestTemplate();
 
}
 
 
 
}
 
 
 
複製程式碼

@HystrixCommand 作用:服務發生錯誤,回撥方法。

@EnableHystrix 啟動斷路器

Fegin使用斷路器

 
改造service-order-feign工程
 
@FeignClient(value="service-member",fallback=MemberFeignService.class)
 
public interface MemberFeign {
 
@RequestMapping("/getMemberAll")
 
public List<String> getOrderByUserList();
 
}
 
@Component
 
public class MemberFeignService implements MemberFeign {
 
 
 
public List<String> getOrderByUserList() {
 
List<String> listUser = new ArrayList<String>();
 
listUser.add("not orderUser list");
 
return listUser;
 
}
 
}
 
 
 
配置檔案新增
 
feign:
 
   hystrix:
 
     enabled: true
 
複製程式碼
 
eureka:
 
  client:
 
    serviceUrl:
 
      defaultZone: http://localhost:8888/eureka/
 
#### tomcat最大接執行緒數
 
server:
 
  port: 8765
 
  tomcat:
 
    max-threads: 50
 
spring:
 
  application:
 
    name: service-order-feign
 
feign:
 
   hystrix:
 
     enabled: true
 
###超時時間
 
hystrix:
 
   command: 
 
     default: 
 
       execution: 
 
        isolation:
 
         thread: 
 
          timeoutInMilliseconds: 4000
複製程式碼

分散式配置中心

什麼是配置中心

在分散式系統中,由於服務數量巨多,為了方便服務配置檔案統一管理,實時更新,所以需要分散式配置中心元件。在Spring Cloud中,有分散式配置中心元件spring cloud config ,它支援配置服務放在配置服務的記憶體中(即本地),也支援放在遠端Git倉庫中。在spring cloud config 元件中,分兩個角色,一是config server,二是config client。

建立git地址

  1. 使用碼雲建立git地址 https://gitee.com/itmayi
  2. config-client-dev.properties ---dev環境
  3. 上傳配置檔案

userName=yushengjun

4. http://localhost:8889/foo/dev 查詢配置中心

建立config-server專案

 
<parent>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-parent</artifactId>
 
<version>1.5.2.RELEASE</version>
 
<relativePath /> <!-- lookup parent from repository -->
 
</parent>
 
 
 
<properties>
 
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 
<java.version>1.8</java.version>
 
</properties>
 
 
 
<dependencies>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-config-server</artifactId>
 
</dependency>
 
 
 
<dependency>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-test</artifactId>
 
<scope>test</scope>
 
</dependency>
 
 
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-starter-eureka</artifactId>
 
</dependency>
 
</dependencies>
 
 
 
<dependencyManagement>
 
<dependencies>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-dependencies</artifactId>
 
<version>Camden.SR6</version>
 
<type>pom</type>
 
<scope>import</scope>
 
</dependency>
 
</dependencies>
 
</dependencyManagement>
 
 
 
 
 
<build>
 
<plugins>
 
<plugin>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-maven-plugin</artifactId>
 
</plugin>
 
</plugins>
 
</build>
 
 
 
<repositories>
 
<repository>
 
<id>spring-milestones</id>
 
<name>Spring Milestones</name>
 
<url>https://repo.spring.io/milestone</url>
 
<snapshots>
 
<enabled>false</enabled>
 
</snapshots>
 
</repository>
 
</repositories>
複製程式碼

application.properties 配置檔案

 
spring.application.name=config-server
 
server.port=8889
 
spring.cloud.config.server.git.uri=https://gitee.com/itmayi/cfg2.git
 
spring.cloud.config.server.git.searchPaths=respo
 
spring.cloud.config.label=master
 
spring.cloud.config.server.git.username=
 
spring.cloud.config.server.git.password=
複製程式碼

spring.cloud.config.server.git.uri:配置git倉庫地址application.properties 配置檔案

spring.cloud.config.server.git.searchPaths:配置倉庫路徑

spring.cloud.config.label:配置倉庫的分支

spring.cloud.config.server.git.username:訪問git倉庫的使用者名稱

spring.cloud.config.server.git.password:訪問git倉庫的使用者密碼

啟動config-server專案

 
@SpringBootApplication
 
@EnableConfigServer
 
public class ConfigServerApplication {
 
public static void main(String[] args) {
 
SpringApplication.run(ConfigServerApplication.class, args);
 
}
 
}
複製程式碼

建立config-client專案

 
<parent>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-parent</artifactId>
 
<version>1.5.2.RELEASE</version>
 
<relativePath /> <!-- lookup parent from repository -->
 
</parent>
 
 
 
<properties>
 
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 
<java.version>1.8</java.version>
 
</properties>
 
 
 
<dependencies>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-starter-config</artifactId>
 
</dependency>
 
 
 
<dependency>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-web</artifactId>
 
</dependency>
 
 
 
<dependency>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-starter-test</artifactId>
 
<scope>test</scope>
 
</dependency>
 
</dependencies>
 
 
 
<dependencyManagement>
 
<dependencies>
 
<dependency>
 
<groupId>org.springframework.cloud</groupId>
 
<artifactId>spring-cloud-dependencies</artifactId>
 
<version>Dalston.RC1</version>
 
<type>pom</type>
 
<scope>import</scope>
 
</dependency>
 
</dependencies>
 
</dependencyManagement>
 
 
 
<build>
 
<plugins>
 
<plugin>
 
<groupId>org.springframework.boot</groupId>
 
<artifactId>spring-boot-maven-plugin</artifactId>
 
</plugin>
 
</plugins>
 
</build>
 
 
 
<repositories>
 
<repository>
 
<id>spring-milestones</id>
 
<name>Spring Milestones</name>
 
<url>https://repo.spring.io/milestone</url>
 
<snapshots>
 
<enabled>false</enabled>
 
</snapshots>
 
</repository>
 
</repositories>
複製程式碼

bootstrap.properties

 
spring.application.name=config-client
 
spring.cloud.config.label=master
 
spring.cloud.config.profile=dev
 
spring.cloud.config.uri= http://localhost:8888/
 
server.port=8881
 
複製程式碼

  • spring.cloud.config.label 指明遠端倉庫的分支
  • spring.cloud.config.profile
    • dev開發環境配置檔案
    • test測試環境
    • pro正式環境
  • spring.cloud.config.uri= http://localhost:8888/ 指明配置服務中心的網址。
 
@SpringBootApplication
 
@RestController
 
public class ConfigClientApplication {
 
 
 
public static void main(String[] args) {
 
SpringApplication.run(ConfigClientApplication.class, args);
 
}
 
 
 
@Value("${userName}")
 
String userName;
 
 
 
@RequestMapping(value = "/getUserName")
 
public String getUserName () {
 
return userName;
 
}
 
}
複製程式碼


相關文章