高可用系统笔记

1.高可用是基于服务无状态

无状态服务:无状态服务(stateless service)对单次请求的处理,不依赖其他请求,也就是说,处理一次请求所需的全部信息,要么都包含在这个请求里,要么可以从外部获取到(比如说数据库),服务器本身不存储任何信息 

有状态服务(stateful service)则相反,它会在自身保存一些数据,先后的请求是有关联的,每个请求可以默认地使用以前的请求信息。

有状态服务可以很容易地实现事务,所以也是有价值的。但是经常听到一种说法,即server要设计为无状态的,这主要是从可伸缩性来考虑的。如果server是无状态的,那么对于客户端来说,就可以将请求发送到任意一台server上,然后就可以通过负载均衡等手段,实现水平扩展。

如果server是有状态的,那么就无法很容易地实现了,因为客户端需要始终把请求发到同一台server才行,这就用到了session高可用集群。接入层无状态化,session集群有状态,比如最常见的session,将用户挑选的商品(购物车),保存到session中,当付款的时候,再从购物车里取出商品信息,如果不做session集群来共享,server将不得不存下前一session为做完这笔业务。因为业务总是有状态的,如一笔订单随着操作在更新状态。

 

2. 业务逻辑层

2.1 阻塞和非阻塞、同步和异步

阻塞和非阻塞:即socket没请求到是立即返回还是一直等下去,所有IO才有阻塞和非阻塞,不涉及IO,只有CPU参与时是没有阻塞的概念的

 

同步和异步:简单来说只是一种模式,

同步:发出一个请求调用时,在没有得到结果之前,该调用就不会返回,调用者线程阻塞模式

异步:异步调用发出后,调用者立即返回,结果完成后,通过状态、通知和回调来通知调用者,调用者线程非阻塞模式

 

为什么两组概念会被人混淆,因为无论同步还是异步调用一般都涉及到IO,涉及到IO就有阻塞和非阻塞。

 

纯异步调用成本稍高,但系统吞吐量和CPU利用比较高

通过消息队列来实现异步调用

线下MQ:kafka

线上MQ:RocketMQ

异步调用涉及到IO,不得不涉及到IO复用模型,跟socket阻塞模型(即一直等着这个socket可用)相比,效率高得多

2.2 IO复用

一次可监听多个句柄,如果都不可用则阻塞,有一个可用的立即返回。

链接:https://www.zhihu.com/question/28594409/answer/52835876

下面举一个例子,模拟一个tcp服务器处理30个客户socket。
假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:
1. 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。
这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。
2. 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。
3. 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。
这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式
这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的(这句话的意思是一旦有消息可以立即被select/poll/epoll检测到,可以立即返回给主流程),整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。

 

select:一次可监听最多1024个fd,可监听fd的最大值也是1024

 

select和epoll的区别主要有三:

1.select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:#define __FD_SETSIZE  1024。表示select最多同时监听1024个fd。而epoll没有,它的限制是最大的打开文件句柄数目。

2.epoll的最大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个"伪"AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂)。

3.使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

2.3 高性能纯异步网络调用设计

server端连接池+server端队列

client端连接池+client端收发队列

超时队列与超时管理器

上下文管理器+状态机

 

 

以58帮帮加好友为例

具体实施:

  1. 进程启动下游连接(server端连接池/主动拉取)加入主动连接池
  2. 建立上游客户端连接,并加入被动连接池
  3. 把上游客户端放在接收队列
  4. 工作线程开始
  5. 逻辑处理请求到客户端发送队列的数据后发送到下游服务器,并加入超时队列
  6. 收到下游服务器的响应,请求加入接收队列,删除超时队列,通知上层回调
  7. 否则,下游响应超时,从超时队列中删除这个请求,并回调
  8. 响应加入服务器发送队列,回复给客户端

 

超时管理器

  1. 发送下游包的超时管理
  2. 避免无限等待
  3. 单独线程
  4. 定时扫描
  5. 定时处理

 

上下文管理器

  1. 请求上下文
  2. 请求的唯一标示
  3. package_key
  4. 超时等删除上下文

 

状态机管理器

  1. 异步调用的状态机
  2. 标志请求的状态
  3. 串行执行的状态机

 

 

2.4 幂等设计

定义:保证服务重复调用和一次调用产生的副作用(如对数据库某条记录)相同

http://www.cnblogs.com/geyifan/p/6128425.html#h22

HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的副作用(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.

这里需要关注几个重点:

1.幂等不仅仅只是一次(或多次)请求对资源没有副作用(比如查询数据库操作,没有增删改,因此没有对数据库有任何影响)。

2.幂等还包括第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。

3.幂等关注的是以后的多次请求是否对资源产生的副作用,而不关注结果,4.网络超时等问题,不是幂等的讨论范围。

5.幂等性是系统服务对外一种承诺(而不是实现),承诺只要调用接口成功,外部多次调用对系统的影响是一致的。声明为幂等的服务会认为外部调用失败是常态,并且失败之后必然会有重试。

数据中的CURD(create/update/read/delete)只有update在某些情况下(更新相对值)不是幂等的。

 

分布式事务 vs 幂等设计

链接:https://www.zhihu.com/question/27604206/answer/68384070

为什么需要幂等性呢?我们先从一个例子说起,假设有一个从账户取钱的远程API(可以是HTTP的,也可以不是),我们暂时用类函数的方式记为:

bool withdraw(account_id, amount)

withdraw的语义是从account_id对应的账户中扣除amount数额的钱;如果扣除成功则返回true,账户余额减少amount;如果扣除失败则返回false,账户余额不变。值得注意的是:和本地环境相比,我们不能轻易假设分布式环境的可靠性。一种典型的情况是withdraw请求已经被服务器端正确处理,但服务器端的返回结果由于网络等原因被掉丢了,导致客户端无法得知处理结果。如果是在网页上,一些不恰当的设计可能会使用户认为上一次操作失败了,然后刷新页面,这就导致了withdraw被调用两次,账户也被多扣了一次钱。

这个问题的解决方案一是采用分布式事务,通过引入支持分布式事务的中间件来保证withdraw功能的事务性。分布式事务的优点是对于调用者很简单,复杂性都交给了中间件来管理。缺点则是一方面架构太重量级,容易被绑在特定的中间件上,不利于异构系统的集成;另一方面分布式事务虽然能保证事务的ACID性质,而但却无法提供性能和可用性的保证。

另一种更轻量级的解决方案是幂等设计。我们可以通过一些技巧把withdraw变成幂等的,比如:

int create_ticket()

bool idempotent_withdraw(ticket_id, account_id, amount)

create_ticket的语义是获取一个服务器端生成的唯一的处理号ticket_id,它将用于标识后续的操作。idempotent_withdraw和withdraw的区别在于关联了一个ticket_id,一个ticket_id表示的操作至多只会被处理一次,每次调用都将返回第一次调用时的处理结果。这样,idempotent_withdraw就符合幂等性了,客户端就可以放心地多次调用。

基于幂等性的解决方案中一个完整的取钱流程被分解成了两个步骤:

1.调用create_ticket()获取ticket_id;

2.调用idempotent_withdraw(ticket_id, account_id, amount)。虽然create_ticket不是幂等的,但在这种设计下,它对系统状态的影响可以忽略,加上idempotent_withdraw是幂等的,所以任何一步由于网络等原因失败或超时,客户端都可以重试,直到获得结果。

和分布式事务相比,幂等设计的优势在于它的轻量级,容易适应异构环境,以及性能和可用性方面。在某些性能要求比较高的应用,幂等设计往往是唯一的选择。

 

2.5 HTTP的幂等性

链接:https://www.zhihu.com/question/27604206/answer/68384070

 

HTTP协议本身是一种面向资源的应用层协议,但对HTTP协议的使用实际上存在着两种不同的方式:一种是RESTful的,它把HTTP当成应用层协议,比较忠实地遵守了HTTP协议的各种规定;另一种是SOA的,它并没有完全把HTTP当成应用层协议,而是把HTTP协议作为了传输层协议,然后在HTTP之上建立了自己的应用层协议。本文所讨论的HTTP幂等性主要针对RESTful风格的,不过正如上一节所看到的那样,幂等性并不属于特定的协议,它是分布式系统的一种特性;所以,不论是SOA还是RESTful的Web API设计都应该考虑幂等性。下面将介绍HTTP GET、DELETE、PUT、POST四种主要方法的语义和幂等性。

HTTP GET方法用于获取资源,不应有副作用,所以是幂等的。比如:GET http://www.bank.com/account/123456,不会改变资源的状态,不论调用一次还是N次都没有副作用。请注意,这里强调的是一次和N次具有相同的副作用,而不是每次GET的结果相同。GET Technology News这个HTTP请求可能会每次得到不同的结果,但它本身并没有产生任何副作用,因而是满足幂等性的。

HTTP DELETE方法用于删除资源,有副作用,但它应该满足幂等性。比如:DELETE http://www.forum.com/article/4231,调用一次和N次对系统产生的副作用是相同的,即删掉id为4231的帖子;因此,调用者可以多次调用或刷新页面而不必担心引起错误。

比较容易混淆的是HTTP POST和PUT。POST和PUT的区别容易被简单地误认为“POST表示创建资源,PUT表示更新资源”;而实际上,二者均可用于创建资源,更为本质的差别是在幂等性方面。在HTTP规范中对POST和PUT是这样定义的:

The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line ...... If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header.

The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.

POST所对应的URI并非创建的资源本身,而是资源的接收者。比如:POST http://www.forum.com/articles的语义是在http://www.forum.com/articles下创建一篇帖子,HTTP响应中应包含帖子的创建状态以及帖子的URI。两次相同的POST请求会在服务器端创建两份资源,它们具有不同的URI;所以,POST方法不具备幂等性。而PUT所对应的URI是要创建或更新的资源本身。比如:PUT http://www.forum/articles/4231的语义是创建或更新ID为4231的帖子。对同一URI进行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有幂等性

在介绍了几种操作的语义和幂等性之后,我们来看看如何通过Web API的形式实现前面所提到的取款功能。很简单,用POST /tickets来实现create_ticket;用PUT /accounts/account_id/ticket_id&amount=xxx来实现idempotent_withdraw。值得注意的是严格来讲amount参数不应该作为URI的一部分,真正的URI应该是/accounts/account_id/ticket_id,而amount应该放在请求的body中。这种模式可以应用于很多场合,比如:论坛网站中防止意外的重复发帖。

上面简单介绍了幂等性的概念,用幂等设计取代分布式事务的方法,以及HTTP主要方法的语义和幂等性特征。其实,如果要追根溯源,幂等性是数学中的一个概念,表达的是N次变换与1次变换的结果相同,有兴趣的读者可以从Wikipedia上进一步了解。

 

题外话:post/get的区别

http://www.oschina.net/news/77354/http-get-post-different

值得一提的是两者底层都是TCP连接,技术层面上没有区别,只有语义上的区别,即get用于获取数据,post用于修改数据。最重要的是GET产生一个TCP数据包,POST产生两个TCP数据包;其他的还有GET参数规定通过URL传递,POST规定放在Request body中;GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息,等等。

 

来源https://www.zhihu.com/question/31640769

使用场景的区别:

Use the POST method if any of the following are true:

* The result of the request has persistent side effects such as adding a new database row.

* The data collected on the form is likely to result in a long URL if you used the GET method.

* The data to be sent is in any encoding other than seven-bit ASCII.

 

Use the GET method if all the following are true:

* The request is to find a resource, and HTML form data is used to help that search.

* The result of the request has no persistent side effects.

* The data collected and the input field names in a HTML form are in total less than 1,024 characters in size.

 

2.6 分布式事务---数据最终一致性保证

1.异步:MQ

2.同步调用:基于异步补偿的分布式事务,本节重点

分布式事务的目的:将非幂等设计转变为幂等设计,必须保证核心接口的幂等性。

这篇讲得不错:http://blog.csdn.net/congyihao/article/details/70195154

下面为58讲师的例子

场景:确认收货,打款给商家这两个操作,两者不在同一库中,不能使用事务一次跑完。

在分布式事务集群内,分了两张表,事务组表,存要跑的事务;事务调用表,存已执行的事务。

分三步A->B->C,A为更新货物状态,B发起自己平台扣款,C对方余额增加

 

 

 

事务组表

txid

state

t1

1->2->3

事务调用表

txid

actionid

callFunc

parameter

value

T1

1

CA

PA

PAV

T1

2

CB

PB

PBV

T1

3

CC

PC

PCV

假如在执行C时失败了,立即回滚,state更新到3,根据事务调用表补偿之前造成的影响,而不是重新再试着执行C,这会造成用户感知到,引发重发操作。

 

 

扩展:

http://www.roncoo.com/article/detail/12424上我们讨论的补偿型TCC(Try-Confirm-Cancle)是58里说的

http://blog.csdn.net/congyihao/article/details/70195154

概念澄清

事务补偿机制: 在事务链中的任何一个正向事务操作, 都必须存在一个完全符合回滚规则的可逆事务.

CAP理论: CAP(Consistency, Availability, Partition Tolerance), 阐述了一个分布式系统的三个主要方面, 只能同时择其二进行实现. 常见的有CP系统, AP系统.

幂等性: 简单的说, 业务操作支持重试, 不会产生不利影响. 常见的实现方式: 为消息额外增加唯一ID.

BASE(Basically avaliable, soft state, eventually consistent): 是分布式事务实现的一种理论标准.

 

柔性事务 vs. 刚性事务

刚性事务是指严格遵循ACID原则(Atomatic/Consistence/Isolation)的事务, 例如单机环境下的数据库事务.

柔性事务是指遵循BASE理论的事务, 通常用在分布式环境中, 常见的实现方式有: 两阶段提交(2PC), TCC补偿型提交, 基于消息的异步确保型, 最大努力通知型.

通常对本地事务采用刚性事务, 分布式事务使用柔性事务.

 

最佳实践

如果业务场景需要强一致性, 那么尽量避免将它们放在不同服务中, 也就是尽量使用本地事务, 避免使用强一致性的分布式事务.

如果业务场景能够接受最终一致性, 那么最好是使用基于消息的最终一致性的方案(异步确保型)来解决.

如果业务场景需要强一致性, 并且只能够进行分布式服务部署, 那么最好是使用TCC方案而不是2PC方案来解决.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值