微服务通用组件模块

chenxin
10
2024-11-20

对于很多微服务模块都需要的通用组件,我们可以把这些通用的提取出来作为一个通用组件包。
这个包是不作为一个微服务的,因此不需要启动类。

新建通用组件模块Consul简介

概念

Consul is a multi-networking tool that offers a fully-featured service mesh solution. It solves the networking and security challenges of operating microservices and cloud infrastructure in multi-cloud and hybrid cloud environments. This documentation describes Consul concepts, the problems it solves, and contains quick-start tutorials for using Consul.

简单来说,consul是一套开源的分布式服务发现配置管理系统,由 HashiCorp 公司用 Go 语言开发。
在SringCloud中提供了spring consul。
consul官网

consul的用途

  • 服务发现:提供HTTP和DNS两种发现方式。
  • 健康监测:支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控。
  • KV存储:Key、Value的存储方式。
  • 多数据中心:Consul支持多数据中心。
  • 可视化Web界面

consul的下载

consul的下载
根据windows32位操作系统选择386版本,64位操作系统选择AMD64版本

怎么使用consul

SpringCloud Consul文档
consul核心功能:

  • 服务发现
  • 配置管理

consul的安装配置

consul的下载
下载完成后,进入解压缩目录,命令行界面输入 consul -version 即可查看consul信息。
使用 consul agent -dev 启动consul,访问localhost:8500若能查看到如下则安装代表安装完成。

服务注册与发现

注册8001服务进consul

引入依赖

<!--SpringCloud consul discovery -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

修改yml

server:
  port: 8001
# ==========applicationName + druid-mysql8 driver===================
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    username: root
    password: 123456
# 关于consul的配置
  cloud:
    consul:
      host: localhost
      port: 8500
  # 指定服务的名称
      discovery:
        service-name: ${spring.application.name}
# ========================mybatis===================
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.cloud.entities
  configuration:
    map-underscore-to-camel-case: true

在主启动类上使用@EnableDiscoveryClient注解,启动服务发现功能

package com.atguigu.cloud;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@MapperScan("com.atguigu.cloud.mapper")
@EnableDiscoveryClient
public class Main8001 {
    public static void main(String[] args)
    {
        SpringApplication.run(Main8001.class);
    }
}

启动8001服务,在consul的ui界面就会发现8001服务已经注册进consul了

注册80服务进consul

引入依赖

<!--SpringCloud consul discovery -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

修改yml

server:
  port: 80
spring:
  application:
    name: cloud-consumer-order
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
        prefer-ip-address: true #优先使用服务ip进行注册


在主启动类上使用@EnableDiscoveryClient注解,启动服务发现功能

package com.atguigu.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class Main80 {
    public static void main(String[] args)
    {
        SpringApplication.run(Main80.class);
    }
}


修改controller
在之前的80消费微服务中,我们调用8001提供者微服务的url是采用硬编码格式,但8001已经注册进服务注册中心,因此使用服务注册中心的微服务名称代替硬编码即可,后续即使我们修改了端口,但微服务的名称是没有改变的,不需要我们每次都修改80中的url。

// 硬编码格式
    //private static final String PaymentSrv_URL="http://localhost:8001";
    //使用服务注册中心上的微服务名称
    private static final String PaymentSrv_URL="http://cloud-payment-service";

启动后发现consul的ui界面已经成功注册80消费微服务

开启负载均衡

