Spring Cloud 与 K8s 的微服务设计(二)

实战 Spring Boot 2.x 结合 K8s 来实现微服务架构设计

微服务架构中,主要的就是服务消费者、服务的生产者可以互通,可以发生调用,在这基础上,还可以实现负载均衡,即一个服务调用另一个服务时,在该服务存在多个节点的情况下,可以通过一些策略来找到该服务的一个合适的节点访问。下面主要介绍服务的生产者与消费者。

先看生产者,引入常用的依赖:

<parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.1.13.RELEASE</version>        <relativePath/>    </parent>
  <properties>    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>    <java.version>1.8</java.version>    <swagger.version>2.6.1</swagger.version>    <xstream.version>1.4.7</xstream.version>    <pageHelper.version>4.1.6</pageHelper.version>    <fastjson.version>1.2.51</fastjson.version>    <springcloud.version>Greenwich.SR3</springcloud.version>    <springcloud.kubernetes.version>1.1.1.RELEASE</springcloud.kubernetes.version>    <mysql.version>5.1.46</mysql.version>  </properties>
  <dependencyManagement>    <dependencies>      <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-dependencies</artifactId>        <version>${springcloud.version}</version>        <type>pom</type>        <scope>import</scope>      </dependency>    </dependencies>  </dependencyManagement>
  <dependencies>      <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-web</artifactId>      <exclusions>        <exclusion>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-tomcat</artifactId>        </exclusion>      </exclusions>    </dependency>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-undertow</artifactId>    </dependency>
  <!-- 配置加载依赖 -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-actuator</artifactId>        </dependency>
        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-actuator-autoconfigure</artifactId>        </dependency>
        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-kubernetes-config</artifactId>        </dependency>
        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>
        <dependency>            <groupId>io.jsonwebtoken</groupId>            <artifactId>jjwt</artifactId>            <version>0.9.0</version>        </dependency>        <dependency>            <groupId>cn.hutool</groupId>            <artifactId>hutool-all</artifactId>            <version>4.6.3</version>        </dependency>
        <dependency>            <groupId>com.google.guava</groupId>            <artifactId>guava</artifactId>            <version>19.0</version>        </dependency>
        <dependency>        <groupId>org.apache.commons</groupId>        <artifactId>commons-lang3</artifactId>        </dependency>
    <dependency>        <groupId>commons-collections</groupId>        <artifactId>commons-collections</artifactId>        <version>3.2.2</version>    </dependency>
    <dependency>      <groupId>io.springfox</groupId>      <artifactId>springfox-swagger2</artifactId>      <version>${swagger.version}</version>    </dependency>    <dependency>      <groupId>io.springfox</groupId>      <artifactId>springfox-swagger-ui</artifactId>      <version>${swagger.version}</version>    </dependency>
  <!-- 数据库分页依赖 -->      <dependency>        <groupId>com.github.pagehelper</groupId>        <artifactId>pagehelper</artifactId>        <version>${pageHelper.version}</version>      </dependency>
        <dependency>            <groupId>org.mybatis.spring.boot</groupId>            <artifactId>mybatis-spring-boot-starter</artifactId>            <version>1.1.1</version>        </dependency>        <dependency>      <groupId>mysql</groupId>      <artifactId>mysql-connector-java</artifactId>      <version>${mysql.version}</version>    </dependency>
  <!-- 数据库驱动 -->        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>druid</artifactId>            <version>1.1.3</version>        </dependency>
    <dependency>      <groupId>com.alibaba</groupId>      <artifactId>fastjson</artifactId>      <version>${fastjson.version}</version>    </dependency>
    <dependency>      <groupId>org.jsoup</groupId>      <artifactId>jsoup</artifactId>      <version>1.11.3</version>    </dependency>
    </dependencies>

复制代码

上面我们使用了比较新的版本:Spring Boot 2.1.13,Cloud 版本是 Greenwich.SR3,其次,我们配置了 K8s 的 ConfigMap 所用的依赖,加上了数据库的一些配置,具体其他的,实现过程中,大家可以自行添加。

