文章目录

十二、ribbon + hystrix 断路器

## 断路器?

就是为 <mark>微服务系统容错</mark>

当请求后台服务失败时,可以通过执行当前服务器的 <mark>一段代码</mark> 来向客户端返回结果

下面一步一步说:

熔断

保护你的系统。为了避免 雪崩效应(小部分服务器失效,导致大面积服务器失效)

比方说:
很多访问向 服务器1服务器1 会向后面的 服务器2 请求服务
如果这时候出现了很多错误,就会出现<mark>熔断</mark>( 服务器1服务器2 的某些服务断开)

熔断:

<mark>就是某个服务器间的服务断开</mark>

如果出现熔断,服务器1向后请求的 服务路2 的路径会断开, 服务器1 执行自身的<mark>降级代码</mark>作为返回结果。

降级代码 ? ==》 就是 一段代码

比方说

  • 错误提示
  • 或者正常的返回结果的缓存
  • 或者说任意的结果(你自己定)

触发熔断 - 条件

断路器会在一定条件下触发熔断(断路器打开)

  • 10秒内20次请求
  • 50%失败,执行了降级代码

半开状态

断开后隔一段时间。

会尝试发送一次请求吗,如果失败断路器继续保持打开状态,繁殖,关闭断路器,自动恢复

## 微服务宕机时,ribbon 无法转发请求

<mark>关闭 user-service 和 order-service</mark>

## 复制 sp06-ribbon 项目,命名为sp07-hystrix

选择 sp06-ribbon 项目,ctrl-c,ctrl-v,复制为sp07-hystrix
关闭 sp06-ribbon 项目,后续测试使用 sp07-hystrix 项目

## 修改 pom.xml

这里的字符根 maven 相关

## 添加 hystrix 起步依赖

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

## 修改 application.yml

spring:
  application:
    name: hystrix
    
server:
  port: 3001
  
eureka:
  client:    
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  MaxAutoRetries: 1
  MaxAutoRetriesNextServer: 2
  OkToRetryOnAllOperations: true

## 主程序添加 @EnableCircuitBreaker 启用 hystrix 断路器

启动断路器,断路器提供两个核心功能:

  • 降级,超时、出错、不可到达时,对服务降级,返回错误信息或者是缓存数据

  • 熔断,当服务压力过大,错误比例过多时,熔断所有请求,所有请求直接降级

可以使用 @SpringCloudApplication 注解代替三个注解

  1. @EnableCircuitBreaker - hystrix 断路
  2. @EnableDiscoveryClient - eureka 客户端
  3. @SpringBootApplication - SpringBoot 主启动
package cn.tedu.sp06;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

//@EnableCircuitBreaker
//@EnableDiscoveryClient
//@SpringBootApplication

@SpringCloudApplication
public class Sp06RibbonApplication {

	@LoadBalanced
	@Bean
	public RestTemplate getRestTemplate() {
		SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory();
		f.setConnectTimeout(1000);
		f.setReadTimeout(1000);
		return new RestTemplate(f);
		
		//RestTemplate 中默认的 Factory 实例中,两个超时属性默认是 -1,
		//未启用超时,也不会触发重试
		//return new RestTemplate();
	}

	public static void main(String[] args) {
		SpringApplication.run(Sp06RibbonApplication.class, args);
	}

}

## RibbonController 中添加降级方法

  • 为每个方法添加降级方法,例如 getItems() 添加降级方法 getItemsFB()
  • 添加 @HystrixCommand 注解,指定降级方法名
package cn.tedu.sp06.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@RestController
public class RibbonController {
	@Autowired
	private RestTemplate rt;
	
	@GetMapping("/item-service/{orderId}")
	@HystrixCommand(fallbackMethod = "getItemsFB") //指定降级方法的方法名
	public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
		return rt.getForObject("http://item-service/{1}", JsonResult.class, orderId);
	}

	@PostMapping("/item-service/decreaseNumber")
	@HystrixCommand(fallbackMethod = "decreaseNumberFB")
	public JsonResult decreaseNumber(@RequestBody List<Item> items) {
		return rt.postForObject("http://item-service/decreaseNumber", items, JsonResult.class);
	}

	/////////////////////////////////////////
	
	@GetMapping("/user-service/{userId}")
	@HystrixCommand(fallbackMethod = "getUserFB")
	public JsonResult<User> getUser(@PathVariable Integer userId) {
		return rt.getForObject("http://user-service/{1}", JsonResult.class, userId);
	}

	@GetMapping("/user-service/{userId}/score") 
	@HystrixCommand(fallbackMethod = "addScoreFB")
	public JsonResult addScore(@PathVariable Integer userId, Integer score) {
		return rt.getForObject("http://user-service/{1}/score?score={2}", JsonResult.class, userId, score);
	}
	
	/////////////////////////////////////////
	
	@GetMapping("/order-service/{orderId}")
	@HystrixCommand(fallbackMethod = "getOrderFB")
	public JsonResult<Order> getOrder(@PathVariable String orderId) {
		return rt.getForObject("http://order-service/{1}", JsonResult.class, orderId);
	}

	@GetMapping("/order-service")
	@HystrixCommand(fallbackMethod = "addOrderFB")
	public JsonResult addOrder() {
		return rt.getForObject("http://order-service/", JsonResult.class);
	}
	
	/////////////////////////////////////////

    //降级方法的参数和返回值,需要和原始方法一致,方法名任意
	public JsonResult<List<Item>> getItemsFB(String orderId) {
		return JsonResult.err("获取订单商品列表失败");
	}
	public JsonResult decreaseNumberFB(List<Item> items) {
		return JsonResult.err("更新商品库存失败");
	}
	public JsonResult<User> getUserFB(Integer userId) {
		return JsonResult.err("获取用户信息失败");
	}
	public JsonResult addScoreFB(Integer userId, Integer score) {
		return JsonResult.err("增加用户积分失败");
	}
	public JsonResult<Order> getOrderFB(String orderId) {
		return JsonResult.err("获取订单失败");
	}
	public JsonResult addOrderFB() {
		return JsonResult.err("添加订单失败");
	}
}


## hystrix 超时设置

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

hystrix等待超时后, 会执行降级代码, 快速向客户端返回降级结果, 默认超时时间是1000毫秒

为了测试 hystrix 降级,我们把 hystrix 等待超时设置得非常小(500毫秒)

