SpringCloud(微服务介绍,远程调用RestTemplate,注册中心Nacos,负载均衡Ribbon,环境隔离,进程和线程的区别)【详解】

时间:2024-04-28 09:46:32

目录

一、微服务介绍

1. 系统架构的演变

1 单体架构

2 分布式服务

3 微服务

2. SpringCloud介绍

SpringCloud简介

SpringCloud版本

3. 小结

二、远程调用RestTemplate【理解】

1. 服务拆分

1 服务拆分原则

2 服务拆分示例

1) 创建父工程

2) 准备用户服务

1. 用户服务的基础代码

2. 启动测试

3) 准备订单服务

1. 订单服务的基础代码

2. 启动测试

2. 远程调用RestTemplate【了解】

1 实现远程调用

说明

实现

测试

2 提供者与消费者

3. 小结

三、注册中心Nacos【重点】

1. 介绍

1 服务治理问题

2 Nacos简介

3 Nacos安装

2. Nacos使用入门

1 添加坐标

2 配置注册中心地址

3 开启服务发现功能

4 功能测试

3. Nacos的原理

1 临时实例与非临时实例

2 Nacos的原理

4. 常见错误

5. 小结

四、负载均衡Ribbon

1. 负载均衡简介

1 什么是负载均衡

2 Ribbon负载均衡

2. Ribbon效果演示

1 搭建用户服务集群

2 订单服务调用用户服务

3. Ribbon实现原理分析

4. Ribbon负载均衡策略

5. 饥饿加载

6. 小结

五、Nacos分组存储与环境隔离

1. Nacos分级存储模型

1 配置实例集群

1 Nacos里实例集群的概念

2 配置实例集群

3 查看配置效果

2 同集群优先访问

1 配置负载均衡策略

2 测试效果

3 Nacos的服务实例权重

1 设置服务实例的权重

2 测试效果

2. namespace环境隔离

1 创建namespace

2 给微服务指定namespace

3 隔离效果

3. 小结

六、进程和线程的区别


一、微服务介绍

本章节学习目标:

  • 什么是微服务,微服务有哪些特征
  • SpringCloud是什么
  • SpringCloud与Dubbo的区别

1. 系统架构的演变

1 单体架构

将业务的所有功能集中在一个项目中开发,打成一个包部署。当网站流量很小时,单体架构非常合适。

单体架构的优缺点如下:

优点:

  • 架构简单

  • 部署成本低

缺点:

  • 耦合度高(维护困难、升级困难)

2 分布式服务

分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。

分布式架构的优缺点:

优点:

  • 降低服务耦合

  • 有利于服务升级和拓展

缺点:

  • 服务有重复代码

  • 服务调用关系错综复杂

  • 服务容错性较差

3 微服务

微服务其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。

因此,可以认为微服务是一种经过良好架构设计的分布式架构方案,微服务以如下特征:

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

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

  • 面向服务:服务对外暴露统一标准的接口,与语言和技术无关。SpringCloud体系里使用HTTP协议的接口

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

但是使用了微服务,也会带来一些新的问题(相对于单体架构来说):

  • 增加了系统间的通信成本

  • 增加了数据一致性问题,分布式事务问题等等

  • 服务数量增加,运维压力大

一旦采用微服务系统架构,就势必会遇到这样几个问题:

  • 这么多小服务,如何管理他们的地址?服务治理的问题,可以使用“注册中心”来解决

  • 这么多小服务,他们之间如何通讯?远程调用,可以使用httpclient、RestTemplate、OpenFeign(优雅的远程调用技术)来解决

  • 这么多小服务,客户端怎么访问他们?要使用网关实现统一的对外访问入口,Gateway来解决

  • 这么多小服务,一旦出现问题了,应该如何自处理?要实现服务的隔离防止雪崩,Hystrix或者Sentinel等解决

对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。

2. SpringCloud介绍

微服务的实现方式很多,例如dubbo+zookeeper, SpringCloud等等。那么这两种微服务实现方案有什么区别呢?

微服务技术方案对比

SpringCloud微服务解决方案:不是一个框架,是一系列框架集合,目前包含二十多个框架,还在不断增加中……

SpringCloud简介

官网地址:Spring Cloud

中文文档(非官方):Spring Cloud中文网-官方文档中文版

SpringCloud是Pivotal团队提供的、基于SpringBoot开箱即用的、一站式微服务解决方案的框架体系。目前已成为国内外使用最广泛的微服务框架。

SpringCloud集成了各种微服务功能组件。

其中常见的组件包括:

SpringCloud版本

SpringCloud的版本命名比较特殊,因为它不是一个组件,而是许多组件的集合,它的命名是以A到Z的为首字母的一些单词(其实是伦敦地铁站的名字)组成:

我们在项目中,会使用Hoxton.SR10版本,对应的SpringBoot版本为2.3.x

3. 小结

系统架构的演进:

  • 单体架构:all in one,一切功能模块全部在一个服务里

    好处:简单省钱

    缺点:耦合性强

  • 分布式架构:拆。把一个系统拆分成多个不同的子系统,每个子系统有不同的功能,所有子系统组合起来才是完整系统

    优点:耦合性降低了

    缺点:重复代码

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

    有一些特征

    • 单一职责:每个服务只做一件事,服务的粒度比较细

    • 服务自治:每个服务可以有独立的技术、独立的团队、独立的数据、独立发布部署

    • 面向服务:所有服务要遵循相同的协议暴露访问接口,所有的服务之间才可以互相调用通信

    • 服务保护:要防止某个服务出错,导致调用者也出错,最终出现大面积的崩溃。要做好隔离和保护

    缺点:

    • 通信成本高。因为服务之间需要跨网络进行请求调用

    • 运维成本高。架构比较复杂,运维比较麻烦

    微服务需要解决的问题(微服务的5个核心组件):

    • 注册中心:解决的是服务治理问题,即 服务地址的管理。Nacos

    • 远程调用:解决服务之间的请求调用问题。RestTemplate(今天临时用),Feign

    • 服务保护:防止服务之间出现级联问题导致雪崩。Hystrix,Sentinel

    • 配置中心:解决的是每个服务都有配置文件,配置文件散乱不方便管理的问题。Nacos

    • 服务网关:解决的是 要给客户端提供一个统一的访问入口。SpringCloudGateway

二、远程调用RestTemplate【理解】

本章节学习目标:

  • 理解服务拆分的原则
  • 使用RestTemplate实现远程调用

1. 服务拆分

1 服务拆分原则

这里我们总结了微服务拆分时的几个原则:

  • 职责单一:不同微服务,不要重复开发相同业务

  • 服务自治:微服务数据独立,不要访问其它微服务的数据库

  • 面向服务:微服务可以将自己的业务暴露为接口,供其它微服务调用:有Controller即可

2 服务拆分示例

要求:

  • 有用户服务,提供“根据id查询用户”功能

  • 有订单服务,提供“根据id查询订单”功能

准备:

  • 创建数据库cloud_user,执行脚本《cloud-user.sql》

  • 创建数据库cloud_order,执行脚本《cloud-order.sql》

项目结构:

1) 创建父工程

注意:在开发项目时,不要随意动依赖坐标。一旦依赖出现问题,就可能导致整个项目出问题

注意:每个服务命名时,以英文字母开头,单词中间用横杠连接。不建议用下划线_连接

注意:每个服务的配置文件里,都必须有应用名称spring.application.name

  1. 删除src文件夹

  2. 修改pom.xml导入依赖

<packaging>pom</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <mysql.version>8.0.31</mysql.version>
        <mybatisplus.version>3.4.1</mybatisplus.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- mysql驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!-- MybatisPlus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatisplus.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
2) 准备用户服务
1. 用户服务的基础代码

1) 创建用户模块

在项目上创建Module:user-service

2) 导入依赖

修改pom.xml,添加依赖坐标

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
    </dependencies>

3) 准备配置文件

创建配置文件application.yaml

server:
  port: 8080 #8080端口
spring:
  application:
    name: user-service #应用名称
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///cloud_user
    username: root
    password: root    
logging:
  level:
    com.itheima.user: debug
  pattern:
    dateformat: HH:mm:ss.SSS

4) 准备引导类

package com.itheima.user;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.itheima.user.mapper")
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

准备三层:

//2. 准备实体类User

package com.itheima.user.pojo;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("tb_user")
public class User {
    private Long id;
    private String username;
    private String address;
}


-------------
//3. 创建UserMapper

package com.itheima.user.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.user.pojo.User;

public interface UserMapper extends BaseMapper<User> {
}


------------
//4. 创建UserService

package com.itheima.user.service;

import com.itheima.user.mapper.UserMapper;
import com.itheima.user.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User findById(Long id){
        return userMapper.selectById(id);
    }
}


-----------
//5. 创建UserController

package com.itheima.user.controller;

import com.itheima.user.pojo.User;
import com.itheima.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User findById(@PathVariable("id") Long id) {
        return userService.findById(id);
    }
}


2. 启动测试
  • 启动服务

  • 在浏览器上输入地址访问测试:http://localhost:8080/user/1

3) 准备订单服务
1. 订单服务的基础代码

1) 创建订单模块

在项目上右键创建Module:order-service

2) 导入依赖

修改pom.xml,添加依赖坐标

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
    </dependencies>

3) 配置文件

创建application.yaml

server:
  port: 7070
spring:
  application:
    name: order-service
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///cloud_order
    username: root
    password: root
logging:
  level:
    com.itheima.order: debug
  pattern:
    dateformat: MM-dd HH:mm:ss.SSS

4) 创建引导类

package com.itheima.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
@MapperScan("com.itheima.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

准备三层:

//2. 创建实体类Order
//把User类拷贝进来,稍后会用到
//创建Order类

package com.itheima.order.pojo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("tb_order")
public class Order {
    private Long id;
    private Long userId;
    private String name;
    private Long price;
    private Integer num;

    @TableField(exist = false)
    private User user;
}


------------
//3. 创建OrderMapper

package com.itheima.order.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.order.pojo.Order;

public interface OrderMapper extends BaseMapper<Order> {
}


-----------
//4. 创建OrderService

package com.itheima.order.service;

import com.itheima.order.mapper.OrderMapper;
import com.itheima.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    public Order findById(Long id){
        Order order = orderMapper.selectById(id);

        return order;
    }
}


-----------
//5. 创建OrderController

package com.itheima.order.controller;

import com.itheima.order.pojo.Order;
import com.itheima.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
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")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @GetMapping("/{id}")
    public Order findById(@PathVariable("id") Long id) {
        return orderService.findById(id);
    }
}



2. 启动测试
  • 启动服务

  • 打开浏览器输入地址访问测试:http://localhost:7070/order/101

2. 远程调用RestTemplate【了解】

1 实现远程调用

说明

问题:

  • 在order-service中,查询一个订单OrderOrder中的user对象为空

  • 在user-service中,提供了根据id查询用户的功能

要求:

  • 在查询订单时,把订单关联的用户信息一并查询出来

方案:

  • 使用SpringMVC提供的RestTemplate,可以发起HTTP请求,调用http://localhost:8080/user/{id}对应的接口

  • 具体步骤是:

    • 在order-service里注册一个RestTemplate对象

    • 在OrderService的findById方法中

      • 查询得到订单之后,再根据订单中的userId,使用RestTemplate向http://localhost:8080/user/{id}发请求,查询对应的User对象

      • 把得到的User对象放到Order里

实现

1. 修改OrderApplication

在引导类中注册一个RestTemplate对象

package com.itheima.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@MapperScan("com.itheima.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
    
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

2. 修改OrderService

  • 使用RestTemplate发HTTP请求,查询对应的用户

package com.itheima.order.service;

import com.itheima.order.mapper.OrderMapper;
import com.itheima.order.pojo.Order;
import com.itheima.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;


    public Order findById(Long id) {
        Order order = orderMapper.findById(id);
		//拼接url地址
        String url = "http://localhost:8080/user/" + order.getUserId();
        //使用RestTemplate向这个地址发请求,查询用户
        User user = restTemplate.getForObject(url, User.class);

        //把查询的结果设置到order对象里
        order.setUser(user);
        return order;
    }
}
测试
  • 在浏览器上输入地址:http://localhost:7070/order/101

  • 查询的结果里有订单信息,也有订单关联的用户信息

2 提供者与消费者

在服务调用关系中,会有两个不同的角色:

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

  • 服务消费者:一次业务中,调用其它微服务的服务。

但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言。

如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?

  • 对于A调用B的业务而言:A是服务消费者,B是服务提供者

  • 对于B调用C的业务而言:B是服务消费者,C是服务提供者

因此,服务B既可以是服务提供者,也可以是服务消费者。

3. 小结

如果拉取nacos的地址

package com.itheima.order.service;

