DevOps和自动化运维实践
上QQ阅读APP看书,第一时间看更新

1.3 Web编程相关体系知识点

很多时候,我们从事DevOps(包括自动化运维)的工作就是将运维工作Web化、API化,这往往会牵涉大量后端开发知识,所以我们需要掌握Web编程相关的知识体系,了解前后端开发工作的不同,这样才能做好DevOps的工作。

1.3.1 为什么要前后端分离

相信大家对前后端分离的开发模式已经不陌生了,其Web架构也比较简单,如图1-2所示。

图1-2 前后端分离的Web架构图示

对于传统的一体式Web架构,大家会发现,业务逻辑处理单独分离出来了,交由后端统一处理,如果从软件开发的层面上来理解,则前端与后端分别处理如下内容。

前端:负责View和Controller层。

后端:只负责Model层,进行业务处理和数据处理等。

为什么要这样做呢?

下面我就以自己所在公司的研发团队来解释下这个问题,目前公司存在着PHP和Java技术团队,另外还有Go/Python后端研发团队和移动端开发团队,这几个团队虽然是在做不同的产品,但是仍然存在大量重复性的开发。比如用PHP编写了组织机构相关的页面,用JSP又要再写一遍。在这种情况下,团队就会开始思考这样一个方案:如果前端实现与后端技术无关,那么页面呈现的部分就可以共用,不同的后端技术只需要实现各自的后端业务逻辑就好。

方案要解决的根本问题是将数据和页面剥离开来。应对这种需求的技术是现成的,前端采用静态网页相关的技术,HTML + CSS + JavaScript,通过AJAX技术调用后端提供的业务接口(主要是PHP研发团队负责)。前后端协商好接口方式,通过HTTP来提供实现,统一使用POST提交数据。接口数据结构使用JSON实现,前端jQuery解析JSON很方便,后端处理JSON的工具就更多了。

这种架构从本质上来说就是SOA(面向服务的架构)。当后端不提供页面,只是纯粹地通过Web API来提供数据和业务的交互能力之后,Web前端就变成了纯粹的客户端角色,与WinForm、移动终端应用属于同样的角色,可以把它们合在一起,统称为前端。以前的一体化架构需要定制页面来实现Web应用,同时还需要定义一套WebService/WSDL来对WinForm和移动终端提供服务。转换为新的架构之后,可以统一使用Web API形式为所有类型的前端提供服务。至于某些类型的前端对该Web API进行的RPC封装,那又是另外一回事了。

通过这样的架构改造,前后端实际上就已经分离开了。抛开其他类型的前端不提,这里只讨论Web前端和后端。由于分离,Web前端在开发的时候完全不需要了解后端使用的是什么技术,只需要知道后端提供的接口及其可以实现的功能即可,而不必去了解Golang/Python、Java/JEE、NoSQL数据库等技术。后端的Go/Python团队和Java团队也脱离了逻辑无关的美学思维,不需要面对美工精细的界面设计约束,也不需要在思考逻辑实现的同时还要去考虑页面上的布局问题,只需要处理自己擅长的逻辑和数据即可。

前后端分离之后,两端的开发人员都可以轻松不少,由于技术和业务都变得更为专注,因此开发的效率也得到了提高。分离带来的好处也会逐渐体现出来,具体如下。

(1)前后职责分离

前端倾向于呈现,着重处理用户体验相关的问题;后端则倾向于处理业务逻辑、数据处理和持久化等相关的问题。在设计清晰的情况下,后端只需要以数据为中心对业务处理算法负责,并按约定为前端提供API;而前端则使用这些接口对用户体验负责即可。

(2)前后技术分离

前端可以不用了解后端技术,也不必关心后端具体的实现技术,只需要会HTML、CSS、JavaScript就能入手;而后端只需要关心后端的开发技术,这样就省去了学习前端技术的麻烦,连Web框架的学习研究都只需要关注Web API即可,而不用去关注基于页面视图的MVC技术(并不是说不需要MVC技术,Web API的数据结构呈现也是View),不用考虑特别复杂的数据组织和呈现。

(3)前后分离带来了用户体验和业务处理解耦

前端可以根据用户不同时期的体验需求迅速改版,对后端毫无影响。同理,后端进行的业务逻辑升级,数据持久方案变更,只要不影响到接口,前端也可以毫不知情。当然如果是需求变更引起了接口变化,那么前后端又需要在一起进行信息同步了。

(4)前后分离,可以分别归约两端的设计

后端只提供API服务,而不必考虑页面呈现的问题。实现SOA架构的API可以服务于各种前端,而不仅仅是Web前端,可以做到一套服务,各端使用。

注意

自动化运维的开发工作很多都会涉及API的封装,所以其更偏后端开发一些。

参考文档:

https://juejin.im/post/5a5380a6518825733365e6za.

1.3.2 什么是RESTful

