熟练掌握SpringCloud流行技术栈,如Nacos、Seata、Zookeeper、Dubbo、OpenFeign、GateWay、Sentinel、SkyWalking和Discovery。熟悉

Nacos
注册中心原理
在这里插入图片描述
nacos1.x
1.提供方调用rest接口发起服务注册,注册自己的ip:端口
2.消费方每隔10秒拉取服务提供方注册列表
3.消费方提供方都需要和nacos每隔5秒进行心跳检测,15秒没有心跳,标志为不健康,30秒没有心跳标志位下线。
4.无论是消费方的上线或者是提供方的下线都会进行udp快速广播,通知全部的服务实例。
5.nacos集群之间的数据同步采用Distro协议。
nacos2.x
把所有的单向的http升级为grpc协议,心跳包仍旧需要发送,但是得益于http2.0的信道复用模式,不需要在进行频繁的TCP握手挥手了,而且也不需要再额外发送 UDP 包广播了,大大提升了性能。

配置中心原理
在这里插入图片描述
nacos客户端一启动,会根据bootstrap.yaml上配置的nacos地址,去nacos config
上拉取自己服务的配置文件信息。
之后进行长轮询,获取到变化以后采用事件发布机制,通知本地属性的变化。

配置优先级 默认
nacos data-id.yml > nacos 共享配置 >本地application.yml>本地bootstrap.yml
nacos上配置了
spring:
cloud:
config:
override-none: true
本地application.yml>本地bootstrap.yml>nacos data-id.yml > nacos 共享配置
Seata
Seata下载对应的代码的服务端,之后修改配置文件使用mysql数据源,启动服务。之后各个微服务注册上自己的服务。
AT、TCC、SAGA和XA
AT模式:
AT模式是Seata默认的事务模式,也是最常用的模式。
技术核心:在每个服务的业务数据库中创建一个undolog表。在事务的第一阶段,Seata确保业务表与undolog表的操作在一个本地事务内。在undolog表中,会分别记录事务提交前后的数据,称之为前镜像和后镜像。
执行流程:在事务的第一阶段,如果需要提交,则会删除undolog;如果需要回滚,则Seata框架会执行底层自动生成的回滚SQL。
特点:AT模式不能保证强一致,会存在中间状态,但性能较高。它要求用户拥有每个数据库的管理权,适用于企业内部的系统。

TC全局事务协调者。
TM分布式事务开启者。
RM每个独立的数据库。

1.TM就是第一个标志@GlobalTransition注解的服务,会去向TC申请开启一个全局事务,并且申请一个全局的XID。
2.每个rm,都会解析更新的sql,开启本地事务,保存更新前的镜像数据和更新后的镜像数据,以及undolog。
3.每个rm分支事务在全部的sql执行完进行本地事务的commit之后,都会向TC全局事务协调器提交执行结果,全部成功的话,通知全部的rm进行本地事务的提交,释放行锁,并且会开启异步线程去删除每个rm的数据快照和unlog文件。
若失败,则会在全部的rm开启一个本地事务,根据xid查询出数据的更新后的镜像数据和当前的数据进行对比,若不一致则会直接抛出异常,(这一步在mysql中是多余的因为会有行锁,seata这样二次比对是防止数据意外被篡改了,其他数据可能发生)
一致则根据undolog生成滚回sql执行,并且提交本地事务。

