原生SpringCloud组件应用学习笔记V1.0

此篇笔记包含的技术:Consul,OpenFeign,LoadBancer,Resilience4J,Micrometer+ZipKin,Gateway。

=======注意,这里有用!!!!!=======

笔记案例代码已经同步到:gitee.com/yanghaokun0...

=======注意,这里有用!!!!!=======

Consul服务发现与注册中心

官网:www.consul.io/

Consul软件安装

Docker方式:

shell 复制代码
docker run -d --name consul -p 8500:8500 consul agent -dev -server -bootstrap -client 0.0.0.0 -ui

安装验证:

访问:http://你的安装地址:8500

安装成功。以上安装没有解决数据持久化,数据都存储在内存中。

客户端注册到Consul中

导入坐标:

xml 复制代码
<!--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配置:

yaml 复制代码
# consul服务地址
consul:
  host: cloud.demo
  port: 8500
yaml 复制代码
# 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

启动类开启服务注册注解:

java 复制代码
@EnableDiscoveryClient

启动微服务组件项目,观察Consul控制台:

Consul分布式配置中心(浅用)

Consul配置云yaml文件内容

解释:在Consul服务中配置了微服务 cloud-product 开发环境(dev)的云配置信息。

客户端读取云配置内容

导入云配置坐标:

xml 复制代码
<!--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 系统级别项目启动文件

内容如下:

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()注解,实现动态刷新,例子如下:

java 复制代码
@ConfigurationProperties(prefix = "dateformat")
@Component
@Data
public class DateFormat {
    private String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    private String dateFormat = "yyyy-MM-dd";
}

创建一个访问接口进行测试,代码如下:

java 复制代码
@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坐标,以及基础公共实体坐标。

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

创建维护某个微服务的openfeign接口:

java 复制代码
@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坐标到使用工程中:

xml 复制代码
<!--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接口所在包:

java 复制代码
@EnableFeignClients(basePackages = {"com.haokun.api.product"})

业务中使用:

java 复制代码
@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配置内容:

yaml 复制代码
# openfeign超时时间配置
spring:
  cloud:
    openfeign:
      client:
        config:
          # 默认调用全部接口的时间为1.5秒
          default:
            connect-timeout: 1500
            read-timeout: 1500
          # 指定微服务组件的超时时间
          cloud-product:
            connect-timeout: 3000
            read-timeout: 3000

超时会报错抛出异常,内容如下:

重试机制:

java 复制代码
/**
 * 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来提供连接供给。

引入连接池坐标:

xml 复制代码
<!--请求连接池-->
<!-- 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配置内容:

yaml 复制代码
# 配置请求连接池
spring:
  cloud:
    openfeign:
      httpclient:
        hc5:
          enabled: true

请求观察:

开启请求响应压缩

yaml配置:

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来统计,达到阈值时熔断。

熔断降级

导入坐标:

xml 复制代码
<!--熔断降级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配置内容:

yaml 复制代码
# 熔断降级前提配置
spring:
  cloud:
    openfeign:
      # 断路器
      circuitbreaker:
        enabled: true
        group:
          enabled: true

坑:

yaml 复制代码
# 延长TimeLimiter默认的请求超时时长 它 1秒的
resilience4j:
  timelimiter:
    configs:
      default:
        timeout-duration: 10s

主要内容:

yaml 复制代码
# 配置断路器
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代码入侵加强:

java 复制代码
@CircuitBreaker(name = "cloud-product", fallbackMethod = "nextOrderFallbackMethod")
@Override
public Boolean nextOrder(Order order) {
}
java 复制代码
public Boolean nextOrderFallbackMethod(Order order,Throwable throwable){
    if (true){
        throw new ServiceException("系统繁忙请稍后再试");
    }
    return false;
}

降级的方法参数有要求,如上:需要与配置了断路器的方式参数一致时,后面还需要加入Throwable参数类型。

开始压测:

其他请求在熔断的时间内将直接降级处理:

隔离

限制对下游的并发数量。

导入坐标:

xml 复制代码
<!--对下游访问的访问隔离-->
<!--resilience4j-bulkhead-->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
</dependency>

yaml配置:

yaml 复制代码
# 对下游访问的访问隔离
resilience4j:
  bulkhead:
    configs:
      default:
        # 最大允许数量
        max-concurrent-calls: 5
        # 时间周期
        max-wait-duration: 1s
    instances:
      cloud-product:
        base-config: default

Java入侵增强:

java 复制代码
@Bulkhead(name = "cloud-product",fallbackMethod = "nextOrderBulkheadFallbackMethod",type = Bulkhead.Type.SEMAPHORE)

降级方法:

开始压测:

允许的范围内成功:

失败:

限流

导入坐标:

xml 复制代码
<!--限流-->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-ratelimiter</artifactId>
</dependency>

yaml配置内容:

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的方式搭建:

shell 复制代码
docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin

浏览器访问:你的服务地址:9411

引入micrometer

父工程版本控制内容:

xml 复制代码
<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>
xml 复制代码
<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>

微服务项目工程引入坐标:

xml 复制代码
<!--链路追踪-->
<!--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配置内容:

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

创建网关微服务独立项目,引入坐标:

xml 复制代码
<!--gateway-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

需要引入所使用的服务注册中心,并注册进入。

yaml配置一个路由:

yaml 复制代码
# 配置网关路由
spring:
  cloud:
    gateway:
      routes:
          # 唯一id
        - id: cloud-order
          # 动态访问服务地址
          uri: lb://cloud-order
          # 断言
          predicates:
            - Path=/order/**

网关三大组件:路由:routes,断言:predicates,过滤器:filters

通过网关的服务地址+端口号进行访问,有效的屏蔽了真正微服务服务的地址。

网关动态获取服务地址

yaml 复制代码
uri: lb://cloud-order

这里的lb,就是:LoadBancer,从服务注册中心拉去下来,负载均衡。

断言predicates

断言官网介绍 docs.spring.io/spring-clou...

项目启动也加载了这些断言。

最常用的就是Path断言了。

测试一下头信息需要携带的内容:需要请求携带头信息authentication,值为存数字才可以断言成功

yaml 复制代码
predicates:
  - Header=authentication, \d+

测试不携带authentication头信息:

携带:

自定义断言 与 自定义过滤器类似。

过滤器

对已断言成功的请求,锦上添花:

参考官网地址: docs.spring.io/spring-clou...

使用内置过滤器工厂往请求头中添加信息,证明请求是经过了网关,若客户端直接绕开网关,则请求失败:

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
          # 唯一id
        - id: cloud-order
          # 动态访问服务地址
          uri: lb://cloud-order
          # 断言
          predicates:
            - Path=/order/**
            # - Header=authentication, \d+
          filters:
            - AddRequestHeader=me, yang

自定义全局过滤器

往请求头中添加客户端请求的IP地址。

java 复制代码
@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;
    }
}

网关发送到微服务时:

统计接口调用时间

java 复制代码
@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

java 复制代码
@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配置它:

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...

相关推荐
工业甲酰苯胺1 小时前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
m0_635502203 小时前
Spring Cloud Gateway组件
网关·微服务·负载均衡·过滤器
bjzhang753 小时前
SpringBoot开发——集成Tess4j实现OCR图像文字识别
spring boot·ocr·tess4j
flying jiang3 小时前
Spring Boot 入门面试五道题
spring boot
小菜yh3 小时前
关于Redis
java·数据库·spring boot·redis·spring·缓存
爱上语文5 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
荆州克莱5 小时前
springcloud整合nacos、sentinal、springcloud-gateway,springboot security、oauth2总结
spring boot·spring·spring cloud·css3·技术
serve the people5 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政10 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
Java小白笔记13 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis