Java技术体系
2023-08-25 17:51:48 0 举报
AI智能生成
Java的技术体系思维导图,包括了微服务、架构师能力、中间件等内容
作者其他创作
大纲/内容
微服务(MicroServices)
起源
Martin Fowler 于 2014 年发表的论文 《MicroServices》<br>
特性
微服务体积小,复杂度低
一个微服务通常只提供单个业务功能的服务,即一个微服务只专注于做好一件事
团队成员少
一个微服务通常只提供单个业务功能的服务,即一个微服务只专注于做好一件事
微服务架构 vs 单体架构<br>
团队规模
微服务架构可以将传统模式下的单个应用拆分为多个独立的服务,每个微服务都可以单独开发、部署和维护。每个服务从设计、开发到维护所需的团队规模小,团队管理成本小。
单体架构的应用程序通常需要一个大型团队,围绕一个庞大的应用程序工作,团队管理的成本大。<br>
数据存储方式
不同的微服务可以使用不同的数据存储方式,例如有的用 Redis,有的使用 MySQL。
单一架构的所有模块共享同一个公共数据库,存储方式相对单一。
部署方式
微服务架构中每个服务都可以独立部署,也可以独立于其他服务进行扩展。如果部署得当,基于微服务的架构可以帮助企业提高应用程序的部署效率。
采用单体架构的应用程序的每一次功能更改或 bug 修复都必须对整个应用程序重新进行部署。
开发模式
在采用微服务架构的应用程序中,不同模块可以使用不同的技术或语言进行开发,开发模式更加灵活。
在采用单体架构的应用程序中,所有模块使用的技术和语言必须相同,开发模式受限。
故障隔离
在微服务架构中,故障被隔离在单个服务中,避免系统的整体崩溃。
在单体架构中,当一个组件出现故障时,故障很可能会在进程中蔓延,导致系统全局不可用。
项目结构
微服务架构将单个应用程序拆分为多个独立的小型服务,每个服务都可以独立的开发、部署和维护,每个服务都能完成一项特定的业务需求。
单体架构的应用程序,所有的业务逻辑都集中在同一个工程中。
Java微服务框架<br>
Spring Cloud
简介
它能够基于 REST 服务来构建服务,帮助架构师构建出一套完整的微服务技术生态链
它是一种微服务规范,提供了服务治理、服务网关、智能路由、负载均衡、断路器、监控跟踪、分布式消息队列、配置管理等领域的解决方案<br>
实现支持<br>
第一代实现:Spring Cloud Netflix
2018 年 12 月12 日,Netflix 公司宣布 Spring Cloud Netflix 系列大部分组件都进入维护模式,不再添加新特性
第二代实现:Spring Cloud Alibaba
2018 年 7 月,Spring Cloud Alibaba 正式开源,并进入 Spring Cloud 孵化器中孵化
2019 年 7 月,Spring Cloud 官方宣布 Spring Cloud Alibaba 毕业
Dropwizard
用于开发高性能和 Restful 的 Web 服务,对配置、应用程序指标、日志记录和操作工具都提供了开箱即用的支持
Restlet
该框架遵循 RST 架构风格,可以帮助 Java 开发人员构建微服务
Spark
最好的 Java 微服务框架之一,该框架支持通过 Java 8 和 Kotlin 创建微服务架构的应用程序
Dubbo
由阿里巴巴开源的分布式服务治理框架
微服务组件
注册中心
Nacos
健康检查
临时实例:客户端上报模式<br>
客户端每5秒向服务端发送一次心跳包
服务端15秒内未接收到心跳,则将服务标记为“不健康”,30秒内未接收到,则进行服务下线
持久化实例:服务端主动检测
nacos主动探知客户端健康状态,默认间隔为20秒<br>
健康检查失败后实例会被标记为不健康,不会被立即删除<br>
数据存储
存储在内存中 ConcurrentHashMap:serviceMap
通讯协议
gRPC
数据变更通知
push(长连接)
pull
CAP模型
AP(默认)-临时实例<br>
distro(分片写入)<br>
Nacos 启动时首先从其他远程节点同步全部数据<br>
Nacos 每个节点是平等的都可以处理写入请求,同时把新数据同步到其他节点<br>
每个节点只负责部分数据,定时发送自己负责数据的校验值到其他节点来保持数据一致性
当该节点接收到属于该节点负责的服务时,直接写入
当该节点接收到不属于该节点负责的服务时,将在集群内部路由,转发给对应的节点,从而完成写入
宕机问题<br>
写入任务会转移到其他节点
网络分区问题
会损害可用性,客户端会表现为有时候服务存在有时候服务不存在<br>
distro 协议不会有脑裂问题
CP-持久化实例
Raft
架构图
Nacos2
协议支持:gRPC
http2 + protobuf
安全机制、鉴权、加密<br>
空间管理
namespace
环境隔离:dev/prd/test
group
业务分层(多租户隔离)
服务定位:namespace+group+service
注册与发现流程
注册
服务续约:客户端每5秒发送心跳
服务端接收心跳信息后,更新心跳时间
服务不健康,则重新标记健康,并通知客户端服务可用
服务上线:客户端远程调用服务端注册
服务实例维护到serviceMap中
开启定时:每5秒进行服务剔除
服务端15秒内未接收到心跳,则将服务标记为“不健康”,30秒内未接收到,则进行服务下线
增加监听器(一致性):通知客户端服务变化
Distro协议
Raft协议
发现
客户端
优先从本地缓存中获取服务实例信息,获取不到则从服务端获取所有服务实例
定时(每秒)从Nacos服务端获取服务实例信息
服务端
返回指定命名空间下内存注册表中所有的永久实例和临时实例给客户端
开启一个UDP服务实例信息变更推送服务
集群部署
集群部署配置
cluster.conf 配置所有节点的 ip:port
至少3个节点
集群架构
直连模式
客户端配置
address=192.168.0.1:8848,192.168.0.2:8848,192.168.0.3:8848
优点
架构简单,用户理解成本低
没有引入额外的组件,没有新增组件的运维成本
缺点
可用性、可伸缩性差:集群本身的扩缩容必须要改动业务代码才能被感知到<br>
使用场景
开发环境、测试环境
VIP模式(负载均衡/SLB)
客户端配置
address={VIP}:8848
优点
高可用,服务通过负载均衡组件进行转发,自动识别可用服务<br>
可伸缩性。水平扩缩容时,只需要让 VIP 感知即可,可伸缩性好<br>
缺点
需要引入负载均衡组件,且对负载均衡组件要求较高
引入VIP,可读性不佳
使用场景
生产环境
域名+SLB
客户端配置
address={域名地址}<br>
优点
更强的高可用,域名的可用性需要由 DNS 域名服务器负责,可用性保障较高<br>
可伸缩性。水平扩缩容时,只需要让 VIP 感知即可,可伸缩性好
缺点
依赖了域名解析系统和负载均衡系统,生产部署时,需要有配套设施的支持
多层网络转发
使用场景
生产环境
寻址服务模式(寻址直连)
客户端配置
address=0.0.0.0?endpoint=127.0.0.1&endpointPort=8080<br>
优点
高可用性。域名的可用性需要由 DNS 域名服务器负责,可用性保障较高;地址服务器的职责单一,有较高的可用性;运行时 Client 直连 Nacos Server 节点,可用性靠 nacos-sdk 保障
可伸缩性。水平扩缩容时,只需要让地址服务器感知即可,可伸缩性好
缺点
依赖了域名解析系统和地址服务器,生产部署时,需要有配套设施的支持
使用场景
生产环境<br>
远程通讯协议
OpenFeign
Feign 是声明式 Web 服务客户端,简化 HTTP API 的开发<br>
Feign 的工作原理<br>
1.通过 @EnableFeignClients 注解启动 Feign Starter 组件
@Import(FeignClientsRegistrar.class)
ResourceLoaderAware
资源加载器,可以加载 classpath 下的所有文件
EnvironmentAware<br>
上下文,可通过该环境获取当前应用配置属性等
ImportBeanDefinitionRegistrar
负责动态注入 IOC Bean,分别注入 Feign 配置类、FeignClient Bean
2.Feign Starter 在项目启动过程中注册全局配置,扫描包下所有的 @FeignClient 接口类,并进行注册 IOC 容器<br>
3.@FeignClient 接口类被注入时,通过 FactoryBean#getObject 返回动态代理类
4.接口被调用时被动态代理类逻辑拦截,将 @FeignClient 请求信息通过编码器生成 Request
5.交由 Ribbon 进行负载均衡,挑选出一个 Server 实例<br>
6.继而通过 Client 携带 Request 调用远端服务返回请求响应
7.通过解码器生成 Response 返回客户端,将信息流解析成为接口返回数据
负载均衡
负载均衡(Load Balance) ,简单点说就是将用户的请求平摊分配到多个服务器上运行,以达到扩展服务器带宽、增强数据处理能力、增加吞吐量、提高网络的可用性和灵活性的目的。
负载均衡方式
服务端负载均衡<br>
F5、Nginx<br>
客户端负载均衡
Ribbon、LoadBanlance
Ribbon
负载均衡策略(IRule 接口)
RoundRobinRule:线性顺序轮询
RandomRule:随机
RetryRule:按照 RoundRobinRule(轮询)的策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试<br>
WeightedResponseTimeRule:平均响应时间权重<br>
BestAvailableRule:最小并发量可用服务实例
AvailabilityFilteringRule:较少并发可用服务实例
ZoneAvoidanceRule(默认):服务区域性能和可用性选择,无区域则与轮询类似<br>
切换策略
注解方式
自定义 @Bean 方法,返回IRule
配置方式
provider.ribbon.NFLoadBalancerRuleClassName
自定义策略
1. 创建一个类,集成AbstractLoadBalancerRule,重写choose方法<br>
2.将定制的负载均衡策略实现类注入到容器
3.主启动类使用 @RibbonClient 注解让我们定制的负载均衡策略生效
工作原理
获取服务实例
1.负载均衡客户端在初始化时向注册中心获取服务注册列表信息
2.根据不同的 IPing 实现,向获取到的服务列表 串行发送 ping,以此来判断服务的可用性
3.如果服务的可用性 发生了改变或者被人为下线,那么重新拉取或更新服务列表<br>
4.当负载均衡客户端有了这些服务注册类列表,自然就可以进行 IRule 负载均衡策略
非健康服务实例下线
服务列表定时维护(定时任务)
使用 IPing 的实现类 PingUrl,每隔 10 秒会去 Ping 服务地址,如果返回状态不是 200,那么默认该实例下线<br>
Ribbon 客户端内置的扫描,默认每隔 30 秒去拉取注册中心的服务实例,如果已下线实例会在客户端缓存中剔除<br>
底层原理实现
1.创建 ILoadBalancer 负载均衡客户端,初始化 Ribbon 中所需的 定时器和注册中心上服务实例列表<br>
2.通过负载均衡策略选择出健康服务列表中的一个 Server
3.将服务名(ribbon-produce)替换为 Server 中的 IP + Port
配置中心
Nacos
配置的动态感知
框架支持
Dubbo
无集成
Spring Boot
@RefshScope<br>
@NacosValue
Spring Cloud<br>
@Value
工作原理
多环境配置
namespace<br>
resource
application
指定运行环境:active=[dev]
application-dev
namespace=dev
application-prd
namespace=prd
nacos多环境配置dev/prd
配置文件优先级
配置共享级别
内部级
spring.cloud.nacos.config
扩展级
spring.cloud.nacos.config.extsion-config[n]
共享级
spring.cloud.nacos.config.shared-configs
配置优先级
shared-configs中n越大、或越靠后,优先级越高
extension-configs中n越大、或越靠后,优先级越高;
shared-configs < extension-configs < 内部;
本地和远程配置
默认情况下nacos属性源配置优先级最高,会覆盖系统属性源和配置属性源(本地文件)
配置更改
spring.cloud.config.allow-override
是否允许Nacos远程配置被本地文件覆盖,默认为true
spring.cloud.config.override-system-properties
Nacos远程配置是否可以覆盖系统属性源(系统环境变量或系统属性),默认为true,即允许
spring.cloud.config.override-none
Nacos远程配置是否不覆盖其他属性源(文件、系统),默认为false,即覆盖其他源(文件、系统),当allow-override:为true时才会生效
最佳实践(参考)
能放本地、不放远程;避免滥用远程服务器
尽量规避优先级
定规则,例如所有配置属性都要加上注释
配置管理人员尽量少
Apollo
Spring Cloud Config
git
svn
流量控制
Sentinel<br>
官网
基本概念
资源
通过 Sentinel 提供的 API 来定义一个资源,使其能够被 Sentinel 保护起来<br>
URL
服务
方法
规则
围绕资源而设定的规则。Sentinel 支持流量控制、熔断降级、系统保护、来源访问控制和热点参数等多种规则,所有这些规则都可以动态实时调整
资源定义
适配主流框架自动定义资源
只要引入相关的适配模块(例如 spring-cloud-starter-alibaba-sentinel),Sentinel 就会自动将项目中的服务(包括调用端和服务端)定义为资源,资源名就是服务的请求路径
通过 SphU 手动定义资源
Sentinel 提供了一个名为 SphU 的类,它包含的 try-catch 风格的 API ,可以帮助我们手动定义资源
通过 SphU.entry("testAbySphU") 定义资源,限流时,抛异常<br>
通过 SphO 手动定义资源
Sentinel 还提供了一个名为 SphO 的类,它包含了 if-else 风格的 API,能帮助我们手动定义资源<br>
通过 SphO.entry("testBbySphO") 定义资源,限流时,返回false
注解方式定义资源
@SentinelResource 注解定义资源
blockHandler<br>
blockHandler 函数访问范围需要是 public
blockHandler 函数访问范围需要是 public<br>
参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException
blockHandler 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 blockHandler 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
务必添加 blockHandler 属性来指定自定义的限流处理方法,若不指定,则会跳转到错误页(用户体验不好)
限流算法
计数器(固定窗口)
滑动窗口
漏桶算法
令牌桶算法
限流原理
熔断降级原理
服务网关
解决的问题
当服务数量众多时,客户端需要维护大量的服务地址,这对于客户端来说,是非常繁琐复杂的
在某些场景下可能会存在跨域请求的问题
<span style="color: rgb(68, 68, 68); font-family: "Helvetica Neue", 微软雅黑, "Microsoft Yahei", Helvetica, Arial, sans-serif; font-size: 14px;">身份认证的难度大,每个微服务需要独立认证</span>
核心功能
同业务域,使用服务注册与发现,跨业务域访问,要通过微服务网关
服务安全性:防止内部服务被调用,通过网关控制暴露的接口(比如,充值服务被知道后,内部服务随便就能调了)
服务隔离:不用的业务域进行服务隔离,同时降低公用网关压力
服务治理:对上游服务可进行限流、熔断,避免上游服务并发过大,导致服务崩溃
服务追踪:所有的请求都是通过网关进入(web前端、跨域服务),方便进行请求链路跟踪
常见的 API 网关实现方案
Spring Cloud Gateway
核心概念
Route(路由)
网关最基本的模块。它由一个 ID、一个目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成<br>
Predicate(断言)
路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务
匹配逻辑
Route 路由与 Predicate 断言的对应关系为“一对多”,一个路由可以包含多个不同断言<br>
一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言
当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发
匹配内容
Path
Method
Header
Cookie
Before
After
Between
Filter(过滤器)
过滤器,我们可以使用它对请求进行拦截和修改,还可以使用它对上文的响应进行再处理
过滤器类型
Pre
在请求被转发到微服务之前可以对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作
Post<br>
在微服务对请求做出响应后可以对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等
作用范围<br>
GatewayFilter:应用在单个路由或者一组路由上的过滤器
AddRequestHeader:拦截传入的请求,并在请求上添加一个指定的请求头参数
AddRequestParameter:拦截传入的请求,并在请求上添加一个指定的请求参数
AddResponseHeader:拦截响应,并在响应上添加一个指定的响应头参数<br>
PrefixPath:拦截传入的请求,并在请求路径增加一个指定的前缀。<br>
RemoveRequestHeader:移除请求头中指定的参数<br>
GlobalFilter:应用在所有的路由上的过滤器
自定义类实现 GlobalFilter 接口,重写 filter 方法
工作流程
1.客户端将请求发送到 Spring Cloud Gateway 上
2.通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler
3.通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑返回响应结果
4.过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑
5.过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等
6.过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等
7.响应原路返回给客户端
YML配置
server:<br> port: 9527 #端口号<br> <br>spring:<br> application:<br> name: microServiceCloudGateway #服务注册中心注册的服务名<br> <br> cloud:<br> gateway: #网关路由配置<br> discovery:<br> locator:<br> enabled: true #默认值为 true,即默认开启从注册中心动态创建路由的功能,利用微服务名进行路由<br><br> routes:<br> #将 micro-service-cloud-provider-dept-8001 提供的服务隐藏起来,不暴露给客户端,只给客户端暴露 API 网关的地址 9527<br> - id: provider_dept_list_routh #路由 id,没有固定规则,但唯一,建议与服务名对应<br> uri: lb://MICROSERVICECLOUDPROVIDERDEPT #动态路由,使用服务名代替上面的具体带端口 http://eureka7001.com:9527/dept/list<br><br> predicates:<br> #以下是断言条件,必选全部符合条件<br> - Path=/dept/list/** #断言,路径匹配 注意:Path 中 P 为大写<br> - Method=GET #只能时 GET 请求时,才能访问<br><br>eureka:<br> instance:<br> instance-id: micro-service-cloud-gateway-9527<br> hostname: micro-service-cloud-gateway<br> client:<br> fetch-registry: true<br> register-with-eureka: true<br> service-url:<br> defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
链路监控
分布式消息
分布式事务
定义
在分布式系统中,每个节点可能有自己的本地事务;当需要跨多个节点通过网络远程协作完成的事务,称为分布式事务<br>
场景
跨JVM进程
跨数据库实例
CAP定理
一致性(Consistency)<br>
副本最新,强一致性:在分布式系统中的所有节点上,数据的副本都必须保持一致的状态。这意味着当一个节点修改了数据之后,其他节点应该能够立即看到这个修改后的结果
可用性(Availability)<br>
高可用:分布式系统在面对用户请求时,应该保证每个请求能够得到响应,并且能够正确地处理请求,即系统始终处于可用状态
分区容忍性(Partition Tolerance)
分布式系统能够在遇到网络分区(节点之间的通信中断)的情况下继续正常运行。也就是说,当网络发生故障或者节点之间无法直接通信时,系统仍然能够保持一致性和可用性。
BASE理论
BA:Basic Available(基本可用)<br>
整个系统在某些不可抗力的情况下,仍然能够保证“可用性”,即一定时间内仍然能够返回一个明确的结果
与高可用区别
“一定时间”可以适当延长,当举行大促时,响应时间可以适当延长(例如,秒杀时用MQ,慢慢消费)
给部分用户返回一个降级页面,直接返回一个降级页面,从而缓解服务器压力<br>
S:Soft State(柔性状态)<br>
允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性<br>
E:Eventual Consisstency(最终一致性)
同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的
解决方案
2PC
将事务分为准备阶段和提交阶段来处理<br>
参加角色
协调者
参与者
过程
第一阶段(voting phase投票阶段)<br>
协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复
各参与者执行事务操作,将undo和redo信息记入事务日志中(但不提交事务)
如参与者执行成功,给协调者反馈同意,否则反馈终止<br>
第二阶段(commit phase提交执行阶段)<br>
所有节点同意<br>
协调者节点向所有参与者节点发出“正式提交(commit)”的请求
参与者节点正式提交事务,释放占用的资源
参与者节点向协调者节点发送Ack完成消息
协调者节点收到所有参与者节点反馈的ack完成消息后,完成事务
任一节点终止/超时
协调者节点向所有参与者节点发出回滚操作的请求
参与者节点利用阶段1写入的undo信息执行回滚,并释放在整个事务期间内占用的资源
参与者节点向协调者节点发送ack回滚完成消息<br>
协调者节点受到所有参与者节点反馈的ack回滚完成消息后,取消事务
优点
尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域
缺点
性能问题:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态
可靠性问题:参与者发生故障,协调者需要给每个参与者额外指定超时机制,超时后整个事务失效,协调者发生故障,参与者会一直阻塞下去,需要额外的备机进行容错
数据的一致性问题(二阶段无法解决的问题):协调者在发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了,那么即使通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否已经提交<br>
3PC<br>
过程
阶段一:CanCommit阶段
事务询问:协调者向所有参与者发出包含事务内容的CanCommit请求,询问是否可以提交事务,并等待所有参与者答复
响应反馈:参与者收到CanCommit请求后,如果认为可以执行事务操作,则反馈yes并进入预备状态,否则反馈No<br>
阶段二:PreCommit阶段
假如所有参与者均反馈yes,协调者预执行事务
有任何一个参与者向协调者发送了No指令,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断
阶段三:DoCommit阶段<br>
提交事务
中断事务
优点<br>
相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务
避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务
缺点
数据不一致问题依然存在
当在参与者收到PreCommit请求后等待do commit指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致(参与者未接收到协调者指令的一定时间后,会自行提交事务)
TCC(事务补偿)<br>
简述<br>
TCC方案是一种应用层面侵入业务的两阶段提交,是目前最火的一种柔性事务方案,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作
过程
第一阶段
Try:主要是对业务系统做检测及资源预留(加锁,锁住资源)<br>
第二阶段
Confirm(确认):执行真正的业务(执行业务,之后释放锁)<br>
Cancel(取消):是预留资源的取消(出问题,释放锁)<br>
最终一致性保障
Try阶段执行成功并开始执行Confirm阶段时,默认Confirm阶段是不会出错的。也就是说只要Try成功,Confirm一定成功(TCC设计之初的定义)<br>
Confirm与Cancel如果失败,由TCC框架进行重试补偿<br>
Confirm和Cancel要实现幂等性
存在极低概率在CC环节彻底失败,则需要定时任务或人工介入
优点
性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源
数据最终一致性:基于Confirm和Cancel的幂等性,保证事务最终完成确认或者取消,保证数据的一致性
可靠性:解决了XA协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群
缺点
TCC的Try、Confirm和Cancel操作功能都要按具体业务来实现,业务耦合度较高,提高了开发成本
本地消息表
角色
事务主动方
事务被动方
过程
通过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务<br>
分布式事务管理器
seata<br>
数据一致性
缓存的目的
加快访问速度
避免大并发情况下,访问数据库导致系统崩溃
适合缓存的数据
热点数据
读多写少
缓存量大但又不常变化的数据,比如详情,评论等
数据一致性问题原因
居于CAP理论,不可能强一致性
解决方案
旁路缓存(Cache Aside)-先写数据库,再删缓存
先写数据库,再写缓存
问题:并发写场景,缓存可能出现不一致
先写缓存,再写数据库?
问题:并发写场景,缓存可能出现不一致
先删缓存,再写数据库?
问题:读写并发场景,缓存可能出现不一致
问题1:读写并发,已更新数据库,尚未删除缓存,此时会读到脏数据
脏数据时间范围:更新数据库后,失效缓存前。这个时间范围很小,通常不会超过几毫秒
问题2:读写并发,读未命中,已读到数据尚未写入缓存时,数据库已更新并删除缓存,此时会出现数据不一致问题<br>
此问题极少可能出现,因为缓存写速度比数据库速度快
问题3:写入频繁时,会经常删除缓存,导致命中率低<br>
1.考虑是否需要使用缓存,缓存适合场景:读多写少
2.更新数据时,使用分布式锁同步更新缓存(影响性能)<br>
3.缓存较短的过期时间,降低对业务的影响<br>
问题4:更新成功,缓存删除失败
重试机制-消息队列
业务入侵,引入消息组件,更复杂,同时依赖消息队列的可靠性
MySQL binlog
可引入阿里的 Canal<br>
无业务入侵,架构更复杂,适合基建完善的大厂
此方式日常开发中比较常用
读写穿透(Read Through/Write Through)
原理:通过Cache Provide,同步更新数据库和缓存
问题
同步写,存在性能问题
依赖数据库和缓存的可用性
异步缓存写入(Write Behind Caching)<br>
原理:先写入Cache Provide,再异步批量写入数据库和缓存中
问题:存在数据丢失问题
场景:消息队列中消息的异步写入磁盘、MySQL 的 Innodb Buffer Pool 机制
分表分库
OOM快速定位<br>
原因
应用一次性申请对象过多
举例:全表查询数据库数据
解决方案:分页查询,限制查询数据量
内存溢出:内存资源耗尽未释放
举例:不断新增connection查询数据库,未释放
解决方案
使用后关闭
使用池化思想
本身资源不足<br>
通过jmap -heap查看堆信息<br>
定位
系统已挂<br>
提前设置-XX:+HeapDumpOnOutofMemoryError -XX:HeapDumpPath=<br>
系统运行中
导出dump文件:jmap -dump:format=b,file=xushu.hprof 14660<br>
Arthas<br>
dump文件分析步骤
文件导入JVisualVM<br>
在类列表上找到最大的自定义对象<br>
找到GCRoot<br>
双击自定义对象
点开其中一个实例
点击其中一个对象
展开字段引用
在线程中显示GCRoot的引用
找到问题代码
架构师
系统架构图
4R架构
定义
软件架构指软件系统的顶层结构(Rank),它定义了系统由哪些角色(Role)组成,角色之间的关系(Relation)和运作规则(Rule)
案例
4+1视图
定义
国内很少用
架构复杂度增加
单体 -> 分布式
绑定UML
UML画架构图存在问题
太丑,不直观
理解困难
视图边界比较容易混淆
常见架构图画法(非标准)
架构图分类
客户端和前端为什么只需要模块划分
一般是可执行程序,单体应用,非分布式
业务架构示例
区块长短
布局:对齐好看
前端架构示例
系统架构示例
后端逻辑架构也叫系统架构
MongoDB Shard架构图
支付中心系统架构图
业务架构图和系统架构图的区别?
应用架构
部署架构
圆点代表网络加速点,一般指网络专线(PUP)
系统序列图
系统序列图用UML序列图画,用来描述核心功能的实现规则(Rule)
架构能力考核
分级考核
P6
P6,P6+ 和 P7- 要求一样
常见问题
P7
P7,技术选型
链式学习法
比较学习法
P7常见问题
P8
P8要求
复杂度
案例证明
中间件
Redis知识体系
Redis基础使用篇
Redis简介
Redis由来
Redis特征
速度快
基于内存<br>
命令执行单线程
IO多路复用
KV数据结构,底层数据结构支持
持久化
主从复制
高可用、分布式部署
基本应用场景
缓存存储类
缓存
session
分布式可重入锁
对象类数据存储
购物车
抽奖
集合操作:交集、并集、差集
点赞、关注
排行榜
位图操作
阻塞队列、堆栈
有序列表
Redis高级使用篇
高级功能
事务
multi、exec/DISCARD<br>
保证多个指令的原子性,但不能拿中间结果做业务处理<br>
pipeline
不能保证原子性,减少网络开销提升性能<br>
lua脚本
保证多个指令的原子性(推荐使用)
发布与订阅
subscribe、publish
Redisson分布式锁
可重入<br>
lua+hash
key
lockObj
field
uuid+threadid
value
重入次数
自旋等待
Semaphore+发布订阅<br>
自动续约-看门狗
原理:HashedWheelTimer时间轮<br>
1.timer.newTimeout 时,会把任务塞入timeouts队列中
2.woker线程启动时,每转一次轮盘,会先删除取消的任务,接着从 timeouts 中获取指令,根据指令执行时间,算出轮盘刻度和轮数,塞入对应的轮盘上<br>
3.循环执行任务:如果是当前轮数,则删除指令并新启线程执行任务(如果指令是取消状态,则删除指令),最后把当前指令的轮数减一
机制:客户端不设置锁时长,服务端配置30s过期并且使用看门狗10s定时轮询增加时间<br>
解决问题:服务挂掉或者重启,则看门狗也被删掉了,被锁的key在30s内会自动释放
可靠性
红锁/联锁<br>
往多个实例加锁,过半成功
尽可能保障Redis的可靠性(不是100%,一般不用)
整体流程
Redis数据结构篇
value的数据结构
String
SDS(simple dynamic string)
底层结构
len
字符串长度
alloc
分配给字符数组的空间长度
buf[]
字符数组,用来保存实际数据<br>
优点
O(1)复杂度获取字符串长度
二进制安全
可以存储音频、图片、视频等
减少内存重分配次数,提高性能
问题
频繁操作,导致内存空间重复分配:trim(str)<br>
字符串追加内存不足导致泄露:concat(str, str)
解法
空间预分配
当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小(小于 1MB 翻倍扩容,大于 1MB 按 1MB 扩容)
惰性空间释放<br>
截取的时候 不马上释放空间,供下次使用
缺点
需要更多的空间
hashs
ziplist
结构
zlbytes,记录整个压缩列表占用对内存字节数
zltail,记录压缩列表「尾部」节点距离起始地址由多少字节,也就是列表尾的偏移量
zllen,记录压缩列表包含的节点数量<br>
zlend,标记压缩列表的结束点,固定值 0xFF(十进制255)
prevlen,记录了「前一个节点」的长度
encoding,记录了当前节点实际数据的类型以及长度;<br>
data,记录了当前节点的实际数据
特点:压缩列表会根据存入的数据的不同类型以及不同大小,分配不同大小的空间,所以是为了节省内存而采用的
联锁更新,适用数据量小的场景:因为是一块完整的内存空间,当里面的元素发生变更时,会产生连锁更新,严重影响我们的访问性能<br>
使用条件(&)
每个键值对小于64byte
键值对数量小于512个
dictht<br>
结构
哈希冲突
链式哈希
头插法
扩容机制
扩容时机
非持久化:已用数据量 >= 分配节点数<br>
持久化:已用数据量 >= 分配节点数*5
扩容方式
渐进式Rehash
迁移时机
指令执行时
定时任务
迁移步骤
为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表<br>
将rehashidx的值设置为0,表示rehash工作正式开始<br>
将ht[0]哈希表在rehashindex索引上的所有键值对rehash到ht[1],当rehash工作完成以后,rehashindex的值+1
所有键值对都会被rehash到ht[1],这时将rehashindex的值设置为-1,表示rehash操作结束<br>
过程中指令执行
在渐进式rehash的过程,如果有增删改查操作时,如果index大于rehashindex,访问ht[0],否则访问ht[1]
Lists
sets
sorts set
消息队列
常见问题
如何保证消息队列的顺序性
生产者是顺序生产的
固定一个消费者进行消费(多个订单,可以多个生产者和消费者)<br>
多生产者,而且消息是无序的
消费时,记录顺序编号到redis,符合顺序则执行,不符合则重入队列(或者存入redis中)
消息队列如何保证消息不丢失
原因:生产者发送消息过程中出现网络问题,导致丢失<br>
开启生产者确认机制(confirm),写入到RabbitMQ成功时,rabbitMQ会回传ACK消息;写入失败时,会有失败回调,可以在失败回调中进行处理,把消息写入数据库,并标志发送失败<br>
原因:队列和消息没有持久化
此时队列和消息是保存在内存中,一旦服务重启或者宕机就会出现消息丢失问题
将队列和消息配置持久化,保存在磁盘中<br>
原因:队列和消息已经持久化;但是在将消息将要写入磁盘时发生宕机
在生产者端开启confirm确认模式;只有当磁盘写入成功后才会返回ack
配置rabbitmq集群<br>
原因:消费者消费消息出现异常
开启消费者手工应答机制(ACK),正常消费成功后,进行ack
共识算法
Raft
简介:可以理解为主从一致性
领导人选举(唯一、权威)
3种状态
Leader
Candidate
Follower
选举过程<br>
follower长时间未收到来自leader的心跳消息,触发选举时停,转变为candidate,将Term加一,然后将新Term和自身的最新日志序号进行广播以期获取选票
其他收到投票请求的节点先过滤掉Term小于自己的请求,然后判断1.自己是否已投票;2.是否认为现在有leader;3.该投票请求的日志index是否大于自己。若判断全通过则投赞成票
收到过半数节点的赞成票的candidate将转变成为leader,开始定时发送心跳消息抑制其他节点的选举行为<br>
投票条件被用来保证leader选举的正确性(唯一),随机的选举时停则被用来保证选举的效率,因为集群处于选举状态时是无法对外提供服务的,应当尽量缩短该状态的持续时间,尽量减小竞选失败发生的概率
问题
何时发起竞选
选举时停超时electionTime:当在此时间段内,没有收到来自leader的心跳消息,就可以认为当前没有leader存在,可以发起竞选
选举时停:150ms-300ms内随机<br>
成为Candidate
一个follower长时间未接收到leader的心跳,则会自增Term(任期),成为候选者进行竞选,竞选时,会发送Term和Index(日志序号)<br>
Term
任期:一个全局可见递增的数字,任期Term是一个全局可见递增的数字<br>
几乎每个在集群间传播的消息都会携带者发送者所属的Term<br>
Index
日志序号
投票
原则:先到先得,一个Term内只能投一次赞成票
条件
Term大于自身
Index大于自身
投票瓜分情况
此次选举失败,触发选举时停,重新选举
日志复制(数据变动)
ZAB
面试问题
项目实践
分布式ID
UUID
特性:36位长度的16进制的字符串
优点
不依赖任何数据源,自行计算,没有网络ID,速度超快,并且全球唯一<br>
缺点
没有顺序性,并且比较长(128bit)
索引问题
mysql B+树索引分裂重组
索引效率下降,占用空间多<br>
适用场景
只要对存储空间没有苛刻要求的都能够适用,比如各种链路追踪、日志存储等
系统时间戳
Redis原子递增
设计思路:通过Redis的INCR/INCRBY自增原子操作命令,保证生成的ID肯定是唯一有序的
优点
整体吞吐量比数据库要高
缺点
Redis实例或集群宕机后,找回最新的ID值有点困难
适用场景
比较适合计数场景,如用户访问量,订单流水号(日期+流水号)等
数据库全局表自增ID
设计思路
设置步长方式
初始值分段
优点
非常简单,有序递增,方便分页和排序
缺点
不利于横向扩展<br>
雪花算法
特征
全局唯一性
趋势递增
保证安全
无规则性,不让别人根据ID猜出业务数据量
含时间戳
高可用
低延迟
高QPS
QPS可以到 409.6 w/s
原生雪花算法序列号12位,也就是1毫秒最大可生成2^12(4096),相当于1秒可生成 4096 * 1000 个ID
场景
百度(uid-generator)、美团(Leaf)及滴滴(Tinyid)等开源分布式ID都是基于雪花算法实现
实现原理
二进制64位长整型数字
1bit保留位
二进制中最高位是符号位,1表示负数,0表示正数,所以固定为0
42bit时间戳
毫秒时间戳,最长可表示69年,从1970到2039年(也可从固定时间点算起)
10bit机器码
每台机器分配一个id,上限是1024(集群的上限)
12bit序列号<br>
自增域,每一毫秒能分配的序列数,最大4096
优点
分布式系统内不会产生ID碰撞,效率高
不需要依赖数据库等第三方系统,稳定性更高,可以根据自身业务分配bit位,非常灵活
生成ID的性能也非常高,每秒能生成26万个自增可排序的ID
问题
时间回拨
解释
时间被调整回到了之前的时间,由于雪花算法重度依赖机器的当前时间,所以一旦发生时间回拨,将有可能导致生成的 ID 可能与此前已经生成的某个 ID 重复
现象引发
网络时间校准<br>
人工校验
闰秒问题
闰秒是偶尔运用于协调世界时(UTC)的调整,经由增加或减少一秒,以消弥精确的时间(使用原子钟测量)和不精确的观测太阳时(称为UT1),之间的差异
雪花算法严重依赖时间戳,当出现负闰秒也就是时间减少一秒时(时间往前回拨1秒),雪花Id就可能出现重复,而原生的雪花算法出现时间回拨的处理方式是直接抛异常
2022年11月,在第27届国际计量大会上,科学家和政府代表投票决定到2035年取消闰秒
解决方案
抛出异常
延迟重试
时钟序列
设计思路
将原本10位的机器码拆分成3位时钟序列及7位机器码
发生时间回拨的时候,时间已经发生了变化,那么这时将时钟序列新增1位,重新定义整个雪花Id
为了避免实例重启引起时间序列丢失,因此时钟序列最好通过DB/缓存等方式存储起来<br>
缺点
集群缩减到 128(2^7)
时间回拨支持 8 次(2^3)
新思路:使用系统启动时间作为固定时间戳
前端精度丢失问题
使用String作为中间转换:JS 中Number是16位的(指的是十进制的数字),而雪花算法计算出来最长的数字是19位的
分页排序优化
增加索引字段
order by 字段增加索引:能解决浅查询的分页,深查询不走索引(回表问题)
order by和查询字段加成联合索引:能解决深查询问题,但查询字段过多,联合索引过大,同时新增查询字段时,不走索引
子查询方式(手动回表)
在子查询中进行排序分页,查出 id 后,再通过 left join 关联表查出其他字段
缺点:子查询内容过多时,不建议使用
从业务端着手,前后端配合查询
前端把最后一个 id 和 排序值传给后端,后端每页查询
哈希冲突解法
链式哈希
被分配到同一个哈希桶上的多个节点用链表连接起来
实践
redis
HashMap
开发地址法
寻找一个新的哈希地址<br>
线性探测法
直接往后找<br>
堆积现象:非同义词冲突
平方探测法(二次探测)<br>
前后查找
实践
ThreadLocalMap
再哈希法
同时构建多个哈希函数,逐个使用,直到不冲突
缺点:增加计算时间,影响性能<br>
公共溢出区
冲突键放到溢出区
秒杀系统
简述:秒杀就是在同一时刻大量请求争抢购买同一商品并完成交易的过程。从架构视角来看,秒杀系统本质是一个高性能、高一致、高可用的三高系统
存在的问题
用户-体验差
秒杀开始,系统瞬间承受平时数十倍甚至上百倍的流量,直接宕掉
用户下单后却付不了款,显示商品已经被其他人买走了
商家-商品超卖
100 件商品,却出现 200 人下单成功,成功下单买到商品的人数远远超过活动商品数量的上限
商家-资金受损
竞争对手通过恶意下单的方式将活动商品全部下单,导致库存清零,商家无法正常售卖
秒杀器猖獗,黄牛通过秒杀器扫货,商家无法达到营销目的
平台-风险不可控
系统的其它与秒杀活动不相关的模块变得异常缓慢,业务影响面扩散
在线人数创新高,核心链路涉及的上下游服务从前到后都在告警
库存只有一份,所有请求集中读写同一个数据,DB 出现单点
存在的问题
高并发<br>
时间极短、 瞬间用户量大,导致服务器崩溃宕机<br>
超卖
不发货用户投诉你,平台封你店,你发货就血亏<br>
恶意请求
黄牛通过秒杀器扫货
链接暴露
恶意提前刷单
数据库崩溃
每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉<br>
组件吞吐量范围
CDN
数万甚至数十万的并发连接
Nginx
数千到数万并发连接,一般单机能支持上万的请求<br>
HA Proxy
数万到数十万的并发连接
Redis
数万到数十万操作/秒,一般单机能支持四五万
Tomcat
几百到几千个请求/秒,一般说是几百个请求
MySQL
数百到数千查询/秒
RabbitMQ<br>
数千到数万消息/秒
RocketMQ<br>
数万到数十万消息/秒
Kafka<br>
数百兆字节/秒
设计思路
前端<br>
资源静态化
CDN缓存
资源压缩,JS、CSS混淆压缩
秒杀链接加盐
url动态化
path加一段 md5(xxx),并且在秒杀系统可配
前端限流
按钮置灰
秒杀前,提示时间
秒杀点击后,不可重复点击
答题控流,人机识别
Nginx/HA Proxy
服务集群负载均衡
恶意请求拦截(单个用户的多次请求)
可通过Nginx限制同一个IP的访问频率
分控
账号分析,机器人识别,秒杀封禁
后端
单一职责,分布式集群
服务限流&降级&熔断
Redis集群
库存预热
Redis -> decr
系统全局变量,无库存停止秒杀,不需要再写Redis
MQ
异步生成订单,扣减库存
数据库
单独部署
表设计简单
索引
管理问题
其他问题
最近在看的书?有什么心得?
自我介绍
项目复盘<br>
项目价值意义
从业务上讲述这个事情的价值,给用户或者其他业务团队提供了什么
使用技术栈<br>
用到的技术组件,以及为什么要这么用
项目的业务数据
例如用户量、活跃用户量,以及核心业务数据,例如订单系统的下单量等
技术维度的数据
例如接口qps,p99值、平均响应时间等
项目职责
项目难点&亮点
数据迁移,设计模式,数据一致性,抽象,架构升级,服务器迁移,重构,性能优化,可用性,稳定性,提高迭代效率,监控,安全性,扩展性,易用性,压测,cicd,服务治理,分库分表,异地多活秒杀,分布式系统的优化,怎么做管理,怎么沟通,敏捷,技术规划,基础架构,资源调度
项目有什么可以优化的?如果重来可以怎么做?
0 条评论
下一页