RESTful是目前最为流行的一种互联网软件架构。因为它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。本节我们将学习RESTful到底是一种什么样的架构。

什么是REST?

REST(Representational State Transfer)这个概念首次出现是在2000年Roy Thomas Fielding(他是HTTP规范的主要编写者之一)的博士论文中,它指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful的。

要理解什么是REST,我们需要理解如下的几个概念。

REST是“表现层状态转化”,它省略了主语。其实“表现层”指的是“资源”的“表现层”。

那么什么是资源(Resources)呢?就是我们平常上网访问的一张图片、一个文档、一个视频等。这些资源我们通过URI来定位,也就是一个URI表示一个资源。

(1)表现层(Representation)

资源是做一个具体的实体信息,其可以有很多种展现方式。而把实体展现出来就是表现层,例如一个txt文本信息,可以输出成html、json、xml等格式,一个图片可以通过jpg、png等方式展现,这个就是表现层的意思。

URI确定一个资源,但是如何确定它的具体表现形式呢?应该在HTTP请求的头信息中用Accept和Content-Type字段进行指定,这两个字段才是对“表现层”的描述。

(2)状态转化(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程。这个过程肯定会涉及数据和状态的变化。而HTTP是无状态的,那么这些状态肯定会保存在服务器端,所以如果客户端想要通知服务器端改变数据和状态的变化,则肯定是需要通过某种方式来通知它。

客户端能通知服务器端的手段,只能是HTTP。具体来说,就是HTTP里面,有几个操作方式的动词。

HTTP动词具体包括如下几个。

GET:从服务器端取出资源(一项或多项)。

POST:在服务器端新建一个资源。

PUT:在服务器端更新资源(客户端提供改变后的完整资源)。

PATCH:在服务器端更新资源(客户端提供改变的属性)。

DELETE:从服务器端删除资源。

HEAD:获取资源的元数据。

OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

RESTful用一句话可以总结为URL定位资源,用HTTP动词描述操作。符合REST原则的架构方式即称之为RESTful。

综合上面的解释,我们下面来总结一下什么是RESTful架构。

1)每一个URI代表一种资源。

2)在客户端和服务器之间传递这种资源的某种表现层。

3)客户端通过HTTP动词,对服务器端资源进行操作,实现“表现层状态转化”。

(3)HTTP状态码

对于HTTP状态码,大家应该已经很熟了,即服务器向用户返回的状态码和提示信息,常见的HTTP状态码如表1-1所示。

表1-1 HTTP状态码详细定义图表

1.3.3 Web后台认证机制

本节将介绍几种常用的认证机制,具体如下。

(1)HTTP Basic Auth

简言之,HTTP Basic Auth简单点说就是每次请求API时都提供用户的username和password,是配合RESTful API使用的最简单的认证方式,只需提供用户名和密码即可,但由于存在将用户名和密码暴露给第三方客户端的风险,因此在生产环境下,HTTP Basic Auth被使用得越来越少。因此,在开发对外开放的RESTful API时,应尽量避免采用HTTP Basic Auth。

(2)OAuth

OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一Web服务上存储的私密的资源(如照片、视频、联系人列表),而无须将用户名和密码提供给第三方应用。

OAuth允许用户提供一个令牌,而不是根据用户名和密码来访问他们存放在特定服务提供者处的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时间段(例如,接下来的2个小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth就使得用户可以授权第三方网站访问他们存储在另外服务提供者处的某些特定信息,而非所有内容。

如图1-3所示的是OAuth2.0的工作流程。

图1-3 OAuth2.0的工作流程图

这种基于OAuth的认证机制适用于个人消费者类的互联网产品,比如社交类APP等应用,但是其不太适合拥有自有认证权限管理的企业应用。

(3)Cookie Auth

Cookie认证机制就是为一次请求认证在服务器端创建一个Session对象,同时在客户端的浏览器端创建一个Cookie对象;通过客户端发送的Cookie对象与服务器端的Session对象进行匹配来实现状态管理。默认情况下,当我们关闭浏览器的时候,Cookie会被删除,但是可以通过修改Cookie的expire time使Cookie在一定时间内有效。

(4)Token Auth

使用基于Token的身份验证方法时,服务器端不需要存储用户的登录记录。大概的流程如下所示。

1)客户端使用用户名与密码请求登录。

2)服务器端收到请求,去验证用户名与密码。

3)验证成功后,服务器端会签发一个Token,再把这个Token发送给客户端。

4)客户端收到Token之后可以把它存储起来,比如放在Cookie里或者Local Storage里。

5)客户端每次向服务器端请求资源的时候都需要带着服务器端签发的Token。

6)服务器端收到请求,然后去验证客户端请求中所携带的Token,如果验证成功,就向客户端返回请求的数据。

(5)Token Auth的优点

Token机制相对于Cookie机制来说,具有如下好处。

支持跨域访问: Cookie是不允许跨域访问的,这一点对Token机制来说是不存在的,Token机制支持跨域访问的前提是用户认证信息通过HTTP头传输。

无状态(也称服务器端可扩展行):Token机制在服务器端不需要存储Session信息,因为Token自身包含了所有登录用户的信息,因此只需要在客户端的Cookie或本地介质中存储状态信息即可。

更适用CDN:可以通过内容分发网络请求你服务器端的所有资料(如JavaScript、HTML及图片等),而你的服务器端只要提供API即可。

去耦:不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,当你的API被调用的时候,直接进行Token生成调用即可。

更适用于移动应用:当我们的客户端是一个原生平台(iOS、 Android或Windows 8等)时,Cookie是不被支持的(需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。

CSRF:因为不再依赖于Cookie,所以Token机制不需要考虑对CSRF(跨站请求伪造)的防范。

性能:一次网络往返时间(通过数据库查询Session信息)总比做一次HMACSHA256计算的Token验证和解析要费时得多。

不需要为登录页面做特殊处理:如果使用的是Protractor做功能测试,那么我们不再需要为登录页面做特殊处理。

基于标准化:这个标准已经存在多个后端库(如.NET、 Ruby、Java、Python和PHP)和多家公司的支持(如Firebase、Google和Microsoft)。

参考文档:

http://www.cnblogs.com/xiekeli/p/5607107.html

1.3.4 同步和异步、阻塞与非阻塞的区别

正如大家所知道的,Nginx的模型是异步非阻塞模型,另外,还有Node.js,这些软件均适用于高性能、高并发场景,事实上,很多DevOps的开发工作都会接触到同步和异步及阻塞与非阻塞,那么我们应该怎么理解它们呢?

(1)同步与异步

同步和异步关注的是消息通信机制。

所谓同步,就是在发出一个“调用”时,在没有得到结果之前,该“调用”不返回;但是一旦调用返回,就会得到返回值了。换句话说,就是由“调用者”主动等待这个“调用”的结果。而异步则正好相反,在发出“调用”之后,这个“调用”就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出之后,“调用者”不会立刻得到结果。而是在“调用”发出之后,“被调用者”通过状态、通知来通知调用者,或者通过回调函数来处理这个调用。

举个通俗一点的例子,比如你打电话问书店老板有没有《DevOps和自动化运维实践》这本书,如果是同步通信机制,则书店老板会说:“你稍等,我查一下”,然后开始进行查找,等到查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而如果是异步通信机制,则书店老板将会直接告诉你:“我查一下啊,查好了打电话给你”,然后直接挂电话了(不返回结果)。待到查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来进行回调。

(2)阻塞与非阻塞

阻塞与非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态。

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用是指在不能立刻得到结果之前,该调用不会阻塞当前线程。还是上面的例子,当你打电话问书店老板有没有《DevOps和自动化运维实践》这本书时,如果是阻塞式调用,那么你会一直把自己“挂起”,直到得到有没有这本书的结果;如果是非阻塞式调用,那么不管老板有没有告诉你,你自己先去做别的事情,当然你偶尔也要过几分钟检查一下老板有没有返回结果。在这里,阻塞与非阻塞与是否同步异步无关,也与老板回答你结果的方式无关。

参考文档:

https://www.zhihu.com/question/19732473/answer/20851256

1.3.5 WebSocket双工通信

在了解WebSocket协议之前,我们先来了解一下什么是Socket(套接字)。随着TCP/IP的广泛使用,Socket(套接字)也越来越多地被使用在网络应用程序的构建中。实际上,Socket编程已经成为网络中传送和接收数据的首选方法。套接字相当于应用程序访问下层网络服务的接口,使用套接字,不同的主机之间可以进行通信,从而实现数据交换。Socket通信则用于在双方建立起连接之后直接进行数据的传输,还可以在连接时实现信息的主动推送,而不需要每次都由客户端向服务器端发送请求。Socket的主要特点包括数据丢失率低,使用简单且易于移植等。

套接字在工作的时候会将连接的对端分成服务器端和客户端,服务器程序将在一个众所周知的端口上监听服务请求,换句话说,服务进程始终是存在的,直到有客户端的访问请求唤醒服务器进程为止,此时,服务器进程会与客户端进程之间进行通信,交换数据。Socket服务器端与客户端通信的流程图如图1-4所示。

图1-4 Socket服务器端与客户端通信过程流程图

接下来我们再来了解什么是WebSocket。

WebSocket协议于2008年诞生,于2011年成为国际标准。基本上所有的浏览器都已经支持了。

WebSocket的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

WebSocket是HTML5的重要特性,它实现了基于浏览器的远程Socket,它使浏览器与服务器可以进行全双工通信,许多浏览器(Firefox、Google Chrome和Safari)都已对此提供了支持。在WebSocket出现之前,为了实现即时通信,采用的技术都是“轮询”,即在特定的时间间隔内,由浏览器对服务器发出HTTP Request,服务器在收到请求之后,返回最新的数据给浏览器刷新。“轮询”使得浏览器需要向服务器不断发出请求,这样会占用大量的带宽。

WebSocket采用了一些特殊的报头,使得浏览器和服务器只需要做一个握手的动作,就可以在浏览器和服务器之间建立一条连接通道。且此连接会保持在活动状态,我们可以使用JavaScript来向连接写入或从中接收数据,就像在使用一个常规的TCP Socket一样。它解决了Web实时化的问题,相比传统HTTP, WebSocket具有如下好处。

❑ 一个Web客户端只建立一个TCP连接。

❑ WebSocket服务端可以推送(PUSH)数据到Web客户端。

❑ 具有更加轻量级的头,减少了数据传送量。

提示

了解WebSocket可以让我们更方便地进行DevOps工作,工作中遇到一个特殊场景时,业务需要我们部署HTTPS,但证书不能部署在负载均衡器上,而必须部署在后面的Web服务器上。如果我们有这些需求,则可以考虑使用WebSocket:1)多个用户之间需要进行交互;2)需要频繁地向服务端请求更新数据。比如弹幕、消息订阅、多玩家游戏、协同编辑、股票基金实时报价、视频会议、在线教育等需要高实时交互的场景。在这些场景中,我们都可以考虑使用WebSocket。