<mark>此设置一般应大于 ribbon 的重试超时时长,例如 10 秒</mark>


spring:
  application:
    name: hystrix
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  MaxAutoRetriesNextServer: 2
  MaxAutoRetries: 1
  OkToRetryOnAllOperations: true
  
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500
            

## 启动项目进行测试

  • 通过 hystrix 服务,访问可能超时失败的 item-service
    http://localhost:3001/item-service/35

  • 通过 hystrix 服务,访问未启动的 user-service
    http://localhost:3001/user-service/7

可以看到,如果 item-service 请求超时,hystrix 会立即执行降级方法

访问 user-service,由于该服务未启动,hystrix也会立即执行降级方法

## 其他的 hystrix 配置

Github - https://github.com/Netflix/Hystrix/wiki/Configuration

  • hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
    请求超时时间,超时后触发失败降级

  • hystrix.command.default.circuitBreaker.requestVolumeThreshold
    10秒内请求数量,默认20,如果没有达到该数量,即使请求全部失败,也不会触发断路器打开

  • hystrix.command.default.circuitBreaker.errorThresholdPercentage
    失败请求百分比,达到该比例则触发断路器打开

  • hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds
    断路器打开多长时间后,再次允许尝试访问(半开),仍失败则继续保持打开状态,如成功访问则关闭断路器,默认 5000

源码: https://github.com/benwang6/spring-cloud-repo

十三、hystrix dashboard 断路器仪表盘

## acutator

仪表盘的监控需要 借助 actuator 插件。
<mark>actuatorSpringBoot 提供的项目监控工具</mark>,可以提供多种项目监控信息。

  • health - 健康状态
  • beans - spring 容器中的所有对象
  • env - 系统环境的变量
  • mappings - spring mvc 映射的路径
  • ...

hystrix 对请求的降级和熔断,可以产生监控信息, hystrix dashboard 可以实时的进行监控

更多 : https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html

## sp07-hystrix 项目添加 actuator,并暴露 hystrix 监控端点

<mark>actuator 是 spring boot 提供的服务监控工具</mark>,提供了各种监控信息的监控端点

management.endpoints.web.exposure.include 配置选项,
可以指定端点名,来暴露监控端点

如果要暴露所有端点,可以用 “*”

## pom.xml 添加 actuator 依赖

右键点击项目或pom.xml, 编辑起步依赖, 添加 actuator 依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

## 调整 application.yml 配置,并暴露 hystrix.stream 监控端点

spring:
  application:
    name: hystrix
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  MaxAutoRetriesNextServer: 1
  MaxAutoRetries: 1
  OkToRetryOnAllOperations: true
  
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream
        // 如果要暴露所有端点,可以用 `“*”` 
        // include: "*"

## 访问 actuator 路径,查看监控端点

http://localhost:3001/actuator

## 新建 sp08-hystrix-dashboard 项目


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.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.tedu</groupId>
	<artifactId>sp08-hystrix-dashboard</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sp08-hystrix-dashboard</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
		</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>
	</dependencies>

	<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>
		</dependencies>
	</dependencyManagement>

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

</project>

application.yml

spring:
  application:
    name: hystrix-dashboard
    
server:
  port: 4001

eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

## 主程序添加 @EnableHystrixDashboard 和 @EnableDiscoveryClient

package cn.tedu.sp08;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@EnableDiscoveryClient
@EnableHystrixDashboard
@SpringBootApplication
public class Sp08HystrixDashboardApplication {

	public static void main(String[] args) {
		SpringApplication.run(Sp08HystrixDashboardApplication.class, args);
	}

}


## 启动,并访问测试

## 访问 hystrix dashboard

http://localhost:4001/hystrix

## 填入 hystrix 的监控端点,开启监控

http://localhost:3001/actuator/hystrix.stream

  • <mark>通过 hystrix 访问服务多次,观察监控信息</mark>

http://localhost:3001/item-service/35

http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100

http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/


## hystrix 熔断

整个链路达到一定的阈值,默认情况下,10秒内产生超过20次请求,则符合第一个条件。
满足第一个条件的情况下,如果请求的错误百分比大于阈值,则会打开断路器,默认为50%。
Hystrix的逻辑,先判断是否满足第一个条件,再判断第二个条件,如果两个条件都满足,则会开启断路器

断路器打开 5 秒后,会处于半开状态,会尝试转发请求,如果仍然失败,保持打开状态,如果成功,则关闭断路器

## 使用 apache 的并发访问测试工具 ab

http://httpd.apache.org/docs/current/platform/windows.html#down

  • <mark>用 ab 工具,以并发50次,来发送20000个请求</mark>
ab -n 20000 -c 50 http://localhost:3001/item-service/35
  • <mark>断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法</mark>

源码: https://github.com/benwang6/spring-cloud-repo

十四、feign 整合ribbon+hystrix

微服务应用中,ribbonhystrix 总是同时出现, feign 整合了两者,并提供了声明式消费者客户端

feign 代替 hystrix+ribbon

## 声明式客户端

只需要定义一个端口,就可以通过接口调用后台服务器

## 新建 sp09-feign 项目


pom.xml

<mark>需要添加 sp01-commons 依赖</mark>

<?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.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.tedu</groupId>
	<artifactId>sp09-feign</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sp09-feign</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</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-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</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>
	</dependencies>

	<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>
		</dependencies>
	</dependencyManagement>

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

</project>

application.yml

spring:
  application:
    name: feign
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

## 主程序添加 @EnableDiscoveryClient 和 @EnableFeignClients

package cn.tedu.sp09;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Sp09FeignApplication {

	public static void main(String[] args) {
		SpringApplication.run(Sp09FeignApplication.class, args);
	}

}


## java 源文件

## feign 声明式客户端

feign 利用了我们熟悉的 spring mvc 注解来对接口方法进行设置,降低了我们的学习成本。
通过这些设置 , feign 可以拼接后台服务的访问路径和提交的参数
例如:

@GetMapping("/{userId}/score") 
JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);

当这样调用该方法:

service.addScore(7, 100);

那么 feign 会向服务器发送请求:
http://用户微服务/7/score?score=100
<mark>注意:如果 score 参数名与变量名不同,需要添加参数名设置:</mark>

