• spring cloud实践


    1. 前言

        spring cloud是一个企业级的基于spring boot的微服务解决方案,生态非常庞大,拥有许多微服务治理组件,一开始netflix贡献了大量的套件,随着netflix组件逐渐不维护,spring cloud开始与其解耦,发展了自己的套件,例如spring cloud gateway等。后来国内的大厂也贡献了自己生态的解决方案,例如alibaba spring cloud。
        注意spring cloud和spring boot的区别:

    维度spring cloudspring boot
    定位spring cloud是一套完整的微服务解决方案,很多基本API都封装好了,需要各个组件去适配(实现)它,例如DiscoveryClient组件的实现者可以是consul/nacos/euraka等,更适合全技术栈使用spring cloud的情形。spring boot是自动化配置而已,简化配置,更方便开发者适配自己的技术栈,但是功能还是和原生的一样。
    配置方式一般用bootstrap.yamlj进行配置,会先启动一个spring cloud容器一般用application.yaml进行配置
    使用场景比较适合新搭建企业,开箱即用,快速全栈使用spring cloud生态对于非spring cloud技术栈的传统企业(例如使用dubbo),迁移spring boot可以简化配置

        spring cloud vs dubbo对比:均具备微服务治理功能

    比较维度spring clouddubbo
    传输协议rest httpdubbo/triple
    负载均衡ribbonround roubin/ random等
    注册中心consul/eurekazookeeper
    集群失败策略ribbon failoverfailover/failsafe/failback
    路由gateway/zuulrouter 模块
    API声明式客户端feign client纯接口API

        本文基于spring boot 2.1.7.RELEASE + spring cloud Greenwich.SR6进行实验,代码仓库见:springCloudDemo。选型时需要注意版本兼容,spring boot版本 + spring cloud版本对应关系:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
    。版本搭配错误可能导致奇怪的问题。涉及到的组件如下:

    微服务组件spring cloud本文使用
    链路追踪skywalking/slegeth✖️
    注册中心consul/eurekaconsul
    配置中心spring cloud confignacos
    网关spring cloud gateway/ zuul✖️
    熔断限流hystrix/sentinel✖️
    RPCfeign client(声明式客户端)+ ribbon(负载均衡)feign client(声明式客户端)+ ribbon(负载均衡)

        一张图总结springCloudDemo架构:
    请添加图片描述

    2. 安装consul

        安装教程见:https://www.consul.io/downloads

        consul 开发模式服务端启动

    consul agent -server -ui -dev
    
    • 1

        启动成功日志:

    
    ==> Starting Consul agent...
               Version: '1.12.3'
               Node ID: 'd72c42e1-9bc0-07b5-a71e-5fe2f6e55579'
             Node name: 'mokas-MacBook-Pro-3.local'
            Datacenter: 'dc1' (Segment: '')
                Server: true (Bootstrap: false)
           Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
          Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
               Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false
    
    ==> Log data will now stream in as it occurs:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

        访问页面: http://localhost:8500/ui

    在这里插入图片描述

    3. nacos安装

        注意mysql8,需要设置时区,jdbc url要正确。已部署到云主机上:101.43.195.208:8848

    https://blog.csdn.net/u010312671/article/details/105930774

    https://blog.csdn.net/LeslieTsai2019/article/details/118759956

        启动nacos:

    sudo /opt/nacos/bin/startup.sh
    
    • 1

        访问nacos:http://101.43.195.208:8848/nacos
    在这里插入图片描述

    4. 定义FeignClient API

        核心是指定serviceId,serviceId为注册到consul上的serviceName。微服务的唯一标识。

    @FeignClient("spring-cloud-provider")
    public interface UserServiceFeignClient {
    
        @RequestMapping("/getByName")
        User getByName(@RequestParam String name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5. 定义consumer

    5.1 开启feign client扫描

        使用注解EnableFeignClients,自动将上面的feign API,生成jdk代理,并注入到spring context作为bean,原理在5.5进行说明。

    @EnableFeignClients(basePackages = "com.jessin.practice.spring.cloud.api")
    @SpringBootApplication
    public class ConsumerApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(ConsumerApplication.class, args);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.2 集成nacos

        引进依赖:

            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-config
                2.1.4.RELEASE
            
    
    • 1
    • 2
    • 3
    • 4
    • 5

        在bootstrap.yaml中配置nacos,由于是spring cloud,配置必须放到bootstrap.yaml中,在spring cloud容器启动时就已经拿到远程变量数据,方便后续子容器注入。

    spring:
      cloud:
        nacos:
          config:
             # nacos-spring-cloud配置方式,file-extension会添加到dataId中,
             # dataId默认是${spring.application.name}.${file-extension}
            server-addr: 101.43.195.208:8848
            file-extension: properties
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

        注意:这是spring cloud的配置方式,nacos spring boot的方式与这不同。不要同时使用spring cloud和spring boot配置nacos,会配置两次连接,而且会有类冲突。

        nacos上的spring-cloud-consumer.properties上的变量为:
    在这里插入图片描述

        之后,可以通过@Value(“${mykey}”)注入nacos上配置的变量,同时通过监听nacosConfigManager,可以得到配置变更的事件回调。

    • RefreshScope 表示该bean在environment有变化时,会动态刷新,重新注入依赖。 nacos跟spring cloud集成的话,配置文件在bootstrap.yaml,变量会自动注入到environment中,因而可以使用@Value,spring cloud才有RefreshScope这个注解,表示可以动态刷新,会重新构建environment
    @Configuration
    @RefreshScope
    @Slf4j
    public class NacosCloudService implements InitializingBean {
    
        @Value("${spring.application.name}")
        private String appName;
    
       // nacos注册进变量,自动refresh
        @Value("${useLocalCache:false}")
        private boolean useLocalCache;
    
        @Value("${myKey:112}")
        private int myKey;
    
        @Autowired
        private NacosConfigManager nacosConfigManager;
        @Autowired
        private NacosConfigProperties configProperties;
    
        public boolean getSwitch() {
            return useLocalCache;
        }
    
        public int getMyKey() {
            return myKey;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            // nacos spring cloud回调方式或者监听event
            nacosConfigManager.getConfigService().addListener(appName + ".properties", configProperties.getGroup(),
                    new Listener() {
                        @Override
                        public Executor getExecutor() {
                            return null;
                        }
    
                        /**
                         * 接收整个配置文件的配置
                         * @param configInfo
                         */
                        @Override
                        public void receiveConfigInfo(String configInfo) {
                            log.info("收到spring cloud配置变更消息:{}", configInfo);
                        }
                    });
        }
    }
    
    • 1
    • 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

    5.3 注册consul

        这里将spring.application.name配置到bootstrap.yaml中,作为consul服务名,consul注册中心为本地consul。需要注意的是spring cloud服务注册,使用spring boot actuator做健康检查,所以需要引进spring-boot-actuator这个jar,默认开放health这个endpoint。另外,默认情况下server.port和management.port是同一个,且management的路径会基于servlet.path作为其前缀,再加上/actuator,需要保证向consul注册的健康检查url是正确的,可以调通的,否则会服务发现不会会报no provider。

        bootstrap.yaml配置:

    spring:
      cloud:
        consul:
          discovery:
            #注册到 Consul 的服务名称,后期客户端会根据这个名称来进行服务调用
            serviceName: ${spring.application.name}
            prefer-ip-address: true
            ip-address: localhost
          #consul注册中心地址
          host: localhost
          port: 8500
    
      application:
        name: spring-cloud-consumer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

        application.yaml配置:

    server:
      port: 9999
    spring:
      mvc:
        servlet:
          # 不能配置为/practice,否则健康检查不通过,服务发现不了
          path: /
          loadOnStartup: 1
        throw-exception-if-no-handler-found: true
        # actuator单独使用一个端口,避免与业务端口共用一个path
    #management:
    #  server:
    #    port: 19999
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5.4 ribbon配置,异常重试:

        在bootstrap.yaml中添加如下配置,底层依然是http连接池。在provider超时时,会进行重试。运行时异常500,错误码不在范围内,不会进行重试。

    #全局
    ribbon:
      #每台重试一次,不包括第一次
      MaxAutoRetries: 0
      #重试2台机器,不包括第一台,总共重试(MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)
      MaxAutoRetriesNextServer: 2
      #每次http请求的超时时间
      ReadTimeout: 2000
      ConnectTimeout: 2000
      #如果对方返回404,抛出异常,以允许重试
      retryableStatusCodes: 404,405,406
      #所有异常都可以重试,否则只有get请求能重试
      OkToRetryOnAllOperations: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5.5 调用FeignClient

        直接使用@Resource FeignClient API 即可调用

    @RestController
    @Slf4j
    public class ConfigController {
    
        @Resource
        private NacosCloudService nacosCloudService;
    
        @Resource
        private UserServiceFeignClient userServiceFeignClient;
    
        /**
         * http://localhost:9991/getUseLocalCache
         *
         * 修改值:
         *
         * curl -X POST "http://101.43.195.208:8848/nacos/v1/cs/configs?dataId=spring-cloud-consumer.properties&group=DEFAULT_GROUP&content=useLocalCache=true"
         *
         * @return
         */
        @RequestMapping("/getUseLocalCache")
        public boolean getUseLocalCache() {
            return nacosCloudService.getSwitch();
        }
    
        /**
         * http://localhost:9991/getKey
         *
         * 修改值:
         *
         * curl -X POST "http://101.43.195.208:8848/nacos/v1/cs/configs?dataId=spring-cloud-consumer.properties&group=DEFAULT_GROUP&content=useLocalCache=true"
         *
         * @return
         */
        @RequestMapping("/getKey")
        public int getKey() {
            // spring cloud
            return nacosCloudService.getMyKey();
        }
    
        /**
         * http://localhost:9991/getUserByName?name=xiaoming
         * @param name
         * @return
         */
        @RequestMapping("/getUserByName")
        public User getUserByName(String name) {
            log.info("name is {}, myKey:{}", name, nacosCloudService.getMyKey());
            return userServiceFeignClient.getByName(name);
        }
    }
    
    • 1
    • 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
    • 50

        consumer feign client调用基本原理:

    a. feign client会使用jdk动态代理,代理逻辑见feign.ReflectiveFeign.FeignInvocationHandler#FeignInvocationHandler。对应的methodHandler为SynchronousMethodHandler。ReflectiveFeign#newInstance构建动态代理。
    b. 代理逻辑最终会调用LoadBalancerFeignClient#execute()

    feign.ReflectiveFeign.FeignInvocationHandler#invoke 
    -> feign.SynchronousMethodHandler#invoke 
    -> 执行RequestInteceptor
    -> org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute 
    -> RetryableFeignLoadBalancer#executeWithLoadBalancer
    ->com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig),利用LoadBalancerCommand进行处理,rxjava,会利用LoadBalancer进行服务发现和重试
    ->org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer#execute,底层会调用RetryTemplate,可以对同一台服务实例进行重试。
    RetryableFeginLoadBalancer#execute,底层封装了RetryTemplate,进行重试。doWithRetry->apache httpClient。均使用LoadBalancer选择服务实例,第一次使用外层设置的server,当失败时,底层可以通过lb重新选择一台服务实例。第一次:com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer,重试见org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicy#registerThrowable。注意,当没有服务示例可用时,会抛出异常,见com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer。
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6. 定义provider

        跟consumer很类似,spring.application.name改为:spring-cloud-provider。同时controller实现FeignClient API,其他的参考仓库:github仓库

    @RestController
    @Slf4j
    public class HelloController implements UserServiceFeignClient {
    
        /**
         * http://localhost:9999/getByName?name=xiaoming
         * @param name
         * @return
         */
        @RequestMapping("/getByName")
        @Override
        public User getByName(String name) {
            log.info("provider 实现,name is {}", name);
            User user = new User();
            user.setName(name);
            user.setAge(18);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    7. 测试

    1. 开启配置中心

        本实例使用的是nacos,已经部署在云端,无需配置

    1. 开启注册中心

        使用consul,则需要手动启动

    consul agent -server -ui -dev
    
    • 1

        访问页面:
    http://localhost:8500/ui

    在这里插入图片描述

    1. 开启两个provider
          provider1:
    java -Dserver.port=9999 -jar provider/target/provider-0.0.1-SNAPSHOT.jar
    
    • 1

    在这里插入图片描述
        provider2:

    java -Dserver.port=9998 -jar provider/target/provider-0.0.1-SNAPSHOT.jar
    
    • 1

    在这里插入图片描述
        可以看到会往consul上注册服务,且注册了actuator的健康检测连接,consul每10秒会调用这个链接检测存活。

    1. 开启一个consumer,并调用接口测试consumer
    java -Dserver.port=9991 -jar consumer/target/consumer-0.0.1-SNAPSHOT.jar
    
    
    • 1
    • 2

    在这里插入图片描述

        通过consul ui可以看到已经启动了3个instance:

    http://localhost:8500/ui/dc1/services
    
    • 1

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

        访问页面:

    http://localhost:9991/getUserByName?name=xiaoming
    
    • 1

        consumer:
    在这里插入图片描述
        调用了provider2:
    在这里插入图片描述

        再次访问,则调用provider1:说明底层使用roundrobin负载均衡
    在这里插入图片描述

    1. nacos测试:

        nacos上的值:
    在这里插入图片描述

        调用http://localhost:9991/getKey,得到myKey的值:
    在这里插入图片描述

        修改myKey:

    curl -X POST "http://101.43.195.208:8848/nacos/v1/cs/configs?dataId=spring-cloud-consumer.properties&group=DEFAULT_GROUP&content=myKey=123"
    
    • 1

    在这里插入图片描述

        可以看到整个文件内容都被替换了,原来的useLocalCache也没有值了。
    在这里插入图片描述
        再次getKey:
    在这里插入图片描述
    6. 异常测试:
        (1) 超时测试,会调用三次provider:
    http://localhost:9991/timeout?timeout=2
    在这里插入图片描述
    consumer:
    在这里插入图片描述
    provider1:
    在这里插入图片描述

    provider2:
    在这里插入图片描述

        (2) 运行时异常,500,不重试
    http://localhost:9991/fail?name=xiaoming
    在这里插入图片描述

    consumer:
    在这里插入图片描述
    provider2:
    在这里插入图片描述

  • 相关阅读:
    051-第三代软件开发-日志容量时间限制
    HI3519DV500快速启动
    大名鼎鼎的OceanBase居然在买Star了?
    基于Java毕业设计在线投稿系统源码+系统+mysql+lw文档+部署软件
    WMS仓储管理系统的使用价值,你知道多少
    202205(第13届)蓝桥杯Scratch图形化编程青少组(国赛_中级)真题
    JAVA进阶学习书籍
    Linux设备树详解
    shiro授权
    系统电容匹配误差和校正布局程序 1994
  • 原文地址:https://blog.csdn.net/ac_dao_di/article/details/126070583