(转)宜立方商城架构总结

宜立方商城架构

分布式+SOA

什么是分布式?

  将一个完整的系统按照功能点拆成若干个相互独立的子系统,每一个子系统可称之为一个节点,每一个节点都可以单独配置多台服务器(集群),各个子系统之间相互进行通信,进行协调合作,共同完成整个系统的业务流程,这就是分布式。

 

什么是集群?

  多台服务器做同一件事,这就是集群。

 

集群和分布式的区别?(狭义理解)

答: 集群就是一个工程部署到多台服务器上,这么多服务器都做同一件事。分布式是把一个项目拆分成若干个工程,各个工程之间相互合作,共同完成整个业务流程。分布式中每一个节点都可以搭集群。

 

为什么要使用分布式?

答:一个最简单的web项目,它的架构可能就是表现层—>业务层—>持久层—>数据库。整个项目部署到一台Tomcat中。一台Tomcat服务器理论支持500个并发,1000个并发需要2个Tomcat,10000并发需要20台服务器做tomcat集群。按理说,通过不断的加Tomcat服务器也是可以解决高并发问题的。但是任何一个项目都应该有用户登录的功能,这里面就必然会涉及到Session复制的问题,Tomcat本身具有Session共享的功能。因为Seesion复制,就导致tomcat集群中节点数量不断增加,其服务能力会先增加后下降,一个集群中一般有5个服务器,效果会最好。我们说,能用硬件解决的问题不用软件解决,但是当硬件解决不了时,只能通过软件来解决。

  所以我们使用了分布式,将整个系统按功能点拆分成若干个独立的子系统,再根据每个子系统的业务访问量单独配置集群,比如首页的并发量就高,订单页面的并发量就会相对较低。而且分布式将Session复制问题单独提到一个子系统中,即单点登录系统,其它子系统没有了Session复制问题,那么其集群中的服务器数量就没有限制了。

 

项目分布式后拆成的子系统有哪些?

答:前台系统、后台管理系统、购物车系统、订单系统、搜索系统、单点登录系统。

 

分布式的优点有哪些?

答:1.将整个系统拆成不同的模块,模块之间使用接口通信,降低了模块之间的耦合度

2.把一个项目拆成若干子项目,方便不同的团队负责不同的子项目

3.当有新功能增加时,只需要再增加一个子项目,调用其它子项目的接口就可以

4.可以灵活的进行分布式部署

 

分布式的缺点有哪些?

答:1.系统之间交互需要使用远程通信,接口开发增加工作量

  2.各个模块有一些通用的业务逻辑无法共用

 

  比方说,前台系统中的首页需要展示商品,需要查询商品信息,订单也需要展示商品,也需要查询商品信息,都是查商品信息,可能都是根据商品id查询商品信息,是同一个业务逻辑。但现在这个业务逻辑能共用吗?不能。在不同的工程里面,你是不能共用的。你只能是这个工程里复制一份,那个工程里复制一份,两份。那好了,我们想把这些业务逻辑共用。怎么才能共用呢?

  所以我们在分布式的基础上引入soa的架构。

 

  SOA:Service Oriented Architecture面向服务的架构。也就是把工程拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。

  基于SOA架构,面向服务的架构,这就要求表现层和服务层是分开的,表现层中没有业务逻辑,要想查数据,需要调用服务层发布的服务, 服务层工程没有表现层,没有页面,只有业务逻辑。

  系统与系统之间的通信是通过dubbo实现的。

可以简单介绍一下dubbo的架构?

答:

 

   Dubbo是一个服务治理工具。为所有的服务提供了一个统一的入口。

   Dubbo架构中包含五个角色,分别是:Container、Provider、Registry、Consumer、Monitor。其中,Container是容器,可以是Spring;Provider是服务提供者,Registry是注册中心、Consumer是服务调用者、Monitor是监控中心。

执行流程如下:

   1. 在初始化容器的时候,Provider发布服务;

   2. Provider将发布的服务在注册中心Registry中进行注册;

   3. Consumer预调用服务,先向注册中心Registry询问有没有该服务被发布,如果有,注册中心向Consumer返回该服务的地址(ip+端口号)

   4. Consumer拿到预调用服务的地址,直接调用该服务

   5. 客户端和服务端在内存中累计调用服务的次数和调用时间,并定时将统计数据发送到监控中心Monitor。

 

   Dubbo的实现只需要导入相应的jar包,服务端要发布服务,客户端要调用服务,都需要导入该jar包

   Zookeeper和Monitor都是独立的服务软件,需要在Linux系统下进行安装。

 

如何解决分布式系统中的分布式缓存问题?
什么是分布式系统?

答:将一个完整的系统按照功能点拆成若干个相互独立的子系统,每一个子系统可称之为一个节点,每一个节点都可以单独配置多台服务器(集群),各个子系统之间相互进行通信,进行协调合作,共同完成整个系统的业务流程,这就是分布式。

分布式和集群是不得不联系在一起的两个概念,如果多台服务器共同处理一件事情,叫集群;如果多台服务器各自处理不同的事情,彼此之间协调合作,共同完成整个系统的工作,就叫做分布式系统。

 

Redis-Cluster是集群,还是分布式缓存系统?

答:既是集群,也是分布式系统。这要看从哪个角度来考虑。

  假如从存储数据是否相同来看,Redis-Cluster中每个结点存储的数据是不一样的,它共有16384个槽(0~16383),假如Redis-Cluster中有3个结点,那么这16384个槽是根据各个节点的性能分布在不同节点上的。第一个节点负责0~5000个槽,第二个节点负责5001~10000个槽,第三个节点负责10001~16383个槽,三个节点分别负责不同范围的数据存储,最终完成对整个数据范围的存储。很显然,这是一个分布式系统。

  但如果从Redis-Cluster中所提供的缓存功能来看,每个结点都是用来做缓存的,各个节点提供的功能都是一样的,当内存不够用时,还可以不断横向添加结点来扩大内存容量,很显然,这是一个集群。

  所以,我们会说,Redis集群是一个分布式集群系统。

   

(面试题)你知道哪些分布式缓存,如果要你设计一个分布式缓存,你会怎么去设计?

答:主要有Memcached和Redis。我使用Redis来做分布式缓存。

刚开始对Redis的操作都是单机版,虽然Redis的速度很快,但是在特别高的并发下,Redis也有性能瓶颈。Redis中的数据都放在内存里面,内存能有多大呢?64G,已经很大了,64G都放满了呢?还能放吗?可以,内存放满了会放在硬盘中的虚拟内存中,一旦用到虚拟内存了,性能就很低了,所以我们尽可能的不要超出内存的容量。如果存不下了,但是数据还是很多,还需要往缓存中放,那怎么办呢?通过搭Redis集群来扩展内存空间。官方给出的Redis集群名称为Redis-Cluster。

Redis-Cluster架构图如下:

 

集群一般都会有一个入口,有一个集群管理工具,但Redis集群没有入口,即没有代理层,集群中的节点都是相互连接的,通过PING-PONG机制来实现各个节点之间的通信,以及判断各个节点的状态,客户端想要连接集群,只需要连接到集群中的任意一个节点即可。

集群中有那么多个结点,结点中保存的数据一样吗?不一样。如果是一样的,那叫主备。既然是集群,就应该是可以扩容的,如果存储空间不足了,可以加结点,加一个服务器进行,存储空间就会变大。所有结点的内存容量加起来才是整个集群内存的总容量,如果每个结点存储的数据都一样,那总容量就只是一个Redis的内存容量了。

Redis集群中,每个结点保存的数据是不一样的。如果不一样,那么当一个结点挂了,那整个集群就不完整了,不完整了就不能用了,所以,要想保证Redis集群的高可用(长时间可使用,而不会宕机),每一个节点都需要加一个备份机,如果这个结点挂了,必须要有备份结点顶上来,来保证集群可以继续提供服务。

Redis集群中有一个投票:容错机制,我们前面说集群中一般都会有一个集群管理工具,但在Redis集群中并没有,那么,我们怎么才能知道集群中哪一个结点挂了呢?Redis集群中有一个投票机制。大家都知道,在选举的时候,是少数服从多数的原则,要判断Redis集群中的某个结点是否挂掉了,需要我们集群中超过半数的节点进行投票,半数以上的节点认为它挂了,它就挂了。假如集群中有5个结点,有三个认为某个结点已经挂了,那么集群就认为这个结点真挂了。这个时候就要看有没有备份结点,如果没有备份结点顶上来,那么集群就会宕机。如果有备份结点,备份结点顶上来,继续维持整个集群的工作,然后管理人员就需要赶快把那个挂掉的节点修理好。那么,集群中最少有几个结点呢?3个!3个结点就可以搭建起一个Redis集群。而在实际开发中,为了保证集群的高可用,还要保证每个结点都有一个备份机,所以,实际中,最小的集群会搭建6个结点。那如果面试官问你:

一个Redis集群至少有几个结点?为什么?

答:有3个结点。这是由Redis集群中的投票:容错机制决定的。Redis集群中,要判断一个结点是否挂掉了,是通过集群中的其他结点投票决定的。当集群中有一半以上的节点都投票认为该节点挂掉了,Redis集群才会认为该节点挂掉了,这就导致一个Redis集群中最少要有3个结点。

当我们使用单机版的Redis做缓存时,操作很简单,当单机版的Redis变成Redis集群后,操作是不是就会变得异常复杂?

答:并不会变多复杂。只需要连接上Redis集群中的任意一个结点,就能连接上整个Redis集群。只是在使用Jedis连接单机版Redis和连接Redis集群时,会有所不同,但对Redis的操作都是一样的。

Redis集群中,每个结点保存的数据是不一样的,那就会有一个问题,如何把数据分散到不同的节点进行存储呢?为解决这个问题,Redis集群中引入了一个概念,叫slot(槽,哈希槽)。Redis集群中一共有16384个槽(0~16383),这是固定的。这些槽有什么作用呢?

当要在Redis集群中放置一个key-value对时,先要对key使用crc16算法得出一个数,然后再用这个数对16384求余数,肯定会得到一个0~16383之间的数,这样每一个key值都会对应一个0~16383之间的哈希槽,然后将key-value键值对放在这个槽对应的Redis结点上就可以了。

槽如何进行分配呢?

答:这是可以由认为设置的。一般情况下会平均分配。为了保证Redis集群的性能,要看Redis集群中有几个结点,还要看每个结点的性能怎么样。假如有3个结点,每个结点的性能都是完全一样的,那么我们就可以把这16384个槽平均分到3个结点上。

0~5000个槽分到第一个结点上

5001~10000个槽分到第二个结点上

10001~16383个槽分到第三个结点上(为了好计算,这样划分)

 

Redis集群中最多有多少个结点?为什么?

答:最多有16384个结点(这里不考虑备份机的问题)。这是由Redis集群中哈希槽的数量决定的,极限情况下,每个结点有一个哈希槽。

架构细节:

(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.

(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责维护node<->slot<->value

Redis集群中内置了16384个哈希槽,当需要在Redis集群中放置一个key-value时,redis先对key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。

数据库架构设计
1 海量数据的存储问题
如今随着互联网的发展,数据的量级也是呈指数的增长,从GB到TB到PB。对数据的各种操作也是愈加的困难,传统的关系型数据库已经无法满足快速查询与插入数据的需求。这个时候NoSQL的出现暂时解决了这一危机。它通过降低数据的安全性,减少对事务的支持,减少对复杂查询的支持,来获取性能上的提升。

但是,在有些场合NoSQL一些折衷是无法满足使用场景的,就比如有些使用场景是绝对要有事务与安全指标的。这个时候NoSQL肯定是无法满足的,所以还是需要使用关系型数据库。如何使用关系型数据库解决海量存储的问题呢?此时就需要做数据库集群,为了提高查询性能将一个数据库的数据分散到不同的数据库中存储。

当数据太多,以至于数据库存不下的时候,我们要做数据库分片。

2 什么是数据库分片
简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。

数据的切分根据其切分规则的类型,可以分为两种切分模式。

(1)一种是按照不同的表(或者Schema)来切分到不同的数据库(主机)之上,这种切可以称之为数据的垂直(纵向)切分。数据量比较大,是因为有几张表中存放的数据量比较大,比如商品表、订单表、商品表等,我把不同的表放到不同的数据库中,订单库、商品库、用户库等。将不同的表放到不同的数据库中,这就是所谓的垂直切割。这种思路是可行的,但是假如我只是单张表数据量特别大,即便是水平切分之后,存这张表的数据库性能也会很低。

(2)另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分。大多数的互联网网站采取的是水平切割,因为垂直切割解决不了单表数据量过大的问题。

当数据库分片后(水平切割),数据由一个数据库分散到多个数据库中。此时系统要查询时需要切换不同的数据库进行查询,那么系统如何知道要查询的数据在哪个数据库中?当添加一条记录时要向哪个数据库中插入呢?这些问题处理起来都是非常的麻烦。

这种情况下可以使用一个数据库中间件mycat来解决相关的问题。接下来了解一下什么是mycat。

3 什么是Mycat?
Mycat背后是阿里曾经开源的知名产品——Cobar。Cobar的核心功能和优势是MySQL数据库分片,此产品曾经广为流传,据说最早的发起者对Mysql很精通,后来从阿里跳槽了,阿里随后将Cobar开源,并维持到2013年年初。然后,就没有然后了。

Cobar的思路和实现路径的确不错。基于Java开发的,实现了MySQL公开的二进制传输协议,巧妙地将自己伪装成一个MySQL Server,目前市面上绝大多数MySQL客户端工具和应用都能兼容。比自己实现一个新的数据库协议要明智的多,因为生态环境在那里摆着。

Mycat是基于Cobar演变而来,对Cobar的代码进行了彻底的重构,使用NIO重构了网络模块,并且优化了Buffer内核,增强了聚合、Join等基本特性,同时兼容绝大多数数据库称为通用的数据库中间件。

简单的说,Mycat就是:一个新颖的数据库中间件产品,支持mysql集群,或者marisdb cluster,提供高可用性数据分片集群。你可以像使用mysql一样使用mycat。对于开发人员来说根本感觉不到mycat的存在。

 

4 Mycat具体是如何进行数据库分片的呢?
答:这一块不是我做的,我并不太清楚内部实现原理。

 

5 Mycat读写分离
数据库读写分离对于大型系统或者访问量很高的互联网应用来说,是必不可少的一个重要功能。对于MySQL来说,标准的读写分离是主从模式,一个写节点Master后面跟着多个读节点,读节点的数量取决于系统的压力,通常是1-3个读节点的配置

 

Mycat读写分离和自动切换机制,需要mysql的主从复制机制配合。主数据库通过主从复制生成一个从数据库,这两个数据库中存放的数据是一模一样的,当对主数据库修改之后,修改结果也会同步到从数据库中。数据库的读写分离要依赖于数据库的主从复制,对主数据库进行写操作,对从数据库进行读操作。

6 Mysql的主从复制
 

主从复制需要注意的地方

1、主DB server和从DB server数据库的版本一致

2、主DB server和从DB server数据库数据名称一致

3、主DB server开启二进制日志,主DB server和从DB server的server_id都必须唯一

 

注意:面试的时候经常会问,你是如何解决项目中的分布式事务问题的?

答:我们项目中使用Mycat管理数据库集群,Mycat本身支持分布式事务。

 

分布式事务问题
在说分布式事务之前,我们先从数据库事务说起。 数据库事务可能大家都很熟悉,在开发过程中也会经常使用到。但是即使如此,可能对于一些细节问题,很多人仍然不清楚。比如很多人都知道数据库事务的几个特性:原子性(Atomicity )、一致性( Consistency )、隔离性或独立性( Isolation)和持久性(Durabilily),简称就是ACID。但是再往下,比如问到隔离性指的是什么的时候可能就不知道了,或者是知道隔离性是什么但是再问到数据库实现隔离的都有哪些级别,或者是每个级别他们有什么区别的时候可能就不知道了。

  本文并不打算介绍这些数据库事务的这些东西,有兴趣可以搜索一下相关资料。不过有一个知识点我们需要了解,就是假如数据库在提交事务的时候突然断电,那么它是怎么样恢复的呢? 为什么要提到这个知识点呢? 因为分布式系统的核心就是处理各种异常情况,这也是分布式系统复杂的地方,因为分布式的网络环境很复杂,这种 “断电”故障要比单机多很多,所以我们在做分布式系统的时候,最先考虑的就是这种情况。这些异常可能有机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失、其他异常等等...

  我们接着说本地事务数据库断电的这种情况,它是怎么保证数据一致性的呢?我们使用SQL Server来举例,我们知道我们在使用 SQL Server 数据库是由两个文件组成的,一个数据库文件和一个日志文件,通常情况下,日志文件都要比数据库文件大很多。数据库进行任何写入操作的时候都是要先写日志的。同样的道理,我们在执行事务的时候数据库首先会记录下这个事务的redo操作日志,然后才开始真正操作数据库,在操作之前首先会把日志文件写入磁盘,那么当突然断电的时候,即使操作没有完成,在重新启动数据库时候,数据库会根据当前数据的情况进行undo回滚或者是redo前滚,这样就保证了数据的强一致性。

接着,我们就说一下分布式事务。

当我们的单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分区,这里所说的分区指的是物理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的ACID已经不能适应这种情况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即使能达到那么效率和性能会大幅下降,最为关键的是再很难扩展新的分区了,这个时候如果再追求集群的ACID会导致我们的系统变得很差,这时我们就需要引入一个新的理论原则来适应这种集群的情况,就是 CAP 原则或者叫CAP定理,那么CAP定理指的是什么呢?

CAP定理

  CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出一个提供数据服务的存储系统无法同时满足数据一致性(Consistency)、数据可用性(Availibility)、分区耐受性(Partition tolerance) :

一致性(Consistency) : 在数据有多份副本的情况下,如果网络、服务器或者软件出现故障,会导致部分副本写入成功,部分副本写入失败。这就会造成各个副本之间的数据不一致,数据内容冲突。实践中,导致数据不一致的情形有很多种,表现形式也多种多样,比如数据更新返回操作失败,事实上数据在存储服务器已经更新成功。

可用性(Availability) : 在多份数据副本分别存放在不同存储设备的情况下,如果一个数据存储设备损坏,就需要将数据访问切换到另一个数据存储设备上,如果这个过程不能很快完成(终端用户几乎没有感知),或者在完成过程中需要停止终端用户访问数据,那么这段时间数据是不可访问的。

分区耐受性(Partition tolerance) : 简单说就是可扩展性(伸缩性)。

  具体地讲在分布式系统中,在大型网站应用中,数据规模总是快速扩张的,因此可伸缩性即分区耐受性必不可少,规模变大以后,机器数量也会变得庞大,这时网络和服务器故障会频繁出现,要想保证应用可用,就必须保证分布式处理系统的高可用性。所以在大型网站中。通常会选择强化分布式存储系统的可用性(A)和伸缩性(P),而在某种程度上放弃一致性(C)。一般说来,数据不一致通常出现在系统高并发写操作或者集群状态不稳(故障恢复、集群扩容......)的情况下,应用系统需要对分布式数据处理系统的数据不一致有所了解并进行某种意义上的补偿和纠错,以避免出现应用系统数据不正确。

  2012年淘宝“双十一”活动期间,在活动第一分钟就涌入了1000万独立用户访问,这种极端的高并发场景对数据处理系统造成了巨大压力,存储系统较弱的数据一致性导致出现部分商品超卖现象(交易成功的商品数超过了商品库存数)。

  CAP原理对于可伸缩的分布式系统设计具有重要意义,在系统设计开发过程中,不恰当地迎合各种需求,企图打造一个完美的产品,可能会使设计进入两难境地,难以为继。

  具体来说,数据一致性又可分为如下几点:

  数据强一致

  各个副本的数据在物理存储中总是一致的;数据更新操作结果和操作响应总是一致的,即操作响应通知更新失败,那么数据一定没有被更新,而不是出于不确定状态。

  数据用户一致

  即数据在物理存储中的各个副本的数据可能是不一致的,但是终端用户访问时,通过纠错和校验机制,可以确定一个一致的且正确的数据返回给用户。

  数据最终一致

  这是数据一致性中较弱的一种,即物理存储的数据可能是不一致的,终端用户访问到的数据可能也是不一致的(同一用户连续访问,结果不同;或者不同用户同时访问,结果不同),但系统经过一段时间(通常是一个比较端的时间段)的自我恢复和修正,数据最终会达到一致。

  因为难以满足数据强一致性,网站通常会综合成本、技术、业务场景等条件,结合应用服务和其他的数据监控与纠错功能,使存储系统达到用户一致,保证最终用户访问数据的正确性。

  CAP定理在迄今为止的分布式系统中都是适用的! 为什么这么说呢?

  这个时候有同学可能会把数据库的2PC(两阶段提交)搬出来说话了。OK,我们就来看一下数据库的两阶段提交。

     对数据库分布式事务有了解的同学一定知道数据库支持的2PC,又叫做 XA Transactions。

MySQL从5.5版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。

其中,XA 是一个两阶段提交协议,该协议分为以下两个阶段:

第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.

第二阶段:事务协调器要求每个数据库提交数据。

  其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中的那部分信息。这样做的缺陷是什么呢? 咋看之下我们可以在数据库分区之间获得一致性。

  如果CAP 定理是对的,那么它一定会影响到可用性。

  如果说系统的可用性代表的是执行某项操作相关所有组件的可用性的和。那么在两阶段提交的过程中,可用性就代表了涉及到的每一个数据库中可用性的和。我们假设两阶段提交的过程中每一个数据库都具有99.9%的可用性,那么如果两阶段提交涉及到两个数据库,这个结果就是99.8%。根据系统可用性计算公式,假设每个月43200分钟,99.9%的可用性就是43157分钟, 99.8%的可用性就是43114分钟,相当于每个月的宕机时间增加了43分钟。

数据库中的2PC追求的是强一致性,这就必然导致可用性很差,这是不合适的。

以上,可以验证出来,CAP定理从理论上来讲是正确的,CAP我们先看到这里,等会再接着说。

BASE理论

  在分布式系统中,我们往往追求的是可用性,它的重要程度比一致性要高,那么如何实现高可用性呢? 前人已经给我们提出来了另外一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:

· Basically Available(基本可用)

· Soft state(软状态)

· Eventually consistent(最终一致性)

  BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:保证数据的基本可用和最终一致。但在有些业务场景下,需要高可用,需要强一致,这就需要开发人根据实际情况进行取舍了。

  有了以上理论之后,我们来看一下分布式事务的问题。

 

分布式事务就是用来解决数据一致性问题的。分布式事务的理论基础是CAP定理和BASE定理。所以,根据这两大理论基础,决定了在处理分布式事务问题上,不能盲目追求数据的强一致性,因为数据的一致性分为三个层次:强一致性、用户一致性和最终一致性。我们虽然竭力保证数据的一致性,但是如果过分追求数据一致性,就会牺牲掉数据的可用性,而可用性往往比一致性更重要。但有些情况下是需要保证数据的强一致性的,这个时候就算牺牲掉数据的可用性,也是没办法的。

分布式事务经典应用场景

最经典的场景就是支付了,一笔支付,是对买家账户进行扣款,同时对卖家账户进行加钱,这些操作必须在一个事务里执行,要么全部成功,要么全部失败。而对于买家账户属于买家中心,对应的是买家数据库,而卖家账户属于卖家中心,对应的是卖家数据库,对不同数据库的操作必然需要引入分布式事务。

买家在电商平台下单,往往会涉及到两个动作,一个是扣库存,第二个是更新订单状态,库存和订单一般属于不同的数据库,需要使用分布式事务保证数据一致性。

处理分布式事务的手段有哪些?

一、两阶段提交(2PC)

  和上一节中提到的数据库XA事务一样,两阶段提交就是使用XA协议的原理,我们可以从下面这个图的流程来很容易的看出中间的一些比如commit和abort的细节。

 

  两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性。在实现方面,在 .NET 中,可以借助 TransactionScop 提供的 API 来编程实现分布式系统中的两阶段提交,比如WCF中就有实现这部分功能。不过在多服务器之间,需要依赖于DTC来完成事务一致性,Windows下微软搞的有MSDTC服务,Linux下就比较悲剧了。

  另外说一句,TransactionScop 默认不能用于异步方法之间事务一致,因为事务上下文是存储于当前线程中的,所以如果是在异步方法,需要显式的传递事务上下文。

  优点: 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)

  缺点: 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景,如果分布式系统跨接口调用,目前 .NET 界还没有实现方案。

二、补偿事务(TCC)

  TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:

Try 阶段主要是对业务系统做检测及资源预留

Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。

Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

 举个例子,假入 Bob 要向 Smith 转账,思路大概是:
   我们有一个本地方法,里面依次调用
   1、首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
   2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
   3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。

   优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些

   缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

三、本地消息表(异步确保)
    本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:

 

基本思路就是:

  消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。

  消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

  生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

  这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。

  优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中有现成的解决方案。

  缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

你们的项目中是如何保证分布式事务问题的?

答:我们的项目用的是一个数据库中间件Mycat来统一管理数据库集群。所以对开发人员而言,使用Mycat就和使用单个mysql数据库一样。Mycat中间件支持分布式事务问题,它底层实现的原理是“弱XA事务”。

 

Mycat里的事务包括以下几种情况:


单SQL不垮分片:事务中的单条SQL在单个节点上执行

单SQL跨分片:事务中的单条SQL在多个节点上执行

事务内多个SQL,在不同的分片上执行

其中,第一种情况,单一SQL仅仅在一个dataNode上执行,此时Mycat事务模式跟标准的数据库事务模式一样,要么提交要么回滚;而对于第二种和第三种的事务,Mycat执行的一种”弱XA事务“模式,此模式的逻辑如下:

首先事务内的SQL在各自的分片上执行并返回状态码,若某个分片上的返回码为ERROR,则Mycat认为事务失败,应用端只能回滚(rollback)事务,Mycat收到回滚指令后,依次回滚事务中涉及到的所有分片;若事务中的所有SQL的执行都返回成功(OK)的返回码,则应用程序提交事务的时候,Mycat会同时向事务中涉及到的节点发送提交事务的指令。

XA事务原理


分布式事务处理( Distributed Transaction Processing,DTP )指一个程序或程序段,在一个或多个资源如数据库或文件上为完成某些功能的执行过程的集合,分布式事务处理的关键是必须有一种方法可以知道事务在任何地方所做的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或全部回滚)。X/Open 组织(即现在的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器(CRM )四部分。一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件,下图是X/Open DTP模型:

XA事务的问题和MySQL的局限


XA事务的明显问题是timeout问题,比如当一个RM出问题了,那么整个事务只能处于等待状态。这样可以会连锁反应,导致整个系统都很慢,最终不可用,另外2阶段提交也大大增加了XA事务的时间,使得XA事务无法支持高并发请求。

避免使用XA事务的方法通常是最终一致性。

举个例子,比如一个业务逻辑中,最后一步是用户账号增加300元,为了减少DB的压力,先把这个放到消息队列里,然后后端再从消息队列里取出消息,更新DB。那么如何保证,这条消息不会被重复消费?或者重复消费后,仍能保证结果是正确的?在消息里带上用户帐号在数据库里的版本,在更新时比较数据的版本,如果相同则加上300;比如用户本来有500元,那么消息是更新用户的钱数为800,而不是加上300;

另外一个方式是,建一个消息是否被消费的表,记录消息ID,在事务里,先判断消息是否已经消息过,如果没有,则更新数据库,加上300,否则说明已经消费过了,丢弃。

前面两种方法都必须从流程上保证是单方向的。

其实严格意义上,用消息队列来实现最终一致性仍然有漏洞,因为消息队列跟当前操作的数据库是两个不同的资源,仍然存在消息队列失败导致这个账号增加300元的消息没有被存储起来(当然复杂的高级的消息队列产品可以避免这种现象,但仍然存在风险),而第二种方式则由于新的表跟之前的事务操作的表示在一个Database中,因此不存在上述的可能性。

MySQL的XA事务,长期以来都存在一个缺陷:

MySQL数据库的主备数据库的同步,通过Binlog的复制完成。而Binlog是MySQL数据库内部XA事务的协调者,并且MySQL数据库为binlog做了优化——binlog不写prepare日志,只写commit日志。所有的参与节点prepare完成,在进行xa commit前crash。crash recover如果选择commit此事务。由于binlog在prepare阶段未写,因此主库中看来,此分布式事务最终提交了,但是此事务的操作并未写到binlog中,因此也就未能成功复制到备库,从而导致主备库数据不一致的情况出现。

 

总结:


我们项目中使用数据库中间件Mycat来统一管理数据库集群,对开发人员来说,使用Mycat和使用单个Mysql数据库是一样的。Mycat中间件支持分布式事务问题,它底层实现的原理是使用了Mysql数据库中的XA事务模式,但是有有所不同,所以,可称Mycat里面是通过“弱XA事务”来实现分布式事务管理的。这种实现分布式事务的方式存在缺陷,就是它追求的是强一致性,根据CAP定理,就必然以牺牲可用性为代价,而可用性往往比一致性更重要,所以不应该追求强一致性,而应该在保证可用性的提前下,实现数据的最终一致性就可以了。

解决这一问题的方式一般有两种,一种是消息队列,一种是本地消息表。

消息队列:,比如一个业务逻辑中,最后一步是用户账号增加300元,为了减少DB的压力,先把这个放到消息队列里,然后后端再从消息队列里取出消息,更新DB。那么如何保证,这条消息不会被重复消费?或者重复消费后,仍能保证结果是正确的?在消息里带上用户帐号在数据库里的版本,在更新时比较数据的版本,如果相同则加上300;比如用户本来有500元,那么消息是更新用户的钱数为800,而不是加上300。

本地消息表:建一个消息是否被消费的表,记录消息ID,在事务里,先判断消息是否已经消息过,如果没有,则更新数据库,加上300,否则说明已经消费过了,丢弃。这两种方法都必须从流程上保证是单方向的。

其实严格意义上,用消息队列来实现最终一致性仍然有漏洞,因为消息队列跟当前操作的数据库是两个不同的资源,仍然存在消息队列失败导致这个账号增加300元的消息没有被存储起来(当然复杂的高级的消息队列产品可以避免这种现象,但仍然存在风险),而第二种方式则由于新的表跟之前的事务操作的表示在一个Database中,因此不存在上述的可能性。
--------------------- 
作者:牧儿 
来源:CSDN 
原文:https://blog.csdn.net/lz1170063911/article/details/80033798 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值