天天看點

spring-cloud 核心功能建構spring-cloud 核心功能建構spring cloud 基本項目依賴Eureka注冊中心Ribbon 負載均衡器Hystrix 降級熔斷feignZuul網關

文章目錄

  • spring-cloud 核心功能建構
  • spring cloud 基本項目依賴
  • Eureka注冊中心
    • 添加Eureka注冊中心服務
      • 添加依賴——eureka服務端
      • 增加啟動類注解
      • 配置application.yml檔案
      • 注冊方添加erueka用戶端依賴
      • 注冊方啟動類添加注解
      • 調用服務寫法一
      • 注冊方配置erueka注冊中心位址
    • erueka 進階配置
      • 高可用叢集
      • 服務注冊
      • 服務續約
      • 服務失效
      • 服務重新整理
      • 自我保護
      • 失效剔除
  • Ribbon 負載均衡器
    • 引入相關依賴
    • 使用RibbonLoadBalancerClient動态擷取執行個體
    • 調用服務寫法二————使用Ribbon攔截器攔截RestTemplate
    • Ribbon 進階配置
      • 負載均衡政策
      • 重試機制
  • Hystrix 降級熔斷
    • 添加Hytrix依賴
    • 啟動類開啟Hystrix注解
      • @EnableHystrix
      • @EnableCircuitBreaker
      • @SpringCloudApplication
    • 設定接口熔斷回調
      • 局部熔斷回調
      • 公共熔斷回調
    • 進階配置
      • 全局逾時時間
      • 局部逾時時間
      • 熔斷器請求量
      • 出錯比例
      • 休眠時間窗
  • feign
    • 基本使用
      • 導入依賴
      • 啟動類添加注解
      • 添加遠端調用的服務消費端端接口
      • 遠端調用方式三————面向接口調用
    • feign 整合 Ribbon 和 Hystrix 重試和熔斷
      • ribbon 和 Hystrix相關配置
      • 建立failback工廠作為熔斷回調
      • 添加遠端調用的服務消費端端接口
  • Zuul網關
    • 基本使用
      • 添加依賴
      • 啟動類開啟Zuul網關注解
    • 進階配置
      • 基于服務的路由配置
      • 忽略服務
      • 局部字首忽略
      • 全局通路字首
      • 整合Ribbon 和 Hystrix
    • 自定義過濾器

spring-cloud 核心功能建構

源碼參考:https://github.com/Autom-liu/spring-cloud-demo

spring cloud 基本項目依賴

父工程項目pom.xml

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.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>
		<spring-cloud.version>Finchley.SR1</spring-cloud.version>
		<mapper.starter.version>2.0.3</mapper.starter.version>
		<mysql.version>5.1.32</mysql.version>
		<pageHelper.starter.version>1.2.5</pageHelper.starter.version>
	</properties>
	
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>tk.mybatis</groupId>
				<artifactId>mapper-spring-boot-starter</artifactId>
				<version>${mapper.starter.version}</version>
			</dependency>
			<dependency>
				<groupId>com.github.pagehelper</groupId>
				<artifactId>pagehelper-spring-boot-starter</artifactId>
				<version>${pageHelper.starter.version}</version>
			</dependency>
			<dependency>
				<groupId>mysql</groupId>
				<artifactId>mysql-connector-java</artifactId>
				<version>${mysql.version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>
	
	<dependencies>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
           

Eureka注冊中心

添加Eureka注冊中心服務

添加依賴——eureka服務端

建立新的子工程,添加如下依賴:

pom.xml:

<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
           

增加啟動類注解

@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {
	public static void main(String[] args) throws Exception {
		SpringApplication.run(EurekaServer.class, args);
	}
}
           

配置application.yml檔案

server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true		# 固定ip通路,目前還沒看到生效
    ip-address: 127.0.0.1 		# 固定ip通路,目前還沒看到生效
           

注冊方添加erueka用戶端依賴

Erueka作為注冊中心,其他往它注冊的服務都是用戶端,無論它是提供方還是消費方

pom.xml

<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
           

注冊方啟動類添加注解

注冊方啟動類需要添加服務發現注解

@EnableDiscoveryClient

調用服務寫法一

調用服務的其中一種寫法,就是通過

DiscoveryClient

類手動從注冊中心擷取執行個體清單

拿到執行個體,拼接URL,發起請求:

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private DiscoveryClient discoverClient;
	
	@GetMapping("/{id}")
	public User queryById(@PathVariable("id") String id) {
		// 根據服務id擷取執行個體清單
		List<ServiceInstance> instances = discoverClient.getInstances("user-service");
		// 從執行個體清單中拿到執行個體
		ServiceInstance instance = instances.get(0);
		// 構造URL
		String url = "http://"+ instance.getHost() +":"+ instance.getPort() +"/user/" + id;
		// 發起請求
		User user = restTemplate.getForObject(url, User.class);
		return user;
	}
	
}
           

