2025-04-19-分布式事务
以下是从上述内容中抽取的重要QA对:
1. 项目相关 Q: 自我介绍 A: 权限中台,主要业务是文档,XX的IM,实现了RABC,ABAC,ACL等经典的权限模型。提供鉴权模型的授权和统一鉴权能力和组合鉴权的接口。还做性能优化和稳定性治理相关工作,以及组织架构调整。
Q: 项目里面最有挑战性的是哪一块? A: 存储业务,核心的文档业务,也有admin的非核心文档业务。按文档id做分库,wiki、文件夹等相对少一点的做了分表,普通业务使用单表。存储杂,数据库做主从拆分,有主从延迟问题,缓存可能数据不一致,做了较多解决方案。
Q: 分表是你做的吗? A: 不是,但有了解实现过,也做过相关调研,分片库有做简单参与。
Q: 分表是按2的n次幂,为什么? A: 方便做数据的迁移,64张分表基本够用,之后没再改过。
2. 分片键与数据冗余 Q: 针对分片键的选择有什么考虑吗? A: 结合业务,如给文档鉴定权限时用文档id做分片键,可使每次业务的主要鉴权请求都在同一个分片,避免分布式事务性能问题。但因应用有基于用户维度查询的诉求(如用户离职、升职转移场景),仅文档分片键不够,所以做了数据冗余,冗余主体维度的分片表来解决问题。
Q: 这里数据冗余是如何写入的,有没有分布式事务? A: 没有分布式事务。文档影响鉴权的表是主表,监听主表的binlog,然后延时消费,做二次确认。
Q: binlog去消费,如何保证消费的消息不会丢失呢? A: 基建帮忙做组件导入到MQ(RocketMQ)里,MQ有消息防丢机制,极端情况主从全挂且磁盘出问题可能有少量丢失。MQ里有消息确认机制,可开启ACK,消费者确认消费成功再手动提交。
3. RocketMQ相关 Q: RocketMQ从他的架构设计上来讲,如何保证消息不丢? A: 发送消息有确认和重试机制;发送成功后网络传输基于TCP有重试保障;消息落盘有同步和异步两种方式,同步性能差但不丢消息更好,项目因考虑性能开的异步,且MQ有主从结构,主节点挂了从库能保证消息不丢;消费时有ACK机制保证消费不丢。
Q: 同步能保证消费不丢失,为什么要改成异步的呢? A: 为保证性能,可以一批之后再刷盘,减少系统调用开销。
Q: RocketMQ的消息消费是不是一定要做幂等设计? A: 认为是要的,因为网络波动可能导致消息重发,消费者接收多条。通过数据库唯一键或分布式锁保证幂等比较简单。
4. 项目难点与设计 Q: 你认为项目的最大的难点在哪里? A: 权限本身复杂,接入方多,要保证灵活性和通用性。如文档鉴权中,要支持统一的权限实体扩散,将具体人和授予的日程、群组关联起来,有前置消息扩散和后置消息判断两种思路,因主体和接入方的情况,做了统一的权限扩散接口,新业务按协议提供关系判断接口,利用泛化调用和lua脚本做业务判断和属性校验。
Q: 什么是泛化调用? A: 会基于上传的服务request response和接口信息自动生成Client,每个业务有唯一namespace id,通过查names ID找到关联的Client,主动调用对应业务方方法进行RPC调用获取关联信息,可做到配置化的实体扩散,还会起lua虚拟机,用lua脚本写配置信息做权限过滤。
Q: 为什么要用lua脚本? A: 一开始用EL表达式,后变成lua脚本,提供开发者后台,业务可自己写lua信息,系统解析,避免业务每次提供特殊过滤条件,减少开发配合编码工作。
5. 技术栈相关 Q: 你们现在技术栈用的主要是Golang吗? A: 对
Q: java和golang的区别? A: 编译上,java有Jvm,可通过不同平台的jvm解析字节码文件实现平台兼容,go在编译时直接指定编译平台;多线程方面,java有线程池,jdk有更轻量的虚拟线程(几个虚拟线程和内核线程几对一关系),go有更轻量的协程;并发方面,java用各种锁,golang用channel;golang语法更简单,没有抽象类,泛型基本不用但支持。
Q: 那Golang它如何做垃圾回收的,内存管理? A: 细节记不清,应该也是可达性分析、计数器,估计没有新生代这些。
6. 面向对象设计与接口拆分 Q: 了解过面向对象的一些设计吗? A: 开闭、单一、里氏替换
Q: 开闭原则是什么意思? A: 隐藏内部信息,不频繁更改内部事件,通过重写方法扩展功能,而非频繁更改。
Q: 比如一个订单接口方法很多,要做拆分优化,会按哪些维度? A: 按职责划分,若提供方明确,可按提供方梳理;若提供方杂且不断拓展,按功能维度划分,如报表功能、消费功能等,每新增功能新开功能或文件维护。
7. 系统架构与缓存 Q: 你们的系统一共拆了几个子系统? A: 比较少,一个鉴权服务,一个授权服务,元数据服务。授权服务负责规则的CRUD;鉴权服务有组合鉴权、单一鉴权、跨人鉴权、数据权限等接口。
Q: 系统请求量很大吗? A: 30万qps左右的峰值。
Q: 什么是多级缓存? A: 大概有四层,结构缓存(如用户查看文档的上级鉴权结果)、中间结构缓存(上次鉴权最终命中的规则ID)、规则缓存(文档对应的规则集)、本地元数据local cache 。
Q: Redis和本地都有做缓存,如何保证数据的一致性,数据更新后怎么同步? A: 本地缓存元数据,变更低频,60秒左右定时拉取保证更新;Redis缓存做规则变更时主动延迟双删保证失效,对敏感业务在文档中设最近时间戳,鉴权时若缓存时间戳小于更新时间戳则认为缓存失效读DB。
Q: 对比时间戳会不会有缓存击穿风险,如何优化? A: 没做过防范,确实可能有。可以用golang的single flight功能保证同样请求只请求一次,生成唯一请求key存储在redis里。
8. 数据库相关 Q: 为什么不先删缓存再更新数据库? A: 先删缓存,若此时另一数据库请求进来未命中,会读到脏数据,第一个线程才开始更新数据。
Q: 如果更新数据库网络抖动超时怎么处理? A: 有延迟双删,两次删除失败会落库记录,有定时任务不断尝试删除成功。
Q: binlog如何保证有序? A: binlog有两种情况,statment保留语言(包括查询),row保留影响的行,项目基本开的是保留影响行,MySQL本身不是单线程,记得binlog是有序的。
9. 分布式事务相关 Q: 分布式事务里面事务的一致性有解决过吗? A: 有,用RocketMQ的事务消息,如统一接入权限业务的多个业务方,审计敏感权限时,发消息和记录本地审计任务涉及分布式事务一致性。先发半消息,记录本地事务,失败回滚,成功提交半消息,有callback保证本地事务超时等情况的回滚。
Q: 如果一直没有callback怎么办? A: callback由RocketMQ保证,半消息不提交会回调,实现callback接口后,RocketMQ会回查确认本地消息和任务情况。
Q: 如果本地事务比较长怎么办? A: 项目不存在此问题,callback的topic可配置延时(好像6秒),不是立马回查。若有大事务,应优先规避,若无法优化,考虑调长回查时间,或本地做兼容处理,如结合业务判断是否正在执行,正在执行则先返回,稍后再查。
Q: 选择用事务消息而不是其他分布式事务解决方案的原因? A: 本身有很多消息要跟业务方解耦,很多业务方不需要监听审计事件,有发消息需求,结合事务消息顺理成章。
10. 其他 Q: http反应状态码304什么意思? A: 3开头一般是重定向,304是临时重定向(回答有误,304是未修改,协商缓存生效,资源未重新请求),302是永久。
Q: 接口数据量大导致IO压力大,如何优化? A: 最简单的是加一些维度的缓存。
Q: A服务调B服务带宽压力大,业务不能融合怎么办? A: B提供接口标识数据是否变更,A先查询,有变更才调。但查从库有延时,查主库读请求压力大。
Q: 什么是深分页,如何解决? A: 深分页如select * from limit 10w, 100 ,mysql会回表完100万数据再查字段。简单解决方法是通过id取出10万后的100个id,通过子查询。
Q: select语句会加锁吗? A: 不是for update应该是无锁的,可能会有意向锁防止select之间语句变更,具体忘了。
Q: 什么情况下要加for update? A: 防止查询之间有人对这行数据进行变更。
