• spring-cloud-kubernetes服务发现之在k8s环境下开发spring cloud应用


         通常情况下,我们的线上的服务在迁移到k8s环境下的时候,都是采用平滑迁移的方案。服务治理与注册中心等都是采用原先的组件。比如spring cloud应用,在k8s环境下还是用原来的一套注册中心(如eureka),服务治理(hystrix,ribbon)等。但是当我们开发新的应用时,我们是可以借助于sping-cloud-kubernetes组件为我们提供的服务发现、负载均衡等来摈弃像eureka这样的注册中心。本文主要通过构建两个spring cloud 服务来演示spring-cloud-kubernetes组件如何做服务的发现,负载均衡等。

    一、使用spring-cloud-kubernetes做服务发现

        分别开发两个服务,服务提供者:product_infra_service, 并通过FeignClient的方式对外提供接口,供消费者调用。服务消费者:product_infra_consumer. 

    首先新建一个module:product-infra-service-api:该项目中配置pom文件的相关依赖如下:

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <!--Service Interface API-->
    
            <!--Feign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>2.1.3.RELEASE</version>
            </dependency>
        </dependencies>

    该项目中定义接口如下: 

    @FeignClient(name="${feign.product-infra.name:product-infra-service}",fallback = PropertyClientFallback.class)

    public interface PropertyClient {
    
        @GetMapping(value="properties")
        List<String> getProperties();
    }

    hystrix的fallback类定义如下:

    @Component
    public class PropertyClientFallback implements PropertyClient{
    
        @Override
        public List<String> getProperties() {
            return new ArrayList<String>();
        }
    }

    接下来,新建服务提供者 product-infra-service项目,在该项目中实现PropertyClient接口。pom文件依赖配置如下:

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!--Service Interface API-->
            <dependency>
                <groupId>com.maidao.center.product_infra</groupId>
                <artifactId>product-infra-service-api</artifactId>
                <version>1.0.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!--SpringBoot Actuator-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
        </dependencies>

    定义controller 类如下:

    @RestController
    @RequestMapping
    public class PropertyController  implements PropertyClient{
    
        @GetMapping("properties")
        public List<String> getProperties(){
            ArrayList<String> properties = new ArrayList<>();
            properties.add("properties1");
            properties.add("properties2");
            return properties;
        }
    
    }

    编译打包成镜像后将项目部署到k8s环境,并在k8s环境下定义服务pod实例所关联到的service:本例中定义k8s service name 为 product-infra-service(与定义的应用名相同).

    新建消费者项目:product-infra-consumer,pom文件配置依赖如下:

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-kubernetes-core</artifactId>
                <version>${springcloud.kubernetes.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-kubernetes-discovery</artifactId>
                <version>${springcloud.kubernetes.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
                <version>${springcloud.kubernetes.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-commons</artifactId>
                <version>${springcloud.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>${springcloud.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                <version>${springcloud.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
                <version>${springcloud.version}</version>
            </dependency>
    
            <!--Service Interface API-->
            <dependency>
                <groupId>com.maidao.center.product_infra</groupId>
                <artifactId>product-infra-service-api</artifactId>
                <version>1.0.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    应用配置如下:
    server:
      port: 8080
    
    
    
    product-infra-service:
      ribbon:
        KubernetesNamespace: ${namespace}  (k8s namespace,实际根据自己服务部署的namespace配置对应的名字)
    
    backend:
      ribbon: 
        eureka:
          enabled: false
        client:
          enabled: true
        ServerListRefreshInterval: 5000
    
    hystrix:
      command:
        BackendCall:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 5000
      threadpool:
        BackendCallThread:
          coreSize: 5
    feign:
      hystrix:
        enabled: true

    新建ProductInfraConsumerController类:通过PropertyClient的引用调用提供者提供的接口。


    @RestController
    @RequestMapping
    public class ProductInfraConcumerController {

    @Autowired
    private PropertyClient propertyClient;

    @GetMapping("properties")
    public List<String> getProductProperties(){

    return propertyClient.getProperties();

    }

    }

    可以看到代码没有变化,调用方式没有变化,在消费者项目中去掉了原先的EurekaClient发现服务的调用。服务发现机制采用的是,spring-cloud-kubernetes-discovery。负载均衡组件采用的是:spring-cloud-starter-kubernetes-ribbon。

    最后将product-infra-consumer编译打包成镜像后,部署到k8s环境下。

    最后通过curl 命令在k8s环境下调用product-infra-consumer的controller方法来测试服务之间的通信:如下所示:

    [root@iZbp174tf9563rykf3bbjjZ ~]# curl http://172.30.28.100:8080/properties

    ["properties1","properties2"]

    停掉product-infra-service服务,再次发起请求,降级fallback方法生效:

    [root@iZbp174tf9563rykf3bbjjZ ~]# curl http://172.30.28.98:8080/properties
    
    []

    可以看到在没有eureka注册中心的情况下,定义的两个服务能够正常通信,且代码不需要做任何变动,只需要在消费者服务中引入spring-cloud-kubernetes相关依赖即可,spring-cloud-kubernetes组件起到了服务发现以及负载均衡的作用。

    二、spring-cloud-kubernetes 服务发现流程

          spring-cloud-kubernetes框架提供了调用kubernetes的原生能力来为现有SpringCloud应用提供服务,架构如下图所示:

    从上图可以看出product-infra-consumer在调用product-infra-service时,通过FeignClient组件拿到service name信息,最底层通过ok-http3,根据service name 调用 api server 获取该service下对应的Pod信息,拿到Pod信息后通过,轮询的方式向这些pod发送请求。spring-cloud-starter-kubernetes-ribbon组件中的KubernetesServerList 继承了 ribbon-loaderbanlancer组件中的AbstractServerList以及实现了 ServerList类中的方法,并通过 KubernetesClient提供的能力向k8s api server 发送请求信息。通过服务名获取pod信息的源码如下:

    public List<Server> getUpdatedListOfServers() {
            Endpoints endpoints = this.namespace != null
                    ? this.client.endpoints().inNamespace(this.namespace)
                            .withName(this.serviceId).get()
                    : this.client.endpoints().withName(this.serviceId).get();
    
            List<Server> result = new ArrayList<Server>();
            if (endpoints != null) {
    
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Found [" + endpoints.getSubsets().size()
                            + "] endpoints in namespace [" + this.namespace + "] for name ["
                            + this.serviceId + "] and portName [" + this.portName + "]");
                }
                for (EndpointSubset subset : endpoints.getSubsets()) {
    
                    if (subset.getPorts().size() == 1) {
                        EndpointPort port = subset.getPorts().get(FIRST);
                        for (EndpointAddress address : subset.getAddresses()) {
                            result.add(new Server(address.getIp(), port.getPort()));
                        }
                    }
                    else {
                        for (EndpointPort port : subset.getPorts()) {
                            if (Utils.isNullOrEmpty(this.portName)
                                    || this.portName.endsWith(port.getName())) {
                                for (EndpointAddress address : subset.getAddresses()) {
                                    result.add(new Server(address.getIp(), port.getPort()));
                                }
                            }
                        }
                    }
                }
            }
            else {
                LOG.warn("Did not find any endpoints in ribbon in namespace ["
                        + this.namespace + "] for name [" + this.serviceId
                        + "] and portName [" + this.portName + "]");
            }
            return result;
        }
  • 相关阅读:
    工作实战之项目常用技术
    Thymeleaf的错误解决方式
    实用小demo
    idea常用的几个插件
    idea2019+Plugins中搜索不到任何插件解决办法
    git的初体验
    springboot2.+的整合log4j2错误解决浅谈
    MobaXterm百度网盘下载
    阿里云RDS云数据库连接步骤
    读源码学编程之——死循环妙用
  • 原文地址:https://www.cnblogs.com/justinli/p/spring-cloud-kubernetes.html
Copyright © 2020-2023  润新知