SpringCloud集群服务
大约 14 分钟
微服务集群简介
微服务集群架构
- 了提高微服务的处理性能,,就需要在多个节点上进行相同的微服务部署,这样就会形成个个微服务集群。所有的微服务如果需要被客户端进行访问,那么就必须通过 Nacos 注册中心进行注册管理,而随着微服务集群中的节点数量增加,伴随而来的就是注册中心的访问量增大,所以为了保证注册中心的服务查找性能,就需要进行服务中心的集群搭建,,这样对干-个微服务架构来讲就需要两个基础的集群架构:服务集群(按照功能划分)、注册中心集群
Nacos 服务集群
1、
rm -rf /usr/local/nacos/{data,logs}
2、
vi /etc/sysconfig/network-scripts/ifcfg-ens33
3、
IPADDR=192.168.190.151
IPADDR=192.168.190.152
IPADDR=192.168.190.153
4、
vi /etc/hostname
5、
vi /etc/hosts
6、
192.168.16.10 nacos-cluster-a
192.168.16.11 nacos-cluster-b
192.168.16.12 nacos-cluster-c
7、
cp /usr/local/nacos/conf/cluster.conf.example /usr/local/nacos/conf/cluster.conf
8、
vi /usr/local/nacos/conf/cluster.conf
9、
vi /usr/local/nacos/conf/cluster.conf
10、
nacos-cluster-a:8848
nacos-cluster-b:8848
nacos-cluster-c:8848
11、
scp /usr/local/nacos/conf/cluster.conf nacos-cluster-b:/usr/local/nacos/conf/
scp /usr/local/nacos/conf/cluster.conf nacos-cluster-c:/usr/local/nacos/conf/
12、
vi /usr/local/nacos/bin/startup.sh
13、
JAVA_OPT="${JAVA_OPT} -Dnacos.server.ip=nacos-cluster-a"
14、
JAVA_OPT="${JAVA_OPT} -Dnacos.server.ip=nacos-cluster-b"
JAVA_OPT="${JAVA_OPT} -Dnacos.server.ip=nacos-cluster-c"
15、
bash -f /usr/local/nacos/bin/startup.sh
16、
DROP DATABASE nacos;
CREATE DATABASE nacos CHARACTER SET UTF8;
USE nacos;
source /usr/local/src/nacos-mysql.sql
17、
192.168.190.151 nacos-cluster-a
192.168.190.152 nacos-cluster-b
192.168.190.153 nacos-cluster-c
Nacos 控制台代理
Nacos 控制台代理
- Nacos 集群一旦启用之后,开发者可以随意的进行该集群任意一节点的访问,以登录 Nacos 控制台,而所做的全部配置都会统一的保存到 MySQL 数据库之中。为了便干 Nacos 集群节点中的控制台统管理,就需要提供一个统一的代理地址,这样不仅可以有效的保护所有的 Nacos 节点也便于运维人员管理。
1、
https://nginx.org/
2、
vi /etc/sysconfig/network-scripts/ifcfg-ens33
3、
vi /etc/hostname
4、
vi /etc/hosts
192.168.16.10 nacos-cluster-a
192.168.16.11 nacos-cluster-b
192.168.16.12 nacos-cluster-c
192.168.16.13 nacos-proxy
5、
cd /var/ftp
6、
wget https://nginx.org/download/nginx-1.21.1.tar.gz
7、
tar xzvf /var/ftp/nginx-1.21.1.tar.gz -C /usr/local/src
8、
mkdir -p /usr/local/nginx/{logs,conf,sbin}
9、
yum -y install pcre-devel openssl openssl-devel
10、
./configure --prefix=/usr/local/nginx/ --sbin-path=/usr/local/nginx/sbin/ --with-http_ssl_module \
--conf-path=/usr/local/nginx/conf/nginx.conf --pid-path=/usr/local/nginx/logs/nginx.pid \
--error-log-path=/usr/local/nginx/logs/error.log --http-log-path=/usr/local/nginx/logs/access.log --with-http_v2_module
11、
make && make install
12、
vi /usr/local/nginx/conf/nginx.conf
13、
upstream nacoscluster {
server nacos-cluster-a:8848 weight=3 ;
server nacos-cluster-b:8848 weight=1 ;
server nacos-cluster-c:8848 weight=2 ;
}
14、
location / {
proxy_pass http://nacoscluster;
root /nacos/;
}
15、
/usr/local/nginx/sbin/nginx -t
16、
firewall-cmd --zone=public --add-port=8848/tcp --permanent
firewall-cmd --reload
17、/usr/local/nginx/sbin/nginx
18、nacos-proxy:8848/nacos
gRPC 注册服务代理
Nacos 服务注册与获取
- 在 Nacos 2.x 开发版本中为了提高服务注册与发现管理的性能,采用了 qRPC 协议,所以在进行服务代理时就可以利用 HAProxy 代理组件来实现 Nacos 集群管理。HAProxy 是一款高可用组件,可以有效的实现集群服务节点的负载均衡以及基于 TCP(第四层)和 HTTP(第七层)应用的代理软件,开发者可以直接通过 HAProxy 官方站点(haproxy.org/)免费获取该组件
1、
cd /var/ftp
wget https://www.haproxy.org/download/2.4/src/haproxy-2.4.2.tar.gz
2、
tar xzvf /var/ftp/haproxy-2.4.2.tar.gz -C /usr/local/src/
3、
cd /usr/local/src/haproxy-2.4.2/
4、
make TARGET=custom ARCH=x86_64 PREFIX=/usr/local/haproxy
make install PREFIX=/usr/local/haproxy
5、
vi /usr/local/haproxy/haproxy.cfg
global # 全局配置
log 127.0.0.1 local0 # 启用日志
nbproc 1 # 监控进程个数
maxconnrate 300 # 进程每秒所能创建的最大连接数
maxcomprate 300 # 压缩速率
maxsessrate 500 # 进程每秒能创建的会话数量
chroot /usr/local/haproxy # HAProxy部署路径
pidfile /usr/local/haproxy/haproxy.pid # pid文件存储路径
maxconn 30000 # 进程所能接收的最大并发连接数
user haproxy # 启动用户名
group haproxy # 启动用户组
daemon # 后台模式运行
stats socket /usr/local/haproxy/stats # 开启统计Socket
defaults # 默认配置
mode http # http处理模式
log global # 全局日志配置
option dontlognull # 不记录健康日志信息
option redispatch # 允许重新分配session
option http-use-htx # 启用HTTP/2
option logasap # 传输大文件时提前记录日志
option httplog # 日志类别
retries 3 # 失败重试次数
timeout queue 1m # 队列超时
timeout connect 5m # 连接超时
timeout client 5m # 客户端超时
timeout server 5m # 服务端超时
timeout http-keep-alive 100s # 保持HTTP连接
timeout check 10s # 超时检查
listen admin_stats # 管理控制台
stats enable # 启用管理控制台
bind 0.0.0.0:9999 # 监控端口设置
mode http # 管理控制台模式
log global # 日志配置
maxconn 10 # 最大连接数量
stats uri /admin # 登录监控子路径配置
stats realm welcome\ Haproxy # 登录提示信息
stats auth admin:admin # 监控的账号密码
stats admin if TRUE # 启用管理员模式
option httplog # http日志记录
stats refresh 30s # 监控刷新时间
stats hide-version # 隐藏页面版本号
frontend nacos_cluster # 代理集群配置(名称自定义)
bind :9848 # 代理端口
mode http # 代理模式
log global # 日志配置
maxconn 8000 # 最大连接数
default_backend nacos_cluster_nodes # 代理节点名称(名称自定义)
backend nacos_cluster_nodes # 集群节点(名称自定义)
mode http # 代理模式
server nacos-a nacos-cluster-a:9848 check # 集群节点
server nacos-b nacos-cluster-b:9848 check # 集群节点
server nacos-c nacos-cluster-c:9848 check # 集群节点
6、
useradd haproxy
7、
/usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.cfg
8、
firewall-cmd --zone=public --add-port=9999/tcp --permanent
firewall-cmd --zone=public --add-port=9848/tcp --permanent
firewall-cmd --reload
9、
http://nacos-proxy:9999/admin admin/admin
微服务集群注册
微服务集群注册
- 为了提高某一个微服务的处理性能,较为常见的做法就是为其配置更多的处理节点,这样为了便于微服务的节点管理,就必须采用相同的 Nacos 领域模式进行服务注册,这样消费端才可以通过指定的领域模型获取微服务数据。
1、
rootProject.name = 'microcloud'
include 'common-api'
include 'provider-dept-8001'
include 'provider-dept-8002'
include 'provider-dept-8003'
include 'consumer-springboot-80'
include 'nacos-example'
2、
DROP DATABASE IF EXISTS yootk8002;
DROP DATABASE IF EXISTS yootk8003;
3、
telnet nacos-server 9848
telnet nacos-proxy 9848
4、
vi /usr/local/haproxy/haproxy.cfg
5、
global
log 127.0.0.1 local0
nbproc 1
maxconnrate 300
maxcomprate 300
maxsessrate 500
chroot /usr/local/haproxy #部署路径
pidfile /usr/local/haproxy/haproxy.pid #pid文件
maxconn 30000
user haproxy
group haproxy
daemon
# turn on stats unix socket
stats socket /usr/local/haproxy/stats
defaults
mode tcp
log global
option dontlognull
option redispatch
option http-use-htx
option logasap
option tcplog
retries 3
#timeout http-request 100s
timeout queue 1m
timeout connect 5m
timeout client 5m
timeout server 5m
timeout connect 5m
timeout http-keep-alive 100s
timeout check 10s
maxconn 100000
listen admin_stats
stats enable
bind 0.0.0.0:9999 #监控端口设置
mode http
log global
maxconn 10
stats uri /admin #登录监控子路径配置
stats realm welcome\ Haproxy
stats auth admin:admin #监控的账号密码
stats admin if TRUE
option httplog
stats refresh 30s
stats hide-version
frontend nacos_cluster
bind :9848
mode tcp
log global
option tcplog
option dontlognull
option nolinger
maxconn 8000
timeout client 30s
default_backend nacos_cluster_nodes
backend nacos_cluster_nodes
mode tcp
server nacos-a nacos-cluster-a:9848 check
server nacos-b nacos-cluster-b:9848 check
server nacos-c nacos-cluster-c:9848 check
6、
/usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.cfg
7、
vi /usr/local/nacos/conf/cluster.conf
192.168.190.151:8848
192.168.190.152:8848
192.168.190.153:8848
scp /usr/local/nacos/conf/cluster.conf nacos-cluster-b:/usr/local/nacos/conf/
scp /usr/local/nacos/conf/cluster.conf nacos-cluster-c:/usr/local/nacos/conf/
8、
DROP DATABASE nacos;
CREATE DATABASE nacos CHARACTER SET UTF8;
USE nacos;
source /usr/local/src/nacos-mysql.sql
9、
/usr/local/nacos/bin/startup.sh
rm -rf /usr/local/nacos/logs/*
客户端服务访问
微服务消费端
- 此时所有的微服务已经成功的注册到了 Nacos 集群之中,这样就需要编写消费端,而后该消费端设置与微服务相同的领域模型数据(NamespacelD、Group、ServiceName 即可实现微服务数据调用
- 客户端在实现服务查找时需要在项目中配置“spring-cloud-starter-alibaba-nacos-依赖库,而后利用该依赖库所提供的 DiscoveryClient 实例即可根据指定的 discovery"微服务名称实现所有相关服务节点的获取
1、
project(":consumer-springboot-80") { // 消费端模块
dependencies {
implementation(project(":common-api")) // 导入公共的子模块
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
}
}
2、
server: # 服务端配置
port: 80 # 这个接口可以随意,反正最终都是由前端提供服务
spring:
application: # 配置应用信息
name: consumer # 是微服务的名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
server-addr: nacos-cluster-a:8848,nacos-cluster-b:8848,nacos-cluster-c:8848 # Nacos服务地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: MuyanCluster # 配置集群名称
username: muyan # 用户名
password: yootk # 密码
register-enabled: false # 消费端不注册
3、
package com.yootk.consumer.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
@Component // Bean注册
@Slf4j // Lombok日志
public class RandomAccessUtil { // 自定义的随机访问算法
@Autowired // 注入Nacos中的发现服务客户端
private DiscoveryClient discoveryClient; // 发现服务
/**
* 根据指定的微服务名称来获取完整的处理路径
* @param serviceName Nacos之中注册的微服务的实例名称
* @param uri 相对的访问路径
* @return 是一个完整的拼凑路径
*/
public String getTargetUrl(String serviceName, String uri) {
// 根据指定的服务名称获取全部的服务实例
List<ServiceInstance> instances = this.discoveryClient.getInstances(serviceName);
// 现在是根据名称来获取所有的实例信息,所以如果名称不存在,一定获取不到
if (instances.size() == 0) { // 没有该服务名称实例
throw new RuntimeException("Nacos服务名称ID无法获取,拜拜喽~");
}
List<String> collect = instances.stream().map(instance -> instance.getUri().toString() + uri)
.collect(Collectors.toList());
int num = ThreadLocalRandom.current().nextInt(collect.size()); // 随机算法
String targetURL = collect.get(num); // 随机抽取一个完整的路径
log.info("获取Nacos服务注册地址:{}", targetURL);
return targetURL;
}
}
4、
package com.yootk.consumer.action;
import com.yootk.common.dto.DeptDTO;
import com.yootk.consumer.util.RandomAccessUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/consumer/dept/*") // 两个不同的服务路径
public class DeptConsumerAction { // 消费端Action
// 定义出要访问的部门微服务所需要的核心路径前缀,随后在具体请求的时候添加传递的参数
public static final String DEPT_ADD_URL = "/provider/dept/add";
public static final String DEPT_GET_URL = "/provider/dept/get/"; // id是自己变更的
public static final String DEPT_LIST_URL = "/provider/dept/list";
public static final String DEPT_SPLIT_URL = "/provider/dept/split";
public static final String SERVICE_ID = "dept.provider"; // Nacos中注册的服务名称
@Autowired
private RestTemplate restTemplate; // 再见最后一面
@Autowired
private RandomAccessUtil randomAccessUtil; // 根据服务名称拼凑路径
@GetMapping("add") // 消费端接口名称
public Object addDept(DeptDTO dto) {
// 需要将当前的DTO对象传递到部门微服务之中,所以此时就要通过RestTemplate对象处理
String deptAddUrl = this.randomAccessUtil.getTargetUrl(SERVICE_ID, DEPT_ADD_URL); // 路径的拼凑
return this.restTemplate.postForObject(deptAddUrl, dto, Boolean.class);
}
@GetMapping("get")
public Object get(Long deptno) {
String deptGetUrl = this.randomAccessUtil.getTargetUrl(SERVICE_ID, DEPT_GET_URL); // 路径的拼凑
return this.restTemplate.getForObject(deptGetUrl + deptno, DeptDTO.class);
}
@GetMapping("list")
public Object list() {
String deptListUrl = this.randomAccessUtil.getTargetUrl(SERVICE_ID, DEPT_LIST_URL); // 路径的拼凑
return this.restTemplate.getForObject(deptListUrl, List.class);
}
@GetMapping("split")
public Object split(int cp, int ls, String col, String kw) {
String deptSplitUrl = this.randomAccessUtil.getTargetUrl(SERVICE_ID, DEPT_SPLIT_URL); // 路径的拼凑
return this.restTemplate.getForObject(deptSplitUrl + "?cp=" + cp + "&ls=" + ls + "&col=" + col + "&kw=" + kw, Map.class);
}
}
5、
package com.yootk.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class StartConsumerApplication { // 沐言科技:www.yootk.com
public static void main(String[] args) {
SpringApplication.run(StartConsumerApplication.class, args);
}
}
CP 与 AP 模式切换
节点数据同步
- 引入 Nacos 服务集群的可以提高服务注册与发现的处理性能,然而在实际的 Nacos 运行过程之中,每一个 Nacos 数据节点都只会保存自己的配置数据,由于集群中的每个节点都需要满足数据的修改与获取的功能需求所以就需要将集群中的节点数据进行自动同步处理,即:某一个节点数据发生改变时将同步到其他节点。
分区容错性
- Nacos 服务集群之中会包含有众多的数据节点,这样所有的数据可以分别保存在不同的数据节点之中,一旦某一个节点发生了故障,应该可以及时的满足分区容错性(简称“P 原则”)设计要求,即:可以直接通过其它非故障节点 Partition toerance、获取所需要的数据项
虽然通过若干个节点可以实现数据的分区存储,但是这若干个节点之间毕竟要有数据同步的需要,所以此时在进行数据处理时就存在有两种实现原则:-一致性原见 Consistency、简称“C 原则”)、可用性原则(Availability、简称“A 原则”
CP 原则:一致性原则+分区容错性原则
- CP 原则属于强一致性原则,要求所有节点可以查询到的数据随时随刻都保持一致(同步中的数据不可查询),即:若干个节点形成一个逻辑的共享区域,某一个节点更新的数据都会立即同步到其它数据节点之中,当数据同步完成后才能返回成功的结果,但是在实际的运行过程中网络鼓掌在所难免,如果此时若于个服务节点之间无法通讯时就会出现错误,从而牺牲了可用性原则(A 原则)。
Raft 算法
- 在 Nacos 中的 CP 原则实现依靠的是 Raft 算法,在 Raft 将一致性算法分为了几个部分安全(Safety)包括领导选取(Leader Selection)、日志复制(Log Replication)、并且使用了更强的一致性来减少了必须需要考虑的状态。在 Raft 算法中将服务端节点划分为三种不同的状态(或者称为角色) ·领导者(Leader):负责客户端交互以及日志复制,在同一时刻的集群环境中只最多存在一个 Leader: 跟随着(Folower)被动请求的节点,跟随 Leader 实现数据同步;: 候选人(Candidate 一个临时的角色,只存在于 Leader 选举阶段,某个节点要想成为 Leader,就需要发出投票请求同时自己也将变为 Candidate 状态,如果选举成功则变为 Leader,否则退回为 Follower。
Raft 节点状态
- 在 Raft 算法中,所有的数据更新指令全部由 Leader 发出,更新命令全部保存在状态机(节点更新命令)之中,这样所有的 Follower 会依据状态机的顺序执行更新命令,当所有的 Folower 会变为 Candidate 状态,并且由一个节点发起 Leader 状态不可用后当投票通过后该节点成为新的 Leader,而此时其他的 Candidate 将变为投票,Follower 继续实现数据同步处理
1、
https://zookeeper.apache.org/
2、
git clone git@gitee.com:mirrors/Nacos.git
3、
curl -X PUT "nacos-proxy:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP&username=nacos&password=nacos"
4、
curl -X PUT "nacos-proxy:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=AP&username=nacos&password=nacos"
Ribbon 服务调用
Ribbon 组件作用
- 在传统的微服务消费端进行服务调用时,需要通过完整的地址来实现微服务的访问,同时在进行微服务调用时,又需要处理网络问题所带来的服务调用问题,这样在一定程度上就增加了客户端调用与维护难度。
- 为了提供便捷的微服务客户端调用支持,在 SpringCloud 项目中提供了 Ribbon 组件该组件是 Netflix 发布的开源项目,其最重要的功能就是提供了客户端的负载均衡算法(可以根据算法随机的实现不同的服务端节点调用),除此之外 Ribbon 还提供有一系列完善的服务调用配置项,例如:连接超时、失败重试、访问权重、优先级调用等,在使用时直接与 RestTemplate 结合即可通过微服务的名称实现服务调用
//最新版禁用bootstrap
implementation('org.springframework.cloud:spring-cloud-starter-bootstrap')
//不用ribbon来实现负载均衡,因为SpringCloud2020.0.1.0版本不使用netflix了。
implementation('org.springframework.cloud:spring-cloud-starter-loadbalancer')
1、
package com.yootk.consumer.config;
import com.yootk.consumer.interceptor.MicroServiceHTTPInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.util.Collections;
@Configuration
public class RestTemplateConfig { // 实现RestTemplate的相关配置
@Autowired
private MicroServiceHTTPInterceptor interceptor; // 注入拦截器
@LoadBalanced
@Bean // 向Spring容器之中进行Bean注册
public RestTemplate getRestTemplate() {
RestTemplate template = new RestTemplate();
template.setInterceptors(Collections.singletonList(this.interceptor));
return template;
}
}
2、
package com.yootk.consumer.action;
import com.yootk.common.dto.DeptDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/consumer/dept/*") // 两个不同的服务路径
public class DeptConsumerAction { // 消费端Action
// 定义出要访问的部门微服务所需要的核心路径前缀,随后在具体请求的时候添加传递的参数
public static final String DEPT_ADD_URL =
"http://dept.provider/provider/dept/add";
public static final String DEPT_GET_URL =
"http://dept.provider/provider/dept/get/"; // id是自己变更的
public static final String DEPT_LIST_URL =
"http://dept.provider/provider/dept/list";
public static final String DEPT_SPLIT_URL =
"http://dept.provider/provider/dept/split";
@Autowired
private RestTemplate restTemplate; // 再见最后一面
@GetMapping("add") // 消费端接口名称
public Object addDept(DeptDTO dto) {
// 需要将当前的DTO对象传递到部门微服务之中,所以此时就要通过RestTemplate对象处理
return this.restTemplate.postForObject(DEPT_ADD_URL, dto, Boolean.class);
}
@GetMapping("get")
public Object get(Long deptno) {
return this.restTemplate.getForObject(DEPT_GET_URL + deptno, DeptDTO.class);
}
@GetMapping("list")
public Object list() {
return this.restTemplate.getForObject(DEPT_LIST_URL, List.class);
}
@GetMapping("split")
public Object split(int cp, int ls, String col, String kw) {
return this.restTemplate.getForObject(DEPT_SPLIT_URL + "?cp=" + cp + "&ls=" + ls + "&col=" + col + "&kw=" + kw, Map.class);
}
}
3、
ribbon: # Ribbon相关配置
eager-load: # 采用饿汉式进行加载
clients: dept.provider # 设置服务名称,使用“,”分割
enabled: true # 启用饿汉式
demo