這種方式感到很low有沒有?

注冊方配置erueka注冊中心位址

需要注意的是一定要給每個服務命名,即

spring.application.name

spring:
  application:
    name: user-consumer
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1 
           

erueka 進階配置

高可用叢集

eureka.client.service-url.defaultZone 值配置多個即可

服務注冊

其實就是配置是否自己作為服務注冊,預設注冊自己,可以配置不注冊自己

eureka.register-with-eureka: false

服務續約

注冊中心和注冊方維持的心跳機制,幾秒告知一次健康狀态

eureka.instance.lease-renewal-interval-in-seconds

開發環境小一點,生産環境預設即可,不宜太小也不宜太大

服務失效

當注冊方遲遲未心跳時,配置幾秒認為它不存活了

eureka.instance.lease-expiration-duration-in-seconds

服務重新整理

預設情況下會緩存服務清單的資料,隔一定時間重新拉取更新,通過該參數可以修改拉取時間,在開發環境下通常設定小一點,生産環境下不需要更改 機關秒

eureka.client.registry-fetch-interval-seconds

自我保護

spring cloud注重CAP原則中的A和P,簡單來說即不輕易相信服務挂了,即它會排查一切可能,隻有确定100%認為它挂了它才剔除,否則隻要有存活的可能,都不會放棄,哪怕它真的挂了…

開啟自我保護,當服務沒有正常保持心跳時,注冊中心并不認為它挂了,因為存在網絡延遲的可能,是以要統計次數及比例,隻有超過50%(或多少的~)才可以認為它真的挂了。

這樣能有效防止注冊中心誤判(服務挂沒挂)

一般不需要關閉,非要關閉就進行如下配置為

false

即可:

eureka.server.enable-self-preservation

失效剔除

前面說到,注冊中心不會輕易判定一個服務說挂就挂的,是以哪怕注冊中心真的認為服務挂了,也要緩沖一個時間,給它最後一次機會,實在不給面子了,才會把它剔除。

是以失效剔除也是需要有時間的,配置如下:機關毫秒

eureka.server.eviction-interval-timer-in-ms

Ribbon 負載均衡器

引入相關依賴

在服務消費方引用

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
           

使用RibbonLoadBalancerClient動态擷取執行個體

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private RibbonLoadBalancerClient ribbonClient;
	
	@GetMapping("/{id}")
	public User queryById(@PathVariable("id") String id) {
		// 使用ribbon負載均衡器可以直接id執行個體
		ServiceInstance instance = ribbonClient.choose("user-service");
		String url = "http://"+ instance.getHost() +":"+ instance.getPort() +"/user/" + id;
		User user = restTemplate.getForObject(url, User.class);
		return user;
	}
	
}
           

調用服務寫法二————使用Ribbon攔截器攔截RestTemplate

第二種寫法,即使用Ribbon攔截器攔截RestTemplate,它對RestTemplate做了一次代理,省去了拼接URL麻煩,直接調用通路即可

首先要将注冊到spring中的restTemplate添加注解标記

@Bean
@LoadBalanced   // 使用該注解,會對restTemplate所有請求進行一個攔截處理url
public RestTemplate restTemplate() {
	return new RestTemplate();
}
           

