SpringCloud基础一

前后端分离开发

在这里插入图片描述

  • 前后端分离开发后,前后端代码不在混合在同一个maven工程中,而是分为前端工程和后端工程。此时前后端代码并行开发,可以加快项目的开发进度
  • 在前后端代码分离后,此时后端工程会打包部署到Tomcat上,前端工程会打包部署到Nginx上

前后端分离开发流程

  • 前后端分离开发后有一个问题:前端人员和后端人员如何进行配合来共同开发一个项目。如果前后端人员的开发习惯或规范不一样就会导致后期联调对接不上,出很多问题,所以为了后期方便开发,就需要先定制接口来规范开发内容,让前后端人员均按照此规范来进行开发,这样就避免了后期前后端联调的麻烦问题,此时开发流程如下图所示:

    在这里插入图片描述

  • 请求(API接口):就是一个http请求地址,主要是去定义:请求路径、请求方式、请求参数、响应数据等内容。示例图如下

    在这里插入图片描述

  • 前后端人员自测

    • 前端
      • 使用mock:产生模拟数据,帮助前端人员进行测试
    • 后端
      • YApi:可用来提前定义接口文档以及请求测试等
        • 可自行进入官网注册并登录后进行操作
      • Swagger:用来生成接口文档
      • PostMan:请求测试

微服务背景引入

单体架构

在这里插入图片描述

  • 单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
    • 优点:架构简单且部署成本低
    • 缺点:耦合度高(维护困难、升级困难)

分布式架构

在这里插入图片描述

  • 分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务
    • 优点:降低服务耦合度,有利于服务升级拓展
    • 缺点:服务调用关系错综复杂
    • 微服务就是分布式架构的一种

微服务

  • 微服务是一种经过良好架构设计的分布式架构方案,特征为:

    • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责

    • 面向服务:服务提供统一标准的接口,与语言和技术无关

    • 自治:团队独立、技术独立、数据独立,独立部署和交付

    • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

      ①优点:拆分粒度更小、服务更独立、耦合度更低

      ②缺点:架构非常复杂,运维、监控、部署难度提高

  • 微服务技术最知名的为:SpringCloud和阿里巴巴的Dubbo,Dubbo后期在SpringCloud的基础上优化产生了SpringCloudAlibaba

    • 国内使用最广泛的为SpringCloud
  • 微服务技术对比

    在这里插入图片描述

  • 企业使用微服务组合种类

    在这里插入图片描述

SpringCloud

  • SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。

    在这里插入图片描述

版本兼容

  • SpringCloud底层是依赖于SpringBoot的,所以它俩之间有严格的版本兼容关系,看而详见SpringCloud官网,目前最新的版本兼容关系如图所示

    注意:

    ​ 由于博主的SpringBoot版本为3.4.0,所以会使用的SpringCloud版本为2024.0.x aka Moorgate

    ​ 推荐使用Spring Cloud 2021.0.x以及Spring Boot 2.7.x版本

    在这里插入图片描述

    • 推荐使用的版本如下

      注意:由于SpringCloudAlibaba最新版为2023,所以若使用SpringAlibaba时,SpringBoot以及SpringCloud只能用2024之前的版本,具体的版本对应关系可详见SpringAlibaba的GItHub

      SpringBootSpringCloudSpringAlibaba最低支持的JDK版本
      2.3.12.RELEASEHoxton.SR122.2.8.RELEASEJDK1.8
      2.3.9.RELEASEHoxton.SR102.2.5.RELEASEJDK1.8
      3.0.72022.0.32022.0.0.0-RC2JDK17
      3.2.x2023.0.x2023.xJDK17

微服务拆分

在这里插入图片描述

  • 微服务拆分原则/注意事项

    • 不同微服务,不能重复开发相同业务
      • 若出现重复相同业务,则就不能称之为微服务
    • 微服务数据独立,不能访问其它微服务的数据库
    • 微服务可以将自己的业务暴露为接口,供其它微服务调用
  • 微服务拆分后的工程结构有两种

    • 方式一:将每个业务都拆分为独立的Project

      在这里插入图片描述

    • 方式二:将每个业务都拆分为Module

      在这里插入图片描述

    • 在快速入门中以第二种方式为例

快速入门

环境准备

后端环境准备,该后端测试项目已上传至Gitee,可自行下载

  • Step1: 创建网络hm-netdocker network create hm-net

  • Step2: 在Linux中利用Docker部署MySQL容器,并将该容器添加到hm-net网络中,代码如下:

    具体过程可详见Docker部分内容

      docker run -id \
        --name mysql \
        -p 3307:3306 \
        -v /root/mysql/conf:/etc/mysql/conf.d \
        -v /root/mysql/logs:/logs \
        -v /root/mysql/data:/var/lib/mysql \
        -v /root/mysql/init:/docker-entrypoint-initdb.d \
        -e TZ=Asia/Shanghai \
        -e MYSQL_ROOT_PASSWORD=123456 \
        --network hm-net \
        mysql
    
  • Step3: 利用vim /root/mysql/conf/hm.cnf在挂载的配置目录下创建该MySQL容器配置文件,该文件代码如下:

    [client]
    default_character_set=utf8mb4
    [mysql]
    default_character_set=utf8mb4
    [mysqld]
    character_set_server=utf8mb4
    collation_server=utf8mb4_unicode_ci
    init_connect='SET NAMES utf8mb4'
    
  • Step4: 利用vim /root/mysql/init/hmall.sql在脚本目录下创建脚本文件hmall.sql,文件代码略

  • Step5: 将项目hmall导入到idea中,并使idea与MySQL容器建立连接,如下:

    在这里插入图片描述

  • Step6: 在该项目中添加SpringBoot的启动项

    在这里插入图片描述

    上述点击之后,则会出现如下界面:

    在这里插入图片描述

    点击对应按钮,即可实现运行或DEBUG运行。但是在运行之前,再做个简单配置:在HMallApplication上点击鼠标右键,会弹出窗口,然后选择Edit Configuration

    在这里插入图片描述

    在弹出窗口中配置SpringBoot的启动环境为 local

    在这里插入图片描述

  • Step8: 在yaml配置文件中修改数据库的ip地址为自己的虚拟机地址以及端口号

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  • Step9: 测试项目是否能够启动成功

    在这里插入图片描述

  • 注意:若数据库连接超时,如图所示,则说明挂起之后无法连接MySQL容器,则需要在Linux中进行解决,解决步骤如下:

    在这里插入图片描述

    • Step1: 关闭防火墙:systemctl stop firewalld

    • Step2: 禁止开机启动防火墙:systemctl disable firewalld

    • Step3: 修改ipv4转发状态

      • vi /usr/lib/sysctl.d/00-system.conf
      • 添加代码net.ipv4.ip_forward = 1
    • Step4: 保存退出后,重启网络服务systemctl restart network

    • 将docker的网络接口设置为不被NetworkManager管理

      • vi /etc/NetworkManager/conf.d/10-unmanage-docker-interfaces.conf

      • 添加代码:

        [keyfile]
        unmanaged-devices=interface-name:docker*;interface-name:veth*;interface-name:br-*;interface-name:vmnet*;interface-name:vboxnet*
        
    • Step5: 重启网络systemctl restart NetworkManager

    • Step6: 重启dockersystemctl restart docker

    • Step7: 启动相应的容器:docker restart mysql

      在这里插入图片描述

商品服务拆分

需求:将hm-service中与商品管理相关的工程拆分到一个微服务module中,命名为item-service

  • Step1: 在hmall中创建模块且模块名为:item-service

    在这里插入图片描述

  • Step2: 在item-service模块的pom.xml文件中添加坐标依赖

    • 将hm-service的pom.xml文件中与商品管理相关的依赖复制到item-service模块的pom.xml文件中,文件代码如下

      <?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.heima</groupId>
              <artifactId>hmall</artifactId>
              <version>1.0.0</version>
          </parent>
      
          <artifactId>item-service</artifactId>
      
          <properties>
              <maven.compiler.source>11</maven.compiler.source>
              <maven.compiler.target>11</maven.compiler.target>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          </properties>
      
          <dependencies>
              <!--common-->
              <dependency>
                  <groupId>com.heima</groupId>
                  <artifactId>hm-common</artifactId>
                  <version>1.0.0</version>
              </dependency>
              <!--web-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <!--数据库-->
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
              </dependency>
              <!--mybatis-->
              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-boot-starter</artifactId>
              </dependency>
              <!--单元测试-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
              </dependency>
      
          </dependencies>
          <build>
              <finalName>${project.artifactId}</finalName>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
          </build>
      </project>
      
  • Step3: 创建启动引导类 item-service\src\main\java\com\hmall\item\ItemApplication.java 代码如下:

    package com.hmall.item;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @MapperScan("com.hmall.item.mapper")
    @SpringBootApplication
    public class ItemApplication {
        public static void main(String[] args) {
            SpringApplication.run(ItemApplication.class, args);
        }
    }
    
  • Step4: 复制 hm-service 中的 application.yml、application-local.yml、application-dev.yml 配置文件到 item-service 模块的 resources目录如下:

    • application.yml 文件修改后如下:

      • server.port的端口号由8080改为8081
      • datasource.url的数据库由hmall改为hm-item
      • openapi.titleopenapi.description参数由 黑马商城接口文档 改为 商品服务接口文档
      • api-rule-resources属性值由com.hmall.controller改为com.hmall.item.controller
      server:
        port: 8081
      spring:
        application:
          name: item-service
        profiles:
          active: dev
        datasource:
          url: jdbc:mysql://${hm.db.host}:3307/hm-item?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: ${hm.db.pw}
      mybatis-plus:
        configuration:
          default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
        global-config:
          db-config:
            update-strategy: not_null
            id-type: auto
      logging:
        level:
          com.hmall: debug
        pattern:
          dateformat: HH:mm:ss:SSS
        file:
          path: "logs/${spring.application.name}"
      knife4j:
        enable: true
        openapi:
          title: 商品服务接口文档
          description: "商品服务接口文档"
          email: itheima@itcast.cn
          concat: itheima
          url: https://www.itcast.cn
          version: v1.0.0
          group:
            default:
              group-name: default
              api-rule: package
              api-rule-resources:
                - com.hmall.item.controller
      
  • Step5: 复制 hm-service 中与商品有关的代码到 item-service 模块中,最终该模块中的代码如下

    注意:将代码复制过来后要修改类中import的包的路径,因为在该模块中,比原始项目多了一层包item

    在这里插入图片描述

    这里有一个地方的代码需要改动,就是ItemServiceImpl中的deductStock方法:

    因为ItemMapper的所在包发生了变化,因此这里代码必须修改包路径

    在这里插入图片描述

  • Step6: 创建hm-item数据库

    • Step6-1: 将本地创建hm-item数据库的sql文件导入运行

      在这里插入图片描述

    • Step6-2: 将idea与hm-item数据库建立连接

      在这里插入图片描述

    • 最终,会在数据库创建一个名为hm-item的database,将来的每一个微服务都会有自己的一个database:

      在这里插入图片描述

  • Step7: 复制一个SpringBoot的启动项并修改,步骤如图所示

    在这里插入图片描述

    在这里插入图片描述

    修改完后如下

    在这里插入图片描述

  • Step8: 启动item-service的引导类ItemApplication,测试是否该模块是否能够运行成功

    在这里插入图片描述

购物车服务拆分

需求:将hm-service中与购物车相关的工程拆分到一个微服务module中,命名为cart-service

注意:该拆分过程与商品服务拆分过程一致,所以此处不在进行图片示例演示

  • Step1: 在hmall中创建模块且模块名为:cart-service

  • Step2: 在cart-service模块的pom.xml文件中添加坐标依赖

    • 将hm-service的pom.xml文件中与购物车服务相关的依赖复制到cart-service模块的pom.xml文件中,文件代码如下

      <?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.heima</groupId>
              <artifactId>hmall</artifactId>
              <version>1.0.0</version>
          </parent>
      
          <artifactId>cart-service</artifactId>
      
          <properties>
              <maven.compiler.source>11</maven.compiler.source>
              <maven.compiler.target>11</maven.compiler.target>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          </properties>
          <dependencies>
              <!--common-->
              <dependency>
                  <groupId>com.heima</groupId>
                  <artifactId>hm-common</artifactId>
                  <version>1.0.0</version>
              </dependency>
              <!--web-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
      
              <!--数据库-->
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
              </dependency>
              <!--mybatis-->
              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-boot-starter</artifactId>
              </dependency>
              <!--单元测试-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
              </dependency>
      
          </dependencies>
          
          <build>
              <finalName>${project.artifactId}</finalName>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
          </build>
      
      </project>
      
  • Step3: 创建启动引导类 item-service\src\main\java\com\hmall\item\CartApplication.java 代码如下:

    package com.hmall.cart;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @MapperScan("com.hmall.cart.mapper")
    @SpringBootApplication
    public class CartApplication {
        public static void main(String[] args) {
            SpringApplication.run(ItemApplication.class, args);
        }
    }
    
  • Step4: 复制 hm-service 中的 application.yml、application-local.yml、application-dev.yml 配置文件到cart-service模块的 resources目录如下:

    • application.yml 文件修改后如下:

      • server.port的端口号由8080改为8082
      • datasource.url的数据库由hmall改为hm-cart
      • openapi.titleopenapi.description参数由 黑马商城接口文档 改为 购物车服务接口文档
      • api-rule-resources属性值由com.hmall.controller改为com.hmall.cart.controller
      server:
        port: 8082
      spring:
        application:
          name: cart-service
        profiles:
          active: dev
        datasource:
          url: jdbc:mysql://${hm.db.host}:3307/hm-cart?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: ${hm.db.pw}
      mybatis-plus:
        configuration:
          default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
        global-config:
          db-config:
            update-strategy: not_null
            id-type: auto
      logging:
        level:
          com.hmall: debug
        pattern:
          dateformat: HH:mm:ss:SSS
        file:
          path: "logs/${spring.application.name}"
      knife4j:
        enable: true
        openapi:
          title: 购物车服务接口文档
          description: "购物车服务接口文档"
          email: itheima@itcast.cn
          concat: itheima
          url: https://www.itcast.cn
          version: v1.0.0
          group:
            default:
              group-name: default
              api-rule: package
              api-rule-resources:
                - com.hmall.cart.controller
      
  • Step5: 复制 hm-service 中与商品有关的代码到 item-service模块 中,最终该模块中的代码如下

    注意:将代码复制过来后要修改类中import的包的路径,因为在该模块中,比原始项目多了一层包cart

    在这里插入图片描述

    这里有一个地方的代码需要改动,就是CartServiceImpl

    • 需要获取登录用户信息,但登录校验功能目前没有复制过来,先写死固定用户id
    • 查询购物车时需要查询商品信息,而商品信息不在当前服务,需要先将这部分代码注释
    package com.hmall.cart.service.impl;
    
    import cn.hutool.core.util.StrUtil;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.hmall.cart.domain.dto.CartFormDTO;
    import com.hmall.cart.domain.po.Cart;
    import com.hmall.cart.domain.vo.CartVO;
    import com.hmall.cart.mapper.CartMapper;
    import com.hmall.cart.service.ICartService;
    import com.hmall.common.exception.BizIllegalException;
    import com.hmall.common.utils.BeanUtils;
    import com.hmall.common.utils.CollUtils;
    import com.hmall.common.utils.UserContext;
    import lombok.RequiredArgsConstructor;
    import org.springframework.stereotype.Service;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    /**
     * <p>
     * 订单详情表 服务实现类
     * </p>
     *
     * @author itheima
     * @since 2023-05-05
     */
    @Service
    @RequiredArgsConstructor
    public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
    
        //private final IItemService itemService;
    
        @Override
        public void addItem2Cart(CartFormDTO cartFormDTO) {
            // 1.获取登录用户
            Long userId = UserContext.getUser();
    
            // 2.判断是否已经存在
            if(checkItemExists(cartFormDTO.getItemId(), userId)){
                // 2.1.存在,则更新数量
                baseMapper.updateNum(cartFormDTO.getItemId(), userId);
                return;
            }
            // 2.2.不存在,判断是否超过购物车数量
            checkCartsFull(userId);
    
            // 3.新增购物车条目
            // 3.1.转换PO
            Cart cart = BeanUtils.copyBean(cartFormDTO, Cart.class);
            // 3.2.保存当前用户
            cart.setUserId(userId);
            // 3.3.保存到数据库
            save(cart);
        }
    
        @Override
        public List<CartVO> queryMyCarts() {
            // 1.查询我的购物车列表
            //List<Cart> carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();
            // TODO 先将用户的id写死为 1
            List<Cart> carts = lambdaQuery().eq(Cart::getUserId, 1L).list();
            if (CollUtils.isEmpty(carts)) {
                return CollUtils.emptyList();
            }
    
            // 2.转换VO
            List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);
    
            // 3.处理VO中的商品信息
            handleCartItems(vos);
    
            // 4.返回
            return vos;
        }
    
        private void handleCartItems(List<CartVO> vos) {
            // 1.获取商品id
            // TODO  暂时无法调用商品服务,先注释
    /*
            Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
            // 2.查询商品
            List<ItemDTO> items = itemService.queryItemByIds(itemIds);
            if (CollUtils.isEmpty(items)) {
                return;
            }
            // 3.转为 id 到 item的map
            Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
            // 4.写入vo
            for (CartVO v : vos) {
                ItemDTO item = itemMap.get(v.getItemId());
                if (item == null) {
                    continue;
                }
                v.setNewPrice(item.getPrice());
                v.setStatus(item.getStatus());
                v.setStock(item.getStock());
            }
    */
        }
    
        @Override
        public void removeByItemIds(Collection<Long> itemIds) {
            // 1.构建删除条件,userId和itemId
            QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();
            queryWrapper.lambda()
                    .eq(Cart::getUserId, UserContext.getUser())
                    .in(Cart::getItemId, itemIds);
            // 2.删除
            remove(queryWrapper);
        }
    
        private void checkCartsFull(Long userId) {
            int count = lambdaQuery().eq(Cart::getUserId, userId).count();
            if (count >= 10) {
                throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", 10));
            }
        }
    
        private boolean checkItemExists(Long itemId, Long userId) {
            int count = lambdaQuery()
                    .eq(Cart::getUserId, userId)
                    .eq(Cart::getItemId, itemId)
                    .count();
            return count > 0;
        }
    }
    
  • Step6: 创建hm-cart数据库

    • Step6-1: 将本地创建hm-cart数据库的sql文件导入运行

    • Step6-2: 将idea与hm-item数据库建立连接

    • 最终,会在数据库创建一个名为hm-item的database,将来的每一个微服务都会有自己的一个database

  • Step7: 复制一个SpringBoot的启动项并修改为cart-service模块的引导类CartApplication

  • Step8: 启动cart-service的引导类CartApplication,测试是否该模块是否能够运行成功

    在这里插入图片描述

