微服务概述
1,什么是微服务?
简而言之,微服务架构风格是一种将单个应用程序开发为一组小服务的方法,每个小服务都在自己的进程中运行,并与轻量级机制(通常是 HTTP 资源 API)进行通信。这些服务是围绕业务能力构建的,并且可以通过完全自动化的部署机制独立部署。这些服务的集中管理极少,可以用不同的编程语言编写并使用不同的数据存储技术。
2,微服务架构思想,没有一个落地的技术栈去做支持,此时,SpringCloud就是一个微服务架构落地技术栈,注意:Springcloud是解决接服务问题的系列技术栈。
.
3,SpringCloud家族常用组件:[已忽略]不计
注册中心:Eureka
配置中心: Config (忽略)
远程调用:Feigh
负载均衡:Ribbon
熔断降级:Hystrix
网关:Zuul(忽略)、Getway
消息总线、链路追踪:Slueth、Zipkin
接下来,将要介绍的是Eureka、Feigh、Ribbon、Hystrix、Getway、Slueth
注意:Config作为统一管理项目的配置文件,简单了解一下即可,本文忽略、实际企业开发中采用的springcloud alibaba的nacos统一注册中心和配置中心。
官网中Zuul网关由于停止维护,采用springcloud自家开发的新的网关技术Getway,实际企业应用中,getway基本替代zuul、所以,这里zuul就不做了解,直接学最新技术(注意,springcloud-getway 只能用在springcloud家族,其他微服务下目前集成不进去,而Zuul可可以用到其他微服务下,getway的使用范围限制大于zuul)
.
赋gitee源码练习:https://gitee.com/xzq25_com/springcloud-tets
一、注册中心:Eureka ⭐⭐⭐
1.1 原理
简单概括就是:服务注册与发现提供了一个服务注册中心、服务发现的客户端,还有一个方便查看所有注册的服务的界面。所有的服务使用Eureka的服务发现客户端来将自己注册到Eureka的服务器上。
玩的就是心跳!!!!如图所示:
Eureka的架构图:
Eureka由两个组件组成: Eureka服务器和Eureka客户端。
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。EurekaClient通过注册中心进行访问 它是一个Java客户端,用于简化Eureka
Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka
Server发送心跳(默认周期为30秒)。如果Eureka
Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。
在微服务项目中,不使用注册中心的现存问题?
举个例子,服务A调用服务B,此时不使用注册中心,则存在需要在服务A中写死服务B的地址信息,如果服务B地址信息发生的改变,则所有调用服务B的服务都需要修改调用B的代码,这种无疑是不可行的。
注册中心Eureka争对上述问题,提供了解决方案::让服务的提供者将信息统一注册到Eureka上(服务名-ip-port),调用者直接从eureka上拉取注册服务列表,直接进行访问,这样效率大大提升
关于Eureka:
1.服务注册端核心启动类上导入@EurekaClient 注解 ok
2.Eureka 作为注册,本身不安全,需要在导入 security 依赖并且配置 yml 文件, 登录时候带上账号、密码
3.EurekaClient 默认 30s 向 service 发送心跳请求,说明自身的存活状态,同时同步注册表中信息。
4.EurekaService 发现 90 秒 client 没有发送心跳请求,则认为 client 宕机
5.EurekaClient 拉取 service 信息到本地缓存,提高访问其他服务的效率
6.EurekaService 在 15min 内,如果超过 85%心跳不正常,依旧对外服务,但是会不删除、添加服务,等到心跳正常,在去其他 service 同步。
7.Eureka 基于 AP 原则,而 Nacos 基于 AP 和 CP 之间横跳
8.Eureka的集群模式中注册中心信息都是互相拉取,是平等的关系,不存在主从关系
1.2 代码
Eureka搭建server服务器,其他服务需要用到Eureka,则导入EurekaClient依赖+注解就可
搭建Eureka-server服务:
1,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2,启动类上添加注解
@EnableEurekaServer
3, application.yml配置文件配置Eureka
eureka:
client:
service-url:
defaultZone: http://eureka8762.com:8762/eureka/ # 将自己配置到其它所有eureka服务上
register-with-eureka: false #不向注册中心注册自己
fetch-registry: false #自己是注册中心,不用获取注册表
server:
enable-self-preservation: true # 开启自我保护机制
eviction-interval-timer-in-ms: 60000
instance:
hostname: eureka8761.com
server:
port: 8761 # 指定端口号
spring:
application:
name: eureka-server # 指定EurekaServer的服务名
同理创建另外Eureka服务,相同配置,端口号不同即可,此在,这里创建两个客户端服务serve1、serve2注册到注册中心
注意:由于配置了集群,这里需要ip和域名映射,如果使用域名,两台eurekaserve服务同ip部署集群下,配置文件中使用ip:port配置可以会失效,这里需要到本地C盘下:C:\Windows\System32\drivers\etc 的host文件中加上如下所示:
127.0.0.1 eureka8761.com
127.0.0.1 eureka8762.com
配置客户端Serve1服务
1,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2,添加注解
@EnableEurekaClient
3, application.yml配置文件配置Eureka
eureka:
client:
service-url:
defaultZone: http://eureka8761.com:8761/eureka/ , http://eureka8762.com:8762/eureka/ # 注册的Eureka服务地址
registry-fetch-interval-seconds: 30
instance:
appname: serve1
同理创建另客户端serve2服务,相同配置,端口不同即可
微服务项目搭建完场之后,启动Eureka集群,启动两个客户端Serve1,Serve2
去往第一个注册中心:8761端口
去往第二个注册中心:8761端口
Eureka的自我保护机制:EurekaService 在 15min 内,如果超过 85%心跳不正常,依旧对外服务,但是会不删除、添加服务,等到心跳正常,在去其他 service 同步。
至此,注册中心就算搭建起来了!!!
.
二、负载均衡:Ribbon ⭐
Ribbon简单来说:就是当前微服务模块集群的时候,需要负载均衡,就用Ribbon,使用也简单,导入依赖,远程调用方法上加注解@LoadBalanced。
Ribbon也可以不导入依赖, spring-cloud-starter-netflix-eureka-client已经引入了Ribbon,注册中心轮询服务就是用的Ribbon的负载均衡的轮询策略,可以不手动导入依赖
在后续要学习的远程调用Feigh中也自动集成了Ribbon,所以这个可以不用,后续的远程调用在配置文件配置一下即可,注解也自动加上了
三、远程调用:Feigh ⭐⭐⭐
3.1 原理
在开发中,我们常用httpClient去远程调用其他系统的接口,需要我们指定调用的url,Feign 是一个声明式的 Web Service 客户端,它实现了一套远程调用的方法,调用的方式也更为优雅。
使用前提:被调用的模块注册到eureka中,能正常运行!!!
关于Feign
1.Feigin 默认支持 ribbon 做负载均衡
2.在调用方服务启动类上加入注解@EnableFeignClients
3.声明接口,接口映射被调用方 Controller 层的方法,这里时本质采用 jdk 动态代理生成代理对象,调用远程方法
4.在声明的接口上加入注解@FeignClient(value=指定服务名)
5.feign 的 fallback 实际是整合 hystrix,需要在 yml 文件中配置 hystrix
6.单独使用 feign 的 fallback 无法获取异常,需要实现 fallbackfactory接口
7.默认情况下,feign 的超时时间是 1s,hystrix 的线程池隔离的超时时间也是1S
关于feigh负载均衡:配置一下即可
feign:
hystrix:
enabled: true
3.2 代码
在调用者服务中使用,创建一个接口,映射服务提供者的controller中的方法
例如:这里Serve2服务,调用Serve1服务,则:接口中的方法则是serve1中controller层的方法,去掉了方法体
1,服务调用方导入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2,创建接口映射,添加注解:@FeignClient
3,Feigh支持降级方法
Feigh还支持方法降级:当服务提供方崩了、资源紧张或者响应速度特备慢的时候,可以走服务调用方设置的降级方法,快速失败,返回托底数据
举例:在淘宝中双11中访问某一件爆款商品,加入购入车的时候,出现了访问时间过久,重新访问一次的页面,此时就是设置了降级方法,因为服务访问人数过多,直接返回另外一个结果。
Feigh的降级方法本质上是整合了后续要学习的Hystrix,这里了解一下即可,后续学习中在说。
注意,fallback不能拿到降级的异常,需要通过FallbackFactory来实现
四、熔断限流:Hystrix ⭐⭐⭐
Hystrix总结是实现三个方向:服务降级、熔断、限流
当微服务中某一个服务出现资源紧张、服务崩了,此时由于微服务中是互相调用关系居多,此时可能出现下面一种情况,整个服务都资源紧张了,这中情况是不可取的。
Hystrix提供了三种策略还解决上述的服务雪崩问题
- Hystrix提供了线程隔离策略
- Hystrix提供了方法的降级,可以实现快速失败,返回托底数据
- Hystrix提供了断路器、熔断器。(摸电门)
- Hystrix还提供了一个近乎实时的图形化界面,可以查看近期服务的执行情况。
接下来就是分别学习了解一下这三种策略
4.1线程池策略
- 客户端发送请求到服务时,由容器的线程池中的线程处理请求,执行业务逻辑代码时,采用Hystrix提供的线程池中的线程处理
- 涉及到了线程的切换时:
– 由于切换了线程,支持超时时间的设置
– 由于切换了线程,导致这个请求的处理速度相对更慢
– 一般比较复杂的业务推荐使用线程池隔离策略
总结:请求交给spring容器线程,处理业务逻辑交给Hystrix线程,这样可以设置超时时间,快速失败!
.
4.2 信号量隔离策略
- 信号量就是一个计数器,客户端发送请求和业务的处理都使用容器的线程池中的线程处理
- 通过这个计数器,确认线程池中的情况,给予快速的反馈
- 没有涉及到线程的切换:
- 不支持超时时间,无法实现快速失败,返回托底数据
- 更适合应用到一些处理速度较快的业务上
总结:请求和业务线程都交给spring容器线程处理,无法设置超时时间,适合用在处理速度快的业务上。
示例:
1,导入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2,启动类上加注解:
@EnableCircuitBreaker
3,方法上@HystrixCommand()进行系列配置!
具体配置方案,查看Hystrix在github上的Wiki百科
@HystrixCommand(commandProperties = {
// 设置隔离策略,THREAD代表线程池隔离,SEMAPHORE代表信号量隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD、SEMAPHORE"),
// 设置超时时间,默认为1s
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
// 超时时间开关,默认开启
@HystrixProperty(name = "execution.timeout.enabled", value = "false"),
// 超时后,是否中断线程,默认为true
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 取消任务后,是否中断线程,默认为false
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "false"),
// 信号量最大请求并发数,默认值为10,10理论上可以处理25000的每秒请求数
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
})
.
4.3 方法降级
针对目标方法设置降级方法,当目标方法出现问题时,会直接快速失败,执行降级方法,返回托底数据 降级方法的描述要和原方法一模一样
示例:
@HystrixCommand(fallbackMethod = "coreFallback"})
public String core() throws InterruptedException {
System.out.println("当前线程名为:" + Thread.currentThread().getName());
return "ok!";
}
public String coreFallback(){
return "服务器挤爆了,请稍后再试!";
}
4.4 断路器、熔断器
断路器是马丁福勒提出的一个思想Hystrix断路器的执行原理、流程 如下图所示:
断路器默认开启,为了更好的查看效果,配置图形化界面
示例:图形化界面
1,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
2,启动类上添加注解: @EnableHystrixDashboard
3,配置ServeletConfig:这里:这里/hystrix.stream路径时固定了,只能时这个路径,不能改变,否则看不到可视化图形界面
/**
* @Author xiaozq
* @Date 2022/11/30 10:04
* <p>@Description:图形化界面</p>
*/
@Configuration
public class HystrixConfig{
@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;
}
}
访问:http://host:port/hystrix
点击下面的monitor按钮,则进入下面界面:标识进入监控范围了
下面来试着对配置了hystrix的方法进行测试,注意,这里没有配置隔离策略,则默认的时线程池隔离策略
注意上面监控,上图配置中可以看出,10s以内(这是默认的,无法修改),来5个请求,请求失败率达到20%(也就是连续5个请求是失败1个,断路器就会开启),则开启断路器10s,10s后进入半开启状态,此时来一个请求,成功,则断路器关闭,业务正常访问,否则,继续开启断路器10s
.
接下来进行测试:
访问http://localhost:8081/prac2/2,掏出postman发请求访问来连续5次,访问正常,则monitor监控上显示如图所示:
.
接下来http://localhost:8081/prac2/1, 访问请求异常的情况,则monitor监控上显示如图所示:
.
五、网关:Getway ⭐⭐⭐
GetWay作为统一配置的网关,可以实现负载均衡,动态路由以及限流
GetWay作为SpringCloud家族中新一代网关,在性能上比Zuul高上1.5倍
Getway内部实现了负载均衡,不需要手动配置,这点了解一下即可,此在,GetWay天然适配Eureka,能够在夫妻集群中负载均衡,默认采用轮询的方式进行。
GetWay的动态路由也是很大一特点,在下面的学习中会清楚的应用起来 GetWay限流内部默认采用的时redis lua脚本+令牌桶算法进行限流,限流方式很多,例如:ip限流,路径限流等,此在限流默认系统时无法捕捉到异常原因,只会返回code码429
5.1 动态路由
1,导入依赖:
<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-gateway</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!--基于Redis实现限流-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
2,添加注解,将getway服务注册到注册中心上,getway服务端口为8083
3,配置application,yml文件
静态路由和动态路由的区别在于配置中关于uri的配置,lb://服务名 (标识动态路由,默认采用的负载均衡),如果是写死的ip:port则是静态路路由
需要了解配置文件中三个关键属性
id: 这个代表路由一个服务的标识,一般与服务名一致
uri: 路由地址
predicates: 断言,需要匹配到这个路径,才会路由到uri
filters:限流策略(默认采用令牌桶的限流策略,所以基本配置都是配置令牌桶相关的参数)
eureka:
client:
service-url:
defaultZone: http://eureka8761.com:8761/eureka/ , http://eureka8762.com:8762/eureka/ # 注册的Eureka服务地址
registry-fetch-interval-seconds: 30
instance:
appname: getway-serve
spring:
application:
name: getway-serve
redis:
host: 120.76.159.196
port: 6379
database: 2
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: serve1
uri: lb://SERVE1
predicates:
- Path=/ser1/**
filters:
- name: SelfRateLimiter # 自定义限流过滤器
args:
# 每秒允许处理的请求数量 # 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 每秒最大处理的请求数量# 令牌桶总容量
redis-rate-limiter.burstCapacity: 1
# 每次请求获取的令牌数
redis-rate-limiter.requestedTokens: 1
#限流策略,对应策略的Bean
key-resolver: "#{@pathKeyResolver}"
- id: serve2
uri: lb://SERVE2
predicates:
- Path=/ser2/**
filters:
# - name: RequestRateLimiter # 官方自带
- name: SelfRateLimiter
args:
# 每秒允许处理的请求数量 # 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 每秒最大处理的请求数量# 令牌桶总容量
redis-rate-limiter.burstCapacity: 1
# 每次请求获取的令牌数
redis-rate-limiter.requestedTokens: 1
#限流策略,对应策略的Bean
key-resolver: "#{@ipKeyResolver}"
这样通过路由8083端口去访问服务,则效果如图所示:
网关访问服务2,服务2调用服务1 的test1方法,成功!!!
5.2 限流
在配置yml文件中filter属性配置就是配置限流的策略,限流默认采用令牌桶限流策略
注意,还需要了解一个类: KeyResolver :指定具体的限流对象,例如ip限流,url限流等
在项目代码中,配置了两中限流方式:IP、URL
注意,在下面配置文件中,filter属性默认的限流name = RequestRateLimiter 这个类,下面配置我采用自定义限流继承RequestRateLimiterGatewayFilterFactory这个类
routes:
- id: serve1
uri: lb://SERVE1
predicates:
- Path=/ser1/**
filters:
- name: SelfRateLimiter # 自定义限流过滤器
args:
# 每秒允许处理的请求数量 # 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 每秒最大处理的请求数量# 令牌桶总容量
redis-rate-limiter.burstCapacity: 1
# 每次请求获取的令牌数
redis-rate-limiter.requestedTokens: 1
#限流策略,对应策略的Bean
key-resolver: "#{@pathKeyResolver}"
- id: serve2
uri: lb://SERVE2
predicates:
- Path=/ser2/**
filters:
# - name: RequestRateLimiter # 官方自带
- name: SelfRateLimiter
args:
# 每秒允许处理的请求数量 # 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 每秒最大处理的请求数量# 令牌桶总容量
redis-rate-limiter.burstCapacity: 1
# 每次请求获取的令牌数
redis-rate-limiter.requestedTokens: 1
#限流策略,对应策略的Bean
key-resolver: "#{@ipKeyResolver}"
继承RequestRateLimiterGatewayFilterFactory,限流策略没变,基本是源码复制过来,稍微重写一下,重写的目的是末尾能够将异常原因设置响应给前端
/**
* 自定义geway限流策略
*/
@Component
public class SelfRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
public SelfRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
super(defaultRateLimiter, defaultKeyResolver);
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
}
@Override
public GatewayFilter apply(Config config) {
KeyResolver resolver = getOrDefault(config.getKeyResolver(), defaultKeyResolver);
RateLimiter<Object> limiter = getOrDefault(config.getRateLimiter(), defaultRateLimiter);
return (exchange, chain) -> {
// exchange : DefaultServerWebExchange
// 示例:对路径,ip限流,
// resolve: value:"/ser1/test1"、"0:0:0:0:0:0:0:1"
// 这里获取到路径
// mono类型都是要采用反应式调用的方式,就好比steam流了,接下载就是对应的api调用,所以这里用到了flatmap
Mono<String> resolve = resolver.resolve(exchange);
return resolve.flatMap(key -> {
// 获取路由id(服务名)
String routeId = config.getRouteId();
if (routeId == null) {
// yml文件中没配置,则从当前请求上下文exchange中获取key为gatewayRoute的路由信息
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
// 获取具体的服务名
routeId = route.getId();
}
String finalRouteId = routeId;
// key = "ser1/test"
// 限流核心方法isAllowed-->redis+lua实现令牌桶算法
// 返回结果:1,是否允许、 2,剩余令牌有无,没有则-1、 3,流速、生产数、剩余数的map集合
/** 打印示例如下:
transfer-encoding--------[chunked]:分块传输编码
X-RateLimit-Remaining--------[0] :剩余量
X-RateLimit-Burst-Capacity--------[1]:桶容量
X-RateLimit-Replenish-Rate--------[1]:生产令牌速度
*/
Mono<RateLimiter.Response> allowed = limiter.isAllowed(routeId, key);
return allowed.flatMap(response -> {
response.getHeaders().entrySet();
// response中设置响应信息,响应出去
for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
}
// 放行
if (response.isAllowed()) {
return chain.filter(exchange);
}
// 不放行
System.out.println("已限流: " + finalRouteId);
ServerHttpResponse httpResponse = exchange.getResponse();
//修改code为500
httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
if (!httpResponse.getHeaders().containsKey("Content-Type")) {
httpResponse.getHeaders().add("Content-Type", "application/json");
}
//此处无法触发全局异常处理,(默认返回429,无其它信息),手动返回response,设置code对应mes信息
HashMap<Object, Object> resultMap = new HashMap<>();
resultMap.put("code", "429");
resultMap.put("mes", finalRouteId + "服务访问人数过多,已限流");
resultMap.put("data", "Server throttling");
resultMap.put("success", "false");
//将map格式转为json
String resultJson = JSONObject.toJSONString(resultMap);
DataBuffer buffer = httpResponse.bufferFactory().wrap(resultJson.getBytes(StandardCharsets.UTF_8));
return httpResponse.writeWith(Mono.just(buffer));
});
});
};
}
private <T> T getOrDefault(T configValue, T defaultValue) {
return (configValue != null) ? configValue : defaultValue;
}
}
此在,在微服务的入口这里,还可以实现令牌的校验,检查登录,做授权服务
本质配置去全局过滤器(后续会采用其他的方式,比如springsecurity+jwt+oauth2做认证授权服务器校验token等),这里大致知道有这么一个过滤器GlobalFilter,Ordered
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String accessToken = request.getHeaders().getFirst("token");
if("xzq".equals(accessToken)){
return chain.filter(exchange);
}else{
// 给响应,跳到登录页面
return login(exchange);
}
}
private Mono<Void> login(ServerWebExchange exchange) {
HashMap<Object, Object> resultMap = new HashMap<>();
resultMap.put("code",401);
resultMap.put("mes","请重新登录授权");
resultMap.put("status", "401");
ServerHttpResponse response = exchange.getResponse();
byte[] bytes = resultMap.toString().getBytes(StandardCharsets.UTF_8);
response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
// 封装响应数据
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
}
@Override
public int getOrder() {
return 0;
}
}
接下啦,用postman测试一下限流效果:
.
六、链路追踪:Flueth、Zipkin(docker配置组件) ⭐⭐
简单来说: 可以采用Sleuth收集所有服务的日志信息,Sleuth会将收集到的日志发送到Zipkin图形化界面中,更快速的定位到为题发生的服务中
在各个服务中配置,统一发送到图形化界面上
1,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
// 直接导入下面,因为zipkin依赖包含了slueth依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2,指定日志级别: 将前端控制器的日志级别设置为DEBUG(因为所有请求都是从前端控制器开始进入的)
logging:
level:
org.springframework.web.servlet.DispatcherServlet: DEBUG
3,配置application.yml文件
zipkin:
sender:
type: web
base-url: http://120.76.159.196:9411
sleuth:
sampler:
probability: 1
这里再serve1和serve2以及网关中加入配置和日志,接下来,启动服务,看控制台!!!!
注意,这里可以看到,出现了DEBUG [serve2,2393be0d0bcda80b,79db3bf31e734485,true]:解释当前服务,总链路id,子链路id,日志是否发送到其他服务
.
4, 配置slueth搭配zipkin可视化:注意看,可视化的方式,可以直接通过web请求收集,也可以通过irabbitmq收集,也可以通过kafka收集,再实际开发过程中,基本都是通过队列实现,因为通过web请求是同步处理的方式,耗时,这样采用异步的方式将日志发送到队列上在可视化到zipkin上,性能好很多!!!
.
关于收集和是持久化日志:
1,收集日志方式Rabiit、Kafka的方式的话,需要配置rabbit,例如采用docker配置,然后配置文件加入配置即可,这里就简单采用web当时看效果啦
.
2,Sleuth异步传输数据到Zipkin,避免Sleuth以Web的方式发送日志信息给Zipkin会造成额外的性能损耗可以通过RabbitMQ实现异步的方式,让Sleuth将日志发送到消息队列,让Zipkin收集即可
.
3, Zipkin默认采用内存存储数据,导致Zipkin重启后,数据全部丢失。
Zipkin也提供了响应的持久化方案,比较熟悉的是ES和MySQL,其中ES更适合存储海量数据,采用ES作为持久化方案只需要Zipkin设置好ES的地址信息,Zipkin会自动创建索引,存储日志信息
方式一:web
5,接下来就是演示效果截图
访问链接:http://120.76.159.196:9411/zipkin/
接下来从getway网关服务开始,网关路由serve2,serve2远程调用serve1的方法,postman测一下看效果
postman测一下,出现一条日志
在下图中可以清晰的看到服务之间的调用,以及调用的那个方法吗,时间长久相对长久,根据时间判断在调用过程中,那个服务请求的时间长,然后可以做具体的优化!!!!,真的一目了然
在下面两张图中,都可以很清晰的看到请求调用浏览器开始时间,结束时间,经过服务器开始结束时间,以及指定看一个服务器可以看到请求经过的的服务的方法、具体的请求方式Get、路径,controller类,方法名等,真的详细,另外在途中最后中可以看到服务serve1调用出问题,红色代表出现异常
http.method
GET
http.path
/ser2/test1
mvc.controller.class
Serve2Controller
mvc.controller.method
getServe1Info1
.
.
最后,赋springcloud简单总结资料:getway换成zuul也简单了解一下吧
.
至此,微服务的基本组件的了解和应用就到此结束了…