Saga 模式
SEATA提供的长事务解决方案,seata-saga模式是通过状态机来实现的。
有个在线的绘制事务状态机的前端页面,把各个服务接口的事务正向流程和出错补充流程已经入参出参都画出来,形成一个配置的json,放到项目里去。
T1 C1
T2 C2
T3 C3
fail 依次调用 C3 C2 C1
T4 C4
它其实就是基于业务代码去进行反向补偿,可以附件配合业务上的消息队列达到最终一致性,不会有全局锁。
当然如果存在全局的事务编排者,可以不用saga框架,自己手动的去 try catch 事务 做业务逻辑的补充,用了saga是面向成功回调或者失败回调接口的,并且有长事务状态机图可以直观观测出数据流的流向。
Zookeeper
ZAB类似Raft协议,强一致。
Zookeeper的四种节点类型包括:
持久节点(Persistent Node):
这种节点在创建后会一直存在于Zookeeper中,除非被显式删除。
临时节点
临时节点的生命周期与创建它的客户端会话绑定。
当客户端会话结束(如客户端与Zookeeper服务器的连接断开)时,该节点会被自动删除。
持久顺序节点
这种节点在创建后会一直存在于Zookeeper中,且每次创建时会自动生成一个唯一的序号。
序号是一个由Zookeeper自动生成的数字,用于保持节点的顺序。
持久顺序节点适用于需要保持节点顺序或提供全局唯一标识符的场景,如分布式系统中的节点注册。
临时顺序节点(Ephemeral Sequential Node):
在节点名称后面自动添加一个单调递增的序号,并在创建它的客户端会话结束后自动删除。
临时顺序节点常用于实现分布式队列或分布式锁,以及需要有序且临时性的数据存储场景。

zk的分布式锁
核心思想:
当客户端要获取锁时,在ZooKeeper中指定的父节点下创建一个临时顺序节点。
使用完锁后,客户端删除自己创建的临时顺序节点,从而释放锁。
1.创建父节点,各个客户端在访问互斥资源的时候,会去挂在这个父节点下创建临时顺序节点。
2.创建完成之后,会去判断自己客户端创建的节点的顺序号是否是最小的。
3.若为最小的则获得锁的资源,否则使用watch机制监听前一个顺序节点的。
4.当客户端释放锁,或者连接超时的时候,都会移除自己的节点,这样就会触发watch机制通知后一个监听的客户端。
5.后一个客户端会再去判断自己是否是最小的顺序节点,去获取锁。

springboot 的zk客户端有 zkclient
Apache Curator (Q瑞特)
new InterProcessMutex(谬塔克斯)

元数据
1.数据版本号:ZooKeeper为每个节点维护了一个版本号,用于记录数据变更的次数。每当节点的数据发生变化时,版本号都会自增。
这个版本号在并发控制中起到关键作用,例如,当多个客户端尝试更新同一节点时,ZooKeeper会使用版本号来确保操作的顺序性和一致性。 CAS
创建时间(Creation Time):
ZooKeeper记录了每个节点的创建时间,即节点首次被添加到ZooKeeper中的时间戳。
这个时间戳可以用于监控和审计节点的生命周期,以及识别旧节点或异常节点。
修改时间(Modification Time):
当节点的数据发生变化时,ZooKeeper会更新该节点的修改时间戳。
修改时间可以帮助用户了解节点数据的最新更新情况,以及用于实现基于时间的缓存失效策略等。
子节点数量(Child Count):
对于目录节点(或称为容器节点),ZooKeeper还记录了其子节点的数量。
子节点数量可以用于快速了解目录节点下的结构复杂性和数据规模。
临时节点会话ID(Ephemeral Owner):
对于临时节点,ZooKeeper还记录了创建该节点的客户端会话的ID。
当会话结束时,ZooKeeper会自动删除与该会话关联的临时节点。

dubbo配合zk注册图
在这里插入图片描述
Dubbo
@DubboService
Provider
@DubboReference
Consumer
我从数据从消费方发送到提供方讲解,
接着是代理层,默认使用jdk的动态代理,创建代理对象,代理真正的数据传输操作。
其次是序列化层,发送方将对象使用默认的protobuf,序列化。
接着是网络协议层,封装对应的Dubbo协议,可选grpc。
接着是传输策略层,可选负载均衡的策略,默认是随机策略。
接着是网络传输层,通过持续运行的socket服务容器,用了netty框架的主从Reactor多线程NIO模型的代码,
搭配上操作系统的epoll事件驱动机制,读取数据流的数据,并且解析协议,反序列化对象。
还会有Monitor,监控节点的调用次数和响应时间,提供健康运维。

Dubbo SPI
机制可以自定义负载均衡、网络协议和序列化方式。
META-INF/dubbo com.example.Robot optimusPrime=com.example.OptimusPrime