微服务远程调用

本示例的初始项目cloud-demo具体搭建过程可详见SpringCloud项目快速搭建部分内容

且本项目已上传至Gitee,可自行下载

背景说明

  • 该项目一共有两个模块,分别为订单模块和用户模块,每个模块各实现了一个功能,该羡项目的完整结构及表现层代码如图所示

    在这里插入图片描述

    • 对应的实体类代码有两个:User、Order

      在这里插入图片描述

  • 运行该项目下两个模块的启动类后测试结果如下

    • order-service模块

      在这里插入图片描述

    • user-service模块

      在这里插入图片描述

    • 从该项目的order-service模块的运行结果可看出

      虽然可以正常运行,但是user为null,这是由于此时order-service模块并未远程调用user-service模块

      因此我们就需要从一个微服务来远程调用另一个微服务,即在order-service模块中来远程调用user-service模块,从而获取数据

  • 解决办法: 可以在微服务中来模拟浏览器发送http请求到另一个微服务,从而获取到数据

    • 即在order-service模块中发送http请求到user-service模块来获取数据
    • Spring给我们提供了一个RestTemplate的API,可以方便的实现Http的各种请求的发送。

    在这里插入图片描述

RestTemplate

  • RestTemplate类是Spring提供的一个API,它继承自抽象类InterceptingHttpAccessor,且实现了RestOperations接口

  • 利用RestTemplate发送http请求与前端ajax发送请求非常相似,都包含四部分信息:

    • ① 请求方式
    • ② 请求路径
    • ③ 响应数据类型
    • ④ 请求参数
  • RestOperations类中提供了大量的方法,部分方法如图所示

    有图可知,对于常见的Get、Post、Put、Delete请求都支持,如果请求参数比较复杂,还可以使用exchange方法来构造请求。

    在这里插入图片描述

  • RestOperations类常用方法

    注意:以下方法在发生异常时均会抛出RestClientException异常

    GET方法解释
    public <T> T getForObject(URI url, Class<T> responseType)发送Get请求并返回指定类型对象
    public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)发送Get请求并返回指定类型对象
    public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)发送Get请求并返回指定类型对象
    POST方法解释
    public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException发送Post请求并返回指定类型对象
    public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException发送Post请求并返回指定类型对象
    public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException发送Post请求并返回指定类型对象
    PUT方法解释
    public void put(URI url, @Nullable Object request)发送PUT请求
    public void put(String url, @Nullable Object request, Object... uriVariables)发送PUT请求
    public void put(String url, @Nullable Object request, Map<String, ?> uriVariables)发送PUT请求
    DELETE方法解释
    public void delete(URI url)发送Delete请求
    public void delete(String url, Object... uriVariables)发送Delete请求
    public void delete(String url, Map<String, ?> uriVariables) 发送Delete请求
    EXCHANGE方法解释
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables)发送任意类型请求,返回ResponseEntity
    public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType)发送任意类型请求,返回ResponseEntity
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)发送任意类型请求,返回ResponseEntity
    • 以上方法在遇到时会进行详细说明,此处省略

快速入门

  • Step1: 在order-service模块中创建一个与三层架构包同级的config包,并在该包下创建一个配置类RemoteCallConfig,代码如下

    注意:该配置类必须用@Configuration注解来标记为配置类

    package at.guigu.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.client.RestTemplate;
    @Slf4j
    @Configuration
    public class RemoteCallConfig {
        @Bean
        public RestTemplate restTemplate() {
            log.info("restTemplate创建成功,微服务远程调用开启");
            return new RestTemplate();
        }
    }
    
    • 注意:

      • order-service模块中的启动类OrderApplication本身也是一个配置类,所以我们也可以直接在该启动类中进行微服务远程调用的配置。

      • 若在启动类中进行配置,则不需要创建配置类RemoteCallConfig,此时启动类OrderApplication代码如下

        package at.guigu;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.context.annotation.Bean;
        import org.springframework.web.client.RestTemplate;
        
        @Slf4j
        @SpringBootApplication
        public class OrderApplication {
            public static void main(String[] args) {
                SpringApplication.run(OrderApplication.class, args);
                log.info("OrderApplication Running");
            }
            @Bean
            public RestTemplate restTemplate() {
                log.info("restTemplate创建成功,微服务远程调用开启");
                return new RestTemplate();
            }
        }
        
  • Step2: 修改业务层代码

    • Step2-1:IOrderService接口中添加方法queryOrderById,代码如下:

      注意:接口中的方法默认就是public abstract,此处写出来只是为了演示,实际项目中可详略

      package at.guigu.service;
      
      import at.guigu.po.Order;
      import com.baomidou.mybatisplus.extension.service.IService;
      
      public interface IOrderService extends IService<Order> {
          // 返回包含用户信息的订单信息
          public abstract Order queryOrderById(Long orderId);
      }
      
    • Step2-2:IOrderService接口中添加方法queryOrderById,代码如下:

      package at.guigu.service.impl;
      
      import at.guigu.mapper.OrderMapper;
      import at.guigu.po.Order;
      import at.guigu.po.User;
      import at.guigu.service.IOrderService;
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import org.springframework.stereotype.Service;
      import org.springframework.web.client.RestTemplate;
      
      @Service
      public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
          private final RestTemplate restTemplate;
      
          public OrderServiceImpl(RestTemplate restTemplate) {
              this.restTemplate = restTemplate;
          }
      
          /**
           * 获取包含用户信息的订单信息
           * @param orderId
           * @return Order
           */
          @Override
          public Order queryOrderById(Long orderId) {
              // 1 查询订单信息
              Order order = this.getById(orderId);
              // 2 利用RestTemplate发起Http请求来获取用户数据,实现远程调用
              // 2.1 设置url路径
              String url = "http://localhost:8081/user/" + order.getUserId();
              // 2.2 发送Http请求,实现远程调用
              User user = restTemplate.getForObject(url, User.class);
              // 3 封装用户数据存储到订单信息中
              order.setUser(user);
              return order;
          }
      }
      
  • Step3: 表现层OrderController类中的queryOrderById方法代码更改如下

    package at.guigu.controller;
    
    import at.guigu.po.Order;
    import at.guigu.service.IOrderService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("order")
    @RequiredArgsConstructor
    public class OrderController {
    
       private final IOrderService orderService;
    
        @GetMapping("{orderId}")
        public Order queryOrderById(@PathVariable("orderId") Long orderId) {
            // 根据id查询订单并返回
            return orderService.queryOrderById(orderId);
        }
    }
    
  • 运行启动类测试

    在这里插入图片描述

提供者与消费者

  • 服务提供者:一次业务中,被其它微服务调用的服务

    • 即提供接口给其它微服务的微服务
  • 服务消费者:一次业务中,调用其它微服务调用的服务

    • 即调用其它微服务提供的接口
  • 在微服务远程调用快速入门的示例中order-srevice为服务消费者,order-service为服务提供者

  • 注意

    • 一个服务既可以是提供者,又可以是消费者
    • 服务的具体身份根据具体业务分析,可能只是其中一个身份,但也可能同时拥有两种身份

服务注册与发现

在这里插入图片描述

在微服务远程调用的示例中,从利用RestTemplate发起Http请求来获取用户数据从而实现远程调用的那一部分代码中可以看出我们是直接将url硬编码到代码中(如上图所示),这样就产生了问题:

​ 1.实际项目中,可能会由于开发、测试、生产环境的切换,导致url地址频繁发生变化

​ 2.实际项目中,为了应对多并发环境,user-service服务可能会部署成一个集群(如下图所示),这就导致user-service服务存在不同的url

所以就不能采用硬编码的方式,但是不采用硬编码的方式,那服务消费者该如何获取服务提供者的地址信息呢?若有多个服务提供者,那服务消费者又要如何选择呢?且如何判断服务提供者的健康状态呢?

这就引出了服务注册中心

在这里插入图片描述

  • 目前开源的注册中心框架有很多,国内比较常见的有:

    • Eureka:Netflix公司出品,目前被集成在SpringCloud当中,一般用于Java应用
    • Nacos:Alibaba公司出品,目前被集成在SpringCloudAlibaba中,一般用于Java应用
    • Consul:HashiCorp公司出品,目前集成在SPringCloud中,不限制微服务语言

    以上几种注册中心都遵循SpringCloud中的API规范,因此在业务开发使用上没有太大差异。由于Nacos是国内产品,中文文档比较丰富,而且同时具备配置管理功能,因此在国内使用较多

    在本章内容中主要讲解Eureka以及Nacos,两者选用一个即可,实际项目推荐Nacos

Eureka注册中心

Eureka代码演示已上传至Gitee,可自行下载

  • Eureka中主要有两部分:

    在这里插入图片描述

    • 服务端:eureka-server注册中心
    • 客户端:eureka-client
      • 客户端主要包括两部分:服务提供者和服务消费者
  • Eureka原理

    在这里插入图片描述

    • 服务提供者
      • Eureka客户端中的服务提供者在服务启动时会创建服务实例信息(包括服务提供者的服务名称、ip地址、端口号等),然后将自己的服务实例信息注册到Eureka服务端(即eureka-server注册中心),并且服务提供者会定期(默认30s/次)向注册中心发送请求,报告自己的健康状态(心跳请求)
      • 当注册中心长时间收不到服务提供者的心跳时,会认为该实例宕机,然后将其从服务的实例列表中剔除
      • 当服务提供者有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
      • 当注册中心服务列表变更时,也会主动通知服务提供者,更新本地服务列表
    • 服务消费者
      • 假设此时有个服务消费者要进行消费,就会从Eureka服务端(即eureka-server注册中心)来 拉取/订阅/发现 相关服务提供者的服务实例信息,,然后服务消费者会结合负载均衡算法来挑选一个服务实例进行远程调用
    • Eureka客户端
      • 为减少Eureka服务端(即eureka-server注册中心)的压力,eureka-client本地会维护一份服务注册表的缓存并定期更新(默认30s/次),服务调用时会优先从本地缓存中读取服务实例信息
  • 相关问题

    在这里插入图片描述

Eureka服务搭建注册中心入门

主要分为三大步骤,如图所示

在这里插入图片描述

  • Step1: 创建一个新的微服务

    注意:由于Eureka自己也是一个微服务,所以此处需要创建一个新的微服务

    • 创建一个新的Maven项目eureka-server

      在这里插入图片描述

    • 创建完成后的项目结构如下

      在这里插入图片描述

  • Step2: 在eureka-server模块的pom.xml文件中引入相关依赖

    • 启动类相关依赖:spring-boot-starter-web

      注意:由于启动类相关依赖已在聚合工程(即父工程)中配置了,所以此处就未进行配置

    • Eureka依赖:spring-cloud-starter-netflix-eureka-server

    <?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>cn.itcast.demo</groupId>
            <artifactId>cloud-demo</artifactId>
            <version>1.0</version>
        </parent>
    
        <artifactId>eureka-server</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>
            <!--Eureka服务端依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  • Step3: 在java包下创建at.guigu包,并在该包下创建启动类EurekaApplication,代码如下

    注意:@EnableEurekaServer注解是开启Eureka服务的必备注解,不可缺失

    package at.guigu;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @Slf4j
    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class, args);
            log.info("Eureka-Server服务端启动成功");
        }
    }
    
  • Step4: 右键源代码配置文件目录(即资源文件resources)→NewFile,创建配置文件application.yml,代码如下:

    • Eureka服务配置
    server:
      port: 10086
    
    spring:
      application:
        # 指定eureka服务端的名称
        name: eurekaserver
    eureka:
      client:
        service-url:
          # 配置eureka服务端的默认地址信息,同时将Eureka服务自身注册到Eureka-server注册中心
          defaultZone: http://127.0.0.1:10086/eureka
    
  • Step5: 运行eureka-server服务的启动类报错:'url' attribute is not specified and no embedded datasource could be configured

    在这里插入图片描述

    • 原因:未配置数据源相关的url

    • 注意:Eureka服务本身是不需要配置数据源的,但是 Spring Boot 自动配置机制 默认需要一个数据源配置,所以解决方式如下:

      • 在Eureka服务的启动类EurekaApplication中,给@SpringBootApplication注解添加exclude属性且属性值为DataSourceAutoConfiguration.class,以此来排除数据源自动配置

        package at.guigu;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
        import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
        
        @Slf4j
        @EnableEurekaServer
        @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
        public class EurekaApplication {
            public static void main(String[] args) {
                SpringApplication.run(EurekaApplication.class, args);
                log.info("Eureka-Server Running");
            }
        }
        
  • Step6: 再次运行eureka-server服务的启动类

    在这里插入图片描述

  • eureka-server服务的启动类运行成功后,浏览器输入url:http://localhost:10086/后会跳转到Eureka的管理页面

    在这里插入图片描述