1.3.6 了解消息中间件

对于消息中间件,其实很多读者都应该不会陌生了,事实上,无论是我们设计电商系统的秒杀系统,还是设计自动化运维后端的API异步任务,还有大数据系统中常用的分布式发布-订阅消息系统Kafka,消息中间件都存在于很多Web平台系统或APP产品中,所以这里也希望大家花时间和精力来了解一下消息中间件,这里以常见的RabbitMQ来进行说明。

1. RabbitMQ基础介绍

(1)什么是RabbitMQ

RabbitMQ是由Erlang语言编写的、实现了高级消息队列协议(AMQP)的开源消息代理软件(也可称为面向消息的中间件)。支持Windows、Linux、Mac OS X操作系统和包括Java在内的多种编程语言。

AMQP(Advanced Message Queuing Protocol)是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并且不受“客户端/中间件”不同产品、不同开发语言等条件的限制。

(2)RabbitMQ的基础概念

Broker:经纪人。提供一种传输服务,维护一条从生产者到消费者的传输线路,保证消息数据能够按照指定的方式进行传输。粗略地可以将图1-5中的RabbitMQ Server当作Broker。

图1-5 RabbitMQ的工作流程图示例

Exchange:消息交换机。指定消息按照什么规则路由到哪个队列Queue。

Queue:消息队列。消息的载体,每条消息都会被投送到一个或多个队列中。

Binding:绑定。作用就是将Exchange和Queue按照某种路由规则绑定起来。

RoutingKey:路由关键字。Exchange根据RoutingKey进行消息投递。

Vhost:虚拟主机。一个Broker可以有多个虚拟主机,用于进行不同用户的权限分离。一个虚拟主机持有一组Exchange、Queue和Binding。

Producer:消息生产者。主要将消息投递到对应的Exchange上面,一般是独立的程序。

Consumer:消息消费者。消息的接收者,一般是独立的程序。

Channel:消息通道,也称信道。在客户端的每个连接里可以建立多个Channel,每个Channel代表一个会话任务。

(3)Rab bitMQ的使用流程

AMQP模型中,消息在producer中产生,发送到MQ的Exchange上,Exchange根据配置的路由方式投递到相应的Queue上,Queue又将消息发送给已经在此Queue上注册的Consumer,消息从Queue到Consumer有push和pull两种方式,如图1-5所示。

消息队列的使用过程具体如下。

1)客户端连接到消息队列服务器,打开一个Channel。

2)客户端声明一个Exchange,并设置相关的属性。

3)客户端声明一个Queue,并设置相关的属性。

4)客户端使用RoutingKey,在Exchange和Queue之间建立好Binding关系。

5)生产者客户端投递消息到Exchange。

6)Exchange接收到消息之后,就根据消息的RoutingKey和已经设置好的Binding,进行消息路由(投递),将消息投递到一个或多个队列里。

7)消费者客户端从对应的队列中获取并处理消息。

(4)Rab bitMQ的优缺点

Rab bitMQ的优点具体如下。

1)由Erlang语言开发,支持大量协议:AMQP、XMPP、SMTP、STOMP。

2)支持消息的持久化、负载均衡和集群,且集群易扩展。

3)具有一个Web监控界面,易于管理。

4)安装部署简单,容易上手,功能丰富,强大的社区支持。

5)支持消息确认机制、灵活的消息分发机制。

Rab bitMQ的缺点具体如下。

