首先需要启动一个微服务项目,这里以idea2022.1版本为例:

  1. 新建父工程(经Spring Initializr创建)

  2. 在父工程下添加多个Module(选择maven Archetype)
    WeLEv.png

  3. 然后为每个服务配置SpringbootApplication

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.lzc;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    @SpringBootApplication
    public class UserApplication
    {
    public static void main( String[] args )
    {
    SpringApplication.run(UserApplication.class,args);
    }
    }
  4. 启动多个服务后,可以看到idea下方的Services会显示已启动过的服务:

    WeYAZ.png

  5. 配置各个服务的端口号后再Rerun一下(此时会显示各个所服务运行的端口号):

    WzAkX.png

Eureka(NetFlix)

Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在其子项目spring-cloud-netflix中,以实现Spring Cloud的服务发现功能。

Eureka采用了C-S的设计架构,包含两个组件:Eureka Server 和 Eureka Client。Eureka Server作为服务注册功能的服务器,它是服务注册中心。各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。Eureka Client是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳。Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。

在Eureka架构中,微服务角色有两类:

  1. EurekaServer:服务端(注册中心)

    记录服务信息

    心跳监控

  2. EurekaClient:(客户端)

    Provider:服务提供者,注册自身服务信息到EurekaServer,同时每隔30秒向EurekaServer发送心跳

    Consumer:服务消费者,根据服务名称从EurekaServer拉取服务列表,同时基于服务列表做负载均衡,选中一个微服务后发起远程调用

Eureka的架构图如下:

WEafo.png

可以简单地将region理解为Eureka集群,zone理解成机房。这样图4-2就很好理解了——一个Eureka集群被部署在了zone1机房和zone2机房中。

Nacos(Alibaba)

首先需要注意SpringCloud Alibaba和Springboot版本之间的对应,对应关系可在官方github上找到,这里使用的版本为:

Spring Cloud Version:Spring Cloud Hoxton.SR9

Spring Cloud Alibaba Version:2.2.6.RELEASE

Spring Boot Version:2.3.2.RELEASE

组件版本关系

每个 Spring Cloud Alibaba 版本及其自身所适配的各组件对应版本如下表所示(注意,Spring Cloud Dubbo 从 2021.0.1.0 起已被移除出主干,不再随主干演进):

Spring Cloud Alibaba Version Sentinel Version Nacos Version RocketMQ Version Dubbo Version Seata Version
2022.0.0.0-RC 1.8.6 2.2.1-RC 4.9.4 ~ 1.6.1
2.2.9.RELEASE 1.8.5 2.1.0 4.9.4 ~ 1.5.2
2021.0.4.0 1.8.5 2.0.4 4.9.4 ~ 1.5.2
2.2.8.RELEASE 1.8.4 2.1.0 4.9.3 ~ 1.5.1
2021.0.1.0 1.8.3 1.4.2 4.9.2 ~ 1.4.2
2.2.7.RELEASE 1.8.1 2.0.3 4.6.1 2.7.13 1.3.0
2.2.6.RELEASE 1.8.1 1.4.2 4.4.0 2.7.8 1.3.0
2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE 1.8.0 1.4.1 4.4.0 2.7.8 1.3.0

使用dependencyManagement可以统一管理项目的版本号,确保应用的各个项目的依赖和版本一致dependencyManagement里只是声明依赖,并不自动实现引入,因此子项目需要显示的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!--父pom.xml-->
<!--子module通过dependency完成继承-->
<dependencyManagement>
<dependencies>
<!--SpringBoot 版本管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!--Springcloud- alibaba 版本管理-->
<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.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

注册中心

目前主流的注册中心有以下几种:

Nacos可以看作是一个集注册中心(服务发现)、配置中心、服务管理(可视化界面)的平台,Nacos有以下核心功能:

  1. 服务注册:Nacos Client(一个微服务)会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
  2. 服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
  3. 服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性(leader选举-raft算法)
  4. 服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
  5. 服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

Nacos可以实现CP+AP(CAP理论),任何一个分布式架构都无法同时满足三个特性!

CAP理论

CAP理论是分布式架构中重要理论:

  • 一致性(Consistency):所有节点在同一时间具有相同的数据;
  • 可用性(Availability) :保证每个请求不管成功或者失败都有响应(每次请求都能获取到非错的响应 但是不保证获取的数据为最新数据);
  • 分区容错性(Partition tolerance) :系统中任意信息的丢失或失败不会影响系统的继续运作(系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择)。