Eureka服务注册快速入门

  • Step1: 在服务提供者(即user-service模块)的pom文件中引入相关依赖

    • Eureka客户端依赖:spring-cloud-starter-netflix-eureka-client
    <?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">
        <parent>
            <artifactId>cloud-demo</artifactId>
            <groupId>cn.itcast.demo</groupId>
            <version>1.0</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>user-service</artifactId>
    
        <!--配置依赖-->
        <dependencies>
            <!--Eureka客户端依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    
  • Step2: 在服务提供者(即user-service模块)的配置文件中编写Eureka配置

    • 配置服务提供者或服务消费者的名称
      • 由于此时user-service模块充当的是服务提供者,所以name属性值即为服务提供者名
    • 配置Eureka客户端的默认地址信息
      • 目的:将服务提供者(即user-service模块)的实例信息注册到Eureka服务端(即eureka-server注册中心)
    server:
      port: 8081
    
    spring:
      application:
        # 配置服务提供者或服务消费者的名称
        # 由于此时user-service模块属于服务提供者,所以该名称即为服务提供者名
        name: userservice
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/cloud_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: root
        password: 123456
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 是否开启下划线和驼峰映射, 默认为true
        map-underscore-to-camel-case: true
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.po
    
      global-config:
        db-config:
          # 全局id类型---即id生成策略
          # id-type: auto
          # 更新策略,默认为not_null
          # update-strategy: not_null
          # 设置数据库表名前缀
          table-prefix: tb_
    
    eureka:
      client:
        service-url:
          # 配置eureka服务端的默认地址信息
          # 目的:将服务提供者(即user-service模块)的实例信息注册到Eureka服务端(即eureka-server注册中心)
          defaultZone: http://127.0.0.1:10086/eureka
          register-with-eureka: true # 是否将该服务的实例信息注册到Eureka(默认为true)
          fetch-registry: true # 是否从注册中心获取服务实例信息(默认为true)
    
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    
  • 运行服务提供者(即user-service模块)以及Eureka服务的启动类后输入http://localhost:10086/打开Eureka的管理页面,由该页面可看到,服务提供者(即user-service模块)的实例信息已注册成功

    在这里插入图片描述

    • 在Eureka服务端(即eureka-server注册中心)可以看到,只有一个服务提供者(即user-service模块)的实例信息,若想要有多个该服务的实例信息来模拟多实例部署的话,则步骤如下:

      在这里插入图片描述

      在这里插入图片描述

      以上操作成功后,服务列表就会新增一个服务提供者(即user-service模块)的启动类UserApplication2

      在这里插入图片描述

      运行该启动类,然后刷新Eureka的管理页面,由该页面可看到,成功注册两个服务提供者(即user-service模块)的实例信息

      在这里插入图片描述

Eureka服务拉取/订阅/发现快速入门

  • Step1: 在服务消费者(即order-service模块)的pom文件中引入相关依赖

    • Eureka客户端依赖:spring-cloud-starter-netflix-eureka-client
    <?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">
        <parent>
            <artifactId>cloud-demo</artifactId>
            <groupId>cn.itcast.demo</groupId>
            <version>1.0</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>order-service</artifactId>
    
        <!--配置依赖-->
        <dependencies>
            <!--Eureka客户端依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    
  • Step2: 在服务消费者(即order-service模块)的配置文件中编写Eureka配置

    • 配置服务提供者或服务消费者的名称
      • 由于此时order-service模块充当的是服务消费者,所以name属性值即为服务消费者名
    • 配置Eureka客户端的默认地址信息
    server:
      port: 8080
    
    spring:
      application:
        # 指定服务提供者/服务消费的名称
        name: orderservice
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/cloud_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: root
        password: 123456
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 是否开启下划线和驼峰映射, 默认为true
        map-underscore-to-camel-case: true
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.po
    
      global-config:
        db-config:
          # 全局id类型---即id生成策略
          # id-type: auto
          # 更新策略,默认为not_null
          # update-strategy: not_null
          # 设置数据库表名前缀
          table-prefix: tb_
    
    eureka:
      client:
        service-url:
          # 配置eureka服务端的默认地址信息
          # 目的:将服务提供者(即user-service模块)的实例信息注册到Eureka服务端(即eureka-server注册中心)
          defaultZone: http://127.0.0.1:10086/eureka
        register-with-eureka: false # 是否将该服务的实例信息注册到Eureka(默认为true)
        fetch-registry: true # 是否从注册中心获取服务实例信息(默认为true)
    
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    
  • Step3: 更改服务消费者(即order-service模块)的业务层OrderServiceImpl类中queryOrderById方法的代码

    • 修改访问的url路径:用服务名代替ip以及端口号
    package at.guigu.service.impl;
    
    import at.guigu.mapper.OrderMapper;
    import at.guigu.po.Order;
    import at.guigu.po.User;
    import at.guigu.service.IOrderService;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    @Service
    public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
        private final RestTemplate restTemplate;
    
        public OrderServiceImpl(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
        }
    
        /**
         * 获取包含用户信息的订单信息
         * @param orderId
         * @return Order
         */
        @Override
        public Order queryOrderById(Long orderId) {
            // 1 查询订单信息
            Order order = this.getById(orderId);
            // 2 利用RestTemplate发起Http请求来获取用户数据
            /*
            // 2.1 设置url路径(存在url硬编码问题,不推荐)
            String url = "http://localhost:8081/user/" + order.getUserId();
            */
            // 2.1 设置url路径:用服务提供者的服务名代替ip以及端口号------解决url硬编码问题(推荐)
            String url = "http://userservice/user/" + order.getUserId();
            // 2.2 发送Http请求,实现远程调用
            User user = restTemplate.getForObject(url, User.class);
            // 3 封装用户数据存储到订单信息中
            order.setUser(user);
            return order;
        }
    }
    
  • Step4: 给创建RestTemplate对应的Bean的方法添加@LoadBalanced注解(该注解为负载均衡注解)

    注意:负载均衡注解@LoadBalanced作用是标记RestTemplate发起的请求要被Ribbon负载均衡来拦截并处理,从而实现通过负载均衡来调用服务实例。(具体可详见负载均衡原理部分内容)

    • 若在服务消费者(即order-service模块)的config包下创建配置类RemoteCallConfig来获取的RestTemplate对应的Bean,则在该配置类对应的方法上添加负载均衡注解@LoadBalanced

      注意:

      @LoadBalanced注解存在于Ribbon客户端依赖中

      ​ 若使用的是Nacos而不是Eureka时,则需要额外导入loadBalancer依赖才能使用@LoadBalanced注解

      package at.guigu.config;
      
      import lombok.extern.slf4j.Slf4j;
      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;
      @Slf4j
      @Configuration
      public class RemoteCallConfig {
          // 负载均衡注解实现服务消费者对Eureka服务拉取/订阅/发现
          @LoadBalanced
          @Bean
          public RestTemplate restTemplate() {
              log.info("restTemplate创建成功,微服务远程调用开启");
              return new RestTemplate();
          }
      }
      
    • 若是通过服务消费者(即order-service模块)的启动类中来获取到的RestTemplate所对应的Bean,则在启动类中的对应方法上添加负载均衡注解@LoadBalanced

      package at.guigu;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.cloud.client.loadbalancer.LoadBalanced;
      import org.springframework.context.annotation.Bean;
      import org.springframework.web.client.RestTemplate;
      
      @Slf4j
      @SpringBootApplication
      public class OrderApplication {
          public static void main(String[] args) {
              SpringApplication.run(OrderApplication.class, args);
              log.info("OrderApplication Running");
          }
          
          // 微服务远程调用
          // @LoadBalanced:负载均衡注解实现服务消费者对Eureka服务拉取/订阅/发现
          @Bean
          @LoadBalanced
          public RestTemplate restTemplate() {
              log.info("restTemplate创建成功,微服务远程调用开启");
              return new RestTemplate();
          }
      }
      
  • Step5: 重启服务消费者(即order-service模块)的启动类进行测试发现:服务消费者会自动利用负载均衡来挑选一个服务实例进行远程调用,如下两图所示

    注意:负载均衡规则默认为轮询规则(即依次调用服务实例) ,所以在测试访问时,8081以及8082端口均能访问到

    在这里插入图片描述

    在这里插入图片描述

Eureka配置文件常用相关配置

eureka:
  client:
    # 是否启用Eureka客户端(默认为true---启用Eureka)
    enabled: true
    service-url:
      # 配置eureka服务端的默认地址信息
      # 目的:将服务提供者(即user-service模块)的实例信息注册到Eureka服务端(即eureka-server注册中心)
      defaultZone: http://127.0.0.1:10086/eureka
    register-with-eureka: false # 是否将该服务的实例信息注册到Eureka(默认为true)
    fetch-registry: true # 是否从注册中心获取服务实例信息(默认为true)

Nacos注册中心

注意:Nacos演示推送到了分支nacos上,可自行在Gitee上下载

  • Nacos与Eureka一样,都是开源的注册中心框架,Nacos是国内产品,中文文档比较丰富,而且同时具备配置管理功能,因此在国内使用较多

Nacos下载安装与配置

Windows版本下载安装与配置

  • Step1: 通过Nacos的官网进入到GitHub,下载当前稳定版本,下载完成后将其解压到对应的目录下即可安装成功

    在这里插入图片描述

  • Step2: 进入到nacos的安装目录下的conf目录找到applications.properties配置文件,可根据自身需求更改端口号

    注意:默认端口号为8848,若该端口号已被占用,则可自行修改端口号

    在这里插入图片描述

  • Step3: 启动nacos

    注意:

    ​ 1.此处只演示单击启动,后续会演示集群启动

    ​ 2.方式一启动属于Nacos集群启动;方式二属于Nacos单点启动

    • 方式一:进入到nacos的安装目录下的bin目录,双击startup.cmd后即可 单机 启动

    在这里插入图片描述

    • 方式二:也可在startup.cmd所在目录下进入命令行窗口输入startup.cmd -m standalone命令来 单机 启动Nacos

      在这里插入图片描述

    • 启动成功后复制如图所示url到浏览器测试是否启动成功

      在这里插入图片描述

  • 启动成功后会自动进入到Nacos管理界面,若想要启动成功后输入用户名及密码进行登录,则:打开配置文件application.properties,修改配置文件中的用户名、密码即可

    在这里插入图片描述

    • 假设我的用户名和密码均为nacos,则此时Nacos启动成功后会先进入登录页面,如下所示

Linux版本下载安装与配置

注意:Linux版本直接从Docker官方仓库拉取对应的nacos镜像即可,步骤略,可详见Docker部分内容

  • 运行并创建配置Nacos的Docker容器的代码如下:

    docker run -d \
    --name nacos \
    --env-file ./nacos/custom.env \
    -p 8848:8848 \
    -p 9848:9848 \
    -p 9849:9849 \
    --network webb \
    --restart=always \
    nacos/nacos-server:v2.4.3
    

Nacos快速入门

  • Step1: 启动Nacos

    具体可详见 Nacos下载安装与配置 部分内容

  • Step2: 在聚合工程(即父工程)cloud-demo的pom文件中添加依赖

    • SpringCloudAlibaba依赖

      • 该依赖在创建聚合工程时博主已添加,若要查看SpringBoot、SpringCloud、SpringAlibaba的版本对应关系,可详见 版本兼容 部分内容
    • LoadBalancer依赖

      Nacos 自2021版本开始已经没有自带ribbon的整合,所以无法通过修改Ribbon负载均衡的模式来实现nacos提供的负载均衡模式,所以就需要引入另一个支持的jar包loadbalancer来实现对@LoadBalanced注解以及LoadBalancer负载均衡的支持

      <!--LoadBalancer依赖-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      </dependency>
      
  • Step3: 在服务提供者(即user-service模块)以及服务消费者(即order-service模块)的pom文件中添加Nacos的客户端依赖(即Nacos服务发现依赖)

    <!--nacos客户端服务管理依赖(即Nacos服务发现依赖)-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    

    注意:若服务提供者(即user-service模块)以及服务消费者(即order-service模块)的pom文件中即存在Eureka依赖,又存在Nacos依赖,则必须注释掉其中一种依赖,否则运行出错,具体可详见 LoadBalancer快速入门部分内容

  • Step4: 在服务提供者(即user-service模块)的配置文件中编写Nacos配置

    • 配置服务提供者或服务消费者的名称
      • 由于此时user-service模块充当的是服务提供者,所以name属性值即为服务提供者名
    • 配置Nacos的默认地址信息
      • 目的:将服务提供者(即user-service模块)的实例信息注册到Nacos

    注意:若存在Eureka地址配置,则必须注释掉

    server:
      port: 8081
    
    spring:
      application:
        # 指定服务提供者的名称
        name: userservice
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/cloud_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: root
        password: 123456
      cloud:
        nacos:
          discovery:
            # 是否启用Nacos的服务注册与发现功能(默认为rue---启用Nacos)
            enabled: true
            # 设置Nacos服务端地址
            server-addr: localhost:8848
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 是否开启下划线和驼峰映射, 默认为true
        map-underscore-to-camel-case: true
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.po
    
      global-config:
        db-config:
          # 全局id类型---即id生成策略
          # id-type: auto
          # 更新策略,默认为not_null
          # update-strategy: not_null
          # 设置数据库表名前缀
          table-prefix: tb_
    
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    
  • Step5: 在服务消费者(即order-service模块)的配置文件中编写Nacos配置

    • 配置服务提供者或服务消费者的名称
      • 由于此时order-service模块充当的是服务消费者,所以name属性值即为服务消费者名
    • 配置Nacos的默认地址信息

    注意:若存在Eureka地址配置,则必须注释掉

    server:
      port: 8080
    
    spring:
      application:
        # 指定服务消费者的名称
        name: orderservice
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/cloud_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: root
        password: 123456
      cloud:
        nacos:
          discovery:
            # 是否启用Nacos的服务注册与发现功能(默认为rue---启用Nacos)
            enabled: true
            # 设置Nacos服务地址
            server-addr: localhost:8848
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 是否开启下划线和驼峰映射, 默认为true
        map-underscore-to-camel-case: true
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.po
    
      global-config:
        db-config:
          # 全局id类型---即id生成策略
          # id-type: auto
          # 更新策略,默认为not_null
          # update-strategy: not_null
          # 设置数据库表名前缀
          table-prefix: tb_
    
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    
  • Step6: 更改服务消费者(即order-service模块)的业务层OrderServiceImpl类中queryOrderById方法的代码

    • 修改访问的url路径:用服务名代替ip以及端口号
    package at.guigu.service.impl;
    
    import at.guigu.mapper.OrderMapper;
    import at.guigu.po.Order;
    import at.guigu.po.User;
    import at.guigu.service.IOrderService;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    @Service
    public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
        private final RestTemplate restTemplate;
    
        public OrderServiceImpl(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
        }
    
        /**
         * 获取包含用户信息的订单信息
         * @param orderId
         * @return Order
         */
        @Override
        public Order queryOrderById(Long orderId) {
            // 1 查询订单信息
            Order order = this.getById(orderId);
            // 2 利用RestTemplate发起Http请求来获取用户数据
            /*
            // 2.1 设置url路径(存在url硬编码问题,不推荐)
            String url = "http://localhost:8081/user/" + order.getUserId();
            */
            // 2.1 设置url路径:用服务提供者的服务名代替ip以及端口号------解决url硬编码问题(推荐)
            String url = "http://userservice/user/" + order.getUserId();
            // 2.2 发送Http请求,实现远程调用
            User user = restTemplate.getForObject(url, User.class);
            // 3 封装用户数据存储到订单信息中
            order.setUser(user);
            return order;
        }
    }
    
  • Step8: 给创建RestTemplate对应的Bean的方法添加@LoadBalanced注解(该注解为负载均衡注解)

    注意:负载均衡注解@LoadBalanced作用是标记RestTemplate发起的请求要被Nacos负载均衡来拦截并处理,从而实现通过负载均衡来调用服务实例。(具体可详见负载均衡原理部分内容)

    • 若在服务消费者(即order-service模块)的config包下创建配置类RemoteCallConfig来获取的RestTemplate对应的Bean,则在该配置类对应的方法上添加负载均衡注解@LoadBalanced

      注意:@LoadBalanced注解并不存在于Nacos依赖中,所在需要在服务消费者(即order-service模块)的pom文件中导入LoadBalancer依赖,否则运行报错

      package at.guigu.config;
      
      import lombok.extern.slf4j.Slf4j;
      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;
      @Slf4j
      @Configuration
      public class RemoteCallConfig {
          // 负载均衡注解实现服务消费者对Nacos服务拉取/订阅/发现
          @LoadBalanced
          @Bean
          public RestTemplate restTemplate() {
              log.info("restTemplate创建成功,微服务远程调用开启");
              return new RestTemplate();
          }
      }
      
    • 若是通过服务消费者(即order-service模块)的启动类中来获取到的RestTemplate所对应的Bean,则在启动类中的对应方法上添加负载均衡注解@LoadBalanced

      package at.guigu;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.cloud.client.loadbalancer.LoadBalanced;
      import org.springframework.context.annotation.Bean;
      import org.springframework.web.client.RestTemplate;
      
      @Slf4j
      @SpringBootApplication
      public class OrderApplication {
          public static void main(String[] args) {
              SpringApplication.run(OrderApplication.class, args);
              log.info("OrderApplication Running");
          }
          
          // 微服务远程调用
          // @LoadBalanced:负载均衡注解实现服务消费者对Nacos服务拉取/订阅/发现
          @Bean
          @LoadBalanced
          public RestTemplate restTemplate() {
              log.info("restTemplate创建成功,微服务远程调用开启");
              return new RestTemplate();
          }
      }
      
  • Step9: 启动服务提供者(即user-service模块)以及服务消费者(即order-service模块)的启动类

    在这里插入图片描述

    • 刷新Nacos的管理页面,由该页面可看到服务注册成功

      注意:此时服务消费者也会将自己的服务实例注册到Nacos中,也就是说此时,服务消费者也是服务提供者

      在这里插入图片描述

  • Step10: PostMan测试,服务消费者会自动利用负载均衡来挑选一个服务实例进行远程调用,如下两图所示

    注意:负载均衡规则默认为轮询规则(即依次调用服务实例) ,所以在测试访问时,8081以及8082端口均能访问到

    在这里插入图片描述

    在这里插入图片描述

  • 注意

    • 使用Nacos进行注册时,不论服务提供者还是服务消费者,它们都会被注册到Nacos,也就是说此时,它们既是服务提供者也是服务消费者

