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