import com.itheima.order.mapper.OrderMapper;
import com.itheima.order.pojo.Order;
import com.itheima.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public Order findById(Long id){
        Order order = orderMapper.selectById(id);

//        String url = "http://localhost:8080/user/"+order.getUserId();
        //访问路径的写法: http://服务名/资源路径
        String url = "http://user-service/user/"+order.getUserId();
        //使用RestTemplate直接发起请求:RestTemplate已经具备负载均衡能力了
        User user = restTemplate.getForObject(url, User.class);
        order.setUser(user);

        return order;
    }
}

远程调用:RestTemplate技术,简单了解,体验微服务架构下的功能实现

服务提供者:提供服务、供其它人调用的一方

服务消费者:使用服务、调用其它服务的一方

三、注册中心Nacos【重点】

  • 能安装启动Nacos
  • 能够使用Nacos作为注册中心

1. 介绍

1 服务治理问题

目前已经可以实现微服务之间的调用,但是我们把服务提供者的网络地址(ip,端口)等硬编码到了代码中,这种做法存在许多问题:

  • 一旦服务提供者地址变化,就需要手工修改代码

  • 一旦服务变得越来越多,人工维护调用关系困难

这时候就需要通过注册中心动态的实现服务治理

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

  • 服务注册:在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳的方式去监测清单中的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。

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

2 Nacos简介

国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。

Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

3 Nacos安装

  1. 下载:https://github.com/alibaba/nacos/releases,下载zip格式的程序包。

    可以直接使用资料里提供的程序包

  2. 安装:

    免安装,直接解压到一个不含中文、空格、特殊字符的目录里

  3. 启动:

    使用cmd切换到nacos的bin目录里

    执行命令:startup.cmd -m standalone,以单机模式启动nacos

  4. 进入管理界面

    打开浏览器输入地址 http://localhost:8848/nacos

    默认帐号:nacos,密码:nacos

2. Nacos使用入门

Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。

主要差异在于:

  • 依赖坐标不同

  • 配置参数不同

1 添加坐标

1) 父工程锁定SpringCloudAlibaba的依赖版本

在父工程pom.xml的dependencyManagement中添加SpringCloudAlibaba的版本锁定

<!--SpringCloudAlibaba-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.6.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

父工程最终的坐标如下:

<modules>
        <module>user-service</module>
        <module>order-service</module>
    </modules>
    <packaging>pom</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <mysql.version>5.1.47</mysql.version>
        <mybatisplus.version>3.4.1</mybatisplus.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!--SpringCloudAlibaba-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- springCloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR10</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- mysql驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatisplus.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

2) 微服务添加nacos的服务发现依赖坐标

在用户服务和订单服务中添加nacos的服务发现包的依赖坐标:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

最终依赖如下:

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
</dependencies>

2 配置注册中心地址

修改微服务的application.yaml,配置nacos的地址

spring:
  application:
    name: order-service #应用名称
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

用户服务的最终配置文件

server:
  port: 8080 #8080端口
spring:
  application:
    name: user-service #应用名称
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///cloud_user?useSSL=false
    username: root
    password: root
logging:
  level:
    com.itheima.user: debug
  pattern:
    dateformat: HH:mm:ss.SSS

订单服务的最终配置文件

server:
  port: 7070
spring:
  application:
    name: order-service #应用名称
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///cloud_order?useSSL=false
    username: root
    password: root
logging:
  level:
    com.itheima.user: debug
  pattern:
    dateformat: HH:mm:ss.SSS
mybatis:
  configuration:
    map-underscore-to-camel-case: true

3 开启服务发现功能

修改所有微服务的引导类,在引导类上添加注解@EnableDiscoveryClient

用户服务最终的引导类

package com.itheima.user;

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

@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.itheima.user.mapper")
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

订单服务最终的引导类

package com.itheima.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.itheima.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

4 功能测试

验证服务是否注册到Nacos

  1. 先启动Nacos,再启动用户服务和订单服务

  2. 打开Nacos控制台,访问http://localhost:8848/nacos,查看注册的服务信息

验证远程调用是否成功

  1. 修改Order服务的引导类,给RestTemplate对象添加 @LoadBalanced

    @Bean
	@LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

  2. 修改OrderService类,根据服务名进行远程调用

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public Order findById(Long id){
        Order order = orderMapper.selectById(id);

        //使用RestTemplate,向名称为user-service的服务发起远程调用,得到结果
        User user = restTemplate.getForObject("http://user-service/user/" + order.getUserId(), User.class);
        order.setUser(user);

        return order;
    }
}

  3. 打开浏览器,访问http://localhost:7070/order/101