Nacos服务分级存储与集群优先

一个服务可以有多个实例,例如我们的user-service模块,可以有以下6个服务实例

  • 127.0.0.1:8081
  • 127.0.0.1:8082
  • 127.0.0.1:8083
  • 127.0.0.1:8084
  • 127.0.0.1:8085
  • 127.0.0.1:8086

假设以上这些服务实例分布于全国各地不同的机房中

  • 127.0.0.1:8081------北京
  • 127.0.0.1:8082------北京
  • 127.0.0.1:8083------上海
  • 127.0.0.1:8084------上海
  • 127.0.0.1:8085------杭州
  • 127.0.0.1:8086------杭州、

此时Nacos就会将处于同一机房或同一地区的服务实例划分为一个集群。

也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型,如下图所示

在这里插入图片描述

微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。如下图所示,杭州机房内的order-service应该优先访问同机房的user-service,若同机房的不可用才会访问上海的user-service

在这里插入图片描述

  • Nacos服务分级存储模型有三级

    • 一级是服务
    • 二级是集群
    • 三级是实例
  • 集群配置

    • 为了更好的演示集群,博主将在创建一个服务提供者(即user-service模块)的实例信息(具体创建过程可详见 Eureka服务注册快速入门 部分内容),端口号为8083,如图所示

      在这里插入图片描述

    • 在服务提供者(即user-service模块)的配置文件中进行配置

      注意:

      spring.cloud.nacos.discovery下用来设置Nacos服务注册与发现的相关配置

      spring.cloud.nacos.config下用来设置Nacos配置中心相关配置(可详见Nacos配置中心部分的内容,此处只提一下)

      spring:
        application:
          # 指定服务提供者的名称
          name: userservice
        cloud:
          nacos:
            # 设置Nacos服务注册与发现相关配置
            discovery:
              # 是否启用Nacos的服务注册与发现功能(默认为rue---启用Nacos)
              enabled: true
              # 设置Nacos服务端地址
              server-addr: localhost:8848
              # 配置集群名称
              cluster-name: HZ
      
    • 重新运行服务提供者(即user-service模块)的启动类,刷新Nacos的管理页面,单击对应服务的详情进入到服务详细页面,即可看user-service服务的三个实例均属于同一个集群

      在这里插入图片描述

    • 假设现在想要更改8083的集群为SH,则在服务提供者(即user-service模块)的配置文件中进行配置

      spring:
        application:
          # 指定服务提供者的名称
          name: userservice
        cloud:
          nacos:
            # 设置Nacos服务注册与发现相关配置
            discovery:
              # 是否启用Nacos的服务注册与发现功能(默认为rue---启用Nacos)
              enabled: true
              # 设置Nacos服务端地址
              server-addr: localhost:8848
              # 配置集群名称
              cluster-name: SH
      
    • 然后仅仅重启端口号为8083的启动类 ,再次刷新Nacos的管理页面,单击对应服务的详情进入到服务详细页面,即可看user-service服务的两个实例分别属于不同的集群

      在这里插入图片描述

为了让服务消费者(即order-service模块)远程调用服务提供者(即user-service模块)时优先选择本地集群,所以也需要为服务消费者(即order-service模块)也配置一个集群属性,步骤如下

  • 在服务消费者(即order-service模块)的配置文件中进行配置

    注意:此处假设服务消费者(即order-service模块)处于HZ的集群中

    server:
      port: 8080
    
    spring:
      application:
        # 指定服务消费者的名称
        name: orderservice
      cloud:
        nacos:
          # 设置Nacos服务注册与发现相关配置
          discovery:
            # 是否启用Nacos的服务注册与发现功能(默认为rue---启用Nacos)
            enabled: true
            # 设置Nacos服务地址
            server-addr: localhost:8848
            cluster-name: HZ
    
    • 重新启动服务消费者(即order-service模块)的启动类,然后刷新Nacos的管理页面发现服务消费者(即order-service模块)成功加入到HZ集群

      在这里插入图片描述

    此时虽然服务消费者(即order-service模块)成功加入到HZ集群,但是还不能优先选择在同一集群中的服务消费者(即user-service模块)实例,因为负载均衡规则默认为ZoneAvoidanceRule,该规则并不能实现根据同集群优先来实现负载均衡;而Nacos中提供了一个NacosRule规则的实现,可以优先从同集群中挑选实例,步骤如下

  • 在服务消费者(即order-service模块)的配置文件中修改负载均衡规则为NacosRule

    # 自定义配置某个服务提供者的负载均衡策略,这里是userservice服务
    userservice: # 指定服务提供者的服务名
      ribbon:
        # 自定义负载均衡策略为NacosRule
        NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
    

    完整配置文件代码如下

    server:
      port: 8080
    
    spring:
      application:
        # 指定服务消费者的名称
        name: orderservice
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/cloud_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: root
        password: 123456
      cloud:
        nacos:
          # 设置Nacos服务注册与发现相关配置
          discovery:
            # 是否启用Nacos的服务注册与发现功能(默认为rue---启用Nacos)
            enabled: true
            # 设置Nacos服务地址
            server-addr: localhost:8848
            cluster-name: HZ
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 是否开启下划线和驼峰映射, 默认为true
        map-underscore-to-camel-case: true
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.po
    
      global-config:
        db-config:
          # 全局id类型---即id生成策略
          # id-type: auto
          # 更新策略,默认为not_null
          # update-strategy: not_null
          # 设置数据库表名前缀
          table-prefix: tb_
    
    # 自定义配置某个服务提供者的负载均衡策略,这里是userservice服务
    userservice: # 指定服务提供者的服务名
      ribbon:
        # # 自定义负载均衡策略为NacosRule
        NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
    
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug
        at.guigu: debug
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    

    注意:SpringCloud2020版本之前可用Ribbon来修改负载均衡规则为NacosRule,若使用的是SpringCloud2020以及更新的版本,则必须利用LoadBalancer来修改负载均衡规则为NacosRule

    此步骤只演示了SpringCloud2020版本之前用Ribbon来修改负载均衡规则为NacosRule的步骤;若使用的是SpringCloud2020以及更新的版本可详见 LoadBalancer自定义负载均衡策略 部分的内容

  • 重新启动服务消费者(即order-service模块)的启动类,然后利用PostMan进行测试

    • 博主进行了6次测试,由结果表明服务消费者(即order-service模块)同集群优先调用,如下两图所示

      在这里插入图片描述

      在这里插入图片描述

    • 服务消费者(即order-service模块)并未调用处不同集群的8083端口,如下图所示

      在这里插入图片描述

  • 假设此时与服务消费者(即order-service模块)处于统一集群的所有服务提供者均不可用,则这个时候它才会去使用处于不同集群的服务提供者的服务实例

    • 博主为了演示该步骤,将关闭8081以及8082端口对应的服务,只开启8083对应的服务,运行结果如下图所示

      在这里插入图片描述

Nacos服务实例权重配置

配置完Nacos服务分级存储以及集群优先后,服务消费者会优先同集群挑选。但在实际项目中可能会由于服务器的设备性能差异,导致部分实例所在机器性能较好,另一部分较差。此时如果处于同集群的服务性能比较差,我们想要使用处于同集群中性能较好的服务时就用到了Nacos服务实例权重

  • Nacos服务实例权重可通过Nacos管理页面来设置,如下所示

    在这里插入图片描述

    • 修改完成后截图如下

      在这里插入图片描述

      如上图所示,将端口号为8081的权重设置为了0.1,此时由于处于同集群的8082权重为1,相差10倍,所以8081被访问的概率会大大降低,而8082被访问的概率会大大增加

    • 利用PostMan进行6次请求测试,结果如下

      在这里插入图片描述

      在这里插入图片描述

  • 注意

    • 服务的默认权重大小均为1
    • 若服务权重设置为0,则该服务实例永远不会被访问
      • 可用于服务版本的平滑升级,用户是感知不到的

Nacos环境隔离

Nacos不仅是一个注册中心,还是一个数据中心。所以在Nacos中为了方便的做数据与服务的管理,就引入了环境隔离

在这里插入图片描述

  • Nacos的服务存储与数据存储的最外层都是一个名为namespace的东西,nacos中可以有多个namespace,用来彼此间的相互隔离(即用来做最外层隔离)。

    • namespace内可以有一个group属性------用来将不同服务或数据进行分组
    • group内可以有一个Service/Data属性------用来服务存储或数据存储
      • 服务在往下就是集群,集群在往下就是实例(即服务实例)
  • 默认情况下,所有的service、data、group都在同一个名为public的命名空间(即namespace)中

    在这里插入图片描述