JDK SPI
src/main/resources/META-INF/services com.example.spi.GreetingService
此接口的具体实现
com.example.spi.impl.EnglishGreetingService
com.example.spi.impl.ChineseGreetingService

ServiceLoader<GreetingService> loader = ServiceLoader.load(GreetingService.class);  
// 遍历并调用服务提供者的方法  
for (GreetingService service : loader) {  
    service.sayGreeting();  
}

Spring boot 2.x resources/META-INF spring.factorys
Spring boot 3.x
resources/META-INF org.springframework.boot.autoconfigure.AutoConfiguration.imports

OpenFeign

@FeignClient(value = "user-service", fallbackFactory = UserServiceFallbackFactory.class)  
public interface UserService {  
  
    @GetMapping("/users/{id}")  
    User getUserById(@PathVariable("id") Long id);  
  
    // ... 其他方法  
}
@Component  
public class UserServiceFallbackFactory implements FallbackFactory<UserService> {  
  
    @Override  
    public UserService create(Throwable cause) {  
        return new UserService() {  
            @Override  
            public User getUserById(Long id) {  
                // 降级逻辑,返回一个默认值或错误信息  
                return new User(0L, "Default User", "Default Address");  
            }  
  
            // ... 其他方法也实现降级逻辑  
        };  
    }  
}

OpenFeign 内置了Ribbon作为客户端负载均衡器,可以自动对服务进行负载均衡,默认带区域的轮询。
Spring cloud GateWay

