Spring Cloud开发实践(六): 基于Consul和Spring Cloud 2021.0的演示项目 ...

打印 上一主题 下一主题

主题 872|帖子 872|积分 2616

目录

Consul 服务

启动Consul服务, 在Win10下可以执行以下命令, 或者存成bat文件运行, 保持窗口打开
  1. consul agent -dev -client=0.0.0.0 -data-dir .\ -advertise 127.0.0.1 -ui -config-dir .\
复制代码
浏览器访问 http://127.0.0.1:8500 , 用于观察后面注册的Node和Health情况
Spring Cloud 项目

这个演示项目使用的 Spring Boot 和 Spring Cloud 都不是最新版本, 因为最新版本最低要求 JDK17. 这里选择的是对应 JDK11 可用的最高版本, 各组件版本明细为

  • Consul 1.15
  • JDK 11
  • Spring Boot 2.7.11
  • Spring Cloud 2021.0.6
整体结构

这个用于演示的项目名称为 Dummy, 包含3个子模块, 分别是 dummy-common-api, dummy-common-impl 和 dummy-admin, 其中

  • dummy-common-api 和 dummy-common-impl 逻辑上属于同一个模块 dummy-common. api 是对外输出的接口, impl是对应的实现
  • dummy-admin 依赖 dummy-common-api , 使用其提供的接口
打包后, 需要部署的是两个jar: dummy-common.jar 和 dummy-admin.jar, 前者提供服务接口, 后者消费前者提供的接口, 并对外(例如前端, 小程序, APP)提供接口
项目的整体结构如下
  1. │   pom.xml
  2. ├───dummy-admin
  3. │   │   pom.xml
  4. │   ├───src
  5. │   │   ├───main
  6. │   │   │   ├───java
  7. │   │   │   └───resources
  8. │   │   │           application.yml
  9. │   │   └───test
  10. │   └───target
  11. ├───dummy-common-api
  12. │   │   pom.xml
  13. │   ├───src
  14. │   │   ├───main
  15. │   │   │   ├───java
  16. │   │   │   └───resources
  17. │   │   └───test
  18. │   └───target
  19. └───dummy-common-impl
  20.     │   pom.xml
  21.     ├───src
  22.     │   ├───main
  23.     │   │   ├───java
  24.     │   │   └───resources
  25.     │   │           application.yml
  26.     │   └───test
  27.     └───target
复制代码
根模块 Dummy

根模块的 pom.xml 中,

  • 定义了子模块, module标签中的内容, 要和子模块目录名一致.
  • 设置JDK版本 11
  • 引入全局 Spring Boot Dependencies, 版本 2.7.11
  • 引入全局 Spring Cloud Dependencies, 版本 2021.0.6
  • 还有一些是Plugin相关的版本, 略
  1. <?xml version="1.0" encoding="UTF-8"?>
  2.     ...
  3.     <name>Dummy: Root</name>
  4.     <modules>
  5.         <module>dummy-common-api</module>
  6.         <module>dummy-common-impl</module>
  7.         <module>dummy-admin</module>
  8.     </modules>
  9.     <properties>
  10.         
  11.         <project.jdk.version>11</project.jdk.version>
  12.         <project.source.encoding>UTF-8</project.source.encoding>
  13.         
  14.         <spring-boot.version>2.7.11</spring-boot.version>
  15.         <spring-cloud.version>2021.0.6</spring-cloud.version>
  16.     </properties>
  17.     <dependencyManagement>
  18.         <dependencies>
  19.             
  20.             <dependency>
  21.                 <groupId>org.springframework.boot</groupId>
  22.                 <artifactId>spring-boot-dependencies</artifactId>
  23.                 <version>${spring-boot.version}</version>
  24.                 <type>pom</type>
  25.                 <scope>import</scope>
  26.             </dependency>
  27.             
  28.             <dependency>
  29.                 <groupId>org.springframework.cloud</groupId>
  30.                 <artifactId>spring-cloud-dependencies</artifactId>
  31.                 <version>${spring-cloud.version}</version>
  32.                 <type>pom</type>
  33.                 <scope>import</scope>
  34.             </dependency>
  35.         </dependencies>
  36.     </dependencyManagement>
  37.     <build>
  38.         ...
  39.     </build>
  40. </project>
复制代码
Dummy Common API 模块