1)由于牺牲了部分性能来换取稳定性,比如消息的持久化功能,使得RabbitMQ在大吞吐量性能方面不及Kafka和ZeroMQ。

2)由于支持多种协议,因此RabbitMQ非常重量级,比较适合于企业级开发。

2. RabbitMQ的应用场景

在介绍RabbitMQ之前,我们先介绍下常见的发布-订阅系统,如图1-6所示。

图1-6 发布-订阅消息模型图

我们很多人都订阅过杂志,其过程很简单。只要告诉邮局我们所要订阅的杂志名和投递的地址,然后付款即可。出版社会将所出版的杂志定期交给邮局,邮局会根据订阅的列表,将杂志送达消费者手中。这样我们就可以看到每一期精彩的杂志了。

仔细思考一下订阅杂志的过程,我们会发现其具有如下几个特点。

1)消费者订阅杂志不需要直接找出版社。

2)出版社只需要把杂志交给邮局。

3)邮局将杂志送达给消费者。

邮局在整个过程中扮演了非常重要的中转作用,在出版社和消费者相互不需要知道对方的情况下,邮局完成了杂志的投递工作。好了,在这里大家将出版社想象成生产者,邮件想象成消息管理器,现在是不是很容易就能理解发布-订阅消息模型呢?

接下来我们再来说一下RabbitMQ在工作中的常用应用场景。

(1)异步处理

场景说明

用户注册后,需要发送注册邮件和注册短信,传统的做法包括两种:串行的方式和并行的方式。

串行方式

如图1-7所示,将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。这里有一个问题就是:邮件、短信并不是必需的,它只是一个通知,而这种做法会让客户端等待没有必要等待的东西。

图1-7 串行工作方式的图示

并行方式

如图1-8所示,将注册信息写入数据库后,在发送邮件的同时发送短信,以上三个任务完成后,返回给客户端,并行的方式能够节省处理的时间。

图1-8 并行工作方式图示

假设三个业务节点使用时间均为50ms,那么串行方式使用时间为150ms,并行方式使用时间为100ms。虽然并行方式已经降低了处理时间,但是,前面说过,邮件和短信对正常的使用网站没有任何影响,客户端没有必要等待其发送完成才显示注册成功,应该是写入数据库后就返回。

消息队列

引入消息队列后,消息队列可以对发送邮件、短信这类非必需的业务逻辑进行异步处理,如图1-9所示。

图1-9 引入消息队列后的工作图示

由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间加上写入消息队列的时间(可以忽略不计),引入消息队列后处理的响应时间是串行的,是并行的

(2)流量削峰

流量削峰一般广泛应用于秒杀活动中。

场景说明

秒杀活动一般会因为流量过大而导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列,如图1-10所示。

图1-10 消息队列在秒杀活动中的应用图示

作用

1)可以控制活动的人数,超过一定阈值的订单将被直接丢弃。

2)可以缓解短时间的高流量压垮应用(应用程序按照自己的最大处理能力获取订单)。

使用MQ消息队列,这就好比是为了防汛而建造葛洲坝,具有堆积大量数据的能力,然后可靠地进行异步输出。

(3)应用解耦

场景说明

双11是购物狂欢节,用户下单后,订单系统需要通知库存系统。传统的做法就是订单系统调用库存系统的接口,如图1-11所示。

图1-11 传统的订单系统和库存系统的工作流程图示

这种做法有一个缺点,那就是当库存系统出现故障时,订单就会失败。为了使得订单系统与库存系统高耦合,我们在此引入消息队列,如图1-12所示。

图1-12 引入消息队列后的订单系统和库存系统的工作图示

在这里我们可以参考下前面的发布-订阅消息模型。

订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功信息。

库存系统:订阅下单的消息,获取下单的消息,进行库操作。

就算库存系统出现故障,消息队列也能保证消息的可靠投递,而不会导致消息丢失。

事实上,RabbitMQ的应用场景并不只有以上三种常见场景,还有很多种场景都适合应用RabbitMQ,最后我们总结下RabbitMQ在工作中的应用场景,具体包括以下几种。

1)跨系统的异步通信,所有需要异步交互的地方都可以使用消息队列。就像我们除了打电话(同步)以外,还需要有发短信、发电子邮件(异步)的通信方式。

2)多个应用之间的耦合,由于消息是平台无关和语言无关的,而且语义上也不再是函数调用,因此其更适合作为多个应用之间的松耦合的接口。基于消息队列的耦合,不需要发送方和接收方同时在线。在企业应用集成(EAI)中,文件传输、共享数据库、消息队列、远程过程调用都可以作为集成的方法。

3)应用内的同步变异步,比如订单处理就可以由前端应用将订单信息放到队列中,后端应用从队列里依次获得消息处理,高峰时的大量订单可以积压在队列里慢慢处理掉。由于同步通常意味着阻塞,而大量线程的阻塞会降低计算机的性能。