具体步骤如下:

  • Step1: 按如图步骤创建命名空间(即namespace)

    在这里插入图片描述

    在这里插入图片描述

  • Step2: 回到服务列表,单击新创建的命名空间后会自动跳转到该命名空间的页面,有图可知此时为空

    在这里插入图片描述

  • Step3: 在服务消费者(即order-serice模块)的yml配置文件中配置命名空间

    spring:
      application:
        # 指定服务消费者的名称
        name: orderservice
      cloud:
        nacos:
          # 设置Nacos服务注册与发现相关配置
          discovery:
            # 是否禁用Nacos的服务注册与发现功能(默认为rue)
            # enabled: true
            # 设置Nacos服务地址
            server-addr: localhost:8848
            cluster-name: HZ
            # 配置命名空间,值为命名空间的ID
            namespace: 90ae0c6d-aeaf-44a8-833d-40575ac14a6b
    
  • Step4: 重启服务消费者(即order-serice模块)的启动类后刷新Nacos的管理页面,由下图可知,此时务消费者(即order-serice模块已处于名为dev的命名空间中

    在这里插入图片描述

  • 此时由于未将服务消费者(即user-service模块)也配置到名为dev的命名空间中(如图一所示),所以利用PostMan进行测试时会报错(如图二所示),因为处于不同的命名空间时会相互隔离,无法访问

    在这里插入图片描述

    在这里插入图片描述

Eureka与Nacos的区别

注意:不论是使用Eureka还是Nacos,都要先实现微服务远程调用中的步骤 ,在Eureka与Nacos的入门中均省略了该部分内容的步骤,具体可详见 微服务远程调用 的内容

Nacos的服务实例分为两种l类型:

  • 临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。

  • 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。

配置一个服务实例为永久实例:

spring:
  cloud:
    nacos:
      # 设置Nacos服务注册与发现相关配置
      discovery:
        ephemeral: false # 设置为非临时实例

Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:

在这里插入图片描述

  • Nacos与eureka的共同点

    • 都支持服务注册、服务周期性拉取
    • 都支持服务提供者心跳方式做健康检测
  • Nacos与Eureka的区别

    • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
    • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

Nacos配置中心

Nacos除了可以做注册中心,同样可以做配置管理来使用

在Nacos配置中心的所有示例中,服务提供者和服务消费者都在命名空间dev下来演示。如何将其均移到开发环境(即dev命名空间)下可详见 Nacos环境隔离 部分的内容

本部分演示已上传至Gitee,可自行下载

统一配置管理

当在Nacos注册中心部署的微服务实例越来越多,达到数十、数百时,逐个修改微服务配置就会很容易出错,就算不出错也要在配置完成后来进行逐个重启,这样就会很麻烦。所以就引入了一种统一配置管理方案,可以集中管理所有实例的配置,同时在配置发生变更时,能够及时通知微服务,从而实现配置的热更新(即不用重启服务,配置会自动生效)

在这里插入图片描述

在Nacos配置中心中添加配置信息的步骤如下

  • Step1: 单击配置列表,然后按图示步骤操作

    在这里插入图片描述

  • Step2: 单击创建配置,然后Nacos会创建一个在指定命名空间下的配置文件,然后在该配置文件中进行相关配置后单击发布即可

    注意:

    ​ 博主使用的是yml配置文件,而yml是yaml的缩写,所以在配置文件id的后缀名为yaml,且配置格式为yaml

    ​ Nacos配置中心的配置格式目前支持yaml以及properties

    在这里插入图片描述

    创建完成之后,Nacos配置中心的配置管理页面对应的命名空间下即出现相关配置文件,如下图所示

    在这里插入图片描述

  • Step3: 删除服务消费者(即user-service模块)的application.yml配置文件中已配置到Nacos配置中心的配置文件中的相关热更新需求的配置代码

微服务拉取配置

在Nacos配置中心完成含有配置信息的配置文件的添加后,微服务要从Nacos管理中心拉取相关配置配件中的配置然后与本地的application.yml配置文件合并之后才能完成项目的启动

为了完成项目的启动spring引入了一种新的配置文件(即bootstrap.yaml文件),该文件会在本地的application.yml配置文件之前被读取,流程如下图所示

在这里插入图片描述

  • Step1: 在服务提供者(即user-service模块)的pom文件中引入nacos-config的客户端依赖

    <!--nacos客户端配置管理依赖(即Nacos配置发现依赖)-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    

    此时服务提供者(即user-service模块)的pom文件完整代码如下

    <?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">
        <parent>
            <artifactId>cloud-demo</artifactId>
            <groupId>cn.itcast.demo</groupId>
            <version>1.0</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>user-service</artifactId>
    
        <!--配置依赖-->
        <dependencies>
            <!--nacos客户端服务管理依赖(即Nacos服务发现依赖)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!--nacos客户端配置管理依赖(即Nacos配置发现依赖)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            </dependency>
        </dependencies>
    </project>
    

    注意:引入该依赖后必须存在bootstrap.yaml文件,否则服务无法启动成功,报错如下图所示,此时有两种解决方式:

    ​ 方式一:创建bootstrap.yaml文件,并写入相关配置,具体可详见第二步

    ​ 方式二:在application.yml配置文件中设置spring.cloud.nacos.config.import-check.enabled=false来禁用配置文件检查

    在这里插入图片描述

  • Step2: 在服务提供者(即user-service模块)的源代码配置文件目录(即资源文件resources)下创建配置文件bootstrap.yml,代码如下:

    注意:由于博主使用的是yml格式,所以创建yml格式的bootstrap配置文件。在实际项目中可根据使用的配置文件格式来创建一样格式的bootstrap配置文件

    spring:
      application:
        name: userservice # 服务名称(即Nacos配置文件id格式的服务名)
      profiles:
        active: dev #开发环境(即Nacos配置文件id格式的profile),这里是dev 
      cloud:
        nacos:
          # 设置Nacos配置中心相关配置
          config:
            file-extension: yaml # Nacos配置文件id格式的后缀名(即文件后缀名)
            server-addr: localhost:8848 # Nacos地址
    

    注意:该配置文件的目的就是去读取Nacos配置中心的配置文件userservice-dev.yaml

    在这里插入图片描述

    • 由于Nacos配置中心的配置文件是在dev的命名空间下,所以需要在bootstrap.yaml配置文件中添加命名空间,最终该配置文件代码如下:

      spring:
        application:
          name: userservice # 服务名称(即Nacos配置文件id格式的服务名)
        profiles:
          active: dev #开发环境(即Nacos配置文件id格式的profile),这里是dev
        cloud:
          nacos:
            # 设置Nacos配置中心相关配置
            config:
              file-extension: yaml # Nacos配置文件id格式的后缀名(即文件后缀名)
              server-addr: localhost:8848
              namespace: 90ae0c6d-aeaf-44a8-833d-40575ac14a6b
      
  • Step3: 删除服务提供者(即user-service模块)的application.yml配置文件中与Nacos中心配置的对应配置文件中的配置信息以及bootstrap.yaml配置文件相同部分的代码

    在这里插入图片描述

    此时服务提供者(即user-service模块)的application.yml配置文件代码如下

    server:
      port: 8081
    
    spring:
    #  application:
    #    # 指定服务提供者的名称
    #    name: userservice
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/cloud_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: root
        password: 123456
      cloud:
        nacos:
          # 设置Nacos服务注册与发现相关配置
          discovery:
            # 是否禁用Nacos的服务注册与发现功能(默认为rue)
            enabled: true
            # 设置Nacos服务端地址
            server-addr: localhost:8848
            cluster-name: HZ
            namespace: 90ae0c6d-aeaf-44a8-833d-40575ac14a6b
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 是否开启下划线和驼峰映射, 默认为true
        map-underscore-to-camel-case: true
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.po
    
      global-config:
        db-config:
          # 全局id类型---即id生成策略
          # id-type: auto
          # 更新策略,默认为not_null
          # update-strategy: not_null
          # 设置数据库表名前缀
          table-prefix: tb_
    
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug
        at.guigu: debug
      # 配置日志输出的时间戳格式
    #  pattern:
    #    dateformat: MM-dd HH:mm:ss:SSS
    
  • Step4: 在服务提供者(即user-service模块)的UserController方法中添加业务逻辑来测试是否能够正常读取Nacos配置中心的对应配置文件中的pattern.dateformat配置,完整代码如下:

    package at.guigu.controller;
    
    import at.guigu.po.User;
    import at.guigu.service.IUserService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/user")
    public class UserController {
        private final IUserService userService;
    
        @Value("${pattern.dateformat}")
        private String dateformat;
    
        @GetMapping("now")
        public String now(){
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
        }
    
        @GetMapping("/{id}")
        public User queryUserById(@PathVariable("id") Long id) {
            return userService.getById(id);
        }
    }
    
  • 重新启动服务提供者(即user-service模块)的启动类,报如下图错误

    在这里插入图片描述

    • 原因是:在SpringCloud2020版本及该版本之后已不在提供对bootstrap的支持,所以应该在服务消费者(即user-service模块)的pom文件中引入bootstrap依赖,此时完整的pom文件代码如下

      <?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">
          <parent>
              <artifactId>cloud-demo</artifactId>
              <groupId>cn.itcast.demo</groupId>
              <version>1.0</version>
          </parent>
          <modelVersion>4.0.0</modelVersion>
      
          <artifactId>user-service</artifactId>
      
          <!--配置依赖-->
          <dependencies>
              <!--nacos客户端依赖(即Nacos服务发现依赖)-->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
              </dependency>
              <!--nacos配置客户端依赖(即Nacos配置发现依赖)-->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
              </dependency>
              <!--bootstrap依赖-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-bootstrap</artifactId>
                  <version>3.1.5</version>
              </dependency>
      
          </dependencies>
      </project>
      
    • 重新运行启动类后若仍报上图所示错误,则需要明确指定出在Nacos配置中心要使用的配置文件名称及后缀

    • 解决方式:在bootstrap.yaml文件中添加代码:spring.config.import=optional:nacos:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension},此时bootstrap.yaml配置文件完整代码如下

      spring:
        application:
          name: userservice # 服务名称(即Nacos配置文件id格式的服务名)
        profiles:
          active: dev #开发环境(即Nacos配置文件id格式的profile),这里是dev
        cloud:
          nacos:
            # 设置Nacos配置中心相关配置
            config:
              file-extension: yaml # Nacos配置文件id格式的后缀名(即文件后缀名)
              server-addr: localhost:8848
              namespace: 90ae0c6d-aeaf-44a8-833d-40575ac14a6b
        config:
          import:
            - optional:nacos:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
      
    • 若仍有问题则检查bootstrap.yml配置文件中是否配置了命名空间、组等内容,若配置了就检查其与Nacos配置中心的对应配置文件是否在同一个命名空间的分组下即可

  • 运行端口号为8081的服务消费者(即user-service模块)的启动类进行测试

    • 由运行结果可知,此时已可正常读取Nacos配置中心的对应配置文件中的相关配置信息

    在这里插入图片描述

配置热更新

在微服务拉取配置中,虽然已经实现了从Nacos配置中心来拉取对应配置文件中的配置信息,但是若Nacos配置中心的对应配置文件中的配置信息更改后,微服务是无法做到实时更新的,除非重启微服务,这在实际项目中就会造成很大的问题,为解决该问题就引入了配置热更新功能

  • 配置热更新:修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新

实现配置热更新方式一

  • Step1: 在使用@Value注解来注入Nacos配置中心对应的配置文件中的配置信息的类上添加@RefreshScope,此时UserController类的完整代码如下

    package at.guigu.controller;
    
    import at.guigu.po.User;
    import at.guigu.service.IUserService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    @RestController
    @RequiredArgsConstructor
    // 实现Nacos配置中心的配置热更新
    @RefreshScope
    @RequestMapping("/user")
    public class UserController {
        private final IUserService userService;
    
        @Value("${pattern.dateformat}")
        private String dateformat;
    
        @GetMapping("/now")
        public String now(){
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
        }
    
        @GetMapping("/{id}")
        public User queryUserById(@PathVariable("id") Long id) {
            return userService.getById(id);
        }
    }
    
  • Step2: 重启服务提供者(即user-service模块)端口为8081的启动类,然后利用PostMan进行测试

    • 未更改Nacos配置中心对应的配置文件中的相关配置信息时,运行结果如下

      在这里插入图片描述

    • 现在在保证微服务不重启的情况下,更改Nacos配置中心对应的配置文件中的相关配置信息后(如图一所示),再次用PostMan进行测试,由运行结果可知(如图一所示),实现了配置的热更新

      在这里插入图片描述

      在这里插入图片描述

实现配置热更新方式二

  • Step1: 在服务提供者(即user-service模块)中创建一个config包,然后再该包下创建一个获取Nacos配置中心对应配置文件的相关配置信息的配置类NacosConProperties,代码如下

    • 使用@Component注解将该类添加到Spring容器中

    • 使用@Data注解实现getter、setter方法

    • 使用@ConfigurationProperties来指定要获取的属性的前缀名

    • 在该方法内部定义要获取的属性的变量名

      注意:只要前缀名.变量名拼接后是相关配置信息就能获取到对应的值

    package at.guigu.config;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    @Component
    @Data
    @ConfigurationProperties(prefix = "pattern")
    public class NacosConProperties {
        private String dateformat;
    }
    
  • Step2: 在服务提供者(即user-service模块)的业务逻辑相关的类UserController中依赖注入该类(即NacosConProperties的bean),然后利用该类来获取Nacos配置中心对应配置文件中的配置信息,代码如下

    package at.guigu.controller;
    
    import at.guigu.config.NacosConProperties;
    import at.guigu.po.User;
    import at.guigu.service.IUserService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    @RestController
    @RequiredArgsConstructor
    // 实现Nacos配置中心的配置热更新
    @RefreshScope
    @RequestMapping("/user")
    public class UserController {
        private final IUserService userService;
        private final NacosConProperties nacosConProperties;
    
        @GetMapping("/now")
        public String now(){
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern(nacosConProperties.getDateformat()));
        }
    
        @GetMapping("/{id}")
        public User queryUserById(@PathVariable("id") Long id) {
            return userService.getById(id);
        }
    }
    
  • Step3: 重启服务提供者(即user-service模块)端口为8081的启动类,然后利用PostMan进行测试

    • 未更改Nacos配置中心对应配置文件的相关配置信息时,运行结果如下

      在这里插入图片描述

    • 现在在保证微服务不重启的情况下,修改Nacos配置中心对应配置文件中的相关配置信息后(如图一所示),再次用PostMan进行测试,由运行结果可知(如图一所示),实现了配置的热更新

      在这里插入图片描述

      在这里插入图片描述

多环境配置共享

假设现在微服务有个配置信息在开发测试生产三个环境下的值是一样的,此时若在不同环境的配置文件中都进行相关配置就会造成高耦合的缺点,为避免该问题就引入了多环境配置共享

  • 在实现了拉取配置以及热更新后,微服务在启动时会自动从Nacos配置中心读取多个不同格式配置文件:

    • 格式一${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension},比如:userservice-dev.yml、userservice-test.yml等
    • 格式二${spring.application.name}.${spring.cloud.nacos.config.file-extension},比如:userservice.yml
    • 不论Nacos配置中心中格式一对应的配置文件如何变化,与格式二相关的配置文件一定会被加载。因此,可以在与格式二相关的配置文件中写入多环境共享的配置信息
  • 配置文件优先级

    在这里插入图片描述

    • Nacos配置中心的格式一的配置文件优先级高于格式二的配置文件优先级
    • Nacos配置中心的配置文件优先级高于服务本地的配置文件优先级

实现步骤如下:

  • Step1: 在Nacos配置中心中新建一个配置文件userservice.yaml文件

    在这里插入图片描述

    • 创建完成之后Nacos配置中心的dev命名空间下就有了两种配置格式的文件

      在这里插入图片描述

  • Step2: 在服务提供者(即user-service模块)获取Nacos配置中心对应配置文件的相关配置信息的配置类NacosConProperties中添加新的属性来获取多环境共享配置文件中的属性,代码如下

    package at.guigu.config;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    @Component
    @Data
    @ConfigurationProperties(prefix = "pattern")
    public class NacosConProperties {
        // userservice-dev.yaml文件中的属性
        private String dateformat;
        // userservice.yaml文件中的属性
        private String envSharedValue;
    }
    
  • Step3: 在服务提供者(即user-service模块)的业务逻辑相关的类UserController中依赖注入该类(即NacosConProperties的bean),然后利用该类来获取Nacos配置中心对应两个配置文件中的配置信息,代码如下

    package at.guigu.controller;
    
    import at.guigu.config.NacosConProperties;
    import at.guigu.po.User;
    import at.guigu.service.IUserService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    @RestController
    @RequiredArgsConstructor
    // 实现Nacos配置中心的配置热更新
    @RefreshScope
    @RequestMapping("/user")
    public class UserController {
        private final IUserService userService;
        private final NacosConProperties nacosConProperties;
    
        @GetMapping("/now")
        public String now(){
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern(nacosConProperties.getDateformat()));
        }
        // 直接返回NacosConProperties类的对象给前端页面,前端会自动将其转为JSON数据
        @GetMapping("nacosConProp")
        public NacosConProperties nacosConProperties() {
            return nacosConProperties;
        }
    
        @GetMapping("/{id}")
        public User queryUserById(@PathVariable("id") Long id) {
            return userService.getById(id);
        }
    }
    
  • Step4: 为了更好的演示多环境配置共享,博主将启动服务提供者(即user-service模块)端口号为8081和8082两个端口的启动类,且8081为开发环境,8082为测试环境

    • 设置8081为开发环境

      由于已在bootstrap.yml配置文件中设置了环境为dev开发环境,所以8081和8082启动后会默认在开发环境下,所以此处不用设置8081为开发环境,只需要更改8082为测试环境即可

    • 设置8082为测试环境步骤如图所示

      注意:设置8082为测试环境之后,启动8082端口的启动类后,该服务是无法读取到userservice-dev.yaml配置文件的,因为环境不同。但是它能读取到多环境共享的配置文件userservice.yaml

      在这里插入图片描述

  • Step5: 启动端口号为8081和8082服务的启动类,然后利用PostMan分别测试两个端口对应的服务

    • 8081读取到了Nacos配置中心的两个配置文件(即userservice-dev.yaml和userservice.yaml)中的配置信息

      在这里插入图片描述

    • 8082只读取到了Nacos配置中心的配置文件userservice.yaml中的配置信息

      在这里插入图片描述

Nacos集群搭建

注意:

​ 在之前关于Nacos的演示中使用的均为单点Nacos部署。而在实际项目中使用的基本均为Nacos集群搭建,因为这样具有高可用性

​ 集群搭建示例已上传至nacosCon分支,可自行下载

在这里插入图片描述

  • 原理
    • 用户请求到后端后,首先会通过负载均衡器来将请求分发到不同的Nacos节点来形成一个集群结构,然后多个Nacos都访问同一个MySQL集群从而实现数据共享
    • 负载均衡可通过Nginx来实现。因为Nginx不仅可以实现负载均衡,还可以实现反向代代理