@GetMapping("/{userId}/score") 
JsonResult addScore(@PathVariable Integer userId, @RequestParam("score") Integer s);

ItemFeignService

package cn.tedu.sp09.service;

import java.util.List;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

@FeignClient("item-service")
public interface ItemFeignService {
	@GetMapping("/{orderId}")
	JsonResult<List<Item>> getItems(@PathVariable String orderId);

	@PostMapping("/decreaseNumber")
	JsonResult decreaseNumber(@RequestBody List<Item> items);
}

UserFeignService

<mark>注意,如果请求参数名与方法参数名不同, @RequestParam 不能省略,并且要指定请求参数名:</mark>
@RequestParam("score") Integer s

package cn.tedu.sp09.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@FeignClient("user-service")
public interface UserFeignService {
	@GetMapping("/{userId}")
	JsonResult<User> getUser(@PathVariable Integer userId);

    // 拼接路径 /{userId}/score?score=新增积分
    // 如果请求参数和方法参数同名,@RequestParam可省略
	@GetMapping("/{userId}/score") 
	JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}

OrderFeignService

package cn.tedu.sp09.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import cn.tedu.sp01.pojo.Order;
import cn.tedu.web.util.JsonResult;

@FeignClient("order-service")
public interface OrderFeignService {
	@GetMapping("/{orderId}")
	JsonResult<Order> getOrder(@PathVariable String orderId);

	@GetMapping("/")
	JsonResult addOrder();

}

FeignController

package cn.tedu.sp09.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp09.service.ItemFeignService;
import cn.tedu.sp09.service.OrderFeignService;
import cn.tedu.sp09.service.UserFeignService;
import cn.tedu.web.util.JsonResult;

@RestController
public class FeignController {
	@Autowired
	private ItemFeignService itemService;
	@Autowired
	private UserFeignService userService;
	@Autowired
	private OrderFeignService orderService;
	
	@GetMapping("/item-service/{orderId}")
	public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
		return itemService.getItems(orderId);
	}

	@PostMapping("/item-service/decreaseNumber")
	public JsonResult decreaseNumber(@RequestBody List<Item> items) {
		return itemService.decreaseNumber(items);
	}

	/////////////////////////////////////////
	
	@GetMapping("/user-service/{userId}")
	public JsonResult<User> getUser(@PathVariable Integer userId) {
		return userService.getUser(userId);
	}

	@GetMapping("/user-service/{userId}/score") 
	public JsonResult addScore(@PathVariable Integer userId, Integer score) {
		return userService.addScore(userId, score);
	}
	
	/////////////////////////////////////////
	
	@GetMapping("/order-service/{orderId}")
	public JsonResult<Order> getOrder(@PathVariable String orderId) {
		return orderService.getOrder(orderId);
	}

	@GetMapping("/order-service")
	public JsonResult addOrder() {
		return orderService.addOrder();
	}
}