4)消息驱动的架构(EDA),系统可分解为消息队列、消息制造者和消息消费者,一个处理流程可以根据需要拆成多个阶段(Stage),各阶段之间可用队列连接起来,将前一个阶段处理的结果放入队列,后一个阶段从队列中获取消息然后继续处理。

5)应用需要更灵活的耦合方式,比如发布订阅,比如可以指定路由规则等。

6)跨局域网,甚至跨城市的通信,比如北京机房与广州机房的应用程序间的通信。

参考文档:

http://blog.csdn.net/whoamiyang/article/details/54954780

https://blog.csdn.net/leixiaotao_java/article/details/78909760

1.3.7 了解负载均衡高可用

在Web的架构设计工作中,我们经常接触到的是Linux集群,即负载均衡高可用,所以了解其基本知识点也是很重要的。

1.服务器健康检测

负载均衡器现如今都使用了非常多的服务器健康检测技术,主要方法是通过发送不同类型的协议包并检查能否接收到正确的应答来判断后端的服务器是否存活,如果后端的服务器出现故障就会自动剔除。主要的服务器健康检测技术包括以下三种。

ICMP:负载均衡器向后端的服务器发送ICMP ECHO包(就是我们俗称的“ping”),如果能正解收到ICMP REPLY,则证明服务器ICMP处理正常,即服务器是“活着”的。

TCP:负载均衡器向后端的某个端口发起TCP连接请求,如果成功完成三次握手,则证明服务器TCP处理正常。

HTTP:负载均衡器向后端的服务器发送HTTP请求,如果收到的HTTP应答内容是正确的,则证明服务器HTTP处理正常。

下面以Nginx为例来简单说明一下,具体如下所示。

upstream模块是Nginx负载均衡的主要模块,其提供了简单的办法用于实现在轮询和客户端IP之间的后端服务器上进行负载均衡,并且还可以对服务器进行健康检查。upstream并不处理请求,而是通过请求后端服务器得到用户的请求内容。在转发给后端时,默认是轮询方式,代码如下所示。下面是一组服务器负载均衡的集合:

        upstream php_pool {
            server 192.168.1.7:80 max_fails=2 fail_timeout=5s;
            server 192.168.1.8:80 max_fails=2 fail_timeout=5s;
            server 192.168.1.9:80 max_fails=2 fail_timeout=5s;
            }

upstream模块的相关指令其解释具体如下。

max_fails:定义可以发生错误的最大次数。

fail_timeout:若Nginx在fail_timeout设定的时间内与后端服务器通信失败的次数超过了max_fails设定的次数,则认为这个服务器不再起作用;在接下来的fail_timeout时间内,Nginx不再将请求分发给失效的机器。

down:把后端标记为离线,仅限于ip_hash。

backup:标记后端为备份服务器,当后端服务器全部无效时才启用。

Nginx的健康检查主要体现在对后端服务提供健康检查,且功能被集成在upstream模块中,其包含两个指令:max_fails和fail_timeout。

健康检查机制具体如下。

在检测到后端服务器故障之后,Nginx依然会把请求转向该服务器,当Nginx发现timeout或者refused后,则会把该请求会分发到upstream的其他节点,直到获得正常数据之后,Nginx才会将数据返回给用户,这也体现了Nginx的异步传输。这一点与LVS/HAProxy区别很大,在LVS/HAProxy里,每个请求都只有一次机会,假如用户发起一个请求,结果该请求分到的后端服务器刚好出现了故障,那么这个请求就失败了。

2.会话保持及其具体实现

会话保持并非Session共享。

在大多数的电子商务应用系统中,或者需要进行用户身份认证的在线系统中,一个客户与服务器经常需要经过好几次的交互过程才能完成一笔交易或一个请求。由于这几次交互过程是密切相关的,因此服务器在进行这些交互的过程中,要完成某一个交互步骤往往需要了解上一次交互的处理结果,或者上几步的交互结果,这就要求所有相关的交互过程都必须由一台服务器来完成,而不能被负载均衡器分散到不同的服务器上。

而这一系列相关的交互过程可能是由客户端到服务器端的一个连接的多次会话完成的,也可能是在客户端与服务器端之间的多个不同连接里的多次会话完成的。关于不同连接的多次会话,最典型的例子就是基于HTTP的访问,一个客户完成一笔交易可能需要多次点击,而一个新的点击所产生的请求,可能会重用上一次点击建立起来的连接,也可能是一个新建的连接。

会话保持是指在负载均衡器上存在这么一种机制,可以识别客户与服务器之间交互过程的关联性,在进行负载均衡的同时,还能保证一系列相关联的访问请求被分配到同一台服务器上。

负载均衡器的会话保持机制