這樣就可以不再通過RibbonLoadBalancerClient去擷取,而是直接展現在URL上,在RestTemplate發起請求之前重寫URL

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	
	@GetMapping("/{id}")
	public User queryById(@PathVariable("id") String id) {
		// 将服務id直接寫進url上
		String url = "http://user-service/user/" + id;
		User user = restTemplate.getForObject(url, User.class);
		return user;
	}
	
}
           

Ribbon 進階配置

負載均衡政策

Ribbon預設負載均衡政策是輪詢的,可以通過如下配置修改負載均衡政策

[service-id].ribbon.NFLoadBalancerRuleClassName

其中

service-id

是服務提供方的應用名稱,即

spring.application.name

配置的名稱

負載均衡政策都在

com.netflix.loadbalancer

包下:

其中有如下幾種政策:

  • RoundRobinRule
  • AvailabilityFilteringRule
  • WeightedResponseTimeRule
  • ZoneAvoidanceRule
  • BestAvailableRule
  • RandomRule
  • Retry

也可以自定義政策,這個上網去查了

重試機制

ribbon重試機制個人感覺有問題,沒成功過,或偶爾成功過

某些人說要加入如下依賴:

<dependency>
		<groupId>org.springframework.retry</groupId>
		<artifactId>spring-retry</artifactId>
</dependency>
           

但另外一些人又說那是老版本的做法,這裡用的是新版本的,不知道會不會問題。

但大多數主要就做如下配置即可的:

以下這些配置均是在

AbstractRibbonCommand

類源碼中有對應指明

[service-id].ribbon.ConnectTimeOut Ribbon連接配接逾時時間
[service-id].ribbon.ReadTimeout Ribbon讀取資料逾時時間
[service-id].ribbon.OkToRetryOnAllOperations 是否對所有操作都進行重試
[service-id].ribbon.MaxAutoRetriesNextServer 切換執行個體的重試次數
[service-id].ribbon.MaxAutoRetries 對目前執行個體的重試次數

但請務必先開啟spring cloud重試功能:

spring.cloud.loadbalancer.retry.enabled: true

Hystrix 降級熔斷

所謂熔斷,即服務超出一定時長未響應及時回報使用者,避免因為阻塞占用連接配接。

添加Hytrix依賴

pom.xml

<dependency>
	    <groupId>org.springframework.cloud</groupId>
	    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
	</dependency>
           

啟動類開啟Hystrix注解

@EnableHystrix

這個注解僅僅隻是Hystrix的基本功能,并不通用

@EnableCircuitBreaker

這個注解包含了服務熔斷,服務降級等處理,更為通用

@SpringCloudApplication

這個注解其實是

@SpringBootApplication

@EnableDiscoveryClient

@EnableCircuitBreaker

,三者合一,是spring cloud項目開發的基本注解!!

設定接口熔斷回調

局部熔斷回調

使用

@HystrixCommand

fallbackMethod

屬性配置,具體看如下代碼:

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private LoadBalancerClient ribbonClient;
	
	@GetMapping("/{id}")
	@HystrixCommand(fallbackMethod = "queryFailback")
	public Result<User> queryById(@PathVariable("id") String id) {
		ServiceInstance instance = ribbonClient.choose("user-service");
		String host = instance.getHost();
		int port = instance.getPort();
		
		String url = String.format("http://%s:%d/user/%s", host, port, id);
		System.out.println(url);
		User user = restTemplate.getForObject(url, User.class);
		return Result.success(user);
	}
	
	public Result<User> queryFailback(String id) {
		return Result.error("伺服器繁忙,請稍後再試....");
	}
	
}
           

公共熔斷回調

所謂公共熔斷回調即對整個類生效,隻需要在類上配置注解

@DefaultProperties

defaultFallback

屬性,在需要熔斷的方法上加上

@HystrixCommand

即可

參考代碼如下:

@RestController
@RequestMapping("consumer")
@DefaultProperties(defaultFallback = "defaultFailback")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private LoadBalancerClient ribbonClient;
	
	@GetMapping("/{id}")
	@HystrixCommand
	public Result<User> queryById(@PathVariable("id") String id) {
		ServiceInstance instance = ribbonClient.choose("user-service");
		String host = instance.getHost();
		int port = instance.getPort();
		
		String url = String.format("http://%s:%d/user/%s", host, port, id);
		System.out.println(url);
		User user = restTemplate.getForObject(url, User.class);
		return Result.success(user);
	}
	
	public Result<User> defaultFailback() {
		return Result.error("伺服器繁忙,請稍後再試試....");
	}
	
}
           