## 启动服务,并访问测试

  • http://eureka1:2001
  • http://localhost:3001/item-service/35
  • http://localhost:3001/item-service/decreaseNumber
    使用postman,POST发送以下格式数据:
    [{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
  • http://localhost:3001/user-service/7
  • http://localhost:3001/user-service/7/score?score=100
  • http://localhost:3001/order-service/123abc
  • http://localhost:3001/order-service/

源码: https://github.com/benwang6/spring-cloud-repo

十五、feign + ribbon 负载均衡和重试

  • 无需额外配置,feign 默认已启用了 ribbon 负载均衡和重试机制。可以通过配置对参数进行调整

重试的默认配置参数:

ConnectTimeout=1000
ReadTimeout=1000
MaxAutoRetries=0
MaxAutoRetriesNextServer=1

## application.yml 配置 ribbon 超时和重试

  • ribbon.xxx 全局配置
  • item-service.ribbon.xxx 对特定服务实例的配置
spring:
  application:
    name: feign
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  
item-service:
  ribbon:
    MaxAutoRetries: 1
    MaxAutoRetriesNextServer: 2
    ConnectTimeout: 1000
    ReadTimeout: 500

## 启动服务,访问测试

http://localhost:3001/item-service/35

源码: https://github.com/benwang6/spring-cloud-repo

十六、feign 整合 hystrix

## feign 启用 hystrix

## application.yml 添加配置

feign 默认没有启用 hystrix ,添加配置,启用 hystrix

feign.hystrix.enabled=true
feign:
  hystrix:
    enabled: true

启用 hystrix 后,访问服务

http://localhost:3001/item-service/35

默认 1秒 会快速失败,没有降级方法时,会显示白板页

可以添加配置,暂时减小降级超时时间,以便后续对降级进行测试

......

feign:
  hystrix:
    enabled: true
    
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

## feign 远程接口中指定降级类(添加降级代码)

远程调用失败, 会执行降级类中的代码

ItemFeignService

...
@FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
public interface ItemFeignService {
...

UserFeignService

...
@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
...

OrderFeignService

...
@FeignClient(name="order-service",fallback = OrderFeignServiceFB.class)
public interface OrderFeignService {
...

## 降级类

降级类需要实现远程接口

ItemFeignServiceFB

package cn.tedu.sp09.service;

import java.util.List;
import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

@Component
public class ItemFeignServiceFB implements ItemFeignService {

	@Override
	public JsonResult<List<Item>> getItems(String orderId) {
		return JsonResult.err("无法获取订单商品列表");
	}

	@Override
	public JsonResult decreaseNumber(List<Item> items) {
		return JsonResult.err("无法修改商品库存");
	}

}


UserFeignServiceFB

package cn.tedu.sp09.service;

import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@Component
public class UserFeignServiceFB implements UserFeignService {

	@Override
	public JsonResult<User> getUser(Integer userId) {
		return JsonResult.err("无法获取用户信息");
	}

	@Override
	public JsonResult addScore(Integer userId, Integer score) {
		return JsonResult.err("无法增加用户积分");
	}

}


OrderFeignServiceFB

package cn.tedu.sp09.service;

import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.web.util.JsonResult;

@Component
public class OrderFeignServiceFB implements OrderFeignService {

	@Override
	public JsonResult<Order> getOrder(String orderId) {
		return JsonResult.err("无法获取商品订单");
	}

	@Override
	public JsonResult addOrder() {
		return JsonResult.err("无法保存订单");
	}

}


## 启动服务,访问测试

http://localhost:3001/item-service/35

源码: https://github.com/benwang6/spring-cloud-repo

十七、feign + hystrix 监控 和 熔断测试

## pom.xml 添加 hystrix 起步依赖

修改sp09-feign项目

<mark>feign 没有包含完整的 hystrix 依赖</mark>
右键点击项目,编辑起步依赖,添加 hystrix 依赖

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

## 主程序添加 @EnableCircuitBreaker

package cn.tedu.sp09;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableCircuitBreaker // 熔断(监控,也需要开启)
@EnableFeignClients // 
@EnableDiscoveryClient // 注册
@SpringBootApplication
public class Sp09FeignApplication {

	public static void main(String[] args) {
		SpringApplication.run(Sp09FeignApplication.class, args);
	}

}


## sp09-feign 配置 actuator,暴露 hystrix.stream 监控端点

actuator 依赖

查看 pom.xml , 确认已经添加了 actuator 依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.yml 暴露 hystrix.stream 端点

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

## 启动服务,查看监控端点

http://localhost:3001/actuator

## hystrix dashboard

启动 hystrix dashboard 服务,填入 feign 监控路径,开启监控
访问 http://localhost:4001/hystrix

## 熔断测试

  • 用 ab 工具,以并发50次,来发送20000个请求
ab -n 20000 -c 50 http://localhost:3001/item-service/35
  • 断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法

源码: https://github.com/benwang6/spring-cloud-repo

十八、order service 调用商品库存服务和用户服务


<mark>sp09-feign项目关闭,不再使用</mark>

## 整合服务

pom.xml

右键点击项目编辑起步依赖,添加以下依赖:

  • actuator
  • feign
  • hystrix
<?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.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.tedu</groupId>
	<artifactId>sp04-orderservice</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sp04-orderservice</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<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>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>cn.tedu</groupId>
			<artifactId>sp01-commons</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<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-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>
				spring-cloud-starter-netflix-hystrix
			</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>
	</dependencies>

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

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Hoxton.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
</project>

application.yml

ribbon 重试和 hystrix 超时这里没有设置,采用了默认值

spring:
  application:
    name: order-service
    
server:
  port: 8201 
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
feign:
  hystrix:
    enabled: true
    
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

主程序

package cn.tedu.sp04;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

//@EnableDiscoveryClient
//@SpringBootApplication

@EnableFeignClients
@SpringCloudApplication
public class Sp04OrderserviceApplication {

	public static void main(String[] args) {
		SpringApplication.run(Sp04OrderserviceApplication.class, args);
	}
}

ItemFeignService

package cn.tedu.sp04.order.feignclient;

import java.util.List;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

@FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
public interface ItemFeignService {
	@GetMapping("/{orderId}")
	JsonResult<List<Item>> getItems(@PathVariable String orderId);

	@PostMapping("/decreaseNumber")
	JsonResult decreaseNumber(@RequestBody List<Item> items);
}


UserFeignService

package cn.tedu.sp04.order.feignclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
	@GetMapping("/{userId}")
	JsonResult<User> getUser(@PathVariable Integer userId);

	@GetMapping("/{userId}/score") 
	JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}

ItemFeignServiceFB

  • 获取商品列表的降级方法,模拟使用缓存数据
package cn.tedu.sp04.order.feignclient;

import java.util.Arrays;
import java.util.List;

import org.springframework.stereotype.Component;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

@Component
public class ItemFeignServiceFB implements ItemFeignService {

	@Override
	public JsonResult<List<Item>> getItems(String orderId) {
		if(Math.random()<0.5) {
			return JsonResult.ok().data(
			
				Arrays.asList(new Item[] {
						new Item(1,"缓存aaa",2),
						new Item(2,"缓存bbb",1),
						new Item(3,"缓存ccc",3),
						new Item(4,"缓存ddd",1),
						new Item(5,"缓存eee",5)
				})
			
			);
		}
		return JsonResult.err("无法获取订单商品列表");
	}

	@Override
	public JsonResult decreaseNumber(List<Item> items) {
		return JsonResult.err("无法修改商品库存");
	}

}

UserFeignServiceFB

  • 获取用户信息的降级方法,模拟使用缓存数据
package cn.tedu.sp04.order.feignclient;

import org.springframework.stereotype.Component;

import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@Componentz
public class UserFeignServiceFB implements UserFeignService {

	@Override
	public JsonResult<User> getUser(Integer userId) {
		if(Math.random()<0.4) {
			return JsonResult.ok(new User(userId, "缓存name"+userId, "缓存pwd"+userId));
		}
		return JsonResult.err("无法获取用户信息");
	}

	@Override
	public JsonResult addScore(Integer userId, Integer score) {
		return JsonResult.err("无法增加用户积分");
	}

}

OrderServiceImpl

package cn.tedu.sp04.order.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp01.service.OrderService;
import cn.tedu.sp04.order.feignclient.ItemFeignService;
import cn.tedu.sp04.order.feignclient.UserFeignService;
import cn.tedu.web.util.JsonResult;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
	
	@Autowired
	private ItemFeignService itemService;
	@Autowired
	private UserFeignService userService;

	@Override
	public Order getOrder(String orderId) {
		//调用user-service获取用户信息
		JsonResult<User> user = userService.getUser(7);
		
		//调用item-service获取商品信息
		JsonResult<List<Item>> items = itemService.getItems(orderId);
		
		
		Order order = new Order();
		order.setId(orderId);
		order.setUser(user.getData());
		order.setItems(items.getData());
		return order;
	}

	@Override
	public void addOrder(Order order) {
		//调用item-service减少商品库存
		itemService.decreaseNumber(order.getItems());
		
		//TODO: 调用user-service增加用户积分
		userService.addScore(7, 100);
		
		log.info("保存订单:"+order);
	}

}

## order-service 配置启动参数,启动两台服务器

  • –server.port=8201
  • –server.port=8202





## 启动服务,访问测试

## hystrix dashboard 监控 order service 断路器

访问 http://localhost:4001/hystrix ,填入 order service 的断路器监控路径,启动监控

  • http://localhost:8201/actuator/hystrix.stream
  • http://localhost:8202/actuator/hystrix.stream

源码: https://github.com/LawssssCat/springcloud

十九、hystrix + turbine 集群聚合监控

hystrix dashboard 一次只能监控一个服务实例,使用 turbine 可以汇集监控信息,将聚合后的信息提供给 hystrix dashboard 来集中展示和监控

## 新建 sp10-turbine 项目


## 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.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.tedu</groupId>
	<artifactId>sp10-turbine</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sp10-turbine</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
		</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>
	</dependencies>

	<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>
		</dependencies>
	</dependencyManagement>

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

</project>

## application.yml

spring:
  application:
    name: turbin
    
server:
  port: 5001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
turbine:
  app-config: order-service
  cluster-name-expression: new String("default")

注意:
new String(“default”)必须这么写
否则启动的时候会抛出异常:
org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field ‘default‘ cannot be found on object of type ‘com.netflix.appinfo.InstanceInfo‘ - maybe not public or not valid?

参数说明:

  • turbine.aggregator.cluster-config 参数设定 cluster 名字
    当使用 default ,默认聚合 turbine.appConfig 中设定的所有服务名的数据;
  • turbine.app-config 参数设定需要收集监控信息的服务名;
  • turbine.cluster-name-expression 参数指定了集群名称为 default
    当我们服务数量非常多的时候,可以启动多个 Turbine 服务来构建不同的聚合集群
    <mark>而该参数可以用来区分这些不同的聚合集群</mark>
    同时该参数值可以在 Hystrix 仪表盘中用来定位不同的聚合集群
    只需要在 Hystrix StreamURL 中通过 cluster 参数来指定。

## 主程序

添加 @EnableTurbine@EnableDiscoveryClient 注解

package cn.tedu.sp10;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.turbine.EnableTurbine;

@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class Sp10TurbineApplication {

	public static void main(String[] args) {
		SpringApplication.run(Sp10TurbineApplication.class, args);
	}

}

## 访问测试

源码: https://github.com/benwang6/spring-cloud-repo

# 二十、zuul API网关

zuul API 网关,为微服务应用提供<mark>统一的对外访问接口</mark>。
zuul 还提供过滤器,对所有微服务提供统一的请求校验。

## 新建 sp11-zuul 项目

## pom.xml

  • <mark>需要添加 sp01-commons 依赖</mark>
<?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.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.tedu</groupId>
	<artifactId>sp11-zuul</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sp11-zuul</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</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>
		<dependency>
			<groupId>cn.tedu</groupId>
			<artifactId>sp01-commons</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	</dependencies>

	<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>
		</dependencies>
	</dependencyManagement>

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

</project>

## applicatoin.yml

  • <mark>zuul 路由配置可以省略,缺省以服务 id 作为访问路径</mark>
spring:
  application:
    name: zuul
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

zuul:
  routes:
    item-service: /item-service/**
    user-service: /user-service/**
    order-service: /order-service/**

## 主程序

添加 @EnableZuulProxy@EnableDiscoveryClient 注解

package cn.tedu.sp11;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@EnableDiscoveryClient // 发现
@SpringBootApplication
public class Sp11ZuulApplication {

	public static void main(String[] args) {
		SpringApplication.run(Sp11ZuulApplication.class, args);
	}

}

## 启动服务,访问测试

# zuul + ribbon 负载均衡

zuul 已经集成了 ribbon,<mark>默认已经实现了负载均衡</mark>

# zuul + ribbon 重试

pom.xml 添加 spring-retry 依赖

  • <mark>需要 spring-retry 依赖</mark>
<dependency>
	<groupId>org.springframework.retry</groupId>
	<artifactId>spring-retry</artifactId>
</dependency>

配置 zuul 开启重试,并配置 ribbon 重试参数

  • <mark>需要开启重试,默认不开启</mark>
spring:
  application:
    name: zuul
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

zuul:
  retryable: true

# 默认全部
# routes:
# item-service: /item-service/**
# user-service: /user-service/**
# order-service: /order-service/**
    
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  MaxAutoRetriesNextServer: 1
  MaxAutoRetries: 1

## zuul + hystrix 降级

创建降级类

  • <mark>getRoute() 方法中指定应用此降级类的服务id,星号或null值可以通配所有服务</mark>

ItemServiceFallback

package cn.tedu.sp11.fallback;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import cn.tedu.web.util.JsonResult;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class ItemServiceFallback implements FallbackProvider {
	// getRoute() 方法中指定应用此降级类的服务id,星号或null值可以通配所有服务
	@Override
	public String getRoute() {
	    //当执行item-service失败,
	    //应用当前这个降级类
		return "item-service";
		//星号和null都表示所有微服务失败都应用当前降级类
		//"*"; //null;
	}

    //该方法返回封装降级响应的对象
    //ClientHttpResponse中封装降级响应
	@Override
	public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return response();
	}

	private ClientHttpResponse response() {
        return new ClientHttpResponse() {
            //下面三个方法都是协议号
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.OK.value();
            }
            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }

            @Override
            public void close() {
            }

            @Override
            public InputStream getBody() throws IOException {
            	log.info("fallback body");
            	String s = JsonResult.err().msg("后台服务错误").toString();
                return new ByteArrayInputStream(s.getBytes("UTF-8"));
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

OrderServiceFallback

package cn.tedu.sp11.fallback;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import cn.tedu.web.util.JsonResult;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class OrderServiceFallback implements FallbackProvider {
	@Override
	public String getRoute() {
		return "order-service"; //"*"; //null;
	}

	@Override
	public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return response();
	}

	private ClientHttpResponse response() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.OK.value();
            }
            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }

            @Override
            public void close() {
            }

            @Override
            public InputStream getBody() throws IOException {
            	log.info("fallback body");
            	String s = JsonResult.err().msg("后台服务错误").toString();
                return new ByteArrayInputStream(s.getBytes("UTF-8"));
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}


降低 hystrix 超时时间,以便测试降级

spring:
  application:
    name: zuul
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

zuul:
  retryable: true
    
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 2000
  MaxAutoRetriesNextServer: 1
  MaxAutoRetries: 1
  
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

启动服务,测试降级

http://localhost:3001/item-service/35

## zuul + hystrix 数据监控

暴露 hystrix.stream 监控端点

  • zuul 已经包含 actuator 依赖
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

开启监控

启动 sp08-hystrix-dashboard ,填入 zuul 的监控端点路径,开启监控
http://localhost:4001/hystrix

填入监控端点:
http://localhost:3001/actuator/hystrix.stream


必须通过zuul网关访问后台服务才产生生监控数据

## zuul + turbine 聚合监控

<mark>修改 turbine 项目,聚合 zuul 服务实例</mark>


spring:
  application:
    name: turbin
    
server:
  port: 5001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
turbine:
  app-config: order-service, zuul
  cluster-name-expression: new String("default")

## 熔断测试

ab -n 20000 -c 50 http://localhost:3001/order-service/123abc

源码: https://github.com/benwang6/spring-cloud-repo

二十一、zuul 请求过滤

## 定义过滤器,继承 ZuulFilter

<mark>在 sp11-zuul 项目中新建过滤器类</mark>

package cn.tedu.sp11.filter;

import javax.servlet.http.HttpServletRequest;

import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import cn.tedu.web.util.JsonResult;

@Component
public class AccessFilter extends ZuulFilter{
	@Override
	public boolean shouldFilter() {
	    //对指定的serviceid过滤,如果要过滤所有服务,直接返回 true
	    
		RequestContext ctx = RequestContext.getCurrentContext();
		String serviceId = (String) ctx.get(FilterConstants.SERVICE_ID_KEY);
		if(serviceId.equals("item-service")) {
			return true;
		}
		return false;
	}

	@Override
	public Object run() throws ZuulException {
		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest req = ctx.getRequest();
		String token = req.getParameter("token");
		if (token == null) {
			//此设置会阻止请求被路由到后台微服务
			ctx.setSendZuulResponse(false);
			//向客户端的响应
			ctx.setResponseStatusCode(200);
			ctx.setResponseBody(JsonResult.err().code(JsonResult.NOT_LOGIN).toString());
		}
		//zuul过滤器返回的数据设计为以后扩展使用,
		//目前该返回值没有被使用
		return null;
	}

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

	@Override
	public int filterOrder() {
	    //该过滤器顺序要 > 5,才能得到 serviceid
		return FilterConstants.PRE_DECORATION_FILTER_ORDER+1;
	}
}

## 访问测试

源码: https://github.com/benwang6/spring-cloud-repo

二十二、zuul Cookie过滤

zuul 会过滤敏感 http 协议头,默认过滤以下协议头:

  • Cookie
  • Set-Cookie
  • Authorization

可以设置 zuul 不过滤这些协议头

zuul:
  sensitive-headers: 

二十三、config 配置中心



yml 配置文件保存到 git 服务器,例如 github.comgitee.com

微服务启动时,从服务器获取配置文件

## github 上存放配置文件

新建 “Project”,命名为 config


将sp02,sp03,sp04,sp11四个项目的yml配置文件,复制到config项目,并改名

  • item-service-dev.yml
  • user-service-dev.yml
  • order-service-dev.yml
  • zuul-dev.yml

  • <mark>最后,清空四个项目中的 application.yml 文件</mark>

禁止配置中心的配置信息覆盖客户端配置

默认配置中心配置 <mark>优先级高</mark>, <mark>配置中心配置会覆盖客户端的所有配置,</mark>

包括命令行参数配置,这样我们在 item-serviceorder-service 中配置的端口号启动参数会无效

item-service 启动参数:

  • –service.port=8001
  • –service.port=8002
    .

order-service 启动参数

  • –service.port=8201
  • –service.port=8202

我们可以设置禁止配置中心的配置将客户端配置覆盖掉
<mark>在四个配置文件中添加下面的配置</mark>

spring:
  ......
  cloud:
    config:
      override-none: true

将 config 项目上传到 github

  • <mark>新建仓库</mark>

  • <mark>仓库命名</mark>

  • <mark>将项目分享到仓库</mark>

  • <mark>选择新建本地仓库</mark>

  • 仓库目录选择 <mark>工作空间</mark> 目录下一个新目录: sp-config

  • 提交项目

  • <mark>填写sp-config仓库地址</mark>

  • <mark>查看远程仓库文件</mark>

## config 服务器

config 配置中心从 git 下载所有配置文件。

而其他微服务启动时从 config 配置中心获取配置信息。

新建 sp12-config 项目


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.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.tedu</groupId>
	<artifactId>sp12-config</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sp12-config</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-server</artifactId>
		</dependency>
		<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-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<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>
		</dependencies>
	</dependencyManagement>

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

</project>

application.yml

spring:
  application:
    name: config-server
  
  cloud:
    config:
      server:
        git:
          uri: https://github.com/你的个人路径/sp-config
          searchPaths: config
          #username: your-username
          #password: your-password
    
server:
  port: 6001
    
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

主程序添加 @EnableConfigServer 和 @EnableDiscoveryClient

package cn.tedu.sp12;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;

@EnableConfigServer
@EnableDiscoveryClient
@SpringBootApplication
public class Sp12ConfigApplication {

	public static void main(String[] args) {
		SpringApplication.run(Sp12ConfigApplication.class, args);
	}

}

启动,访问测试

## config 客户端

修改以下项目,从配置中心获取配置信息

  • sp02-itemservice
  • sp03-userservice
  • sp04-orderservice
  • sp11-zuul

pom.xml 添加 config 客户端依赖

右键点击项目,编辑起步依赖,添加 config client 依赖

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

在四个项目中添加 bootstrap.yml


bootstrap.yml,引导配置文件,先于 application.yml 加载

  • <mark>item-service</mark>
spring:
  cloud:
    config:
      discovery:
        enabled: true # config server 可发现
        service-id: config-server # config server id
      name: item-service # 哪个配置
      profile: dev # 哪个profile
      
eureka:
  client:
    service-url:
	  # 指定默认注册中心
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

  • <mark>user-service</mark>
spring: 
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
      name: user-service
      profile: dev
      
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

  • <mark>order-service</mark>
spring: 
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
      name: order-service
      profile: dev
      
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

  • <mark>zuul</mark>
spring: 
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
      name: zuul
      profile: dev
      
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

启动服务,观察从配置中心获取配置信息的日志

注意:这里端口号不同,是因为在命令行里面写了端口号,并且在配置里面声明不忽略命令行的操作

控制台打印:

如果发现服务无法请求别的服务。
去注册中心看看
.
如果在 eureka 界面发现(这里。在配置里面配置,能看到服务连接的 ip)
服务 ip 不是我们本机的 192.168.64.1(服务是本机开的,理所当然应该是本机ip)

发现用的是我 虚拟机的另一个 net 网卡 ip

分析服务器和内部局域网的网络结构

向 eureka 注册

  1. 列出所有网卡
    172.19.0.xxx
    192.167.1.xxx
    192.168.64.xxx
  2. 服务提交ip时,顺序查找不是本机回环 ip 的地址

<mark>之所以出现上面,ip不是本机,正是因为顺序查找时候,先找到上面jip,误以为服务来自上面ip</mark>
最终导致,别的服务无法连接到错误ip的服务

因此,我们需要<mark>在每个服务上</mark>指定使用的正确的网卡

<mark>其次,eureka 注册中心,为服务注册时,有可能自动选择主机名进行注册</mark>,而不是使用 ip 地址。
<mark>主机名在局域网中有可能不会被正确的解析</mark>
在,eureka 上,应该使用 ip 进行注册
并且,指定在界面列表中显示的格式

eureka:
 instance: 
   instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}

## 配置刷新

spring cloud 允许 <mark>运行时动态刷新</mark> 配置,可以<mark>重新从配置中心获取新的配置信息</mark>

user-service 为例演示配置刷新

pom.xml

user-servicepom.xml 中添加 actuator 依赖
右键点击 sp03-user-service 项目,编辑起步依赖,添加 actuator 依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

yml 配置文件中暴露 refresh 端点

  • <mark>修改 config 项目中的 user-service-dev.yml</mark>, 并提交推送到远程仓库
    并提交推送到远程仓库
    并提交推送到远程仓库
    并提交推送到远程仓库
    <mark>并提交推送到远程仓库</mark>
    并提交推送到远程仓库
sp:
  user-service:
    users: "[{\"id\":7, \"username\":\"abc\",\"password\":\"123\"},{\"id\":8, \"username\":\"def\",\"password\":\"456\"},{\"id\":9, \"username\":\"ghi\",\"password\":\"789\"}]"

spring:
  application:
    name: user-service
  cloud:
    config:
      override-none: true
    
server:
  port: 8101
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka  

management:
  endpoints:
    web:
      exposure:
        include: refresh

UserServiceImpl 添加 @RefreshScope 注解

  • <mark>只允许对添加了 @RefreshScope@ConfigurationProperties 注解的 Bean 刷新配置</mark>,可以将更新的配置数据注入到 Bean
package cn.tedu.sp03.user.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.type.TypeReference;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp01.service.UserService;
import cn.tedu.web.util.JsonUtil;

import lombok.extern.slf4j.Slf4j;

@RefreshScope
@Slf4j
@Service
public class UserServiceImpl implements UserService {
	@Value("${sp.user-service.users}")
	private String userJson;
	
	@Override
	public User getUser(Integer id) {
		log.info("users json string : "+userJson);
		List<User> list = JsonUtil.from(userJson, new TypeReference<List<User>>() {});
		for (User u : list) {
			if (u.getId().equals(id)) {
				return u;
			}
		}
		
		return new User(id, "name-"+id, "pwd-"+id);
	}

	@Override
	public void addScore(Integer id, Integer score) {
		// 这里增加积分
		log.info("user "+id+" - 增加积分 "+score);
	}

}

重启配置中心, 再重启sp03, 查看暴露的刷新端点

修改config项目的user-service-dev.yml文件并提交

现在的配置数据中只有7,8,9三个测试用户的数据,没有99这个用户的数据

在user-service-dev.yml文件添加99用户的数据

sp:
  user-service:
    users: "[{\"id\":7, \"username\":\"abc\",\"password\":\"123\"},{\"id\":8, \"username\":\"def\",\"password\":\"456\"},{\"id\":9, \"username\":\"ghi\",\"password\":\"789\"},{\"id\":99, \"username\":\"aaa\",\"password\":\"111\"}]"

<mark>修改后提交推送到远程仓库</mark>
测试访问

用postman访问刷新端点, 刷新配置

  • 刷新端点路径:
    http://localhost:8101/actuator/refresh

  • 使用 postman 向刷新端点发送 post 请求

访问 user-service,查看动态更新的新用户数据

源码: https://github.com/benwang6/spring-cloud-repo


二十四、config bus + rabbitmq 消息总线配置刷新

上面实现了点的配置刷新,但是点多的时候,麻烦。

要让多个服务同时刷新,可以添加 config bus


<mark>这里的,bus 我们叫 消息总线(辅助传送消息)</mark>

bus 接收到 配置中心的刷新消息 ,就群发到各个服务点

群发过程中,消息总线需要一个中间服务,叫 消息中间件服务器、消息服务器、消息队列服务器
.
常用的:

  • RabbitMQ (目前好用 2020年3月10日)
  • ActiveMQ
  • RocketMQ (阿里)(需要集成)

下面,我们先把消息中间件 rabbitmq 搭建起来

post 请求消息总线刷新端点,服务器会向 rabbitmq 发布刷新消息,接收到消息的微服务会向配置服务器请求刷新配置信息

## rabbitmq 安装笔记

RabbitMQ 服务器搭建

  1. 克隆出 centos-7-1810 创建连接克隆,起名 rabbitmq>
    右键>管理>克隆>当前状态>链接克隆

  2. 启动后,设置ip
    ip:192.168.64.140(宿主机 192.168.64.1 )

    脚本 ./setip.sh 可直接设置 - 下载

  3. 检查是否跟 windows 宿主机互联(ping 不通,重复步骤2,检查联网模式,网关,ip,服务是否重启)

  4. 远程工具连接140服务器(xshell、winscp、filezilla 随便)(推荐圣器MobaXterm)
    后面两个用于文件传输
    <mark>文件传输选择 sftp协议</mark>

接着,就是 rabbitmq 的安装


笔记 1 ⭐️⭐️⭐️ https://lawsssscat.blog.csdn.net/article/details/104768116
笔记 2 https://blog.csdn.net/weixin_38305440/article/details/102810522

## 动态更新配置的微服务

根据上面的笔记,配置完 rabbitmq ,在管理界面把 新建的用户 admin 权限全部打开。就可以继续下面操作了。

  1. 需要动态更新配置的微服务,添加 spring cloud bus 依赖,并添加 rabbitmq 连接信息
    修改以下微服务

    • sp02-item-service
    • sp03-user-service
    • sp04-order-service
    • sp11-zuul
    • sp12-config
  2. 然后,配置 rabbitmq 的连接信息
    直接修改sp12-config 的 application.yml
    2/3/4/11 的 config目录的四个文件,并推送到远程仓库

  3. config 项目暴露 bus-refresh 刷新断点

pom.xml 添加 spring cloud bus 依赖

使用 STS 编辑起步依赖,分别添加 busrabbitmq 依赖

<mark>修改5个项目</mark>

<!-- bus依赖 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-bus</artifactId>
		</dependency>
<!-- rabbitmq 三个依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-rabbit-test</artifactId>
			<scope>test</scope>
		</dependency>

配置文件中添加 rabbitmq 连接信息

在以下配置文件中修改:

  • config 中的4个配置文件
  • sp12-config 项目的 application.yml

注意:

  • <mark>连接信息请修改成你的连接信息</mark>
  • <mark>config项目需要提交</mark>
spring:
  ......
  rabbitmq:
    host: 192.168.64.140
    # rabbit client 端通信端口
    port: 5672
    username: admin
    password: admin

注意,修改了要提交

config-server 暴***us-refresh 刷新端点

修改 sp12-config 项目的 application.yml, 暴***us-refresh端点

management:
  endpoints:
    web:
      exposure:
        include: bus-refresh

## 启动服务(看启动是否成功)

启动后
localhost:2001

http://192.168.64.140:15672/#/connections

## 请求刷新端点发布刷新消息

注意:

  • 在新标签中测试

  • 如果刷新指定的微服务,可按下面格式访问:
    http://localhost:6001/actuator/bus-refresh/user-service:8101
    (指定服务,指定端口的服务配置刷新)

## config 本地文系统(选做)

可以把配置文件保存在配置中心服务的 resources 目录下,直接访问本地文件
把配置文件保存到 sp12-config 项目的 resources/config 目录下

修改 application.yml 激活 native profile,并指定配置文件目录

  • <mark>必须配置</mark> spring.profiles.active=native 来激活本地文件系统
  • <mark>本地路径默认</mark>:[classpath:/, classpath:/config, file:./, file:./config]
spring:
  application:
    name: config-server
  profiles:
    active: native
  
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config

# git:
# uri: https://github.com/你的用户路径/sp-config
# searchPaths: config
# username: your-username
# password: your-password
        
    
  rabbitmq:
    host: 192.168.64.140
    port: 5672
    username: admin
    password: admin

    
server:
  port: 6001
    
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
management:
  endpoints:
    web:
      exposure:
        include: bus-refresh

源码: https://github.com/benwang6/spring-cloud-repo

二十五、sleuth 链路跟踪



随着系统规模越来越大,<mark>微服务之间调用关系变得错综复杂,一条调用链路中可能调用多个微服务,任何一个微服务不可用都可能造整个调用过程失败</mark>

<mark>spring cloud sleuth 可以跟踪调用链路,分析链路中每个节点的执行情况</mark>

## sleuth 跟踪原理

跟踪

链路头,会生成一个标识(<mark>黄色部分</mark>),标识整条链路(下图)

链路每个节点,都会存储这个标识,

如果标识一样,就说明是同一条链路

监控

每条链路节点上还有一个布尔值,用来指定这个节点是否被监控

如zipking 监控,会把监控信息呈现出来(下图)


<mark>注意</mark>

  1. 如果访问量大,每次访问都会有数据发送到zipking,导致网络拥挤
  2. 因此,sleuth、zipking 默认只分析 10% 的数据
    (10次请求,只有一次发送到zipking)

下面,分两步

  • 添加 sluth
  • 添加 zipking

## 微服务中添加 spring cloud sleuth 依赖

(非常简单,添加依赖,无需配置)

修改以下微服务的 pom.xml,添加 sleuth 依赖

  • sp02-item-service
  • sp03-user-service
  • sp04-order-service
  • sp11-zuul

编辑起步依赖,分别 sleuth 依赖
(为什么选这四个呢?因为这四个有调用关系)

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

## 在控制台查看链路跟踪日志

四个微服务的控制台日志中,可以看到以下信息:
[服务id,请求id,span id,是否发送到zipkin]

  • 请求id:请求到达第一个微服务时生成一个请求id,该id在调用链路中会一直向后面的微服务传递
  • span id:链路中每一步微服务调用,都生成一个新的id

[zuul,6c24c0a7a8e7281a,6c24c0a7a8e7281a,false]

[order-service,6c24c0a7a8e7281a,993f53408ab7b6e3,false]

[item-service,6c24c0a7a8e7281a,ce0c820204dbaae1,false]

[user-service,6c24c0a7a8e7281a,fdd1e177f72d667b,false]


二十六、sleuth + zipkin 链路分析

zipkin 可以收集链路跟踪数据,提供 <mark>可视化</mark> 的链路分析

<mark>zipkin 不直接从服务接收数据,通过从rabbitmq中获取数据</mark>

## 修改 2/3/4/11 向 rabbitmq 发送链路跟踪信息

  • 添加 zipkin 客户端依赖
  • 添加 rabbitmq 依赖 和链接信息(前面完成了)

## 链路数据抽样比例

默认 10% 的链路数据会被发送到 zipkin 服务。可以配置修改抽样比例

spring:
  sleuth:
    sampler:
      probability: 0.1

## zipkin 服务

官网:https://zipkin.io/

下载 zipkin 服务器
https://github.com/openzipkin/zipkin

## 启动 zipkin 时,连接到 rabbitmq

java -jar zipkin-server-2.12.9-exec.jar --zipkin.collector.rabbitmq.uri=amqp://admin:admin@192.168.64.140:5672

<mark>注意,这里需要有 --zipkin.collector.rabbitmq.uri 参数,用以指定要链接的rabbitmq服务器(指定从哪里接收信息)</mark>

新版logo(2020年3月11日)

<mark>9441端口</mark>

## 微服务添加 zipkin 起步依赖

修改以下微服务

  • sp02-item-service
  • sp03-user-service
  • sp04-order-service
  • sp11-zuul
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

<mark>如果没有配置过 spring cloud bus,还需要添加 rabbitmq 依赖和连接信息</mark>

## 启动并访问服务,访问 zipkin 查看链路分析


新版

源码: https://github.com/benwang6/spring-cloud-repo