会话保持机制的目的是保证在一定时间内某个用户与系统会话只交给同一台服务器进行处理,这一点在满足网银、网购等应用场景的需求时格处重要。负载均衡器实现会话保持一般包含如下几种方案。

1)基于源IP地址的持续性保持:主要用于四层负载均衡,这种方案应该是大家最为熟悉的会话保持方案,LVS/HAProxy、Nginx都有类似的外理机制,Nginx具有ip_hash算法,HAProxy具有source算法。

2)基于Cookie数据的持续性保持:主要用于七层负载均衡,用于确保同一会话的报文能够被分配到同一台服务器中。其中,根据服务器的应答报文中是否携带含有服务器信息的Set_Cookie字段,又可以分为Cookie插入保持和Cookie截取保持。

3)基于HTTP报文头的持续性保持:主要用于七层负载均衡,当负载均衡器接收到某一个客户端的首次请求时,会根据HTTP报文头关键字建立持续性表项,记录下为该客户端分配的服务器情况,在会话表项的生存期内,后续具有相同HTTP报文头信息的连接都将发往该服务器进行处理。

3.什么是Session

Session在网络应用中常称为“会话”,借助它可以提供服务器端与客户端系统之间必要的交互。因为HTTP协议本身是无状态的,所以经常需要通过Session来解决服务器端和浏览端的保持状态的解决方案。Session是由应用服务器维持的一个服务器端的存储空间,用户在连接服务器时,会由服务器生成一个唯一的SessionID,该SessionID可作为标识符用于存取服务器端的Session存储空间。

SessionID这一数据是保存到客户端的,用Cookie进行保存,用户提交页面时,会将这一SessionID提交到服务器端,来存取Session数据。服务器端也可以通过URL重写的方式来传递SessionID的值,因此它不是完全依赖于Cookie的。如果客户端Cookie禁用,则服务器端可以通过重写URL的方式来自动保存Session的值,并且这个过程对程序员是透明的。

什么是Session共享?

随着网站业务规模和访问量的逐步增大,原本由单台服务器、单个域名组成的迷你网站架构可能已经无法满足发展的需要了。

此时我们可能会购买更多的服务器,并且以频道化的方式启用多个二级子域名,然后根据业务功能将网站分别部署在独立的服务器上,或者通过负载均衡技术(如Haproxy、Nginx)使得多个频道共享一组服务器。

如果我们把网站程序分别部署到多台服务器上,而且独立为几个二级域名,由于Session存在实现原理上的局限性(PHP中Session默认以文件的形式保存在本地服务器的硬盘上),这就使得网站用户不得不经常在几个频道之间来回输入用户名和密码登录,导致用户体验大打折扣;另外,原本程序可以直接从用户Session变量中读取的资料(例如昵称、积分、登入时间等),因为无法跨服务器同步更新Session变量,因此开发人员必须实时读写数据库,从而增加了数据库的负担。于是,解决网站跨服务器的Session共享问题的需求变得迫切起来,最终催生了多种解决方案,下面列举3种较为可行的方案来进行对比和探讨。

(1)基于Cookie的Session共享

对于这个方案我们可能会比较陌生,但它在大型网站中应用普遍。其原理是对全站用户的Session信息加密、序列化后以Cookie的方式统一种植在根域名下(如“.host.com”)。当浏览器访问该根域名下的所有二级域名站点时,与域名相对应的所有Cookie内容的特性都将传递给它,从而实现用户的Cookie化Session在多服务器间的共享访问。

这个方案的优点是无须额外的服务器资源;缺点是由于受HTTP协议头信息长度的限制,其仅能够存储小部分的用户信息,同时Cookie化的Session内容需要进行安全加解密(如采用DES、RSA等进行明文加解密;再由MD5、SHA-1等算法进行防伪认证),另外它也会占用一定的带宽资源,因为浏览器会在请求当前域名下的任何资源时将本地Cookie附加在HTTP头中传递到服务器上。

(2)基于数据库的Session共享

数据库的首选当然是大名鼎鼎的MySQL数据库,这里建议使用内存表Heap,以提高Session操作的读写效率。这个方案的实用性比较强,相信大家普遍都在使用。它的缺点在于Session的并发读写能力取决于MySQL数据库的性能;同时还需要我们自己来实现Session淘汰逻辑,以便定时地从数据表中更新、删除Session记录;当并发过高时容易出现表锁,虽然我们可以选择行级锁的表引擎,但不可否认的是,使用数据库存储Session还是有些杀鸡用牛刀的架势。

(3)Session复制

熟悉Tomcat或Weblogic的朋友对Session复制应该是非常熟悉和了解了,仅从字面意义上也非常好理解。Session复制就是将用户的Session复制到Web集群内的所有服务器上,Tomcat或Weblogic自身都携带了这种处理机制。但其缺点也很明显:随着机器数量的增加,网络负担成指数级上升,性能也将随着服务器数量的增加而急剧下降,而且很容易引起网络风暴。