3. Nacos的原理

1 临时实例与非临时实例

从Nacos1.0开始就提供了一个配置参数:spring.cloud.nacos.discovery.ephemeral,用于设置微服务实例是临时实例还是非临时实例

  • 值为true:微服务是临时实例。当微服务长时间不能向Nacos续约时,Nacos会剔除微服务实例的信息

  • 值为false:微服务是非临时实例。即使微服务长时间不能向Nacos续约,Nacos也仅仅是将服务实例标记为不健康状态,而不会剔除服务实例的信息

两者的作用是:

  • 临时实例:适用于应对流量突增的情况,当流量洪峰时,增加服务实例数量;当流量高峰过去以后,服务实例停止,就自动从Nacos里剔除了

  • 非临时实例:适用于服务的保护。当Nacos发现短时间有大量微服务不健康时,实际可能是Nacos的网络波动等情况,而微服务实例其实是正常的。 这时Nacos仍然保留服务信息,供消费者进行拉取

2 Nacos的原理

4. 常见错误

启动微服务时,可能会报以下错误:

出错的原因,通常是Nacos缓存问题:

  • 在使用Nacos的时候,每次有服务注册到Nacos,Nacos会把服务的信息缓存起来。缓存的数据在Nacos软件的data文件夹里

  • 下次再使用Nacos的时候,如果 之前注册服务的ip地址 和 这一次注册服务的ip地址不同,就会报这个错

解决的方法是:

  • 清理掉Nacos里的data文件夹,再启动Nacos,启动微服务。 就没有缓存了,就不会报这个错了

5. 小结

注册中心是什么,有什么用?解决了服务治理问题

  • 维护所有活跃的服务地址列表

  • 监控所有服务的健康状态

注册中心的模式:

  • 服务注册:每个微服务启动时,会把自己的地址信息上报给注册中心;

    然后注册中心会监控服务的健康状态:通过心跳续约的方式,或者通过主动探测模式

  • 服务发现:当微服务需要远程调用其它服务时,从注册中心里拉取地址列表信息,再发起远程调用

注册中心的技术:

  • Eureka:NetFlix网飞的注册中心技术。功能比较简单,管理界面也比较简陋

  • Nacos:Alibaba的注册中心技术。功能丰富强大,有管理界面可以进行服务的管理

使用Nacos作为注册中心:

  1. 安装启动Nacos:startup.cmd -m standalone ,或者用我们提供的cmd脚本

  2. 每个微服务需要整合Nacos

    添加依赖:先添加SpringCloudAlibaba的依赖版本锁定,再给每个微服务添加nacos-discovery依赖坐标

    修改配置:修改每个微服务的application.yaml,其中要有

    • spring.application.name, 配置服务名

    • spring.cloud.nacos.discovery.server-addr,配置Nacos的地址,写成 localhost:8848

    修改引导类:修改每个微服务的引导类,添加@EnableDiscoveryClient

  3. 启动微服务,微服务会自动把自己的地址信息上报给Nacos。

    打开Nacos管理界面 http://localhost:8848/nacos,帐号:nacos,密码:nacos

    找到服务管理,可以查看已注册的服务信息列表

Nacos的原理了解:

  • Nacos的服务实例分为两种:临时实例,非临时实例

    临时实例,当服务不健康时,Nacos会剔除服务信息

    非临时实例, 当服务不健康时,Nacos仅仅是标记为不健康状态,并不会剔除服务信息

  • 服务注册:微服务一启动,就会立即自动注册到Nacos里

  • 服务健康状态的监控:

    如果是临时实例,需要由微服务每5秒一次向Nacos发送心跳续约;如果15秒没有心跳,服务信息会被Nacos标记为不健康;30秒以后会把服务信息剔除掉

    如果是非临时实例,Nacos会每20秒主动探测一次微服务的状态。如果不正常,也仅仅是标记不健康,不会剔除信息

四、负载均衡Ribbon

本章节学习目标:

  • 理解负载均衡的概念与分类
  • 能够使用ribbon实现负载均衡

1. 负载均衡简介

1 什么是负载均衡

通俗的讲,负载均衡就是将负载(工作任务,访问请求)分摊到多个操作单元上进行执行。

