Spring Cloud Alibaba——Nacos实现服务治理

淩亂°似流年 2022-12-08 15:39 289阅读 0赞

引言

本博客总结微服务开发中各个微服务调用的实现,并使用 Nacos 完成服务注册和发现。

文章中会涉及到 maven 的使用,以及 spring boot 的一些知识。开发工具采用 IDEA 2020.2。

设计一个电商订单和商品购买微服务,实现微服务的注册发现与调用。

一、模块设计

本案例采用电商网站作为展示,涉及到的三个微服务有:shop-user、shop-product、shop-order,还有一个公共依赖模块shop-common。他们的依赖、调用关系如下所示:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70

shop-user 是用户微服务,端口是807x

shop-product 是商品微服务,端口是808x

shop-order 是订单微服务,端口是809x

三个微服务之间可以通过HTTP请求相互调用业务逻辑。

二、创建Maven父工程

为了便于依赖的管理,和项目维护,在实际生产中,往往通过父工程来管理各个 maven 微服务模块,和maven 依赖模块。

(在这里我需要简单说明一下这个大的maven 工程下面,如何理解各个子模块的关系。案例中包含了三个微服务(shop-user/shop-product/shop-order),和一个公共依赖模块(shop-common),它们都会作为一个 maven 子模块存放到父工程目录下,但实际上,在实际部署的时候,三个微服务是分开部署的,因为三个微服务之间的关系,除了通过父工程来统一维护一些依赖版本之外,没有什么在代码层面的耦合关系。而公共依赖模块则在代码层面耦合到各个模块中,部署之后,也是你中有我的概念。)

首先 New ——> Project ——> Maven ,选择好JDK 版本后,直接Next,跳过 archetype 选项。

填写必要的项目名称和存储位置,maven坐标等信息,点击finish:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 1

idea可以快速为我们创建并打开新项目,由于 Maven 父工程只做版本管理,不需要写任何代码,因此一般都会直接删除 src 目录:

20200920173923870.png

紧接着,我们需要修改父工程 pom 文件。它主要需要负责两件事:1、指定父工程 2、依赖版本的锁定

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>org.morty</groupId>
  7. <artifactId>shop</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <!-- 指定父工程-->
  10. <parent>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-parent</artifactId>
  13. <version>2.1.5.RELEASE</version>
  14. </parent>
  15. <properties>
  16. <java.version>1.8</java.version>
  17. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18. </properties>
  19. <!-- 版本锁定-->
  20. <dependencyManagement>
  21. <dependencies>
  22. <dependency>
  23. <groupId>org.springframework.cloud</groupId>
  24. <artifactId>spring-cloud-dependencies</artifactId>
  25. <version>Greenwich.SR5</version>
  26. <type>pom</type>
  27. <scope>import</scope>
  28. </dependency>
  29. <dependency>
  30. <groupId>com.alibaba.cloud</groupId>
  31. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  32. <version>2.1.1.RELEASE</version>
  33. <type>pom</type>
  34. <scope>import</scope>
  35. </dependency>
  36. </dependencies>
  37. </dependencyManagement>
  38. </project>

下表展示了 Spring Cloud Alibaba & Spring Cloud & Spring Boot 兼容关系:




































Spring Cloud Version Spring Cloud Alibaba Version Spring Boot Version
———- ———- ———-
Spring Cloud Hoxton 2.2.x.RELEASE 2.2.x.RELEASE
Spring Cloud Greenwich 2.1.x.RELEASE 2.1.x.RELEASE
Spring Cloud Finchley 2.0.x.RELEASE 2.0.x.RELEASE
Spring Cloud Edgware 1.5.x.RELEASE 1.5.x.RELEASE

三、创建基础依赖模块

new——>Module…

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 2