(4)基于Memcache/Redis的Session共享

Memcache是一款基于Libevent的多路异步I/O技术的内存共享系统,简单的Key +Value数据存储模式使其代码逻辑小巧高效,因此在并发处理能力上其占据了绝对优势。

另外值得一提的是,Memcache的内存hash表所特有的Expires数据过期淘汰机制,正好与Session的过期机制不谋而合,这就降低了删除过期Session数据的代码复杂度。但对比“基于数据库的存储方案”,仅逻辑这块就给数据表带来了巨大的查询压力。

redis作为NoSQL的后起之秀,经常拿来与memcached作对比。redis作为一种缓存,或者干脆称之为NoSQL数据库,提供了丰富的数据类型(list、set等),可以将大量数据的排序从单机内存解放到redis集群中进行处理,并且可以用于实现轻量级消息中间件。在性能比较方面,redis在小于100KB的数据读写上其速度优于memcached。在我们所用的系统中,redis已经取代了memcached存放Session数据。

4.了解其常见算法

工作中,负载均衡器的算法还是很多的,例如LVS的rr、wrr及wlc等,还有Nginx的rr、weight、ip_hash及一致性Hash算法,这里以Nginx的常见算法为例来进行说明。

Nginx的常见算法,具体如下所示。

轮询(默认)

各个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器出现故障,则会跳过该服务器分配至下一个监控的服务器,并且它无须记录当前所有连接的状态,所以它是一种无状态调度。

weight

指定在轮询的基础上加上权重,weight和访问比率成正比,即用于表明后端服务器性能的好坏,这种情况特别适合于后端服务器性能不一致的工作场景。

ip_hash

每个请求均按访问IP的Hash结果进行分配,当新的请求到达时,先将其客户端IP通过哈希算法进行计算得出一个值,在随后的请求中,客户端IP的哈希值只要是相同的,就会分配至同一个后端服务器,该调度算法可以解决Session的问题,但有时也会导致分配不均,即无法保证负载均衡。

fair(第三方)

按后端服务器的响应时间来分配请求,响应时间短的将会优先分配。

url_hash(第三方)

按访问URL的Hash结果来分配请求,使每个URL都定向到同一个后端服务器,后端服务器为缓存时会比较有效。

在upstream中加入Hash语句,server语句中不能写入weight等其他的参数,hash_method表示所使用的Hash算法,如下所示:

        upstream web_pool {
            server squid1:3128;
            server squid2:3128;
            hash $request_uri;
            hash_method crc32;
        }

Tengine增加的一致性Hash算法

Tengine增加的一致性Hash算法应该是借鉴了目前最为流行的一致性Hash算法思路,其具体实现如下。

将各个Server虚拟成N个节点,均匀分布到Hash环上,每次请求都将根据配置的参数计算出一个Hash值,在Hash环上查找离这个Hash值最近的虚拟节点,对应的server将作为该次请求的后端机器,这样做的好处是如果动态地增加机器,或者某台Web机器发生崩溃情况,则对整个集群的影响最小。

Nginx作为负载均衡机器,其所提供的upstream模块的ip_hash算法机制(操持会话)能够将某个IP的请求定向到同一台后端服务器上,这样一来,该IP下的某个客户端和某个后端服务器就能建立起稳固的连接了。在Nginx的各种算法中,ip_hash也是我们应用得最多的算法之一。我们可以利用其保持会话的特性来提高缓存命中(在CDN体系中这是一个很重要的技术指标)。图1-13所示的是ip_hash在某小型安全CDN项目中的应用,Cache主要由两层缓存层组成,包括边缘CDN及父层CDN缓存机器,缓存这块所利用的就是Nginx本身的Cache机制。

图1-13 边缘缓存机器及父层缓存机器架构图示

边缘CDN机器nginx.conf的相关配置文件如下所示:

        upstream parent {
            ip_hash;
            server 119.90.1.2 max_fails=3 fail_timeout=20s;
              server 119.90.1.3 max_fails=3 fail_timeout=20s;
              server 61.163.1.2 max_fails=3 fail_timeout=20s;
              server 61.163.1.3 max_fails=3 fail_timeout=20s;
            }

由上述可知,大家可以发现边缘CDN机器利用了ip_hash算法来保持会话,每一台边缘的机器都会固定回源到父层CDN机器上获取所需要的Cache内容;这里如果不是采用ip_hash算法,而是采用默认的round-robin算法,那么当客户端在边缘CDN机器上漏掉了(miss),则边缘机器向父层回源时获取的Cache内容将是随机的,理论上,每一台父层机器都必须要有边缘机器所需要的Cache内容,如果没有,则父层Cache将向源站进一步回源(即缓存命中率低),从而造成源站回源压力过大,这样的架构设计也是有问题的(后续这里改成了一致性Hash算法)。