文章目录
注:文章为博主自用版
nacos
nacos原理简述
nacos优势(自理解):注册中心+配置中心(比如阿波罗这种)+自带负载均衡,功能比较齐全,且阿里系中间件 经历了双十一的考验,国产,文档易读且社区活跃。
注册中心原理: 配置注册到注册中心(registerInstance方法),注册中心得到了客户端地址和实例名,通过心跳检测机制,知道是否存活,如果一段时间内发现不存在心跳 则认定不存在 踢下线。
ribbon: nacos集成了ribbon , 原理是有一个拦截器,将实例名解析成网址,在本地缓存。
nacos一致性协议:
Distro协议是Nacos社区自研的一种AP分布式协议,是面向临时实例设计的一种分布式协议,其保证在某些Nacos节点宕机后,整个临时实例处理系统依旧可以正常工作。作为一种有状态的中间件应用内嵌协议,Distro保证了各个Nacos节点对于注册请求的统一协调和储存。
Distro协议的主要设计思想如下:
- Nacos 每个节点是平等的都可以处理写请求,同时把新数据同步到其他节点。
- 每个节点只负责部分数据,定时发送自己负责数据的校验值到其他节点来保持数据一致性。
- 每个节点独立处理读请求,及时从本地发出响应。
新加入的 Distro 节点会进行全量数据拉取。具体操作是轮询所有的 Distro 节点,通过向其他的机器发送请求拉取全量数据。
在全量拉取操作完成之后,Nacos 的每台机器上都维护了当前的所有注册上来的非持久化实例数据。
在 Distro 集群启动之后,各台机器之间会定期的发送心跳。心跳信息主要为各个机器上的所有数据的元信息(之所以使用元信息,是因为需要保证网络中数据传输的量级维持在⼀个较低水平)。这种数据校验会以心跳的形式进行,即每台机器在固定时间间隔会向其他机器发起⼀次数据校验请求。
⼀旦在数据校验过程中,某台机器发现其他机器上的数据与本地数据不⼀致,则会发起⼀次全量拉取请求,将数据补齐。
nacos原理源码分析
AP模式集群数据同步源码分析
全量数据同步
首先我们需要先找到DistroProtocol类型,它就是Distro协议的实现,然后在它的构造方法中,启动了一个startDistroTask()任务,其中包括了初始化同步任务 startLoadTask()
private void startDistroTask() {
if (EnvUtil.getStandaloneMode()) {
isInitialized = true;
return;
}
startVerifyTask();
// 初始化同步任务
startLoadTask();
}
startLoadTask()数据加载任务创建了一个DistroLoadDataTask任务,并传入了一个修改当前节点Distro协议完成状态的回调函数。
private void startLoadTask() {
DistroCallback loadCallback = new DistroCallback() {
@Override
public void onSuccess() {
isInitialized = true;
}
@Override
public void onFailed(Throwable throwable) {
isInitialized = false;
}
};
//传入了一个修改当前节点Distro协议完成状态的回调函数。
GlobalExecutor.submitLoadDataTask(
new DistroLoadDataTask(memberManager, distroComponentHolder, DistroConfig.getInstance(), loadCallback));
}
接下来我们需要查看load方法,这里判断了几种情况,其中loadAllDataSnapshotFromRemote(读取所有远程的数据快照)
private void load() throws Exception {
// 若除了自身之外没有其他节点,则休眠一秒,可以其他节点未启动
while (memberManager.allMembersWithoutSelf().isEmpty()) {
Loggers.DISTRO.info("[DISTRO-INIT] waiting server list init...");
TimeUnit.SECONDS.sleep(1);
}
// 若数据类型为空,说明distroComponentHolder的组件注册器还未初始化完毕
while (distroComponentHolder.getDataStorageTypes().isEmpty()) {
Loggers.DISTRO.info("[DISTRO-INIT] waiting distro data storage register...");
TimeUnit.SECONDS.sleep(1);
}
// 加载每个类型的数据
for (String each : distroComponentHolder.getDataStorageTypes()) {
if (!loadCompletedMap.containsKey(each) || !loadCompletedMap.get(each)) {
// 调用加载方法,并标记已处理
loadCompletedMap.put(each, loadAllDataSnapshotFromRemote(each));
}
}
}
调用loadAllDataSnapshotFromRemote(each)方法获取同步数据,从其他节点获取同步数据,使用DistroTransportAgent获取数据,使用DistroDataProcessor来处理数据。
private boolean loadAllDataSnapshotFromRemote(String resourceType) {
// 获取数据传输对象
DistroTransportAgent transportAgent = distroComponentHolder.findTransportAgent(resourceType);
// 获取数据处理器
DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);
if (null == transportAgent || null == dataProcessor) {
Loggers.DISTRO.warn("[DISTRO-INIT] Can't find component for type {}, transportAgent: {}, dataProcessor: {}",
resourceType, transportAgent, dataProcessor);
return false;
}
// 向每个节点请求数据
for (Member each : memberManager.allMembersWithoutSelf()) {
try {
Loggers.DISTRO.info("[DISTRO-INIT] load snapshot {} from {}", resourceType, each.getAddress());
// 获取数据
DistroData distroData = transportAgent.getDatumSnapshot(each.getAddress());
// 解析数据
boolean result = dataProcessor.processSnapshot(distroData);
Loggers.DISTRO
.info("[DISTRO-INIT] load snapshot {} from {} result: {}", resourceType, each.getAddress(),
result);
// 若解析成功,标记此类型数据已经加载完毕
if (result) {
distroComponentHolder.findDataStorage(resourceType).finishInitial();
return true;
}
} catch (Exception e) {
Loggers.DISTRO.error("[DISTRO-INIT] load snapshot {} from {} failed.", resourceType, each.getAddress(), e);
}
}
return false;
}
这里我们要查看一下如何获取的数据,使用DistroTransportAgent获取数据getDatumSnapshot()方法,看完这个方法我们还要看一下如何处理数据。
@Override
public DistroData getDatumSnapshot(String targetServer) {
// 从节点管理器获取目标节点信息
Member member = memberManager.find(targetServer);
// 判断目标服务器是否健康
if (checkTargetServerStatusUnhealthy(member)) {
throw new DistroException(
String.format("[DISTRO] Cancel get snapshot caused by target server %s unhealthy", targetServer));
}
// 构建请求参数
DistroDataRequest request = new DistroDataRequest();
// 设置请求的操作类型为DataOperation.SNAPSHOT(数据快照)
request.setDataOperation(DataOperation.SNAPSHOT);
try {
Response response = clusterRpcClientProxy.sendRequest(member, request);
if (checkResponse(response)) {
return ((DistroDataResponse) response).getDistroData();
} else {
throw new DistroException(
String.format("[DISTRO-FAILED] Get snapshot request to %s failed, code: %d, message: %s",
targetServer, response.getErrorCode(), response.getMessage()));
}
} catch (NacosException e) {
throw new DistroException("[DISTRO-FAILED] Get distro snapshot failed! ", e);
}
}
使用DistroDataProcessor来处理数据processSnapshot(distroData)方法
@Override
public boolean processSnapshot(DistroData distroData) {
// 反序列化获取distroData为ClientSyncDatumSnapshot
ClientSyncDatumSnapshot snapshot = ApplicationUtils.getBean(Serializer.class)
.deserialize(distroData.getContent(), ClientSyncDatumSnapshot.class);
// 处理结果集,这里将返回远程节点负责的所有Client以及所有的service、instance信息
for (ClientSyncData each : snapshot.getClientSyncDataList()) {
// 处理每一个client
handlerClientSyncData(each);
}
return true;
}
具体处理数据方法handlerClientSyncData()
private void handlerClientSyncData(ClientSyncData clientSyncData) {
Loggers.DISTRO.info("[Client-Add] Received distro client sync data {}", clientSyncData.getClientId());
// 因为是同步数据,所以这里缓存数据
clientManager.syncClientConnected(clientSyncData.getClientId(), clientSyncData.getAttributes());
Client client = clientManager.getClient(clientSyncData.getClientId());
// 升级客户端服务信息
upgradeClient(client, clientSyncData);
}
这里的核心点就是upgradeClient方法,此方法的目的就是同步各个节点的数据
private void upgradeClient(Client client, ClientSyncData clientSyncData) {
List<String> namespaces = clientSyncData.getNamespaces();
List<String> groupNames = clientSyncData.getGroupNames();
List<String> serviceNames = clientSyncData.getServiceNames();
List<InstancePublishInfo> instances = clientSyncData.getInstancePublishInfos();
// 已同步的服务集合
Set<Service> syncedService = new HashSet<>();
for (int i = 0; i < namespaces.size(); i++) {
//从获取的数据中构建一个Service对象
Service service = Service.newService(namespaces.get(i), groupNames.get(i), serviceNames.get(i));
//单例模式
Service singleton = ServiceManager.getInstance().getSingleton(service);
// 标记此service已经被处理
syncedService.add(singleton);
// 获取当前实例
InstancePublishInfo instancePublishInfo = instances.get(i);
// 判断是否包含当前获取的实例
if (!instancePublishInfo.equals(client.getInstancePublishInfo(singleton))) {
// 不包含则添加
client.addServiceInstance(singleton, instancePublishInfo);
// 当前节点发布服务注册事件
NotifyCenter.publishEvent(
new ClientOperationEvent.ClientRegisterServiceEvent(singleton, client.getClientId()));
}
}
// 若当前client内部已发布的service不在本次同步的列表内,说明已经过时了,要删掉
for (Service each : client.getAllPublishedService()) {
if (!syncedService.contains(each)) {
client.removeServiceInstance(each);
NotifyCenter.publishEvent(
new ClientOperationEvent.ClientDeregisterServiceEvent(each, client.getClientId()));
}
}
}
到这里就完成了数据初始化同步,Nacos每台机器上都维护了当前所有注册上来的非持久化实例数据。
但是各位要注意的是,这只是一个新的Nacos节点上线的同步数据操作,那么如果某个注册的客户端节点改变那,那么所有的Nacos节点都是需要数据同步的
增量数据同步
数据完成初始化后,节点的数据发生变化后需要将增量数据同步到其他节点。
这里我们就要关注的重点为:
DistroClientDataProcessor类继承了SmartSubscriber,遵循Subscriber/Notify(订阅发布)模式,当有订阅的事件触发时会进行回调通知。
DistroClientDataProcessor订阅了ClientChangedEvent(服务改变)、ClientDisconnectEvent(服务断开)和ClientVerifyFailedEvent(验证失败)事件。
在subscribeTypes方法中体现
@Override
public List<Class<? extends Event>> subscribeTypes() {
List<Class<? extends Event>> result = new LinkedList<>();
result.add(ClientEvent.ClientChangedEvent.class);
result.add(ClientEvent.ClientDisconnectEvent.class);
result.add(ClientEvent.ClientVerifyFailedEvent.class);
return result;
}
这里我们重点关注ClientChangedEvent事件,那么当事件触发时,会调用onEvent方法
@Override
public void onEvent(Event event) {
if (EnvUtil.getStandaloneMode()) {
return;
}
if (!upgradeJudgement.isUseGrpcFeatures()) {
return;
}
if (event instanceof ClientEvent.ClientVerifyFailedEvent) {
syncToVerifyFailedServer((ClientEvent.ClientVerifyFailedEvent) event);
} else {
// 增量同步调用方法
syncToAllServer((ClientEvent) event);
}
}
查看syncToAllServer()方法
private void syncToAllServer(ClientEvent event) {
Client client = event.getClient();
// Only ephemeral data sync by Distro, persist client should sync by raft.
if (null == client || !client.isEphemeral() || !clientManager.isResponsibleClient(client)) {
return;
}
if (event instanceof ClientEvent.ClientDisconnectEvent) {
DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);
distroProtocol.sync(distroKey, DataOperation.DELETE);
// 节点变更事件,即增量数据同步方法
} else if (event instanceof ClientEvent.ClientChangedEvent) {
DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);
distroProtocol.sync(distroKey, DataOperation.CHANGE);
}
}
查看同步方法sync()向除本节点外的所有节点进行数据同步,对每个节点执行具体的同步逻辑syncToTarget方法。
//通过配置延迟开始同步
//@param distroKey 同步数据的分发密钥
//@param action 数据操作的动作
public void sync(DistroKey distroKey, DataOperation action) {
sync(distroKey, action, DistroConfig.getInstance().getSyncDelayMillis());
}
//开始将数据同步到所有远程服务器。
//@param distroKey 同步数据的分发密钥
//@param action 数据操作的动作
//@param delay 同步延迟时间
public void sync(DistroKey distroKey, DataOperation action, long delay) {
for (Member each : memberManager.allMembersWithoutSelf()) {
syncToTarget(distroKey, action, each.getAddress(), delay);
}
}
继续跟踪:syncToTarget(方法)
/**
* 开始同步到目标服务器。
*
* @param distroKey 同步数据的分发密钥
* @param action 数据操作的动作
* @param targetServer 目标服务器
* @param delay 同步延迟时间
*/
public void syncToTarget(DistroKey distroKey, DataOperation action, String targetServer, long delay) {
DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(),
targetServer);
DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay);
distroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask);
if (Loggers.DISTRO.isDebugEnabled()) {
Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, targetServer);
}
}
分析到这里就能清楚增量数据同步的信息了,那么其他服务在接受到数据增量更新以后还会调用upgradeClient(客户端升级方法)来进行数据同步这里和全量的过程就一致了。
nacos部署与整合springcloud alibaba
(注:演示demo 单机环境)
整合
java项目配置:
pom
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--实现配置的动态变更-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- nacos注册中心客户端 内置ribbon(starter里面其实已经包含了)-->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
yml
spring:
datasource:
# MySql
url: jdbc:mysql://192.168.31.165:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: file
cloud:
nacos:
discovery:
# server in local can only be 127.0.0.1 but not localhost or ip
server-addr: 127.0.0.1:8848
部署
单机模式支持mysql
在0.7版本之前,在单机模式时nacos使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力,具体的操作步骤:
1.安装数据库,版本要求:5.6.5+
2.初始化mysql数据库,数据库初始化文件:mysql-schema.sql
3.修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=nacos_devtest
db.password=youdontknow
nacos bin目录下 在dos界面输入命令(演示单机启动)
startup.cmd -m standalone
启动可能报错:
原因1: 数据库没配置好
原因2:Fail to init node, please see the logs to find the reason.
data目录是之前测试的,可能冲突了,测试环境下直接把data目录删除
openFeign
openFeign原理简述
feignClient: 同样集成了ribbon 跨服务去调用时,会去ribbon里面找。
(rpc协议 在spring中是基于http协议的 可以理解为远程调用 rpc流程:调用——》 序列化——》 通信 ——》 反序列化——》 被调用方),
且openfegin集成了Hystrix(豪猪) 可以用在调用进行降级处理
gateway
gateway原理简述
待补充
sentinel
sentinel原理简述
降级———— fallback = xxx.class 设置降级策略,例如订单服务、积分服务中,积分失败了 在降级中做补偿措施。
. 限流———— 限制访问 给出提示
. 熔断———— 例如配置当降级异常数量、比例达到某个程度时,直接进入熔断(表示直接进入降级方法 而无需进入业务方法)
. 三种常用限流算法:滑动窗口算法(常用) 令牌桶算法 漏桶算法
. 滑动窗口算法 :
seata
seata原理简述
通过undo日志来实现,例如插入一条数据,生成一条删除语句,如果有异常则执行删除语句。
seata首推AT模式: 主要起一个事务协调者的作用
. 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
. 二阶段:提交异步化,非常快速地完成。回滚通过一阶段的回滚日志进行反向补偿。
其它模式:TCC(主要是自定义内容),SAGA,XA
tips: 为什么首推AT模式?
XA 缺点:性能差(阻塞), 优点:强一致性
Saga 缺点:没有读取隔离 优点:性能好 (例如,客户可以看到正在创建的订单,但在下一秒,订单将因补偿交易而被删除)
TCC:有业务侵入,需要额外编写代码
AT: 综合以上
seata部署与整合springcloud alibaba (坑点多)
(注:demo测试部署,nacos版本为2.0.3 seata版本为1.6.1 版本有可能有较强关联性)
整合
【重要提醒(2023.12.15补充):本文 seata整合nacos+springcloud alibaba部分 因为坑较多,博主单独写了一篇博客
在博主的主页自行搜seata查看,本文其它部分 以后仍会更新,但关于seata整合部分 不再维护】
(有许多需要注意的点,在配置中有标注 【注:】,请认真阅读)
java项目配置:
pom文件
<!-- seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!--解决seata序列化问题(jackson版本冲突)-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-serializer-kryo</artifactId>
</dependency>
<!-- Seata 与 cloud alibaba整合 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
yml(与nacos整合):
# Seata 配置
seata:
application-id: seata-server
tx-service-group: default_tx_group #【注1】
# 是否启用数据源bean的自动代理
enable-auto-data-source-proxy: false
registry:
type: nacos
nacos:
# Nacos 服务地址
server-addr: http://localhost:8848
group: SEATA_GROUP
namespace: 891d7906-dd03-4b8c-9fe9-a1f0609b3189
application: seata-server # 【注2】
username: nacos
password: nacos
cluster: default
config:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
group: SEATA_GROUP
namespace: 891d7906-dd03-4b8c-9fe9-a1f0609b3189
service:
vgroup-mapping:
default_tx_group: default #【注3】 (key与【注1】的value保持一致)
disable-global-transaction: false
client:
rm:
# 是否上报成功状态
report-success-enable: true
# 重试次数
report-retry-count: 5
部署
seata服务配置:
- 创建seata\script\server\db sql脚本中的库表
- 修改seata\conf 的数据源, 修改type为nacos 配置nacos
server:
port: 7091
spring:
application:
name: seata-server # 我们java项目中的配置需要保持一致,上文配置中的 【注2】
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
# 修改为nacos
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
username: nacos
password: nacos
data-id: seataServer.properties #【注4】
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
# 修改为nacos
type: nacos
nacos:
application: seata-server # 【与spring.application.name保持一致】
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
username: nacos
password: nacos
store:
# support: file 、 db 、 redis
mode: db
db:
datasource: druid
dbType: mysql
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.31.165:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
user: root
password: root
server:
service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login
- 启动seata
- nacos配置
(如果你不想出现莫名其妙的bug 请务必不能忽视这一步)
第一个箭头与【注:4】保持一致,第二个箭头与上面的group名保持一致,
第三个箭头key的最后一个 . 后面的值 与【注3】的key保持一致,value与【注3】的value保持一致
tips: 如果你明明配置好了 也确定【注】里面的内容都对应上了 java项目启动 还是一直报错
io.seata.config.exception.ConfigNotFoundException: service.vgroupMapping. xxxx , 那应该是版本对应问题,博主一开始部署的seata 1.7 , 觉得应该也能用,结果一直报这错误;官方文档声明的是nacos 2.2.0对应seata1.6.1 ;本文整合实测为nacos2.0.3, seata 1.6.1可行。
此外, seata lib包下的jdbc包中有mysql5和mysql8的两个驱动包,在seata1.7中,可以不用删除,只要指定的driver class为com.mysql.cj.jdbc.Driver即可,但是在seata1.6中,如果我们数据库是mysql8,需要手动删除mysql5的驱动包!
如果很不幸,我们设备中一开始先部署过一遍seata1.7, 删了之后再部署1.6,且1.6修改了不同的数据库url,可能会发现启动seata时一直报错,那可能是因为nacos中的seata旧配置没有删除(修改)
最后 附上一个博主的pom.xml文件,里面验证了springcloud , springcloud alibaba,springboot,netflix-hystrix, sentinel ,nacos ,seata ,openfeign 的正确版本对应,采用的是2021.x中最新的版本,也是springboot 2.x中 最新的版本对应关系
(2022.x与springboot3对应,而springboot3又和高版本jdk关联,博主暂没研究)
root 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 子项目继承父项目pom文件时 父项目需要packing指定为pom
daclare the packing:pom when child extends this moudule-->
<packaging>pom</packaging>
<groupId>com.demo</groupId>
<artifactId>MySpringCloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>MySpringCloud</name>
<description>Demo project for Spring Boot</description>
<!-- 配置版本 version config 用于依赖管理版本号el表达式取值
act on dependencyManagement-dependencies-dependency-version by ${xxx.version}-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
<nacos.client.version>2.0.3</nacos.client.version>
<sentinel.version>1.8.6</sentinel.version>
<netflix-hystrix.version>2.2.10.RELEASE</netflix-hystrix.version>
<mybatis-plus.version>3.5.1</mybatis-plus.version>
<fastjson.version>1.2.76</fastjson.version>
<servlet.version>4.0.1</servlet.version>
<skywalking.version>8.5.0</skywalking.version>
<seata.version>1.6.1</seata.version>
<mysql.version>8.0.32</mysql.version>
<lombok.version>1.18.12</lombok.version>
</properties>
<!-- author by qkj-->
<!-- 声明依赖及其版本 但不引入, 子模块进行引入 (但子模块无需声明版本 否则会使用声明的版本号)-->
<!-- note: dependencyManagement only declare the dependency and version but not import,
child moudule should import again without declare the version when need dependency -->
<!-- 注意:dependencyManagement与dependencies区别 如果在父模块使用dependencies,子模块都继承,不管用不用得上-->
<!-- note: difference with dependencies:child moudule extends dependencies unconditional -->
<dependencyManagement>
<dependencies>
<!-- diff:cloud-dependencies and cloud-starter?
dependencies only declare the version and download jar with starter -->
<!-- cloud-dependencies and cloud-starter 的区别 一般带 dependencies 的 artifactId 都是声明版本
点进去看就能发现 里面是声明的dependencyManagement, 而starter则是我们要下载具体的xxx-starter.jar包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<!-- 需要加上type 否则在阿里云可能下载不了
declare type:pom or maybe download fall in alibaba maven
type pom: 已打包为pom-->
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- alibaba与cloud整合 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<!-- scope:import can only declare in dependencyManagement; resolve the problem of single extends -->
<!--<scope>import</scope> 只会在dependencyManagement中出现-->
<!-- 解决单继承问题 换句话说 加上import 可以使用spring-cloud-alibaba-dependencies面的dependencyManagement -->
<!-- such as spring-cloud-alibaba-dependencies already include spring-cloud-starter-alibaba-nacos-discovery
child module can declare spring-cloud-starter-alibaba-nacos-discovery without version-->
<scope>import</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>${lombok.version}</version>
</dependency>
<!--fastjson 注意过低版本会有漏洞(建议关注新版) low version such as 1.2.71 before with remote bug-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- nacos注册中心客户端 内置ribbon-->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos.client.version}</version>
</dependency>
<!-- sentinel-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${sentinel.version}</version>
</dependency>
<!-- sentinel控制台-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>
<!-- feign集成hystrix豪猪降级-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>${netflix-hystrix.version}</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
</dependency>
<!-- skywalking工具类:自定义链路追踪等-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<!-- 与sw版本对应 -->
<version>${skywalking.version}</version>
</dependency>
<!-- skywalking和logback整合-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>${skywalking.version}</version>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- mybatis plus 动态数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<!--解决seata序列化问题(jackson版本冲突问题)-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-serializer-kryo</artifactId>
<version>${seata.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 可以理解为声明子类(子模块) -->
<modules>
<module>common</module>
<module>user</module>
<module>file</module>
</modules>
<!--注意 子模块有工具类(没有main方法) 不能在父模块这里声明maven插件 子模块继承 会导致打包报找不到main方法(unalbe to find main class) -->
</project>
子模块 pom文件 (以file模块为例)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.demo</groupId>
<artifactId>MySpringCloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 因为file是我们的业务模块 通俗点说是一个小项目 到时候要打包java -jar运行的 所以打包方式为jar(可以不写 默认为jar) 而不是pom-->
<packaging>jar</packaging>
<groupId>com.qkj</groupId>
<artifactId>file</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>file</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<!-- 父模块该依赖为pom 子模块该依赖也尽量定义为pom
父模块该依赖未使用<scope>import</scope>:当前模块如果还有子模块必须声明为pom 没有则随意 -->
<!-- 但是父模块该依赖使用了import 可以理解为继承了该依赖内置的dependencyManagement 必须使用pom 且声明版本号-->
<!-- must declare type:pom when child module exist or parent module dependency declare scope:import;
version should be declared if parent module dependency declare scope:import,else type could be default or other-->
<!-- so shoule better declare the type: pom -->
<version>${spring-cloud.version}</version>
<type>pom</type>
</dependency>
<!-- alibaba与cloud整合 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--fastjson 注意过低版本会有漏洞 low version such as 1.2.71 before with remote bug-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- nacos注册中心客户端 内置ribbon-->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<!-- sentinel-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<!-- sentinel控制台-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<!--feign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 载入open feign 要用到-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!-- 豪猪熔断-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!--Nacos Discovery-->
<!-- 在spring-cloud-alibaba-dependencies里面声明了版本 又因为设置了scope:import 所以在我们自己写的父模块pom文件中 不能显式看到-->
<!-- declare in spring-cloud-alibaba-dependencies-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--实现配置的动态变更-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- skywalking工具类:自定义链路追踪等-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
</dependency>
<!-- skywalking和logback整合 print traceId in our logs-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
</dependency>
<dependency>
<groupId>com.qkj</groupId>
<artifactId>user-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- scope :默认provided 没有传递性 ,compile具有传递性 换句话说就是会把jar包打进来,而runtime表示不作用在编译时 如JDBC驱动-->
<!-- compile can pass on but provided(default) can't , runtime means not compile-->
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>tomcat-jdbc</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!--解决seata序列化问题(jackson版本冲突)-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-serializer-kryo</artifactId>
</dependency>
<!-- validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Seata 与 cloud alibaba整合 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<version>2.3.4.RELEASE</version>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>false</filtering>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
rocketmq
rocketmq原理简述
线程池、队列组成,队列在rocketmq中 其实是用copyonWriteList完成的
待补充
其它常见微服务问题
降级,限流,熔断
降级:当主功能出现故障时,降级实现补偿机制。
博主个人理解 一个解耦的try catch ,
在@FeignClient中 指定fallbackFactory 类,fallbackFactory指定的类里面捕获住异常,并实现降级策略)
限流:在高并发场景下,为了保护系统免受过载的影响,可以对请求的流量进行限制,确保系统的稳定性。
限流机制可以通过设置最大并发数、请求排队、请求频率控制等方式来实现
熔断:当某个服务的错误率超过一定阈值或者响应时间过长时,熔断机制可以自动切断对该服务的访问,避免连锁故障,同时快速返回错误响应。
缓存穿透,缓存击穿,缓存雪崩
缓存穿透:缓存穿透是指查询一个缓存中和数据库中都不存在的数据,导致每次查询这条数据都会透过缓存,直接查库,最后返回空。当用户使用这条不存在的数据疯狂发起查询请求的时候,对数据库造成的压力就非常大,甚至可能直接挂掉。
(简而言之就是 查询的结果一直为空)
解决方案:1. 缓存空数据 2. 使用布隆过滤器
缓存击穿:缓存击穿是指当缓存中某个热点数据过期了(比如微博热搜),在该热点数据重新载入缓存之前,有大量的查询请求穿过缓存,直接查询数据库。这种情况会导致数据库压力瞬间骤增,造成大量请求阻塞,甚至直接挂掉。
解决缓存击穿的方法也有两种,第一种是设置key永不过期;第二种是使用分布式锁,保证同一时刻只能有一个查询请求重新加载热点数据到缓存中,这样,其他的线程只需等待该线程运行完毕,即可重新从Redis中获取数据。
缓存雪崩:缓存雪崩是指当缓存中有大量的key在同一时刻过期(还是可以参考微博),或者Redis直接宕机了,导致大量的查询请求全部到达数据库,造成数据库查询压力骤增,甚至直接挂掉。
解决方案:1.(针对key过期) 错开过期时间 2.(针对redis宕机) 主从架构