添加必要的依赖:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>shop</artifactId>
  7. <groupId>org.morty</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>shop-common</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-data-jpa</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.projectlombok</groupId>
  19. <artifactId>lombok</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>com.alibaba</groupId>
  23. <artifactId>fastjson</artifactId>
  24. <version>1.2.58</version>
  25. </dependency>
  26. <dependency>
  27. <groupId>mysql</groupId>
  28. <artifactId>mysql-connector-java</artifactId>
  29. </dependency>
  30. </dependencies>
  31. </project>

创建domain实体类,User、Product、Order,这样,其他三个微服务可以依赖使用:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 3

  1. package com.morty.domain;
  2. import lombok.Data;
  3. import javax.persistence.Entity;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.GenerationType;
  6. import javax.persistence.Id;
  7. @Entity(name = "shop_user")
  8. @Data
  9. public class User {
  10. @Id
  11. // 数据库自增
  12. @GeneratedValue(strategy = GenerationType.IDENTITY)
  13. private Integer uid;
  14. private String username;
  15. private String password;
  16. private String telephone;
  17. }
  18. package com.morty.domain;
  19. import lombok.Data;
  20. import javax.persistence.Entity;
  21. import javax.persistence.GeneratedValue;
  22. import javax.persistence.GenerationType;
  23. import javax.persistence.Id;
  24. @Data
  25. @Entity(name = "shop_product")
  26. public class Product {
  27. @Id
  28. @GeneratedValue(strategy = GenerationType.IDENTITY)
  29. private Integer pid;
  30. private String pname;
  31. // 商品价格
  32. private Double pprice;
  33. // 库存
  34. private Integer stock;
  35. }
  36. package com.morty.domain;
  37. import lombok.Data;
  38. import javax.persistence.Entity;
  39. import javax.persistence.GeneratedValue;
  40. import javax.persistence.GenerationType;
  41. import javax.persistence.Id;
  42. @Data
  43. @Entity(name = "shop_order")
  44. public class Order {
  45. @Id
  46. @GeneratedValue(strategy = GenerationType.IDENTITY)
  47. private Integer oid;
  48. private Integer uid;
  49. private String username;
  50. private Integer pid;
  51. private String pname;
  52. private Double pprice;
  53. /** 购买数量*/
  54. private Integer number;
  55. }

四、创建微服务模块

依次创建shop-user、shop-product、shop-order 三个微服务,并依赖 shop-common。篇幅有限,以 shop-product 为例。

1、和shop-common的创建方式一样,新建一个 Module,并命名 shop-product,修改pom文件,添加 shop-common依赖和 web starter:

  1. <dependencies>
  2. <!-- 依赖基础模块-->
  3. <dependency>
  4. <groupId>org.morty</groupId>
  5. <artifactId>shop-common</artifactId>
  6. <version>1.0-SNAPSHOT</version>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-web</artifactId>
  11. </dependency>
  12. </dependencies>

2、创建spring boot 启动类:

  1. @SpringBootApplication
  2. @Slf4j
  3. public class ProductApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(ProductApplication.class);
  6. log.info("-----------启动成功------------");
  7. }
  8. }

3、修改配置文件:

  1. server:
  2. port: 8081
  3. spring:
  4. application:
  5. name: service-product
  6. datasource:
  7. driver-class-name: com.mysql.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/shop?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
  9. username: root
  10. password: 123456
  11. jpa:
  12. properties:
  13. hibernate:
  14. hbm2ddl:
  15. auto: update
  16. # InnoDB方言
  17. dialect: org.hibernate.dialect.MySQL5InnoDBDialect

然后就是创建 controller、service、dao:

  1. @Slf4j
  2. @RestController
  3. @RequestMapping("/product")
  4. public class ProductController {
  5. @Autowired
  6. private ProductService productService;
  7. /**
  8. * 查询订单信息
  9. * @param pid
  10. * @return
  11. */
  12. @GetMapping("/{pid}")
  13. public Product getProduct(@PathVariable("pid") Integer pid) {
  14. log.info("收到查询商品信息请求,商品编号:{}", pid);
  15. Product product = productService.getProduct(pid);
  16. log.info("商品信息查询成功:{}", JSON.toJSONString(product));
  17. return product;
  18. }
  19. }
  20. @Service
  21. public class ProductService {
  22. @Autowired
  23. private ProductDao productDao;
  24. public Product getProduct(Integer productId) {
  25. return productDao.findById(productId).get();
  26. }
  27. }
  28. public interface ProductDao extends JpaRepository<Product, Integer> {
  29. }

