Java
JVM如何管理内存的?
Java中内存管理是JVM自动进行的,创建对象或者变量时JVM会自动分配内存,当JVM发现某些对象不再需要的时候,就会对该对象占用的内存进行释放操作,而且使得分配出来的内存能够提供给所需要的对象。
Java基础自动拆装箱如何实现?
拆箱就是把引用数据类型拆成基本数据类型,装箱是把基本数据类型包装成引用数据类型;基本数据类型不需要new,直接存储在栈中,更加高效;包装类型就是让基本数据类型的变量具有类中对象的特征,有些地方需要对象,如集合中;
HashMap为什么要扩容为2倍?HashMap中的链表替换为数组可以吗?扩容因子改大改小有什么影响?
扩容必须2倍是为了尽可能的解决哈希冲突,扩容2倍保证了容量始终是2的n次方,向集合中添加元素时,会使用(n - 1)&hash的计算方法来得出该元素在集合中的位置,n-1的二进制会全为1,位运算时可以充分散列,避免不必要的哈希冲突;
数组查找遍历快,链表删除,插入快,结合了这两者的优点,采用拉链法解决哈希冲突;
扩容因子越大,碰撞的概率也就越大,发生碰撞后的代价也更大,结果导致效率大打折扣;扩容因子越小,数组元素就越稀疏,会浪费一定的空间;因此扩容因子=0.75也是一个空间换时间的考虑;
GC停顿原因,如何降低停顿
GC任务是识别和回收垃圾对象进行内存清理,为了让GC可以高效的执行,在进行GC时系统会进入一个停顿的状态,终止所有应用线程,只有这样系统才不会有新的垃圾产生,停顿保证了系统状态在某一瞬间的一致性,有益于更好的标记垃圾对象;减少GC停顿时间:合理设置内存大小、选择合适的GC算法、调整GC线程数、减少机器的负载,避免IO过重和Swap内存交换;
HashMap和Hashtable的区别
1)继承的父类不同,HashMap继承AbstractMap类,Hashtable继承Dictionary类;2)Hashtable的方法使用Synchronize保证线程安全,HashMap线程不安全;3)Hashtable的Key和Value都不允许为Null,HashMap都允许;4)哈希值的使用不同,HashTable直接使用对象的hashCode(key.hashCode() & 0x7FFFFFFF)% table.length,而HashMap重新计算hash值((key.hashCode() ^ (key.hashCode() >>> 16)) & (table.length - 1));5)Hashtable默认大小为11(通过质数求余分散hash),容量不要求2次幂,扩容为原大小*2+1,HashMap默认大小16(采用2次Hash分散hash)。容量要求2次幂,扩容为原大小*2;
ArrayList和LinkedList的区别
1)ArrayList是基于动态数组实现,默认容量0,第一次调用add时才初始化容量为10,每次扩容1.5倍,如果扩大1.5倍依然不满足需求,则扩大为满足需求的最小容量,随机访问快,插入删除慢,LinkedList是基于双链表实现,随机访问慢,插入删除快;2)LinkedList不仅实现了List接口,还实现了Deque接口,可作为双向对列,栈和List集合使用,功能强大;3)LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的是实际的数据和前后节点的位置;
CMS和G1垃圾收集器工作原理
CMS:1)初始标记,标记GC Roots能直接关联到的对象,速度很快,需要Stop The World;2)并发标记,进行GC Roots Tracing的过程,耗时最长;3)重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,比初始标记阶段稍长但远比并发标记的时间短,需要Stop The World;4)并发清除;
G1:将整个Java堆划分为多个大小相等的独立区域Region,虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分Region(不需要连续)的集合,整体使用标记整理,局部使用复制算法;1)初始标记,标记GC Roots能直接关联到的对象,速度很快,需要Stop The World;2)并发标记,从GC Root 开始对堆中对象进行可达性分析,耗时较长,可与用户程序并发执行;3)最终标记,修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机会记录这段时间内的对象变化;4)筛选回收,对各个Region中的回收价值和成本进行排序,根据用户所期望的GC停顿是时间来制定回收计划,只回收一部分Region,时间是用户可控制的;
Java的强软弱虚四种引用
强引用,一般new出来的对象都是强引用,GC不会回收强引用的对象;软引用:软引用的对象不那么重要,当内存不足时可以被回收,非常适合于创建缓存;弱引用:只是引用一个对象,若一个对象的所有引用都是弱引用的话,下次GC会回收该对象,一般用在集合类中,特别是哈希表;虚引用:一般用于对实现比较精细的内存使用控制,对于移动设备来说比较有意义;
线程间通信方式
线程间主要通过共享内存和消息传递的方式进行通信。包括synchronized、wait/notify、PipedInputStream管道通信、共享变量、Lock的condition、volatile、CountDownLatch、LockSupport等;
long类型的赋值是否是原子的
Java对long和double的赋值操作是非原子操作,long和double占用的字节数都是8也就是64bits,在32位操作系统上对64位的数据的读写要分两步完成,每一步取32位数据,这样对double和long的赋值操作就会有问题:如果有两个线程同时写一个变量内存,一个进程写低32位,而另一个写高32位,这样将导致获取的64位数据是失效的数据。因此需要使用volatile关键字来防止此类现象
数据库
怎么设计数据库表
第一范式:字段不可分;第二范式:非主属性必须完全函数依赖于主属性;第三范式:消除了第二范式中的传递函数依赖;第四范式:要求把同一表内的多对多关系删除;第五范式:从最终结构重新建立原始结构;BC范式:符合3NF,并且主属性不依赖于主属性;理论上说达到第三范式是符合要求的但是一般生产环境下为了数据查询方便,数据会有一定的冗余,也就是说一般达到第二范式即可。
MySQL使用UUID作为主键的问题
1)InnoDB采用B+树组织索引,在B+树中所有记录节点都是按键值的大小顺序存放在同一层的叶节点中,各叶节点指针进行连接,UUID无序,会导致B+树索引在写的时候有过多的随机写操作,InnoDB会产生巨大的IO压力;2)UUID虽然能够保证ID的唯一性,但是无法满足业务系统需要的很多其他特性,例如:时间粗略有序性、可反解和可制造性;3)UUID比较长,会占用空间大,间接导致数据库性能下降;4)在写的时候不能产生有序的append操作,而需要进行insert操作,将读取整个B+树节点到内存,在插入这条记录后会将整个节点写回磁盘,这种操作在记录占用空间比较大的情况下,性能会明显下降;
中间件
聊聊Dubbo框架
Dubbo是阿里巴巴开源的RPC服务框架,2018年初加入Apache孵化器,2019年成为Apache的顶级项目;Dubbo的服务注册中心为Zookeerper,服务监控中心为dubbo-monitor,无消息总线、服务跟踪、批量任务等组件,内置了Spring、Jetty、Log4j服务容器,支持的协议包括dubbo、http、rest、redis、memcached,节点角色包括:provide(暴露服务的服务提供方)、consumer(调用远程服务的服务消费方)、registry(服务注册于发现的注册中心)、monitor(统计服务调用次数和调用时间的监控中心)、container(服务运行容器Tomcat);特点:面向接口代理的高性能RPC调用、智能负载均衡、服务自动注册与发现、高度可拓展能力、运行期流量调度、可视化的服务治理和运维;
服务调用流程:0.服务容器负责启动,加载,运行服务提供者。1.服务提供者在启动时,向注册中心注册自己提供的服务。2.服务消费者在启动时,向注册中心订阅自己所需的服务。3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
聊聊OSGI
OSGI是JAVA动态模块系统,定义了一套模块应用开发的框架,通过OSGI可以动态加载、更新和卸载模块而不用停止服务,实现系统的模块化、热插拔,允许模块相互依赖但松耦合,分享服务更简单;OSGI中组件被称为Bundle,由一些提供Bundle自身功能的资源(如Java类文件、配置文件等)、MANIFEST.MF文件和其它资源文件构成,在运行时环境中,每个Bundle都有一个独立的Class Loader,Bundle 之间通过不同的类加载器和在MANIFEST.MF文件中定义的包约束条件就可以轻松实现包级别的资源隐藏和共享,从而实现基于组件方式的开发和运行。
ApplicationContext初始化过程
获取ApplicationContext的方式:1)通过WebApplicationUtils工具类获取;2)通过ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、XmlWebApplicationContext类获取;3)自定义工具类实现Spring的ApplicationContextAware接口;主要调用了AbstractApplicationContext的refresh方法完成初始化,加载配置文件,解析并初始化bean,执行BeanPostProcessor,检查监听bean注册,发布事件;
如何处理MQ重复消费
消费端处理消息的业务逻辑保持幂等性,比如消费消息Insert数据库操作给消息ID做唯一主键、Redis的set操作天然幂等、或者存储消息消费记录,每次消费判断去重;
HDFS高可用实现
主备NameNode机制:1)一台主NameNode一台备NameNode;2)ZK FailoverController故障转移控制器,定期检测NameNode的运行状态,实现故障切换;3)共享存储系统(JournalNode 同步EditsLog)保存了NameNode在运行过程中所产生的 HDFS的元数据,主NameNode和备NameNode通过共享存储系统实现元数据同步;4)DataNode会同时向主NameNode和备NameNode上报数据块的位置信息,实现数据块和DataNode映射关系的共享;
Dubbo高可用实现
1)注册中心宕机任然可以通过本地缓存调用服务,但不能注册新服务;2)支持根据IP地址的方式直连服务,不通过注册中心;3)Dubbo支持多种负载均衡策略:随机、轮训、最少连接数、一致性Hash;4)支持服务的降级策略配置、服务屏蔽(调用直接返回空)、服务容错(调用失败返回空);5)服务熔断策略配置:失败自动切换(重试其他服务器)、快速失败(一次失败立即报错)、失败安全(异常时忽略)、失败自动恢复(后台记录失败请求定时重发)、并行调用多个服务器(一个成功即返回)、广播调用所有提供者,逐个调用,任意一台报错则报错;
Libevent事件通知库
Libevent是一个用C语言编写的、轻量级的开源高性能事件通知库,特点:事件驱动、高性能、轻量级、专注于网络、源代码精炼易读、跨平台、支持多种操作系统、支持多种I/O多路复用技术(epoll、 poll、dev/poll、select、kqueue)、支持 I/O、定时器和信号等事件;Libevent已经被广泛的应用作为底层的网络库如memcached等;(Redis是自己实现的事件驱动没使用libevent、Libevent之于C相当于Netty之于Java);
主要方法:event_base_new初始化事件框架、event_base_get_method获取使用的事件方法(epoll等)、event_base_dispatch事件循环、event_base_free销毁事件框架;
其他
什么是RESTFUL,和RPC调用有什么区别?
Rest:基于Http实现,侧重资源、耦合性低、兼容性好、开发效率高、跨语言、更规范、更通用、利用了Http本身的特性,但性能较低,适用于对外或者调用频次不是很高的服务;RPC:基于Socket+接口代理实现,侧重于动作,调用简单(本地调用)、性能高、延迟低、耦合性强、接口版本管理复杂、无法跨语言,适用于内部、IO密级型或者调用频次高的服务;
开放式问题。如何设计一个rpc框架。
RPC框架的核心目标是解决分布式系统中服务之间的调用问题,包含3个角色:服务提供者(提供服务,注册到注册中心)、服务消费者(从注册中心获取服务信息)、注册中心;
RPC远程调用过程:1)服务调用方以本地调用方式调用服务;2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;即序列化;3)client stub找到服务地址,并将消息通过网络发送到服务端;4)server stub收到消息后进行解码,即反序列化;5)server stub根据解码结果调用本地的服务;6)本地服务执行处理逻辑;7)本地服务将结果返回给server stub;8)server stub将返回结果打包成消息,Java里的序列化;9)server stub将打包后的消息通过网络并发送至消费方;10)client stub接收到消息,并进行解码, Java里的反序列化;11)服务调用方得到最终结果。
RPC框架涉及技术:1)建立通信,即客户端和服务端建立TCP连接,可使用Netty作为底层通信框架;2)网络传输,数据的序列化和反序列化;3)服务寻址和服务调用,包括负载均衡、熔断降级等;4)其他包括服务地址缓存、异步调用、线程池执行、版本控制等;
如何进行领域建模?
领域模型是对领域内的概念类或现实世界中对象的可视化表示,领域建模就是对系统所要解决的问题域的问题的归纳、分析的过程;通过建立领域模型能够从现实的问题域中找到最有代表性的概念对象,并发现其中的类和类之间的关系,因为所捕捉出的类是反馈问题域本质内容的信息;
领域类包括:边界对象,负责系统参与者和系统内部的交流,将系统内部和外部事物隔离保护起来,主要责任包括输入、输出、过滤;实体对象,代表存储的信息,作为业务行为的载体;控制对象,协调类间的工作,控制事件流,为实体类分配责任;三种实体类之间的约束:参与者只能和边界类交互,边界类只能和控制类交互,实体类只能和控制类交互,控制类可以和边界类和实体类交互;
领域模型设计的步骤为:1.从业务描述中提取名词;2.从提取出来的名词中总结业务实体,区分名词中的属性、角色、实体、实例,形成问题域中操作实体的集合;3. 从业务实体集合中抽象业务模型,建立问题域的概念;4.用UML提供的方法和图例(可使用类图、对象图)进行领域模型设计、确定模型之间的关系;
可运用四色建模法进行领域分析,用蓝色表示命令,用红色表示实体,用绿色表示领域事件,用黄色表示补充信息;
TCP流量控制和拥塞控制
流量控制是为了避免发送端发送数据太快,接收端来不及接收,可能会丢失数据,通过大小可变的滑动窗口实现的,TCP会话的双方都各自维护一个发送窗口(包括已发送未确认、允许发送未发送)和一个接收窗口(未接受但准备接收),发送端窗口大小不能超过接收端窗口大小的值,TCP窗口单位是字节,在TCP报文中专门有一段字节记录窗口大小;
拥塞控制是为了提高网络利用率,降低丢包率,并保证网络资源对每条数据流的公平性,包括慢启动、拥塞避免、快速重传、快速恢复,第一次发送时不清楚网络状况,所以通过慢启动试探将发送窗口逐渐增大;
如何设计秒杀系统
秒杀系统特点:定时开始,同一时间涌入大量流量;库存有限,下单数量远大于库存;操作可靠,不能超卖,同时要考虑卖出去;
1.减少请求数:客户端按钮点击后可灰化防止反复点击;JS可控制X秒内只发送一次请求;后端可根据账户ID进行计数和去重,保证X秒内只发送一个请求;通过问答滑块等功能避免机器人刷单对服务器的压力;2.使用动静分离提高页面加载速度:秒杀商品生成专门的页面,静态为主,使用CDN缓存,减少没必要的请求落到服务器,缩短请求路径;3.系统隔离:秒杀活动可以有很多,为了不影响正常业务,可以考虑从业务、技术和数据库层面做隔离;4.使用分布式缓存Redis缓存查询数据,避免读请求直接到数据库;同时对请求做限流、熔断和服务降级;5.使用MQ作为写请求的队列,异步处理,使用分布式锁控制对数据库同一行记录进行操作的并发度;数据库考虑分库分表;6.防止超卖,采用预减库存的方式,买家下单后,库存为其保留一定的时间,超过这段时间,库存自动释放,释放后其他买家可以购买;
聊聊Servlet
1)Servlet是一个接口,定义了Java处理网络请求的规范,主要方法包括:init、service、destroy、getServletConfig、getServletInfo;2)生命周期:init、service、destroy;3)工作原理:Servlet容器将Servlet类载入内存,并产生Servlet实例和调用它具体的方法,一个应用程序中每种Servlet类型只能有一个实例,用户请求Servlet容器调用Servlet的Service方法,并传入ServletRequest对象和ServletResponse对象(由Servlet容器如TomCat封装好);每一个应用程序Servlet容器还会创建一个ServletContext对象,封装了上下文应用程序的环境详情,每个应用程序只有一个ServletContext;4)Servlet容器默认采用单实例多线程的方式来处理请求,这样减少产生Servlet实例的开销,提升了对请求的响应时间,对于Tomcat可以在server.xml中通过Connector元素设置线程池中线程的数目;
如何设计高并发系统
1)系统拆分,各自数据库,分摊压力,水平扩容;2)缓存,针对读多写少的场景抗高并发;3)MQ实现异步写和串行化;4)分库抗高并发,分表提高SQL性能,读写分离分摊读压力;5)Elasticsearch,分布式可扩容,承载查询统计全文搜索类业务;
数据库层面的优化手段
SQL和索引使用优化、表结构和字段类型优化、数据库配置参数优化、硬件优化(SSD、RAID、内存等)、分库分表、读写分离、缓存、NoSQL、搜索引擎ES;
网站访问慢如何定位和优化
可能原因:服务器带宽不够、服务器负载大、数据库瓶颈、网站静态资源过大;
优化:加带宽、数据库优化、后台架构优化、CDN、减少Http请求、静态化缓存;
常见性能参数
L1 Cache:容量32-256KB,访问延时0.5ns;L2 Cache:容量2MB-4MB,访问延时 7ns;Mutex加锁/解锁:访问延时100ns;内存:访问延时100ns,顺序读取1MB数据耗时0.25ms;SATA磁盘:磁盘寻道10ms, 顺序读取1MB数据耗时20ms;固态盘 SSD:访问延时0.1~0.2ms;机房内网络来回0.5ms;千兆网络发送1M数据10 ms;异地机房间网络来回30~100 ms;
MySQL:16核64G,TPS:3万,QPS:4万;Kafka:可达百万级QPS;Redis:10万QPS;Windows单进程最大线程数2000,Linux单进程最大线程数1000;Java每起一个线程消耗1M内存;Tomcat默认150并发;Nginx一般可达5w并发;
系统可靠性:0.999(3个9,年停机8.76小时)、0.9999(4个9,年停机50分钟)、0.99999(5个9,年停机5分钟)、0.999999(6个9,年停机30秒)
系统架构设计
架构的理解:1)系统需求和业务范围确定后才能开始系统架构设计;架构设计没有规范可遵循,存在即合理,适合系统开发和运行的架构就是最合理的系统架构;系统架构就是一些模型图,是不同类型的模型图用于给不同角色的人理解系统和沟通的工具;2)架构是经过系统性地思考, 权衡利弊之后在现有资源约束下的最合理决策, 最终明确的系统骨架: 包括子系统, 模块, 组件,以及他们之间协作关系, 约束规范, 指导原则,并由它来指导团队中的每个人思想层面上的一致;
系统架构图包括:1)逻辑架构,面向产品经理,用于确定系统的功能范围和子系统划分,关键点在于系统划分和明确子系统间的协作和调用关系,可用系统流程和系统结构图表示;2)开发架构,面向开发人员,用于明确模块划分、开发框架技术选型、开发规范等;3)物理架构,面向部署和运维人员,用于确定系统的网络环境、硬件环境和软件环境;4)业务架构,包括业务规划,业务模块、业务流程,对整个系统的业务进行拆分,对领域模型进行设计,把现实的业务转化成抽象对象;5)数据架构,指导数据库的设计,包括数据库,实体模型和物理架构中数据存储的设计;6)技术架构,确定组成应用系统的实际运行组件(lvs,nginx,tomcat等)、这些运行组件之间的关系以及部署到硬件的策略,主要考虑系统的非功能性特征,对系统的高可用、高性能、扩展、安全、伸缩性、简洁等做系统级的把握;
架构设计流程
1)需求分析,确定功能、质量、约束范围和目标;2)领域建模,构建业务领域模型,整理软件功能和非功能要求的业务数据、业务流程;3)确定关键需求,确定关键功能需求、约束需求、质量需求;4)决定划分顶级子系统、架构风格选型、开发技术选型、二次开发技术选型、集成技术选型;5)细化架构设计,从逻辑架构、开发架构、运行架构、物理架构、数据架构五个方面出发,对模块划分、接口定义、领域模型、技术选型、控制流程、硬件分布、软件部署、存储格式等内容进行详细设计;6)架构验证,对后续工作产生重大影响且造成返工代价很高的任何工作,都应该安排原型测试和评审工作。同时,进行必要的软件技术选型验证工作;
开源框架选型
考虑点:功能性能满足度、开源协议风险、学习成本(文档资料配套)、集成成本(优先低耦合低侵入)、维护成本(升级、日志、可视化管理)、社区生态;
安全相关
对称加密(加密解密密钥是相同的,包括DES、3DES、AES,分为分组加密:一次加密明文中的一个块和序列加密,算法模式有ECB,CBC,CFB,OFB:一次加密明文中的一个位)、非对称加密(加密和解密是不同的秘钥,包括RSA、DSA、ECC)、哈希算法(MD5、SHA-1、HMac)、编码算法(base64);
数据库安全:非授权用户对数据库的恶意存取和破坏、数据库中重要或敏感的数据被泄露、安全环境的脆弱性、SQL输入;解决:数据加密、漏洞扫描、数据脱敏、数据审计、敏感数据梳理、强制存取控制(数据分密级,用户分不同级别许可证);
当一个应用启动缓慢如何优化?
1)利用提前展示出来的Window快速展示出来一个界面,给用户快速反馈的体验;2)避免在启动时做密集沉重的初始化,可以考虑懒加载;3)定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等;
Linux的进程
1)系统启动时会建立一个init进程,它是其他所有进程的根进程;2)Linux不提供直接创建进程的方法,只提供fork方法创建进程,即从一个老进程复制出来,开辟一段内存空间,再把老的进程空间的内容复制过来;3)子进程终结时会通知父进程并清空自己所占据的内存并在内核里留下自己的退出信息exit code,父进程需要对子进程使用wait系统调用获取退出信息并清空子进程占据的内核空间;4)如果父进程先于子进程终结,子进程成为孤儿进程,孤儿进程会被过继给init进程,由init负责wait调用;5)如果父进程不对子进程调用wait,则不能清空内核数据,子进程成为僵尸进程,会占据内核空间;6)Intel CPU提供Ring0~Ring3四种运行级别,Linux只是用了Ring0内核态和Ring3用户态两个级别,用户态不能访问内核态的地址空间和数据,需要执行系统调用、异常、中断、IO等进入内核态,内核态可以随时进出用户态,用户程序访问硬件资源只能执行系统调用进入内核态,通过内核接口访问;
RPC框架和序列化协议
常用的序列化协议有:JDK自带的序列化、Json、Hessian、Protobuf;序列化/反序列化时间:Protobuf
常用RPC框架:Hessian(hession序列化协议、无注册中心、基于接口的动态代理)、gRPC(protobuf序列化协议、无注册中心、Google开源)、Thrift(thrift序列化协议、无注册中心、Facebook开源)、Dubbo(支持多种序列化协议默认hessian、有注册中心)、Dubbox(当当网扩展)、SpringCloud、RMI(Java自带、类似Hessian);
服务调用失败如何处理
失败重试(设置重试次数)、失败回调(告知调用方做后续处理)、快速失败(减小影响)、服务降级、服务熔断、服务容错、流量控制、故障隔离;
分布式事务实现
1)分布式事务由AP、TM、RM组成,TX协议定义了AP和TM间的接口,XA协议定义了RM和TM间的接口;2)刚性事务满足ACID,柔性事务满足BASE,包括:两阶段型、补偿型、异步确保型、最大努力通知型;3)MySQL中有两种XA事务:内部XA事务中使用binlog作为协调者,外部XA事务使用客户端或者数据库代理层作为协调者,只有序列化事务隔离级别才能够使用XA事务;4)JTA是Java对XA规范的定义,主要包括TranscationManager、XASource、Xid3个接口,Atomicos是实现JTA的三方框架;
2PC:1)分为准备和提交两个阶段,准备阶段执行事务但不提交;2)问题:同步阻塞、单点问题、数据不一致(网络原因导致部分节点收到Commit消息)、保守(单节点失败会导致整个失败没有容错);3)锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题;4)3PC将准备阶段一分为二并加入了超时机制,解决了单点故障,减少了同步阻塞,也有不一致问题;
TCC:1)每个操作分为Try、Confirm、Cancel3个阶段,认为只要Try成功则Confirm一定成功;2)实现流程较简单,但数据一致性较2PC差,对应用侵入强,改造成本高,实现难度大;3)电商领域使用较多;
基于消息的最终一致性:1)本质上是两个事务,上游在本地事务保证本地操作和消息发送的原子性,下游通过不断重试保证最终一致性;2)本地消息表:上游将消息记录到数据库表中,再轮训发送消息;3)MQ事务消息:利用消息中间件的事务消息机制保证上游业务和发送消息的原子性;
阿里GTS/Fescar方案:1)基于2PC的优化方案,核心在于通过减少阻塞来提高性能;2)通过将XA提交中的第一阶段与第二阶段解耦,将提交过程转换为第一阶段本地事务提交+第二阶段异步清理的方式,从而提供提升系统性能,同时通过在GTS内部维护应用级别的日志与锁信息,实现了全局事务的回滚与并发控制;3)低侵入,改造成本很低;
分布式事务框架:LCN(本身不创建事务通过协调本地事务实现,3种模式:LCN、TCC、TXC类似阿里GTS,依赖Redis分布式锁,核心步骤:创建事务组、添加事务组、关闭事务组)、Seata(即阿里Fesatr/GTS)、EasyTransaction(支持TCC、补偿、事务消息等多种模式);