SpringCloud 基础篇

Eureka

CAP

​ Consitency 一致性

​ Availablility 可用性

​ Partition tolerance 分区容错性

Eureka : AP

Zookeeper : CP

Eureka 体系架构

在这里插入图片描述

每个Server和Client都会维护一个自己的注册表在本地服务,而且是不一致的(也就是没有C–》数据不一致)

工程

Eureka Server
1、Eureka Server依赖

2@EnableEurekaServer : 注解到启动类

配置yml

eureka:
  instance:
    hostname: localhost #指定Eureka主机
  client:
    register-with-eureka: false # 指定是否向注册中心注册自己 (自己是否注册自己)
    fetch-registry: false # 指定此客户端是否能够获取eureka注册信息
    service-url:  #暴露服务中心地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
Eureka Client (provider)
1、Eureka Client 依赖

  <!--eureka 客户端依赖-->
  <dependency>
    <groupId>org.springframework.cloud</groupId> 
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>

2、在配置文件中指定要注册的 Eureka Server 地址,指定自己微服务名称
  

配置yml

eureka:
  instance:
    instance-id: localhost #指定当前客户端在注册中心的名称
  client:
    service-url:  #暴露服务中心地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
      
spring:
	application:
		name: eureka-provider-8080 # 指定当前微服务对外暴露的名称
Eureka Client (consumer)
1、Eureka Client 依赖

  <!--eureka 客户端依赖-->
  <dependency>
    <groupId>org.springframework.cloud</groupId> 
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>

2、在配置文件中指定要注册的 Eureka Server 地址,指定自己微服务名称

3、在javaConfig 类中为 RestTemplate 添加 @LoadBalanced, 实现负载均衡
  
  	@LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

配置yml

eureka:
  instance:
    instance-id: localhost #指定当前客户端在注册中心的名称
  client:
    service-url:  #暴露服务中心地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
      
spring:
	application:
		name: eureka-consumer-8081 # 指定当前微服务对外暴露的名称
Client 注解的区别
@EnableEurekaClient // 只发现注册 Eureka Client
@EnableDiscoveryClient // 注册发现所有(Eureka 、 consul 、 nacos、zk)

注意: 当引入了 spring-cloud-starter-netflix-eureka-client; 两个注解都可以不用,可自动服务发现注册

Eureka 自我保护机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qzr3gzt2-1604910603333)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201102105023608.png)]

【原文】Emergency (紧急情况) ! Eureka may be incorrectly claiming(判断) instances(指微服务 主机) are up when they’re not. Renewals(续约,指收到的微服务主机的心跳) are lesser than threshold(阈值) and hence(从此) the instances are not being expired(失效) just to be(只是为了) safe.

【翻译】紧急情况!当微服务主机联系不上时,Eureka 不能够正确判断它们是否处于 up 状 态。当更新(指收到的微服务主机的心跳)小于阈值时,为了安全,微服务主机将不再失效。

默认情况下,EurekaServer 在 90 秒内没有检测到服务列表中的某微服务,则会自动将 该微服务从服务列表中删除。但很多情况下并不是该微服务节点(主机)出了问题,而是由 于网络抖动等原因使该微服务无法被EurekaServer发现,即无法检测到该微服务主机的心跳。 若在短暂时间内网络恢复正常,但由于 EurekaServer 的服务列表中已经没有该微服务,所以 该微服务已经无法提供服务了。

在短时间内若 EurekaServer 丢失较多微服务,即 EurekaServer 收到的心跳数量小于阈值, 为了保证系统的可用性(AP),给那些由于网络抖动而被认为宕机的客户端“重新复活”的 机会,Eureka 会自动进入自我保护模式:服务列表只可读取、写入,不可执行删除操作。当 EurekaServer 收到的心跳数量恢复到阈值以上时,其会自动退出 Self Preservation 模式。

默认值修改

启动自我保护的阈值因子默认为 0.85,即 85%。即 EurekaServer 收到的心跳数量若小于 应该收到数量的 85%时,会启动自我保护机制。

自我保护机制默认是开启的,可以通过修改 EurekaServer 中配置文件来关闭。但不建议 关闭。

eureka:
	server:
		renewal-percent-threshold: 0.75 #设置自我保护机制阈值,默认是0.85
		enable-self-preservation: false # 关闭自我保护机制,默认是 true
		

OpenFeign

简介

声明式 REST 客户端:Feign 通过使用 JAX-RS(Java Api eXtensions for RESTful Web Services,简单来说,就是一种使用注解来实现 RESTful 的技术)或 SpringMVC 注解的装饰方 式,生成接口的动态实现。

Feign,假装,伪装。
OpenFeign 可以将提供者提供的 Restful 服务伪装为接口进行消费,消费者只需使用“feign 接口 + 注解”的方式即可直接调用提供者提供的 Restful 服务,而无需再使用 RestTemplate。
需要注意:
	该伪装的Feign接口是由消费者调用,与提供者没有任何关系。 
  Feign仅是一个伪客户端,其不会对请求做任何处理。
	Feign是通过注解的方式实现RESTful请求的。
OpenFeign 是运行在消费者端的,使用 Ribbon 进行负载均衡,所以 OpenFeign 直接内 置了 Ribbon。即在导入 OpenFeign 依赖后,无需再专门导入 Ribbon 依赖了。

开发步骤

1、添加OpenFeign依赖
  
  <!--feign 依赖--> 
  <dependency>
		<groupId>org.springframework.cloud</groupId> 
  	<artifactId>spring-cloud-starter-openfeign</artifactId>
	</dependency>
  
2、定义Feign接口,指定要访问的微服务
   
  @FeignClient("service-provider-demo")
  @RequestMapping("/provider/demo")
  public interface ServiceInterface {
      @PostMapping("/save")
      boolean save(@RequestBody Model model);
  }

