1 Star 14 Fork 9

hehui / SpringCloudAlibabaTutorial

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 48.99 KB
一键复制 编辑 原始数据 按行查看 历史
ylimhhmily 提交于 2023-05-20 17:57 . modify README.md

SpringCloudAlibaba 学习课程

I、大纲

[TOC]

II、简介

一站式速学 SpringCloudAlibaba 体系自学课程,希望帮助更多想学的童鞋快速掌握 SCA 体系。

III、各微服务占用端口情况

模块 微服务工程名 端口 功能描述
sc01-eureka eureka-server 8761 Eureka 服务端
eureka-provider 9000 Eureka 提供方
eureka-consumer 9001 Eureka 消费方
sc02-nacos nacos-server 8848
nacos-provider 9010 Nacos 提供方
nacos-consumer 9011 Nacos 消费方
sc03-cluster nacos-cluster-provider 9020
9021
Nacos 提供方
nacos-cluster-consumer 9022
9023
Nacos 消费方
nginx 8888 Nginx 反向代理
sca01-nacos-regdis nacos-registry-provider 9030 Nacos 提供方
nacos-discovery-consumer 9031 Nacos 消费方
sca02-nacos-config nacos-dynamic-port 9032 Nacos 动态配置端口
nacos-dynamic-env 9033 Nacos 动态环境变量
sca03-openfeign openfeign-provider 9040
9041
9042
Openfeign 提供方
ribbon-consumer 9043 Ribbon 消费方
openfeign-consumer 9044 Openfeign 消费方
degrade-consumer 9045 降级消费方
sca04-gateway gateway-server 9050 网关服务端
gateway-provider 9051 网关提供方
gateway-consumer 9052 网关消费方
gateway-filter-server 9053 网关过滤器
sca05-sentinel sentinel-manual-flow-demo - 手动限流
sentinel-manual-degrade-demo - 手动降级
sentinel-dashboard 9999 限流控制台
sentinel-auto-flowdeg-consumer 9061 自动限流/降级消费方
sentinel-flow-degrade-provider 9062 限流/降级提供方
sentinel-online-flowdeg-consumer 9063 在线修改限流降级消费方
sentinel-nacos-persist-consumer 9064 限流规则持久化消费方
sca06-dubbo zookeeper - Zookeeper 注册中心
dubbo-facade - dubbo 接口模块
dubbo-discovery-provider 9070
29070
dubbo 提供方
dubbo-discovery-consumer 9075
29075
dubbo 消费方
dubbo-check-consumer 9076
29076
dubbo 依赖检查消费方
dubbo-loadbalance-consumer 9076
29076

29071
29072
dubbo 负载均衡消费方
dubbo-broadcast-consumer 9077
29077
dubbo 广播调用消费方
dubbo-cache-consumer 9078
29078
dubbo 缓存操作消费方
dubbo-p2p-consumer 9079
29079
dubbo 点对点消费方
dubbo-generic-consumer 9080
29080
dubbo 泛化调用消费方
dubbo-jdk-spi - dubbo 和 jdk spi
dubbo-wrapper - dubbo wrapper 机制

一、体系介绍

1.1 组件介绍

1、解决了什么问题?

2、如何完整的阐述涉及到的组件?

1.1.1 Nacos Registry

1.1.2 Nacos Config

1.1.3 OpenFeign

1.1.4 Gateway

1.1.5 Dubbo

1.1.6 Sentinel

1.1.7 Seata

image-20230504160927741

1.2 SpringCloud 最佳实践

1、什么是最佳实践?

2、实践的又是什么?