進階配置

以下這些配置其實都在

HystrixCommandProperties

類源碼中有對應指出的,并不是亂來的。

全局逾時時間

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds

局部逾時時間

接口方法上加上如下注解:

@HystrixCommand(commandProperties = {
			@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
	})
           

熔斷器請求量

熔斷器會在一定的請求次數後對請求進行分析統計,并跳入下一個狀态,比如統計失敗次數過多,将進入熔斷狀态。

通過如下配置項進行配置:

circuitBreaker.requestVolumeThreshold

出錯比例

前面說到達到一定的請求進行一次統計,比如統計失敗次數過多,将進入熔斷狀态,這裡的過多,其實是根據一定比例來判定的,即出錯比例

可以通過如下配置項進行配置:

circuitBreaker.errorThresholdPercentage

休眠時間窗

熔斷器熔斷後并不是完事了,還要嘗試恢複,是以會在一定的時間後從熔斷狀态變為半開放狀态,測試目前服務叢集可用性,這個時間即休眠時間窗

可用通過如下配置項進行配置:

circuitBreaker.sleepWindowInMilliseconds

通過注解的方式配置,結合局部逾時時間、熔斷請求量、休眠時間窗、出錯比例一起使用,最終配置的結果如下:

@HystrixCommand(commandProperties = {
			@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000"),
			@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
			@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
			@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),
	})
           

feign

基本使用

feign基本功能即代替restTemplate寫法,注意它隻是遠端調用的工具,封裝以簡化代碼開發而已的,并沒有改變遠端調用的工作。

導入依賴

feign 已經包含了ribbon和hystrix依賴,但是為了全面,将同時導入也無所謂

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
           

啟動類添加注解

啟動類添加如下注解開啟feign用戶端

@EnableFeignClients

添加遠端調用的服務消費端端接口

feign 遠端調用其實是通過配置熟悉的

spring mvc

文法來構造URL,代替restTemplate方式的調用。

方式就是以接口的形式定義相關配置,如URI,傳回值,參數清單等。這個接口方法的定義保持和

spring mvc

端的

web

層方法一緻。

@FeignClient("user-service")
@RequestMapping("/user")
public interface UserClient {

 	@GetMapping("/{id}")
	User queryById(@PathVariable("id") String id);
}
           

遠端調用方式三————面向接口調用

前面說到兩種遠端調用的方式,都是需要顯式指出URL,調用restTemplate,這樣寫看似代碼邏輯清晰,簡單明了。但是大量寫死的代碼造成代碼備援,邏輯重複,可維護性差。是以就出現了最終的高端寫法:

面向接口調用,是一個相當抽象的過程,看不出來内部使用什麼實作的。方式也很簡單,不需要RestTempalte,也不需要LoadBalancerClient,直接注入前面自定義的配置接口即可,具體參考如下代碼:

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private UserClient userClient;
	
	@GetMapping("/{id}")
	public Result<User> queryById(@PathVariable("id") String id) {
		User user = userClient.queryById(id);
		return Result.success(user);
	}
	
}
           

讓你完全看不出遠端調用的痕迹,這隻是一種寫法而已,底層仍然時想注冊中心發起遠端調用的。

這種寫法說好聽點就是減少代碼備援,避免代碼重複,但其實就是裝逼式寫法,增加了了解難度,看得懂的人秀,看不懂的人怎麼也别想不懂… _

feign 整合 Ribbon 和 Hystrix 重試和熔斷

溫馨提示:不建議使用,因為沒有成功的。可能是我技術問題

feign 本身提供了 Ribbon 和 Hystrix,它就相當于是spring cloud家族的一個內建工具,是以導入依賴的時候不需要

ribbon

的相關依賴(

Hystrix

還是要的,因為剛出版,沒有完全內建)

ribbon 和 Hystrix相關配置

大多數和原來一樣的,隻不過要開啟feign