3、在启动类上添加@EnableFeignClients(basePackages="com.ckl.edu.consumer.service")注解,指定扫描的包
  

超时设置

feign:
	client:
		config:
			default:
				connectTimeout: 5000 # 指定feign连接提供者的超时时限
				readTimeout: 5000 # 指定feign从请求到提供者响应的超时时限

Gizip压缩设置

compression:
	request:
		enable: true # 开启对请求的压缩
		mime-types: ["text/xml","application/xml","application/json"] # 指定对那些 MIME 类型的文件进行压缩
		min-request-size: 2048 # 指定弃用压缩的最小文件大小,单位字节
	response:
		enable: true # 开启对客户端响应的压缩

Ribbon 负载均衡

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0QPAusqZ-1604910603335)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201102155546655.png)]

consumer 自己维护一张服务注册表在本地。

内置负载均衡策略

1) RoundRobinRule 轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多 轮询 10 轮。若最终还没有找到,则返回 null。

2) RandomRule	随机策略,从所有可用的 provider 中随机选择一个。

3) RetryRule	重试策略。先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内
重试。默认的时限为 500 毫秒。

4) BestAvaliableRule 最可用策略。选择并发量最小的 provider,即连接的消费者数量最少的 provider。

5) AvailabilityFilteringRule 可用过滤算法。该算法规则是:过滤掉处于熔断状态的 provider 与已经超过连接极限的 provider,对剩余 provider 采用轮询策略。

6) ZoneAvoidanceRule zone 回避策略。根据 provider 所在 zone 及 provider 的可用性,对 provider 进行选择。

7) WeightedResponeTimeRule “权重响应时间”策略。根据每个 provider 的平均响应时间计算其权重,响应时间越快
权重越大,被选中的机率就越高。在刚启动时采用轮询策略。后面就会根据权重进行选择了。
	

更换内置策略

方式一:配置文件

service-provider-demo: # 要负载均衡的提供者微服务名称
	ribbon: # 指定要使用的负载均衡策略
		NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

方式二:javaConfig (bean)

	@Bean
  public IRule loadBalanceRule() {
      return new RandomRule();
  }

Hystrix 服务熔断与服务降级

在分布式环境中,许多服务依赖中的一些服务发生失败是不可避免的。Hystrix 是一 个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix 通过 隔离服务之间的访问点、停止跨服务的级联故障以及提供回退选项来实现这一点,所有这些 都可以提高系统的整体弹性。

Hystrix 是一种开关装置,类似于熔断保险丝。在消费者端安装一个 Hystrix 熔断器,当 Hystrix 监控到某个服务发生故障后熔断器会开启,将此服务访问链路断开。不过 Hystrix 并 不会将该服务的消费者阻塞,或向消费者抛出异常,而是向消费者返回一个符合预期的备选 响应(FallBack)。通过 Hystrix 的熔断与降级功能,避免了服务雪崩的发生,同时也考虑到了 用户体验。故 Hystrix 是系统的一种防御机制。

服务熔断

  1. 雪崩效应

2)服务雪崩

3)熔断机制

服务降级

Hystrix 对于服务降级的实现方式有两种:
1、fallbackMethod 服务降级 
2、fallbackFactory 服务降级。
fallbackMethod 服务降级

步骤

1、添加Hystrix依赖
  <!--hystrix 依赖--> 
  <dependency>
    <groupId>org.springframework.cloud</groupId> 
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  </dependency>

2、修改处理器方法。在处理器方法上添加@HystrixCommond注解
3、在处理器中定义服务降级方法
  
	@HystrixCommand(fallbackMethod = "getHystrixHandle")
    @PostMapping("/get/{id}")
    boolean save(@PathVariable("id") String id){
        return true;
    }

    public boolean getHystrixHandle(@PathVariable("id") String id){
       return true;
    }

4、在启动类上添加 @EnableCircuitBreaker 注解(或将@SpringBootApplication注解替换为 @SpringCloudApplication 注解)
  
  
fallbackFactory 服务降级。

定义降级处理类

@Component
public class OrmDataServiceFallbackFactory implements FallbackFactory<OrmDataService> {

    @Override
    public OrmDataService create(Throwable throwable) {
        return new OrmDataService(){
            @Override
            public List<Map<String, String>> getDownSpeed() {
                return new ArrayList<>();
            }
        };
    }
}

修改 Feign 接口

@FeignClient(value = "OrmDataService-provider-demo", fallbackFactory = OrmDataServiceFallbackFactory.class)
@RequestMapping("/provider/demo")
public interface OrmDataServiceFeign  {
    @PostMapping("getDownSpeed")
    List<Map<String,String>> getDownSpeed();
}

FallBack

定义降级处理类

@Component
@RequestMapping("/fallback/provider/demo")
class OrmDataServiceFallback implements OrmDataService {

    @Override
    public List<Map<String, String>> getDownSpeed() {
        return null;
    }
}

修改 Feign 接口

@FeignClient(value = "OrmDataService-provider-demo", fallback = OrmDataServiceFallback.class)
@RequestMapping("/provider/demo")
public interface OrmDataServiceFeign  {
    @PostMapping("getDownSpeed")
    List<Map<String,String>> getDownSpeed();
}

隔离策略

1)线程隔离 : Hystrix的默认隔离策略。系统会创建一个依赖线程池,为每个依赖请求分配一个独立的线程,而每个依赖所拥有的线程数量是有上限的。当对该依赖的调用请求数 量达到上限后再有请求,则直接拒绝该请求,并对该请求做降级处理。所以对某依赖的 并发量取决于为该依赖线程池所分配的线程数量。

2)信号量隔离 : 对依赖的调用所使用的线程仍为请求线程,即不会为依赖请求再新创建新 的线程。但系统会为每种依赖分配一定数量的信号量,而每个依赖请求分配一个信号。 当对该依赖的调用请求数量达到上限后再有请求,则直接拒绝该请求,并直接对该请求 做降级处理。所以对某依赖的并发量取决于为该依赖所分配的信号量数量。

