1 Star 13 Fork 8

hehui / SpringCloudAlibabaTutorial

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

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

MIT License Copyright (c) 2023 hehui Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

一站式速学 SpringCloudAlibaba 体系教程 展开 收起
Java
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/ylimhhmily/SpringCloudAlibabaTutorial.git
git@gitee.com:ylimhhmily/SpringCloudAlibabaTutorial.git
ylimhhmily
SpringCloudAlibabaTutorial
SpringCloudAlibabaTutorial
master

搜索帮助