实践杂谈

1、微服务项目结构

实践杂谈

1、传统项目中一般分为三层架构MVC,缺点就是某个功能出错可能会导致整个项目崩溃,而且随着项目越来越大,维护起来也比较困难,开发时,对于项目的代码整合也是一个比较大的难题。

2、分布式系统开发,其实就是将一个大的项目分为不同的系统(记住不是服务)进行开发,这里所指的系统可能包括多个服务,系统之间互不影响,独立运行。

3、SOA在分布式开发的基础上,按照服务进行业务的划分,通过服务来划分不同的项目,比起分布式来说,粒度更细而且功能更加明确。主要用到的报文传输是http+xml

4、微服务在SOA的基础上,粒度更细、更加轻量、使用http+json进行传输。

实践杂谈

其实微服务开发就是面向接口开发,比如说我有一个用户管理的服务,首先我会先创建需要暴露给其他服务调用接口服务,记住这里是要被那些调用者引用,一方面不暴露真实的实现逻辑,另一方面调用方通过继承的方式也避免了代码冗余。然后我再另起一个项目实现这些接口,这个才是我真正的用户管理服务,同理其他服务也是类似。

至于实现起来的话,也很简单,利用maven的依赖继承关系很好实现。这里提供接口的一般为pom类型,他不是一个真正的服务,只是起到暴露接口的作用,而实现类才是真正的服务。调用者就类似于单点应用的controller层。

2、注册登录

2.1 注册

实践杂谈

说白了,就是在接口中声明一个注册的接口,然后在实现类中实现这个接口完成注册功能的实现。这样调用者通过spring cloud的feign或者dubbo进行RPC远程调用,就可以完成两个服务之间的通讯。调用者通过判断响应实体是否正确即可进行相应的页面渲染。

这样将接口分离出去之后,以后如果有新服务,只需要将该接口引入即可对该服务的功能进行调用,这样服务之间代码冗余比较低,不用再在另一个服务上面还要写该服务的路径啊、参数啊什么的。不过如果传参时要注意添加@RequestParam和@RequestBody注解,不然进行feign调用时会失败。

2.2 登录

实践杂谈

1、在用户服务这个服务中,首页会有登录这个接口的实现的嘛,然后如果用户第一次登录时,验证通过后,生成一个带有效期(比如90 天)的token,存放到redis(key:token,value:userId)中并且返回该token给调用者(消费者)。这样用户以后访问时通过携带该token,然后到redis中判断并且查找到该token在有效期内对应的用户信息,然后即可查询到用户信息。

2、而在消费者这边呢?第一次登录时,feign调用用户服务的登录接口服务,如果用户服务返回成功的登录信息token,通过将该token添加到cookie中,这样每次请求时通过请求头获取到该token传给用户服务,即可查询到该token对应的用户信息。

 

注意:对用户服务来说,什么cookie这些是不用管的,只负责存储token、查询token对应用户信息、生成token等功能。也就是其他服务要查询信息,你只要给我token,通过后我就给你这个token对应的用户信息。

 

问题?如果登录多次,那么token不就在redis中有很多吗?可以在数据库中添加一个字段存放token,下次登录时,如果存在token,判断token是否失效,如果失效,重新生成并存到redis和数据库并发送给客户端,如果有效,直接返回该token不就OK了吗?如果不存在,按照上述登录进行。

3 消息服务

实践杂谈

1、该功能一个具体实例:用户注册成功后需要发送邮件、短信、微信啊什么的给用户。

2、第一步用户注册成功后,添加用户邮箱到MQ中,消息平台(消费者)监听到对应的队列中有数据时,取出来进行消费,根据传输文本中包含的发送类型(短信、微信、邮件)进行相应的通知。

4 QQ授权登录

实践杂谈

1、会员服务提供两个服务接口,一个根据openid查询用户信息其实就是返回token,登录返回的token。另一个根据用户名和密码进行登录成功后,将openid存到用户表进行绑定后,还是返回登录成功的token。

2、消费者web服务首先利用QQ互联的一些准备工作获取到openid之后,根据该openid去查找用户信息,如果能查找到,说明该用户已经被绑定,此时直接返回该用户登录成功的token然后利用该token即可完成用户信息的查找

3、如果openid找不到用户,那么首先将该openid存放到session中,然后跳转到一个需要QQ互联管理的页面输入账号密码进行账号绑定。输入完成后,从session中获取到openid,然后传入用户密码+openid调用会员服务的绑定接口,如果绑定正确,那么就返回一个token用于获取用户信息,如果绑定失败,那么返回当前页面重新绑定。

4、其实返回token就是返回一个主页啊、或者其他需要获取用户信息才能访问的页面。这里其实还有一个利用该token去调用会员服务的请求。

4 支付宝支付

实践杂谈

1、本地应用通过Post的方式提交表单参数给支付宝,支付宝解析该参数进行显示,这里传输的参数可以通过加密(RSA或者RSA2)的方式进行传输,这样可以防止别人篡改金额啊等等什么的。

2、支付成功后,本地浏览器会重定向显示支付页面,这是一个同步通知,因为她是不安全的(本地浏览器重定向嘛),所以他不会更改你的订单状态,只是返回一个付款成功的页面

3、支付宝如果确认支付成功之后,他会以httpclient 的方式异步通知本地应用说,订单已经支付成功,并且通知本地应用进行订单状态的修改,然后跳转到回调地址。

4.1 微服务支付?