为了模拟集群搭建,博主将模拟三个Nacos节点,不同Nacos节点的端口分别为:8845、8846、8847。环境模拟步骤如下

  • Step1: 将Nacos压缩包解压到任意非中文目录下

    在这里插入图片描述

  • Step2: 将conf目录下的配置文件cluster.conf.example重命名为cluster.conf,然后添加每个Nacos集群中每个Nacos节点的ip地址和端口号

    注意:由于博主是在本地来进行集群搭建演示的,所以ip地址均为本地ip

    127.0.0.1:8845
    127.0.0.1:8847
    127.0.0.1:8849
    

    在这里插入图片描述

  • Step3: 修改conf目录下的配置文件application.properties:增加支持MySQL数据源配置,添加MySQL数据源的url、用户名和密码

    • 模板代码如下(具体可详见官网中的集群模式部署)

      注意:

      ​ 1.Nacos默认有一个内嵌数据库来实现数据的存储,该内嵌数据库存在弊端(即不方便观察数据存储的基本情况),为了解决该不断就引入了内嵌数据库的功能

      ​ 2.该步骤的目的是使Nacos使用外嵌数据库MySQL,方便管擦和数据存储的情况

      ​ 3.不论使用的是Nacos单点模式还是集群模式,都可以使用外嵌数据库MySQL

      # 指定使用mysql数据源配置(注意:在老版本中使用spring.datasource.platform=mysql)
      spring.sql.init.platform=mysql
      # 指定集群中MySQL的数量
      db.num=1
      # 配置数据库url以及用户密码
      db.url.0=jdbc:mysql://${mysql_host}:${mysql_port}/${nacos_database}?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
      db.user.0=${mysql_user}
      db.password.0=${mysql_password}
      

      注意:在实际项目中可配置MySQL集群,在配置文件中用db.url.0表示第一个数据库;db.url.1表示第二个数据库,依次类推。在本示例中只配置了一个外嵌数据库

      在这里插入图片描述

  • Step4: 将nacos目录复制三分,分别命名为:nacos1、nacos2、nacos3

    在这里插入图片描述

  • Step5: 分别进入到三个nacos目录下的conf目录下的配置文件application.properties,将它们的端口号分别修改为8845、8847、8849,即server.port=端口号

    在这里插入图片描述

  • Step6: 分别进入到nacos1、nacos2、nacos3目录下的bin目录,然后cmd打开命令行窗口,直接输入命令startup.cmd来启动nacos1、nacos2、nacos3

    注意:Linux、Windows、Ubuntu的启动方式都是不一样的,具体可详见官网中的集群模式部署

    • 在启动时若报错Address already in use: bind,则说明端口号已被其它进程占用,可结束占用该端口的进程或修改端口号

    在这里插入图片描述

  • 注意:若Nacos集群启动时,本地内存不够则解决方法:利用文本编辑器打开bin目录下的startup.cmd,然后将Nacos集群启动时所用的内存改小即可,如图所示

    在这里插入图片描述

集群搭建步骤如下

  • Step1: 搭建Nacos数据库,初始化数据库表结构

    注意:

    ​ 官网已准备了数据库表的初始化的SQL文件,可官网自行下载

    ​ 本步骤详细过程省略,创建完Naocs数据库及表后,结构如下图所示

    在这里插入图片描述

  • Step2: 配置Nacos(可详见模拟集群搭建的步骤)

  • Step3: 启动Nacos集群(可详见模拟集群搭建的步骤)

  • Step4: Nginx反向代理

    • Step4-1: 去Nginx官网下载Nginx压缩包

      在这里插入图片描述

    • Step4-2: 将Nginx解压到自己想要安装的目录下,然后找到Nginx中的conf目录,打开nginx.conf文件

      在这里插入图片描述

    • Step4-3: 在nginx.conf文件中配置以下代码

      注意:以下代码必须粘贴到 http代码块 内部

      upstream nacos-cluster {
      	server 127.0.0.1:8845;
      	server 127.0.0.1:8847;
      	server 127.0.0.1:8849;
      }
      server {
          listen       80;
          server_name  localhost;
      
          location /nacos {
          proxy_pass   http://nacos-cluster;
          }
      }
      

      在这里插入图片描述

    • Step4-4: 在nginx.conf文件中配置grpc通讯端口

      注意:

      ​ 1.以下代码与 http代码块 同级

      ​ 2.在springboot3.x版本中的gRPC通讯端口存在1000的偏移量,所以需要配置一下rpc通讯端口;而在springboot2.x版本中的gRPC通讯端口不存在偏移量,所以就不需要该4-4步骤

      ​ 3.该4-4步骤可先省略,若最后运行失败再添加该步骤

      stream {
          upstream nacos-grpc{
              server 127.0.0.1:9845;
      		server 127.0.0.1:9847;
      		server 127.0.0.1:9849;
          }
          server {
              listen       1080;
              proxy_pass   nacos-grpc;
          }
      }
      

      在这里插入图片描述

    • Step4-5: 然后到Nginx目录下,打开cmd命令行窗口,然后输入命令start nginx.exe,来启动Nginx

      注意:若自己已有Nginx,且已启动,则在Nginx目录下执行nginx.exe -s reload命令重启生效即可

      在这里插入图片描述

  • Step5: 打开浏览器输入localhost/nacos进行测试

    注意:Nginx会通过负载均衡自动从Nacos集群中挑选一个Nacos节点进行访问

    在这里插入图片描述

  • Step6: 将微服务的所有配置文件中Nacos的地址更改为localhost:80

    在这里插入图片描述

  • Step7: 运行服务提供者以及服务消费者的启动类,然后刷新Nacos

    在这里插入图片描述

  • 博主使用的是SpringBoot3.x版本的,假设我未配置gRPC偏移量,则运行服务提供者或服务消费者的启动类时,会报如图所示gRPC客户端错误

    原因:SpringBoot3.x版本存在gRPC偏移量,且偏移量为1000。若不在Nginx的配置文件中配置gRPC偏移量的话,Nginx默认的80端口就无法映射到1080端口,导致服务提供者或服务消费者无法通过Nginx的负载均衡来添加到Nacos注册中心或配置中心上

    在这里插入图片描述

负载均衡

负载均衡实例均已Eureka为基础进行,并未使用Nacos,不过它们的原理是一样的

  • 主流的负载方案主要有两种

    负载方案解释
    集中式负载均衡(服务端负载均衡)在服务提供者和服务消费者之间使用独立的代理方式进行负载,
    客户端负载均衡客户端根据自己的请求情况自己做负载均衡,比如Ribbon
    • 集中式负载均衡(服务端负载均衡)的中间层可以通过硬件或软件来实现
      1. 若通过硬件实现,则硬件是直接跟交换机进行连接,比如:F5
        • 因为任何请求进来都是先进入到交换机
        • 性能高但成本也高(一线大厂用)
      2. 若通过软件实现,则需要先安装一个负载均衡的软件,比如:Nginx
        • 性能低,但成本也低
  • 常见的负载均衡算法

    常见的负载均衡算法解释
    随机通过随机选择服务进行执行,一般这种方式使用较少
    轮询负载均衡默认实现方式,请求来之后排队处理
    加权轮询通过对服务器性能的分型,给高配置,低负载的服务器分配更高的权重,均衡各个服务器的压力
    地址Hash通过客户端请求的地址的HASH值取模映射进行服务器调度。ip ->hash
    最小链接数即使请求均衡了,压力不一定会均衡,最小连接数法就是根据服务器的情况,比如请求积压数等参数,将请求分配到当前压力最小的服务器上。 最小活跃数
  • 注意:

    • 在设置负载均衡之前必须首先实现服务的注册与发现,具体步骤可详见 服务注册与发现 部分内容

Ribbon负载均衡

注意:Ribbon负载均衡演示在主分支上,可自行在Gitee上下载

  • Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具,Ribbon客户端组件提供了一系列完善的配置,比如超时、重试等等。
    • 通过Load Balancer获取到服务提供的所有机器实例后,Ribbon会自动根据某种负载均衡策略(比如:轮询、随机等)去调用这些服务
    • Ribbon也可以实现我们自己的负载均衡算法

注意:Ribbon 负载均衡是由Spring Cloud Netflix 提供的客户端负载均衡器,已在新版 Spring Cloud 中被弃用,SpringCloud2020版本之前可用Ribbon负载均衡

