后端开发架构方法论-面经持续更新中
2023-01-08 03:18:03 0 举报
AI智能生成
包含“三高”架构、DDD领域驱动思想、微服务架构总结、系统稳定性支撑相关经验、问题排查流程、团队管理方法论等等等等,用于学习、面试等等,帮助大家在寒冬中找到满意的工作。欢迎大家查缺补漏,沟通学习,内容还在持续更新中 某中厂程序员-丈育 写于凌晨3点
作者其他创作
大纲/内容
“三高”设计
稳定性
事前预防
性能优化(缓存(车辆等)
异步化(如商详页面、下单))
压测摸底
应急预案制定(限流降级)
故障演练
各种研发规范流程、预发环境测试、日志统一接入sdk、利用注解、统一格式、慢SQL优化、集成监控报警
异步化(如商详页面、下单))
压测摸底
应急预案制定(限流降级)
故障演练
各种研发规范流程、预发环境测试、日志统一接入sdk、利用注解、统一格式、慢SQL优化、集成监控报警
事中定位处理
快速响应、通知业务、上下游等、第一时间止损,需要有三个角色:通讯员,处理人,决策者,看监控灭火错误日志
事后复盘
复盘,整改优化、经验总结
减少系统非稳定的时间
减少系统恢复所需要的时间
高并发
高性能
集群部署
多级缓存
分库分表索引优化
异步化
限流
削峰填谷MQ
并行处理
预计算
缓存预热
减少IO次数
减少IO数据大小
程序逻辑优化
池化
JVM优化
锁选择(分段锁、乐观锁)
高可用
对等节点的故障转移
非对等节点的故障转移,如MySql主从切换,redis哨兵等
接口层面的幂等、超时、重试策略等
接口层面降级,非核心接口熔断、核心接口有备选链路(针对调用方)
hystrix熔断原理
熔断
时间窗口内某个错误达到某个量并且超过指定比例,熔断器开启(熔断器会开启一定时间 sleepWindows)
熔断恢复
当发生熔断,达到SleepWindows指定时间后,则状态会由OPEN转换为HALF_OPEN,此时只会让一个请求通过,如果该请求执行成功,状态则会转换为CLOSED,否则会回到OPEN状态,并且熔断时间设置为当前时间,重新等待SleepWindows的时间(达到一定时间后,放行一个请求去尝试是否要继续熔断)
限流(对超过接口处理能力的请求直接返回错误码或拒绝请求,针对被调用方)
4种限流算法对比
1. 固定窗口(计数器),缺点很明显,管理粗放,而且临界情况下会超过限流阈值
2. 滑动窗口,窗口越多越平滑,但是窗口越多,算法需要的空间容量越多,且本身还是窗口算法,无法完全解决临界情况下超过限流阈值的情况
可以通过redis的zset实现
score是记录请求的时间戳,利用zremrangeByScore删除(当前时间-窗口)时刻之前的数据,
利用Zcard(统计key对应队列元素数量)判断当前时间所在窗口的请求数来进行限流
(窗口=队列长度)
由于要统计每个请求,在限流阈值较高的情况,所占容量会很大
限定 60s 内操作不得超过 100w 次这样的参数,它是不适合做这样的限流的,因为会消耗大量的存储空间。
score是记录请求的时间戳,利用zremrangeByScore删除(当前时间-窗口)时刻之前的数据,
利用Zcard(统计key对应队列元素数量)判断当前时间所在窗口的请求数来进行限流
(窗口=队列长度)
由于要统计每个请求,在限流阈值较高的情况,所占容量会很大
限定 60s 内操作不得超过 100w 次这样的参数,它是不适合做这样的限流的,因为会消耗大量的存储空间。
sentinel实现:
通过环形数组实现,环形数组可以代表一分钟或者一秒钟,通过当前时间戳取模定位到数组元素,即在环中,属于第几个窗口,窗口元素保存当前窗口请求数,错误数,窗口起始时间等数据;窗口过期则数据重置,窗口实例本身是复用的,减少ygc压力。
举个例子,我们需求统计1min内的数据,可以申请size=60的数组,每个元素代表1秒内的统计窗口。我们拿到当前的时间戳,1577017699235,去掉毫秒数得1577017699,对60取模得19,取数组中index=19的窗口,获取WindowWrap的windowStart,如果windowStart=1577017699000,取直接使用当前窗口统计数据,否则,说明此窗口已过期,重置WindowWrap对象的值,新窗口统计。
通过环形数组实现,环形数组可以代表一分钟或者一秒钟,通过当前时间戳取模定位到数组元素,即在环中,属于第几个窗口,窗口元素保存当前窗口请求数,错误数,窗口起始时间等数据;窗口过期则数据重置,窗口实例本身是复用的,减少ygc压力。
举个例子,我们需求统计1min内的数据,可以申请size=60的数组,每个元素代表1秒内的统计窗口。我们拿到当前的时间戳,1577017699235,去掉毫秒数得1577017699,对60取模得19,取数组中index=19的窗口,获取WindowWrap的windowStart,如果windowStart=1577017699000,取直接使用当前窗口统计数据,否则,说明此窗口已过期,重置WindowWrap对象的值,新窗口统计。
3. 漏桶,流量经过漏桶后速度恒定(实现可以是请求存到队列排队,然后消费队列的速度恒定,队列满了直接拒绝请求),无法应对突发流量,适合性能较弱服务
4. 令牌桶,可以通过控制令牌发放速度应对突发流量,压榨机器,适合性能较强服务
MQ场景的可靠性保证,Producer的重试机制,Broker的持久化机制,Consumer的ACK机制
灰度发布,支持按机器维度小流量发布,观察日志后平稳后全量上线
监控报警:全方位的监控体系,基本的如CPU、内存、磁盘、网络,另外像JVM、中间件、数据库等监控及业务指标的监控
业务指标监控怎么做?
日志接入监控平台,监控平台根据正则统计,例如10秒周期内下单成功数,失败数等等
灾备演练,类似当前的“混沌工程”,对系统进行一些破坏性手段,观察局部故障是否会引起可用性问题。
高可用的方案主要从冗余、取舍、系统运维3个方向考虑,同时需要有配套的值班机制和故障处理流程,当出现线上问题时,可及时跟进处理。
高扩展
合理的分层架构
但是要平衡服务多了之后的性能问题(网络上多了一跳)
存储层的拆分(分库分表)
业务层的拆分
业务流程分,如电商场景的商品服务、订单服务这种
按核心、非核心接口拆分
按请求源,如ToC、ToB或App、H5这样分
性能指标
平均响应时间
TP90 TP99等
吞吐量
问题排查
CPU Load/使用率 过高
概念:先明确load和使用率的概念和关系
OOM
响应慢(FULL GC)
项目开发流程
理流程
设计整体流程
定单据
确定实体和状态流转
填功能
增删改查
开发规范
接口定义规范
1. 命名 get getList add update等等
2. 请求方式 get查post更新delete删除等等
3. 参数
公共参数(设备、网络、ticket等)
敏感参数加密(非对称)
敏感参数加密(非对称)
4. 返回
结构统一,如:
业务状态码
错误提示
数据
状态信息等
业务状态码
错误提示
数据
状态信息等
5. 幂等
6. 签名
开发流程规范
1. 需求评审前
项目管理
Scrum敏捷开发
团队管理
规模
TWO PIZZA(两个披萨原则)
如果两个披萨喂不饱一个团队,那么这个团队就太大了,6-10人
研发团队存在的问题
1. 难以kpi化与考核的工作
代码数?bug数?加班时长?营收? 这些指标都不合理
2. 离代码很近,离业务很远
研发没有去深入业务贴近业务的动力,导致做出来的项目没有贴合业务场景
3. 跨部门合作(斗争?)
包括产研关系,业务和研发的关系。业务认为研发bug多、不给力、能力差、排期长;研发认为业务智商跟不上、业务有提成自己没有等等。导致目标不统一,团队间的内耗
理解SaaS
第一步:企业画像:理清使用你SaaS产品的核心企业画像。例如所处行业、企业规模、地域等。
第二步:组织架构:识别企业中的关键角色及其职责,了解组织架构是一个比较快捷的方法。
第三步:核心业务流程:梳理出企业的核心业务流程。前面识别出的角色和人物就像一个个的点,流程会讲这些人错综复杂的关系联系起来。
第四步:诊断和定位企业的核心需求和问题,这是真正开始发挥SaaS价值的第一步
第五步:针对问题提出对应有效的解决方案,并进行可行性验证
第二步:组织架构:识别企业中的关键角色及其职责,了解组织架构是一个比较快捷的方法。
第三步:核心业务流程:梳理出企业的核心业务流程。前面识别出的角色和人物就像一个个的点,流程会讲这些人错综复杂的关系联系起来。
第四步:诊断和定位企业的核心需求和问题,这是真正开始发挥SaaS价值的第一步
第五步:针对问题提出对应有效的解决方案,并进行可行性验证
架构师技能要求
技术要求
架构决策能力
架构持续分析优化
前沿技术更新同步
技术知识广泛涉猎
非技术要求
企业合规性考虑
业务领域知识
人际交往能力
政治敏感性
架构决策能力
架构持续分析优化
前沿技术更新同步
技术知识广泛涉猎
非技术要求
企业合规性考虑
业务领域知识
人际交往能力
政治敏感性
分布式场景
CAP原则
C
系统更新后,后续的所有用户读到的都是最新的数据
A
每个操作总是能在一定时间内返回结果
P
是否可以对数据分区,这是考虑到性能和可伸缩性
矛盾
CP
出现分区且要保证一致性,必然设计节点间通信,无法保证一定时间内返回,所以无法保证A
AP
要保证一定时间内返回,那么节点间的通信就无法保证完整,所以无法保证C
CA
需要在一定时间内返回,那么需要一定时间内保护数据,那么只能使用单点可以保证这一点,失去P
BASE理论
Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写
一致性
coherence 缓存一致,指的是多核场景下如何保证多核缓存一致,一般是单机维度,不涉及分布式
consensus 共识,即多个提议者达成共识的过程,例如PAXOS,RAFT都是共识算法,没有达到一致,但是是很多一致性协议的基础
复制状态机
复制状态机是一种很有效的容错技术,基于复制日志来实现,每个 Server 存储着一份包含命令序列的日志文件,状态机会按顺序执行这些命令。因为日志中的命令和顺序都相同,因此所有节点会得到相同的数据。
因此保证系统一致性就简化为保证操作日志的一致,这种复制日志的方式被大量运用,如 GSF、HDFS、ZooKeeper和 etcd 都是这种机制
因此保证系统一致性就简化为保证操作日志的一致,这种复制日志的方式被大量运用,如 GSF、HDFS、ZooKeeper和 etcd 都是这种机制
区块链
30种共识算法 https://www.infoq.cn/article/consensuspedia-an-encyclopedia-of-29-consensus-algorithms
consistency 一致性
强一致
线性一致性(原子性)
顺序一致性
弱一致
最终一致性五类变种
1. 因果一致性
2. 读己之所写一致性
3. 会话一致性
4. 单调读一致性
5. 单调写一致性
一致性协议
PAXOS
共识算法,能够让分布式网络中的节点在出现错误时仍然保持一致,当然前提是没有恶意节点,也就是拜占庭将军问题
但在传统的分布式系统中不用担心这个问题的,因为不论是分布式数据库、消息队列、分布式存储,你的机器都不会故意发送错误信息,最常见的问题反而是节点失去响应,所以它们在这种前提下,Paxos是足够用的。
但在传统的分布式系统中不用担心这个问题的,因为不论是分布式数据库、消息队列、分布式存储,你的机器都不会故意发送错误信息,最常见的问题反而是节点失去响应,所以它们在这种前提下,Paxos是足够用的。
ZAB
RAFT
和ZAB一样都是强一致协议,和ZAB最大的区别是ZAB的Leader收到写请求会等待半数Followers执行完才返回;
而RAFT只需要把执行Log复制给Followers,只要半数Followers收到Log就返回
而RAFT只需要把执行Log复制给Followers,只要半数Followers收到Log就返回
解决方案
2PC
3PC
TCC
事务消息
事务和发消息放在同一个事务
本地消息表
发消息改为插入数据库记录,单独的线程轮询表来发送。然后事务和插入消息表放一个事务
缺点:业务需增加一个业务无关的表,高耦合,占用数据库资源
MQ事务消息
流程
1. 发送半消息
2. 发送成功,执行事务
3. 成功失败告知mq
4. 没有结果告知mq的情况下,mq本身会轮询半消息,查业务的回调接口
5.若状态是提交,则投递消息;回滚则不提交
只是保证通知和事务的一致,无法保证多方事务一致
SEATA
AT
机制:两阶段提交的演变
阶段1. 业务数据和解析出来的回滚日志放到同一个事务,在一阶段提交,释放本地锁和连接资源,一阶段提交前需要获取全局锁,否则不能提交
阶段2. 提交异步化,快速完成;
回滚通过一阶段的回滚日志完成。
阶段1. 业务数据和解析出来的回滚日志放到同一个事务,在一阶段提交,释放本地锁和连接资源,一阶段提交前需要获取全局锁,否则不能提交
阶段2. 提交异步化,快速完成;
回滚通过一阶段的回滚日志完成。
写隔离通过全局锁
读隔离默认是读未提交,可以通过读锁(select for update达到读已提交,阻塞其他事务获取锁)
读隔离默认是读未提交,可以通过读锁(select for update达到读已提交,阻塞其他事务获取锁)
TCC
属于两阶段,只是把commit和rollback的动作交给业务自定义
SAGA
适用场景:流程长,参与者包含其他老系统,无法提供tcc要求的三个接口
优点:高性能,无锁,一阶段提交本地事务,高吞吐
缺点:不保证隔离性,主要是脏写的场景,发生脏写后无法回滚,例如,在事务中给A充值给B扣款,如果A充值成功,B事务提交前,A把钱消费了,那么就无法回滚了,业务前回不来了。需要通过业务去规避,要么遵循宁可长款,不可短款,事务总是优先扣款,发生回滚可以通过平台退款;要么通过向前回滚(重试扣除B的钱)达到最终一致
XA
利用数据源本身的XA协议支持
自研实现逻辑 仿Saga流程
流程
1. 初始化时,注册@BranchTransactional注解下的方法,保存在concurrentHashMap中 [methodName, methodRef],用于后续分支事务补偿时根据方法名反射调用
2. 事务开始时通过@GlobalTransactional开始全局事务,根据参数中的标记的唯一ID作为分布式锁的KEY,保证隔离性,如订单开单场景,这样其他线程就无法操作该订单
3. 接下来保证原子性,利用Saga T1->T2->T3[IF Error]... C3->C2->C1
代码设计
1. 利用注解+AOP,@GlobalTransactional @BranchTransactional
2. Spring初始化时,实现BeanPostProcessor,针对 @BranchTransactional,将方法注册到HashMap中
2. Spring初始化时,实现BeanPostProcessor,针对 @BranchTransactional,将方法注册到HashMap中
数据库设计
事务、子事务保存到数据库,作为Saga Log
分布式存在的问题
NPC
Network Delay
Process Pause
Clock Drift
分布式事务主要问题在NP
空补偿
原服务未执行,补偿服务先执行,原服务请求丢失
解决:业务需要允许空补偿,返回成功,因为这种基本重试也没用
悬挂
补偿服务比原服务先执行,原服务来晚了
解决:业务应记录补偿日志,执行原服务时检查
设计模式
6大原则
单一职责
开闭原则
李氏替换
迪米特法则(最少知道)
接口隔离
依赖倒置
23种模式
创建型
单例
原型
抽象工厂
建造者 ✅
builder
结构型
代理
适配器 ✅
对接外部系统
桥接
装饰
外观
享元
组合
行为型
策略 ✅
减少if/else的代码,可以把适配方法放在策略中,通过遍历策略通过上下文匹配出对应的策略
命令
责任链 ✅
例如 登录校验,分为基础登录非空校验、门店权限校验、获取角色数据 等handler
状态 ✅
动作封装在状态里
观察者
中介者
迭代器
访问者
备忘录
模板 ✅
例如 结算,不同的结算方共同的步骤,1. 数据校验 2. 创建购物车(默认实现)3. 结算方结算单 4. 结算后置处理方法(发消息等)
系统设计
秒杀系统
隔离
业务隔离
做成营销活动,比如可以提前报名,那么开发就可以提前知道用户量,做好准备和系统预热
系统隔离
运行时隔离,模块部署单独的集群
数据隔离
启动单独的cache和db存放热点数据,目的也是不想0.01%的数据影响另外99.99%
削峰
通过增加验证码或者答题等,把峰值的下单请求给拉长了,从以前的1s之内延长到2~10秒左右
后端处理时,可以通过
a. 线程池等待
b. 自定义队列存放 x
c. 请求序列化到数据库或者mq x
后端处理时,可以通过
a. 线程池等待
b. 自定义队列存放 x
c. 请求序列化到数据库或者mq x
动静分离
CDN缓存静态内容
分层校验
CDN
读逻辑
如用户资质、商品状态、秒杀状态等校验
热点可以利用本地缓存做二级缓存,过期时间可以比较短,使其被动失效,读脏数据这里没有大问题,数据库会做一致性校验,不会超卖
热点可以利用本地缓存做二级缓存,过期时间可以比较短,使其被动失效,读脏数据这里没有大问题,数据库会做一致性校验,不会超卖
写逻辑
库存校验
这里可以全部放在缓存,如库存校验和扣库存,后台定时任务刷db
这里可以全部放在缓存,如库存校验和扣库存,后台定时任务刷db
DB
不能超售
实时热点分析
采集后可以发mq等让下游做好准备,下游可以准备、预热缓存
限流、降级、熔断(兜底)
超卖(兜底)
1. 超售
a. 数据库控制 update auction_auctions set
quantity = quantity-#count#
where auction_id = #itemId# and quantity >= #count#
b. 先查缓存中商品数量
2. 同一个用户多次秒杀到同一个商品
a. 数据库方案:商品id+用户id唯一索引(秒杀商品少,写入的频率低)
b. 一般采用缓存方案:把用户的购买记录也存缓存 如hash结构: key : bizId+itemId filed : uid
3, 扣库存时机
a. 下单减库存(一般采用这种,可能存在用户不付款的问题,可以设计超时机制)
b. 付款减库存(用户体验影响,付款时可能没库存了)
a. 数据库控制 update auction_auctions set
quantity = quantity-#count#
where auction_id = #itemId# and quantity >= #count#
b. 先查缓存中商品数量
2. 同一个用户多次秒杀到同一个商品
a. 数据库方案:商品id+用户id唯一索引(秒杀商品少,写入的频率低)
b. 一般采用缓存方案:把用户的购买记录也存缓存 如hash结构: key : bizId+itemId filed : uid
3, 扣库存时机
a. 下单减库存(一般采用这种,可能存在用户不付款的问题,可以设计超时机制)
b. 付款减库存(用户体验影响,付款时可能没库存了)
安全
防提前刷
链接参数加入随机数,前端轮询接口,秒杀开始时才返回
唯一ID生成系统
12306购票系统
一次浏览器访问URL的过程
多层级组织架构表设计
一般父子结构表设计存在的问题是,多层的查询没法一下查询出来所有的叶子,需要多次递归;
一个解决方案是增加一个链接字段(如下图level,含义是从根节点到当前节点父节点的所有节点的拼接)通过like 'XX%',可以跨层级查询到叶子,不需要递归
一个解决方案是增加一个链接字段(如下图level,含义是从根节点到当前节点父节点的所有节点的拼接)通过like 'XX%',可以跨层级查询到叶子,不需要递归
子主题
重构
如何有效、正确执行?
1. 明确重构的目标
重构是有代价的,引入风险(BUG),投入资源(业务推进放缓);
重构首要目的一定是能够推进业务发展的,然后才是性能等问题
重构首要目的一定是能够推进业务发展的,然后才是性能等问题
2.明确当前系统状态
吃透代码和架构的情况下才可以重构,最好有之前了解业务的同事,比只看代码好,能够了解设计的初衷
3. 重构的目标需要被量化
列出重构的要点(范围or边界),确定PRD,在涉及的团队中达成一致,包括上下游,前端,测试
4. 重构中必须建立或维护数据流
通过log的形式建立业务流转记录,通过埋点或者手动等,目的
1. 能看出对于数据的处理,存储是否有影响
2. 重构中或重构后能通过数据验证效果,后续可以不断优化
1. 能看出对于数据的处理,存储是否有影响
2. 重构中或重构后能通过数据验证效果,后续可以不断优化
5. 采用迭代式重构
将一次大的重构拆分拆多次迭代上线,小迭代的风险比较可控,每次重构不应该超过一个正常的迭代周期(2周),
并且重构过程对bug的容忍度比较低,上一次重构的结果影响下一次重构迭代的进行
并且重构过程对bug的容忍度比较低,上一次重构的结果影响下一次重构迭代的进行
6. 重构首选团队熟悉的技术
7. 重构前务必和业务方沟通
【携程】缓存最终一致和强一致方案总结
最终一致
1. 多源触发,查询db最新数据发送MQ到缓存组件,触发缓存更新
缓存消费MQ,查缓存数据,若不一致(可以对比更新时间,然后对比数据内容)则更新
缓存消费MQ,查缓存数据,若不一致(可以对比更新时间,然后对比数据内容)则更新
1. 定时任务扫表
2. 业务主动发MQ
3. 业务数据库binlog
2. 并发控制,解决并发多次更新的情况下,多个请求更新db和更新cache顺序不确定的问题
触发源A old val = 1 diff cache update 1
触发源B old val = 2 diff cache update 2
以上出现不一致
那么可以把这diff和update加分布式锁或lua来保证两步的原子性
触发源A old val = 1 diff cache update 1
触发源B old val = 2 diff cache update 2
以上出现不一致
那么可以把这diff和update加分布式锁或lua来保证两步的原子性
强一致
0 条评论
下一页