这个模块用于生成依赖的jar包, 作用非常重要. 以下详细说明
pom.xml 中除了定义和父模块的关系, 需要引入 openfeign
  1. <?xml version="1.0" encoding="UTF-8"?>
  2.     ...
  3.     <parent>
  4.         <groupId>com.rockbb.test</groupId>
  5.         <artifactId>dummy</artifactId>
  6.         <version>1.0-SNAPSHOT</version>
  7.         <relativePath>../pom.xml</relativePath>
  8.     </parent>
  9.     <artifactId>dummy-common-api</artifactId>
  10.     <packaging>jar</packaging>
  11.     <version>1.0-SNAPSHOT</version>
  12.     <name>Dummy: Commons API</name>
  13.     <dependencies>
  14.         <dependency>
  15.             <groupId>org.springframework.cloud</groupId>
  16.             <artifactId>spring-cloud-starter-openfeign</artifactId>
  17.         </dependency>
  18.         ...
  19.     </dependencies>
  20.     <build>
  21.         ...
  22.     </build>
  23. </project>
复制代码
定义一个 UserDTO, 这个是用于传输的数据对象
  1. @Data
  2. public class UserDTO implements Serializable {
  3.     private Long id;
  4.     private String name;
  5. }
复制代码
对应的服务接口. 这里用到了 @FeignClient 注解

  • @FeignClient 是给 dummy-admin 模块用的

    • name= CommonConstant.SERVICE_NAME 就是 "dummy-common", 因为这个API模块中所有Service接口都使用同样的名称, 这边做成常量
    • contextId = "userDTOService" 如果不加这个参数, 多个 FeignClient 使用同样的 name 时, 就会冲突. 这个一般直接定义为这个 service 的bean名称
    • path = "/userDTOService" 用于指定当前类中所有接口的请求前缀. 在更早的版本中, 可以将 @RequestMapping 和 @FeignClient 联用, 这个是定义在 @RequestMapping 中的, 后来不允许了, 因为有安全风险.

  • @GetMapping 和 @PostMapping 同时用于 dummy-admin 和 dummy-common

    • 对于 dummy-admin, 这就是 FeignClient 的请求路径
    • 对于 dummy-common, 这就是 Contoller 方法的服务路径
    • 需要注意 @GetMapping 请求的接口形式, 必须显式添加 @RequestParam("id") 这类 GET 模式的参数注解, 否则使用 @GetMapping 的 Feign 请求也会被转为 POST 而导致请求错误.

  1. @FeignClient(name = CommonConstant.SERVICE_NAME, contextId = "userDTOService", path = "/userDTOService")
  2. public interface UserDTOService {
  3.     @GetMapping("/get")
  4.     UserDTO get(@RequestParam("id") long id);
  5.     @PostMapping("/add")
  6.     int add(@RequestBody UserDTO dto);
  7. }
复制代码
在 dummy-admin 中, 这个接口会被实例化为 feign 代理, 在模块中可以像普通 service 一样调用, 而在 dummy-common 中, 不引入 feign 依赖, 或者在 @EnableFeignClients 的 basePackages 中避开本包路径, 就会忽略这个注解, 从而实现模块间接口的关联.
与现在很多 Spring Cloud 项目中单独拆出一个 Service 模块的做法, 这种实现有很多的优点

  • 开发过程友好. 与单机开发几乎一样的代码量, 唯一区别是要注意 Get 和 Post 对请求参数的格式和个数的约束
  • 易重构易扩展. 可以借助 IDE 的代码分析能力, 改动自动标红, 避免人为错误和遗漏
  • 性能开销小, 如果 DTO 直接映射到数据库字段, 可以全程使用一个类.
Dummy Common Impl 模块