1、首先利用订单表创建一个token

2、将该token存放在redis(key:token,value:订单表id)中,并且设定过期时间(?因为你下单后,你不支付其实库存也已经减少了的,所以需要有一个过期时间)

3、返回该token给客户端

4、客户端通过该token查询订单表id,如果没找到证明过期了。

5、通过订单表id查询订单表生成form提交表单,提交给支付宝进行参数解析一切OK。

5 分布式事务解决方案

分布式事务:就是说一次大的操作由不同的小操作(事务参与者)组成,这些小操作分布在不同的服务器(数据库),属于不同的应用,分布式事务保证这些小操作要么全部成功,要么全部失败。其实就是为了保证不同数据库的数据一致性。

5.1 CPA理论

实践杂谈

数据一致性:更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。

服务可用性:所有读写请求在一定时间内没有得到响应,可以终止而不会一直等待。就是说比如A服务调用B服务时,如果B服务宕机或者网络延时等一直未收到返回结果,那么A服务会返回一个错误结果(服务降级),而不会一直等待B服务的返回结果

分区容错性:分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

5.2 Base理论

基于CPA进化而来的,是对CPA中一致性和可用性权衡的结果,核心思想:即使无法做到强一致性,但每个业务根据自身的特点采用适当的方式来使系统达到最终一致性。

基本可用:指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。但不等价于不可用。比如:搜索引擎0.5秒返回查询结果,但由于故障,2秒响应查询结果;网页访问过大时,部分用户提供降级服务,等。

软状态(柔性事务):允许系统存在中间状态,并且该中间状态不会影响系统整体可用性。即允许系统在不同节点间副本同步的时候存在延时。

最终一致性:系统中的所有数据副本经过一定时间后,最终能够达到一致的状态,不需要实时保证系统数据的强一致性。最终一致性是弱一致性的一种特殊情况。BASE理论面向的是大型高可用可扩展的分布式系统,通过牺牲强一致性来获得可用性。ACID是传统数据库常用的概念设计,追求强一致性模型。

ACID,指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、(强)一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。Base理论为柔性事务

5.2.1 柔性事务和刚性事务

柔性事务满足BASE理论(基本可用,最终一致)
刚性事务满足ACID理论

柔性事务分为

  1. 两阶段型(2PC)
  2. 补偿型
  3. 异步确保型
  4. 最大努力通知型几种。 

5.3 常见分布式事务解决方案

5.3.1  2PC(两段提交协议)

实践杂谈

在第一段提交中,

1、首先leader(协调者)会向参与者发送一个准备通知,这个通知很强大,其实这个通知就是让参与者回答“你们在这个运行的过程中有没有发生异常啊?”

2、如果两个参与者都回答“OK”(程序中没有产生异常),那么直接进入第二段提交

3、如果有至少一个回答“Not”,那么肯定不能进入第二段提交了,就直接回滚吧。

第二段提交:

1、leader接收到所有参与者都回答成功的消息后,会再发送一个commit通知,类似说“你们可以提交了”

2、然后参与者提交后会(其实就是事务的commit操作)向leader发送一个success的响应,表示已提交

3、如果其中某个提交失败?这种情况一般很少,就除非在提交前节点突然挂了,这种的话只能通过一些补偿、重试、机制来搞定

5.3.2 XA接口

XA–eXtended Architecture 在事务中意为分布式事务 
XA由协调者(coordinator,一般为transaction manager)和参与者(participants,一般在各个资源上有各自的resource manager)共同完成。在MySQL中,XA事务有两种。

5.3.3 什么是JTA

作为java平台上事务规范JTA(Java Transaction API)也定义了对XA事务的支持,实际上,JTA是基于XA架构上建模的,在JTA 中,事务管理器抽象为javax.transaction.TransactionManager接口,并通过底层事务服务(即JTS)实现。像很多其他的java规范一样,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供,目前JTA的实现主要由以下几种:
1.J2EE容器所提供的JTA实现(JBoss)
2.独立的JTA实现:如JOTM,Atomikos.这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。如Tomcat,Jetty以及普通的java应用。

5.3.4  3PC三段提交

三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。

实践杂谈

与两阶段提交不同的是,三阶段提交有两个改动点。

1、引入超时机制。同时在协调者和参与者中都引入超时机制。
2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

CanCommit阶段

3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

1.事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。

2.响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

PreCommit阶段

协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

1.发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。

2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。

3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

1.发送中断请求 协调者向所有参与者发送abort请求。

2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

doCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况。

执行提交

1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。

2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。

3.响应反馈 事务提交完之后,向协调者发送Ack响应。

4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。

中断事务 协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

1.发送中断请求 协调者向所有参与者发送abort请求

2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。

3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息

4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。 )

2PC与3PC的区别

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

5.3.5 TCC

TCC(Try-Confirm-Cancel),则是将业务逻辑分成try、confirm/cancel两个阶段执行。其事务处理方式为:
1、 在全局事务决定提交时,调用与try业务逻辑相对应的confirm业务逻辑;
2、 在全局事务决定回滚时,调用与try业务逻辑相对应的cancel业务逻辑。
可见,TCC在事务处理方式上,是很简单的:要么调用confirm业务逻辑,要么调用cancel逻辑

5.3.6 MQ分布式事物

 采用时效性高的 MQ,由对方订阅消息并监听,有消息时自动触发事件
采用定时轮询扫描的方式,去检查消息表的数据。