若想要测试则对应的SpringBoot、SpringCloud、SpringCloudAlibaba的版本分别为:2.3.9.RELEASE、Hoxton.SR10、2.2.5.RELEASE

  • 负载均衡流程简述

    在这里插入图片描述

    服务消费者通过url发起请求后,Ribbon负载均衡会通过负载均衡拦截器(LoadBalancerInterceptor)会拦截该请求,然后根据该请求中的服务名从Eureka服务端(即eureka-server注册中心)中来拉取该服务名对应的服务实例信息,最后Ribbon负载均衡会根据载均衡规则来调用服务实例

    负载均衡规则默认为ZoneAvoidanceRule,是一种轮询方案

  • 负载均衡流程详述(SpringCloud2020版本之前)

    在这里插入图片描述

    服务消费者通过url发起请求后,Ribbon负载均衡会通过负载均衡拦截器(LoadBalancerInterceptor)会拦截该请求,然后将该请求传给Ribbon负载均衡器客户端(RibbonLoadBalancerClient类),而Ribbon负载均衡器客户端(RibbonLoadBalancerClient类)获取到url中的服务id(即服务名)后会将其传给动态服务列表负载均衡器(即DynamicServerListLoadBalance

    然后,动态服务列表负载均衡器(即DynamicServerListLoadBalance)就会根据服务名从Eureka服务端(即eureka-server注册中心)中来拉取该服务名对应的服务实例信息,并将其传给IRule,IRule会利用内置的负载均衡规则来选取其中一个返回给负载均衡拦截器(LoadBalancerInterceptor

    负载均衡拦截器(LoadBalancerInterceptor)获取到真实的ip地址和端口号后就会将url替换为真实的url来访问服务提供者,从而获取到相应数据

负载均衡原理

  • 负载均衡是通过@LoadBalanced注解实现的

    • 作用:标记RestTemplate发起的请求要被Ribbon负载均衡来拦截并处理,从而实现通过负载均衡来调用服务实例
  • 拦截的动作是通过实现ClientHttpRequestInterceptor接口的实现类LoadBalancerInterceptor来完成的

    • ClientHttpRequestInterceptor接口中只有一个方法ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException,主要用来拦截RestTemplate发起的请求

    • 实现类LoadBalancerInterceptor中具体定义了该方法,代码如下

      public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
              URI originalUri = request.getURI();
              String serviceName = originalUri.getHost();
              Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
              return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
          }
      

源码跟踪

DUGEG测试:给URI originalUri = request.getURI();这句代码打上断点进行DEBUG测试

在这里插入图片描述

LoadBalancerIntercepor

  • DEBUG运行,PostMan发送请求后会自动跳转到断点处,可看出LoadBalancerIntercepor类(即负载均衡拦截器)拦截了用户的户的HttpRequest请求,然后在该方法内部做了如下几件事:

    • request.getURI():获取请求uri,本例中就是 http://userservice/user/1
    • originalUri.getHost():获取uri路径的主机名,其实就是服务id(即服务名userservice
    • this.loadBalancer.execute():处理服务id,和用户请求。

    在这里插入图片描述

  • 运行到最后一行代码时,可看到使用的是RibbonLoadBalancerClient对象(Ribbon负载均衡客户端对象)的execute方法来处理服务id和用户请求(如下图所示)

    注意:在Spring Cloud 2020.0版本之前使用的是RibbonLoadBalancerClient对象的execute方法,但是在Spring Cloud 2020.0版本之后,RibbonLoadBalancerClient就被弃用了,它被BlockingLoadBalancerClient代替

    在这里插入图片描述

LoadBalancerClient接口的实现类RibbonLoadBalancerClient

  • 进入到execute方法后

    在这里插入图片描述

  • 继续进入到该方法对应的重载方法execute中,然后debuge完如图所示代码后发现会获取一个动态服务列表负载均衡器对象(即DynamicServerListLoadBalance),该对象中包含了服务消费者实例所对应的所有端口号

    • getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来

    • getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8081端口的服务

    在这里插入图片描述

  • 既然获取到了端口号,那么下一行代码Server server = getServer(loadBalancer, hint)就是通过负载均衡来获取其中一个服务实例,所以进入到getServer方法,发现该方法也是在RibbonLoadBalancerClient类(Ribbon负载均衡器客户端)中,且该方法调用了chooseServer方法

    在这里插入图片描述

LoadBalancerClient接口的实现类ZoneAwareLoadBalancer

  • 进入到chooseServer方法中,进入后,发现它调用了其父类中的chooseServer方法

    在这里插入图片描述

  • 进入到其父类的chooseServer方法后DEBUG到return rule.choose(key)发现其调用rule对象中的choose方法来返回一个ZoneAvoidanceRule对象(默认负载均衡规则对象)

    在这里插入图片描述

IRule接口

  • 定位到rule发现rule对象是IRule接口的对象,然后查看该接口的实现类发现,其有四个实现类

    • 这四个实现类就是负载均衡已有的规则
    • 这说明负载均衡规则由IRule接口来实现,若想要自定义负载均衡规则,则实现该接口并重写该接口中的方法即可

    在这里插入图片描述

  • 回到Server server = getServer(loadBalancer, hint)并DEBUG该行代码,此时会根据默认的负载均衡规则来获取到将要使用的端口号

    在这里插入图片描述

  • 然后即可使用真实的ip地址和端口号来代替url了

自定义默认负载均衡策略

  • Ribbon的负载均衡规则是通过IRule接口定义的,每个子接口就是一种规则,IRule接口的关系图如下

    在这里插入图片描述

    • IRule接口是所有负载均衡策略的父接口,该接口的核心方法为choose方法,用来选择一个服务实例
    • AbstractLoadBalancerRule抽象类:该抽象类中定义了一个IloadBalancer来辅助负责均衡策略选取合适的服务端实例
  • 常见的负载均衡策略

    默认的实现就是ZoneAvoidanceRule,是一种轮询方案

    可通过自定义负载均衡策略来指定要使用的内置负载均衡策略

    内置负载均衡规则类解释
    ZoneAvoidanceRule(默认规则)以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
    RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
    RetryRule重试机制的轮询选择逻辑
    WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
    BestAvailableRule忽略那些短路(即失效)的服务器,并选择并发数较低的服务器。
    RandomRule随机选择一个可用的服务器。
    AvailabilityFilteringRule对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的<cllientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。
    • RetryRule是在RoundRobinRule的基础上进行重试机制
      • 若通过RoundRobinRule轮询选择到的服务实例为null或已失效时,在客户端请求未超时 的情况下会重新通过RoundRobinRule轮询选择服务
    • WeightedResponseTimeRule
      • 若一个服务的平均响应时间越短则权重越大,则该服务实例被选中的概率也就越大
      • 若想要手动重新配置权重策略,则需要通过nacos的NacosRule来设置
    • AvailabilityFilteringRule:先过滤掉故障服务器,再选择并发数较低的服务器。

自定义默认负载均衡策略主要有两种方法

方法一(全局配置):在服务消费者(即order-service模块)中定义一个新的IRule接口的对象

  • 方法一有两种解决方案:

    • 方案一: 在config包下创建一个配置类LoadBalancerConfig,然后在该类中定义randomRule方法,代码如下

      注意:方案一的配置类必须放在启动类所能扫描到的包下,否则全局配置就无法生效,如图所示

      在这里插入图片描述

      package at.guigu.config;
      
      import com.netflix.loadbalancer.IRule;
      import com.netflix.loadbalancer.RandomRule;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Slf4j
      @Configuration
      public class LoadBalancerConfig {
          @Bean
          public IRule randomRule() {
              log.info("成功自定义负载均衡规则");
              // 返回自定义要使用的负载均衡策略的对象
              return new RandomRule();
          }
      }
      
    • 方案二: 在服务消费者(即order-service模块)的启动类中定义randomRule方法,代码如下

      package at.guigu;
      
      import com.netflix.loadbalancer.IRule;
      import com.netflix.loadbalancer.RandomRule;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.annotation.Bean;
      
      @Slf4j
      @SpringBootApplication
      public class OrderApplication {
          public static void main(String[] args) {
              SpringApplication.run(OrderApplication.class, args);
              log.info("OrderApplication Running");
          }
          @Bean
          public IRule randomRule(){
              // 自定义负载均衡规则
              // 返回自定义要使用的负载均衡策略的对象
              return new RandomRule();
          }
              /*
          // 微服务远程调用
          // 由于已在config包下创建相关配置类RemoteCallConfig,所以此处注掉,两者功能相同
          // @LoadBalanced:负载均衡注解实现服务消费者对Eureka服务拉取/订阅/发现
          @Bean
          @LoadBalanced
          public RestTemplate restTemplate() {
              log.info("restTemplate创建成功,微服务远程调用开启");
              return new RestTemplate();
          }
           */
      
      }
      

方法二(局部配置)

  • 方法二也有两种解决方案

    • 方案一: 在全局配置的方案一中已经明确说明,如果Ribbon配置类LoadBalancerConfig并不在服务消费者(即user-service模块)的启动类能扫描到的包下的话,此时就变为了局部配置,如图所示

      在这里插入图片描述

      • Step1: 在启动类所能扫描到的包之外创建一个ribbon包,并在该包下创建一个配置类LoadBalancerConfig,然后在该类中定义iRule方法,代码如下

        注意:此时方法名必须为iRule,否则局部配置失效

        package at.guigu.config;
        
        import com.netflix.loadbalancer.IRule;
        import com.netflix.loadbalancer.RandomRule;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        @Slf4j
        @Configuration
        public class LoadBalancerConfig {
            // 方法名必须为iRule,否则局部配置失效
            @Bean
            public IRule iRule() {
                log.info("成功自定义负载均衡规则");
                // 返回自定义要使用的负载均衡策略的对象
                return new RandomRule();
            }
        }
        
      • Step2: 给服务消费者(即order-service模块)的启动类OrderApplication添加@RibbonClients并在该注解内使用@RibbonClient注解来指定服务消费者(即user-service模块)的服务名及其对应的配置类

        package at.guigu;
        
        import at.ribbon.LoadBalancerConfig;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.context.annotation.Bean;
        import org.springframework.cloud.netflix.ribbon.RibbonClient;
        import org.springframework.cloud.netflix.ribbon.RibbonClients;
        
        @Slf4j
        @SpringBootApplication
        // 配置自定义负载均衡策略(局部配置)
        @RibbonClients(value = {
                @RibbonClient(name = "userservice", configuration = LoadBalancerConfig.class)
        })
        public class OrderApplication {
            public static void main(String[] args) {
                SpringApplication.run(OrderApplication.class, args);
                log.info("OrderApplication Running");
            }
                /*
            // 微服务远程调用
            // 由于已在config包下创建相关配置类RemoteCallConfig,所以此处注掉,两者功能相同
            // @LoadBalanced:负载均衡注解实现服务消费者对Eureka服务拉取/订阅/发现
            @Bean
            @LoadBalanced
            public RestTemplate restTemplate() {
                log.info("restTemplate创建成功,微服务远程调用开启");
                return new RestTemplate();
            }
             */
        }
        
    • 方案二: 在服务消费者(即order-service模块)的配置文件application.yml中配置自定义默认负载均衡规则

      # 自定义配置某个服务提供者的负载均衡策略,这里是userservice服务
      userservice: # 指定服务提供者的服务名
        ribbon:
          NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 自定义默认的负载均衡规则
      

      服务消费者(即order-service模块)的配置文件application.yml完整代码如下:

      server:
        port: 8080
      
      spring:
        application:
          # 指定服务提供者/服务消费的名称
          name: orderservice
        # 配置数据库连接信息以及数据源信息
        datasource:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/cloud_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
          username: root
          password: 123456
      
      mybatis-plus:
        configuration:
          # 配置MyBatisPlus的运行日志
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
          # 是否开启下划线和驼峰映射, 默认为true
          map-underscore-to-camel-case: true
        # 实体类的别名扫描包---即设置别名
        type-aliases-package: at.guigu.po
      
        global-config:
          db-config:
            # 全局id类型---即id生成策略
            # id-type: auto
            # 更新策略,默认为not_null
            # update-strategy: not_null
            # 设置数据库表名前缀
            table-prefix: tb_
      
      eureka:
        client:
          service-url:
            # 配置eureka服务端的默认地址信息
            # 目的:将服务提供者(即user-service模块)的实例信息注册到Eureka服务端(即eureka-server注册中心)
            defaultZone: http://127.0.0.1:10086/eureka
          register-with-eureka: false # 是否将该服务的实例信息注册到Eureka(默认为true)
          fetch-registry: true # 是否从注册中心获取服务实例信息(默认为true)
      
      # 自定义配置某个服务提供者的负载均衡策略,这里是userservice服务
      userservice: # 指定服务提供者的服务名
        ribbon:
          NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 自定义负载均衡策略为随机策略
      
      logging:
        level:
          # 配置指定包及其子包下的所有类的日志级别为debug
          at.guigu: debug
        # 配置日志输出的时间戳格式
        pattern:
          dateformat: MM-dd HH:mm:ss:SSS
      
  • 注意

    • 方法一是全局配置:在服务消费者(即order-service模块)中调用任何服务消费者都会使用该自定义的负载均衡策略

    • 方法二是局部配置:在服务消费者(即order-service模块)中调用的任何服务提供者若不在进行自定义负载均衡策略配置,则会使用默认的负载均衡策略

    • 一般使用默认的负载均衡策略即可,除非有特定要求时才做更改

    • 在实际项目中,若通过代码方式自定义则需要重新打包发布;若通过配置文件方式则不需要重新打包发布,只是说无法做到全局配置而已

自定义负载均衡策略

在自定义默认负载均衡策略中使用的都是已经存在的负载均衡策略,本部分内容将自己定义新的负载均衡策略来使用

  • Step1: 在ribbon包下创建自定义负载均衡策略的类RibbonRandomRuleConfig

    • Step1-1: 继承实现了IRule接口的AbstractLoadBalancerRule抽象类
    • Step1-2: 重写choose方法来自定义负载均衡策略(该方法为核心方法,用来选根据负载均衡策略择一个服务实例)
    package at.ribbon;
    
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.ILoadBalancer;
    import com.netflix.loadbalancer.Server;
    
    import java.util.List;
    import java.util.concurrent.ThreadLocalRandom;
    
    public class RibbonRandomRuleConfig extends AbstractLoadBalancerRule {
    
        // 自定义负载均衡策略
        @Override
        public Server choose(Object o) {
            // 获取负载均衡器对象(该对象中包含了服务实例列表)
            ILoadBalancer loadBalancer = this.getLoadBalancer();
            // 获取当前请求的服务提供者的实例列表
            List<Server> reachableServers = loadBalancer.getReachableServers();
            Server server = null;
            do {
                // 自定义随机负载均衡策略
                int random = ThreadLocalRandom.current().nextInt(reachableServers.size());
                server = reachableServers.get(random);
            } while (!(server.isAlive()));
            return server;
        }
        // 初始化配置
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
        }
    }
    

后续步骤按照 自定义默认负载均衡策略 的步骤即可,可将该自定义负载均衡策略配置为全局配置或局部配置,此处只进行局部配置的配置文件形式的演示

  • Step2: 在服务消费者(即order-service模块)的配置文件application.yml中配置自定义负载均衡规则

    # 自定义配置某个服务提供者的负载均衡策略,这里是userservice服务
    userservice: # 指定服务提供者的服务名
      ribbon:
        NFLoadBalancerRuleClassName: at.guigu.ribbon.RibbonRandomRuleConfig # 自定义负载均衡规则
    

    服务消费者(即order-service模块)的配置文件application.yml完整代码略

懒加载与饥饿加载

  • 懒加载

    • 定义:即延迟资源或对象的初始化 ,直到它被真正需要时才进行加载,而不是在系统启动或对象创建时立即加载
    • Ribbon默认采用的就是懒加载 ,在第一次访问时才会去创建LoadBalanceClient,请求时间会比较长,但在后续访问时,请求时间就会缩短
    • 优点
      • 节省资源:延迟加载避免了不必要的资源占用,尤其在服务实例较多但使用不频繁的情况下。
      • 提升启动性能:系统启动时无需加载所有服务和实例信息,从而加快启动速度。
      • 按需加载:只有在需要时才加载资源或服务,可以更灵活地应对动态变化。
    • 缺点
      • 首次调用延迟:由于资源只有在第一次使用时才加载,可能会导致首次调用的延迟。
      • 复杂性增加:实现懒加载需要额外的逻辑控制,可能增加代码复杂性。
      • 潜在的异常处理:如果懒加载的资源加载失败,可能会导致调用失败,需要额外的异常处理机制。
  • 饥饿加载

    • 定义:在系统启动或对象创建时立即加载所有必要的资源或对象 ,不论这些资源是否会被马上使用。这种方式旨在提前完成资源准备,以减少后续的访问延迟。
    • 优点
      • 避免首次调用延迟访问:所有资源在启动阶段已加载,后续使用时无需等待。
      • 逻辑简单:实现逻辑较为直接,不需要动态判断或控制加载时机。
      • 一致性高:所有资源在启动时就已准备好,减少运行时的资源不可用风险
    • 缺点
      • 启动时间长:因为所有资源在系统启动时加载,会增加初始化的耗时。
      • 资源浪费:如果某些资源从未使用过,提前加载可能导致资源浪费。
      • 内存压力大:大量资源在启动时同时加载,可能会占用较多内存或计算资源。
  • 将Ribbon改为饥饿加载

    • 在服务消费者(即order-service模块)的配置文件application.yml中更改指定服务提供者的加载方式为饥饿加载

      • Step1: 开启饥饿加载
      • Step2: 指定饥饿加载的服务提供者的服务名称
      ribbon:
        eureka:
          # 开启饥饿加载(默认为false,即未开启)
          enabled: true
          # 指定饥饿加载的服务提供者的服务名称
          clients: userservice
      

      若指定多个服务提供者,则方式如下

      ribbon:
        eureka:
          # 开启饥饿加载
          enabled: true
          # 指定饥饿加载的服务提供者的服务名称
          clients: 
            - userservice
            - xxxservice
      

LoadBalancer负载均衡

注意:LoadBalancer负载均衡推送到了分支master02上,可自行在Gitee上下载

Spring Cloud LoadBalancer是由Spring Cloud 提供的官方负载均衡实现,从 Spring Cloud 2020 开始推荐使用,用来替代Ribbon

  • Spring Cloud LoadBalancer 提供了两种负载均衡的客户端

    • RestTemplate:RestTemplate是sping提供的用于访问Rest服务的客户端
      • RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率,默认情况下,RestTemplate 默认依赖idK的HTTP连接工具。
    • WebClient:WebClient是从sping webFlux5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具
      • 它的响应式编程是基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。
  • Spring Cloud LoadBalancer 仅支持两种负载均衡策略

    • 轮询策略RoundRobinLoadBalancer------默认
    • 随机选择策略RandomLoadBalancer

快速入门

  • Step1: 若服务消费者(即order-service模块)的pom文件中存在nacos服务发现依赖:spring-cloud-starter-alibaba-nacos-discovery,则利用<exclusions>标签排除Ribbon

    注意:若不存在该依赖,则跳过第一步即可

    <!--nacos服务发现依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <exclusions>
            <!--排除Ribbon-->
            <exclusion>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
  • Step2: 在服务消费者(即order-service模块)的pom文件中添加LoadBalancer依赖

    <!--LoadBalancer依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    
    • 完整pom文件代码如下

      <?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">
          <parent>
              <artifactId>cloud-demo</artifactId>
              <groupId>cn.itcast.demo</groupId>
              <version>1.0</version>
          </parent>
          <modelVersion>4.0.0</modelVersion>
      
          <artifactId>order-service</artifactId>
      
          <!--配置依赖-->
          <dependencies>
              <!--Eureka客户端依赖-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
              </dependency>
              <!--nacos服务注册的依赖-->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                  <exclusions>
                      <!--排除Ribbon-->
                      <exclusion>
                          <groupId>org.springframework.cloud</groupId>
                          <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              <!--LoadBalancer依赖-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
              </dependency>
          </dependencies>
      
      </project>
      
  • Step3: 在服务消费者(即order-service模块)的配置文件application.yml中配置Ribbon为禁用状态

    SpringCloud2020版本之后就不需要在配置文件application.yml中配置Ribbon为禁用状态,不过还是最好加上该步骤

    spring:
      cloud:
        loadbalancer:
          ribbbon:
            # 禁用Ribbon
            enabled: false
    
    • 完整配置文件代码如下

      server:
        port: 8080
      
      spring:
        application:
          # 指定服务提供者/服务消费的名称
          name: orderservice
        # 配置数据库连接信息以及数据源信息
        datasource:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/cloud_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
          username: root
          password: 123456
        cloud:
          loadbalancer:
            ribbbon:
              # 禁用Ribbon
              enabled: false
      
      mybatis-plus:
        configuration:
          # 配置MyBatisPlus的运行日志
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
          # 是否开启下划线和驼峰映射, 默认为true
          map-underscore-to-camel-case: true
        # 实体类的别名扫描包---即设置别名
        type-aliases-package: at.guigu.po
      
        global-config:
          db-config:
            # 全局id类型---即id生成策略
            # id-type: auto
            # 更新策略,默认为not_null
            # update-strategy: not_null
            # 设置数据库表名前缀
            table-prefix: tb_
      
      eureka:
        client:
          service-url:
            # 配置eureka服务端的默认地址信息
            # 目的:将服务提供者(即user-service模块)的实例信息注册到Eureka服务端(即eureka-server注册中心)
            defaultZone: http://127.0.0.1:10086/eureka
          register-with-eureka: false # 是否将该服务的实例信息注册到Eureka(默认为true)
          fetch-registry: true # 是否从注册中心获取服务实例信息(默认为true)
      
      logging:
        level:
          # 配置指定包及其子包下的所有类的日志级别为debug
          at.guigu: debug
        # 配置日志输出的时间戳格式
        pattern:
          dateformat: MM-dd HH:mm:ss:SSS
      
  • 运行后可能后报如下错误,原因:在服务消费者(即order-service模块)的pom文件中导入了两种负载均衡(即Ribbon和LoadBalancer)的依赖(即Eureka和Nacos)

    在这里插入图片描述

  • 解决方式

    • 方式一:删除掉其中一个依赖,保留要使用的依赖(即Eureka服务与Nacos服务选用一个即可,否则会有冲突)

    • 方式二:在配置文件中禁用Eureka或nacos的服务发现功能

      # 禁用eureka服务发现,启用 Nacos服务发现
      spring.cloud.nacos.discovery.enabled: true
      eureka.client.enabled: false
      
      # 启用eureka服务发现,禁用 Nacos服务发现
      spring.cloud.nacos.discovery.enabled: false
      eureka.client.enabled: true
      

自定义默认负载均衡策略

  • Spring Cloud LoadBalancer 仅支持两种负载均衡策略
    • 轮询策略RoundRobinLoadBalancer------默认
    • 随机选择策略RandomLoadBalancer

自定义LoadBalancer的默认负载均衡策略只有一种方法,且只能进行局部配置

  • Step1: 在启动类所能扫描到的包之外创建一个loadbalancer包,并在该包下创建一个配置类LoadBalancerConfig,然后在该类中定义randomLoadBalancer方法,代码如下

    package at.loadBalancer;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
    
    @Slf4j
    @Configuration
    public class LoadBalancerConfig {
        @Bean
        public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory LoadBalancerClientFactory) {
            String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            log.info("自定义默认负载均衡成功");
            return new RandomLoadBalancer(LoadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
        }
    }
    
  • Step2: 给服务消费者(即order-service模块)的启动类OrderApplication添加@LoadBalancerClients并在该注解内使用@LoadBalancerClient注解来指定服务消费者(即user-service模块)的服务名及其对应的配置类

    package at.guigu;
    
    import at.loadBalancer.LoadBalancerConfig;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
    import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @Slf4j
    @LoadBalancerClients({@LoadBalancerClient(name = "userservice",
            configuration = LoadBalancerConfig.class
    )})
    @SpringBootApplication
    public class OrderApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
            log.info("OrderApplication Running");
        }
        /*
        // 微服务远程调用
        // 由于已在config包下创建相关配置类RemoteCallConfig,所以此处注掉,两者功能相同
        // @LoadBalanced:负载均衡注解实现服务消费者对Eureka服务拉取/订阅/发现
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            log.info("restTemplate创建成功,微服务远程调用开启");
            return new RestTemplate();
        }
         */
    }
    

自定义负载均衡策略

由自定义默认负载均衡策略可知,若想要修改默认的负载均衡策略,必须要实现 自定义默认负载均衡策略 中的两个步骤

