• springcloud之项目实战服务治理


    写在前面

    这篇文章 我们已经搭建完成了优惠券模块的单体版本,为了向微服务化迈出坚实的一步,这部分来看下服务治理的内容,并在我们的项目中引入服务治理,下面我们就开始吧!

    源码

    1:什么是,为什么需要服务治理?

    假定现在有服务A和服务B,并且服务A和服务B都有3个节点,因此现在有一个3节点的服务A集群和一个3节点的服务B集群,通常的做法是在外层挂一层VIP,假定服务A要调用服务B,则过程如下图:

    在这里插入图片描述

    我们只需要将服务B的服务列表维护在VIP层就行了,但注意这个过程是手动的,因此这种方式就可能存在如下的问题:

    1:需要手动维护服务器列表VIP pool中,如果是集群很大的话,这将会是一个很大的工作量
    2:新的机器上线和下线,需要手动更新VIP pool,且在此期间不可用机器还能继续处理请求(时间比较长,从人工发现,到人工替换完成)
    
    • 1
    • 2

    可以看到上述的问题的根源都是手动维护,如果我们能将手动维护改为自动维护,不管是集群中的新机器(服务注册),还是机器故障(服务剔除),都能够自动发现,这些工作在微服务环境下我们叫做服务治理。为了实现这些需求,我们需要引入一个新的三方组件(注册中心),由它来负责维护这些信息,服务的提供方从将自己的信息注册在这里(服务注册),服务的消费方从这里获取可用的服务列表(服务发现),可以参考下图:

    然后注册中心通过心跳机制来检测服务提供方的存活情况(“服务探活”或“心跳检查”),发现服务不可用则可以执行服务剔除,然后将这个变化告知服务消费者,这样请求就不会访问到不可用服务了(以为服务探活是有时间间隔的,所以还是会有访问到不可用服务的情况,但是时间相比于手动方式会短很多),参考下图:
    在这里插入图片描述

    nacos就是一个具体的注册中心中间件,接下来一起看下。

    2:nacos分析

    2.1:领域模型

    在这里插入图片描述

    如上图所以就展示了领域模型中三个组成,服务,集群,实例,如下:

    • 服务
      在服务这个层级上我们可以配置元数据和服务保护阈值等信息。服务阈值是一个 0~1 之间的数字,当服务的健康实例数与总实例的比例小于这个阈值的时候,说明能提供服务的机器已经没多少了。这时候 Nacos 会开启服务保护模式,不再主动剔除服务实例,同时还会将不健康的实例也返回给消费者。尽管这样做可能造成请求失败,但间接保证了最低限度的服务可用性。
    • 集群
      一个服务由很多服务实例组成,在每个服务实例启动的时候,我们可以设置它所属的集群,在集群这个层级上,我们也可以配置元数据。除此之外,我们还可以为持久化节点设置健康检查模式。所谓持久化节点,是一种会保存到 Nacos 服务端的实例,即便该实例的客户端进程没有在运行,实例也不会被服务端删除,只不过 Nacos 会将这个持久化节点状态标记为不健康,Nacos 可以采用一种“主动探活”的方式来对持久化节点做健康检查。除了持久化节点以外,大部分服务节点在 Nacos 中以“临时节点”的方式存在,它是默认的服务注册方式,从名字中我们就可以看出,这种节点不会被持久化保存在 Nacos 服务器,临时节点通过主动发送 heartbeat 请求向服务器报送自己的状态。
    • 实例
      对应的就是我们的应用实例。即一个进程。在nacos的控制界面我们查看每个实例的IP,端口等信息,并且可以设置其上线,下线状态,权重等。

    可以发现服务,集群,实例都是可以设置元数据的,这样我们就可以在不同的纬度来设置信息,client端根据这些元数据信息就可以执行不同的操作(通过IP,端口调用到服务,通过权重来动态设置负载均衡策略)

    2.2:数据模型

    数据模型分为三个层次,namespace,group,servieId/dataId,是包含的关系,可以参考下图:
    在这里插入图片描述

    namepsace:一般用来区分环境,如本地环境,测试环境,生产环境等,默认部署到叫public的公共命名空间中
    group:namepspace下的概念,默认是default-group分组,不同分组的微服务是相互隔离的
    serviceId/dataId:就是具体的微服务了,如订单服务,用户服务,支付服务等
    
    • 1
    • 2
    • 3

    更具体的可以参考下图:
    在这里插入图片描述

    可以有很多的namespace,每个namespace下有若干个group,每个group有若干个serviceId/dataId。通过三者的组合我们就能定位到一个具体的微服务了,比如production(namepsace)+mainGroup(group)+orderService(serivceId)就可以定位到生产环境下的main组下的订单微服务了。

    2.3:架构

    架构图如下:
    在这里插入图片描述
    各组件说明如下:

    provider app:服务提供者
    consumer app:服务消费者
    openapi:nacos提供rest接口,用户增删改查服务信息
    config service:提供配置中心的功能
    naming service:提供注册中心的功能(本部分析的)
    nacos core:提供nacos的核心功能,如启动模式,数据存储等
    consistency protocol:处理naco集群模式下的数据一致性问题
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    关于openapi详细可以参考这里 。另外nacos-core提供的功能如下图:
    在这里插入图片描述

    3:nacos环境搭建

    人的一生由很多部分组成,健康的身体,美满幸福的家庭,无话不谈的好友,花之不尽的财富(做梦),但,除了健康的身体是一串数字的1之外,其他的都是0,即有了最好,没有也无所谓。同理,一个系统架构也由很多分布构成,而这里的1是高可用性,要实现高可用性基本解决如下的两个问题就行了:

    1:单点故障
        我们做系统架构时要假定,我们必须悲观的认为所有的服务都是会挂掉的,避免单点故障
    2:故障恢复
        故障的机器在重启后,要能够恢复到故障前的状态
    
    • 1
    • 2
    • 3
    • 4

    想要解决以上的这2个问题,就需要依赖于服务器集群了,而nacos也不例外,所以,我们这部分就一起来搭建一个nacos的集群环境,并应用在后续的优惠券功能微服务化的改造中。

    • 下载
      点鸡
    • 解压
      首先解压到一个目录,再重新复制一份,在一台机器起两个服务模拟集群,如下:
      在这里插入图片描述
    • 修改配置conf/application.properties
      主要修改端口号和MySQL地址,如下:
    server.port=8858
    spring.datasource.platform=mysql
    db.url.0=jdbc:mysql://192.168.64.129:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
    db.user.0=root
    db.password.0=123456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 创建集群配置conf/cluster.conf
      没有就创建,如下:
    192.168.10.62:8858
    192.168.10.62:8868
    
    • 1
    • 2
    • 创建schema和db
    create schema nacos;
    
    conf/nacos-mysql.sql
    
    • 1
    • 2
    • 3
    • 启动
      注意俩都启动。
    JHP+Administrator@jhp MINGW64 /d/test/nacos-cluster/nacos-cluster-2/nacos/bin
    $ ./startup.cmd
    "nacos is starting with cluster"
    
             ,--.
           ,--.'|
       ,--,:  : |                                           Nacos 2.0.0
    ,`--.'`|  ' :                       ,---.               Running in cluster mode, All function modules
    |   :  :  | |                      '   ,'\   .--.--.    Port: 8868
    :   |   \ | :  ,--.--.     ,---.  /   /   | /  /    '   Pid: 18952
    |   : '  '; | /       \   /     \.   ; ,. :|  :  /`./   Console: http://192.168.64.1:8868/nacos/index.html
    '   ' ;.    ;.--.  .-. | /    / ''   | |: :|  :  ;_
    |   | | \   | \__\/: . ..    ' / '   | .; : \  \    `.      https://nacos.io
    '   : |  ; .' ," .--.; |'   ; :__|   :    |  `----.   \
    |   | '`--'  /  /  ,.  |'   | '.'|\   \  /  /  /`--'  /
    '   : |     ;  :   .'   \   :    : `----'  '--'.     /
    ;   |.'     |  ,     .-./\   \  /            `--'---'
    '---'        `--`---'     `----'
    
    2023-10-11 09:00:10,954 INFO The server IP list of Nacos is [192.168.10.62:8858, 192.168.10.62:8868]
    ...
    2023-10-11 09:00:30,852 INFO Nacos started successfully in cluster mode. use external storage
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    出现Nacos started successfully in cluster mode. use external storage就是成功了。

    • 访问
      访问任意一个节点就行,账号nacos/nacos:
      在这里插入图片描述

    在这里插入图片描述

    4:服务注册到nacos

    首先我们改造模板模块和计算模块,让其服务注册到nacos,首先需要在每个模块对应的impl实现子模块中引入nacos注册的依赖如下:

    
    
        com.alibaba.cloud
        spring-cloud-starter-alibaba-nacos-discovery
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    之后需要引入nacos相关的配置项:

    spring:
      application:
        ...
      cloud:
        nacos:
          discovery:
            # 可以配置多个,逗号分隔
            server-addr: 192.168.64.1:8858
            # 默认就是application name,一般不用配置
            service: coupon-calculation-serv
            # nacos客户端向服务端发送心跳的时间间隔,时间单位其实是ms
            heart-beat-interval: 5000
            # 服务端没有接受到客户端心跳请求就将其设为不健康的时间间隔,默认为15s
            # 注:推荐值该值为15s即可,如果有的业务线希望服务下线或者出故障时希望尽快被发现,可以适当减少该值
            heart-beat-timeout: 20000
            # [注意] 这个IP地址如果更换网络后变化,会导致服务调用失败,建议先不要设置
            # ip: 172.20.7.228
            # 元数据部分 - 可以自己随便定制
            metadata:
              mydata: abc
            # 客户端在启动时是否读取本地配置项(一个文件)来获取服务列表
            # 注:推荐该值为false,若改成true。则客户端会在本地的一个文件中保存服务信息,当下次宕机启动时,会优先读取本地的配置对外提供服务。
            naming-load-cache-at-start: false
            # 创建不同的集群
            cluster-name: Cluster-A
            # 命名空间ID,Nacos通过不同的命名空间来区分不同的环境,进行数据隔离,
            # 服务消费时只能消费到对应命名空间下的服务。
            # [注意]需要在nacos-server中创建好namespace,然后把id copy进来
            namespace: dev
            # [注意]两个服务如果存在上下游调用关系,必须配置相同的group才能发起访问
            group: myGroup
            # 向注册中心注册服务,默认为true
            # 如果只消费服务,不作为服务提供方,倒是可以设置成false,减少开销
            register-enabled: true
            # 类似长连接监听服务端信息变化的功能
            watch:
              enabled: true
            watch-delay: 30000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    配置项含义如下:
    在这里插入图片描述

    在正式注册服务之前我们需要先在nacos添加这里的命名空间dev,如下:
    在这里插入图片描述
    配置完毕后,分别启动服务,就能完成在nacos注册中心的注册了,成功后在nacos控制台查看:
    在这里插入图片描述

    5:改造用户模块基于服务发现调用

    接着我们改造用户模块基于服务发现调用,这里我们以requestCoupon接口,即发放优惠券为例来看下,首先我们将coupon-customer-impl中对于模板模块和计算模块的impl实现的依赖,而改为对公用的api的相关bean模块的依赖,如下:

    
    
    <dependency>
        <groupId>${project.groupId}groupId>
        <artifactId>coupon-calculation-apiartifactId>
        <version>${project.version}version>
    dependency>
    
    
    <dependency>
        <groupId>${project.groupId}groupId>
        <artifactId>coupon-template-implartifactId>
        <version>${project.version}version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    接着配置用来执行服务调用的WebClient,以Java config方式来配置,如下:

    // Configuration注解用于定义配置类
    // 类中定义的Bean方法会被AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext扫描并初始化
    @org.springframework.context.annotation.Configuration
    public class Configuration {
    
        @Bean
        @LoadBalanced
        public WebClient.Builder register() {
            return WebClient.builder();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接着来改造领取优惠券的方法requestCoupon,如下:

    @Slf4j
    @Service
    public class CouponCustomerServiceImpl implements CouponCustomerService {
        ...
        /**
          * 用户领取优惠券
          */
        @Override
        public Coupon requestCoupon(RequestCoupon request) {
            /*
            Mono> entityMono = client.get()
              .uri("http://coupon-template-serv/template/xxxx")
              .accept(MediaType.APPLICATION_JSON)
              .retrieve()
              .toEntity(CouponTemplateInfo.class);
              */
            CouponTemplateInfo templateInfo = webClientBuilder.build()
                    // 声明了这是一个GET方法
                    .get()
                    .uri("http://coupon-template-serv/template/getTemplate?id=" + request.getCouponTemplateId())
                    .header(TRAFFIC_VERSION, request.getTrafficVersion())
                    .retrieve()
                    .bodyToMono(CouponTemplateInfo.class)
                    .block();
    
            // 模板不存在则报错
            if (templateInfo == null) {
                log.error("invalid template id={}", request.getCouponTemplateId());
                throw new IllegalArgumentException("Invalid template id");
            }
    
            // 模板不能过期
            long now = Calendar.getInstance().getTimeInMillis();
            Long expTime = templateInfo.getRule().getDeadline();
            if (expTime != null && now >= expTime || BooleanUtils.isFalse(templateInfo.getAvailable())) {
                log.error("template is not available id={}", request.getCouponTemplateId());
                throw new IllegalArgumentException("template is unavailable");
            }
    
            // 用户领券数量超过上限(通过优惠券对应的规则)
            Long count = couponDao.countByUserIdAndTemplateId(request.getUserId(), request.getCouponTemplateId());
            if (count >= templateInfo.getRule().getLimitation()) {
                log.error("exceeds maximum number");
                throw new IllegalArgumentException("exceeds maximum number");
            }
    
            Coupon coupon = Coupon.builder()
                    .templateId(request.getCouponTemplateId())
                    .userId(request.getUserId())
                    .shopId(templateInfo.getShopId())
                    .status(CouponStatus.AVAILABLE)
                    .templateInfo(templateInfo)
                    .build();
            couponDao.save(coupon);
            return coupon;
        }
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    接着启动服务,就可以来调用测试了:
    json参数

    {
        "userId": 1,
        "couponTemplateId": 2
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    在这里插入图片描述

    写在后面

    参考文章列表

    Open API 指南

    如何给集群设置VIP(虚拟IP)

    k8s实现nacos动态伸缩1

    k8s实现nacos动态伸缩2

    springcloud和springboot版本对应关系

    springcloud,springcloud-alibaba,springboot版本对应关系

  • 相关阅读:
    [学习笔记] 概率与期望及其应用
    2310C++模板继承
    计算机毕业设计之java+ssm在线心理评测与咨询系统
    Python 调硬件参数速度加快
    198.打家劫舍,213.打家劫舍II,337.打家劫舍III
    力扣刷题day43|123买卖股票的最佳时机III、188买卖股票的最佳时机IV
    NTB0101GS1Z 电压电平 移位器 产品概述 特性
    04-React路由5版本(高亮, 嵌套, 参数传递... )
    @vitejs/plugin-legacy 为你的 Vite 项目提供对旧版浏览器的支持
    命理八字之答案之书前端uniapp效果实现
  • 原文地址:https://blog.csdn.net/wang0907/article/details/133084620