对比
1) 线程是进程的一个执行体,其具有独立运行的特性,而信号量却不是,其仅仅是线程执行的条件。
2) 线程隔离中请求线程与提供者调用线程不是同一个线程,而信号量隔离中请求线程与调用线程是同一个线程。
3) 线程隔离的执行效率要高于信号量隔离的,因为线程隔离的执行体数量是信号量隔离的2倍。
4) 线程隔离使每台主机处理请求的数量是有限制的,因为主机线程数量是有上限的。而信号量隔离不同,其没有上限,因为所谓信号量就是一个计数器,是一个数值,其不存在上限。
5) 在服务器少而请求并发量大的情况下不建议使用线程隔离,否则可能会使系统对请求的并发能力下降。
6) 线程隔离便于控制反馈给客户端的降级时间。
修改策略
hystrix.command.default.execution.isolation.strategy=thread
hystrix.command.default.execution.isolation.strategy=semaphore
代码中
HystrixCommandProperties.Setter().withExecutionLsolationStrategy(ExecutionIsolationStrategy.THREAD)

HystrixCommandProperties.Setter().withExecutionLsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
默认值

在HystrixCommandProperties 类的构造器中设置有这些高级属性的默认值

执行隔离其它属性

1)线程执行超时时限 :

​ 在默认的线程执行隔离策略中,关于线程的执行时间,可以为其设置超时时限。当然, 首先通过下面的属性开启该超时时限,该属性默认是开启的,即默认值为 true。若要关闭, 则可以配置文件中设置该属性的值为 false。

hystrix.command.default.execution.timeout.enabled = true

开启超时时限后,设置时限长度(默认值:1000毫秒)

hystrix.command.default.execution.isolation.thread.timeoutInMillseconds = 1000
2)超时中断(默认true)
hystrix.command.default.execution.isolation.thread.interruptOnTimeout = true
3)取消中断(默认false)
hystrix.command.default.execution.isolation.thread.interruptOncanle = false

4)信号量数量(默认10)

hystrix.command.default.execution.isolation.samephore.maxConcurrentRequests = 10
服务降级属性
1)降级请求最大数量(默认:10)

​ 该属性仅限于信号量隔离。当信号量已用完后再有请求到达,并不是所有请求都会进行 降级处理,而是在该属性设置值范围内的请求才会发生降级,其它请求将直接拒绝。

hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests = 10
2)服务降级开关(默认 true)

无论是线程隔离还是信号量隔离,当请求数量到达其设置的上限后再有请求到达是否会对请求进行降级处理,取决于该属性值的设置。若该属性值设置为 false,则不进行降级, 而是直接拒绝请求。

hystrix.command.default.fallback.enabled = true
服务熔断属性
1)熔断功能开关(开启熔断器功能,默认 true)
hystrix.command.default.circuitBreaker.enable = true
2)熔断器开启阈值(默认值 为 20)

当在时间窗内(10 秒)收到的请求数量超过该设置的数量后,将开启熔断器。默认值 为 20。

注意,开启熔断器是指将拒绝所有请求;关闭熔断器是指将使所有请求通过。

hystrix.command.default.circuitBreaker.requestVolumeThreshold = 20
3)熔断时间(默认值 为 5000 毫秒)

当熔断器开启该属性设置的时长后,会尝试关闭熔断器,以恢复被熔断的服务。默认值 为 5000 毫秒。

hystrix.command.default.circuitBreaker.sleepWindowMillsesonds = 5000
4) 熔断开启错误率(默认值为 50,即 50%)

当请求的错误率高于该百分比时,开启熔断器。默认值为 50,即 50%。

hystrix.command.default.circuitBreaker.errorThresholdPercentage = 50
5) 强制开启熔断器( 默认值为 false)

设置熔断器无需条件开启,拒绝所有请求。默认值为 false。

hystrix.command.default.circuitBreaker.forceOpen = false
6) 强制关闭熔断器( 默认值为 false)

设置熔断器无需条件的关闭,通过所有请求。默认值为 false。

hystrix.command.default.circuitBreaker.forceClosed = false

线程池相关属性

线程池相关配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6VsXVT4z-1604910603338)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201103114456301.png)]

Dashboard 监控仪表盘

开发步骤

1) 添加hystrix-dashboard与actuator依赖
    <!-- hystrix-dashboard 依赖 --> 
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> 
		</dependency>
    <!--actuator 依赖--> 
    <dependency>
      <groupId>org.springframework.boot</groupId> 
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

2) 配置文件中开启actuator的hystrix.stream监控终端 
		
		# 开启 actutor 的相关监控终端
		management:
			endpoint:
				web:
					exposure:
						include: hystrix.stream
		# 设置服务熔断时限
		hystrix:
			command:
				default:
					execution:
						isolation:
							thread:
								timeoutInMilliseconds: 3000


3) 在启动类上添加@EnableHystrixDashboard注解

仪表盘

(SSE Server-Send Events, H5 中的一个子规范)

GUI

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0RLxTuu1-1604910603341)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201103115234167.png)]

Turbine

Turbine 可以使用一个 Dashboard 来查看你想查看的一个或 若干个集群的运行状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K2WWGb28-1604910603342)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201103115355654.png)]

开发
(1) Turbine Client
			至少要有actuator与neflix-hystrix依赖
  		
  		<!--actuator 依赖-->
      <dependency>
     		 <groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter-actuator</artifactId> 						</dependency>
      <!--hystrix 依赖--> 
			<dependency>
      		<groupId>org.springframework.cloud</groupId> 
        	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
      </dependency>
  
			在配置文件中必须开启acturator的hystrix.stream监控终端
			# 开启 actutor 的相关监控终端
      management:
        endpoint:
          web:
            exposure:
              include: hystrix.stream
			
			spring:
				application:
					name: turbine-8180 # 指定微服务对外暴露的名称