模块的 pom.xml

  • 引入 spring-boot-starter-web, 因为要提供 RestController 的能力
  • 引入 spring-cloud-starter-consul-discovery 或 spring-cloud-starter-consul-all, 因为要接 Consul
  • 引入 dummy-common-api 依赖, 因为 Controller 请求定义在 API 中
  • 打包使用 spring-boot-maven-plugin 的 repackage, 因为要打 fat jar, 在服务器上实现单包部署
  1. <?xml version="1.0" encoding="UTF-8"?>
  2.     ...
  3.     <name>Dummy: Common Implementation</name>
  4.     <dependencies>
  5.         
  6.         <dependency>
  7.             <groupId>org.springframework.boot</groupId>
  8.             <artifactId>spring-boot-starter-web</artifactId>
  9.         </dependency>
  10.         <dependency>
  11.             <groupId>org.springframework.boot</groupId>
  12.             <artifactId>spring-boot-starter-test</artifactId>
  13.             <scope>test</scope>
  14.         </dependency>
  15.         
  16.         <dependency>
  17.             <groupId>org.springframework.cloud</groupId>
  18.             <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  19.         </dependency>
  20.         <dependency>
  21.             <groupId>org.springframework.cloud</groupId>
  22.             <artifactId>spring-cloud-starter-consul-all</artifactId>
  23.         </dependency>
  24.         ...
  25.         <dependency>
  26.             <groupId>com.rockbb.test</groupId>
  27.             <artifactId>dummy-common-api</artifactId>
  28.             <version>${project.version}</version>
  29.         </dependency>
  30.     </dependencies>
  31.     <build>
  32.         <finalName>dummy-common</finalName>
  33.         <resources>
  34.             ...
  35.         </resources>
  36.         <plugins>
  37.             ...
  38.             <plugin>
  39.                 <groupId>org.springframework.boot</groupId>
  40.                 <artifactId>spring-boot-maven-plugin</artifactId>
  41.                 <executions>
  42.                     <execution>
  43.                         <goals>
  44.                             <goal>repackage</goal>
  45.                         </goals>
  46.                     </execution>
  47.                 </executions>
  48.             </plugin>
  49.         </plugins>
  50.     </build>
  51. </project>
复制代码
配置部分 application.yml

  • 定义服务端口 8762
  • 定义 servlet 路径, 必须定义, 否则不会配置 Controller 请求
  • spring.application.name: dummy-common 定义了本服务的名称, 这个名称就是在 FeignClient 中引用的服务名称, 需要与 FeignClient 中的值一致
  • spring.config.import  如果使用这个设置, 依赖要使用 consul-all, 因为 consul-discovery 中不带 consul-config. 使用这个设置后, 会自动使用默认的 Consul 地址和端口
  • cloud.consul.host 和 port 如果使用了config.import, 在这里可以修改默认的值, 如果不使用config.import, 则必须配置 host 和 port, 依赖可以换成 consul-discovery
  • cloud.consul.discovery.health-check-path 用于更改默认的 health 检查请求路径, 默认的是 /actuator/health, 这里改为 /health
  • cloud.consul.discovery.instance-id 用于定义当前实例在 Consul 里的实例ID. 默认使用 application.name-port, 如果正好这个服务在两个服务器上分别跑了一个实例, 且实例端口一样, 就会产生冲突, 可以改为 application.name-[随机串] 的形式避免冲突
  1. server:
  2.   port: 8762
  3.   tomcat:
  4.     uri-encoding: UTF-8
  5.   servlet:
  6.     context-path: /
  7. spring:
  8.   application:
  9.     name: dummy-common
  10.   config:
  11.     import: 'optional:consul:' #This will connect to the Consul Agent at the default location of "http://localhost:8500"
  12. #  cloud:
  13. #    consul:
  14. #      host: 127.0.0.1
  15. #      port: 8500
  16. #      discovery:
  17. #        health-check-path: /health # replace the default /actuator/health
  18. #        instance-id: ${spring.application.name}:${random.value}
复制代码
代码部分, 首先是实现 health 检查的处理方法, 这部分是普通的 RestController 方法. 返回字符串可以任意指定, 只要返回的 code 是 200 就可以
  1. @RestController
  2. public class HealthCheckServiceImpl {
  3.     @GetMapping("/health")
  4.     public String get() {
  5.         return "SUCCESS";
  6.     }
  7. }
复制代码
服务接口的实现类, 这里实现了两个接口方法 get 和 add

  • 使用 @RestController 注解, 与 API Service 中方法上的 @GetMapping 和 @PostMapping 配合, 将 Service 方法映射为 Controller 方法
  • 在类上的 @RequestMapping("userDTOService") 方法是必须的, 因为在 API Service 中与 @FeignClient 冲突无法定义, 只能在这里定义
  • 方法和参数上除了 @Override 不需要任何注解, 因为都在 API Service 上定义过了. 这里加上注解也没问题, 但是要手工保持一致.
  1. @RestController
  2. @RequestMapping("userDTOService")
  3. public class UserDTOServiceImpl implements UserDTOService {
  4.     @Autowired
  5.     private UserRepo userRepo;
  6.     @Override
  7.     public UserDTO get(long id) {
  8.         log.debug("Get user: {}", id);
  9.         UserDTO user = new UserDTO();
  10.         user.setId(id);
  11.         user.setName("dummy");
  12.         return user;
  13.     }
  14.     @Override
  15.     public int add(UserDTO dto) {
  16.         log.debug("Add user: {}", dto.getName());
  17.         return 0;
  18.     }
  19. }
