【springcloud】springcloud alibaba生态中间件原理简述

注:文章为博主自用版

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服务配置:

  1. 创建seata\script\server\db sql脚本中的库表
  2. 修改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
  1. 启动seata
  2. 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宕机) 主从架构

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟秋与你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值