(2) Turbine Server
			至少要有如下依赖:
			netflix-turbine依赖
			netflix-hystrix-dashboard 依赖 
			netflix-hystrix 依赖
			actuator依赖
			eureka client 依赖
			
			<!-- hystrix-turbine 依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
        </dependency>
        <!-- hystrix-dashboard 依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <!--hystrix 依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--actuator 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--eureka 客户端依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        
        修改启动类
        添加 
        @EnableTurbine // 开启Trubine 集合功能 
        @EnableHystrixDashboard // 开启Hystrix仪表盘功能

		在配置文件中要配置turbine:指定要监控的group及相应的微服务名称
		
		turbine:
			app-config: turbine-8180 # 指定要监控的微服务名称 用 , 隔开
			aggregator: 
				#cluster-config: group1,group2
				custer-config: default # 指定要监控的微服务组,default为默认组
			cluster-name-expreesion: "'default'" # 指定要监控的微服务组名称,默认组名称为default
Turbine 聚合监控**–**监控多个组集群

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-noshLMbD-1604910603343)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201103143751203.png)]

配置文件

server:
	port: 8880

spring:
	application:
		name: turbine-8880
		
eureka:
	client:
		service-url:
			defaultZone: http://localhost:8000/eureka # 指定Eureka服务注册中心
	instance:
		metdata-map:
			cluster: group1 # 自定义Eureka元数据

Turbine Server

server:
	port: 8888

turbine:
			app-config: turbine-8880 # 指定要监控的微服务名称 用 , 隔开
			aggregator: 
				cluster-config: group1,group2
			cluster-name-expreesion: metadate['cluster'] # 指定要监控的微服务组名称,来自于Eureka元数据cluster

Zuul

ZUUL 是从设备和 web 站点到 Netflix 流应用后端的所有请求的前门。作为边界服务 应用,ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。它还具有根据情况将请 求路由到多个 Amazon Auto Scaling Groups 的能力。

Zuul 主要提供了对请求的路由与过滤功能。
1) 路由:将外部请求转发到具体的微服务实例上,是外部访问微服务的统一入口。
2) 过滤:对请求的处理过程进行干预,对请求进行校验、鉴权等处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yflucul1-1604910603344)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201103153408476.png)]

开发步骤

添加依赖
	<!--zuul 依赖--> 
	<dependency>
		<groupId>org.springframework.cloud</groupId> 				
		<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
	</dependency>
	
	在启动类添加注解
	@EnableZuulProxy // 开启 zuul 代理模式

路由策略

路由策略配置
zuul:
	routes: 
		# 指定微服务的路由规则
		service-consumer-demo-8080: /demo8080/**
		service-consumer-demo-8081: /demo8081/**
路由前辍
zuul:
	# 指定统一前缀
	prefix: /ckl
服务名屏蔽
zuul:
	# 屏蔽所有微服务名称
	ignored-service: "*"
	# 屏蔽指定的微服务名称
	# ignored-service: service-consumer-demo-8080
路径屏蔽
zuul:
	# 屏蔽指定 URI
	ignored-patterns: /**/demo8080/**
敏感请求头屏蔽

默认情况下,像 Cookie、Set-Cookie 等敏感请求头信息会被 zuul 屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。

zuul:
	# 指定 token 被屏蔽
	sensitive-headers: token

负载均衡

​ 用户提交的请求被路由到一个指定的微服务中,若该微服务名称的主机有多个,则默认采用负载均衡策略是轮询

服务降级

定义fallback类

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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-02 17:32
 */
@Component
class OrmDataServiceFallback implements FallbackProvider {

    // 指定要降级的微服务名称
    @Override
    public String getRoute() {
        // 对所有微服务降级
        // return "*";
        // 仅对指定的微服务进行降级
        return "service-consumer-demo-8080";
    }

    /**
     * description:
     *      定制降级响应
     * @param route
     * @param cause
     * @return org.springframework.http.client.ClientHttpResponse
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.SERVICE_UNAVAILABLE;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.SERVICE_UNAVAILABLE.value();            }

            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                String msg = "降级信息";
                return new ByteArrayInputStream(msg.getBytes());
            }

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

请求过滤

定义 RouteFilter

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-02 17:32
 */
@Component
class RouterFilter extends ZuulFilter {

    /***
     * description:
     *      指定路由之前执行过滤
     * @param
     * @return java.lang.String
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 在系统最小值 -3 的前面执行
        return -5;
    }

    /***
     * description:
     *      该过滤的条件是,只有请求参数携带有 user 的请求才可访问端口号为/demo8080 工程, 否则返回 401,未授权。
     *      当然,对/demo8090 工程的访问没有限制。简单来说就是,只有当访 问/demo8080 且 user 为空时是通不过过滤的,
     *      其它请求都可以。
     * @param
     * @return boolean
     */
    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String user = request.getParameter("user");
        String uri = request.getRequestURI();
        if(uri.contains("/demo8080") && StringUtils.isEmpty(user)){
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }

    /***
     * description:
     *      对请求通过过滤的后的逻辑
     * @param
     * @return boolean
     */
    @Override
    public Object run() throws ZuulException {
        System.out.println("通过过滤");
        return null;
    }
}

令牌桶限流

使用 Guava 库的 RateLimit 完成限流,其底层使用的是令牌桶算法实现的限流

令牌桶每秒仅生成 2 个令牌。即每秒可以处理 2 个请求。

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-02 17:32
 */
@Slf4j
@Component
class RouterFilter extends ZuulFilter {

    // 每秒产生2个令牌, 令牌桶每秒仅生成 2 个令牌。即每秒可以处理 2 个请求。
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);