接下来,我们看启动时加载的配置文件,这里加了关于 K8s ConfigMap 所管理的配置所在的信息,以及保证服务被发现,开启了所有的 namespace,同时还启动了配置自动刷新的功能,注意的是,该配置需要在 bootstrap 文件:

spring:  application:    name: cas-server  cloud:    kubernetes:      config:        sources:         - name: ${spring.application.name}           namespace: default      discovery:        all-namespaces: true #发现所有的命令空间的服务      reload:        enabled: true        mode: polling #自动刷新模式为拉取模式,也可以是事件模式 event        period: 500 #拉取模式下的频率
logging: #日志路径设置  path: /data/${spring.application.name}/logs

复制代码

剩下的一些配置可以在 application 文件中配置:

spring:  profiles:    active: dev
server:  port: 2000  undertow:    accesslog:      enabled: false      pattern: combined  servlet:    session:      timeout: PT120M #session 超时时间
client:  http:    request:      connectTimeout: 8000      readTimeout: 30000
mybatis: #持久层配置  mapperLocations: classpath:mapper/*.xml  typeAliasesPackage: com.damon.*.model

复制代码

接下来看下启动类:

/** * * @author Damon * @date 2020 年 1 月 13 日 下午 8:29:42 * */@Configuration@EnableAutoConfiguration@ComponentScan(basePackages = {"com.damon"})//@SpringBootApplication(scanBasePackages = { "com.damon" })@EnableConfigurationProperties(EnvConfig.class)public class CasApp {  public static void main(String[] args) {    SpringApplication.run(CasApp.class, args);  }}

复制代码

这里我们没有直接用注解 @SpringBootApplication,因为主要用到的就是几个配置,没必要全部加载。

我们看到启动类中有一个引入的 EnvConfig.class:

/** * @author Damon * @date 2019 年 10 月 25 日 下午 8:54:01 * */
@Configuration@ConfigurationProperties(prefix = "greeting")public class EnvConfig {
    private String message = "This is a dummy message";
    public String getMessage() {        return this.message;    }
    public void setMessage(String message) {        this.message = message;    }

复制代码

这就是配置 ConfigMap 中的属性的类。剩下的可以自己定义一个接口类,来实现服务生产者。

最后,我们需要在 K8s 下部署的话,需要准备几个脚本。

1. 创建 ConfigMap

kind: ConfigMapapiVersion: v1metadata:  name: cas-serverdata:  application.yaml: |-    greeting:      message: Say Hello to the World    ---    spring:      profiles: dev    greeting:      message: Say Hello to the Dev    spring:      profiles: test    greeting:      message: Say Hello to the Test    spring:      profiles: prod    greeting:      message: Say Hello to the Prod

复制代码

设置了不同环境的配置,注意,这里的 namespace 需要与服务部署的 namespace 一致,这里默认的是 default,而且在创建服务之前,先得创建这个。

2. 创建服务部署脚本

apiVersion: apps/v1kind: Deploymentmetadata:  name: cas-server-deployment  labels:    app: cas-serverspec:  replicas: 3  selector:    matchLabels:      app: cas-server  template:    metadata:      labels:        app: cas-server    spec:      nodeSelector:        cas-server: "true"      containers:      - name: cas-server        image: cas-server        imagePullPolicy: Always        ports:          - name: cas-server01            containerPort: 2000        volumeMounts:        - mountPath: /home/cas-server          name: cas-server-path        - mountPath: /data/cas-server          name: cas-server-log-path        - mountPath: /etc/kubernetes          name: kube-config-path        args: ["sh", "-c", "nohup java $JAVA_OPTS -jar -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC cas-server.jar --spring.profiles.active=dev", "&"]      volumes:      - name: cas-server-path        hostPath:          path: /var/pai/cas-server      - name: cas-server-log-path        hostPath:          path: /data/cas-server      - name: kube-config-path        hostPath:          path: /etc/kubernetes

复制代码

注意:这里有个属性 replicas,其作用是当前 pod 所启动的副本数,即我们常说的启动的节点个数,当然,你也可以通过前面讲的脚本来执行生成多个 pod 副本。如果这里没有设置多个的话,也可以通过命令来执行:

kubectl scale --replicas=3 deployment cas-server-deployment

复制代码

这里,我建议使用 Deployment 类型的来创建 pod,因为 Deployment 类型更好的支持弹性伸缩与滚动更新。

同时,我们通过 --spring.profiles.active=dev 来指定当前 pod 的运行环境。

3. 创建一个 Service

最后,如果服务想被发现,需要创建一个 Service:

apiVersion: v1kind: Servicemetadata:  name: cas-server-service  namespace: defaultspec:  ports:  - name: cas-server01    port: 2000    targetPort: cas-server01  selector:    app: cas-server

复制代码

注意,这里的 namespace 需要与服务部署的 namespace 一致,这里默认的是 default。

看看服务的消费者,同样,先看引入常用的依赖:

<parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.1.13.RELEASE</version>        <relativePath/>    </parent>
  <properties>    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>    <java.version>1.8</java.version>    <swagger.version>2.6.1</swagger.version>    <xstream.version>1.4.7</xstream.version>    <pageHelper.version>4.1.6</pageHelper.version>    <fastjson.version>1.2.51</fastjson.version>    <springcloud.version>Greenwich.SR3</springcloud.version>    <!-- <springcloud.version>2.1.8.RELEASE</springcloud.version> -->    <springcloud.kubernetes.version>1.1.1.RELEASE</springcloud.kubernetes.version>    <mysql.version>5.1.46</mysql.version>  </properties>
  <dependencyManagement>    <dependencies>      <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-dependencies</artifactId>        <version>${springcloud.version}</version>        <type>pom</type>        <scope>import</scope>      </dependency>    </dependencies>  </dependencyManagement>
  <dependencies>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-web</artifactId>      <exclusions>        <exclusion>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-tomcat</artifactId>        </exclusion>      </exclusions>    </dependency>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-undertow</artifactId>    </dependency>
        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>
   <!-- 配置加载依赖 -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-actuator</artifactId>        </dependency>
        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-actuator-autoconfigure</artifactId>        </dependency>
        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-kubernetes-config</artifactId>            </dependency>
    <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-commons</artifactId>        </dependency>
  <!-- 结合 k8s 实现服务发现 -->    <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-kubernetes-core</artifactId>        </dependency>
        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-kubernetes-discovery</artifactId>        </dependency>
  <!-- 负载均衡策略 -->    <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>        </dependency>

    <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>        </dependency>
  <!-- 熔断机制 -->        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>        </dependency>
        <dependency>            <groupId>cn.hutool</groupId>            <artifactId>hutool-all</artifactId>            <version>4.6.3</version>        </dependency>
    <dependency>      <groupId>com.alibaba</groupId>      <artifactId>fastjson</artifactId>      <version>${fastjson.version}</version>    </dependency>
    <dependency>      <groupId>org.jsoup</groupId>      <artifactId>jsoup</artifactId>      <version>1.11.3</version>    </dependency>
    <dependency>      <groupId>io.springfox</groupId>      <artifactId>springfox-swagger2</artifactId>      <version>${swagger.version}</version>    </dependency>    <dependency>      <groupId>io.springfox</groupId>      <artifactId>springfox-swagger-ui</artifactId>      <version>${swagger.version}</version>    </dependency>

    <dependency>        <groupId>org.apache.commons</groupId>        <artifactId>commons-lang3</artifactId>        </dependency>
    <dependency>        <groupId>commons-collections</groupId>        <artifactId>commons-collections</artifactId>        <version>3.2.2</version>    </dependency>
    <!-- 数据库分页 -->      <dependency>        <groupId>com.github.pagehelper</groupId>        <artifactId>pagehelper</artifactId>        <version>${pageHelper.version}</version>      </dependency>
        <dependency>            <groupId>org.mybatis.spring.boot</groupId>            <artifactId>mybatis-spring-boot-starter</artifactId>            <version>1.1.1</version>        </dependency>
        <dependency>      <groupId>mysql</groupId>      <artifactId>mysql-connector-java</artifactId>      <version>${mysql.version}</version>    </dependency>
  <!-- 数据库驱动 -->        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>druid</artifactId>            <version>1.1.3</version>        </dependency>
  </dependencies>

复制代码

这里大部分的依赖跟生产者一样,但,需要加入服务发现的依赖,以及所用的负载均衡的策略依赖、服务的熔断机制。

接下来 bootstrap 文件中的配置跟生产者一样,这里不在说了,唯一不同的是 application 文件:

backend:  ribbon:    eureka:      enabled: false    client:      enabled: true    ServerListRefreshInterval: 5000
ribbon:  ConnectTimeout: 3000  ReadTimeout: 1000  eager-load:    enabled: true    clients: cas-server-service,edge-cas-service,admin-web-service #负载均衡发现的服务列表  MaxAutoRetries: 1 #对第一次请求的服务的重试次数  MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)  OkToRetryOnAllOperations: true  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #负载均衡策略
hystrix:  command:    BackendCall:      execution:        isolation:          thread:            timeoutInMilliseconds: 5000 #熔断机制设置的超时时间  threadpool:    BackendCallThread:      coreSize: 5

复制代码

引入了负载均衡的机制以及策略(可以自定义策略)。

接下来看启动类:

/** * @author Damon * @date 2020 年 1 月 13 日 下午 9:23:06 * */
@Configuration@EnableAutoConfiguration@ComponentScan(basePackages = {"com.damon"})@EnableConfigurationProperties(EnvConfig.class)@EnableDiscoveryClientpublic class AdminApp {
    public static void main(String[] args) {        SpringApplication.run(AdminApp.class, args);    }
}

复制代码

同样的 EnvConfig 类,这里不再展示了。其他的比如:注解 @EnableDiscoveryClient 是为了服务发现。

同样,我们新建接口,假如我们生产者有一个接口是:

http://cas-server-service/api/getUser

复制代码

则,我们在调用它时,可以通过 RestTemplate Client 来直接调用,通过 Ribbon 来实现负载均衡:

@LoadBalanced  @Bean  public RestTemplate restTemplate() {    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();    requestFactory.setReadTimeout(env.getProperty("client.http.request.readTimeout", Integer.class, 15000));    requestFactory.setConnectTimeout(env.getProperty("client.http.request.connectTimeout", Integer.class, 3000));    RestTemplate rt = new RestTemplate(requestFactory);    return rt;  }

复制代码

可以看到,这种方式的分布式负载均衡实现起来很简单,直接注入一个初始化 Bean,加上一个注解 @LoadBalanced 即可。

在实现类中,我们只要直接调用服务生产者:

ResponseEntity<String> forEntity = restTemplate.getForEntity("http://cas-server/api/getUser", String.class);

复制代码

其中,URL 中 必须要加上 "http://",这样即可实现服务的发现以及负载均衡,其中,LB 的策略,可以采用 Ribbon 的几种方式,也可以自定义一种。

最后,可以在实现类上加一个熔断机制:

@HystrixCommand(fallbackMethod = "admin_service_fallBack")public Response<Object> getUserInfo(HttpServletRequest req, HttpServletResponse res) {
    ResponseEntity<String> forEntity = restTemplate.getForEntity(envConfig.getCas_server_url() + "/api/getUser", String.class);        logger.info("test restTemplate.getForEntity(): {}", forEntity);        if (forEntity.getStatusCodeValue() == 200) {                logger.info("================================test restTemplate.getForEntity(): {}", JSON.toJSON(forEntity.getBody()));                logger.info(JSON.toJSONString(forEntity.getBody()));        }}

复制代码

其中发生熔断时,回调方法:

private Response<Object> admin_service_fallBack(HttpServletRequest req, HttpServletResponse res) {    String token = StrUtil.subAfter(req.getHeader("Authorization"), "bearer ", false);    logger.info("admin_service_fallBack token: {}", token);    return Response.ok(200, -5, "服务挂啦!", null);  }

复制代码

其返回的对象必须与原函数一致,否则可能会报错。

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值