关于 P 的理解,我觉得是在整个系统中某个部分挂掉或者宕机了,并不影响整个系统的运作;而可用性是指某个系统的某个节点挂了,但是并不影响系统的接受或者发出请求(可通过集群实现)。

CAP 不可能都取,只能取其中2个的原因如下:

  • 如果C是第一需求的话,那么会影响A的性能,因为要数据同步,不然请求结果会有差异,但是数据同步会消耗时间,期间可用性就会降低。
  • 如果A是第一需求,那么只要有一个服务在,就能正常接受请求,但是对于返回结果变不能保证,原因是,在分布式部署的时候,数据一致的过程不可能想切线路那么快。
  • 再如果,同时满足一致性和可用性,那么分区容错就很难保证了,也就是单点,也是分布式的基本核心。

另一种理解角度:想象两个节点分处分区两侧 ,允许至少一个节点更新状态会导致数据不一致,即丧失了C性质;如果为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了A性质。除非两个节点可以互相通信,才能既保证C又保证A,这又会导致丧失P性质

分布式系统协议

一致性协议算法主要有Paxos、Raft、ZAB

Paxos算法是Leslie Lamport在1990年提出的一种基于消息传递的一致性算法,非常难以理解,基于Paxos协议的数据同步与传统主备方式最大的区别在于:Paxos只需超过半数的副本在线且相互通信正常,就可以保证服务的持续可用,且数据不丢失。

Raft是斯坦福大学的Diego Ongaro、John Ousterhout两个人以易理解为目标设计的一致性算法,已经有了十几种语言的Raft算法实现框架,较为出名的有etcd,Google的Kubernetes也是用了etcd作为他的服务发现框架。Raft是Paxos的简化版,与Paxos相比,Raft强调的是易理解、易实现,Raft和Paxos一样只要保证超过半数的节点正常就能够提供服务。这篇文章 《ETCD教程-2.Raft协议》 详细讲解了Raft原理,非常有意思,感兴趣的同学可以看看。

ZooKeeper Atomic Broadcast (ZAB, ZooKeeper原子消息广播协议)是ZooKeeper实现分布式数据一致性的核心算法,ZAB借鉴Paxos算法,但又不像Paxos算法那样,是一种通用的分布式一致性算法,它是一种特别为ZooKeeper专门设计的支持崩溃恢复的原子广播协议。

安装Nacos

根据官方提供的Springcloud-Alibaba对应微服务组件版本表得到所需要安装的Nacos版本为1.4.2,下载后解压,在bin目录下找到startup.cmd(linux下则为startup.sh),修改此文件中mode选项,设置为单机模式(默认启动为集群模式):

set *MODE*="standalone"

修改完成后运行startup.cmd,可以发现nacos默认运行在内存中(默认端口号为8848):

1
2
3
4
5
6
7
2023-01-06 21:23:42,070 INFO Tomcat started on port(s): 8848 (http) with context path '/nacos'

2023-01-06 21:23:42,073 INFO Nacos started successfully in stand alone mode. use embedded storage

2023-01-06 21:23:57,345 INFO Initializing Servlet 'dispatcherServlet'

2023-01-06 21:23:57,351 INFO Completed initialization in 6 ms

注册微服务

打开idea,分别为order-service、stock-service两个微服务配置nacos:

修改各自子目录下的application.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8010 #端口自定义
spring:
application:
name: order-service #应用名称
cloud:
nacos:
server-addr: 127.0.0.1:8848 #本机地址
discovery:
username: nacos
password: nacos
namespace: public

设置完成后,分别启动OrderApplicationStockApplication,并登录nacos(127.0.0.1:8848)来查看微服务运行状态:

kC1L5.png

远程调用

服务之间相互调用时须经过负载均衡器!

RestTemplate

RestTemplate是Spring提供的用于访问REST服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法(传统情况下在java代码里访问restful服务,一般使用Apache的HttpClient,不过此方法使用起来太过繁琐)

1
2
3
4
5
6
7
@Bean
@LoadBalanced //配置负载均衡器
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder)
{
RestTemplate restTemplate=restTemplateBuilder.build(); //构造器模式
return restTemplate;
}

配置完成后,就可以在OrderController中调用stock-service中的请求:

1
2
3
4
5
6
7
8
9
10
@Autowired
RestTemplate restTemplate;
@RequestMapping("/add")
public String add()
{
System.out.println("下单成功");
//springboot调用服务的方式restTemplate: 远程调用stock-service中的/stock/reduce请求
String msg= restTemplate.getForObject("http://stock-service/stock/reduce",String.class);
return "hello orderservice"+msg;
}

模拟负载均衡