最后,手动创建 shop 数据库,然后启动服务,可以看到表已经自动创建好了,向 shop_product 表插入一条商品信息:

  1. INSERT INTO `shop_product`(pname, pprice, stock) VALUES('皮大衣', '120', '20');

打开浏览器,访问接口,可以正常返回:

20200920203450598.png

五、微服务调用

按照类似的步骤创建好了三个微服务之后,我们来实现订单到商品的微服务调用。需要说明的是,任何两个服务之间都是可以通过http请求进行调用,而不完全需要服务治理功能,也就是说,如果我们指定了ip和端口号,实际上就可以实现微服务的调用。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 4

为了演示方便,这里只列出关键代码,并去掉了Service的接口层。

提供必要的 restTemplate:

  1. @Bean
  2. public RestTemplate restTemplate() {
  3. return new RestTemplate();
  4. }

DAO:

  1. public interface OrderDao extends JpaRepository<Order, Integer> {
  2. }

Service:

  1. @Service
  2. public class OrderService {
  3. @Autowired
  4. private OrderDao orderDao;
  5. public void createOrder(Order order) {
  6. orderDao.save(order);
  7. }
  8. }

Controller:

  1. @Slf4j
  2. @RestController
  3. @RequestMapping("/order")
  4. public class OrderController {
  5. @Autowired
  6. private RestTemplate restTemplate;
  7. @Autowired
  8. private OrderService orderService;
  9. /***
  10. * 下单
  11. * @param pid
  12. * @return
  13. */
  14. @GetMapping("/prod/{pid}")
  15. public Order order(@PathVariable("pid") Integer pid) {
  16. log.info("接收到{}号商品的下单请求,准备调用商品微服务", pid);
  17. // 调用商品微服务,查询商品信息
  18. Product prod = restTemplate.getForObject("http://localhost:8081/product/" + pid, Product.class);
  19. log.info("查询到{}号商品信息,内容是:{}", pid, JSON.toJSONString(prod));
  20. // 下单(即创建订单并保存)
  21. Order order = new Order();
  22. order.setUid(1);
  23. order.setUsername("测试用户");
  24. order.setPid(pid);
  25. order.setPname(prod.getPname());
  26. order.setPprice(prod.getPprice());
  27. order.setNumber(1);
  28. // 订单入库
  29. orderService.createOrder(order);
  30. log.info("创建订单成功,订单信息为:{}", JSON.toJSONString(order));
  31. return order;
  32. }
  33. }

然后在配置文件中指定 8091 端口号,以及数据库地址等必要信息。启动 OrderApplication 和 ProductApplication,调用 /order/prod/{pid} 接口:

2020092619282785.png

检查控制台打印的日志:

订单微服务:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 5

商品微服务:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 6

同时数据库也出现了刚才添加的订单记录:

20200926193119365.png

六、服务治理

在前面的微服务调用案例中,我们通过 restTemplate 对象,配合 ip + port 的形式,实现了最简单的订单微服务到商品微服务的调用逻辑。

但这在实际生产中会存在较大的问题:

1、一旦服务提供者的地址发生变化,就不得不去修改服务调用者的代码,即便是使用配置文件也治标不治本。

2、在高并发场景中,服务一般需要进行集群部署,会有多个服务提供者实例。那么就需要通过负载均衡调用不同的服务提供者,来分散单个服务实例的访问压力,上面这种调用方式显然无法满足负载均衡的要求。

3、一旦微服务变得越来越多,如果管理服务清单将会是一个大问题。

基于以上几点,就有了服务治理的概念:

服务治理是微服务架构中最核心、最基本的模块。用于实现各个微服务的自动化注册和发现。