1.2.1 Provider + Consumer + Eureka 服务搭建

  • 相关网址:

  • Eureka Server 启动

    org.springframework.cloud spring-cloud-starter-netflix-eureka-server ```
    • 添加应用启动类、添加Eureka服务端注解、设置Tomcat绑定端口、设置Eureka地址

    • 设置 Tomcat 启动端口为 6000(为什么不行?

  • Eureka Provider 工程

    • 引入包

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
    • 添加应用启动类、添加Eureka客户端注解、设置Tomcat绑定端口、设置Eureka地址

  • Eureka Consumer 工程

    • 引入包

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
    • 添加应用启动类、添加Eureka客户端注解、设置Tomcat绑定端口、设置Eureka地址

  • 怎么进行负载均衡呢?

1.2.2 Provider + Consumer + Nacos 服务搭建

1.2.3 Consumer + Provider + nginx 集群搭建

  • Cluster Provider:多台节点
  • Cluster Consumer:多台节点
  • Nginx
image-20230504192811712

二、Nacos 注册与发现

2.1 Nacos 工程案例

2.1.1 Nacos 服务注册案例搭建

image-20230504234730377

2.1.2 Nacos 服务订阅案例搭建

  • nacos-discovery-consumer 关键 pom 引用

    • <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
  • 发起远程调用

2.2 Nacos 注册流程源码分析

2.2.1 Nacos Client 发起注册流程

  • 怎么快速熟悉注册流程?

    • 可以去看百度的一些介绍,通过别人的一些分析源码的思路来学习
    • 直接去官网去搜索一些官方的资料,来学习一些流程
    • 自己去琢磨,去研究,如果没有一套很好的方法体系的话,那么研究起来就会非常痛苦
  • 异常堆栈能给什么启示?

  • 注册流程:1.x 版本、2.x 版本

    • AbstractAutoServiceRegistration#onApplicationEvent
    • NacosServiceRegistry#register
    • NamingClientProxyDelegate#register
      • 1.x 为什么先注册心跳?
      • 2.x 基于长连接?

2.2.2 Nacos Server 处理注册流程

  • V1:InstanceController
    • Service#serviceMap
    • Service#onDelete
  • V2:InstanceRequestHandler
    • serviceManager
      • singletonRepository
      • namespaceSingletonMaps
    • clientManager
      • publishers
    • Client
      • addServiceInstance
      • removeServiceInstance
    • 事件
      • ClientEvent.ClientChangedEvent
      • ClientOperationEvent.ClientRegisterServiceEvent
      • MetadataEvent.InstanceMetadataEvent

2.3 Nacos 订阅流程源码分析

2.3.1 Nacos Client 发起订阅流程

  • 怎么理解订阅关键环节?

  • 如何巧妙创造订阅异常?

    • 第一步:启动 Nacos Server
    • 第二步:启动 Nacos Provider
    • 第三步:启动 Nacos Consumer
    • 第四步:关闭 Nacos Server
    • 第五步:向 Nacos Consumer 发起 HTTP 请求调用
  • 订阅流程

    • DynamicServerListLoadBalancer#updateListOfServers
    • NacosServerList#getServers
    • NamingClientProxyDelegate#subscribe
      • scheduleUpdateIfAbsent
      • 关注1.0与2.0分支分叉逻辑

2.3.2 Nacos 心跳与剔除机制

  • 心跳和剔除分别解决了什么问题?

    • 心跳:Client 与 Server 之间的一个连接情况,维持Client与Server之间的联系,防止许久不联系的话,担心被Server删除干掉
    • 剔除:解决的是,Server 这边很久都没感知到 Client 是否活跃的情况,因此需要将这些许久非活跃的数据删除掉,侧重于Server端的逻辑
  • 强调这里讨论 1.0 版本非长连接的机制

  • 心跳(客户端)

    • 回顾注册环节提到的心跳代码片段
    • NamingClientProxyDelegate#register
    • BeatTask#run
  • 剔除(服务端)

    • 思考应该在哪里开启剔除监测?
    • Service#init
    • ClientBeatCheckTask#run
    • deleteIp
  • UDP 推送

    • getPushService().serviceChanged(service)

三、Nacos 配置中心

3.1 Nacos Config 工程案例

3.1.1 Nacos 动态更新端口案例搭建

3.1.2 Nacos 动态更新配置案例搭建

  • 重点关注放在 bootstrap.yml

    • spring:
        cloud:
          nacos:
            config:
              extension-configs[0]:
                dataId: nacos-dynamic-user.properties
                refresh: true
              extension-configs[1]:
                dataId: nacos-dynamic-common.properties
                refresh: true
              extension-configs[2]:
                dataId: nacos-dynamic-global.properties
                refresh: true
  • image-20230507124659138

3.2 Nacos 配置中心源码分析

3.2.1 Nacos 客户端配置更新机制

  • 如何模拟实时拉取效果?

  • 根据配置返回内容反推源头

  • 源码关键节点

    • NacosConfigService
    • ClientWorker 构造方法
    • ConfigRpcTransportClient
    • ClientWorker#executeConfigListen
  • 堆栈流程

    • image-20230507151436544

3.2.2 Nacos 服务端配置更新机制

  • 找准核心操作源头 URL
  • 精准定位到 ConfigController
  • 源码关键节点
    • ConfigController#publishConfig
    • 发布 ConfigDataChangeEvent 事件
    • 发布 LocalDataChangeEvent 事件
    • LongPollingService 注册监听的回调
    • DataChangeTask#run

3.3 Nacos 集群同步源码分析

3.3.1 CP + AP 模式

  • C(Consistency)A(Avaliability)P(Partition Tolerance)是什么?

    • image-20230507201030723
    • 如果想解决分区容忍的现象:意味着,需要将 dataA 进行复制给到其他节点
    • 如果想解决复制的问题,但是又会引发复制过程带来的可用性问题
    • 既然可用性也受损的话,那么是不是可以直接去掉分区概念
  • 为什么一定得保证 P ?

    • 既然去掉分区,那就意味着没有多节点的概念,从而意味着是单节点
    • 单节点的好处就是,不用复制数据到其他节点,因为自己就是唯一,然后一致性得到了保证
    • 但是如果,单节点发生的宕机,那是不是意味着可用性又受损了
    • 如果想拥有可用性,那么又变相的反推需要多节点,那是不是又回到了分区的概念
    • 因此呢,我们需要保证P,也就是需要对分区进行一定的容忍
    • 而这个所谓的一定的容忍,可以是一段时间之内,也可以是一定数量之间,但是最终可以通过设计方案达到数据的最终一致
  • CP 应用案例(价格)

    • 一致性(强) + 分区容忍新
    • image-20230507200408490
  • AP 应用案例(点赞)

    • 可用性 + 分区容忍性
    • image-20230507201231395
  • Nacos、Eureka、Zookeeper 典型模式

    • Nacos:AP + CP
    • Eureka:AP
    • Zookeeper:CP

3.3.2 Distro 一致性协议

  • 新节点同步机制:com.alibaba.nacos.core.distributed.distro.DistroProtocol#startLoadTask

  • 平等机制:com.alibaba.nacos.naming.web.DistroFilter

  • 路由转发机制:com.alibaba.nacos.naming.web.DistroFilter

  • 异步复制机制:com.alibaba.nacos.core.distributed.distro.DistroProtocol#sync(DistroKey, DataOperation, long)

  • 健康检查机制:com.alibaba.nacos.core.distributed.distro.DistroProtocol#startVerifyTask

  • 本地读机制:com.alibaba.nacos.naming.controllers.InstanceController#list

  • 最最关键的环节:

    • 每个 Nacos Server 都会处理一部分应该由自己负责的数据

    • public boolean responsible(String responsibleTag) {
          final List<String> servers = healthyList;
          
          if (!switchDomain.isDistroEnabled() || EnvUtil.getStandaloneMode()) {
              return true;
          }
          
          if (CollectionUtils.isEmpty(servers)) {
              // means distro config is not ready yet
              return false;
          }
          // localAddress = 192.168.100.183:8848
          String localAddress = EnvUtil.getLocalAddress();
          // 获取起始索引值
          int index = servers.indexOf(localAddress);
          // 获取结束索引值
          int lastIndex = servers.lastIndexOf(localAddress);
          if (lastIndex < 0 || index < 0) {
              return true;
          }
      
          // distroHash(responsibleTag) 计算出来的值与 servers.size() 求余后的结果,
          // 若在 [index, lastIndex] 中则为当前节点负责的数据
          int target = distroHash(responsibleTag) % servers.size();
          return target >= index && target <= lastIndex;
      }
    • 每个 Nacos Server 都会存储所有 Nacos Client 端的所有数据

3.3.3 Raft 一致性协议

  • image-20230507223521621
  • V1 版本的选举与同步
    • 选举
      • RaftCore
      • 角色 LEADER、FOLLOWER、CANDIDATE
      • MasterElection
    • 同步
      • HeartBeat
  • V2 版本的选举与同步
    • 选举
      • com.alibaba.nacos.naming.consistency.DelegateConsistencyServiceImpl
      • com.alibaba.nacos.naming.consistency.persistent.PersistentConsistencyServiceDelegateImpl#PersistentConsistencyServiceDelegateImpl
      • com.alibaba.nacos.naming.consistency.persistent.impl.PersistentServiceProcessor#afterConstruct
      • com.alibaba.nacos.core.distributed.raft.JRaftProtocol#addRequestProcessors
      • com.alibaba.nacos.core.distributed.raft.JRaftServer#createMultiRaftGroup
      • com.alipay.sofa.jraft.RaftGroupService#start(boolean)
      • com.alipay.sofa.jraft.RaftServiceFactory#createAndInitRaftNode
      • com.alipay.sofa.jraft.core.NodeImpl#init
      • com.alipay.sofa.jraft.core.NodeImpl#electSelf
      • com.alipay.sofa.jraft.core.NodeImpl#becomeLeader
    • 同步
      • com.alipay.sofa.jraft.ReplicatorGroup#addReplicator(com.alipay.sofa.jraft.entity.PeerId)

四、OpenFeign 负载均衡

4.1 OpenFeign 工程案例

4.1.1 Ribbon 工程案例搭建

  • Ribbon 是什么?

    • Netflix 的开源项目,主要来提供关于客户端的负载均衡的能力。
  • Ribbon

    • Feign:Netflix,SpringCloud 的第一代 LB(负载均衡)客户端工具包
    • OpenFeign:SpringCloud 自研,SpringCloud 的第二代LB(负载均衡)客户端工具包,扩展支持了 @RequestMapping、@GetMapping 等之类的注解的能力
  • image-20230508224755712
  • openfeign-provider 9040

  • ribbon-consumer 9043

4.1.2 OpenFeign 工程案例搭建

4.1.3 负载均衡与降级案例搭建

4.2 OpenFeign 源码分析

4.2.1 @FeignClient 注解扫描机制

  • 核心流程讲解

    • image-20230510222500806
  • 源码流程跟踪

    • 扫描流程:org.springframework.cloud.openfeign.EnableFeignClients

      • org.springframework.cloud.openfeign.FeignClientsRegistrar

        • org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions

          • org.springframework.cloud.openfeign.FeignClientsRegistrar#registerDefaultConfiguration

          • org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients

            • org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
            • protected boolean isCandidateComponent(MetadataReader metadataReader)
            • protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition)
            • scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
          • org.springframework.cloud.openfeign.FeignClientsRegistrar#registerClientConfiguration

            • registry.registerBeanDefinition(
                    name + "." + FeignClientSpecification.class.getSimpleName(),
                    builder.getBeanDefinition());
          • org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient

            • org.springframework.cloud.openfeign.FeignClientFactoryBean + factoryBean.getObject()

            • org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget

            • org.springframework.cloud.openfeign.HystrixTargeter#target

            • feign.ReflectiveFeign#newInstance

            •   public <T> T newInstance(Target<T> target) {
                  Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
                  Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
                  List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
                
                  for (Method method : target.type().getMethods()) {
                    if (method.getDeclaringClass() == Object.class) {
                      continue;
                    } else if (Util.isDefault(method)) {
                      DefaultMethodHandler handler = new DefaultMethodHandler(method);
                      defaultMethodHandlers.add(handler);
                      methodToHandler.put(method, handler);
                    } else {
                      methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
                    }
                  }
                  InvocationHandler handler = factory.create(target, methodToHandler);
                  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                      new Class<?>[] {target.type()}, handler);
                
                  for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
                    defaultMethodHandler.bindTo(proxy);
                  }
                  return proxy;
                }
    • 调用流程:feign.ReflectiveFeign.FeignInvocationHandler#invoke

      • public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          if ("equals".equals(method.getName())) {
            try {
              Object otherHandler =
                  args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
              return equals(otherHandler);
            } catch (IllegalArgumentException e) {
              return false;
            }
          } else if ("hashCode".equals(method.getName())) {
            return hashCode();
          } else if ("toString".equals(method.getName())) {
            return toString();
          }
        
          return dispatch.get(method).invoke(args);
        }
      • feign.SynchronousMethodHandler#invoke

      • com.alibaba.nacos.client.naming.NacosNamingService#selectInstances

        • 这就是 OpenFeign 首次发起远程调用时,最最最慢的核心原因

4.2.2 Feign 的上下文隔离机制

  • 入口:org.springframework.cloud.openfeign.FeignClientFactoryBean#feign

    • image-20230510230417128
  • getTarget() 所在对象的参数信息来源?

  • 源码分析:

    • org.springframework.cloud.openfeign.FeignClientFactoryBean#get

    • org.springframework.cloud.context.named.NamedContextFactory#getInstance

    • org.springframework.cloud.context.named.NamedContextFactory#getContext

    • org.springframework.cloud.context.named.NamedContextFactory#createContext

      • 含有 OpenFeign 的项目,就是比没有 OpenFeign 的项目会慢一点点的核心原因

      • protected AnnotationConfigApplicationContext createContext(String name) {
           AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
           if (this.configurations.containsKey(name)) {
              for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                 context.register(configuration);
              }
           }
           for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
              if (entry.getKey().startsWith("default.")) {
                 for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                 }
              }
           }
           context.register(PropertyPlaceholderAutoConfiguration.class,
                 this.defaultConfigType);
           context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                 this.propertySourceName,
                 Collections.<String, Object>singletonMap(this.propertyName, name)));
           if (this.parent != null) {
              // Uses Environment from parent as well as beans
              context.setParent(this.parent);
              // jdk11 issue
              // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
              context.setClassLoader(this.parent.getClassLoader());
           }
           context.setDisplayName(generateDisplayName(name));
           
           // 重点在这里,创建了一个迷你的上下文对象,并且刷新了迷你上下文对象进行实例化操作
           context.refresh();
           return context;
        }

五、Gateway 网关路由

5.1 Gateway 路由案例搭建

5.2 自定义拦截过滤

  • 使用拦截
  • 自定义拦截
    • 局部拦截
      • 继承类:AbstractGatewayFilterFactory
      • CostGatewayFilterFactory
      • Cost=ON/OFF
      • 借鉴:org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory
    • 全局拦截
      • 继承类:GlobalFilter
      • LoggerGlobalFilter
      • LOG=ON
      • 借鉴:还是 org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory

5.3 转发重定向机制

  • 思考寻找调试突破口

    • 制造异常
    • 在必经之路的过滤器中进行断点
  • 分析异常日志可以抓取哪些重要信息

    • image-20230513172610385
  • 在 ExceptionHandlingWebHandler 中添加断点

  • 一边调试一边分析堆栈日志

    • 找到 Gateway 框架体系处理的入口:org.springframework.http.server.reactive.ReactorHttpHandlerAdapter
    • 构建网关上下文:org.springframework.web.server.adapter.HttpWebHandlerAdapter
    • 遍历 Mapping 获取真实处理请求的 Handler:org.springframework.web.reactive.DispatcherHandler
    • 构建过滤器链:org.springframework.cloud.gateway.handler.FilteringWebHandler
    • 过滤器必经之路:org.springframework.cloud.gateway.handler.FilteringWebHandler.DefaultGatewayFilterChain
      • 负载均衡过滤器:org.springframework.cloud.gateway.filter.LoadBalancerClientFilter
      • 利用 Netty 发送网络请求过滤器:org.springframework.cloud.gateway.filter.NettyRoutingFilter
  • 官网调用流程抽象总结:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/images/spring_cloud_gateway_diagram.png

六、Sentinel 限流降级

6.1 Sentinel 工程案例

6.1.1 Sentinel 手动设置限流/降级案例搭建

  • 限流
    • 官方文档:https://sentinelguard.io/zh-cn/docs/flow-control.html
    • 并发数:触发阈值直接抛弃
    • QPS
      • 直接拒绝:触发阈值直接抛弃
      • 冷启动:在一段时间内针对突发流量缓慢增长处理数量
      • 匀速器:请求以均匀的速度通过;
  • 降级
    • 官方文档:https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
    • RT:统计时长内,请求总数大于预设请求数,且慢请求个数大于预设比例,则熔断拒绝一段时间
    • 异常比例:统计时长内,请求总数大于预设请求数,且异常比例大于预比例,则熔断拒绝一段时间
    • 异常数:统计时长内,请求总数大于预设请求数,且异常数大于预设数值,则熔断拒绝一段时间

6.1.2 Sentinel 自动获取限流/降级案例搭建

6.1.3 Sentinel 接入 Nacos 案例搭建

  • 官方文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

  • image-20230514172503486
  • 动态数据源支持

    • spring.cloud.sentinel.datasource.flow.nacos.server-addr=localhost:8848
      spring.cloud.sentinel.datasource.flow.nacos.data-id=sentinel-online-flowdeg-consumer-sentinel.properties
      spring.cloud.sentinel.datasource.flow.nacos.group-id=DEFAULT_GROUP
      spring.cloud.sentinel.datasource.flow.nacos.rule-type=flow
  • 配置文件内容怎么写呢?

    • Sentinel 控制台保存一下
    • F12
    • CV大法
  • 利弊

    • 利:简单方便,仅需配置中心即可完美动态更新限流降级配置
    • 弊:不够直观,不知道怎么设置限流降级,对开发人员要求高

6.2 Sentinel 持久化操作

6.2.1 探索 Sentinel 规则持久化机制

image-20230514172401543
  • 怎么寻找持久化突破口?

    • spring:
        cloud:
          sentinel:
            transport:
              port: 8719
              dashboard: localhost:9999
  • 尝试断点,dashboard 都拿来在哪些地方用?

    • com.alibaba.cloud.sentinel.SentinelProperties.Transport#getDashboard

    • if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER))
          && StringUtils.hasText(properties.getTransport().getDashboard())) {
          System.setProperty(TransportConfig.CONSOLE_SERVER,
                             properties.getTransport().getDashboard());
      }
    • com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration#init

      • // earlier initialize
        if (properties.isEager()) {
            InitExecutor.doInit();
        }
  • 断点 + 代码分析

    • com.alibaba.csp.sentinel.init.InitExecutor#doInit 添加断点后,发现没进来,为什么?

    • 触发一次 Http 请求调用后,发现进入了 com.alibaba.csp.sentinel.init.InitExecutor#doInit 断点

    • 循环逻辑代码块分析

      • for (InitFunc initFunc : loader) {
        	RecordLog.info("[InitExecutor] Found init func: " + initFunc.getClass().getCanonicalName());
        	insertSorted(initList, initFunc);
        }
        for (OrderWrapper w : initList) {
            w.func.init();
            RecordLog.info(String.format("[InitExecutor] Executing %s with order %d",
                                         w.func.getClass().getCanonicalName(), w.order));
        }
    • 寻找到了 com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter#start 的相关收发数据逻辑

      • 关键代码 1:开启服务端层面的线程

        • socketReference = serverSocket;
          executor.submit(new ServerThread(serverSocket));
          success = true;
          port = serverSocket.getLocalPort();
      • 关键代码 2:从 ServerSocket 的 accept 方法收数据

        • socket = this.serverSocket.accept();
          setSocketSoTimeout(socket);
          HttpEventTask eventTask = new HttpEventTask(socket);
          bizExecutor.submit(eventTask);
    • 数据源处理核心代码

      • if (FLOW_RULE_TYPE.equalsIgnoreCase(type)) {
            List<FlowRule> flowRules = JSONArray.parseArray(data, FlowRule.class);
            FlowRuleManager.loadRules(flowRules);
            if (!writeToDataSource(getFlowDataSource(), flowRules)) {
                result = WRITE_DS_FAILURE_MSG;
            }
            return CommandResponse.ofSuccess(result);
        }

6.2.2 联动 Sentinel 与 Nacos 的持久化实现

image-20230514172401543
  • 分析如何扩展数据源

    • if (FLOW_RULE_TYPE.equalsIgnoreCase(type)) {
          List<FlowRule> flowRules = JSONArray.parseArray(data, FlowRule.class);
          FlowRuleManager.loadRules(flowRules);
          if (!writeToDataSource(getFlowDataSource(), flowRules)) {
              result = WRITE_DS_FAILURE_MSG;
          }
          return CommandResponse.ofSuccess(result);
      }
  • 为什么 getFlowDataSource() 是 null 值?

  • 如何给 getFlowDataSource() 赋值呢?

    • 充分利用已有方法:com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry#registerFlowDataSource
    • 充分利用 Spring 的特性
      • org.springframework.beans.factory.InitializingBean
      • @javax.annotation.PostConstruct
  • 接着又如何得到 com.alibaba.csp.sentinel.datasource.WritableDataSource 实现类呢?

    • 创建 com.alibaba.nacos.api.config.ConfigService 对象
    • 调用 com.alibaba.nacos.api.config.ConfigService 进行发布数据

6.3 核心 SphU.entry 源码分析

6.3.1 ProcessorSlotChain 过滤器链流程

  • 如何找到限流降级的入口?

  • 曾经无数次练习过的 Demo 入手:sca05-sentinel 内部的各个模块

  • 找到 Sphu.entry 内的核心必经环节:CtSph#entryWithPriority

    • 创建上下文:com.alibaba.csp.sentinel.context.ContextUtil#trueEnter

    • 构建过滤器链:com.alibaba.csp.sentinel.CtSph#lookProcessChain

      • com.alibaba.csp.sentinel.slotchain.SlotChainProvider#newSlotChain

      • com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder#build

      • // NodeSelectorSlot -> ClusterBuilderSlot -> LogSlot -> StatisticSlot ->
        // AuthoritySlot -> SystemSlot -> FlowSlot -> DefaultCircuitBreakerSlot -> DegradeSlot
      • 从官网了解一些限流降级模块的作用:https://sentinelguard.io/zh-cn/docs/basic-implementation.html

    • 过滤器链触发调用:

      • Entry e = new CtEntry(resourceWrapper, chain, context);
        try {
            chain.entry(context, resourceWrapper, null, count, prioritized, args);
        } catch (BlockException e1) {
            e.exit(count, args);
            throw e1;
        } catch (Throwable e1) {
            // This should not happen, unless there are errors existing in Sentinel internal.
            RecordLog.info("Sentinel unexpected exception", e1);
        }

6.3.2 StatisticSlot 核心处理逻辑分水岭

  • 分水岭的由来

    • @Override
      public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                        boolean prioritized, Object... args) throws Throwable {
          try {
              // 先触发后续的过滤器
              fireEntry(context, resourceWrapper, node, count, prioritized, args);
      
              // 然后再做请求通过数累加、线程数累加等等
              node.increaseThreadNum();
              node.addPassRequest(count);
      
              // 省略部分代码
          } catch (PriorityWaitException ex) {
              // 省略部分代码
          } catch (BlockException e) {
              // 省略部分代码
              throw e;
          } catch (Throwable e) {
              // 省略部分代码
              throw e;
          }
      }
  • 核心校验逻辑

    • com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot

      • void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
            Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();
            if (authorityRules == null) {
                return;
            }
            Set<AuthorityRule> rules = authorityRules.get(resource.getName());
            if (rules == null) {
                return;
            }
            for (AuthorityRule rule : rules) {
                if (!AuthorityRuleChecker.passCheck(rule, context)) {
                    // 校验不通过,抛出异常,并且将【来源 Origin】、【规则 rule】抛出去,便于识别哪条规则不通过了
                    throw new AuthorityException(context.getOrigin(), rule);
                }
            }
        }
    • com.alibaba.csp.sentinel.slots.system.SystemSlot

      • public static void checkSystem(ResourceWrapper resourceWrapper, int count) throws BlockException {
            // NPE 判断,如果为空,不做任何处理
            if (resourceWrapper == null) {
                return;
            }
            // Ensure the checking switch is on.
            // 没有配置规则的话,那么就是处于 false 状态,并且直接返回,不做任何校验处理
            if (!checkSystemStatus.get()) {
                return;
            }
        
            // for inbound traffic only
            // 并且该系统监控指标,只允许 IN 方向的控制,对于 OUT 方向的则不做任何校验处理
            if (resourceWrapper.getEntryType() != EntryType.IN) {
                return;
            }
        
            // 接下来就是一系列的指标判断,纯粹的比大小判断
            // total qps
            double currentQps = Constants.ENTRY_NODE.passQps();
            if (currentQps + count > qps) {
                throw new SystemBlockException(resourceWrapper.getName(), "qps");
            }
            // total thread
            int currentThread = Constants.ENTRY_NODE.curThreadNum();
            if (currentThread > maxThread) {
                throw new SystemBlockException(resourceWrapper.getName(), "thread");
            }
            double rt = Constants.ENTRY_NODE.avgRt();
            if (rt > maxRt) {
                throw new SystemBlockException(resourceWrapper.getName(), "rt");
            }
            // load. BBR algorithm.
            if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
                if (!checkBbr(currentThread)) {
                    throw new SystemBlockException(resourceWrapper.getName(), "load");
                }
            }
            // cpu usage
            if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
                throw new SystemBlockException(resourceWrapper.getName(), "cpu");
            }
        }
    • com.alibaba.csp.sentinel.slots.block.flow.FlowSlot

      • public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, 
                              ResourceWrapper resource,
                              Context context, 
                              DefaultNode node, 
                              int count, boolean prioritized) throws BlockException {
            if (ruleProvider == null || resource == null) {
                return;
            }
            Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
            if (rules != null) {
                for (FlowRule rule : rules) {
                    // 具体的流控规则校验
                    if (!canPassCheck(rule, context, node, count, prioritized)) {
                        // 校验失败的话,则抛出异常
                        throw new FlowException(rule.getLimitApp(), rule);
                    }
                }
            }
        }
    • com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot

      • void performChecking(Context context, ResourceWrapper r) throws BlockException {
            // 如果没有降级规则的话,那么就直接返回,不需要做任何降级处理
            List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
            if (circuitBreakers == null || circuitBreakers.isEmpty()) {
                return;
            }
            for (CircuitBreaker cb : circuitBreakers) {
                if (!cb.tryPass(context)) {
                    throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
                }
            }
        }

七、Dubbo 服务调用框架

7.1 Dubbo 工程案例

7.1.1 Provider & Consumer 案例搭建

  • 我们要搭建的工程架构是怎样的?

    • image-20230515231030935
  • 这里停顿一下,思考下,应该怎么正确的添加 pom 引用?

    • <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-dubbo</artifactId>
      </dependency>
      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
  • 工程需要哪些模块?

  • 需要怎么填写 yml 配置文件内容?

  • 借鉴性参考:https://cn.dubbo.apache.org/zh-cn/overview/quickstart/java/spring-boot/

7.1.2 依赖检查 & 负载均衡案例搭建

  • 依赖检查
    • check
    • 验证 check = false 后,提供方启动成功了,那么消费方是否还能调用提供方成功?
      • 第一步:启动消费方;
      • 第二步:消费方其实会阻塞等待 20s;
      • 第三步:当消费方在20s等待的过程中,立马启动提供方;
      • 第四步:待提供方启动成功后,消费方这边就等待20s睡眠完成,然后再次发起远程调用。
  • 负载均衡
    • 搭建多套提供方
      • image-20230515235449701
    • loadbalance = roundrobin
    • -Dserver.port=9070 -Ddubbo.protocol.port=29070
    • -Dserver.port=9071 -Ddubbo.protocol.port=29071
    • -Dserver.port=9072 -Ddubbo.protocol.port=29072

7.1.3 广播调用 & 缓存操作案例搭建

  • braodcast
    • image-20230517070758927
  • cache
    • image-20230517070830955

7.1.4 点对点调用案例搭建

  • url = dubbo://IP地址:端口
  • -Dcom.springcloudali.ms.facde.DemoService=dubbo://localhost:29070

7.1.5 泛化调用案例搭建

  • 模拟HTTP请求
    • 协议:http vs dubbo
    • 端口:443 vs 20880 --》 从注册中心拿
    • 路径:相对地址 vs 类名/方法名/方法入参类型名
    • 请求体:requestBody vs reqArgs
  • 如何标识泛化形式调用

7.2 Dubbo 源码分析

7.2.1 JDK SPI 与 Dubbo SPI 机制

  • SPI:Service Provider Interface,“服务” 提供 “接口”
    • image-20230518234936314
  • JDK SPI:ServiceLoader.load
    • 使用 load 方法频率高,容易影响 IO 吞吐和内存消耗。
    • 使用 load 方法想要获取指定实现类,需要自己进行遍历并编写各种比较代码。
  • Dubbo SPI:ApplicationModel.defaultModel().getExtensionLoader
    • 增加缓存,来降低磁盘IO访问以及减少对象的生成。
    • 使用Map的hash查找,来提升检索指定实现类的性能。

7.2.2 Dubbo 服务发布流程

image-20230520080042792

7.2.3 Dubbo 服务订阅流程

image-20230520080308342

7.2.4 Dubbo Wrapper 机制

image-20230520075853355

7.2.5 Dubbo 协议编解码

image-20230520080410005

image-20230520080448602

Java
1
https://gitee.com/ylimhhmily/SpringCloudAlibabaTutorial.git
git@gitee.com:ylimhhmily/SpringCloudAlibabaTutorial.git
ylimhhmily
SpringCloudAlibabaTutorial
SpringCloudAlibabaTutorial
master

搜索帮助