spring:
  application:
    name: user-consumer
  cloud:
    loadbalancer:
      retry:
        enabled: true   # 開啟spring cloud 重試功能
user-service:
  ribbon:
    # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule    # 更換負載均衡政策
    ConnectTimeout: 250     #  Ribbon連接配接逾時時間
    ReadTimeout: 1000       #  Ribbon讀取資料逾時時間
    OkToRetryOnAllOperations: true    # 是否對所有操作都進行重試
    MaxAutoRetriesNextServer: 100      # 切換執行個體的重試次數
    MaxAutoRetries: 1                 # 對目前執行個體的重試次數
feign:
  hystrix:
    enabled: true           # 開啟feign的hystrix熔斷
ribbon:
  ConnectTimeout: 500    # 連接配接逾時時長
  ReadTimeout: 1000         # 讀取逾時時長
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000    # 設定hystrix的逾時時間
           

建立failback工廠作為熔斷回調

建議用工廠模式,不然不容易成功

@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

 	@Override
	public UserClient create(Throwable cause) {
		return new UserClient() {

 			@Override
			public User queryById(String id) {
				return null;
			}
		};
	}

}
           

添加遠端調用的服務消費端端接口

和之前一樣,使用接口,但是需要在@FeignClient注解上添加多一個fallbackFactory 屬性:

@FeignClient(value = “user-service”, fallbackFactory = UserClientFallbackFactory.class)

注意,feign的熔斷不走hystrix的流程,是以feign配置和hystrix配置是會沖突的,是以如果要用

feign

的熔斷功能的話,就要把原來的hystrix熔斷相關的注解去掉,不然不會成功。(雖然就沒成功過!)

Zuul網關

是主要的對外接口,外部必須通過網關才能通路具體的微服務,網關同時具有服務聚合的功能。

簡單來說,就是Controller

基本使用

網關作為單獨的項目存在,也是

spring boot

服務

添加依賴

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
           

啟動類開啟Zuul網關注解

@EnableZuulProxy

注意使用ZuulProxy而不是ZuulApplication,功能更完善

進階配置

基于服務的路由配置

所謂路由配置即URI到服務的映射關系配置

這其實是Zuul的預設配置,預設它會将所有的服務名稱作為字首到URI上去,是以即使不配置,也可照常通路。

但是有時候需求,并不要求服務名稱作為URI字首通路時,就需要指定配置了。

zuul:
  routes:
    user-service:
      path: /user-service/**
      serviceId: user-service
           

忽略服務

有時候并不希望有些服務對外使用,但是網關預設是全部注冊上的,是以需要配置忽略的服務

zuul:
  routes:
    user-service: /user/**
  ignored-services:
    - user-consumer
           

局部字首忽略

通路 zuul.routes.[service-id].path 是需要帶字首的

zuul:
  routes:
    user-service:
      path: /user/**
      serviceId: user-service
      strip-prefix: false
           

全局通路字首

zuul.prefix: /api

整合Ribbon 和 Hystrix

配置其實和之前一個樣,隻需要加上如下配置即可

zuul.retryable
zuul:
  retryable: true   # 開啟重試
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 100
  OkToRetryOnAllOperations: true
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000    # 設定hystrix的逾時時間
           

自定義過濾器

先直接看示範

@Component
public class LoginFilter extends ZuulFilter {

	@Override
	public boolean shouldFilter() {
		return true;
	}

	@Override
	public Object run() throws ZuulException {
		RequestContext context = RequestContext.getCurrentContext();
		HttpServletRequest request = context.getRequest();
		
		String token = request.getParameter("token");
		
		if(StringUtils.isBlank(token)) {
			// 不存在,則攔截
			context.setSendZuulResponse(false);
			HttpServletResponse response = context.getResponse();
			response.setStatus(HttpStatus.FORBIDDEN.value());
			response.setContentType("text/json;charset=UTF-8");
			context.setResponse(response);
//			context.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
			
			context.setResponseBody(JSON.toJSONString(Result.error("沒有權限")));
		}
		
		return null;
	}

	@Override
	public String filterType() {
		return FilterConstants.PRE_TYPE;
	}

	@Override
	public int filterOrder() {
		return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
	}

}
           

繼續閱讀