复制代码
dummy-common 模块运行后会将接口注册到 Consul, 启动后注意观察两部分:

  • Consul 的日志输出和控制面板显示, 在-dev模式下, 节点注册后 Consul 日志会显示模块的名称和心跳检测记录, 面板上会显示新的 Node
  • Consul 控制面板中显示的 Health Checks 是否正常, 如果不正常, 需要检查 /health 路径为什么访问失败
Dummy Admin 模块

dummy-admin 是调用接口, 并对外提供服务的模块
pom.xml 和 dummy-common 基本一样, 因为都要连接 Consul, 都要提供 Controller 方法
  1. <?xml version="1.0" encoding="UTF-8"?>
  2.     ...
  3.     <name>Dummy: Admin API</name>
  4.     <dependencies>
  5.         <dependency>
  6.             <groupId>org.springframework.boot</groupId>
  7.             <artifactId>spring-boot-starter-web</artifactId>
  8.         </dependency>
  9.         <dependency>
  10.             <groupId>org.springframework.boot</groupId>
  11.             <artifactId>spring-boot-starter-test</artifactId>
  12.             <scope>test</scope>
  13.         </dependency>
  14.         <dependency>
  15.             <groupId>org.springframework.cloud</groupId>
  16.             <artifactId>spring-cloud-starter-consul-discovery</artifactId>
  17.         </dependency>
  18.         <dependency>
  19.             <groupId>com.rockbb.test</groupId>
  20.             <artifactId>dummy-common-api</artifactId>
  21.             <version>${project.version}</version>
  22.         </dependency>
  23.     </dependencies>
  24.     <build>
  25.         <finalName>dummy-admin</finalName>
  26.         <resources>
  27.            ...
  28.         </resources>
  29.         <plugins>
  30.             <plugin>
  31.                 <groupId>org.springframework.boot</groupId>
  32.                 <artifactId>spring-boot-maven-plugin</artifactId>
  33.                 <executions>
  34.                     <execution>
  35.                         <goals>
  36.                             <goal>repackage</goal>
  37.                         </goals>
  38.                     </execution>
  39.                 </executions>
  40.             </plugin>
  41.             ...
  42.         </plugins>
  43.     </build>
  44. </project>
复制代码
在主应用入口, 除了 @SpringBootApplication 以外, 还需要增加两个注解

  • @EnableDiscoveryClient(autoRegister=false) 连接到 Consul 并使用服务发现, 默认会将当前节点也注册到 Consul 作为服务. 对于纯消费节点, 不对其它节点提供接口的, 使用 autoRegister=false 可以避免将自己注册到 Consul
  • @EnableFeignClients(basePackages = {"com.rockbb.test.dummy.common.api"}) 扫描对应的包, 对 @FeignClient 注解实例化接口代理
  1. /* Attach to discovery service without registering itself */
  2. @EnableDiscoveryClient(autoRegister=false)
  3. @EnableFeignClients(basePackages = {"com.rockbb.test.dummy.common.api"})
  4. @SpringBootApplication
  5. public class AdminApp {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(AdminApp.class, args);
  8.     }
  9. }
复制代码
在调用方法的地方, 按普通 Service 注入和调用
  1. @Slf4j
  2. @RestController
  3. public class IndexController {
  4.     @Autowired
  5.     private UserDTOService userDTOService;
  6.     @GetMapping(value = "/user_get")
  7.     public String doGetUser() {
  8.         UserDTO user = userDTOService.get(100L);
  9.         return user.getId() + ":" + user.getName();
  10.     }
  11.     @GetMapping(value = "/user_add")
  12.     public String doAddUser() {
  13.         UserDTO user = new UserDTO();
  14.         user.setName("foobar");
  15.         int result = userDTOService.add(user);
  16.         return String.valueOf(result);
  17.     }
复制代码
可以通过注入的 DiscoveryClient 对象, 查看对应服务的服务地址(一般不需要)
  1. @Autowired
  2. private DiscoveryClient discoveryClient;
  3. @GetMapping("/services")
  4. public Optional<URI> serviceURL() {
  5.     return discoveryClient.getInstances(CommonConstant.SERVICE_NAME)
  6.             .stream()
  7.             .map(ServiceInstance::getUri)
  8.             .findFirst();
  9. }
复制代码
参考


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表