服务注册:在服务治理框架中,都会构建一个注册中心。每个服务单元向注册中心登记自己提供服务的详细信息。注册中心会基于这些微服务的详细信息,生成一张服务清单。注册中心需要以心跳的方式检测清单中服务是否可用,如果发现心跳异常的服务,会从服务清单中剔除不可用的服务。

服务发现:服务消费者向注册中心咨询服务,并获取所有服务的实例清单,实现对具体服务的访问。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 7

常用的服务治理框架有:

ZooKeeper:是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要用来解决分布式应用中经常遇到的一些数据管理问题,如统一命名服务、状态同步服务、集群管理、分布式应用配置项管理等。

Eureka:是Spring Cloud Netfix 中的重要组件,主要作用是做服务注册和发现,但现在已经闭源。

Nacos:是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 Spring cloud Alibaba 的组件之一,负责服务注册发现和服务配置,可以认为是 Eureka + Config 的组合升级版服务治理框架。

七、Nacos-discovery 实现微服务调用

7.1 启动 Nacos 服务

首先,如果想使用 Nacos 注册中心服务,必须到官网上下载启动压缩包。值得一提的是,原来的 Eureka 是通过 Spring boot 构建一个专门用于实现注册发现的微服务,这需要我们手动去构建这样一个重要的架构组件,但是 Nacos 则提供了独立的启动程序,让开发者可以开箱即用,进一步提高了微服务部署的效率。

nacos 下载地址:https://nacos.io/zh-cn/docs/quick-start.html

不论你是在 Windows 环境上学习和练习,还是在 Linux 服务器上安装部署,都只需要简单的一键启动即可。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 8

启动成功后,我们通过浏览器访问控制台,默认用户名和密码都是 nacos,下图登录成功后进入首页:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 9

7.2 将微服务注册到 Nacos

以 shop-product 为例,演示如何将微服务注册到 Nacos。

1、微服务中添加 nacos 客户端依赖:

  1. <!-- nacos 客户端-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
  5. </dependency>

2、为主类添加@EnableDiscoveryClient 注解:

  1. @Slf4j
  2. @EnableDiscoveryClient
  3. @SpringBootApplication
  4. public class ProductApplication {
  5. public static void main(String[] args) {
  6. SpringApplication.run(ProductApplication.class);
  7. log.info("-----------启动成功------------");
  8. }
  9. }

3、配置 Nacos Server 地址:

  1. spring:
  2. cloud:
  3. nacos:
  4. discovery:
  5. server-addr: localhost:8848

4、启动微服务,查看 Nacos 控制台:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 10

7.3 Nacos 实现微服务调用

针对前面第五节订单到商品的微服务调用方式,我们调整一下restTemplate 代码,以服务治理推荐的方式来实现微服务调用。

引入服务发现客户端对象:

  1. @Autowired
  2. private DiscoveryClient discoveryClient;

修改代码:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 11

启动 shop-order 、shop-product 微服务,它们会自动注册到 Nacos 中。

20200926203820350.png

重新调用下单接口,可以看到接口依然调用成功:

20200926203958359.png

20200926203930188.png

总结

微服务注册中心的主要功能是负责服务注册发现,它会生成一张注册服务清单,可以简单理解为一个服务名称和对应服务地址的对照表,服务消费者使用服务名称调用服务提供者的接口时,会直接发送到对应地址:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ3NDUwNjk_size_16_color_FFFFFF_t_70 12

微服务如果想要注册到 Nacos Server,需要完成三件事:

1、添加 nacos-discovery 依赖

2、启动服务发现客户端,即添加 @EnableDiscoveryClient 注解到主类

3、配置 Nacos server 注册中心地址和端口号

发表评论

表情:
评论列表 (有 0 条评论,289人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring Cloud Eureka(服务治理)(2)

    1.服务发现与消费 下面来尝试构建一个服务消费者,它主要完成两个目标,发现服务以及消费服务。其中服务发现的任务由Eureka的客户端完成,而服务消费者的任务由Ribbon