此篇笔记包括的技术:Consul,OpenFeign,LoadBancer,Resilience4J,Micrometer+ZipKin,Gateway。
=======注意,这儿有用!!!!!=======
笔记事例代码已经同步到:gitee.com/yanghaokun0…
=======注意,这儿有用!!!!!=======
Consul服务发现与注册中心
Consul软件装置
Docker方法:
docker run -d --name consul -p 8500:8500 consul agent -dev -server -bootstrap -client 0.0.0.0 -ui
装置验证:
装置成功。以上装置没有解决数据耐久化,数据都存储在内存中。
客户端注册到Consul中
导入坐标:
<!--consul服务发现注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--web健康检测-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
yaml装备:
# consul服务地址
consul:
host: cloud.demo
port: 8500
# consul注册发现装备
spring:
cloud:
consul:
host: ${consul.host}
port: ${consul.port}
discovery:
# 注册到consul的服务称号
service-name: ${spring.application.name}
prefer-agent-address: true
# 心跳检查
heartbeat:
# 敞开
enabled: true
# 在注册时运用ip地址而不是主机名
prefer-ip-address: true
发动类敞开服务注册注解:
@EnableDiscoveryClient
发动微服务组件项目,观察Consul操控台:
Consul分布式装备中心(浅用)
Consul装备云yaml文件内容
解说:在Consul服务中装备了微服务 cloud-product 开发环境(dev)的云装备信息。
客户端读取云装备内容
导入云装备坐标:
<!--consul云装备读取-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
创立 bootstrap.yaml 体系等级项目发动文件
内容如下:
---
spring:
application:
name: cloud-product
profiles:
active: dev
---
# consul服务地址
consul:
host: cloud.demo
port: 8500
---
# consul注册发现装备
spring:
cloud:
consul:
host: ${consul.host}
port: ${consul.port}
discovery:
# 注册到consul的服务称号
service-name: ${spring.application.name}
prefer-agent-address: true
# 心跳检查
heartbeat:
# 敞开
enabled: true
# 在注册时运用ip地址而不是主机名
prefer-ip-address: true
---
spring:
cloud:
consul:
# 装备中心
config:
# 装备文件称号已 - 进行分割
profile-separator: '-'
# 装备文件类型
format: yaml
watch:
# 监听更新时刻距离 默许 55秒 测试 设置为 1秒
wait-time: 1
装备解说:项目一发动就去注册到Consul服务中,并且读取cloud-product-dev.yaml 文件的内容到本地。
Spring中读取装备文件内容完成动态改写的两种方法:
-
@Value()注解 + @RefreshScope注解 完成动态改写。
-
@Component注解 + @ConfigurationProperties()注解,完成动态改写,比如如下:
@ConfigurationProperties(prefix = "dateformat")
@Component
@Data
public class DateFormat {
private String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
private String dateFormat = "yyyy-MM-dd";
}
创立一个拜访接口进行测试,代码如下:
@RestController("/yun")
public class YunConfigController {
@Resource
private DateFormat dateFormat;
@GetMapping("/getInfo")
public String getInfo(){
return dateFormat.getDateTimeFormat();
}
}
拜访:
更新Consul上的云装备文件内容:
观察idea操控台打印:
再次拜访测试接口:
已完成云装备更新影响程序结果的作用。
OpenFeign长途调用接口
维护base-openfeign-api项目接口
根据以往项目开发经验,将OpenFeign维护的接口,单独创立出一个工程项目进行维护,过程如下:
创立一个base-openfeign-api的项目。
导入openfeign坐标,以及基础公共实体坐标。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创立维护某个微服务的openfeign接口:
@FeignClient("cloud-product")
public interface ProductApi {
/**
* 根据产品id查询产品信息
*/
@GetMapping("/product/getProductById/{id}")
public ResponseResult<ProductVO> getProductById(@PathVariable("id") Integer id);
}
接口上核心注解:@FeignClient() ,标明次api接口面向注册中心哪个服务。
运用base-openfeign-api项目进行长途拜访
导入base-openfeign-api坐标到运用工程中:
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--长途调用服务接口项目-->
<dependency>
<groupId>com.haokun</groupId>
<artifactId>base-openfeign-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
发动类上激活你需求的api接口所在包:
@EnableFeignClients(basePackages = {"com.haokun.api.product"})
业务中运用:
@Resource
private ProductApi productApi;
// 调用接口
ResponseResult<ProductVO> productRes = productApi.getProductById(order.getProductId());
上述过程已完成,在微服务一个体系内,能够运用openfeign的方法进行长途调用。
复制一份产品微服务程序,形成集群服务,运用openfeign长途调用产品服务,测试 LoadBancer 负载均衡:
发动:
LoadBancer 默许完成的是轮训算法进行拜访:
经过上述测试,已经完成openfeign + LoadBancer + consul 服务的注册发现,负载均衡,长途拜访功能。
LoadBancer的依赖由consul带入。
经过上面的过程已经完成了事例的第一个版别,下面逐渐参加微服务组件,代码地址如下: gitee.com/yanghaokun0…
openfeign的高档特性
调用时刻超时机制
yaml装备内容:
# openfeign超不时刻装备
spring:
cloud:
openfeign:
client:
config:
# 默许调用全部接口的时刻为1.5秒
default:
connect-timeout: 1500
read-timeout: 1500
# 指定微服务组件的超不时刻
cloud-product:
connect-timeout: 3000
read-timeout: 3000
超时会报错抛出反常,内容如下:
重试机制:
/**
* openfeign装备
*/
@SpringBootConfiguration
public class FeignConfig {
/**
* 重试装备
*
* @return
*/
@Bean
public Retryer myRetryer() {
// openfeign 默许装备是不走重试战略的
// return Retryer.NEVER_RETRY;
// 最大恳求次数为3 (1+2),初始距离时刻为100ms,重试间最大距离时刻为1s
return new Retryer.Default(100, 1, 3);
}
}
能够完成在恳求超不时,从头发送两次恳求。
恳求日志
观察恳求日志:
恳求连接池
openfeign 默许运用的是 java.base java.net.HttpURLConnection,没有连接池,替换为 httpClient 连接池,为openfeign来供给连接供给。
引进连接池坐标:
<!--恳求连接池-->
<!-- httpclient5-->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>
<!-- feign-hc5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<version>13.1</version>
</dependency>
yaml装备内容:
# 装备恳求连接池
spring:
cloud:
openfeign:
httpclient:
hc5:
enabled: true
恳求观察:
敞开恳求呼应紧缩
yaml装备:
# 敞开恳求呼应紧缩形式
spring:
cloud:
openfeign:
# 紧缩
compression:
# 恳求
request:
enabled: true
# 最小紧缩要求
min-request-size: 2048
# 紧缩数据类型
mime-types: text/xml, application/xml, application/json
# 呼应
response:
enabled: true
Resilience4j熔断降级,限流,阻隔
我对熔断降级,限流,阻隔的理解:熔断就是在恳求拜访一个服务时,触发了设置的阈值,然后开端熔断,不再恳求,并且快速回来数据,称为降级处理,阻隔和限流很相似,是设置拜访一个服务的恳求数量保持在多少,多的恳求会进行降级处理。
-
设定一个需求:在拜访一个恳求时,恳求数量6个,出现了3次反常信息,错误率达到50%,进行熔断降级处理,并回来体系繁忙请稍后再试的数据内容。
-
设定第二个需求:拜访一个恳求,这个恳求1秒只能并发5个,多的恳求会降级处理,并回来当时人数较多,请稍后再试的数据。
-
设定第三个需求:拜访一个恳求,这个恳求1秒只能经过10个,多的恳求会降级处理,并回来当时人数较多,请稍后再试的数据。
针对resilience4j熔断的方法两种:超时,反常,我将运用openfeign来操控超时,若超时抛出反常,让resilience4j来计算,达到阈值时熔断。
熔断降级
导入坐标:
<!--熔断降级resilience4j-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!--由于断路维护等需求AOP完成,所以必须导入AOP包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
yaml装备内容:
# 熔断降级前提装备
spring:
cloud:
openfeign:
# 断路器
circuitbreaker:
enabled: true
group:
enabled: true
坑:
# 延伸TimeLimiter默许的恳求超不时长 它 1秒的
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 10s
主要内容:
# 装备断路器
resilience4j:
circuitbreaker:
configs:
# 默许断路器装备
default:
# 断路器方法 计数的方法
sliding-window-type: count_based
# 计算数量周期
sliding-window-size: 6
# 最小计算数量
minimum-number-of-calls: 6
# 阈值 百分比
failure-rate-threshold: 50
#熔断时长 秒
wait-duration-in-open-state: 10s
# 需求敞开半开状况来测试
automatic-transition-from-open-to-half-open-enabled: true
# 半开测试拜访恳求数量
permitted-number-of-calls-in-half-open-state: 2
# 计数的反常
record-exceptions:
- java.lang.Exception
instances:
cloud-product:
base-config: default
Java代码侵略加强:
@CircuitBreaker(name = "cloud-product", fallbackMethod = "nextOrderFallbackMethod")
@Override
public Boolean nextOrder(Order order) {
}
public Boolean nextOrderFallbackMethod(Order order,Throwable throwable){
if (true){
throw new ServiceException("体系繁忙请稍后再试");
}
return false;
}
降级的方法参数有要求,如上:需求与装备了断路器的方法参数共同时,后面还需求参加Throwable参数类型。
开端压测:
其他恳求在熔断的时刻内将直接降级处理:
阻隔
限制对下流的并发数量。
导入坐标:
<!--对下流拜访的拜访阻隔-->
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
yaml装备:
# 对下流拜访的拜访阻隔
resilience4j:
bulkhead:
configs:
default:
# 最大答应数量
max-concurrent-calls: 5
# 时刻周期
max-wait-duration: 1s
instances:
cloud-product:
base-config: default
Java侵略增强:
@Bulkhead(name = "cloud-product",fallbackMethod = "nextOrderBulkheadFallbackMethod",type = Bulkhead.Type.SEMAPHORE)
降级方法:
开端压测:
答应的范围内成功:
失败:
限流
导入坐标:
<!--限流-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
yaml装备内容:
# 限流
resilience4j:
ratelimiter:
configs:
# 默许每秒只能经过1个恳求
default:
limit-for-period: 10
limit-refresh-period: 1s
timeout-duration: 1
instances:
cloud-product:
base-config: default
Java代码侵略增强:
压测触发,降级处理:
总结:resilience4j不好用,装备繁琐,代码侵略,难维护!
分布式链路追寻
micrometer(收集数据) + zipkin(数据展示)
建立 zipkin
Docker的方法建立:
docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin
浏览器拜访:你的服务地址:9411
引进micrometer
父工程版别操控内容:
<properties>
<!--链路追寻版别操控-->
<micrometer-tracing.version>1.2.0</micrometer-tracing.version>
<micrometer-observation.version>1.12.0</micrometer-observation.version>
<feign-micrometer.version>12.5</feign-micrometer.version>
<zipkin-reporter-brave.version>2.17.0</zipkin-reporter-brave.version>
</properties>
<dependencyManagement>
<dependencies>
<!--链路追寻版别操控-->
<!--micrometer-tracing-bom导入链路追寻版别中心 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bom</artifactId>
<version>${micrometer-tracing.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--micrometer-tracing目标追寻 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--micrometer-observation 4-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>${micrometer-observation.version}</version>
</dependency>
<!--feign-micrometer 5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
<version>${feign-micrometer.version}</version>
</dependency>
<!--zipkin-reporter-brave 6-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
<version>${zipkin-reporter-brave.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
微服务项目工程引进坐标:
<!--链路追寻-->
<!--micrometer-tracing目标追寻 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--feign-micrometer 4-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
yaml装备内容:
# 链路追寻zipkin装备
management:
zipkin:
tracing:
# zipkin服务端地址
endpoint: http://cloud.demo:9411/api/v2/spans
tracing:
sampling:
# 默许0.1 10次恳求收集一次,值越大收集越及时
probability: 1.0
发送恳求,观察zipkin操控台
能够发现第一次恳求拜访很慢。
点击一个恳求详细内容:SHOW按钮点击
发送一个会报反常的恳求:
Gateway网关
网关HelloWorld
创立网关微服务独立项目,引进坐标:
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
需求引进所运用的服务注册中心,并注册进入。
yaml装备一个路由:
# 装备网关路由
spring:
cloud:
gateway:
routes:
# 仅有id
- id: cloud-order
# 动态拜访服务地址
uri: lb://cloud-order
# 断语
predicates:
- Path=/order/**
网关三大组件:路由:routes,断语:predicates,过滤器:filters
经过网关的服务地址+端口号进行拜访,有效的屏蔽了真正微服务服务的地址。
网关动态获取服务地址
uri: lb://cloud-order
这儿的lb,就是:LoadBancer,从服务注册中心拉去下来,负载均衡。
断语predicates
断语官网介绍 docs.spring.io/spring-clou…
项目发动也加载了这些断语。
最常用的就是Path断语了。
测试一下头信息需求带着的内容:需求恳求带着头信息authentication,值为存数字才能够断语成功
predicates:
- Header=authentication, d+
测试不带着authentication头信息:
带着:
自定义断语 与 自定义过滤器相似。
过滤器
对已断语成功的恳求,锦上添花:
参考官网地址: docs.spring.io/spring-clou…
运用内置过滤器工厂往恳求头中增加信息,证明恳求是经过了网关,若客户端直接绕开网关,则恳求失败:
spring:
cloud:
gateway:
routes:
# 仅有id
- id: cloud-order
# 动态拜访服务地址
uri: lb://cloud-order
# 断语
predicates:
- Path=/order/**
# - Header=authentication, d+
filters:
- AddRequestHeader=me, yang
自定义全局过滤器
往恳求头中增加客户端恳求的IP地址。
@Component
public class ClientIpFilter implements GlobalFilter, Ordered {
private static final String CLIENT_IP_HEADER = "X-Client-IP";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取客户端IP
String clientIp = exchange.getRequest().getRemoteAddress().getHostString();
// ReadOnlyHttpHeaders 报错
// exchange.getRequest().getHeaders().add(CLIENT_IP_HEADER, clientIp);
ServerHttpRequest request = exchange.getRequest().mutate().header(CLIENT_IP_HEADER, clientIp).build();
return chain.filter(exchange.mutate().request(request).build());
}
/**
* 值越小,优先级越高
*/
@Override
public int getOrder() {
return -1;
}
}
网关发送到微服务时:
计算接口调用时刻
@Component
@Slf4j
public class GlobalTimeTestFilter implements GlobalFilter, Ordered {
//开端拜访时刻
private static final String BEGIN_VISIT_TIME = "begin_visit_time";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//先记录下拜访接口的开端时刻
exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
if (beginVisitTime != null) {
log.info("拜访接口主机: " + exchange.getRequest().getURI().getHost());
log.info("拜访接口端口: " + exchange.getRequest().getURI().getPort());
log.info("拜访接口URL: " + exchange.getRequest().getURI().getPath());
log.info("拜访接口URL参数: " + exchange.getRequest().getURI().getRawQuery());
log.info("拜访接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms");
log.info("分割线: ###################################################");
System.out.println();
}
}));
}
@Override
public int getOrder() {
return -2;
}
}
自定义条件过滤器
恳求头中带着指定id
@Component
@Slf4j
public class CheckGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckGatewayFilterFactory.Config> {
public CheckGatewayFilterFactory() {
super(CheckGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("经过了校验条件过滤器");
List<String> ids = List.of("2670", "2690");
String id = exchange.getRequest().getHeaders().getFirst("id");
if (Objects.isNull(id) || id.isEmpty() || !ids.contains(id)) {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return List.of("id");
}
@Data
public static class Config {
private String id;
}
}
yaml装备它:
# 装备网关路由
spring:
cloud:
gateway:
routes:
# 仅有id
- id: cloud-order
# 动态拜访服务地址
uri: lb://cloud-order
# 断语
predicates:
- Path=/order/**
# - Header=authentication, d+
filters:
- AddRequestHeader=me, yang
- Check
恳求拜访:
失败:
成功:
以上的笔记就结束了对原生SpringCloud的一些组件学习与练习。代码已标签: gitee.com/yanghaokun0…