    /***
     * description:
     *      指定路由之前执行过滤
     * @param
     * @return java.lang.String
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 在系统最小值 -3 的前面执行
        return -5;
    }

    /***
     * description:
     *      该过滤的条件是,只有请求参数携带有 user 的请求才可访问端口号为/demo8080 工程, 否则返回 401,未授权。
     *      当然,对/demo8090 工程的访问没有限制。简单来说就是,只有当访 问/demo8080 且 user 为空时是通不过过滤的,
     *      其它请求都可以。
     * @param
     * @return boolean
     */
    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        if(!RATE_LIMITER.tryAcquire()){
            log.warn("访问量超载");
            // 指定当前请求未通过,默认值为 true
            context.setSendZuulResponse(false);
            // 429 : 请求数量过多
            context.setResponseStatusCode(429);
            return false;
        }
        HttpServletRequest request = context.getRequest();
        String user = request.getParameter("user");
        String uri = request.getRequestURI();
        if(uri.contains("/demo8080") && StringUtils.isEmpty(user)){
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }

    /***
     * description:
     *      对请求通过过滤的后的逻辑
     * @param
     * @return boolean
     */
    @Override
    public Object run() throws ZuulException {
        System.out.println("通过过滤");
        return null;
    }
}
			

多维请求限流

针对 Zuul 编写了一个限流库(spring-cloud-zuul-ratelimit),提供多种细 粒度限流策略

<!-- spring-cloud-zuul-ratelimit 依赖 --> 
<dependency>
	<groupId>com.marcosbarbero.cloud</groupId> 
	<artifactId>spring-cloud-zuul-ratelimit</artifactId> 
	<version>2.0.5.RELEASE</version>
</dependency>

修改配置文件

zuul:
	routes: 
		# 指定微服务的路由规则
		service-consumer-demo-8080: /demo8080/**
		service-consumer-demo-8081: /demo8081/**

# 对限流策略进行配置
# 以下配置意思是:30秒内允许10个访问,并且要求总请求时间小于20秒
ratelmit:
	enable: true # 开启限流
	#repository: REDIS # 存储的地方
	default-policy: # 设置限流策略,可以替换为  某服务的服务名
		refresh-interval: 30 # 限流单位时间窗口(单位 秒)
		limit: 10 # 在指定单位时间窗口内启动限流的限定值
		quota: 20	# 指定限流的时间窗口数量(单位 秒)
		type: user,origin,url # 指定限流检查的对象类型

添加异常处理页面

在工程的在 src/main/resources 目录下再定义新的目录 public/error,必须是这个目录名称。
然后 在该目录中定义一个异常处理页面,名称必须是异常状态码(429),扩展名必须为 html。

429.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lM6m5ohL-1604910603346)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201104165434100.png)]

灰度发布

**Zuul** 灰度发布原理 , 基于 Eureka 元数据的
需要灰度发布的服务,配置
server:
	port: 8080

spring:
	application:
		name: service-customer-demo

eureka:
  client:
      register-with-eureka: false # 指定是否向注册中心注册自己 (自己是否注册自己)
      fetch-registry: false # 指定此客户端是否能够获取eureka注册信息
      service-url:  #暴露服务中心地址
        defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
        
	instance:
		metadata-map: # 指定的元数据
			host-mark: running-host
			
			
#########################################

server:
	port: 8081

spring:
	application:
		name: service-customer-demo


eureka:
  client:
      register-with-eureka: false # 指定是否向注册中心注册自己 (自己是否注册自己)
      fetch-registry: false # 指定此客户端是否能够获取eureka注册信息
      service-url:  #暴露服务中心地址
        defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
        
	instance:
		metadata-map: # 指定的元数据
			host-mark: running-host
			
#########################################
#需要灰度发布的版本
server:
	port: 8082

spring:
	application:
		name: service-customer-demo


eureka:
  client:
      register-with-eureka: false # 指定是否向注册中心注册自己 (自己是否注册自己)
      fetch-registry: false # 指定此客户端是否能够获取eureka注册信息
      service-url:  #暴露服务中心地址
        defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
        
	instance:
		metadata-map: # 指定的元数据
			host-mark: gray-host
			
			
Zuul的配置

添加依赖

        <dependency>
            <groupId>io.jmnarloch</groupId>
            <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

配置文件

server:
	port: 9000

spring:
	application:
		name: service-zuul-demo

eureka:
  client:
      register-with-eureka: false # 指定是否向注册中心注册自己 (自己是否注册自己)
      fetch-registry: false # 指定此客户端是否能够获取eureka注册信息
      service-url:  #暴露服务中心地址
        defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
        
zuul:
	routes:
		service-consumer-demo: /service-consumer-demo/**

定义过滤器(方式一)

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-02 17:32
 */
@Slf4j
@Component
class GrayFilter extends ZuulFilter {


    /***
     * description:
     *      指定路由之前执行过滤
     * @param
     * @return java.lang.String
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 在系统最小值 -3 的前面执行
        return -5;
    }

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

    /***
     * description:
     *      对请求通过过滤的后的逻辑
     * @param
     * @return boolean
     */
    @Override
    public Object run() throws ZuulException {
        // 获取请求上下文
        RequestContext context = RequestContext.getCurrentContext();
        // 获取请求
        HttpServletRequest request = context.getRequest();
        // 获取指定的头信息,盖头信息在浏览器提交请求时携带
        String mark = request.getHeader("gray-mark");

        // 默认将请求路由到 running-host 上
        // "host-mark" 与 "running-host" 实在消费者工程中
        // 添加的元数据 key - value
        RibbonFilterContextHolder
                .getCurrentContext()
                .add("host-mark","running-host");

        // 若mark的值不为空,且为 "enable", 则将请求路由到 gray-host 上
        if(!StringUtils.isEmpty(mark) && "enable".equals(mark)){
            RibbonFilterContextHolder
                    .getCurrentContext()
                    .add("host-mark","gray-host");
        }
        return null;
    }
}

定义过滤器(方式二)

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-02 17:32
 */