根据负载均衡发生位置的不同,一般分为服务端负载均衡客户端负载均衡

  • 服务端(提供者)负载均衡指的是发生在服务提供者一方,比如常见的nginx负载均衡。

  • 客户端(消费者)负载均衡指的是发生在服务请求的一方,也就是在发送请求之前已经选好了由哪个实例处理请求。

我们在微服务调用关系中一般会选择客户端负载均衡,也就是在服务调用的一方来决定服务由哪个提供者执行。

2 Ribbon负载均衡

Ribbon是NetFlix提供的客户端负载均衡工具,它有助于控制HTTP和TCP客户端的行为。

Ribbon提供了多种负载均衡算法,包括轮询、随机等等,同时也支持自定义负载均衡算法。

它不需要独立部署,而是几乎存在于SpringCloud的每个组件中,包括Nacos也已经引入了Ribbon。

  • 当使用RestTemplate发起远程调用时,需要给RestTemplate对象添加@LoadBalanced注解

  • 当使用OpenFeign发起远程调用时,不需要做任何额外配置,就具备负载均衡效果

2. Ribbon效果演示

在刚刚的案例中,order-service要调用user-service,而user-service只有一个服务实例。但是实际环境中为了保证高可用,通常会搭建集群环境。比如user-service搭建了集群,这就需要实现对用户服务的负载均衡效果

我们以订单中调用用户服务为例,来演示负载均衡的问题

基础服务代码:

  • 注册中心:Nacos

  • 用户服务:提供findById功能

  • 订单服务:提供findById功能,并通过RestTemplate远程调用 用户服务。

1 搭建用户服务集群

1) 复制用户服务的配置文件

复制用户服务的配置文件,准备多个不同后缀名的配置环境application-环境标识.yaml

application-8080serv.yaml:设置为8080端口

server:
  port: 8080
spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848    
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///cloud_user?useSSL=false
    username: root
    password: root
logging:
  level:
    com.itheima.user: debug
  pattern:
    dateformat: HH:mm:ss.SSS

application-8081serv.yaml:设置为8081端口

server:
  port: 8081
spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848    
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///cloud_user?useSSL=false
    username: root
    password: root
    
logging:
  level:
    com.itheima.user: debug
  pattern:
    dateformat: HH:mm:ss.SSS

2) 设置启动链接

设置步骤略,参考前边euraka集群的配置

给用户服务准备多个启动链接,一个激活8080serv配置文件,一个激活8081serv配置文件

3) 启动两个用户服务

  1. 启动两个用户服务,启动后在nacos界面上可看到用户服务有两个实例

  2. 再启动订单服务

2 订单服务调用用户服务

在浏览器*问http://localhost:7070/order/101,多次刷新访问,发现多个用户服务都被调用到了,而且是轮询的方式。

3. Ribbon实现原理分析

@LoadBalanced注解

注解的文档中说明了:如果使用@LoadBalanced标记一个RestTemplate或者WebClient对象,表示将会配置使用一个LoadBanalcerClient对象

LoadBalancerClient源码

LoadBalancerClient有一个子类RibbonLoadBalancerClient,它使用Ribbon实现了负载均衡调用

IRule源码

在上一步的getServer方法代码如下:

继续跟进去,发现是由rule对象挑选了一个目标服务地址

这个IRule是什么呢

4. Ribbon负载均衡策略

1) 负载均衡策略介绍

IRule是负载均衡策略的*接口,它的每个实现类就是一个我们可以选择使用的负载均衡策略

Ribbon内置了多种负载均衡策略,这些策略的*接口是com.netflix.loadbalancer.IRule,它的常用实现有:(可参考上一章节里的IRule类图)

2) 修改负载均衡策略

配置文件方式

我们可以通过修改配置文件来调用Ribbon的负载均衡策略,只要修改调用者的配置文件即可:

服务名:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

例如,在订单服务的application.yaml里添加如下配置:

#调用用户服务时,使用指定的负载均衡策略
user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

代码@Bean方式

我们修改订单服务的配置类(或引导类),添加:

@Bean
public IRule randomRule(){
    return new RandomRule();
}

注意事项

  1. 使用配置文件方式,只能设置 调用某一服务时指定负载均衡策略,而不是全局的策略;

    使用@Bean方式,配置的是全局的负载均衡策略

  2. 实际开发中,通常使用默认的负载均衡策略,不需要修改