已经把消费和提供服务注册,调用消费服务会报如下错误
Caused by: java.net.UnknownHostException: cloud-payment-service
在未注册到 Consul 之前,服务消费者通常通过硬编码的方式指定服务提供者的 URL(例如 http://192.168.1.100:8080),因此可以直接找到并访问固定的 IP 地址。
而在注册到 Consul 后,服务消费者通过服务名称(例如 cloud-payment-service)访问服务,注册中心返回的是服务实例的地址列表,而不是具体的单个 IP 地址。对于微服务集群,通常一个服务名称对应多个实例,因此仅通过服务名称无法确定具体调用哪个实例。
为了实现动态实例选择,Consul 提供了负载均衡支持。通过在服务消费者的 RestTemplate 上添加 @LoadBalanced 注解,启用客户端负载均衡功能,使其能够从服务实例列表中动态选择一个可用实例。

package com.atguigu.cloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
  // 使用@LoadBalanced注解
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

重新启动消费微服务测试后,可以正常调用提供微服务

consul与其它注册中心的异同

什么是CAP

C: Consistency (强一致性)
A: Availability (可用性)
P: Partition tolerance (分区容错性)

经典CAP图

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

常见三个注册中心的异同

组件名语言CAP服务健康检查对外暴露接口Spring Cloud 集成
EurekaJavaAP可配支持HTTP已集成
ConsulGoCP支持HTTP/DNS已集成
ZookeeperJavaCP支持客户端已集成

AP

当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性结论:违背了一致性C的要求,只满足可用性和分区容错,即AP

CP

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性,Consul 遵循CAP原理中的CP原则,保证了强一致性和分区容错性,且使用的是Raft算法,比zookeeper使用的Paxos算法更加简单。虽然保证了强一致性,但是可用性就相应下降了,例如服务注册的时间会稍长一些,因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 ;在leader挂掉了之后,重新选举出leader之前会导致Consul 服务不可用。结论:违背了可用性A的要求,只满足一致性和分区容错,即CP

服务的配置与刷新

分布式系统的配置问题

在分布式系统中,一个单体应用中的业务通常会被修改为很多子服务,系统中会存在大量的服务。
比如,许多子服务连接的是同一台电脑上的数据库,如果数据库进行了主机迁移,那么每一个子服务都需要修改一次配置文件,过于繁琐。
所以需要一套集中式,动态的配置管理设置。

SpringCloud Consul即支持服务注册发现也支持分布式配置

分布式服务配置

引入依赖

<!--SpringCloud consul config-->
        <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.yml

applicaiton.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更加高
bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml

Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment
Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap contextApplication Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap ContextApplication Context配置的分离。
application.yml文件改为bootstrap.yml,由bootstrap存储所有配置信息或者两者共存,application.yml存放单个微服务所需配置,bootstrap.yml村全局配置
因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml

使用consul作为服务注册中心的时候,微服务项目启动需要依赖于consul,因此要先启动consul再启动微服务项目,在启动微服务项目之前,consul会将通用的配置信息发送给给微服务项目,因此会出现两种配置,consul上的全局配置,微服务上的配置。显然consul上的系统级全局配置优先级更高。

在这里我们使用两者共存的方式,spring的配置放到bootstrap.yml中,而数据库,端口号等配置放在application.yml中
bootstrap.yml

spring:
  application:
    name: cloud-payment-service
    ####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
      config:
        profile-separator: '-' # 文件分隔符,使用 - 为分隔符,如下
        format: YAML

# config/cloud-payment-service/data
#       /cloud-payment-service-dev/data
#       /cloud-payment-service-prod/data

application.yml

server:
  port: 8001

# ==========applicationName + druid-mysql8 driver===================
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    username: root
    password: 123456
  profiles:
    active: dev # 多环境下激活环境,不写默认配置
# ========================mybatis===================
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.cloud.entities
  configuration:
    map-underscore-to-camel-case: true

consul服务器配置key/value配置填写

上面的官网配置规则已经给出,但官网给出的单词之间是以逗号分割的,由于我们在bootstrap.yml中配置了以 - 分割,所以命名规则可以是
cloud-payment-service-dev这种微服务名称-环境这种形式。
首先在consul的控制台的Key/Value界面创建 config文件夹

之后分别创建cloud-payment-service,cloud-payment-service-dev,cloud-payment-service-prod三个文件夹,分别代表不同环境下生效的配置文件夹,分别在三个文件夹中创建data文件
data文件即是我们放在consul上的配置文件,需要时yaml的格式。
例如,

atguigu:
  info: dev config

就可以在bootstrap.yml中获取到这个值

atguigu:
  info: ${atguigu.info}

测试

@Value("${atguigu.info}")
    private String infoByConsul;
    @GetMapping("/pay/get/info")
    private String getInfoByConsul(){
        System.out.println(infoByConsul);
        return infoByConsul;
    }

bootstrapt首先从consul中获取atguigu.info,然后使用@Value注入infoByConsul,打印infoByConsul
在控制台中可以看到dev config被正确打印出来,在application.yml中可以切换生效的环境,从而使用不同的配置。

动态刷新

consul上的配置更新,需要微服务也更新相关配置。
在主启动类上使用@RefreshScope开启动态刷新。
开启动态刷新后,默认会等待一会刷新,可以在bootstrapt中配置spring.cloud.sonsul.config.watch.wait-time设置刷新时间,以秒为单位

spring:
  application:
    name: cloud-payment-service
    ####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
      config:
        profile-separator: '-' # default value is ",",we update '-',否则就会是config/cloud,payment,service/data这种形式
        format: YAML
# 一秒刷新
        watch:
          wait-time: 1
atguigu:
  info: ${atguigu.info}

# config/cloud-payment-service/data
#       /cloud-payment-service-dev/data
#       /cloud-payment-service-prod/data

经过测试,我的刷新仍然没有生效。
因为@Value注解Spring容器启动的时候只会解析一次,但在方法中会每次动态解析。

@Value("${atguigu.info}")
    private String infoByConsul;
    @GetMapping("/pay/get/info")
    private String getInfoByConsul(@Value("${atguigu.info}") String info){
        System.out.println(info);
        System.out.println(infoByConsul);
        return infoByConsul;
    }

Consul的配置持久化

要想实现关闭windows或关机后consul的数据仍然存在,那么必须将consul的数据存储到一个文件夹中。

新建文件夹

在consul的解压目录新建 mydata文件夹

启动脚本

在consul的解压目录下新建 consul_start.bat
写入以下内容,脚本中的文件夹部分更改为自己的

@echo.服务启动......  
@echo off  
@sc create Consul binpath= "D:\consul\consul.exe agent -server -ui -bind=127.0.0.1 -client=0.0.0.0 -bootstrap-expect  1  -data-dir D:\consul\mydata   "
@net start Consul
@sc config Consul start= AUTO  
@echo.Consul start is OK......success
@pause

以管理员形式启动脚本
如果显示Consul服务已经存在,那么可以sc delete Consul删除服务后,重新管理员启动脚本即可

将其余微服务的通用组件复制粘贴到通用组件模块即可,引入相关依赖。
我这里主要有实体类,全局异常处理类,以及返回结果类

引入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.atguigu.cloud</groupId>
        <artifactId>cloud2024</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>cloud-api-commons</artifactId>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
    </dependencies>
</project>

打包

为了让其余微服务可以获取到此模块的通用组件,需要打包为jar包

删除消费和订单微服务中通用组件并改造pom

将消费和订单微服务中的订单和消费通用组件删除后,分别在两个微服务的pom文件中加入并刷新

<!-- 引入自己定义的api通用包 -->
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

启动微服务

启动消费和订单微服务,发现仍然可以正常启动
那么提取通用组件模块功能就完成了

动物装饰