@Slf4j
@Component
class GrayFilter2 extends ZuulFilter {

    // 定义一个院子布尔型,由于当前类是单例,所以具有全局性
    private AtomicBoolean flag = new AtomicBoolean(true);

    /***
     * description:
     *      指定路由之前执行过滤
     * @param
     * @return java.lang.String
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 在系统最小值 -3 的前面执行
        return -5;
    }


    @Override
    public boolean shouldFilter() {

        return true;
    }

    /***
     * description:
     *      对请求通过过滤的后的逻辑
     * @param
     * @return boolean
     */
    @Override
    public Object run() throws ZuulException {
        // 获取请求上下文
        RequestContext context = RequestContext.getCurrentContext();
        // 获取请求
        HttpServletRequest request = context.getRequest();

        // 默认将请求路由到 running-host 上
        // "host-mark" 与 "running-host" 实在消费者工程中
        // 添加的元数据 key - value
        if(flag.get()){
            RibbonFilterContextHolder
                    .getCurrentContext()
                    .add("host-mark","running-host");
            flag.set(false);
        }else {
            RibbonFilterContextHolder
                    .getCurrentContext()
                    .add("host-mark","gray-host");
            flag.set(true);
        }
        
        return null;
    }
}

Spring Cloud Config

Spring Cloud Config 就是对微服务的配置文件进行统一管理的。其工作原理是,我们首 先需要将各个微服务公共的配置信息推送到 GitHub 远程版本库。然后我们再定义一个 Spring Cloud Config Server,其会连接上这个 GitHub 远程库。这样我们就可以定义 Config 版的 Eureka Server、提供者与消费者了,它们都将作为 Spring Cloud Config Client 出现,它们都会通过连 接 Spring Cloud Config Server 连接上 GitHub 上的远程库,以读取到指定配置文件中的内容。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MBl8INOI-1604910603348)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201105101647342.png)]

步骤(server

1、导入 config server 的依赖,将其它所有依赖删除 
	
	<dependency>
		<groupId>org.springframework.cloud</groupId> 
		<artifactId>spring-cloud-config-server</artifactId>
	</dependency>
	
2、在启动类上添加@EnableConfigServer注解
3、在配置文件中指定要连接的git远程库地址等信息

server:
	port: 9999
	
spring:
	cloud:
		config:
			server:
				git:
					uri: git@github.com:ckl001/springmvc.git
					timeout: 5 # 获取到远程 git 库连接超时时间,默认5s
					default-lable: master # git 分支,默认master

步骤Config 版的 Eureka 服务器(client)

 1、添加 config 客户端依赖
    <!--spring cloud config 客户端依赖--> 
    <dependency>
        <groupId>org.springframework.cloud</groupId> 
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>

定义bootstrap.yml

1) bootstrap.yml中配置的是应用启动时所必须的配置信息。
2) application.yml中配置的是应用运行过程中所必须的配置信息 
3) bootstrap.yml优先于application.yml进行加载。
spring:
	cloud:
		config: 
			uri: localhost:9999 # 指定 COnfigServer 的地址
			lable: master # 指定要访问远程库的分支
			name: application-provder-config # 指定要从远程库读取的配置文件名,无需扩展名
			profile: dev # 选择环境

配置自动更新

Spring Cloud Bus

用于将服务和服务实例与分布式消息系统链接在一起的事件总线。在集群中传播状 态更改很有用(例如配置更改事件)。

在这里插入图片描述
在这里插入图片描述

修改远程库中的配置文件

添加 application-provider-config.yml

flag: true
创建提供者工程 06 -config-provider-bus

步骤

1) 导入actuator与bus-kafka依赖
				<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-kafka</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>
2) 在配置文件中指定要连接的kafka集群,并开启actuator的bus-refresh监控终端 
bootstarp.yml
spring:
	cloud:
		config: 
			uri: localhost:9999 # 指定 COnfigServer 的地址
			lable: master # 指定要访问远程库的分支
			name: application-provder-config # 指定要从远程库读取的配置文件名,无需扩展名
			profile: dev # 选择环境

# 注册 Kafka 集群
kafka:
	bootstarp-servers: kafkaOS1:9092,kafkaOS2:9092,kafkaOS3:9092

# 开启 bus-refresh 监控终端
management:
	endpoints:
		web:
			exposure:
				include: bus-refresh

3) 在需要自动更新的类上添加@RefreshScope注解

Spring Cloud Sleuth+Zipkin

(Spring Cloud Sleuth可以实现)针对Spring Cloud应用程序的分布式跟踪,兼容Zipkin、 HTrace 和基于日志的(如 Elk)跟踪。

Spring Cloud Sleuth 为 Spring Cloud 实现了一个分布式跟踪解决方案,大量借鉴了 Dapper、Zipkin 和 HTrace。对于大多数用户来说,Sleuth 是不可见的,并且你的当前应用与 外部系统的所有交互都是自动检测的。你可以简单地在日志中捕获数据,或者将其发送到远程收集器中。

服务跟踪理论中存在有跟踪单元的概念,而跟踪单元中涉及三个重要概念:trace、span, 与 annotation。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oq3316QV-1604910603360)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201105155651109.png)]

trace 与 span

1) trace:跟踪单元是从客户端所发起的请求抵达被跟踪系统的边界开始,到被跟踪系统向客户返回响应为止的过程,这个过程称为一个 trace。

2) span:每个 trace 中会调用若干个服务,为了记录调用了哪些服务,以及每次调用所消耗的时间等信息,在每次调用服务时,埋入一个调用记录,这样两个调用记录之间的区域称为一个 span。

3) 关系:一个Trace由若干个有序的Span组成。

Spring Cloud Sleuth 为服务之间调用提供链路追踪功能。为了唯一的标识 trace 与 span, 系统为每个 trace 与 span 都指定了一个 64 位长度的数字作为 ID,即 traceID 与 spanID。