spring:  
  cloud:  
    gateway:  
      routes:  
        - id: service1_route  
          uri: http://localhost:8081  # 目标URI  
          order: 0                    # 路由优先级,数字越小,优先级越高  
          predicates:  
            - Path=/service1/**       # 断言,匹配以 /service1/ 开头的请求路径  
          filters:  
            - AddRequestHeader=X-Request-Id, 123456  # 过滤器,为请求添加一个头信息  
  
        - id: service2_route  
          uri: http://localhost:8082  
          order: 1  
          predicates:  
            - Path=/service2/**  
          filters:  
            # 可以配置多个过滤器,以列表形式给出  
            - AddRequestHeader=Another-Header, abcdef  
            - RewritePath=/service2/(?<segment>.*), /$\{segment}  # 过滤器,重写请求路径

自定义过滤器。

@Component  
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomGatewayFilterFactory.Config> {  
  
    public CustomGatewayFilterFactory() {  
        super(Config.class);  
    }  
  
    @Override  
    public List<String> shortcutFieldOrder() {  
        // 定义配置参数的顺序(如果有的话)  
        return Collections.singletonList("myParam");  
    }  
  
    @Override  
    public GatewayFilter apply(Config config) {  
        return (exchange, chain) -> {  
            // 自定义过滤逻辑  
            // ...  
  
            // 继续处理请求链  
            return chain.filter(exchange);  
        };  
    }  
  
    public static class Config {  
        // 可以在这里定义配置参数  
        private String myParam;  
  
        // ... getters, setters  
    }  
}

Sentinel

四种限流算法

1.固定窗口算法
10秒一个窗口,每个10s超过10请求就拒绝,会存在临界的第9秒9次,后一个窗口的第一秒9次,这就相当于两秒钟18次已经没有触发限流。
2.滑动窗口算法
类似Sentinel的算法,就是将固定的窗口划分的十分小,只有200ms。
5 qps
& & & & & & & & & & & & & & &
! ! ! ! !
第一个 5 个 200ms 窗口 ,最多5个请求,若超出了,则这1s内全部的请求会被拦截,向后滑动200ms一个小窗口,在计算新的1s窗口的请求量,进行限流判断。
3.漏桶算法
类似队列,消息发生填充是随机速度的,但是消费方的消费速度总是恒定的。
3.令牌漏桶算法
类似漏桶算法,消息发生填充是随机速度的,但是消费方的最大的消费速度总是恒定的,但是相比较漏桶算法,最大的不同在于,桶里面已经预置了令牌,对于短时间大流量也能匹配上一定的消费速度去处理,而漏桶算法永远都是恒定的。

页面限流截图
在这里插入图片描述
防范DDos。
低级的DDos,就是相同ip、客户端ua、相同账号这样发起大规模请求调用,这样是小儿科,Nginx就有针对ip请求速度的限制,网关也能进行限制。
高级的DDos,调用肉鸡,发起最真实的恶意请求,这样根本上防护不了,只能说保护服务不宕机、卡死。
3种攻击方式,
带宽攻击,比如文件上传接口,直接把服务的带宽占满,或者频繁进行静态资源的访问,这样可以借助厂商的cdn,或者是使用公共的对象存储,把图片放oss,让静态压力由云服务厂商的大带宽去处理。
协议攻击、如TCP,三次握手,直接就发送第一个syn,之后不连接了,tcp协议在服务端就会进入半连接状态,在超时时间之内打满。这个可以让运维优化TCP参数,调整半连接的队列。
后端接口攻击,直接攻击对外暴露的接口,这种只能通过高防服务器,云厂商大带宽服务器和高性能处理加上它们的ai识别算法,去做前置拦截过滤掉恶意流量。

使用Sentinel进行ToC接口的限流,防止瞬时流量过大,导致cpu,内存,io资源占满,任务队列无限排队,引起服务或者数据库宕机、卡死,因为某一个接口导致全服务不可用,防范DDos。

落地实践,我们会在Gateway,Sentinel整合网关,所有的toc接口能都暴露出来,针对文件上传之类的占带宽的接口,会有整体的限制。这个限制多少,预估算加上测试去测,比如最大上传5M图片,假设我们网络带宽最大是1000Mbps,那就是1000/8/5,这个是上限,取50%,就是限制的qps,当然准确都需要测试去测,防止由于带宽把重要的动态数据请求阻塞。

针对动态数据请求的攻击,首先让测试,测出每个服务单压每个接口最大的承受能力是多少,比如针对某一服务,发起超高的攻击qps,看服务能处理的qps是多少,两个qps不一样,差值其实都是在tomcat的等待队列等待,那这个服务响应的qps,就是需要接口限制的qps了,因为已经开始等待了,并且数据库和cpu内存都是这一个接口在使用,与其累计的等待不如直接拒绝。

拒绝策略,都是直接响应失败,并且在网关层会往mq发送消息,记录用户的操作入参和userId,如真的出现ddos,对于正常的用户下单,我们也能在后续发短信或者补偿优惠的方式进行二次挽救。
其次出现高流量,在k8s环境额外预留一些Iass资源,使用hpa动态扩容的方式,来应对,突发流量也是有效的,但是业务接口一般瓶颈在于数据库。

整体的目标就是不能让ddos,瞬时流量击垮冷系统。

客户端引入sentinel的核心依赖,配置上sentinel 服务端的地址,java -jar 启动sentinel 服务,web控制台可以进入,配置并且把流控配置写入nacos。

SkyWalking
全链路监控,配合web页面,能够显示出每个接口在各个服务包括mq,mysql,redis的调用链路以及各执行时间。
针对某个业务方法,每个方法的执行耗时,已经入参、出参。
SkyWalking我们是这样用的,用的是SkyWalking on Kubernetes,它是一个自定义CRD ,operator的方式,用过yaml增加annotation的方式,去动态的为java的pod增加skywalking的探针,不用自己去java -jar 指定agent了。
对于重要的服务,订单、支付、库存核心服务,会开启debug模式的日志级别,并且打印核心方法每个方法调用栈的入参和出参,并且每一步都打上完整详细的日志,通过skywalking的gpc上传日志到es。然后专门的EFK,日志系统也会收集一份日志。
当有用户有线上问题需排查的时候,记录下他大致操作的时间,做了什么操作,输入了什么参数,得到了什么响应。之后我们去es上根据时间索引查询大致的时间范围,以及对应的userid,操作的接口,确定下skywalking的tranreid,skywalking上很直观的就能看到调用的链路,已经那个方法那条sql出了什么问题。EFK定位大致的接口调用,skywalking直观的去找问题点。
第二点就是,SkyWalking的报警机制,监控各个服务的响应时间,比如一分钟之内平均响应时间大于原来95原则的两倍。

Discovery
全链路灰度发布。有这样的需要,就是局部的服务进行版本升级。
比如下订单 首先调用 风控服务。之后风控服务的风控规则升级了,增加了很多的限制规则。
之后经过软件生命周期uat测试内部全测完了,要上线了,会单独先部署一个deploy,只有一个升级版本镜像的pod。这个pod的app标签都是同一个service的,也就是说feign调用会有一部分流量调到这个pod,但是我们会结合Discovery,在Gateway的nacos配置文件进行header头的配置,也就是说只有具特定head头标识的流量才能分配到新版本的pod,Discovery的作用就是流量染色,灰度发布的作用。
上线生产之后,内部的测试员工,用他们的账号,拥有特别head头的账号进行测试,确定风控服务没问题之后,去除原有head头的限制,他会热加载,之后删除原有老版本的deploy,扩容新版的deploy副本实例,这样就做到无缝链路灰度了,要是有问题,那就不升级,原有的用户也不受影响。也适用于是否让用户选择是否参与内部全新版本,之后给她一个新版本的head头。

Discovery还有一种模式是百分比分配流量的方式,百分之10分配到新的版本,90分配到旧的版本,但是这样对于用户来讲还是会有风险。类似istio。

分布式全局ID
1.雪花算法
8字节8*8=64位
第一个不用,后面时间戳(41位),工作机器ID(10位),序列号(12位)
全局唯一性:由于时间戳、工作机器ID和序列号三者的组合,使得雪花算法能够在分布式系统中生成全局唯一的ID。
时间有序性:由于时间戳的存在,生成的ID大致按照时间递增排序,这对于需要按时间排序的场景非常有用。
实践方式,一次获取两批量的id,一批id使用完之后,异步的去请求第二批id,这样就避免了id单条获取,已经批量用完之后的数据拉取卡顿,两个id池交互获取。
我们的消息队列的自定义id就是这样的获取方式。
2.redis自增
我们就是用这种方式,专门定义了一个id服务,用于获取各类id。并且我们的业务id都会有相互关联关系。
比如,
现在新增了一件商品,需要新增分类、商品的spu、商品的sku,这三个id。
分类号,这个是redis 从10000自增的每次加1 ,假设取到某个值11111。 11111
那么该商品的spu,从10000000开始也每次自增加1,后四位和该分类的后四位一样1111。
100000001111 1七个0 后面四个1,这样spu可以自增到百万级别。
该商品的sku,1000000000,后四位也是1111。这样sku可以自增到千万级别。
并且当进行取模16运算分库分表的时候,由于基因法的存在,该商品的查询都能落到同一个库表里面。
并且业务上也能直观的看出来都属于统一商品。
注意前端js对于大于17位的long会丢失精度,需要转成字符串返回。

分布式事务
对于后台业务,面向运营人员的接口,直接@GlableTransition,一个注解开启全局的分布式锁,注意在锁的时候必须确保表设计了索引,尽量能直接锁到行锁级别,若出现范围更新,出现间隙锁,最好也小批量操作,因为面向运营的接口,并发几乎忽略,可以使用二阶段锁的seata AT模式。

重点是对于toc的高并发接口,
最开始,分清两种异常,需要解决的是有程序健壮性带来的异常问题,能处理的runtime Exception,最常见的,入参没校验,带来空指针,数组越界了或者是insert sql的时候,字段超长了,这种问题引起的分布式事务出现异常就很低级错误了。
第二种就是需要解决的异常引起的分布式事务问题,最常见的就是东西流量,接口调用超时,小概率是k8s网络引起的,运维的网络隔离策略写错,其次概率是程序代码没写好,某些入参的情况下需要处理很久,最大概率还是数据库被某些代码弄得卡死了,没索引直接全表扫描了,导致的超时。

连接超时可以确定对方服务没做。但是服务读取超时,最头疼的在于你都不知道对方服务,到底成功做了,还是没成功回滚了。

解决方法:
首先,从业务层面避免多层级的链式调用,其次使用中央编排模式。
就像A->B->C
转成 A->B 响应B的结果给A以后, A->C。将长事务有个中心编排器。
其次不要把微服务的数据库拆的很细,比如是否对于小体量公司业务是否可以考虑服务层代码是分布式的,但是数据库是同一个,这样就可以控制成本地事务,或者就相关联程度高的服务,如订单和支付不进行独占式数据库,控制成本地事务,就不会有这么多问题。

若是大体量Toc互联网业务,
处理长事务的方式是通过seata 的 saga模式,写事务状态机,自定义的反向补偿,加上mq,加上幂等判断去处理的,加上幂等就能很好解决对方服务到底成功了做成功没,没成功就不补偿了,成功了才去反向补偿。

比如我负责的核心交易层下单接口。我们是异步编排的方式,读操作异步并行,写操作同步执行。
全局不开启任何事务,只开启本地事务。
第一步,生成一个全局订单号,调用风控服务,获取商品最新的信息,三件事件都是读操作,可以异步并行,其中某一步出异常了,直接响应出错结果,不涉及事务。
年月日时分秒01订单来源八位会员号后四位,总共20为,这一步从redis查询,
同步阻塞至第二步,调用库存服务,库存服务也开启本地事务,
根据sku,查询此商品的销售库存是否可用,update stock set online_num = online_num -1,lock_num=lock_num+1 where sku=100 and online_num-1> -1;
若成功则,写入锁定表一条记录,订单号,以及锁定的商品号,以及锁定数量。
同步阻塞至第三步,调用优惠服务,一样去锁定优惠券,不让用户的一张优惠券在两个订单同时存在,写入优惠券的锁定表,订单号,优惠券号。
第四步,计算销售价格,调用物流服务,获得物流价格。
第五步,计算销售价格-物流价格得到支付价格和前端传过来的价格对比。若成功则生成父订单,这个时候再去把这个订单id,以及其他关联的用户、商品、库存和其他信息插入订单表。假设最后这一步insert出错了,通过saga 的补偿策略,依次进行逆向逻辑。
1.订单新增,取得订单号,往mq发送订单逻辑删除。
2.锁定优惠券,取得订单号,往mq发送优惠券和订单的关联锁定表逻辑删除,当然需要先查询是否存在记录,幂等的去做。
3.库存服务也是如此。
若是优惠券锁定失败,就从优惠券和库存开始反向补偿。
没开启任何事务,也没全局锁,不然对于某个商品id的库存id进行全局锁,直接会导致其他线程对此商品无法下单锁库存。

这里还有两个点,为什么还需要开启本地事务,因为需要二次保障,防止mq挂了,反向补偿的消息都发不了,这是就会有大量的数据是不一致的。
还有就是为什么一定要用消息队列补偿,直接rpc调用不行,因为假设我最后一步,插入订单失败了,这时候再用rpc调用其他服务的反向补偿接口,是不合理的,因为,自己的服务都出现未知异常了,进行远程rpc长事务补偿和正向流程执行复杂程度差不多,同步rpc调用大概率不可靠了,还会出错,而直接发消息安全的多,并且消息可以有状态存储,不像rpc失败,消息直接就没了。

分布式锁
核心公共服务,争夺一个共享资源,没争取到的进行队列等待。
Redisson,Rlock的共享资源就是。
在同一台,redis去设置相同的key,的一个hash 键值对,大key就是Rlock的字符串,hashkey就是java线程号的hash值,v是1,重入次数。
当其他线程尝试占有锁是,发现已经被别的线程占用了,就进行订阅,通过redis stream pub sub的方式,再将自己进行park阻塞,当次key不存在时,会唤醒全部的订阅者,强锁,抢到锁的进行unpark。
当然这是lua脚本的方式进行原子判断的。
zk
上面的。
reenlock
AQs队列,加 cas强锁,加park阻塞。
syncroizy
采用操作系统层面的monitor的源语,进行锁进入,锁释放,内部维护等待和阻塞队列,count 表示重入次数。

分布式会话
无状态jwt、redis的token,hash 取模的tomcat。
分布式任务
分片任务。

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值