# SpringCloud
# 服务注册中心
# Eureka
引入Eureka --> pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2
3
4
Eureka基本配置 --> application.yml
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka/
server:
#关闭自我保护机制,保证不可用服务被即使剔除
enable-self-preservation: true
eviction-interval-timer-in-ms: 2000
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
入口类加上@EnableEurekaServer注解来注册一下
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
2
3
4
5
6
7
接下来做个服务提供者 --> pom.xml
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2
3
4
5
application.yml
eureka:
client:
#表示是否将自己注册进EurekaServer 默认true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#集群版
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
instance:
instance-id: payment8001 #修改本服务名称
prefer-ip-address: true #访问路径显示id地址
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,默认是90秒,超时将剔除服务
lease-expiration-duration-in-seconds: 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
入口类加上@EnableEurekaClient、@EnableDiscoveryClient把我们的Eureka客户端注册上去
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
2
3
4
5
6
7
8
接下来做我们的消费者,一样的先加pom
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2
3
4
5
然后application.yml
eureka:
client:
#表示是否将自己注册进EurekaServer 默认true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#集群版
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
2
3
4
5
6
7
8
9
TIP
因为引入的是Eureka2.2.1版本,自带了ribbon,可以配置fetch-registry: true 来实现负载均衡,默认用的是轮询算法。
然后启动类
@SpringBootApplication
@EnableEurekaClient
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
2
3
4
5
6
7
启动之后可以看到有两个注册中心,两个提供者,一个消费者。 注册中心与提供者本文只介绍一个创建方法,如果需要集群, 多复制一个就行。
# zookeeper
本文zookeeper在linux上演示
zookeeper 常用命令
默认端口:2181
启动 :./zkServer.sh start
登录:./zkCli.sh
服务提供者:(这里由于有依赖冲突,排除了两个依赖) --> pom.xml
<!-- 引入zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
<!--排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
application.yml:注册到zookeeper,如果zookeeper集群,在connect-string加逗号即可
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 192.168.172.22:2181
2
3
4
5
6
启动类:(加上@EnableDiscoveryClient)
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}
2
3
4
5
6
7
接着消费者pom:
<!-- 引入zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
<!--排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
application.yml(基本和上面服务提供者一样)
spring:
application:
name: cloud-consumer-order
cloud:
zookeeper:
connect-string: 192.168.172.22:2181
2
3
4
5
6
因为要以http调用提供者,加入RestTemplate。
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
2
3
4
5
6
7
8
9
启动类:(加@EnableDiscoveryClient)
@SpringBootApplication
@EnableDiscoveryClient
public class OrderZKMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderZKMain80.class,args);
}
}
2
3
4
5
6
7
8
Contriller用以下方法调用即可
restTemplate.getForObject(提供者服务名+接口地址,返回entity)
服务全部启动之后可以看到一个提供者与一个消费者
# consul
本文演示在windows平台运行
consul常用命令
默认端口:8500
启动:consul agent -dev
consul启动完成后做一个提供者 --> pom.xml
<!-- springCloud sonsul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
2
3
4
5
application.yml
spring:
application:
name: consul-provider-payment
# consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
2
3
4
5
6
7
8
9
10
启动类加上@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8006.class,args);
}
}
2
3
4
5
6
7
8
接下来消费者pom.xml:
<!-- springCloud sonsul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
2
3
4
5
application.yml:
spring:
application:
name: cloud-consumer-order
# consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
2
3
4
5
6
7
8
9
10
启动类加上@EnableDiscoveryClient(这里就省去RestTemplate环节了)
@SpringBootApplication
@EnableDiscoveryClient
public class OrderConsulMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderConsulMain80.class,args);
}
}
2
3
4
5
6
7
服务全部启动之后可以看到一个提供者与一个消费者
# 负载均衡于服务调用
# Ribbon
# Ribbon默认自带的负载规则
Ribbon核心组件Irule
先来看看类结构:
根据特定算法中从服务列表中选取一个要访问的服务
1.com.netflix.loadbalancer.RoundRobinRule 轮询
2.com.netflix.loadbalancer.RandomRule 随机
3.com.netflix.loadbalancer.RetryRule 先按照RoundRobinRule轮询的策略获取服务,如果获取服务失败则在指定时间内会进行充实,获取可用的服务
4.WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
5.BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
6.AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
7.ZoneAvoidanceRule 默认规则,符合判断server所在区域的性能和server的可用性选择服务器
# 尝试自己手写一个轮询算法
面向接口编程,先写个接口
package com.hzh.springcloud.lb;
import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
/**
* 手写一个负载均衡
*/
public interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
2
3
4
5
6
7
8
9
10
11
12
13
实现类
package com.hzh.springcloud.lb;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
/**
* 拿到接口的请求数
* @return
*/
public final int getAndIncrement(){
int current;
int next; //请求数
do{
current = this.atomicInteger.get();
//接口请求数+1并防止请求数大于Integer类型的上限值
//2147483647是Integer的最大长度,为什么不用Integer.MAX_VALUE呢?
//因为Integer.MAX_VALUE会多创建一个栈区,写死了就不会
next = current >= 2147483647 ? 0 : current +1;
//一直自旋,取到我们想要的值为止
}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("********第几次访问,次数next:"+next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
//轮询算法:接口请求次数 % 服务器集群总数量 = 实际调用服务器位置下标
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Controller~~
@Resource
private RestTemplate restTemplate;
//自己写的负载均衡算法
@Resource
private LoadBalancer loadBalancer;
/**
* 测试自己手写的负载均衡
* @return
*/
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB(){
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
上面CLOUD-PAYMENT-SERVICE服务接口
@GetMapping("/payment/lb")
public String getPaymentLB(){
return serverPort;
}
2
3
4
测试结果:第一次调用
第二次调用
# OpenFeign
# OpenFeign是什么?
Feign是一个声明式WebService客户端。使用Feign能让编写WebService客户端更加简单。 它得使用方法是定义一个服务接口然后在上面添加注解,Feign也支持可插拔式的编码和解码器。Spring Cloud对Feign进行了 封装,使其支持了SpringMvc标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡
# 开始使用OpenFeign
添加pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2
3
4
yml
server:
port: 80
eureka:
client:
#表示是否将自己注册进EurekaServer 默认true
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
2
3
4
5
6
7
8
9
启动类
package com.hzh.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients //使用feign
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
业务接口
package com.hzh.springcloud.service;
import com.hzh.springcloud.entities.CommonResult;
import com.hzh.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Component
@FeignClient(value="CLOUD-PAYMENT-SERVICE") //表示调用某个服务
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}") //表示调用某个服务下的某个接口
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Controller
package com.hzh.springcloud.controller;
import com.hzh.springcloud.entities.CommonResult;
import com.hzh.springcloud.entities.Payment;
import com.hzh.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Feign依赖了Ribbon,所以自带负载均衡功能,默认轮询,测试:
# OpenFeign超时控制
OpenFeign默认等待1s,超时则会抛feign.FeignException$NotFound异常
application.yml 设置fegin客户端超时时间
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
ConnectTimeout: 5000
2
3
4
5
# OpenFeign日志打印
日志级别
NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、URL、响应状态码及执行时间
HEADERS:除了BASIC中定义的信息之外,还有请求和响应头信息
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
# 开始使用
添加一个Feginconfig类,配置日志级别
package com.hzh.springcloud.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeginConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
application.yml添加相关日志配置
logging:
level:
# feign日志以什么级别监控哪个接口
com.hzh.springcloud.service.PaymentFeignService: debug
2
3
4
控制台输出
2020-03-18 17:48:35.734 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] ---> GET http://CLOUD-PAYMENT-SERVICE/payment/get/1 HTTP/1.1
2020-03-18 17:48:35.734 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] ---> END HTTP (0-byte body)
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] <--- HTTP/1.1 200 (60ms)
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] connection: keep-alive
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] content-type: application/json
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] date: Wed, 18 Mar 2020 09:48:35 GMT
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] keep-alive: timeout=60
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] transfer-encoding: chunked
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById]
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] {"code":200,"message":"查询成功,serverPort:8001","data":{"id":1,"serial":"123123"}}
2020-03-18 17:48:35.795 DEBUG 4328 --- [-nio-80-exec-10] c.h.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] <--- END HTTP (87-byte body)
2
3
4
5
6
7
8
9
10
11
# 服务降级
# Hystrix断路器
# Hystrix断路器重要概念
1.服务降级
服务器忙,请稍后再试,不让客户端等待立刻返回一个友好提示,fallback
那些情况会触发降级?
1)程序运行异常
2)超时
3)服务熔断触发服务降级
4)线程池/信号量打满也会导致服务降级
2.服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回一个友好的提示
3.服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
# 使用Hystrix服务降级
引入pom
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2
3
4
5
启动类@EnableCircuitBreaker激活
package com.hzh.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //激活hystrix
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
业务类,假设paymentInfo_TimeOut处理完成要5秒,必须要在3秒内给客户端响应请求,不能让其一直等待 则需要加入以下配置,处理超时则在规定时间内返回繁忙提示。 如果抛异常,也会调用paymentInfo_TimeOutHandler方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { ////如果异常了就进入paymentInfo_TimeOutHandler
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") //规定3秒以内视为正常调用
})
public String paymentInfo_TimeOut(Integer id){
int timeNum = 5;
try{
TimeUnit.SECONDS.sleep(timeNum);
}catch (InterruptedException e){
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName() + " paymentInfo_TimeOut,id:"+id+"\t"+"正常访问ok" + "耗时:"+timeNum+"秒";
}
/**
* paymentInfo_TimeOut超时或异常后执行
* @param id
* @return
*/
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName() + " paymentInfo_TimeOutHandler,id:"+id+"\t"+"繁忙请稍后再说/(ㄒoㄒ)/~~";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上面是介绍在服务提供端,在消费端也能用,接着演示一个全局与特有fallback的类
package com.hzh.springcloud.controller;
import com.hzh.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //使用全局fallback方法
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
// })
@HystrixCommand //使用全局响应
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
//int age = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对方支付系统繁忙请10秒种后再试或者自己运行出错请检查自己";
}
/**
* 全局fallback方法
* @return
*/
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
如果没使用全局就调用paymentTimeOutFallbackMethod()。
如果使用全局就调用payment_Global_FallbackMethod()。
发现以上代码耦合性太高了,业务代码与异常代码混在一起了,接着来做一下解耦
首先从消费端的service层改造,因为使用Feign调用服务都在这个接口,可以实现这个接口来对其进行改造
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
2
3
4
5
6
7
8
9
10
做一个PaymentFallbackService类来实现以上接口,从而可以对每个接口做服务降级
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-------PaymentFallbackService fall paymentInfo_OK,/(ㄒoㄒ)/~~";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-------PaymentFallbackService fall paymentInfo_TimeOut,/(ㄒoㄒ)/~~";
}
}
2
3
4
5
6
7
8
9
10
11
12
还要再application.yml中的feign来开启Hystrix
feign:
hystrix:
enabled: true #在Feign中开启Hystrix
2
3
测试结果: 如果调用paymentInfo_OK,发生服务降级则会return "-------PaymentFallbackService fall paymentInfo_OK,/(ㄒoㄒ)/~~";
# 服务熔断
熔断类型:
1)熔断打开
请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开长达 到所设时间则进入半熔断状态
2)熔断关闭
熔断关闭不会对服务进行熔断
3)熔断半开
部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务回复正常,关闭熔断
服务熔断演示:以下代码片段设置了断路器如果在10秒内访问10次失败率达到60%就会拉闸
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间窗日期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60") //失败率达到百分之多少后跳闸
})
public String paymentCircuitBreaker(Integer id){
if(id < 0){
throw new RuntimeException("id不能为负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(Integer id){
return "id不能为负数,请稍后再试,/(ㄒoㄒ)/~~ id:"+id;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 服务限流
由于Hystrix已经停止更新,这个侧重点移到其他主流并没有停止更新的服务降级框架中学习
# Hystrix图形化实时监控器
监控者 ---> pom.xml
<!-- Hystrix实时监控仪表盘 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
2
3
4
5
监控着主启动类上加上@EnableHystrixDashboard表示开启监控器
地址栏配置http://localhost:监控器工程端口号/hystrix 访问到主页面
输入地址+端口/hystrix.stream 即可进入监控主页面
# 注意事项
注意被监控者一定要有以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2
3
4
被监控者的主启动类要有以下Bean
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream"
* 只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 服务网关
# Gateway
# Gateway三大核心概念
Route(路由)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
Predicate(断言)
参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改
# 引入gateway
pom.xml,网关是不需要web模块,注意要把web模块去掉
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2
3
4
5
application.yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名j进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行l路由
- id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行l路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
启动成功后,访问9527的接口就能路由到8001上
更多路由规则和过滤器使用参考spring Gateway官方文档
# 自定义过滤器
gateway自定义过滤器实现GlobalFilter, Ordered接口即可
package com.hzh.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
/**
* gateway自定义过滤器
*/
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("**************come in MyLogGateWayFilter" + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null){
log.info("*****用户名null,非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# SpringCloud Config分布式配置中心
# spring-cloud-config 服务端
# 使用spring-cloud-config通过GitHub获取配置信息
引入pom
<!-- config seriver -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
2
3
4
5
GitHub:
yml:
server:
port: 3344
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: https://github.com/JayeHao/springcloud-config.git #Github上面的git仓库名字
username: username
password: password
search-paths:
- springcloud-config #搜索目录
label: master #读取分支
#服务注册到eureka
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
启动后访问http://localhost:3344/master/config-prod.yml可以看到下图,即读取GitHub配置成功
访问规则
访问规则
/{label}/{application}-{profile}.yml
/{application}-{profile}.yml 默认访问master分支
/{application}/{profile}[/{label}] 返回json
label: 分支
name:服务名
profiles:环境(dev/test/prod)
# spring-cloud-config 客户端
引入客户端pom
<!-- config 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
2
3
4
5
新建bootstrap.yml,为什么要用bootstrap呢?来看下面介绍
bootstrap是什么?
application.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更高
Spring Cloud会创建一个“Bootstrap Context”,作为spring应用的“application Context”的父上下文。 初始化的时候,“Bootstrap Context”负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部 获取的“Environment”。
“Bootstrap”属性有高优先级,默认情况下,它们不会被本地配置覆盖。“BootStrap context”和“Application context” 有着不同约定,所以新增了一个“bootstrap.yml”文件,保证“bootstrap Context”和“Application Context”配置的分离。
要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的。 因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于 application.yml
server:
port: 3355
spring:
application:
name: config-client
cloud:
#config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称
uri: http://localhost:3344 #配置中心地址
#服务注册到eureka
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
接着Controller通过@Value("${config.info}")就从配置中心获取到相关配置信息
客户端动态更新github上的配置:
1.注意pom要有spring-boot-starter-actuator监控依赖
2.暴露监控端点,修改bootstrap.yml:
# 暴露监控端点(用于动态加载配置)
management:
endpoints:
web:
exposure:
include: "*"
2
3
4
5
6
3.Controller加@RefreshScope。
自测因为加了@RefreshScope之后发现获取不到配置了,所以新增一个配置类来放配置属性
@Data
@Component
@RefreshScope
public class Parmeters {
@Value("${config.info}")
private String configInfo;
}
2
3
4
5
6
7
8
Controller
package com.hzh.springcloud.controller;
import com.hzh.springcloud.config.Parmeters;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 从3344拿配置信息
*/
@RestController
public class ConfigClientController {
@Resource
private Parmeters parmeters;
@GetMapping("/configInfo")
private String getConfigInfo(){
return parmeters.getConfigInfo();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
4.需要发送一个post请求让客户端刷新,以windows平台的curl为例发送
curl -X POST "http://localhost:3355/actuator/refresh"
测试结果
在github推送后,没发post请求前:
发送post请求后可以发现实现动态刷新
# SpringCloud Bus消息总线
# RabbitMQ
在Windows平台安装
1.需要安装Erlang和RobbitMQ
2.在RoobbitMQ安装目录的sbin下执行命令(安装可视化插件): rabbitmq-plugins enable rabbitmq_management
3.启动RobbitMQ后访问地址是否安装成功: http://localhost:15672
4.默认登录账号密码: guest
开始使用RabbitMQ
这里有三个服务,3344是服务总线/configServer的服务端/配置中心,3355和3366是configServer的客户端
1.在3344添加RabbitMQ的支持
<!-- 添加消息总线RabbitMQ支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
2
3
4
5
2.3344application.yml添加RabbitMQ相关配置(在这之前已经配置好git与eureka)
#rabbitMQ相关配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#暴露bus刷新配置的端点
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
2
3
4
5
6
7
8
9
10
11
12
13
3.3355和3366的修改
添加rabbitMQ支持
<!-- 添加消息总线RabbitMQ支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
2
3
4
5
bootstrap.yml,这里注意rabbitmq与application同级。 在前面的加载动态配置有指出暴露,所以这里不写。
spring:
application:
name: config-client
cloud:
#config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称
uri: http://localhost:3344 #配置中心地址
#rabbitMQ相关配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4.开始测试
服务启动可以看到eureka上都已注册好,启动没问题
首先看3344总线上的版本号信息:
3355和3366上的版本号:
目前服务上的配置信息都是最新的,接下来改动提交github
提交完成后看看3344和3355,3366上的信息
3355、3366
可以看到3355、3366并没有更新。 刷新一下3344总线:curl -X POST "http://localhost:3344/actuator/bus-refresh"
完成之后发现3355、3366更新完成
3366
一次发送,广播通知,处处生效。
说完全局广播,再说说定点通知
给配置中心发送: http://配置中心域名和端口/actuator/bus-refresh/{destination}
例如只通知3355(config-client是3355服务名):
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355
# 多个定点通知
curl -X POST "http://localhost:3344/actuator/bus-refresh/{config-client:3355,config-client:3366}
2
3
# SpringCloud Stream 消息驱动
# 什么是SpringCloud Stream?
官方定义SpringCloud Stream是一个构建消息驱动微服务的框架。
应用程序通过inputs或者outputs来与SpringCloud Stream中binder对象交互。 通过我们配置来binding(绑定),而SpringCloud Stream的binder对象负责与消息中间件交互。 所以,我们只需要搞清楚如何与SpringCloud Stream交互就可以方便使用消息驱动的方式。
通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。 SpringCloud Stream为一些提供商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
注意:目前仅支持RabbitMQ、Kafka。
# 为什么要用SpringCloud Stream?
由于在微服务架构中引入多个MQ,开发维护时需要关注它们的细节, 我们不想关注它们的细节,因此诞生了SpringCloud Stream。 它可以屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。
# 官网
http://spring.io/projects/spring-cloud-stream#overview
# Binder
在没有绑定器这个概念的情况下,我们的springBoot应用要直接与消息中间件进行消息交互的时候,由于各消息中间件构建的初衷不同, 它们的实现细节上会有较大的差异性,通过定义绑定器作为中间层,完美的实现了应用程序与消息中间件细节之间的隔离。 Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitMQ切换为kafka), 使得微服务开发得高度解耦,服务可以关注更多自己得业务流程
通过定义绑定器Binder作为中间层,实现了应用程序于消息中间件细节之间的隔离。
# SpringCloud Stream标准流程
1.Binder
很方便的连接中间件,屏蔽差异
2.Channel
通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
3.Source和Sink
简单的可理解为参照对象是SpringCloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。
# 生产者于消费者
接下来添加3个模块
# 1.8801,作为消息生产者
引入中间件依赖
<!-- stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
2
3
4
5
application.yml:
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
prot: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称的定义
content-type: application/json # 设置消息类型,本次为json,本文则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
instance:
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30秒)
lease-renewal-interval-in-seconds: 2
#Eureka服务端在收到最后一次心跳后等待时间上限,默认是90秒,超时将剔除服务
lease-expiration-duration-in-seconds: 5
# 在信息列表时显示主机名称
instance-id: send-8801.com
# 访问的路劲变为IP地址
prefer-ip-address: true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
接口定义一个发送方法
package com.hzh.springcloud.service;
public interface IMessageProvider {
public String send();
}
2
3
4
5
6
接口实现
package com.hzh.springcloud.service.impl;
import com.hzh.springcloud.service.IMessageProvider;
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 javax.annotation.Resource;
import java.util.UUID;
@EnableBinding(Source.class) //定义消息的推送管道
public class IMessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; // 消息发送管道
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****serial:"+serial);
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
用Controller测试:
package com.hzh.springcloud.controller;
import com.hzh.springcloud.service.IMessageProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class SendMessageController {
@Resource
private IMessageProvider iMessageProvider;
@GetMapping(value = "/sendMessage")
public String sendMessage(){
return iMessageProvider.send();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
启动测试,进入RabbitMQ管理页面可以看到Exchanges有我们的studyExchange
发送消息可以看到:
# 2.8802,作为消息接收者
引入中间件依赖
<!-- stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
2
3
4
5
applicaiton.yml
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: # 表示定义的名称,用于于bingding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
prot: 5672
username: guest
password: guest
bindings:
input: # 这个名字是一个通道的名称
destination: studyExchange #表示要使用的Exchange 名称定义
content-type: application/json # 设置消息类型,本次为json,本文则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
instance:
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30秒)
lease-renewal-interval-in-seconds: 2
#Eureka服务端在收到最后一次心跳后等待时间上限,默认是90秒,超时将剔除服务
lease-expiration-duration-in-seconds: 5
# 在信息列表时显示主机名称
instance-id: send-8802.com
# 访问的路劲变为IP地址
prefer-ip-address: true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Controller加入监听,如果8801发送信息这边能接收到并打印控制台
package com.hzh.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message) {
System.out.println("消费者1号,---->消息:" + message.getPayload() + "\t" + "prot:" + serverPort);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
调用8801发送消息接口后,可以看到8802接收到消息并打印
# 3.分组消费于持久化 8803
不同组是可以全面消费的(重复消费), 同一组内会发生竞争关系,只有其中一个可以消费。
启动8801-8803可以看到它们在不同的组,存在重复消费问题
导致原因:默认分组group是不同的,组流水号不一样,被认为不同组,可以消费
解决:8802,8803的配置在input下加上group,使它们在同一分组下就只能有一个被消费
group: cloud # 分组名
group属性还做到了消息的持久化,重启后能够拿到未曾消费的消息
# SpringCloud Sleuth分布式请求链路跟踪
# 为什么要出现这个技术?要解决的问题?
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生 最后的请求结果,每一个前端请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现 高延时或错误都会引起整个请求最后的失败
# 测试运行
下载zipkin-server-2.12.9-exec.jar
https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
java -jar zipkin-server-2.12.9-exec.jar运行
给8001和80,服务提供者与调用者添加pom
<!-- 【链路追踪】包含了sleuth+zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2
3
4
5
添加配置applicaiton.yml
spring:
application:
name: cloud-consumer-order #服务名
zipkin: #链路追踪
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1 #采样率值介于0到1之间,1则表示全部采集
2
3
4
5
6
7
8
进入zipkin前台页面查看链路 http://localhost:9411/zipkin/