annotation

用于及时记录事件的实体,表示一个事件发生的时间点。这些实体本身仅仅是为了原理 叙述的方便,对于 Spring Cloud Sleuth 本身并没有什么必要性。这样的实体有多个,常用的 有四个:
1) cs: Client Send,表示客户端发送请求的时间点。
2) sr: Server Receive,表示服务端接收到请求的时间点。 
3) ss: Server Send,表示服务端发送响应的时间点。
4) cr: Client Receive,表示客户端接收到服务端响应的时间点。

Sleuth 的日志采样

日志生成

只要在工程中添加了 Spring Cloud Sleuth 依赖, 那么工程在启动与运行过程中就会自动 生成很多的日志。Sleuth 会为日志信息打上收集标记,需要收集的设置为 true,不需要的设 置为 false。这个标记可以通过在代码中添加自己的日志信息看到。

日志采样率

Sleuth 对于这些日志支持抽样收集,即并不是所有日志都会上传到日志收集服务器,日 志收集标记就起这个作用。默认的采样比例为: 0.1,即 10%。在配置文件中可以修改该值。 若设置为 1 则表示全部采集,即 100%。
Sleuth 默认采用的是水塘抽样算法。

跟踪日志”的生产者 Sleuth

开发步骤

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

在需要收集的类里面添加跟踪日志

@Sl4j
log.info("我是被跟踪的日志");

添加日志配置

logging:
	pattern:
		console: level-%level %msg%n # 设置日志输出格式
	level:
		root: info
		org.hibernate: info
		org.hibernate.type.descriptor.sql.BasicBinder: trace
		org.hibernate.type.descriptor.sql.BasicExtractor: trace
		com.edu.ckl: debug

zipkin

zipkin 是 Twitter 开发的一个分布式系统 APM(Application Performance Management,应 用程序性能管理)工具,其是基于 Google Dapper 实现的,用于完成日志的聚合。其与 Sleuth 联用,可以为用户提供调用链路监控可视化 UI 界面。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lTfQFO1x-1604910603365)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201105161748429.png)]

zipkin 服务器主要由 4 个核心组件构成:

1) Collector:收集组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为 Zipkin 内部处理的Span 格式,以支持后续的存储、分析、展示等功能。

2) Storage:存储组件,它主要用于处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,也可以修改存储策略,例如,将跟踪信息存储到数据库中。

3) API:外部访问接口组件,外部系统通过这里的API可以实现对系统的监控。

4) UI:用于操作界面组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和分析跟踪信息。

日志发送方式

在 Spring Cloud Sleuth + Zipkin 系统中,客户端中一旦发生服务间的调用,就会被配置在 微服务中的 Sleuth 的监听器监听,然后生成相应的 Trace 和 Span 等日志信息,并发送给 Zipkin 服务端。发送的方式主要有两种,一种是通过 via HTTP 报文的方式,也可以通过 Kafka、 RabbitMQ 发送。

zipkin 服务端搭建

1、下载
curl -sSL https://zipkin.io/quickstart.sh | bash -s

2、启动
java -jar zipkin.jar

3、zipkin UI
localhost:9411/zipkin/

创建 zipkin 客户端工程**-via**

1) 导入zipkin客户端依赖
  <!--zipkin 客户端依赖,其包含了 sleuth 依赖--> 
  <dependency>
      <groupId>org.springframework.cloud</groupId> 
      <artifactId>spring-cloud-starter-zipkin</artifactId>
  </dependency>

2) 在配置文件中指定zipkin服务器地址,并设置Sleuth采样率
	
	spring: 
		zipkin:
			sender: 
				type: kafka
			base-url: http://localhost:9411/ # 指定zipkin 服务器地址
		sleuth:
			sampler:
				probability: 1.0 # 设置采样比例为 1.0, 即全部都需要
	

sleuth + kafka + zipkin

默认情况下,Sleuth 是通过将调用日志写入到 via 头部信息中的方式实现链路跟踪的, 但在高并发下,这种方式的效率会非常低,会影响链路信息查看的。此时,可以让 Sleuth 将其生成的调用日志写入到 Kafka 或 RabbitMQ 中,让 zipkin 从这些中间件中获取日志,效率会提高很多。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Dhc4w0B-1604910603366)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201105173037708.png)]

启动

1) zk 启动
2) kafka 集群启动
3) zipkin 启动
java -DKAFKA_BOOTSTRAP_SERVERS=kafkaOS1:9092 –jar zipkin.jar
4)启动Eureka
5)启动 provider 工程
6)启动 consumer 工程

Spring Cloud Stream

一个轻量级的事件驱动微服务框架,用于快速构建可连接到外部系统的应用程序。 在 Spring Boot 应用程序之间使用 Kafka 或 RabbitMQ 发送和接收消息的简单声明式模型。

Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架。通过使用 Spring Cloud Stream,可以有效简化开发人员对消息中间件的使用复杂度,让系统开发人员 可以有更多的精力关注于核心业务逻辑的处理。但是目前 Spring Cloud Stream 只支持 RabbitMQ 和 Kafka 的自动化配置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XUnvcBTt-1604910603367)(/Users/chenkanglin/Library/Application Support/typora-user-images/image-20201106104950874.png)]

应用程序的核心部分(Application Core)通过 inputs 与 outputs 管道,与中间件连接, 而管道是通过绑定器 Binder 与中间件相绑定的。

stream kafka 微服务

创建生产者步骤

1) 导入spring-cloud-stream-binder-kafka依赖
	      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-kafka</artifactId>
        </dependency>

2) 创建生产者类。在生产者类上添加@EnableBinding()注解,并声明管道 
3) 定义处理器,在处理器中调用消息生产者,使其发送消息
4) 在配置文件中注册kafka集群,并指定管道所绑定的主题及类型