Nacos默认调用NetFlix-Ribbon来实现负载均衡:

StockController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@RestController
@RequestMapping("/stock")
public class StockController {
@Value("${server.port}") //@Value("${}"):可以获取对应属性文件中定义的属性值。
String port;

@RequestMapping("/reduce")
public String reduce ()
{
System.out.println("扣减库存");
return "扣减库存"+"当前调用的stock-service port为"+port;
}
}

常用配置参数

保护阈值

可以设置为0-1之间的浮点数,它其实是⼀个⽐例值(当前服务健康实例数/当前服务总实例数)⼀般流程下, nacos是服务注册中⼼,服务消费者要从nacos获取某⼀个服务的可⽤实例信息,对于服务实例有健康/不健康状态之分, nacos在返回给消费者实例信息的时候,会返回健康实例。这个时候在⼀些⾼并发、⼤流量场景下会存在⼀定的问题。如果服务A有100个实例, 98个实例都不健康了,只有2个实例是健康的,如果nacos只返回这两个健康实例的信息的话,那么后续消费者的请求将全部被分配到这两个实例,流量洪峰到来, 2个健康的实例也扛不住了,整个服务A 就扛不住,上游的微服务也会导致崩溃,产⽣雪崩效应

保护阈值的意义在于当服务A健康实例数/总实例数 < 保护阈值 的时候,说明健康实例真的不多了,这个时候保护阈值会被触
发(状态true),nacos将会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康
的实例,请求失败,但这样也比造成雪崩要好,牺牲了⼀些请求,保证了整个系统的⼀个可用。

临时实例

配置Nacos集群

单机搭建伪集群

(1)新建三个目录(nacos-8849、nacos-8850、nacos-8851)(至少三个节点),修改各自conf/application.properties文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
### Default web server port 分别修改三个节点端口:
server.port=8849

### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://192.168.1.107:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=Lzc8081501.

(2)在datagrip中执行官方默认的nacos建表sql语句(./conf/nacos-mysql.sql),得到以下结果:

kcQSd.png

(3)修改cluster.conf文件(复制官方提供的example文件对其进行修改):

1
2
3
4
5
#it is ip
#example 如果是在windows下 不要使用127.0.0.1本地地址会报错!!!(因为一旦启动后会追加本地nacos节点的地址到该文件)
192.168.193.129:8849
192.168.193.129:8850
192.168.193.129:8851

(4)依次启动三个节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"nacos is starting with cluster"

,--.
,--.'|
,--,: : | Nacos 1.4.1
,`--.'`| ' : ,---. Running in cluster mode, All function modules
| : : | | ' ,'\ .--.--. Port: 8850
: | \ | : ,--.--. ,---. / / | / / ' Pid: 27348
| : ' '; | / \ / \. ; ,. :| : /`./ Console: http://192.168.1.107:8850/nacos/index.html
' ' ;. ;.--. .-. | / / '' | |: :| : ;_
| | | \ | \__\/: . .. ' / ' | .; : \ \ `. https://nacos.io
' : | ; .' ," .--.; |' ; :__| : | `----. \
| | '`--' / / ,. |' | '.'|\ \ / / /`--' /
' : | ; : .' \ : : `----' '--'. /
; |.' | , .-./\ \ / `--'---'
'---' `--`---' `----'

2023-01-07 16:09:40,843 INFO The server IP list of Nacos is [127.0.0.1:8849, 127.0.0.1:8850, 127.0.0.1:8851]

2023-01-07 16:09:41,848 INFO Nacos is starting...

2023-01-07 16:09:42,849 INFO Nacos is starting...

2023-01-07 16:09:43,857 INFO Nacos is starting...

2023-01-07 16:09:44,865 INFO Nacos is starting...

2023-01-07 16:09:45,393 INFO Nacos started successfully in cluster mode. use external storage

配置Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 #配置nacos-cluster
upstream nacos-cluster {
server 127.0.0.1:8849;
server 127.0.0.1:8850;
server 127.0.0.1:8851;
}
server {
listen 80; #nginx启动的默认端口号
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location /nacos {
proxy_pass http://nacos-cluster;
#反向代理 当输入 127.0.0.1:80/nacos时,nginx就会通过反向代理映射到nacos-cluster集群上去
}
location = /50x.html {
root html; #nginx默认error界面

}
}

最后将微服务注册中心的server-addr属性改为nginx就完成了:

1
2
3
cloud:
nacos:
server-addr: 127.0.0.1:80 #本地nginx端口地址

访问localhost:80/nacos查看集群中节点情况:

k7dwv.png