但是自定义负载均衡策略属于开发者自己定义一个新的负载均衡规则

  • 自定义负载均衡策略时我们可以对照RoundRobinLoadBalancer轮询策略以及RandomLoadBalancer随机选择策略的实现

    由图可知,两个负载均衡策略的类均实现了ReactorServiceInstanceLoadBaancer接口,且都有choose方法

    在这里插入图片描述

    • 进入到ReactorServiceInstanceLoadBaancer接口,发现该接口是继承ReactorLoadBalancer<T>接口,且泛型为服务实例接口(即ServiceInstance),如图所示

      在这里插入图片描述

    • 继续进入到ReactorLoadBalancer<T>接口,发现该接口继承ReactiveLoadBalancer<T>接口

      在这里插入图片描述

    • 继续进入到ReactiveLoadBalancer<T>接口可知,该接口即为顶层接口,且choose方法由该接口定义

      在这里插入图片描述

经过以上分析可知,若想要自定义负载均衡策略,只需要创建一个实现了ReactorServiceInstanceLoadBalancer接口的方法即可,

实现同集群权重优先调用的步骤如下:

  • Step1: 在启动类所能扫描到的包之外创建一个loadbalancer包,并在该包下创建一个实现ReactorServiceInstanceLoadBalancer接口的实现类NacosSameClusterWeightedRule,代码如下

    package at.loadbalancer;
    
    import java.math.BigDecimal;
    import java.util.*;
    import java.util.stream.Collectors;
    import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
    import com.alibaba.nacos.api.naming.pojo.Instance;
    import com.alibaba.nacos.api.utils.StringUtils;
    import com.alibaba.nacos.client.naming.core.Balancer;
    import jakarta.annotation.Resource;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.loadbalancer.DefaultResponse;
    import org.springframework.cloud.client.loadbalancer.EmptyResponse;
    import org.springframework.cloud.client.loadbalancer.Request;
    import org.springframework.cloud.client.loadbalancer.Response;
    import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
    import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import reactor.core.publisher.Mono;
    
    
    @Slf4j
    // 自定义负载均衡实现需要实现 ReactorServiceInstanceLoadBalancer 接口 以及重写choose方法
    public class NacosSameClusterWeightedRule implements ReactorServiceInstanceLoadBalancer {
    
        // 注入当前服务的nacos的配置信息
        @Resource
        private NacosDiscoveryProperties nacosDiscoveryProperties;
    
        // loadbalancer 提供的访问当前服务的名称
        final String serviceId;
    
        // loadbalancer 提供的访问的服务列表
        ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    
        public NacosSameClusterWeightedRule(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
            this.serviceId = serviceId;
            this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        }
    
        /**
         * 服务器调用负载均衡时调的放啊
         * 此处代码内容与 RandomLoadBalancer 一致
         */
        public Mono<Response<ServiceInstance>> choose(Request request) {
            ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
            return supplier.get(request).next().map((serviceInstances) -> {
                return this.processInstanceResponse(supplier, serviceInstances);
            });
        }
    
        /**
         * 对负载均衡的服务进行筛选的方法
         * 此处代码内容与 RandomLoadBalancer 一致
         */
        private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
            Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
            if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
                ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
            }
    
            return serviceInstanceResponse;
        }
    
        /**
         * 对负载均衡的服务进行筛选的方法
         * 自定义
         * 此处的 instances 实例列表  只会提供健康的实例  所以不需要担心如果实例无法访问的情况
         */
        private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
            if (instances.isEmpty()) {
                return new EmptyResponse();
            }
            // 获取当前服务所在的集群名称
            String currentClusterName = nacosDiscoveryProperties.getClusterName();
            // 过滤在同一集群下注册的服务 根据集群名称筛选的集合
            List<ServiceInstance> sameClusterNameInstList  = instances.stream().filter(i-> StringUtils.equals(i.getMetadata().get("nacos.cluster"),currentClusterName)).collect(Collectors.toList());
            ServiceInstance sameClusterNameInst;
            if (sameClusterNameInstList.isEmpty()) {
                // 如果为空,则根据权重直接过滤所有服务列表
                sameClusterNameInst = getHostByRandomWeight(instances);
            } else {
                // 如果不为空,则根据权重直接过滤所在集群下的服务列表
                sameClusterNameInst = getHostByRandomWeight(sameClusterNameInstList);
            }
            return new DefaultResponse(sameClusterNameInst);
        }
    
        private ServiceInstance getHostByRandomWeight(List<ServiceInstance> sameClusterNameInstList){
    
            List<Instance> list = new ArrayList<>();
            Map<String,ServiceInstance> dataMap = new HashMap<>();
            // 此处将 ServiceInstance 转化为 Instance 是为了接下来调用nacos中的权重算法,由于入参不同,所以需要转换,此处建议打断电进行参数调试,以下是我目前为止所用到的参数,转化为map是为了最终方便获取取值到的服务对象
            sameClusterNameInstList.forEach(i->{
                Instance ins = new Instance();
                Map<String, String> metadata = i.getMetadata();
    
                ins.setInstanceId(metadata.get("nacos.instanceId"));
                ins.setWeight(new BigDecimal(metadata.get("nacos.weight")).doubleValue());
                ins.setClusterName(metadata.get("nacos.cluster"));
                ins.setEphemeral(Boolean.parseBoolean(metadata.get("nacos.ephemeral")));
                ins.setHealthy(Boolean.parseBoolean(metadata.get("nacos.healthy")));
                ins.setPort(i.getPort());
                ins.setIp(i.getHost());
                ins.setServiceName(i.getServiceId());
    
                ins.setMetadata(metadata);
    
                list.add(ins);
                // key为服务ID,值为服务对象
                dataMap.put(metadata.get("nacos.instanceId"),i);
            });
            // 调用nacos官方提供的负载均衡权重算法
            Instance hostByRandomWeightCopy = ExtendBalancer.getHostByRandomWeightCopy(list);
    
            // 根据最终ID获取需要返回的实例对象
            return dataMap.get(hostByRandomWeightCopy.getInstanceId());
        }
    
    }
    
    class ExtendBalancer extends Balancer {
        /**
         * 根据权重选择随机选择一个
         */
        public static Instance getHostByRandomWeightCopy(List<Instance> hosts) {
            return getHostByRandomWeight(hosts);
        }
    }
    
  • Step2: 继续在loadbalancer包下创建一个配置类LoadBalancerConfig,然后在该类中定义randomLoadBalancer方法,代码如下

    package at.loadbalancer;
    
    import at.loadbalancer.NacosSameClusterWeightedRule;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
    
    @Slf4j
    @Configuration
    public class LoadBalancerConfig {
        @Bean
        public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory LoadBalancerClientFactory) {
            String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            log.info("自定义默认负载均衡成功");
            return new NacosSameClusterWeightedRule(LoadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
        }
    }
    
  • Step3: 给服务消费者(即order-service模块)的启动类OrderApplication添加@LoadBalancerClients并在该注解内使用@LoadBalancerClient注解来指定服务消费者(即user-service模块)的服务名及其对应的配置类

    package at.guigu;
    
    import at.loadbalancer.LoadBalancerConfig;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
    import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @Slf4j
    @LoadBalancerClients({@LoadBalancerClient(name = "userservice",
            configuration = LoadBalancerConfig.class
    )})
    @SpringBootApplication
    public class OrderApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
            log.info("OrderApplication Running");
        }
        /*
        // 微服务远程调用
        // 由于已在config包下创建相关配置类RemoteCallConfig,所以此处注掉,两者功能相同
        @Bean
        public RestTemplate restTemplate() {
            log.info("restTemplate创建成功,微服务远程调用开启");
            return new RestTemplate();
        }*/
    }
    
  • 自定义LoadBalancer负载均衡策略来源于https://www.jianshu.com/p/3b26ebe4ab3e

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/956940.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MFC中添加Richedit2.0控件导致程序无法运行的解决方法mfc richedit2 Detected memory leaks! 及控件使用

错误&#xff1a;拖Richedit2.0控件到界面&#xff0c;编译提示mfc richedit2 Detected memory leaks! 原因&#xff1a;在MFC中添加Richedit2.0控件&#xff0c;可能会导致程序出错或无法运行。这是由于RichEdit没有初始化导致的。 解决&#xff1a;在 继承自CWinApp的类中的…

每打开一个chrome页面都会【自动打开F12开发者模式】,原因是 使用HBuilderX会影响谷歌浏览器的浏览模式

打开 HBuilderX&#xff0c;点击 运行 -> 运行到浏览器 -> 设置web服务器 -> 添加chrome浏览器安装路径 chrome谷歌浏览器插件 B站视频下载助手插件&#xff1a; 参考地址&#xff1a;Chrome插件 - B站下载助手&#xff08;轻松下载bilibili哔哩哔哩视频&#xff09…

【电视盒子】HI3798MV300刷机教程笔记/备份遥控码修复遥控器/ADB/线刷卡刷/电视盒子安装第三方应用软件

心血来潮&#xff0c;看到电视机顶盒满天飞的广告&#xff0c;想改造一下家里的电视盒子&#xff0c;学一下网上的人刷机&#xff0c;但是一切都不知道怎么开始&#xff0c;虽然折腾了一天&#xff0c;以失败告终&#xff0c;还是做点刷机笔记。 0.我的机器 年少不会甄别&…

USART_串口通讯轮询案例(HAL库实现)

引言 前面讲述的串口通讯案例是使用寄存器方式实现的&#xff0c;有利于深入理解串口通讯底层原理&#xff0c;但其开发效率较低&#xff1b;对此&#xff0c;我们这里再讲基于HAL库实现的串口通讯轮询案例&#xff0c;实现高效开发。当然&#xff0c;本次案例需求仍然和前面寄…

AI绘画入门:探索数字艺术新世界(1/10)

引言&#xff1a;AI 绘画的兴起与现状 在科技飞速发展的当下&#xff0c;AI 绘画如同一场艺术领域的风暴&#xff0c;正以惊人的速度席卷而来&#xff0c;彻底改变着我们对艺术创作的认知。近年来&#xff0c;AI 绘画相关的话题屡屡登上热搜&#xff0c;从社交媒体上各种 AI 生…

多线程杂谈:惊群现象、CAS、安全的单例

引言 本文是一篇杂谈&#xff0c;帮助大家了解多线程可能会出现的面试题。 目录 引言 惊群现象 结合条件变量 CAS原子操作&#xff08;cmp & swap&#xff09; 线程控制&#xff1a;两个线程交替打印奇偶数 智能指针线程安全 单例模式线程安全 最简单的单例&…

Ubuntu -- 几行命令使用Ollama部署本地AI大模型, 仅调用api, 快速测试api效果

需求 需要在本地快速部署一个大模型, 然后使用 局域网 的其他电脑进行 api调用为了快速测试, 大模型选择了 qwen2:0.5B 进行快速测试 开始 下载安装 ollama curl -fsSL https://ollama.com/install.sh | sh验证安装 ollama --version下载安装模型并运行 ollama run qwen2:…

无降智o1 pro——一次特别的ChatGPT专业模式探索

这段时间和朋友们交流 ChatGPT 的使用心得&#xff0c;大家都提到一个很“神秘”的服务&#xff1a;它基于 O1 Pro 模型&#xff0c;能够在对话里一直保持相对高水平的理解和回复&#xff0c;不会突然变得“降智”。同时&#xff0c;整体使用还做了免折腾的网络设置——简单一点…

1. 基于图像的三维重建

1. 基于图像的三维重建 核心概念三维重建中深度图、点云的区别&#xff1f;深度图点云总结 深度图到点云还需要什么步骤&#xff1f;1. **获取相机内参**2. **生成相应的像素坐标**3. **计算三维坐标**4. **构建点云**5. **处理颜色信息&#xff08;可选&#xff09;**6. **去除…

国内有哪些著名的CRM系统提供商?

嘿&#xff0c;你有没有想过&#xff0c;在这个信息爆炸的时代里&#xff0c;企业怎么才能更好地管理客户关系呢&#xff1f;答案就是使用高效的CRM系统。今天我就来给大家聊聊那些在国际上非常有名的CRM系统提供商吧。 悟空CRM 首先不得不提的就是悟空CRM了&#xff01;这可…

QTableWidget的简单使用

1.最简单的表格示例&#xff1a; ui->tableWidget->setRowCount(2);// 设置行数ui->tableWidget->setColumnCount(3);// 设置列数&#xff0c;一定要放在设置行表头之前QStringList rowHeaderList;// 行表头rowHeaderList << QStringLiteral("姓名"…

Jenkins-Pipeline简述

一. 什么是Jenkins pipeline&#xff1a; pipeline在jenkins中是一套插件&#xff0c;主要功能在于&#xff0c;将原本独立运行于单个或者多个节点的任务连接起来&#xff0c;实现单个任务难以完成的复杂发布流程。Pipeline的实现方式是一套Groovy DSL&#xff0c;任何发布流程…

Hadoop美食推荐系统 爬虫1.8w+数据 协同过滤余弦函数推荐美食 Springboot Vue Element-UI前后端分离

Hadoop美食推荐系统 爬虫1.8w数据 协同过滤余弦函数推荐美食 Springboot Vue Element-UI前后端分离 【Hadoop项目】 1. data.csv上传到hadoop集群环境 2. data.csv数据清洗 3.MapReducer数据汇总处理, 将Reducer的结果数据保存到本地Mysql数据库中 4. SpringbootEchartsMySQL 显…

两份PDF文档,如何比对差异,快速定位不同之处?

PDF文档比对是通过专门的工具或软件&#xff0c;自动检测两个PDF文件之间的差异&#xff0c;并以可视化的方式展示出来。这些差异可能包括文本内容的修改、图像的变化、表格数据的调整、格式的改变等。比对工具通常会标记出新增、删除或修改的部分&#xff0c;帮助用户快速定位…

苍穹外卖项目总结(二)

本篇对苍穹外卖后半部分进行介绍&#xff0c;重点是redis缓存的使用以及微信小程序客户端开发。 目录 一、菜品管理 1.1新增菜品 1.2菜品的分页查询 1.3删除菜品 1.4修改菜品 1.5设置营业状态 二、微信小程序客户端的开发 三、Redis的基本使用 常用命令&#xff1a; 缓…

MyBatisPlus简介及入门案例

一、简介 官网&#xff1a;https://baomidou.com/introduce/ 1.简介 MyBatisPlus只做增强&#xff0c;不做改变&#xff0c;为简化开发、提高效率而生 2.特性 (1)无侵入 只做增强不做改变&#xff0c;引入它不会对现有工程产生影响&#xff0c;如丝般顺滑 (2)损耗小 启动…

欧拉(Euler 22.03)安装ProxySQL

下载离线安装包 proxysql-2.0.8-1-centos7.x86_64.rpm 链接: https://pan.baidu.com/s/1R-SJiVUEu24oNnPFlm9wRw 提取码: sa2w离线安装proxysql yum localinstall -y proxysql-2.0.8-1-centos7.x86_64.rpm 启动proxysql并检查状态 systemctl start proxysql 启动proxysql syste…

电子应用设计方案96:智能AI充电器系统设计

智能 AI 充电器系统设计 一、引言 智能 AI 充电器系统旨在为各种电子设备提供高效、安全、智能的充电解决方案&#xff0c;通过融合人工智能技术&#xff0c;实现自适应充电、优化充电效率和保护电池寿命。 二、系统概述 1. 系统目标 - 自适应识别不同设备的充电需求&#xf…

TongESB7.1.0.0如何使用dockercompose运行镜像(by lqw)

文章目录 安装准备安装 安装准备 1.安装好docker和dockercompose&#xff1a; docker、docker-compose安装教程&#xff0c;很详细 2.上传好安装相关文件 安装 使用以下命令导入管理端镜像和运行时镜像 docker load -i tongesb_manage_7100.tar docker load -i tongesb_se…

Python 模拟真人鼠标轨迹算法 - 防止游戏检测

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…