spring:
	application:
		name: service-provider-stream-kafka
	
	cloud:
		stream:
			kafka:
				binder: 
					brokers: kafkaOS1:9092,kafkaOS2:9092,kafkaOS3:9092 # 指定Stream锁连接的kafka集群
					auto-create-topics: true # 指定是否自动创建主题
			
			bindings:
				output: # 指定要输出的消息主题及类型
					destination: persons
					content-type: text/plain

生产者类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

/**
 * @author chenkanglin
 * @desc    生产者
 * @Date 2020-11-06 11:45
 */
@Component
// spring cloud 中的 source
// 将 MQ 与生产者类通过消息管理通道相绑定
@EnableBinding(Source.class)
public class SomeProducer {

    @Autowired
    @Qualifier(Source.OUTPUT)
    private MessageChannel channel;

    public void sendMsg(String message){
        channel.send(MessageBuilder.withPayload(message).build());
    }
}

消息发送给多个主题的生产者

Source 的 MessageChannel 完成了将消息发送给某个指定主题的功能,

若要将消息发送给多个主题,则需要自定义 Channel。

import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;

public interface CustomSource {

  	// 需要在配置文件配置
    String CHANNEL_NAME = "ckl";

    @Output(CustomSource.CHANNEL_NAME)
    MessageChannel output();
}

//---------------------------------------------------------------
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

/**
 * @author chenkanglin
 * @desc    生产者,发送个多个主题
 * @Date 2020-11-06 11:45
 */
@Component
// spring cloud 中的 source
// 将 MQ 与生产者类通过消息管理通道相绑定
@EnableBinding({Source.class, CustomSource.class})
public class SomeProducer {

    @Autowired
    @Qualifier(Source.OUTPUT)
    private MessageChannel channel;


    @Autowired
    @Qualifier(Source.OUTPUT)
    private MessageChannel customChannel;



    public void sendMsg(String message){
        channel.send(MessageBuilder.withPayload(message).build());
        customChannel.send(MessageBuilder.withPayload(message).build());
    }
}

修改配置

spring:
	application:
		name: service-provider-stream-kafka
	
	cloud:
		stream:
			kafka:
				binder: 
					brokers: kafkaOS1:9092,kafkaOS2:9092,kafkaOS3:9092 # 指定Stream锁连接的kafka集群
					auto-create-topics: true # 指定是否自动创建主题
			
			bindings:
				output: # 指定要输出的消息主题及类型
					destination: persons
					content-type: text/plain
				ckl:
					destination: countries #目的地
					content-type: text/plain
创建消费者步骤
 1)导入spring-cloud-stream-binder-kafka依赖
 2)创建消费者类。在消费者类上添加@EnableBinding()注解,并声明管道。 
 3)在消费者类中定义消费方法,在方法上添加相应的注解。
 4)在配置文件中注册kafka集群,并指定管道所绑定的主题

创建消息消费者 – @PostConstruct 方式

Spring Cloud Stream 提供了三种创建消费者的方式,这三种方式的都是在消费者类的“消 费”方法上添加注解。只要有新的消息写入到了管道,该“消费”方法就会执行。只不过三 种注解,其底层的实现方式不同。即当新消息到来后,触发“消费”方法去执行的实现方式 不同。
1)@PostConstruct:以发布/订阅方式实现
2)@ServiceActivator:以新消息激活服务的方式实现 
3)@StreamListener:以监听方式实现
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-06 16:30
 */
@Component
@EnableBinding(Sink.class)
public class SomeCustomer {

    @Autowired
    @Qualifier(Sink.INPUT)
    private SubscribableChannel channel;

    public void sout(){
        channel.subscribe(new MessageHandler() {
            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                System.out.println(message.getHeaders());
                byte[] payload = (byte[]) message.getPayload();
                System.out.println(new String(payload));
            }
        });
    }
}

修改配置文件

spring:
	application:
		name: service-customer-stream-kafka
	
	cloud:
		stream:
			kafka:
				binder: 
					brokers: kafkaOS1:9092,kafkaOS2:9092,kafkaOS3:9092 # 指定Stream锁连接的kafka集群
					auto-create-topics: true # 指定是否自动创建主题
			
			bindings:
				output: # 指定要输出的消息主题及类型
					destination: persons
					content-type: text/plain
				ckl:
					destination: countries #目的地
					content-type: text/plain
				input:
					destination: persons #目的地

@ServiceActivator 方式

注解所标注的方法是以服务的形式出现的,只要管道中的数据发生了变化就会激活该 服务。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;

/**
 * @author chenkanglin
 * @desc 其不会出现在 Spring 容器。
 * @Date 2020-11-06 16:30
 */
public class SomeCustomer {

    @Autowired
    @Qualifier(Sink.INPUT)
    private SubscribableChannel channel;

    public void sout(){
        channel.subscribe(new MessageHandler() {
            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                System.out.println(message.getHeaders());
                byte[] payload = (byte[]) message.getPayload();
                System.out.println(new String(payload));
            }
        });
    }
}

//---------------------------------------

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-06 16:30
 */
@Component
@EnableBinding(Sink.class)
public class SomeCustomer2 {

    @ServiceActivator(inputChannel = Sink.class)
    public void sout(Object message){
        System.out.println(message);
    }
}


@StreamListener 方式

该方式是以监听的方式实现,只要管道中的流数据发生变化,其就会触发该注解所标注 的方法的执行。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-06 16:30
 */
public class SomeCustomer2 {

    @ServiceActivator(inputChannel = Sink.class)
    public void sout(Object message){
        System.out.println(message);
    }
}

// -----------------------------------------------------
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;

/**
 * @author chenkanglin
 * @desc
 * @Date 2020-11-06 16:30
 */
@Component
@EnableBinding(Sink.class)
public class SomeCustomer3 {

    @StreamListener(Sink.INPUT)
    public void sout(Object message){
        System.out.println(message);
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值