mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-10-18 07:53:45 +08:00
mod
This commit is contained in:
91
极客时间专栏/后端技术面试 38 讲/架构的核心原理/21丨分布式架构:如何应对高并发的用户请求.md
Normal file
91
极客时间专栏/后端技术面试 38 讲/架构的核心原理/21丨分布式架构:如何应对高并发的用户请求.md
Normal file
@@ -0,0 +1,91 @@
|
||||
<audio id="audio" title="21丨分布式架构:如何应对高并发的用户请求" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ae/a9/aeefb5e102ab30df0ae1a39b1a51eea9.mp3"></audio>
|
||||
|
||||
互联网应用以及云计算的普及,使得架构设计和软件技术的关注点从如何实现复杂的业务逻辑,转变为如何满足大量用户的高并发访问请求。
|
||||
|
||||
一个简单的计算处理过程,如果一旦面对大量的用户访问,整个技术挑战就会变得完全不同,软件开发方法、技术团队组织、软件的过程管理都会完全不同。
|
||||
|
||||
以新浪微博为例,新浪微博最开始只有两个工程师,一个前端,一个后端,两个人开发了一个星期就把新浪微博开发出来了。现在许多年过去了,新浪微博的技术团队有上千人,这些人要应对的技术挑战,一方面来自于更多更复杂的功能,一方面来自于随着用户量的增加而带来的高并发访问压力。
|
||||
|
||||
这种挑战和压力几乎对所有的大型互联网系统都是一样的,淘宝、百度、微信等,虽然功能各不相同,但都会**面对同样的高并发用户的访问请求压力**。要知道,同样的功能,供几个人使用和供几亿人使用,技术架构是完全不同的。
|
||||
|
||||
当同时访问系统的用户不断增加的时候,需要消耗的系统计算资源也不断增加,需要更多的CPU和内存去处理用户的计算请求,需要更多的网络带宽去传输用户的数据,需要更多的磁盘空间去存储用户的数据。当消耗的资源超过了服务器资源的极限的时候,服务器就会崩溃,整个系统无法正常使用。
|
||||
|
||||
那么如何解决高并发的用户请求带来的问题?
|
||||
|
||||
## 垂直伸缩与水平伸缩
|
||||
|
||||
为了应对高并发用户访问带来的系统资源消耗,一种解决办法是垂直伸缩。所谓的垂直伸缩就是提升单台服务器的处理能力,比如用更快频率的CPU,用更多核的CPU,用更大的内存,用更快的网卡,用更多的磁盘组成一台服务器,使单台服务器的处理能力得到提升。通过这种手段提升系统的处理能力。
|
||||
|
||||
在大型互联网出现之前,传统的行业,比如银行、电信这些企业的软件系统,主要是使用垂直伸缩这种手段实现系统能力的提升,在服务器上增强,提升服务器的硬件水平。当业务增长,用户增多,服务器计算能力无法满足要求的时候,就会用更强大的计算机,比如更换更快的CPU和网卡、更大的内存和磁盘,从服务器升级到小型机,从小型机提升到中型机,从中型机提升到大型机,服务器越来越强大,处理能力越来越强大,当然价格也越来越昂贵,运维越来越复杂。
|
||||
|
||||
垂直伸缩带来的价格成本和服务器的处理能力并不一定呈线性关系,也就是说,增加同样的费用,并不能得到同样的计算能力。而且计算能力越强大,需要花费的钱就越多。
|
||||
|
||||
同时,受计算机硬件科技水平的制约,单台服务器的计算能力并不能无限增加,而互联网,特别是物联网的计算要求几乎是无限的。
|
||||
|
||||
因此,在互联网以及物联网领域,并不使用垂直伸缩这种方案,而是使用水平伸缩。
|
||||
|
||||
所谓的水平伸缩,指的是不去提升单机的处理能力,不使用更昂贵更快更厉害的硬件,而是使用更多的服务器,将这些服务器构成一个分布式集群,通过这个集群,对外统一提供服务,以此来提高系统整体的处理能力。
|
||||
|
||||
但是要想让更多的服务器构成一个整体,就需要在架构上进行设计,让这些服务器成为整体系统的一个部分,将这些服务器有效地组织起来,统一提升系统的处理能力。这就是互联网应用和云计算中普遍采用的分布式架构方案。
|
||||
|
||||
## 互联网分布式架构演化
|
||||
|
||||
分布式架构是互联网企业在业务快速发展过程中,逐渐发展起来的一种技术架构,包括了一系列的分布式技术方案:分布式缓存、负载均衡、反向代理与CDN、分布式消息队列、分布式数据库、NoSQL数据库、分布式文件、搜索引擎、微服务等等,还有将这些分布式技术整合起来的分布式架构方案。
|
||||
|
||||
这些分布式技术和架构方案是互联网应用随着用户的不断增长,为了满足高并发用户访问不断增长的计算和存储需求,逐渐演化出来的。可以说,几乎所有这些技术都是由应用需求直接驱动产生的。
|
||||
|
||||
下面我们通过一个典型的互联网应用的发展历史,来看互联网系统是如何一步一步逐渐演化出各种分布式技术,并构成一个复杂庞大的分布式系统的。
|
||||
|
||||
在最早的时候,系统因为用户量比较少,可能只有几个用户,比如刚才提到的微博。一个应用访问自己服务器上的数据库,访问自己服务器的文件系统,构成了一个单机系统,这个系统就可以满足少量用户使用了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c7/59/c70b213ba0a66af102295d28e3f3de59.png" alt="">
|
||||
|
||||
如果这个系统被证明业务上是可行的,是有价值的,那么用户量就会快速增长。比如像新浪微博引入了一些明星大V开通微博,于是迅速吸引了这些明星们的大批粉丝前来关注。这个时候服务器就不能够承受访问压力了,需要进行第一次升级,数据库与应用分离。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/f4/4bae5511de89a43fb97e7b130b075ef4.jpeg" alt="">
|
||||
|
||||
前面单机的时候,数据库和应用程序是部署在一起的。进行第一次分离的时候,应用程序、数据库、文件系统分别部署在不同的服务器上,从1台服务器变成了3台服务器,那么相应的处理能力就提升了3倍。
|
||||
|
||||
这种分离几乎是不需要花什么技术成本的,只需要把数据库、文件系统进行远程部署,进行远程访问就可以了。
|
||||
|
||||
而随着用户进一步的增加,更多的粉丝加入微博,3台服务器也不能够承受这样的压力了,那么就需要使用缓存改善性能。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/62/8a/62056af8209eeb9ec478baee3d04808a.jpeg" alt="">
|
||||
|
||||
所谓缓存,就是将应用程序需要读取的数据缓存在缓存中,通过缓存读取数据,而不是通过数据库读取数据。缓存主要有分布式缓存和本地缓存两种。分布式缓存将多台服务器共同构成一个集群,存储更多的缓存数据,共同对应用程序提供缓存服务,提供更强大的缓存能力。
|
||||
|
||||
通过使用缓存,一方面应用程序不需要去访问数据库,因为数据库的数据是存在磁盘上的,访问数据库需要花费更多的时间,而缓存中的数据只是存储在内存中的,访问时间更短;另一方面,数据库中的数据是以原始数据的形式存在的,而缓存中的数据通常是以结果形式存在,比如说已经构建成某个对象,缓存的就是这个对象,不需要进行对象的计算,这样就减少了计算的时间,同时也减少了CPU的压力。最主要的,应用通过访问缓存降低了对数据库的访问压力,而数据库通常是整个系统的瓶颈所在。降低了数据库的访问压力,就是改善整个系统的处理能力。
|
||||
|
||||
随着用户的进一步增加,比如微博有更多的明星加入进来,并带来了更多的粉丝。那么应用服务器可能又会成为瓶颈,因为连接大量的并发用户的访问,这时候就需要对应用服务器进行升级。通过负载均衡服务器,将应用服务器部署为一个集群,添加更多的应用服务器去处理用户的访问。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a3/d5/a30d89088e3054b2be79abbd1de2e8d5.jpeg" alt="">
|
||||
|
||||
在微博上,我们的主要操作是刷微博,也就是读微博。如果只是明星们发微博,粉丝刷微博,那么对数据库的访问压力并不大,因为可以通过缓存提供微博数据。但事实上,粉丝们也要发微博,发微博就是写数据,这样数据库会再一次成为整个系统的瓶颈点。单一的数据库并不能承受这么大的访问压力。
|
||||
|
||||
这时候的解决办法就是数据库的读写分离,将一个数据库通过数据复制的方式,分裂为两个数据库,主数据库主要负责数据的写操作,所有的写操作都复制到从数据库上,保证从数据库的数据和主数据库数据一致,而从数据库主要提供数据的读操作。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/11/53/119dd5523c9eb2166e275693ed4e7b53.jpeg" alt="">
|
||||
|
||||
通过这样一种手段,将一台数据库服务器水平伸缩成两台数据库服务器,可以提供更强大的数据处理能力。
|
||||
|
||||
对于大多数的互联网应用而言,这样的分布式架构就已经可以满足用户的并发访问压力了。但是对于更大规模的互联网应用而言,比如新浪微博,还需要解决海量数据的存储与查询,以及由此产生的网络带宽压力以及访问延迟等问题。此外随着业务的不断复杂化,如何实现系统的低耦合与模块化开发、部署也成为重要的技术挑战。
|
||||
|
||||
海量数据的存储,主要通过分布式数据库、分布式文件系统、NoSQL数据库解决。直接在数据库上查询已经无法满足这些数据的查询性能要求,还需要部署独立的搜索引擎提供查询服务。同时减少数据中心的网络带宽压力,提供更好的用户访问延时,使用CDN和反向代理提供前置缓存,尽快返回静态文件资源给用户。
|
||||
|
||||
为了使各个子系统更灵活易于扩展,则使用分布式消息队列将相关子系统解耦,通过消息的发布订阅完成子系统间的协作。使用微服务架构将逻辑上独立的模块在物理上也独立部署,单独维护,应用系统通过组合多个微服务完成自己的业务逻辑,实现模块更高级别的复用,从而更快速地开发系统和维护系统。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/02/72/0202d3e8b73c4cc4b978b0b989029072.jpeg" alt="">
|
||||
|
||||
微服务、消息队列、NoSQL等这些分布式技术在出现早期的时候,比较有技术难度和使用门槛,只在相对比较大规模的互联网系统中使用。但是这些年随着技术的不断成熟,特别是云计算的普及,使用门槛逐渐降低,许多中小规模的系统,也已经普遍使用这些分布式技术架构设计自己的互联网系统了。
|
||||
|
||||
## 小结
|
||||
|
||||
随着互联网越来越普及,越来越多的企业采用面向互联网的方式开展自己的业务。传统的IT系统,用户量是有限而确定的,超市系统的用户主要是超市的收银员,银行系统的用户主要是银行的柜员,但是超市、银行这些企业如果使用互联网开展自己的业务,那么应用系统的用户量可能会成千上万倍地增加。
|
||||
|
||||
这些海量的用户访问企业的后端系统,就会产生高并发的访问压力,需要消耗巨大的计算资源,如何增加计算资源以满足高并发的用户访问压力,正是互联网架构技术的核心驱动力。主要就是各种分布式技术,我将会在后续讲解其中比较典型的几种分布式技术架构。
|
||||
|
||||
## 思考题
|
||||
|
||||
互联网应用系统和传统IT系统面对的挑战,除了高并发,还有哪些不同?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流。
|
95
极客时间专栏/后端技术面试 38 讲/架构的核心原理/22 | 缓存架构:如何减少不必要的计算?.md
Normal file
95
极客时间专栏/后端技术面试 38 讲/架构的核心原理/22 | 缓存架构:如何减少不必要的计算?.md
Normal file
@@ -0,0 +1,95 @@
|
||||
<audio id="audio" title="22 | 缓存架构:如何减少不必要的计算?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/54/63/5442cb8d5aaf76287a8896fa83dd9563.mp3"></audio>
|
||||
|
||||
上一篇我们讲到,互联网应用的主要挑战就是在高并发情况下,大量的用户请求到达应用系统服务器,造成了巨大的计算压力。互联网应用的核心解决思路就是采用分布式架构,提供更多的服务器,从而提供更多的计算资源,以应对高并发带来的计算压力及资源消耗。
|
||||
|
||||
那么有没有办法减少到达服务器的并发请求压力呢?或者请求到达服务器后,有没有办法减少不必要的计算,降低服务器的计算资源消耗,尽快返回计算结果给用户呢?
|
||||
|
||||
有,解决的核心就是缓存。
|
||||
|
||||
所谓缓存,就是将需要多次读取的数据暂存起来,这样在后面,应用程序需要多次读取的时候,就不必从数据源重复加载数据了,这样就可以降低数据源的计算负载压力,提高数据响应速度。
|
||||
|
||||
一般说来,缓存可以分成两种,通读缓存和旁路缓存。
|
||||
|
||||
**通读(read-through)缓存**,应用程序访问通读缓存获取数据的时候,如果通读缓存有应用程序需要的数据,那么就返回这个数据;如果没有,那么通读缓存就自己负责访问数据源,从数据源获取数据返回给应用程序,并将这个数据缓存在自己的缓存中。这样,下次应用程序需要数据的时候,就可以通过通读缓存直接获得数据了。
|
||||
|
||||
通读缓存在架构中的位置与作用如下图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5e/db/5e7556f257facad7980bcfd07b060fdb.png" alt="">
|
||||
|
||||
**旁路(cache-aside)缓存**,应用程序访问旁路缓存获取数据的时候,如果旁路缓存中有应用程序需要的数据,那么就返回这个数据;如果没有,就返回空(null)。应用程序需要自己从数据源读取数据,然后将这个数据写入到旁路缓存中。这样,下次应用程序需要数据的时候,就可以通过旁路缓存直接获得数据了。
|
||||
|
||||
旁路缓存在架构中位置与作用如下图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d1/e1/d127542911a21454a786b210ca6ecce1.png" alt="">
|
||||
|
||||
## 通读缓存
|
||||
|
||||
互联网应用中主要使用的通读缓存是CDN和反向代理缓存。
|
||||
|
||||
**CDN(Content Delivery Network)即内容分发网络**。我们上网的时候,App或者浏览器想要连接到互联网应用的服务器,需要网络服务商,比如移动、电信这样的服务商为我们提供网络服务,建立网络连接才可以上网。
|
||||
|
||||
而这些服务商需要在全国范围内部署骨干网络、交换机机房才能完成网络连接服务,这些交换机机房可能会离用户非常近,那么互联网应用能不能在这些交换机机房中部署缓存缓存服务器呢?这样,用户就可以近距离获得自己需要的数据,既提高了响应速度,又节约了网络带宽和服务器资源。
|
||||
|
||||
当然可以。这个部署在网络服务商机房中的缓存就是CDN,因为距离用户非常近,又被称作网络连接的第一跳。目前很多互联网应用大约80%以上的网络流量都是通过CDN返回的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e9/1e/e95cf98ddc2f33accc7a783d247f721e.png" alt="">
|
||||
|
||||
CDN只能缓存静态数据内容,比如图片、CSS、JS、HTML等内容。而动态的内容,比如订单查询、商品搜索结果等必须要应用服务器进行计算处理后才能获得。因此,互联网应用的静态内容和动态内容需要进行分离,静态内容和动态内容部署在不同的服务器集群上,使用不同的二级域名,即所谓的动静分离,一方面便于运维管理,另一方面也便于CDN进行缓存,使CDN只缓存静态内容。
|
||||
|
||||
**反向代理缓存也是一种通读缓存**。我们上网的时候,有时候需要通过代理上网,这个代理是代理我们的客户端上网设备。而反向代理则代理服务器,是应用程序服务器的门户,所有的网络请求都需要通过反向代理才能到达应用程序服务器。既然所有的请求都需要通过反向代理才能到达应用服务器,那么在这里加一个缓存,尽快将数据返回给用户,而不是发送给应用服务器,这就是反向代理缓存。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/64/1b/6456704366dcd0b7b8607d85e31a631b.png" alt="">
|
||||
|
||||
用户请求到达反向代理缓存服务器,反向代理检查本地是否有需要的数据,如果有就直接返回,如果没有,就请求应用服务器,得到需要的数据后缓存在本地,然后返回给用户。
|
||||
|
||||
## 旁路缓存
|
||||
|
||||
CDN和反向代理缓存通常会作为系统架构的一部分,很多时候对应用程序是透明的。而应用程序在代码中主要使用的是对象缓存,**对象缓存是一种旁路缓存。**
|
||||
|
||||
不管是通读缓存还是旁路缓存,缓存通常都是以<key, value>的方式存储在缓存中,比如,CDN和反向代理缓存中,每个URL是一个key,那么URL对应的文件内容就是value。而对象缓存中,key通常是一个ID,比如用户ID,商品ID等等,而value则是一个对象,就是ID对应的用户对象或者商品对象。
|
||||
|
||||
对于<key, value>的数据格式,我们在前面在数据结构讨论过,比较快速的存取方式是使用Hash表。**因此通读缓存和旁路缓存在实现上,基本上<strong><strong>用的**</strong>是Hash表</strong>。
|
||||
|
||||
程序中使用的对象缓存,可以分成两种。一种是本地缓存,缓存和应用程序在同一个进程中启动,使用程序的堆空间存放缓存数据。本地缓存的响应速度快,但是缓存可以使用的内存空间相对比较小,但是对于大型互联网应用所需要缓存的数据通以T计,这时候就要使用远程的分布式缓存了。
|
||||
|
||||
分布式缓存是指将一组服务器构成一个缓存集群,共同对外提供缓存服务,那么应用程序在每次读写缓存的时候,如何知道要访问缓存集群中的哪台服务器呢?我们以Memcached为例,看看分布式缓存的架构:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ca/6e/ca000caeaca469128dee3a59ddd0896e.png" alt="">
|
||||
|
||||
Memcached将多台服务器构成一个缓存集群,缓存数据存储在每台服务器的内存中。事实上,使用缓存的应用程序服务器通常也是以集群方式部署的,每个程序需要依赖一个Memcached的客户端SDK,通过SDK的API访问Memcached的服务器。
|
||||
|
||||
应用程序调用API,API调用SDK的路由算法,路由算法根据缓存的key值,计算这个key应该访问哪台Memcached服务器,计算得到服务器的IP地址和端口号后,API再调用SDK的通信模块,将<key, value>值以及缓存操作命令发送给具体的某台Memcached服务器,由这台服务器完成缓存操作。
|
||||
|
||||
那么,路由算法又是如何计算得到Memcached的服务器IP端口呢?比较简单的一种方法,和Hash算法一样,利用key的Hash值对服务器列表长度取模,根据余数就可以确定服务器列表的下标,进而得到服务器的IP和端口。
|
||||
|
||||
## 缓存注意事项
|
||||
|
||||
使用缓存可以减少不必要的计算,能够带来三个方面的好处:
|
||||
|
||||
1. 缓存的数据通常存储在内存中,距离使用数据的应用也更近一点,因此相比从硬盘上获取,或者从远程网络上获取,它获取数据的速度更快一点,响应时间更快,性能表现更好。
|
||||
1. 缓存的数据通常是计算结果数据,比如对象缓存中,通常存放经过计算加工的结果对象,如果缓存不命中,那么就需要从数据库中获取原始数据,然后进行计算加工才能得到结果对象,因此使用缓存可以减少CPU的计算消耗,节省计算资源,同样也加快了处理的速度。
|
||||
1. 通过对象缓存获取数据,可以降低数据库的负载压力;通过CDN、反向代理等通读缓存获取数据,可以降低服务器的负载压力。这些被释放出来的计算资源,可以提供给其他更有需要的计算场景,比如写数据的场景,间接提高整个系统的处理能力。
|
||||
|
||||
但是缓存也不是万能的,如果不恰当地使用缓存,也可能会带来问题。
|
||||
|
||||
首先就是**数据脏读**的问题,缓存的数据来自数据源,如果数据源中的数据被修改了,那么缓存中的数据就变成脏数据了。
|
||||
|
||||
主要解决办法有两个,一个是**过期失效**,每次写入缓存中的数据都标记其失效时间,在读取缓存的时候,检查数据是否已经过期失效,如果失效,就重新从数据源获取数据。缓存失效依然可能会在未失效时间内读到脏数据,但是一般的应用都可以容忍较短时间的数据不一致,比如淘宝卖家更新了商品信息,那么几分钟数据没有更新到缓存,买家看到的还是旧数据,这种情况通常是可以接受的,这时候,就可以设置缓存失效时间为几分钟。
|
||||
|
||||
另一个办法就是**失效通知**,应用程序更新数据源的数据,同时发送通知,将该数据从缓存中清除。失效通知看起来数据更新更加及时,但是实践中,更多使用的还是过期失效。
|
||||
|
||||
此外,并不是所有数据使用缓存都有意义。在互联网应用中,大多数数据访问都是有热点的,比如热门微博会被更多阅读,热门商品会被更多浏览。那么将这些热门的数据保存在缓存中是有意义的,因为缓存通常使用内存,存储空间比较有限,只能存储有限的数据,热门数据存储在缓存中,可以被更多次地读取,缓存效率也比较高。
|
||||
|
||||
相反,**如果缓存的数据没有热点,写入缓存的数据很难被重复读取,那么使用缓存就不是很有必要了**。
|
||||
|
||||
## 小结
|
||||
|
||||
缓存是优化软件性能的杀手锏,任何需要查询数据、请求数据的场合都可以考虑使用缓存。缓存几乎是无处不在的,程序代码中可以使用缓存,网络架构中可以使用缓存,CPU、操作系统、虚拟机也大量使用缓存,事实上,缓存最早就是在CPU中使用的。对于一个典型的互联网应用而言,使用缓存可以解决绝大部分的性能问题,如果需要优化软件性能,那么可以优先考虑哪里可以使用缓存改善性能。
|
||||
|
||||
除了本篇提到的系统架构缓存外,客户端也可以使用缓存,在App或者浏览器中缓存数据,甚至都不需要消耗网络带宽资源,也不会消耗CDN、反向代理的内存资源,更不会消耗服务器的计算资源。
|
||||
|
||||
## 思考题
|
||||
|
||||
我们从Memcached路由算法讲到余数Hash算法,但是,这种算法在Memcached服务器集群扩容,也就是增加服务器的时候,会遇到较大的问题,问题是什么呢?应该如何解决?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流进步一下。
|
114
极客时间专栏/后端技术面试 38 讲/架构的核心原理/23 | 异步架构:如何避免互相依赖的系统间耦合?.md
Normal file
114
极客时间专栏/后端技术面试 38 讲/架构的核心原理/23 | 异步架构:如何避免互相依赖的系统间耦合?.md
Normal file
@@ -0,0 +1,114 @@
|
||||
<audio id="audio" title="23 | 异步架构:如何避免互相依赖的系统间耦合?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/62/36/628a594936bee49e8da34a3a242efa36.mp3"></audio>
|
||||
|
||||
上一篇文章中我们讨论过,使用缓存架构可以减少不必要的计算,快速响应用户请求。但是缓存只能改善系统的读操作性能,也就是在读取数据的时候,可以不从数据源中读取,而是通过缓存读取,以加速数据读取速度。
|
||||
|
||||
但是对于写操作,缓存是无能为力的。虽然缓存的写入速度也很快,但是通常情况下,我们不能把用户提交的数据直接写入缓存中,因为缓存通常被认为是一种不可靠的存储。缓存通常无法保证数据的持久性和一致性等这些数据存储的基本要求,因此数据写操作还是需要写入到RDBMS或者NoSQL数据库中,但是数据库操作通常都比较慢。
|
||||
|
||||
那么如何提高系统的写操作的性能呢?
|
||||
|
||||
此外,两个应用系统之间需要远程传递数据,常规的做法就是直接进行远程调用,用HTTP或者其他RMI方式进行远程调用。但是这种方式其实是把两个应用耦合起来了,被调用的应用产生了故障或者升级,都可能会引起调用者故障,或者也不得不升级。
|
||||
|
||||
这种系统间的耦合情况又该如何避免呢?
|
||||
|
||||
解决以上问题的主要手段就是使用消息队列的异步架构,有时候也被称为事件驱动架构。
|
||||
|
||||
## 使用消息队列实现异步架构
|
||||
|
||||
消息队列实现异步架构是目前互联网应用系统中一种典型的架构模式。所谓异步架构是和同步架构相对应的。同步架构是说,当应用程序调用服务的时候,当前程序需要阻塞等待服务完成,返回服务结果后才能继续向下执行。
|
||||
|
||||
如下图例子:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ad/a5/adf559cc0708403567b5703f733e90a5.png" alt="">
|
||||
|
||||
应用程序代码ClintCode需要发送邮件,调用接口服务EmailService,实现了EmailService接口的SmtpEmailAdapter通过SMTP协议与远程服务器通信,远程邮件服务器可能有很多邮件在等待发送,当前邮件可能要等待较长时间才能发送成功,发送成功后再通过远程通信返回结果给应用程序。
|
||||
|
||||
在这个过程中,当远程服务器发送邮件的时候,应用程序必须阻塞等待。准确地说,是执行应用程序代码的线程被阻塞。这种阻塞,一方面导致线程不能释放被占用的系统资源,导致系统资源不足,影响系统性能。另一方面,也导致无法快速给用户返回响应结果,用户体验较差。此外,如果远程服务器出现异常,这个异常会传递给应用程序ClientCode,如果应用程序没有妥善处理好这个异常,就会导致整个请求处理失败。
|
||||
|
||||
事实上,在大部分应用场景下,发送邮件是不需要得到发送结果的,比如用户注册的时候,发送账号激活邮件,无论邮件是否发送成功,都可以给用户返回“激活邮件已经发送,请查收邮件确认激活”。如果发送失败,只需要提示用户“点击重新发送”,再次发送邮件即可。
|
||||
|
||||
那么如何使应用程序不阻塞等待呢?解决方案就是使用消息队列实现异步架构。
|
||||
|
||||
如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/66/eb/66216c1ebe21493334ae11eb14dec8eb.png" alt=""><br>
|
||||
应用程序ClientCode调用EmailService的时候,EmailService将调用请求封装成一个邮件发送消息发送给消息队列,然后就直接返回了,应用程序收到返回以后就可以继续执行,快速完成用户响应,释放系统资源。
|
||||
|
||||
而发送给消息队列的邮件发送消息,则会被一个专门的消息队列消费者程序QueueConsumer消费掉,这个消费者通过SmtpEmailAdapter调用远程服务器,完成邮件发送。如果远程服务处理异常,这个异常只会传递给消费者程序QueueConsumer,而不会影响到应用程序。
|
||||
|
||||
典型的消息队列异步架构如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/54/68/54655d0542912c741189b99cb3f29968.png" alt="">
|
||||
|
||||
消息队列异步架构的主要角色包括消息生产者、消息队列和消息消费者。消息生产者通常就是主应用程序,生产者将调用请求封装成消息发送给消息队列。此外还需要开发一个专门的消息消费者程序,用来从消息队列中获取、消费消息,由消息消费者完成业务逻辑处理。
|
||||
|
||||
消息队列的职责就是缓冲消息,等待消费者消费。根据消息消费方式又分为点对点模式和发布订阅模式两种。
|
||||
|
||||
在点对点模式中,多个消息生产者向消息队列发送消息,多个消息消费者消费消息,每个消息只会被一个消息消费者消费。
|
||||
|
||||
如下图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/89/c8/8908d32e5db7313c66e85b3edb1dcec8.png" alt="">
|
||||
|
||||
上面举例的发送邮件的场景就是一个典型的点对点模式场景。任何需要发送邮件的应用程序都可以作为消息生产者向消息队列发送邮件消息。而通过SMTP协议,调用远程服务发送邮件的消息消费者程序可以部署在多台服务器上,但是对于任何一个消息,只会被发送给其中的一个消费者服务器。这些服务器可以根据消息的数量动态伸缩,保证邮件能及时发送。如果有某台消费者服务器宕机,既不会影响其他消费者处理消息发送邮件,也不会影响生产者程序正常运行。
|
||||
|
||||
在发布订阅模式中,开发者可以在消息队列中设置主题,消息生产者的消息按照主题进行发送,多个消息消费者可以订阅同一个主题,每个消费者都可以收到这个主题的消息拷贝,然后按照自己的业务逻辑分别进行处理计算。
|
||||
|
||||
如下图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1a/93/1a966517c893fcc19ec8d2c297707393.png" alt="">
|
||||
|
||||
消息生产者向消息队列某个主题发布消息m,多个消息消费者订阅该主题,就会分别收到这个消息m。典型场景就是新用户注册,新用户注册的时候一方面需要发送激活邮件,另一方面可能还需要发送欢迎短信,还可能需要将用户信息同步给关联产品,当然还需要将用户信息保存到数据库中。
|
||||
|
||||
这种场景也可以用点对点模式,由应用程序,也就是消息生产者构造发送邮件的消息,发送到邮件消息队列,以及构造短信消息,构造新用户消息,构造数据库消息分别发送到相关的消息队列里,然后由对应的消息消费者程序分别获取消息进行处理。
|
||||
|
||||
但更好的处理方式是使用发布订阅模式。在消息队列中创建“新用户注册”主题,应用程序只需要发布包含新用户注册数据的消息到该主题中,相关消费者再订阅该主题即可。不同的消费者都订阅该主题,得到新用户注册消息,然后根据自己的业务逻辑从消息中获取相关的数据,进行处理。
|
||||
|
||||
如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ee/fa/ee12690c0253c8b094040801f4b0c2fa.png" alt="">
|
||||
|
||||
发布订阅模式下,一个主题可以被重复订阅,所以如果需要扩展功能,可以在对当前的生产者和消费者都没有影响的前提下,增加新的消费者订阅同一个主题即可。
|
||||
|
||||
## 消息队列异步架构的好处
|
||||
|
||||
使用消息队列实现异步架构可以解决文章开篇提出的问题,实现更高的写操作性能以及更低的耦合性。让我们总结一下,使用消息队列的异步架构都有什么好处。
|
||||
|
||||
### 改善写操作请求的响应时间
|
||||
|
||||
使用消息队列,生产者应用程序只需要将消息发送到消息队列之后,就可以继续向下执行了,无需等待耗时的消息消费处理,也就是说,可以更快速地完成请求处理操作,快速响应用户。
|
||||
|
||||
### 更容易进行伸缩
|
||||
|
||||
我在[第4篇文章](https://time.geekbang.org/column/article/169545)中说过,应用程序也可以通过负载均衡实现集群伸缩,但是这种集群伸缩是以整个应用服务器为单位的。如果只是其中某些功能有负载压力,比如当用户上传图片,需要对图片进行识别、分析、压缩等一些比较耗时的计算操作时,也需要伸缩整个应用服务器集群。
|
||||
|
||||
事实上,图片处理只是应用的一个相对小的功能,如果因为这个就对应用服务器集群进行伸缩,代价可能会比较大。如果用消息队列,将图片处理相关的操作放在消费者服务器上,那么就可以单独针对图片处理的消费者集群进行伸缩。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7a/9f/7ab4fd31d3bd76425eeaf797c261ad9f.png" alt="">
|
||||
|
||||
### 削峰填谷
|
||||
|
||||
互联网应用的访问压力随时都在变化,系统的访问高峰和低谷的并发压力可能也有非常大的差距。如果按照压力最大的情况部署服务器集群,那么服务器在绝大部分时间内都处于闲置状态。但利用消息队列,我们可以将需要处理的消息放入消息队列,而消费者可以控制消费速度,因此能够降低系统访问高峰时压力,而在访问低谷的时候还可以继续消费消息队列中未处理的消息,保持系统的资源利用率。
|
||||
|
||||
### 隔离失败
|
||||
|
||||
使用消息队列,生产者发送消息到消息队列后就继续自己后面的计算,消费者如果在处理消息的过程中失败,不会传递给生产者,应用程序具有更高的可用性。
|
||||
|
||||
### 降低耦合
|
||||
|
||||
如上面发送邮件的例子所示,如果调用是同步的,那么意味着调用者和被调用者必然存在依赖,一方面是代码上的依赖,应用程序需要依赖发送邮件相关的代码,如果需要修改发送邮件的代码,就必须修改应用程序,而且如果要增加新的功能,比如发送短信,也必须修改应用程序;另一方面是结果的依赖,应用程序必须要等到返回调用结果才能继续执行,如果调用出现异常,应用程序必须要处理这个异常。
|
||||
|
||||
我们知道,耦合会使软件僵硬、笨拙、难以维护,而使用消息队列的异步架构可以降低调用者和被调用者的耦合。调用者发送消息到消息队列,不需要依赖被调用者的代码和处理结果,增加新的功能,也只需要增加新的消费者就可以了。
|
||||
|
||||
## 小结
|
||||
|
||||
消息队列实现异步架构是改善互联网应用写操作性能的重要手段,也是一种低耦合、易扩展的分布式应用架构模式。但是使用这种架构有些方面也需要注意。
|
||||
|
||||
比如,消费者程序可能没有完成用户请求的操作,上面发送邮件的例子,消费者程序发送邮件的时候可能会遇到各种问题,从而未完成邮件发送。
|
||||
|
||||
邮件的问题还比较简单,比如可以提示用户:“如果未收到邮件,点击按钮重新发送。”但是如果是提交订单,或者发起支付的话,就需要更复杂的用户交互和处理方法了。比如将订单消息发送到消息队列后,就立即返回,这个时候可以在用户端App展现一个进度条,提示用户“订单处理中”,等消费者程序完成订单处理后,发送消息给用户App,显示最终的订单结果信息。
|
||||
|
||||
## 思考题
|
||||
|
||||
异步架构中最主要的技术就是消息队列,目前主要的消息队列产品有哪些?各有什么优缺点?
|
||||
|
||||
欢迎你在评论区说说你对消息队列产品的了解,我会和你一起交流,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
@@ -0,0 +1,75 @@
|
||||
<audio id="audio" title="24 | 负载均衡架构:如何用10行代码实现一个负载均衡服务?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fe/99/fe0eab837246accaec92411fdd077e99.mp3"></audio>
|
||||
|
||||
负载均衡是互联网系统架构中必不可少的一个技术。通过负载均衡,可以将高并发的用户请求分发到多台应用服务器组成的一个服务器集群上,利用更多的服务器资源处理高并发下的计算压力。
|
||||
|
||||
那么负载均衡是如何实现的,如何将不同的请求分发到不同的服务器上呢?
|
||||
|
||||
早期,实现负载均衡需要使用专门的负载均衡硬件设备,这些硬件通常比较昂贵。随着互联网的普及,越来越多的企业需要部署自己的互联网应用系统,而这些专用的负载均衡硬件对他们来说成本太高,于是出现了各种通过软件实现负载均衡的技术方案。
|
||||
|
||||
## HTTP重定向负载均衡
|
||||
|
||||
HTTP重定向负载均衡是一种比较简单的负载均衡技术实现。来自用户的HTTP请求到达负载均衡服务器以后,负载均衡服务器根据某种负载均衡算法计算得到一个应用服务器的地址,通过HTTP状态码302重定向响应,将新的IP地址发送给用户浏览器,用户浏览器收到重定向响应以后,重新发送请求到真正的应用服务器,以此来实现负载均衡。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/74/4f/74d1a57c8d5b168501e15cc92da0034f.png" alt="">
|
||||
|
||||
这种负载均衡实现方法比较简单,如果是用Java开发的话,只需要在Servlet代码中调用响应重定向方法就可以了。在简化的情况下,只需要不到十行代码就可以实现一个HTTP重定向负载均衡服务器。
|
||||
|
||||
HTTP重定向负载均衡的优点是设计比较简单,但是它的缺点也比较明显,一方面用户完成一次访问,就需要请求两次数据中心,一次请求负载均衡服务器,一次是请求应用服务器,请求处理性能会受很大的影响。
|
||||
|
||||
另一个问题是因为响应要重定向到真正的应用服务器,所以需要把应用服务器的IP地址暴露给外部用户,这样可能会带来安全性的问题。负载均衡服务器通常不部署应用代码,也会关闭不必要的访问端口,设置比较严格的防火墙权限,通常安全性更好一点。因此,一个互联网系统通常只将负载均衡服务器的IP地址对外暴露,供用户访问,而应用服务器则只是用内网IP,外部访问者无法直接连接应用服务器。但是使用HTTP重定向负载均衡,应用服务器不得不使用公网IP,外部访问者可以直接连接到应用服务器,系统的安全性会降低。
|
||||
|
||||
因此HTTP重定向负载均衡在实践中很少使用。
|
||||
|
||||
## DNS负载均衡
|
||||
|
||||
另一种实现负载均衡的技术方案是DNS负载均衡。我们知道浏览器或者App应用访问数据中心的时候,通常是用域名进行访问,HTTP协议则必须知道IP地址才能建立通信连接,那么域名是如何转换成IP地址的呢?就是通过DNS服务器来完成。当用户从浏览器发起HTTP请求的时候,首先要到DNS域名服务器进行域名解析,解析得到IP地址以后,用户才能够根据IP地址建立HTTP连接,访问真正的数据中心的应用服务器,这时候就可以在DNS域名解析的时候进行负载均衡,也就是说,不同的用户进行域名解析的时候,返回不同的IP地址,从而实现负载均衡。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6c/c7/6c504d347d9aa2bca5dedc3e9d750dc7.png" alt="">
|
||||
|
||||
从上面的架构图可以看到,DNS负载均衡和HTTP重定向负载均衡似乎很像。那么DNS会不会有性能问题和安全性问题呢?
|
||||
|
||||
首先和HTTP重定向不同,用户不需要每次请求都进行DNS域名解析,第一次解析后,域名缓存在本机,后面较长一段时间都不会再进行域名解析了,因此性能方面不会是问题。
|
||||
|
||||
其次,如果如图中所示,域名解析直接得到应用服务器的IP地址,确实会存在安全性问题。但是大型互联网应用通常并不直接通过DNS解析得到应用服务器IP地址,而是解析得到负载均衡服务器的IP地址。也就是说,大型网互联网应用需要两次负载均衡,一次通过DNS负载均衡,用户请求访问数据中心负载均衡服务器集群的某台机器,然后这台负载均衡服务器再进行一次负载均衡,将用户请求分发到应用服务器集群的某台服务器上。通过这种方式,应用服务器不需要用公网IP将自己暴露给外部访问者,避免了安全性问题。
|
||||
|
||||
DNS域名解析是域名服务商提供的一项基本服务,几乎所有的域名服务商都支持域名解析负载均衡,只需要在域名服务商的服务控制台进行一下配置,不需要开发代码进行部署,就可以拥有DNS负载均衡服务了。目前大型的互联网应用,淘宝、百度、Google等全部使用DNS负载均衡。比如用不同的电脑ping [www.baidu.com](http://www.baidu.com)就可以看到,不同电脑得到的IP地址是不同的。
|
||||
|
||||
## 反向代理负载均衡
|
||||
|
||||
我在[第22篇](https://time.geekbang.org/column/article/188667),缓存架构中提到用户请求到达数据中心以后,最先到达的就是反向代理服务器。反向代理服务器查找本机是否有请求的资源,如果有就直接返回资源数据,如果没有,就将请求发送给后面的应用服务器继续处理。事实上,发送请求给应用服务器的时候,就可以进行负载均衡,将不同的用户请求分发到不同的服务器上面去。Nginx这样的HTTP服务器就会同时提供反向代理与负载均衡功能。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d6/43/d656da82d725cb206dbcf7cebb420e43.png" alt="">
|
||||
|
||||
反向代理服务器是工作在HTTP协议层之上的,所以它代理的也是HTTP的请求和响应。作为互联网应用层的一个协议,HTTP协议相对说来比较重,效率比较低,所以反向代理负载均衡通常用在小规模的互联网系统上,只有几台或者十几台服务器的规模。
|
||||
|
||||
## IP负载均衡
|
||||
|
||||
反向代理负载均衡是工作在应用层网络协议上的负载均衡,因此也叫应用层负载均衡。应用层负载均衡之下的负载均衡方法是在TCP/IP协议的IP层进行负载均衡,IP层是网络通讯协议的网络层,所以有时候叫网络层负载均衡。它的主要工作原理是当用户的请求到达负载均衡服务器以后,负载均衡服务器会对网络层的数据包的IP地址进行转换,修改IP地址,将其修改为应用服务器的IP地址,然后把数据包重新发送出去,请求数据就会到达应用服务器。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a6/dc/a62e851bec43aac1a30cc45db11abbdc.png" alt="">
|
||||
|
||||
IP负载均衡不需要在HTTP协议层工作,可以在操作系统内核直接修改IP数据包的地址,因此,效率比应用层的反向代理负载均衡高得多。但是它依然有一个缺陷,不管是请求还是响应的数据包,都要通过负载均衡服务器进行IP地址转换,才能够正确地把请求数据分发到应用服务器,或者正确地将响应数据包发送到用户端程序。请求的数据通常比较小,一个URL或者是一个简单的表单,但是响应的数据不管是HTML还是图片,或者是JS、CSS这样的资源文件通常都会比较大,因此负载均衡服务器会成为响应数据的流量瓶颈。
|
||||
|
||||
## 数据链路层负载均衡
|
||||
|
||||
数据链路层负载均衡可以解决响应数据量大而导致的负载均衡服务器输出带宽不足的问题。也就是说,负载均衡服务器并不修改数据包的IP地址,而是修改数据链路层里的网卡mac地址,在数据链路层实现负载均衡。而应用服务器和负载均衡服务器都使用相同的虚拟IP地址,这样IP路由就不会受到影响,但是网卡会根据自己的mac地址,选择负载均衡服务器发送到自己网卡的数据包,交给对应的应用程序去处理,处理结束以后,当把响应的数据包发送到网络上的时候,因为IP地址没有修改过,所以这个响应会直接到达用户的浏览器,而不会再经过负载均衡服务器。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e4/4c/e4cc84d4c9f7d76082df4163ac8d414c.png" alt="">
|
||||
|
||||
链路层负载均衡避免响应数据再经过负载均衡服务器,因而可以承受较大的数据传输压力,所以,目前大型互联网应用基本都使用链路层负载均衡。
|
||||
|
||||
Linux上实现IP负载均衡和链路层负载均衡的技术是LVS,目前LVS的功能已经集成到Linux中了,通过Linux可以直接配置实现这两种负载均衡。
|
||||
|
||||
## 小结
|
||||
|
||||
负载均衡技术在早期刚出现的时候,设备昂贵,使用复杂,只有大企业才用得起、用得上,但是到了今天,随着互联网技术的发展与普及,负载均衡已经是最常用的分布式技术之一了,使用也非常简单。如果使用云计算平台,只需要在控制台点击几下,就可以配置实现一个负载均衡了。即使是自己部署一个负载均衡服务器,不管是直接用Linux还是用Nginx,也不是很复杂。
|
||||
|
||||
我在这里主要描述的是负载均衡的网络技术架构。事实上,实现一个负载均衡,还需要关注负载均衡的算法,也就是说,当一个请求到达负载均衡服务器的时候,负载均衡服务器该选择集群中的哪一台服务器将请求发送给它?
|
||||
|
||||
目前主要的负载均衡算法有轮询、随机、最少连接几种。轮询就是将请求轮流发给应用服务器,随机就是将请求随机发送给任一台应用服务器,最少连接则是根据应用服务器当前正在处理的连接数,将请求分发给最少连接的服务器。
|
||||
|
||||
## 思考题
|
||||
|
||||
利用HTTP重定向只需要很少代码就可以完成一个简化的负载均衡,你能否利用你熟悉的编程语言写一个简化的HTTP重定向负载均衡demo?
|
||||
|
||||
欢迎你在评论区写下你的答案,我会和你一起交流,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
99
极客时间专栏/后端技术面试 38 讲/架构的核心原理/25 | 数据存储架构:如何改善系统的数据存储能力?.md
Normal file
99
极客时间专栏/后端技术面试 38 讲/架构的核心原理/25 | 数据存储架构:如何改善系统的数据存储能力?.md
Normal file
@@ -0,0 +1,99 @@
|
||||
<audio id="audio" title="25 | 数据存储架构:如何改善系统的数据存储能力?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f4/89/f451f738a611e9a52301752affca0a89.mp3"></audio>
|
||||
|
||||
在整个互联网系统架构中,承受着最大处理压力,最难以被伸缩的,就是数据存储部分。原因主要有两方面。一方面,数据存储需要使用硬盘,而硬盘的处理速度要比其他几种计算资源,比如CPU、内存、网卡都要慢一些;另一方面,数据是公司最重要的资产,公司需要保证数据的高可用以及一致性,非功能性约束更多一些。
|
||||
|
||||
因此数据存储通常都是互联网应用的瓶颈。在高并发的情况下,最容易出现性能问题的就是数据存储。目前用来改善数据存储能力的主要手段包括:数据库主从复制、数据库分片和NoSQL数据库。
|
||||
|
||||
## 数据库主从复制
|
||||
|
||||
我们以MySQL为例,看下数据库主从复制的实现技术以及应用场景。
|
||||
|
||||
MySQL的主从复制,顾名思义就是将MySQL主数据库中的数据复制到从数据库中去。主要的复制原理是,当应用程序客户端发送一条更新命令到主服务器数据库的时候,数据库会把这条更新命令同步记录到Binlog中,然后由另外一个线程从Binlog中读取这条日志,通过远程通讯的方式将它复制到从服务器上面去。
|
||||
|
||||
从服务器获得这条更新日志后,将其加入到自己的Relay Log中,然后由另外一个SQL执行线程从Relay log中读取这条新的日志,并把它在本地的数据库中重新执行一遍,这样当客户端应用程序执行一个update命令的时候,这个命令会同时在主数据库和从数据库上执行,从而实现了主数据库向从数据库的复制,让从数据库和主数据库保持一样的数据。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b4/cc/b44938ad3560931905ecdfdd763d50cc.png" alt=""><br>
|
||||
通过数据库主从复制的方式,我们可以实现数据库读写分离。写操作访问主数据库,读操作访问从数据库,使数据库具有更强大的访问负载能力,支撑更多的用户访问。在实践中,通常采用一主多从的数据复制方案,也就是说,一个主数据库将数据复制到多个从数据库,多个从数据库承担更多的读操作压力,以及不同的角色,比如有的从数据库用来做实时数据分析,有的从数据库用来做批任务报表计算,有的单纯做数据备份。
|
||||
|
||||
采用一主多从的方案,当某个从数据库宕机的时候,还可以将读操作迁移到其他从数据库上,保证读操作的高可用。但如果主数据库宕机,系统就没法使用了,因此现实中,也会采用MySQL主主复制的方案。也就是说,两台服务器互相备份,任何一台服务器都会将自己的Binlog复制到另一台机器的Relay Log中,以保持两台服务器的数据一致。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2c/0f/2ca67b5a646b7a1ee206f32c78303e0f.png" alt="">
|
||||
|
||||
使用主主复制需要注意的是,主主复制仅仅用来提升数据写操作的可用性,并不能用来提高写操作的性能。任何时候,系统中都只能有一个数据库作为主数据库,也就是说,所有的应用程序都必须连接到同一个主数据库进行写操作。只有当该数据库宕机失效的时候,才会将写操作切换到另一台主数据库上。这样才能够保证数据库数据的一致性,不会出现数据冲突。
|
||||
|
||||
此外,不管是主从复制还是主主复制,都无法提升数据库的存储能力,也就是说,不管增加多少服务器,这些服务器存储的数据都是一样的。如果数据量太大,数据库无法存下这么多的数据,通过数据库复制是无法解决问题的。
|
||||
|
||||
## 数据库分片
|
||||
|
||||
我们上面说到,数据库主从复制无法解决数据库的存储问题,但是数据库分片技术可以解决。也就是说,将一张表的数据分成若干片,每一片都包含了数据表中一部分的行记录,然后每一片存储在不同的服务器上,这样一张表就存储在多台服务器上了。
|
||||
|
||||
最简单的数据库分片存储可以采用硬编码的方式,在程序代码中直接指定一条数据库记录要存放到哪个服务器上。比如将用户表分成两片,存储在两台服务器上,那么就可以在程序代码中根据用户ID进行分片计算,ID为偶数的用户记录存储到服务器1,ID为奇数的用户记录存储到服务器2。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/76/38/76a4efee00c67cb1fa745b6f3dfdfa38.png" alt=""><br>
|
||||
但是硬编码方式的缺点比较明显。首先,如果要增加服务器,那么就必须修改分片逻辑代码,这样程序代码就会因为非业务需求产生不必要的变更;其次,分片逻辑耦合在处理业务逻辑的程序代码中,修改分片逻辑或者修改业务逻辑都可能使另一部分代码因为不小心的改动而出现Bug。
|
||||
|
||||
但是我们可以通过使用分布式关系数据库中间件解决这个问题,将数据的分片逻辑在中间件中完成,对应用程序透明。
|
||||
|
||||
比如MYCAT。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c3/d7/c3e3bae9e9ed1c5de922d0d6be000ad7.png" alt=""><br>
|
||||
应用程序像使用MySQL数据库一样连接MYCAT,提交SQL命令。MYCAT在收到SQL命令以后,查找配置的分片逻辑规则。比如上图例子中,根据地区进行数据分片,不同地区的订单存储在不同的数据库上,那么MYCAT就可以解析出SQL中的地区字段prov,根据这个字段连接相对应的数据库。例子中SQL的地区字段是“wuhan”,而在MYCAT中配置“wuhan”对应的数据库是DB1,用户提交的这条SQL最终会被发送给DB1数据库进行处理。
|
||||
|
||||
实践中,更常见的数据库分片算法是我们所熟悉的余数Hash算法,根据主键ID和服务器的数目进行取模计算,根据余数连接相对应的服务器。
|
||||
|
||||
## 关系数据库的混合部署
|
||||
|
||||
我在上面提到了关系数据库的主从复制、主主复制、数据库分片这几种改善数据读写以及存储能力的技术方案。事实上,这几种方案可以根据应用场景的需要混合部署,也就是说,可以在一个系统中混合使用以上多种技术方案。
|
||||
|
||||
对于数据访问和存储压力不太大,对可用性要求也不太高的系统,也许部署在单一服务器上的数据库就可以解决,所有的应用服务器都连接访问这一台数据库服务器。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/88/3d/881e828ef3768b01e5f4cca64f14673d.png" alt="">
|
||||
|
||||
如果访问量比较大,同时对数据可用性要求也比较高,那么就需要使用数据库主从复制技术,将数据库部署在多台服务器上。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/67/90/6766189ab62fc48871858084455d9b90.png" alt=""><br>
|
||||
随着业务复杂以及数据存储和访问压力的增加,这时候可以选择业务分库。也就是说,将不同业务相关的数据库表,部署在不同的服务器上,比如类目数据和用户数据相对关联关系不大,服务的应用也不一样,那么就可以将这两类数据库部署在不同的服务器上。而每一类数据库还可以继续选择使用主从复制,或者主主复制。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9b/7b/9b09155fb8e7f744cbbdbd7896ef5e7b.png" alt="">
|
||||
|
||||
不同的业务数据库,其数据库存储的数据和访问压力也是不同的,比如用户数据库的数据量和访问量就可能是类目数据库的几十倍,甚至上百倍。那么这时候就可以针对用户数据库进行数据分片,而每个分片数据库还可以继续进行主从复制或者主主复制。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c8/98/c8094fc37a2d2402e3b737b44a14a398.png" alt="">
|
||||
|
||||
## NoSQL数据库
|
||||
|
||||
NoSQL数据是改善数据存储能力的一个重要手段。NoSQL数据库和传统的关系型数据库不同,它主要的访问方式不是使用SQL进行操作,而是使用Key、Value的方式进行数据访问,所以被称作NoSQL数据库。NoSQL数据库主要用来解决大规模分布式数据的存储问题。常用的NoSQL数据有Apache HBase,Apache Cassandra等,Redis虽然是一个分布式缓存技术产品,但有时候也被归类为NoSQL数据库。
|
||||
|
||||
NoSQL数据库面临的挑战之一是数据一致性问题。如果数据分布存储在多台服务器组成的集群上,那么当有服务器节点失效的时候,或者服务器之间网络通信故障的时候,不同用户读取的数据就可能会不一致。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9b/d0/9bad2794b6538d0fe456cd6736882cd0.png" alt="">
|
||||
|
||||
比如用户1连接服务器节点A,用户2连接服务器节点B,当两个用户同时修改某个数据的时候,如果正好服务器A和服务器B之间的网络通信失败,那么这两个节点上的数据也就不一致了,其他用户在访问这个数据的时候,可能会得到不一致的结果。
|
||||
|
||||
关于分布式存储系统有一个著名的CAP原理,CAP原理说:一个提供数据服务的分布式系统无法同时满足数据一致性(Consistency)、可用性(Availability)和分区耐受性(Partition Tolerance)这三个条件。
|
||||
|
||||
一致性是说,每次读取的数据都应该是最近写入的数据或者返回一个错误,而不是过期数据,也就是说,数据是一致的。
|
||||
|
||||
可用性是说,每次请求都应该得到一个响应,而不是返回一个错误或者失去响应,不过这个响应不需要保证数据是最近写入的。也就是说,系统需要一直都是可以正常使用的,不会引起调用者的异常,但是并不保证响应的数据是最新的。
|
||||
|
||||
分区耐受性说,即使因为网络原因,网络分区失效的时候,部分服务器节点之间消息丢失或者延迟了,系统依然应该是可以操作的。
|
||||
|
||||
CAP原理是说,当网络分区失效发生的时候,我们要么取消操作,保证数据就是一致的,但是系统却不可用;要么继续写入数据,但是数据的一致性就得不到保证了。
|
||||
|
||||
对于一个分布式系统而言,网络失效一定会发生,也就是说,分区耐受性是必须要保证的,而对于互联网应用来说,可用性也是需要保证的,分布式存储系统通常需要在一致性上做一些妥协和增强。
|
||||
|
||||
Apache Cassandra解决数据一致性的方案是,在用户写入数据的时候,将一个数据写入集群中的三个服务器节点,等待至少两个节点响应写入成功。用户读取数据的时候,从三个节点尝试读取数据,至少等到两个节点返回数据,并根据返回数据的时间戳,选取最新版本的数据。这样,即使服务器中的数据不一致,但是最终用户还是能得到一个一致的数据,这种方案也被称为最终一致性。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8a/dc/8ad71240caaf1fcba3b22e711a4b0bdc.png" alt="">
|
||||
|
||||
## 小结
|
||||
|
||||
有人说,架构是一门关于权衡的艺术,这一点在数据存储架构上表现得最为明显。由于数据存储的挑战性和复杂性,无论你选择何种技术方案,都会带来一些新的问题和挑战。数据存储架构没有银弹,没有一劳永逸的解决方案,唯有在深刻理解自己业务场景和各种分布式存储技术特点的基础上,进行各种权衡考虑,选择最合适的解决方案,并想办法弥补其缺陷,才能真正解决问题。
|
||||
|
||||
我在架构模块第一篇就讨论了垂直伸缩和水平伸缩这两种不同的架构思路。因为各种原因,互联网应用主要采用的是水平伸缩,也就是各种分布式技术。事实上,在数据存储方面,有时候采用垂直伸缩,也就是使用更好的硬件服务器部署数据库,也是一种不错的改善数据存储能力的手段。
|
||||
|
||||
## 思考题
|
||||
|
||||
分布式架构的一个最大特点是可以动态伸缩,可以随着需求变化,动态增加或者减少服务器。对于支持分片的分布式关系数据库而言,比如我们使用MYCAT进行数据分片,那么随着数据量逐渐增大,如何增加服务器以存储更多的数据呢?如果增加一台服务器,如何调整数据库分片,使部分数据迁移到新的服务器上?如何保证整个迁移过程快速、安全?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
83
极客时间专栏/后端技术面试 38 讲/架构的核心原理/26 | 搜索引擎架构:如何瞬间完成海量数据检索?.md
Normal file
83
极客时间专栏/后端技术面试 38 讲/架构的核心原理/26 | 搜索引擎架构:如何瞬间完成海量数据检索?.md
Normal file
@@ -0,0 +1,83 @@
|
||||
<audio id="audio" title="26 | 搜索引擎架构:如何瞬间完成海量数据检索?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/50/c2/5044140b68a715d4c266a2ddb18cbcc2.mp3"></audio>
|
||||
|
||||
我们在使用搜索引擎的时候,搜索结果页面会展示搜索到的结果数目以及花费时间。比如用Google搜索中文“后端技术”这个词,会显示找到约6.7亿条结果,用时0.45秒。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6d/43/6dee1fc91438b7974f734ff08ae4a343.png" alt="">
|
||||
|
||||
我们知道Google收录了全世界几乎所有的公开网页,这是一个非常庞大的数目,那么Google是如何做到在如此短的时间内完成了如此庞大的数据搜索呢?
|
||||
|
||||
## 搜索引擎倒排索引
|
||||
|
||||
数据的搜索与查找技术是计算机软件的核心算法,这方面已有非常多的技术和实践。而对于搜索引擎来说,要对海量文档进行快速内容检索,主要使用的是倒排索引技术。
|
||||
|
||||
像Google这样一个互联网搜索引擎,首先需要通过网络爬虫获取全球的公开网页。那么搜索引擎如何知道全世界的网页都在哪里呢?
|
||||
|
||||
事实上,互联网一方面是将全世界的人和网络应用联系起来,另一方面,也将全世界的网页通过超链接联系起来,几乎每个网页都包含了一些其他网页的超链接,这些超链接互相链接,就让全世界的互联网构成了一个大的网络。所以,搜索引擎只需要解析这些网页,得到里面的超链接,然后继续下载这些超链接的网页,继续解析,这样就可以得到全世界的网页了。
|
||||
|
||||
这个过程具体是这样的。首先选择一些种子URL,然后通过爬虫将这些URL对应的页面爬下来。其实,所谓的爬虫,就是发送URL请求,下载相应的HTML页面,然后将这些Web页面存储在自己的服务器上,并解析这些页面的HTML内容,当解析到网页里超链接URL的时候,再检查这个超链接是否已经在前面爬取过了,如果没有,就把这个超链接放到一个队列中,后面会请求这个URL,得到对应的HTML页面并解析其包含的超链接……如此不断重复,就可以将全世界的Web页面存储到自己的服务器中。
|
||||
|
||||
爬虫系统架构如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ee/cd/ee3c796bf1897e3255f3edff47a7cacd.png" alt="">
|
||||
|
||||
得到了全部网页以后,需要对每个网页进行编号,得到全部网页的文档集合。然后再解析每个页面,提取文档里的每个单词,如果是英文,那么每个单词都用空格分隔,比较容易;如果是中文,需要使用中文分词器才能提取到每个单词,比如“后端技术”,使用中文分词器得到的就是“后端”、“技术”两个词。
|
||||
|
||||
然后考察每个词在哪些文档中出现,比如“后端”在文档2、4、5、7中出现,“技术”在文档1、2、4中出现,这样我们就可以得到一个单词、文档矩阵:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4d/13/4db4adbeb66b2ece4022d394ed727613.png" alt="">
|
||||
|
||||
把这个单词、文档矩阵按照单词→文档列表的方式组织起来,就是倒排索引了:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/06/75/06f1dda3a1d2380370a60fbce1f17375.png" alt="">
|
||||
|
||||
我们这个例子中只有2个单词、7个文档。事实上,Google数以万亿的网页就是这样通过倒排索引组织起来的,网页数量虽然不可思议地庞大,但是单词数却是比较有限的,所以,整个倒排索引的大小相比网页数量要小得多。Google将每个单词的文档列表存储在硬盘中,而对于文档数量没那么大的应用而言,文档列表也可以存储在内存中。每个单词记录下硬盘或者内存中的文档列表地址,搜索的时候,只要搜索到单词,就可以快速得到文档地址列表。根据列表中的文档编号,展示对应的文档信息,就完成了海量数据的快速检索。
|
||||
|
||||
而搜索单词的时候,我们可以将所有单词构成一个Hash表,根据搜索词直接查找Hash表,就可以得到单词了。如果搜索词是“后端”,那么快速得到文档列表,有4个;如果搜索词是“后端技术”,那么首先需要对搜索词进行分词,得到“后端”、“技术”两个搜索单词,分别得到这两个单词的文档列表,然后将这两个文档列表求交集,也很快可以得到搜索结果,有两个。
|
||||
|
||||
虽然搜索引擎利用倒排索引已经可以很快得到搜索结果了,但是实践中,搜索引擎应用还会使用缓存对搜索进行加速,将整个搜索词对应的搜索结果直接放入缓存,以减少倒排索引的访问压力,以及不必要的集合计算。
|
||||
|
||||
## 搜索引擎结果排序
|
||||
|
||||
有了倒排索引,虽然可以快速得到搜索结果了,但是,如果搜索结果比较多,哪些文档应该优先展示给用户呢?我们使用Google搜索“后端技术”的时候,虽然Google告诉我们,搜索结果有6.7亿个,但是我们通常在搜索结果列表的头几个,就能找到想要的结果,而列表越往后,结果也越不是我们想要的。Google是如何知道我们想要的结果是哪些呢?这样的搜索结果展示显然是排过序的,那搜索引擎的结果是如何排序的呢?
|
||||
|
||||
事实上,Google使用了一种叫PageRank的算法,计算每个网页的权重,搜索结果就按照权重排序,权重高的网页在最终结果显示的时候排在前面。为什么权重高的网页正好就是用户想要看到的呢?我们先看下这个网页权重算法,即PageRank算法。
|
||||
|
||||
PageRank算法认为,如果一个网页里包含了某个网页的超链接,那么就表示该网页认可某个网页,或者说,该网页给某个网页投了一票。如下A、B、C、D四个网页,箭头指向的方向就是表示超链接的方向,B的箭头指向A,表示B网页包含A网页的超链接,也就是B网页给A网页投了一票。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d2/f5/d2763103bd0b10d6c21dd70ea1c096f5.png" alt="">
|
||||
|
||||
开始的时候,所有网页都初始化权重值为1,然后根据超链接关系计算新的权重。比如B页面包含了A和D两个页面的超链接,那么自己的权重1就被分成两个1/2分别投给A和D。而A页面的超链接包含在B、C、D三个页面中,那么A页面新的权重值就是这个三个页面投给它的权重值之和:1/2 + 1/3 + 1 = 11/6。
|
||||
|
||||
经过一轮PageRank计算后,每个页面都有了新的权重,然后基于这个新的权重再继续一轮计算,直到所有的网页权重稳定下来,就得到最终所有网页的权重,即最终的PageRank值。
|
||||
|
||||
通常,在一个网页中包含了另一个网页,是对另一个网页的认可,认为这个网页质量高,值得推荐。而被重要网页推荐的网页也应该是重要的,PageRank算法就是对这一设想的实现,PageRank值代表了一个网页受到的推荐程度,越受推荐越重要,就越是用户想看到的。基于每个网页的PageRank值对倒排索引中的文档列表进行排序,排在前面的文档通常也是用户想要看到的文档。
|
||||
|
||||
PageRank算法对于互联网网页排序效果很好,但是,对于那些用户生成内容(UGC)的网站而言,比如豆瓣、知乎,或者我们的[InfoQ](https://www.infoq.cn/),如果想在这些网站内部进行搜索,PageRank算法就没什么效果了。因为豆瓣的影评,知乎的回答,InfoQ的技术文章之间很少通过超链接进行推荐。
|
||||
|
||||
那么,要相对这些站内搜索引擎的结果进行排序,就需要利用其它一些信息以及算法,比如可以利用文章获得的点赞数进行排序,点赞越多,表示越获得其它用户的认可,越应该在搜索结果中排在前面。利用点赞数排序,或者PageRank排序,都是利用内容中存在的推荐信息排序,而这些推荐信息来自于广大参与其中的人,因此这些算法实现也被称作“集体智慧编程”。
|
||||
|
||||
除了用点赞数进行排序,有时候,我们更期望搜索结果按照内容和搜索词的相关性进行排序,比如我在infoq.cn搜索PageRank,我其实并不想看那些点赞很多,但是只提到一点点PageRank的文章,而想看主要讲PageRank算法的文章。
|
||||
|
||||
这种情况可以使用词频TF进行排序,词频表示某个词在该文档中出现的频繁程度,也代表了这个词和该文档的相关程度。词频公式如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/53/e3/53ba5cb404c5aa008db221fb106b0ee3.png" alt="">
|
||||
|
||||
使用豆瓣电影进行搜索的时候,豆瓣的搜索结果主要是电影名中包含了搜索词的电影,比如我们搜索“黑客”这个词,豆瓣的搜索结果列表就是以“黑客”为电影名的电影。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9f/0b/9f23220ee088a22a4d2d198ffba2290b.png" alt="">
|
||||
|
||||
但是,如果我想搜索电影内容是关于黑客的,但是标题里可能没有“黑客”两个字的电影,豆瓣的搜索就无能为力了。几年前,我自己专门写了一个电影搜索引擎,利用豆瓣的影评内容建立倒排索引,利用词频算法进行排序,搜索的结果如下,这个结果更符合我对电影搜索引擎的期待。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0b/c7/0b960049970ad13d26d2dd8d648108c7.png" alt="">
|
||||
|
||||
如果你对这个搜索引擎有兴趣,源代码的地址在这里:[https://github.com/itisaid/sokeeper](https://github.com/itisaid/sokeeper)
|
||||
|
||||
## 小结
|
||||
|
||||
事实上,搜索引擎技术不只是用在Google这样的搜索引擎互联网应用中,对于大多数应用而言,如果想要对稍具规模的数据进行快速检索,都需要使用搜索引擎技术。而对于淘宝这样的平台型应用,搜索引擎技术甚至驱动其核心商业模式。一方面,淘宝海量的商品需要通过搜索引擎完成查找,另一方面,淘宝的主要盈利来自于搜索引擎排名。所以,本质上,淘宝的核心技术和盈利模式跟百度、Google都是一样的。
|
||||
|
||||
## 思考题
|
||||
|
||||
文中我们讨论了PageRank算法,如果只有几百个网页,那么写一个程序计算每个网页PageRank就可以了,但是如果是Google这样万亿级的网页,网页之间的超链接关系数量更加庞大,而PageRank算法又需要多轮计算,如何才能较快地计算出所有网页的PageRank值呢?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
95
极客时间专栏/后端技术面试 38 讲/架构的核心原理/27 | 微服务架构:微服务究竟是灵丹还是毒药?.md
Normal file
95
极客时间专栏/后端技术面试 38 讲/架构的核心原理/27 | 微服务架构:微服务究竟是灵丹还是毒药?.md
Normal file
@@ -0,0 +1,95 @@
|
||||
<audio id="audio" title="27 | 微服务架构:微服务究竟是灵丹还是毒药?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/09/7c/09f1f5dac54e2a4c9a318d13eb4a767c.mp3"></audio>
|
||||
|
||||
微服务架构是从单体架构演化而来的。所谓单体架构,指的就是整个互联网系统所有代码打包在一个程序中,部署在一个集群上,一个单体应用构成整个系统。
|
||||
|
||||
而微服务架构则是将这个大的应用里面的一些模块拆分出来,这些模块独立部署在一些相对较小的服务器集群上,而应用通过远程调用的方式依赖这些独立部署的模块,完成业务处理。这些被独立部署的模块就被称为微服务,而这样的应用架构也被称为微服务架构。
|
||||
|
||||
应该说,模块化、低耦合一直以来都是软件设计追求的目标,独立部署的微服务使模块之间的依赖关系更加清晰,也更加隔离,使系统易于开发、维护,代表了正确的技术方向。但是在实践中,有时候却会出现这样的情况:用了微服务,系统反而变得更加难以开发、维护。技术团队痛苦不堪,觉得是微服务的锅,主张放弃微服务,退回到单体架构。
|
||||
|
||||
那么,究竟该不该上微服务?微服务是灵丹还是毒药?
|
||||
|
||||
## 单体架构的困难和挑战
|
||||
|
||||
阿里巴巴大约是国内最早尝试微服务的企业之一。让我们先回顾一下这段历史,看看当年阿里巴巴为什么要上微服务架构?微服务架构能解决什么问题?用好微服务需要做哪些准备?
|
||||
|
||||
阿里巴巴开始尝试微服务架构大约是在08年,在此之前,一个网站就是一个大应用,一个用Java开发的war包就包含了整个应用。系统更新的时候,即使只是更新其中极小的一部分,也要重新打包整个war包,发布整个系统。
|
||||
|
||||
随着业务的不断发展,这样的单体巨无霸系统遇到了越来越多的困难。
|
||||
|
||||
### 编译、部署困难
|
||||
|
||||
一个应用系统一个war包,这个war包可能有几个G大。对于开发工程师来说,开发编译和部署都是非常困难的,当时我用自己的电脑编译这个war包,大约需要半个多小时。工程师在开发的过程中,即使只改了庞大系统中的一行代码,也必须重新打包完整的系统,才能做开发测试。这样的单体系统对于开发部署和测试都是非常困难的,有时候甚至一天都写不了几行代码。
|
||||
|
||||
### 代码分支管理困难
|
||||
|
||||
因为单体应用非常庞大,所以代码模块也是由多个团队共同维护的。但最后还是要编译成一个单体应用,统一发布。这就要求把各个团队的代码merge在一起,这个过程很容易发生代码冲突。而merge的时候又是应用要发布的时候,发布过程本来就复杂,再加上代码merge带来的问题,各种情况纠缠在一起,极易出错。所以,在单体应用时代每一次应用发布,都需要搞到深更半夜。
|
||||
|
||||
### 数据库连接耗尽
|
||||
|
||||
对于一个巨型的应用而言,因为有大量的用户进行访问,所以必须把应用部署到大规模的服务器集群上。然后每个应用都需要与数据库建立连接,大量的应用服务器连接到数据库,会对数据库的连接产生巨大的压力,某些情况下甚至会耗尽数据库的连接。
|
||||
|
||||
### 新增业务困难
|
||||
|
||||
因为所有的业务都耦合在一个单一的大系统里,随着时间的发展,这个系统会变得非常的复杂,想要维护这样一个系统是非常困难和复杂的。很多工程师入职公司半年,都还不能熟悉业务,于是熟悉系统的老员工们忙得要死,加班加点干活,不熟悉系统的新员工们一帮忙就出乱,跟着加班加点地干活。整个公司热火朝天地干活,但最后还是常常出故障,新的功能迟迟不能上线。
|
||||
|
||||
### 发布困难
|
||||
|
||||
因为单体系统一个war包就包含了所有的代码,新版本发布的时候,即使跟自己开发的代码一点关系没有,但就因为包含了自己的代码,以防万一,所有开发工程师都不得不跟着发布值班,结果真正更新代码功能的只有几个人,而整个部门都要跟着加班。有代码更新的同事汗流浃背进行代码冲突处理和修复发布bug,没有代码更新的同事陪着聊天、打瞌睡、打游戏。
|
||||
|
||||
这些单体架构带来的困难,很多工程师自身都是有切身体会的。所以,在开始进行微服务架构重构的时候,虽然也遇到了很多挑战和困难,但是大家为了自身的利益,还是团结一致,成功实施了微服务架构重构。
|
||||
|
||||
## 微服务框架原理
|
||||
|
||||
当时,阿里自己开发了一个微服务框架实施微服务架构重构,这个微服务框架就是后来很著名的Dubbo。Dubbo应该说是借鉴了此前更早的SOA架构方案,即面向服务的体系架构。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fb/b1/fb06394d6cf986ef4f92fe2ef64f91b1.png" alt="">
|
||||
|
||||
在面向服务的体系架构里面,服务提供者向注册中心注册自己的服务,而服务调用者到注册中心去发现服务,发现服务以后,根据服务注册中心提供的访问接口和访问路径对服务发起请求,由服务的提供者完成请求,返回结果给调用者。后来的各种微服务框架,其实都可以认为是SOA架构的一种实现。但是在早期的SOA架构实践中,WSDL、SOAP这些协议都比较重,服务的注册与发现描述协议很复杂,服务的调用效率也比较低。
|
||||
|
||||
Dubbo在借鉴SOA架构的基础上进行了优化,抛弃了SOA一些不必要的规范约束,使用二进制协议进行服务注册与调用,执行效率和使用的简洁性都得到了极大提升。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/94/8f/949c822a95180ce269a4a8c2e8ffcf8f.png" alt="">
|
||||
|
||||
Dubbo架构和SOA架构一样,最核心的组件也是3个,分别是服务提供者、服务消费者和服务注册中心。
|
||||
|
||||
服务的提供者顾名思义就是微服务的具体提供者,通过微服务容器对外提供服务,而服务的消费者就是应用系统或是其他的微服务。
|
||||
|
||||
具体过程是服务的提供者程序在Dubbo的服务容器中启动,通过服务管理容器向服务注册中心进行注册,声明服务提供者提供的接口参数和规范,并且注册自己所在服务器的IP地址和端口。
|
||||
|
||||
服务的消费者如果想要调用某个服务,只需依赖服务提供者的接口进行编程即可。而服务接口通过Dubbo框架的代理访问机制,调用Dubbo的服务框架客户端,服务框架客户端会根据服务接口声明,去注册中心查找对应的服务提供者启动在哪些服务器上,并且将这个服务器列表返回给客户端。客户端根据某种负载均衡策略,选择某一个服务器,通过远程通讯模块发送具体的服务调用请求。
|
||||
|
||||
服务调用请求通过Dubbo底层自己的远程通讯模块,也就是RPC调用方式,将请求发送到服务的提供者服务器,服务提供者服务器收到请求以后,将该请求发送给服务提供者程序,完成服务的执行,并将服务执行处理结果通过远程调用通讯模块RPC返回给服务消费者客户端,服务消费者客户端将结果返回给服务调用程序,从而完成远程服务的调用,获得服务处理的结果。
|
||||
|
||||
## 微服务架构的落地实践
|
||||
|
||||
阿里当时进行微服务架构重构的目标比较明确,要解决的问题也是工程师们日常开发的痛点,大家热情参与其中,所以阿里的微服务重构过程还是比较成功的。
|
||||
|
||||
阿里微服务重构成功的另外一个重要因素是,即使在单体时代,war包内的模块关系也还是比较清晰的。所以在重构微服务的时候,只需要对这些模块进行较小的改动,进行微服务部署就可以了。
|
||||
|
||||
那么回到我们开头的问题,为什么有些企业会觉得用了微服务之后,反而问题更多了呢?
|
||||
|
||||
有些实施微服务的技术团队,既没有统一共识,让大家都热情参与;又没有做好模块划分,模块的职责边界不清,依赖关系混乱,很多单体架构下被隐藏的问题,到了微服务上反而变得更加严重,于是觉得是微服务这个技术有问题。
|
||||
|
||||
微服务不同于分布式缓存、分布式消息队列或者分布式数据库这些技术,这些技术相对说来是比较“纯粹”的技术,和业务的耦合关系不是非常大,使用这些技术的场景也比较明确。而微服务本身和业务强相关,如果业务关系没梳理好,模块设计不清晰,使用微服务架构很可能得不偿失,带来各种挫折。
|
||||
|
||||
很多技术团队在实施微服务的时候,把关注的重点放在了微服务技术框架上。事实上,微服务技术框架作为一个工具对于成功实施微服务是最不重要的,最重要的是使用微服务究竟能得到什么,也就是自己的需求是什么。
|
||||
|
||||
实施微服务的关注点应该遵循以下一个倒三角模型:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/04/7f/04aea38c47791a1a407e2e636e11577f.jpg" alt="">
|
||||
|
||||
首先明确自己的需求:我们到底想用微服务达到什么样的目的?需求清晰了,再去考虑具体要实现的价值,再根据价值构建我们的设计原则,根据原则寻找最佳实践,最后根据实践去选择最合适的工具。
|
||||
|
||||
如果相反,先找到一个工具,然后用工具硬往上套需求,只会导致技术也没用好,业务也没做好,所有人都疲惫不堪,事情变得一团糟,最后反过来怪技术没用。
|
||||
|
||||
## 总结
|
||||
|
||||
微服务和业务的关系是非常紧密的,仅仅用好微服务技术框架是无法成功实施微服务的。成功实施微服务最重要的是做好业务的模块化设计,模块之间要低耦合,高聚合,模块之间的依赖关系要清晰简单。只有这样的模块化设计,才能够构建出良好的微服务架构。如果系统本身就是一团遭,强行将它们拆分在不同的微服务里,只会使系统变得更加混乱。
|
||||
|
||||
关于模块的设计,可参考[第19篇文章,](https://time.geekbang.org/column/article/185043)[组件设计原则](https://time.geekbang.org/column/article/185043)。
|
||||
|
||||
## 思考题
|
||||
|
||||
最后留一个问题吧。现在中台的概念比较火,中台和微服务的关系也比较密切,你理解的中台是什么样的,有什么样的价值,满足什么样的需求,和微服务的关系是什么呢?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流进步一下。
|
125
极客时间专栏/后端技术面试 38 讲/架构的核心原理/28 | 高性能架构:除了代码,你还可以在哪些地方优化性能?.md
Normal file
125
极客时间专栏/后端技术面试 38 讲/架构的核心原理/28 | 高性能架构:除了代码,你还可以在哪些地方优化性能?.md
Normal file
@@ -0,0 +1,125 @@
|
||||
<audio id="audio" title="28 | 高性能架构:除了代码,你还可以在哪些地方优化性能?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/35/20/359ce9b535ac3a9c7fc5f68b23c3a220.mp3"></audio>
|
||||
|
||||
系统性能是互联网应用最核心的非功能性架构目标,系统因为高并发访问引起的首要问题就是性能问题:高并发访问的情况下,系统因为资源不足,处理每个请求的时间都会变慢,看起来就是性能变差。
|
||||
|
||||
因此,性能优化是互联网架构师的核心职责之一,通常我们想到性能优化,首先想到的就是优化代码。事实上,一个系统是由很多方面组成的,所有这些方面都可以进行优化,就是我们接下来要讲的7层优化。
|
||||
|
||||
进行性能优化的一个首要前提是,我们必须知道系统当前的性能状况,然后才能进行性能优化。而了解系统性能状况必须通过性能测试,我们先看下性能测试。
|
||||
|
||||
## 性能指标
|
||||
|
||||
所谓性能测试,就是模拟用户请求,对系统施加高并发的访问压力,观察系统的性能指标。系统性能指标主要有响应时间、并发数、吞吐量和性能计数器。
|
||||
|
||||
所谓**响应时间**,是指从发出请求开始到收到最后响应数据所需要的时间。响应时间是系统最重要的性能指标,最直接地反映了系统的快慢。
|
||||
|
||||
**并发数**是指系统同时处理的请求数,这个数字反映了系统的负载压力情况。性能测试的时候,通常在性能压测工具中,用多线程模拟并发用户请求,每个线程模拟一个用户请求,这个线程数就是性能指标中的并发数。
|
||||
|
||||
**吞吐量**是指单位时间内系统处理请求的数量,体现的是系统的处理能力。我们一般用每秒HTTP请求数HPS、每秒事务数TPS、每秒查询数QPS这样的一些指标来衡量。
|
||||
|
||||
吞吐量、响应时间和并发数三者之间是有关联性的。并发数不变,响应时间足够快,那么单位时间的吞吐量就会相应的提高。比如说并发数是1,响应时间如果是100ms,那么TPS就可以是10。如果响应时间是500ms,但是TPS吞吐量就变成了2。
|
||||
|
||||
**性能计数器**,指的是服务器或者操作系统性能的一些指标数据,包括系统负载 System Load、对象和线程数、内存使用、CPU使用、磁盘和网络I/O使用等指标,这些指标是系统监控的重要参数,反映系统负载和处理能力的一些关键指标,通常这些指标和性能是强相关的。这些指标很高,成为瓶颈,通常也预示着性能可能会出现问题。在实践中运维和开发人员会对这些性能指标设置一些报警的阈值。当监控系统发现性能计数器超过阈值的时候,就会向运维和开发人员报警,以便及时发现、处理系统的性能问题。
|
||||
|
||||
## 性能测试
|
||||
|
||||
性能测试是使用性能测试工具,通过多线程模拟用户请求对系统施加高并发的访问压力,得到以上这些性能指标。事实上,性能测试随着性能测试工具逐渐增加请求线程数,系统的吞吐量和响应时间会呈现出不同的性能特性。具体说来,整个测试过程又可细分为性能测试、负载测试、压力测试三个阶段。
|
||||
|
||||
**性能测试**是指以系统设计初期规划的性能指标为预期目标,对系统不断地施加压力,验证系统在资源可接受的范围内是否达到了性能的预期目标。这个过程中,随着并发数的增加,吞入量也在增加,但是响应时间变化不大。系统正常情况下的并发访问压力应该都在这个范围内。
|
||||
|
||||
**负载测试**则是对系统不断地施加并发请求,增加系统的压力,直到系统的某项或多项指标达到安全临界值。这个过程中,随着并发数的增加,吞吐量只有小幅的增加,达到最大值后,吞吐量还会下降,而响应时间则会不断增加。
|
||||
|
||||
**压力测试**是指在超过安全负载的情况下,增加并发请求数,对系统继续施加压力,直到系统崩溃,或者不再处理任何请求,此时的并发数就是系统的最大压力承受能力。这个过程中,吞吐量迅速下降,响应时间迅速增加,到了系统崩溃点,吞吐量为0,响应时间无穷大。
|
||||
|
||||
性能压测工具不断增加并发请求线程数,持续对系统进行性能测试、负载测试、压力测试,得到对应的TPS和响应时间,将这些指标画在一个坐标系里,就得到系统的性能特性曲线。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/27/60/277a0f7ee4ed7eca950f206589847860.png" alt="">
|
||||
|
||||
除了测出性能指标,性能测试有时候还需要进行稳定性测试。**稳定性测试**是指持续地对被测试系统施加一定的并发访问压力,使系统运行较长一段时间,以此检测系统是否稳定。通常,线上系统的负载压力是不稳定的,有时候,为了更好地模拟线上访问压力,稳定性测试的并发访问压力也可以不断调整压测线程数,在不稳定的并发压力下,测试系统的稳定性。
|
||||
|
||||
## 性能优化
|
||||
|
||||
一个系统是由很多方面构成的,程序只是这个系统的一小部分,因此进行性能优化的时候,也需要从系统的角度出发,综合考虑优化方案。
|
||||
|
||||
### 用户体验优化
|
||||
|
||||
性能优化的最终目的是让用户有更好的性能体验,所以性能优化最直接的其实是优化用户体验。同样500毫秒的响应时间,如果收到全部响应数据后才开始显示给用户,相比收到部分数据就开始显示,对用户的体验就完全不一样。同样,在等待响应结果的时候,只显示一个空白的页面和显示一个进度条,用户感受到的性能也是完全不同的。
|
||||
|
||||
除了用户体验优化这种比较**主观的性能优化**,即使我们想要真正优化性能指标,进行**客观的性能优化**,我们也可以从系统的角度,全方位考虑系统的各个方面。
|
||||
|
||||
从系统的宏观层面逐渐往下看,可以在7个层面进行性能优化。
|
||||
|
||||
### 第一层:数据中心优化
|
||||
|
||||
首先是数据中心性能优化,我们开发的软件是部署在数据中心的,对于一个全球访问的互联网应用而言,如果只有一个数据中心,那么最远的用户访问这个数据中心,即使以光速进行网络通信,一次请求响应的网络通信时间也需要130多毫秒。这已经是一个人可以明显感受到的响应延迟了。
|
||||
|
||||
所以,现在大型的互联网应用基本都采用多数据中心方案,在全球各个主要区域都部署自己的数据中心,就近为区域用户提供服务,加快响应速度。
|
||||
|
||||
### 第二层:硬件优化
|
||||
|
||||
我在专栏文章[21篇](https://time.geekbang.org/column/article/187517)讲分布式架构时,就对比分析了垂直伸缩和水平伸缩两种架构方案。事实上,即便使用水平伸缩,在分布式集群服务器内部,依然可以使用垂直伸缩,优化服务器的硬件能力。有时候,硬件能力的提升,对系统性能的影响是非常巨大的。
|
||||
|
||||
我在做Spark性能优化时发现,网络通信是整个计算作业的一个重要瓶颈点。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ba/fa/baccdc1d70c72261a7c043b07325a4fa.png" alt="" title="1G网卡">
|
||||
|
||||
我们看到,在使用1G网卡的情况下,某些计算阶段的网络通信开销时间需要50多秒。如果用软件优化的方法,进行数据压缩,一方面提升有限,另一方面还需要消耗大量CPU的资源,使CPU资源成为瓶颈。
|
||||
|
||||
后来通过硬件升级的办法进行优化,使用10G网卡替换1G网卡,网络通信时间消耗得到极大改善。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1c/e3/1cefebf76b3a46426e82a77edb595ae3.png" alt="" title="10G网卡">
|
||||
|
||||
原来需要50多秒的通信时间,现在只需要10多秒就可以完成,整个作业计算时间也大大缩短。硬件优化效果明显。
|
||||
|
||||
### 第三层:操作系统优化
|
||||
|
||||
不同操作系统以及操作系统内的某些特性也会对软件性能有重要影响。还是Spark性能优化的例子,在分析作业运行期CPU消耗的数据时,我发现在分布式计算的某些服务器上,操作系统自身消耗的CPU占比特别高。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/70/80/7026bf7c633328d814c577ce1d25e480.png" alt="">
|
||||
|
||||
图中蓝色部分是系统占用CPU,红色部分是Spark程序占用CPU,某些时候系统占用CPU比Spark程序占用CPU还高。经过分析发现,在某些版本的Linux中,transparent huge page这个参数是默认打开的,导致系统占用CPU过高。关闭这个参数后,系统CPU占用下降,整个计算时间也大幅缩短了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e7/91/e7f9366e299eb834a188cc26e57eef91.png" alt="">
|
||||
|
||||
### 第四层:虚拟机优化
|
||||
|
||||
像Java这样的编程语言开发的系统是需要运行在JVM虚拟机里的,虚拟机的性能对系统的性能也有较大影响,特别是垃圾回收,可能会导致应用程序出现巨大的卡顿。关于JVM虚拟机优化的有关原理可以参考《[Java虚拟机原理:JVM为什么被称为机器(machine)?](https://time.geekbang.org/column/article/168945)》
|
||||
|
||||
### 第五层:基础组件优化
|
||||
|
||||
在虚拟机之下,应用程序之上,还需要依赖各种基础组件,比如Web容器,数据库连接池,MVC框架等等。这些基础组件的性能也会对系统性能有较大影响。
|
||||
|
||||
### 第六层:架构优化
|
||||
|
||||
我们这个模块就是讨论各种互联网技术架构,大部分技术架构方案也是用来提升系统性能的。主要有缓存、消息队列、集群。
|
||||
|
||||
**缓存**:通过从缓存读取数据,加快响应时间,减少后端计算压力,缓存主要是提升读的性能。
|
||||
|
||||
**消息队列**:通过将数据写入消息队列,异步进行计算处理,提升系统的响应时间和处理速度,消息队列主要是提升写的性能。
|
||||
|
||||
**集群**:将单一服务器进行伸缩,构建成一个集群完成同一种计算任务,从而提高系统在高并发压力时候的性能。各种服务器都可以构建集群,应用集群、缓存集群、数据库集群等等。
|
||||
|
||||
### 第七层:代码优化
|
||||
|
||||
通过各种编程技巧和设计模式提升代码的执行效率,也是我们最能控制的一个优化手段。具体技巧有:
|
||||
|
||||
使用合理的数据结构优化性能,可参考《[数据结构原理:Hash表的时间复杂度为什么是O(1)?](https://time.geekbang.org/column/article/167938)》。
|
||||
|
||||
编写性能更好的SQL语句以及使用更好的数据库访问方式,可参考《[数据库原理:PrepareStatement为什么性能好又安全?](https://time.geekbang.org/column/article/172000)》。
|
||||
|
||||
实现异步I/O与异步方法调用,避免不必要的阻塞,可参考《[反应式编程框架设计:如何使程序调用不阻塞等待、立即响应?](https://time.geekbang.org/column/article/184379)》
|
||||
|
||||
此外,还可以使用线程池、连接池等对象池化技术,复用资源,减少资源的创建。当然最重要的还是利用各种设计模式和设计原则,开发清晰、易维护的代码。因为一团糟的代码里面有什么性能问题谁也搞不清楚,也没办法优化。
|
||||
|
||||
## 小结
|
||||
|
||||
性能优化的一般步骤是:首先进行性能测试,根据测试结果进行性能分析,寻找性能的瓶颈点,然后针对瓶颈进行优化,优化完成后继续进行性能测试,观察性能是否有所改善,是否达到预期的性能目标,如果没有达到目标,继续分析新的瓶颈点,不断迭代优化。
|
||||
|
||||
性能优化的一个前提是需要进行性能测试,了解系统的性能指标,才能有目标地进行性能优化。另一个前提是,必须要了解系统的内部结构,能够分析得到引起性能问题的原因所在,并能够解决问题。
|
||||
|
||||
因此性能优化是对一个架构师技能和经验的全面挑战,是架构师的必备技能之一。
|
||||
|
||||
## 思考题
|
||||
|
||||
除了文中提到的这些性能优化手段,还有哪些优化手段?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流。
|
@@ -0,0 +1,99 @@
|
||||
<audio id="audio" title="29 | 高可用架构:我们为什么感觉不到淘宝应用升级时的停机?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3b/00/3b44fc7fde302e5f32970689908aba00.mp3"></audio>
|
||||
|
||||
十几年前,我参加阿里巴巴面试的时候,觉得阿里巴巴这样的网站Web应用开发简直小菜,因为我之前是做类似Tomcat这样的Web容器开发的,所以面试的时候信心满满。
|
||||
|
||||
确实,面试官前面的问题都是关于数据结构、操作系统、设计模式的,也就是我们这个专栏模块一和模块二的内容。我感觉自己回答得还不错,所以更加信心满满。这时候,面试官忽然提了一个问题:
|
||||
|
||||
我们的Web程序每个星期都会发布一个新版本,但是程序要求7*24小时可用,也就是说,启动新版本程序替换老程序,进行程序升级的时候,程序还在对外提供服务,用户没有感觉到停机,我们是怎么做到的呢?
|
||||
|
||||
应用程序升级必须要用新版本的程序包替代老版本的程序包,并重新启动程序,这段时间程序是不能对外提供服务的,用户请求一定会失败。但是阿里巴巴让这段时间的用户请求依然是成功的。打个比方,就是要在飞机飞行过程中更换发动机,还不能让乘客感觉到。这个问题当时完全不在我的知识范围之内,但是我知道这个需求场景是真实存在的,而且确实应该是可以做到的,可是我完全不知道是怎么做到的。
|
||||
|
||||
面试官看我瞠目结舌,笑着问我,想不想知道答案。我立刻回答说想知道,结果面试官跟我说,加入我们团队你就知道了。
|
||||
|
||||
这其实是一个关于互联网应用可用性的问题。我们知道,Web应用在各种情况下都有可能不可访问,也就是不可用。各种硬件故障,比如应用服务器及数据库宕机、网络交换机宕机、磁盘损坏、网卡松掉等等。还有各种软件故障,程序Bug什么的。即使没有Bug,程序要升级,必须要关闭进程重新启动,这段时间应用也是不可用的;此外,还有外部环境引发的不可用,比如促销引来大量用户访问,导致系统并发压力太大而崩溃,以及,黑客攻击、机房火灾、挖掘机挖断光缆,各种情况导致的应用不可用。
|
||||
|
||||
而互联网的高可用是说,在上面各种情况下,应用都要是可用的,用户都能够正常访问系统,完成业务处理。
|
||||
|
||||
这似乎是不可能的任务。
|
||||
|
||||
## 高可用的度量
|
||||
|
||||
首先我们看下,什么叫做应用的高可用,以及可用性如何度量。业界通常用多少个9来说明互联网应用的可用性。比如说淘宝的可用性是4个9,就是说淘宝的服务99.99%可用。这句话的意思是,淘宝的服务要保证在所有的运行时间里只有0.01%不可用,也就是说一年大概有53分钟不可用。这个99.99%就叫做系统的可用性指标,这个值的计算公式是:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/38/d1/3868f02123df73e2faf6405f121836d1.png" alt="">
|
||||
|
||||
一般说来,两个9表示系统基本可用,年度不可用时间小于88小时;3个9是较高可用,年度不可用时间小于9个小时;4个9是具有自动恢复能力的高可用,年度不可用时间小于53分钟;5个9指极高的可用性,年度不可用时间小于5分钟。我们熟悉的互联网产品的可用性大多是4个9。淘宝、百度、微信,差不多都是这样。
|
||||
|
||||
下面我会讨论各种高可用技术方案。但不管是哪种方案,实现高可用需要投入的技术和设备成本都非常高。因此可用性并不是越高越好,而是要根据产品策略寻找高可用投入产出的最佳平衡点,像支付宝这样的金融产品就需要更高的可用性,而微博的可用性要求就会相对低一些。
|
||||
|
||||
可用性指标是对系统整体可用性的一个度量。在互联网企业中,为了更好地管理系统的可用性,界定好系统故障以后的责任,通常会用故障分进行管理。一般过程是,根据系统可用性指标换算成一个故障分,这个故障分是整个系统的故障分,比如10万分,然后根据各自团队各个产品各个职能角色承担的责任的不同,把故障分下发给每个团队,直到每个人,也就是说每个工程师在年初的时候就会收到一个预计的故障分。然后每一次系统出现可用性故障的时候,都会进行故障考核,划定到具体的团队和责任人以后,会扣除他的故障分。如果到了年底的时候,如果一个工程师的故障分为负分,那么很有可能会影响他的绩效考核。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a2/30/a254fce23e3cc3b9aecdc7fb9072ba30.png" alt="" title="故障分=故障时间*故障权重">
|
||||
|
||||
## 高可用的架构
|
||||
|
||||
系统的高可用架构就是要在上述各种故障情况下,保证系统依然可以提供服务,具体包含以下几种架构方案。我们已经在前面几篇架构专栏中提到过这些架构方案,这里我们从高可用的视角重新审视以下这些架构是如何实现高可用的。
|
||||
|
||||
### 冗余备份
|
||||
|
||||
既然各种服务器故障是不可避免的,那么架构设计上就要保证,当服务器故障的时候,系统依然可以访问。具体上就是要实现服务器的冗余备份。
|
||||
|
||||
冗余备份是说,提供同一服务的服务器要存在冗余,即任何服务都不能只有一台服务器,服务器之间要互相进行备份,任何一台服务器出现故障的时候,请求可以发送到备份的服务器去处理。这样,即使某台服务器失效,在用户看来,系统依然是可用的。
|
||||
|
||||
我在负载均衡架构这篇文章中讲了通过负载均衡服务器,将多台应用服务器构成一个集群共同对外提供服务,这样可以利用多台应用服务器的计算资源,满足高并发的用户访问请求。事实上,**负载均衡还可以实现系统的高可用**。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/76/45/76ff8741ebbb4d4e6211eae59f6e1d45.png" alt="">
|
||||
|
||||
负载均衡服务器通过心跳检测发现集群中某台应用服务器失效,然后负载均衡服务器就不将请求分发给这台服务器,对用户而言,也就感觉不到有服务器失效,系统依然可用。
|
||||
|
||||
回到我们开头的问题,阿里巴巴就是用这种方法实现的。应用程序升级的时候,停止应用进程,但是不影响用户访问。因为应用程序部署在多台服务器上,应用程序升级的时候,每次只STOP一台或者一部分服务器,在这些机器上进行程序升级,这个时候,集群中还有其他服务器在提供服务器,因此用户感觉不到服务器已经停机了。
|
||||
|
||||
此外我在数据存储架构这篇文章中提到的**数据库主主复制,也是一种冗余备份**。这个时候,不只是数据库系统RDBMS互相进行冗余备份,数据库里的数据也要进行冗余备份,一份数据存储在多台服务器里,保证当任何一台服务器失效,数据库服务依然可以使用。
|
||||
|
||||
### 失败隔离
|
||||
|
||||
保证系统高可用的另一个策略是失败隔离,将失败限制在一个较小的范围之内,使故障影响范围不扩大。具体实现**失败隔离的主要架构技术是消息队列**。
|
||||
|
||||
一方面,消息的生产者和消费者通过消息队列进行隔离。如果消费者出现故障的时候,生产者可以继续向消息队列发送消息,而不会感知到消费者的故障,等消费者恢复正常以后再去从消息队列中消费消息,所以从用户处理的视角看,系统一直是可用的。
|
||||
|
||||
发送邮件消费者出现故障,不会影响生产者应用的运行,也不会影响发送短信等其他消费者正常的运行。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/97/e0/97b1ffdb295ea3cbfa453794e0e0e5e0.png" alt="">
|
||||
|
||||
另一方面,由于分布式消息队列具有削峰填谷的作用,所以在高并发的时候,消息的生产者可以将消息缓冲在分布式消息队列中,消费者可以慢慢地从消息队列中去处理,而不会将瞬时的高并发负载压力直接施加到整个系统上,导致系统崩溃。也就是**将压力隔离开来**,使消息生产者的访问压力不会直接传递到消息的消费者,这样可以提高数据库等对压力比较敏感的服务的可用性。
|
||||
|
||||
同时,**消息队列还使得程序解耦,将程序的调用和依赖隔离开来**,我们知道,低耦合的程序更加易于维护,也可以减少程序出现Bug的几率。
|
||||
|
||||
### 限流降级
|
||||
|
||||
限流和降级也是保护系统高可用的一种手段。在高并发场景下,如果系统的访问量超过了系统的承受能力,可以通过限流对系统进行保护。限流是指对进入系统的用户请求进行流量限制,如果访问量超过了系统的最大处理能力,就会丢弃一部分的用户请求,保证整个系统可用,保证大部分用户是可以访问系统的。这样虽然有一部分用户的请求被丢弃,产生了部分不可用,但还是好过整个系统崩溃,所有的用户都不可用要好。
|
||||
|
||||
降级是保护系统的另一种手段。有一些系统功能是非核心的,但是它也给系统产生了非常大的压力,比如说在电商系统中有确认收货这个功能,即便我们不去确认收货,系统也会超时自动确认收货。
|
||||
|
||||
但实际上确认收货这个操作是一个非常重的操作,因为它会对数据库产生很大的压力:它要进行更改订单状态,完成支付确认,并进行评价等一系列操作。如果在系统高并发的时候去完成这些操作,那么会对系统雪上加霜,使系统的处理能力更加恶化。
|
||||
|
||||
解决办法就是在系统高并发的时候,比如说像淘宝双11的时候,当天可能整天系统都处于一种极限的高并发访问压力之下,这时候就可以将确认收货、评价这些非核心的功能关闭,将宝贵的系统资源留下来,给正在购物的人,让他们去完成交易。
|
||||
|
||||
### 异地多活
|
||||
|
||||
我们前面提到的各种高可用策略,都还是针对一个数据中心内的系统架构,针对服务器级别的软硬件故障而言的。但如果整个数据中心都不可用,比如说数据中心所在城市遭遇了地震,机房遭遇了火灾或者停电,这样的话,不管我们前面的设计和系统多么的高可用,系统依然是不可用的。
|
||||
|
||||
为了解决这个问题,同时也为了提高系统的处理能力和改善用户体验,很多大型互联网应用都采用了异地多活的多机房架构策略,也就是说将数据中心分布在多个不同地点的机房里,这些机房都可以对外提供服务,用户可以连接任何一个机房进行访问,这样每个机房都可以提供完整的系统服务,即使某一个机房不可使用,系统也不会宕机,依然保持可用。
|
||||
|
||||
异地多活的架构考虑的重点就是,用户请求如何分发到不同的机房去。这个主要可以在域名解析的时候完成,也就是用户进行域名解析的时候,会根据就近原则或者其他一些策略,完成用户请求的分发。另一个至关重要的技术点是,因为是多个机房都可以独立对外提供服务,所以也就意味着每个机房都要有完整的数据记录。用户在任何一个机房完成的数据操作,都必须同步传输给其他的机房,进行数据实时同步。
|
||||
|
||||
数据库实时同步最需要关注的就是数据冲突问题。同一条数据,同时在两个数据中心被修改了,该如何解决?为了解决这种数据冲突的问题,某些容易引起数据冲突的服务采用类似MySQL的主主模式,也就是说多个机房在某个时刻是有一个主机房的,某些请求只能到达主机房才能被处理,其他的机房不处理这一类请求,以此来避免关键数据的冲突。
|
||||
|
||||
## 小结
|
||||
|
||||
除了以上的高可用架构方案,还有一些高可用的运维方案:通过自动化测试减少系统的Bug;通过自动化监控尽早发现系统的故障;通过预发布验证发现测试环境无法发现的Bug;灰度发布降低软件错误带来的影响以及评估软件版本升级带来的业务影响等等。
|
||||
|
||||
## 思考题
|
||||
|
||||
预发布验证是将一台线上生产环境的服务器当做预发布服务器,在进行应用升级的时候,先在预发布服务器上进行升级。软件工程师访问这台服务器,验证系统正常后,再发布到其他服务器上。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b4/8f/b4d0281be379b8bfe622ba8bf0cde88f.png" alt="">
|
||||
|
||||
发布在这台预发布服务器上的应用,即使存在Bug,外部用户也不会感觉到,这是为什么呢?什么样的Bug是测试环境不能发现而需要到预发布服务器上才能发现的呢?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流。
|
118
极客时间专栏/后端技术面试 38 讲/架构的核心原理/30 | 安全性架构:为什么说用户密码泄漏是程序员的锅?.md
Normal file
118
极客时间专栏/后端技术面试 38 讲/架构的核心原理/30 | 安全性架构:为什么说用户密码泄漏是程序员的锅?.md
Normal file
@@ -0,0 +1,118 @@
|
||||
<audio id="audio" title="30 | 安全性架构:为什么说用户密码泄漏是程序员的锅?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/27/71/27de8b139a5bbe71ac4f0d16f8be7571.mp3"></audio>
|
||||
|
||||
系统安全是一个老生常谈又容易被忽视的问题,往往只有在系统被攻击了,数据泄漏了,才会关注软件安全问题。互联网应用因为要向全球用户提供服务,在任何地方都可以访问互联网应用,任何恶意的用户可以在世界任何地方对互联网系统发起攻击,因此互联网系统又具有天然的脆弱性。
|
||||
|
||||
在互联网各种安全问题中,最能引发话题,刺激大众神经的就是用户密码泄露。数据库被拖库,导致所有的数据泄露,这种系统安全问题涉及的因素可能有很多,大部分都和开发软件的程序员没有关系。但是因为数据库被拖库,黑客直接获得了用户密码等敏感信息,导致用户密码泄露就是程序员的责任了。
|
||||
|
||||
## 数据加解密
|
||||
|
||||
通过对用户密码、身份证号、银行卡号等敏感数据加密,保护数据安全,是软件安全性架构的一部分,是程序员和架构师的责任。
|
||||
|
||||
软件开发过程中,主要使用的加密方法有三种:单向散列加密、对称加密和非对称加密。
|
||||
|
||||
用户密码加密通常使用的是**单向散列加密**。所谓的单向散列加密是指对一串明文信息进行散列(hash)加密,得到的密文信息是不可以被解密的,也就是说给定一个密文,即使是加密者也无法知道它的明文是什么的,加密是单向的,不支持解密。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b4/97/b4e74dde3cd348d8a62a78fffa8ee497.png" alt="">
|
||||
|
||||
单向散列加密事实上是一种hash算法。我们熟悉的MD5算法就是一种单向散列加密算法,单向散列算法虽然无法通过对密文进行解密计算,还原得到原始明文。但是,如果知道了算法,就可以通过彩虹表的方法进行破解。彩虹表是常用明文和密文的映射表,很多人喜欢用生日做密码,其实生日的组合是非常有限的,轻易就可以建一个生日和密文的映射表。如果黑客得到了密文,可以通过查表的办法得到密码明文。
|
||||
|
||||
因此在实践中,使用单向散列算法加密,还需要在计算过程中加点“盐”salt,如果黑客不知道加的“盐”是什么,就无法建立彩虹表,还原得到明文。
|
||||
|
||||
单向散列加密的主要应用场景就是应用到用户密码加密上。加密和密码校验过程如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8c/41/8ce17005cd840a10750f7b1337749a41.png" alt="">
|
||||
|
||||
用户在注册的时候需要输入密码,应用服务器得到密码以后,调用单向散列加密算法,对密码进行加密,然后将加密后的密文存储到数据库中去。用户下一次登录的时候,在客户端依然需要输入密码,而用户输入的密码发送到Web服务器以后,Web服务器对输入的密码再进行一次单向散列加密,得到密文,然后和从数据库中取出来的密文进行对比,如果两个密文是相同的,那么用户的登录验证就是成功的。通过这种手段,可以保证用户密码的安全性,即使数据库被黑客拖库,也不会泄漏用户密码。
|
||||
|
||||
密码加密的时候也需要加点“盐”,这种场景下,每个用户加密的“盐”都可以不同,比如用用户的ID作为盐,这样可以增加破解的难度。
|
||||
|
||||
另一种加密手段是**对称加密**。
|
||||
|
||||
对称加密,顾名思义,就是使用一个加密算法和一个密钥,对一段明文进行加密以后得到密文,然后使用相同的密钥和对应的解密算法,对密文进行解密,就可以计算得到明文。对称加密主要用于加密一些敏感信息,对密文进行信息传输和存储,但是在使用的时候,必须要解密得到明文信息的一些场景。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9b/fb/9b61baa73c8f71b8ecb00fd3462cf0fb.png" alt="">
|
||||
|
||||
比如说用户的信用卡卡号,很多互联网电商网站支持用户使用信用卡进行支付。但如果直接把信用卡号、有效期、安全码存储在数据库中是比较危险的,所以必须对这些信息进行加密,在数据库中存储密文。但是在使用的时候又必须要对密文进行解密,还原得到明文,才能够正常使用。所以这个时候就要使用对称加密算法,在存储的时候使用加密算法进行加密,在使用的时候使用解密算法进行解密。
|
||||
|
||||
还有一种加密被称作**非对称加密**。所谓的非对称加密是指在加密的时候使用一个加密算法和一个加密密钥进行加密,得到一个密文。在解密的时候,必须使用解密算法和解密密钥进行解密才能够还原得到明文,加密密钥和解密密钥完全不同。通常加密密钥被称作公钥,解密密钥被称作私钥。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2a/c2/2a323c6e26ea861194d9749222279ac2.png" alt="">
|
||||
|
||||
非对称加密的典型应用场景,就是我们常见的HTTPS。用户在客户端进行网络通讯的时候,对数据使用加密密钥即公钥和加密算法进行加密,得到密文。到了数据中心的服务器以后,使用解密密钥即私钥和解密算法进行解密,得到明文。
|
||||
|
||||
由于非对称加密需要消耗的计算资源比较多,效率也比较差,HTTPS并不是每次请求响应都用非对称加密,而是先利用非对称加密,在客户端和服务器之间交换一个对称加密的密钥,然后每次请求响应都用对称加密。这样,用非对称加密保证对称加密密钥的安全,再用对称加密密钥保证请求响应数据的安全。
|
||||
|
||||
使用非对称加密,还可以实现数字签名。用数字签名的时候是反过来的,自己用私钥进行加密,得到一个密文,但是其他人可以用公钥将密文解开,因为私钥只有自己才拥有,所以等同于签名。一段经过自己私钥加密后的文本,文本内容就等于是自己签名认证过的。我在后面要讲到的区块链架构中,交易就使用非对称加密进行签名。
|
||||
|
||||
## HTTP攻击与防护
|
||||
|
||||
互联网应用对外提供服务主要就是通过HTTP协议,任何人都可以在任何地方通过HTTP协议访问互联网应用,因此HTTP攻击是黑客攻击行为中门槛最低的攻击方式,也是最常见的一种互联网攻击。而HTTP攻击中,最常见的是SQL注入攻击和XSS攻击。
|
||||
|
||||
SQL注入攻击就是攻击者在提交的请求参数里面,包含有恶意的SQL脚本。如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9a/05/9abecfd74cdd3a40588609462f38e205.png" alt="">
|
||||
|
||||
如果在Web页面中有个输入框,要求用户输入姓名,普通用户输入一个普通的姓名Frank,那么最后提交的HTTP请求如下:
|
||||
|
||||
```
|
||||
http://www.a.com?username=Frank
|
||||
|
||||
```
|
||||
|
||||
服务器在处理计算后,向数据库提交的SQL查询命令如下:
|
||||
|
||||
```
|
||||
Select id from users where username='Frank';
|
||||
|
||||
```
|
||||
|
||||
但是恶意攻击者可能会提交这样的HTTP请求:
|
||||
|
||||
```
|
||||
http://www.a.com?username=Frank';drop table users;--
|
||||
|
||||
```
|
||||
|
||||
即输入的uername是:
|
||||
|
||||
```
|
||||
Frank';drop table users;--
|
||||
|
||||
```
|
||||
|
||||
这样,服务器在处理后,最后生成的SQL是这样的:
|
||||
|
||||
```
|
||||
Select id from users where username='Frank';drop table users;--';
|
||||
|
||||
```
|
||||
|
||||
事实上,这是两条SQL,一条select查询SQL,一条drop table删除表SQL。数据库在执行完查询后,就将users表删除了,系统崩溃了。
|
||||
|
||||
SQL注入攻击我在[第6篇](https://time.geekbang.org/column/article/172000)讲到过,最有效的防攻击手段是SQL预编译。Java开发的话最好使用PrepareStatement提交SQL,而MyBatis等ORM框架主要的SQL提交方式就是用PrepareStatement。
|
||||
|
||||
XSS攻击即**跨站点脚本攻击**,攻击者构造恶意的浏览器脚本文件,使其在其他用户的浏览器上运行,进而进行攻击。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f4/d5/f4e0b1efb1b9777c2b0f9eb77016f3d5.png" alt="">
|
||||
|
||||
攻击者发送一个含有恶意脚本的请求给被攻击的服务器,比如通过发布微博的方式向微博的服务器发送恶意请求,被攻击的服务器将恶意脚本存储到本地的数据库中,其他的正常用户通过被攻击的服务器浏览信息的时候,服务器会读取数据库中含有恶意脚本的数据,并向其展现给正常的用户,在正常用户的浏览器上执行,从而达到攻击的目的。
|
||||
|
||||
XSS攻击防御的主要手段是**消毒**,检查用户提交的请求中是否含有可执行的脚本,因为大部分的攻击请求都包含JS等脚本语法,所以可以通过HTML转义的方式,对比较有危险的脚本语法关键字进行转义。比如把“>”转义为“&gt”,HTML显示的时候还是正常的“>”,但是这样的脚本无法在浏览器上执行,也就无法达到攻击的目的。
|
||||
|
||||
由于HTTP攻击必须以HTTP请求的方式提交到服务器,因此可以在服务器的入口统一进行拦截,对含有危险信息的请求,比如drop table,JS脚本等,进行消毒转义,或者直接拒绝请求。即设置一个Web应用防火墙,将危险请求隔离。
|
||||
|
||||
针对Web应用防火墙,我们可以自己开发一个统一的请求过滤器进行拦截,也可以使用ModSecurity([http://www.modsecurity.org/](http://www.modsecurity.org/))这样的开源WAF(Web Application Firewall)。
|
||||
|
||||
## 小结
|
||||
|
||||
硬件指令和操作系统可能会有漏洞,我们使用的各种框架和SDK可能也有漏洞,这些漏洞从被发现,到被公开,再到官方修复漏洞,可能会经过一个或长或短的时间,这个时间内就可能被掌握这些漏洞的黑客利用,攻击系统。
|
||||
|
||||
这种漏洞在官方修复之前,我们基本没有办法应对。但是黑客攻击也不是无意义的攻击,而是为了各种利益而来,很多时候是针对数据而来,做好数据加密存储与传输,即使是数据泄露了,黑客无法对数据解密,利用数据获利,也可以保护我们的数据资产。
|
||||
|
||||
同时加强请求的合法性检查,避免主要的HTTP攻击,及时更新生产环境的各种软件版本,修复安全漏洞,提高黑客攻击的难度,使其投入产出不成比例,从而营造一个相对安全的生产环境。
|
||||
|
||||
## 思考题
|
||||
|
||||
除了文中提到的HTTP攻击方式,还有哪些比较常见的HTTP攻击?对应的防护手段有哪些?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流。
|
176
极客时间专栏/后端技术面试 38 讲/架构的核心原理/31 | 大数据架构:大数据技术架构的思想和原理是什么?.md
Normal file
176
极客时间专栏/后端技术面试 38 讲/架构的核心原理/31 | 大数据架构:大数据技术架构的思想和原理是什么?.md
Normal file
@@ -0,0 +1,176 @@
|
||||
<audio id="audio" title="31 | 大数据架构:大数据技术架构的思想和原理是什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2a/6a/2a47e9e29050880d52e924507fac416a.mp3"></audio>
|
||||
|
||||
我在开篇词讲到,任何新技术都不是凭空产生的,都是在既有技术的基础之上,进行了一些创新性的组合扩展,应用到一些合适的场景之中,然后爆发出来巨大的生产力。后面几篇我要讲的大数据技术,区块链技术都是如此。
|
||||
|
||||
大数据技术其实是分布式技术在数据处理领域的创新性应用,本质和我们此前讲到的分布式技术思路一脉相承:用更多的计算机组成一个集群,提供更多的计算资源,从而满足更大的计算压力要求。
|
||||
|
||||
前面我们讨论的各种分布式缓存、负载均衡、分布式存储等都是讲如何在高并发的访问压力下,利用更多的计算机满足用户的请求访问压力。而大数据技术讨论的是,如何利用更多的计算机满足大规模的数据计算要求。
|
||||
|
||||
大数据就是将各种数据统一收集起来进行计算,发掘其中的价值。这些数据,既包括数据库的数据,也包括日志数据,还包括专门采集的用户行为数据;既包括企业内部自己产生的数据,也包括从第三方采购的数据,还包括使用网络爬虫获取的各种互联网公开数据。
|
||||
|
||||
面对如此庞大的数据,如何存储,如何利用大规模的服务器集群处理计算大量的数据,就是大数据技术的核心关键。
|
||||
|
||||
## 分布式文件存储HDFS架构
|
||||
|
||||
大规模数据计算首先要解决的是大规模数据的存储问题。如何将数百T,数百P的数据存储起来,通过一个文件系统统一管理,这本身就是一个极大的挑战。
|
||||
|
||||
我曾在专栏[第5篇](https://time.geekbang.org/column/article/169533)讲过,分布式文件系统HDFS的架构。
|
||||
|
||||
HDFS可以将数千台服务器组成一个统一的文件存储系统,其中NameNode服务器充当文件控制块的角色,进行文件元数据管理,即记录文件名、访问权限、数据存储地址等信息,而真正的文件数据则存储在DataNode服务器上。
|
||||
|
||||
DataNode以块为单位存储数据,所有的块信息,比如块ID、块所在的服务器IP地址等,都记录在NameNode,而具体的块数据则存储在DataNode上。理论上,NameNode可以将所有DataNode服务器上的所有数据块都分配给一个文件,也就是说,一个文件可以使用所有服务器的硬盘存储空间,达到数百P的大小。
|
||||
|
||||
此外,HDFS为了保证不会因为硬盘或者服务器损坏而导致文件损坏,还会对数据块进行复制,每个数据块都会存储在多台服务器上,甚至多个机架上。
|
||||
|
||||
## 大数据计算MapReduce架构
|
||||
|
||||
数据存储在HDFS上的最终目标还是为了计算,进行数据分析或者机器学习,从而获得有益的结果。但是如果像传统的应用程序那样,把HDFS当做普通文件,从文件读取数据,进行计算,那么对于需要一次计算数百T数据的大数据计算场景,就不知道要算到什么时候了。
|
||||
|
||||
大数据处理的经典计算框架是MapReduce。MapReduce的核心思想是对数据进行分片计算。既然数据是以块为单位分布存储在很多台服务器组成的集群上,那么能不能就在这些服务器上针对每个数据块进行分布式计算呢?
|
||||
|
||||
事实上,MapReduce将同一个计算程序启动在分布式集群的多台服务器上,每个服务器上的程序进程都读取本服务器上要处理的数据块进行计算,因此,大量的数据就可以同时进行计算了。但是这样的话,每个数据块的数据都是独立的,如果这些数据块需要进行关联计算怎么办?
|
||||
|
||||
MapReduce将计算过程分成两个部分,一个是map过程,每个服务器上会启动多个map进程,map优先读取本地数据进行计算,计算后输出一个<key, value>集合。另一个是reduce过程,MapReduce在每个服务器上都启动多个reduce进程,然后对所有map输出的<key, value>集合进行shuffle操作。所谓shuffle就是将相同的key发送到同一个reduce进程,在reduce中完成数据关联计算。
|
||||
|
||||
我们以经典的WordCount,也就是统计所有数据中相同单词的词频数据为例,看看map和reduce的处理过程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1b/5b/1b7b317b5863743112b3f45f7ae9025b.png" alt="">
|
||||
|
||||
假设原始数据有两个数据块,MapReduce框架启动两个map进程进行处理,分别读入数据。map函数对输入数据进行分词处理,然后针对每个单词输出<单词, 1>这样的<key, value>结果。然后,MapReduce框架进行shuffle操作,相同的key发送给同一个reduce进程,reduce的输入就是<key, value列表>这样的结构,即相同key的value合并成一个value列表。
|
||||
|
||||
在这个例子中,这个value列表就是很多个1组成的列表。reduce对这些1进行求和操作,就得到每个单词的词频结果了。具体的MapReduce程序如下:
|
||||
|
||||
```
|
||||
public class WordCount {
|
||||
|
||||
public static class TokenizerMapper
|
||||
extends Mapper<Object, Text, Text, IntWritable>{
|
||||
|
||||
private final static IntWritable one = new IntWritable(1);
|
||||
private Text word = new Text();
|
||||
|
||||
public void map(Object key, Text value, Context context
|
||||
) throws IOException, InterruptedException {
|
||||
StringTokenizer itr = new StringTokenizer(value.toString());
|
||||
while (itr.hasMoreTokens()) {
|
||||
word.set(itr.nextToken());
|
||||
context.write(word, one);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class IntSumReducer
|
||||
extends Reducer<Text,IntWritable,Text,IntWritable> {
|
||||
private IntWritable result = new IntWritable();
|
||||
|
||||
public void reduce(Text key, Iterable<IntWritable> values,
|
||||
Context context
|
||||
) throws IOException, InterruptedException {
|
||||
int sum = 0;
|
||||
for (IntWritable val : values) {
|
||||
sum += val.get();
|
||||
}
|
||||
result.set(sum);
|
||||
context.write(key, result);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上面讲述了map和reduce进程合作完成数据处理的过程,那么这些进程是如何在分布式的服务器集群上启动的呢?数据是如何流动,最终完成计算的呢?我们以MapReduce1为例看下这个过程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/71/15/710e8315ab9723d7410a93bb5f871015.png" alt="">
|
||||
|
||||
MapReduce1主要有JobTracker和TaskTracker两种进程角色,JobTracker在MapReduce集群中只有一个,而TaskTracker则和DataNode一起,启动在集群的所有服务器上。
|
||||
|
||||
MapReduce应用程序JobClient启动后,会向JobTracker提交作业,JobTracker根据作业中输入文件路径分析,需要在哪些服务器上启动map进程,然后就向这些服务器上的TaskTracker发送任务命令。
|
||||
|
||||
TaskTracker收到任务后,启动一个TaskRunner进程下载任务对应的程序,然后反射加载程序中的map函数,读取任务中分配的数据块,进行map计算。map计算结束后,TaskTracker会对map输出进行shuffle操作,然后TaskRunner加载reduce函数进行后续计算。
|
||||
|
||||
HDFS和MapReduce都是Hadoop的组成部分。
|
||||
|
||||
## 大数据仓库Hive架构
|
||||
|
||||
MapReduce虽然只有map和reduce两个函数,却几乎可以满足任何大数据分析和机器学习的计算场景。不过复杂的计算可能需要多个job才能完成,这些job之间还需要根据其先后依赖关系进行作业编排,开发比较复杂。
|
||||
|
||||
数据分析传统上主要使用SQL进行分析,如果能根据SQL自动生成MapReduce,那么可以极大降低大数据技术在数据分析领域的应用门槛。
|
||||
|
||||
Hive就是这样一个工具。我们看下,对于如下一条常见的SQL语句,Hive是如何将其转换成MapReduce计算的。
|
||||
|
||||
```
|
||||
SELECT pageid, age, count(1) FROM pv_users GROUP BY pageid, age;
|
||||
|
||||
```
|
||||
|
||||
这是一条常见的SQL统计分析语句,统计不同年龄的用户访问不同网页的兴趣偏好,具体数据输入和执行结果示例如下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/31/23/31746fc5876e5078dac36f0fa4779123.jpg" alt="">
|
||||
|
||||
我们看这个示例就会发现,这个计算场景和WordCount很像。事实上也确实如此,我们可以用MapReduce的计算过程完成这条SQL的处理。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3d/76/3d4978dff6074ee9b1b44fbab0d16976.jpg" alt="">
|
||||
|
||||
map函数输出的key是表的行记录,value是1,reduce函数对相同的行记录,也就是相同的key的value集合进行求和计算,就得到最终的SQL输出结果了。
|
||||
|
||||
那么Hive要做的就是将SQL翻译成MapReduce程序代码,实际上,Hive内置了很多Operator,每个Operator完成一个特定的计算过程,Hive将这些Operator构造成一个有向无环图DAG,然后根据这些Operator之间是否存在shuffle将其封装到map或者reduce函数,就可以提交给MapReduce执行了。Operator组成的DAG图示例如下,这是一个包含where查询条件的SQL,where查询条件对应一个FilterOperator。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cb/a6/cb1236ad035ca01cffbb9df47fa88fa6.jpg" alt="">
|
||||
|
||||
Hive整体架构如下,Hive的表数据存储在HDFS。表的结构,比如表名、字段名、字段之间的分隔符等存储在Metastore。用户通过Client提交SQL到Driver,Driver请求Compiler将SQL编译成如上示例的DAG执行计划,然后交给Hadoop执行。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/40/7c/40e472fef9c766a79a13cd49aa57ee7c.jpg" alt="">
|
||||
|
||||
## 快速大数据计算Spark架构
|
||||
|
||||
MapReduce主要使用硬盘存储计算过程中的数据,这样虽然可靠性比较高,但是性能其实比较差。此外,MapReduce只能使用map和reduce函数进行编程,虽然能够完成各种大数据计算,但是编程比较复杂。而且,受map和reduce编程模型简单的影响,复杂的的计算必须组合多个MapReduce job才能完成,编程难度进一步增加。
|
||||
|
||||
Spark在MapReduce基础上进行改进,主要使用内存进行中间计算数据存储,加快了计算执行时间,在某些情况下,性能可以提升上百倍。Spark的主要编程模型是RDD,弹性数据集。在RDD上定义了许多常见的大数据计算函数,利用这些函数,可以用极少的代码完成较为复杂的大数据计算。前面举例的WorkCount,如果用Spark编程,只需要三行代码:
|
||||
|
||||
```
|
||||
val textFile = sc.textFile("hdfs://...")
|
||||
val counts = textFile.flatMap(line => line.split(" "))
|
||||
.map(word => (word, 1))
|
||||
.reduceByKey(_ + _)
|
||||
counts.saveAsTextFile("hdfs://...")
|
||||
|
||||
```
|
||||
|
||||
首先,从HDFS读取数据,构建出一个RDD textFile。然后,在这个RDD上执行三个操作:将输入数据的每一行文本用空格拆分成单词;将每个单词进行转换,word→(word, 1),生成<Key, Value>的结构;相同的Key进行统计,统计方式是对Value求和。最后,将RDD counts写入到HDFS,完成结果输出。
|
||||
|
||||
上面代码中flatMap、map、reduceByKey都是Spark的RDD转换函数,RDD转换函数的计算结果还是RDD,所以上面三个函数可以写在一行代码,最后得到的还是RDD。Spark会根据程序中的转换函数生成计算任务执行计划,这个执行计划就是一个DAG。Spark可以在一个作业中完成非常复杂的大数据计算。
|
||||
|
||||
Spark DAG示例如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c8/08/c83f34a6b6e5c0c081e4ab48ce705a08.jpg" alt="">
|
||||
|
||||
如上所示,A、C和E是从HDFS上加载的RDD,A经过groupBy分组统计转换函数后得到RDD B,C经过map转换函数后得到RDD D,D和E经过union合并转换函数后得到RDD F,B和F经过join连接转换函数后得到最终结果RDD G。
|
||||
|
||||
## 大数据流计算架构
|
||||
|
||||
Spark虽然比MapReduce快很多,但是大多数场景下,计算耗时依然是分钟级别的,这种计算一般被称为大数据批处理计算。而在实际应用中,有些时候需要在毫秒级完成不断输入的海量数据的计算处理,比如实时对摄像头采集的数据进行监控分析,这就是所谓的大数据流计算。
|
||||
|
||||
早期比较著名的流式大数据计算引擎是Storm,后来随着Spark的火爆,Spark上的流式计算引擎Spark Streaming也逐渐流行起来。Spark Streaming的架构原理是将实时流入的数据切分成小的一批一批的数据,然后将这些小的一批数据交给Spark执行。由于数据量比较小,Spark Streaming又常驻系统,不需要重新启动,因此可以毫秒级完成计算,看起来像是实时计算一样。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ba/b5/ba987158d27fc5bbad382c0111e3b2b5.jpg" alt="">
|
||||
|
||||
最近几年比较流行的大数据引擎Flink其架构原理其实和Spark Streaming很相似,随着数据源的不同,根据数据量和计算场景的要求,可以灵活适应流计算和批处理计算。
|
||||
|
||||
## 小结
|
||||
|
||||
大数据技术可以说是分布式技术的一个分支,都是面临大量的计算压力,采用分布式服务器集群的方案解决问题。差别是大数据技术要处理的数据具有关联性,所以需要有个中心服务器进行管理,NameNode、JobTracker都是这样的中心服务器。
|
||||
|
||||
## 思考题
|
||||
|
||||
SQL生成MapReduce计算的时候,如果遇到join这样的SQL操作,map函数和reduce函数该如何设计?以如下SQL和输入数据为例:
|
||||
|
||||
```
|
||||
SELECT pv.pageid, u.age FROM page_view pv JOIN user u ON (pv.userid = u.userid);
|
||||
|
||||
```
|
||||
|
||||
page_view:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/32/db/32f6e0431cfbad0bf055fa3e22fd4edb.png" alt="9">
|
||||
|
||||
user:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/53/bc/53ef8d27c7a32e02e878230cf861b0bc.png" alt="">
|
103
极客时间专栏/后端技术面试 38 讲/架构的核心原理/32 | AI与物联网架构:从智能引擎到物联网平台.md
Normal file
103
极客时间专栏/后端技术面试 38 讲/架构的核心原理/32 | AI与物联网架构:从智能引擎到物联网平台.md
Normal file
@@ -0,0 +1,103 @@
|
||||
<audio id="audio" title="32 | AI与物联网架构:从智能引擎到物联网平台" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/30/a9/30141d36551f67d4dd523d5975eea7a9.mp3"></audio>
|
||||
|
||||
当我们在说大数据技术的时候,说的可能是几种差别很大的技术。
|
||||
|
||||
一种是大数据底层技术,指的就是各种大数据计算框架、存储系统、SQL引擎等等,这些技术比较通用,经过十几年的优胜劣汰,主流的技术产品相对比较集中,主要就是我上篇专栏讨论的MapReduce、Spark、Hive、Flink等技术产品。
|
||||
|
||||
一种是大数据平台技术,Spark、Hive这些大数据底层技术产品不像我们前面讨论过的分布式缓存、分布式消息队列,在处理用户请求的应用中,使用这些技术产品的API接口就可以了。大数据计算的数据通常不是用户请求的数据,计算时间也往往超过了一次用户请求响应能够接受的时间。但是大数据的计算结果又常常需要在用户交互过程中直接呈现,比如电商常用的智能推荐,用户购买一个商品,系统会推荐可能感兴趣的商品,这些推荐的商品就是大数据计算的结果。所以在互联网系统架构中,需要把处理用户请求的在线业务系统和大数据计算系统打通。这就需要一个大数据平台来完成。
|
||||
|
||||
此外还有一种技术是数据分析与机器学习算法,上面提到的商品智能推荐就是这样一种算法,通过算法向用户呈现他感兴趣的商品,使互联网应用看起来好像有某种智能一样。
|
||||
|
||||
## 大数据平台架构
|
||||
|
||||
我们先看下大数据平台架构。上面说过,大数据平台主要就是跨越需要长时间处理的大数据计算和需要实时响应的互联网应用之间的鸿沟,使系统成为一个完整的整体。
|
||||
|
||||
一个典型的大数据平台架构如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d2/ae/d2ba4c3628056e8948f771931fb1a0ae.png" alt="">
|
||||
|
||||
整个大数据平台可以分为三个部分:数据采集、数据处理和数据输出。
|
||||
|
||||
首先要有数据,数据主要有两个来源,一方面是应用服务器以及前端App实时产生的数据、日志以及埋点采集的数据,另一方面是外部爬虫和第三方数据。
|
||||
|
||||
通过大数据平台的数据同步系统,这些数据导入到HDFS中。由于不同数据源格式不同,数据源存储系统不同,因此需要针对不同的数据源,开发不同的同步系统。同时,为了能够更好地对写入到HDFS的数据进行分析和挖掘,还需要对这些数据进行清洗、转换,因此数据同步系统实际上承担的是传统数据仓库ETL的职责,即数据的抽取(Extract)、转换(Transform)、载入(Load)。
|
||||
|
||||
写入到HDFS的数据会被MapReduce、Spark、Hive等大数据计算框架执行。数据分析师、算法工程师提交SQL以及MapReduce或者Spark机器学习程序到大数据平台。大数据平台的计算资源通常总是不足的,因此这些程序需要在任务调度管理系统的调度下排队执行。
|
||||
|
||||
SQL或者机器学习程序的计算结果写回到HDFS,然后再通过数据同步系统导出到数据库,应用服务器就可以直接访问这些数据,在用户请求的时候为用户提供服务了,比如店铺访问统计数据,或者智能推荐数据等。
|
||||
|
||||
所以有了大数据平台,用户产生的数据就会被大数据系统进行各种关联分析与计算,然后又应用于用户请求处理。只不过这个数据可能是历史数据,比如淘宝卖家只能查看24小时前的店铺访问统计。
|
||||
|
||||
大数据计算也许需要几个小时甚至几天,但是用户有时候可能需要实时得到数据。比如想要看当前的访问统计,那么就需要用到大数据流计算了。来自数据源的数据实时进入大数据流计算引擎Spark Streaming等,实时处理后写入数据库。这样卖家既可以看到历史统计数据,又可以看到当前的统计数据。
|
||||
|
||||
## 智能推荐算法
|
||||
|
||||
大数据平台只是提供了数据获取、存储、计算、应用的技术方案,真正挖掘出这些数据之间的关系,让数据发挥价值的是各种机器学习算法。这些各种算法中,最常见的大概就是智能推荐算法了。
|
||||
|
||||
我们在淘宝购物,在头条阅读新闻,在抖音刷短视频,背后其实都有智能推荐算法。这些算法不断分析、计算我们的购物偏好、浏览习惯,然后为我们推荐可能喜欢的商品、文章、视频。事实上,这些产品的推荐算法是如此智能、高效,以至于我们常常一打开淘宝,就买个不停;一打开抖音,就停不下来。
|
||||
|
||||
我们看几种简单的推荐算法,了解一下推荐算法背后的原理。
|
||||
|
||||
基于**人口统计的推荐**是相对比较简单的一种推荐算法。根据用户的基本信息进行分类,然后将商品推荐给同类用户。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a6/2a/a6519660c588c7d1adece089b0b3612a.png" alt="">
|
||||
|
||||
用户A和用户C的年龄相近,性别相同,那么可以将用户A和用户C划分为同类。用户A喜欢商品D,那么推测用户C可能也喜欢这个商品,系统就可以将这个商品推荐给用户C。
|
||||
|
||||
图中示例比较简单,在实践中,还应该根据用户收入、居住地区、学历、职业等各种因素进行用户分类,以使推荐的商品更加准确。
|
||||
|
||||
**基于商品属性的推荐**和基于人口统计的推荐相似,只是根据商品的属性进行分类,然后根据商品分类进行推荐。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/00/2d/00f24e40f1a7213923f15a6cdc5f002d.png" alt="">
|
||||
|
||||
电影A和电影D都是科幻、战争类型的电影,如果用户A喜欢看电影A,那么很有可能他也会喜欢电影D,就可以给用户A推荐电影D。
|
||||
|
||||
这和我们的生活常识也是相符合的,如果一个人连续看了几篇关于篮球的新闻,那么很大可能再给他推荐一篇篮球的新闻,他还是会有兴趣看。
|
||||
|
||||
**基于用户的协同过滤推荐**,根据用户的喜好进行用户分类,然后根据用户分类进行推荐。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b0/ee/b0e2409bd54dfa517a4f787531f869ee.png" alt="">
|
||||
|
||||
这个例子里,用户A和用户C都喜欢商品A和商品B,根据他们的喜好可以分为同类。然后用户A还喜欢商品D,那么将商品D推荐给用户C,他可能也会喜欢。
|
||||
|
||||
现实中,跟我们有相似喜好品味的人,也常常被我们当做同类,他们喜欢的其他东西,我们也愿意去尝试。
|
||||
|
||||
**基于商品的协同过滤推荐**,则是根据用户的喜好对商品进行分类,然后根据商品分类进行推荐。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e1/11/e1d9d7658eb9828d4fd40bc26900e211.png" alt="">
|
||||
|
||||
这个例子中,喜欢商品B的用户A和B都喜欢商品D,那么商品B和商品D就可以分为同类。那么对于同样喜欢商品B的用户C,很有可能也喜欢商品D,就可以将商品D推荐给用户C。
|
||||
|
||||
这里描述的推荐算法比较简单,事实上,要想做好推荐其实是非常难的。用户不要你觉得他喜欢,而要他觉得喜欢,有很多智能推荐的效果不好,被用户吐槽“人工智障”。推荐算法的不断优化需要不断收集用户反馈,不断迭代算法和升级数据。
|
||||
|
||||
## 物联网大数据架构
|
||||
|
||||
物联网的目标是万物互联,将我们生产生活有关的一切事物都通过物联网连接起来。家里的冰箱、洗衣机、扫地机器人、空调都通过智能音响连接起来。汽车、停车场、交通信号灯都通过交通指挥中心连接起来。这些被连接的设备数据再经过分析计算反馈给工厂、电厂、市政规划等生产管理部门,控制生产投放。
|
||||
|
||||
物联网架构的关键是终端设备数据的采集、处理与设备的智能控制,背后依然是大数据与AI算法。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fc/3d/fc91925b408be839db63d3acca42df3d.png" alt="">
|
||||
|
||||
终端设备负责采集现场数据,这些数据被汇总到智能网关,智能网关经过初步的转换、计算后将数据发送给物联网大数据平台,大数据平台通过消息队列接收发送上来的各种数据。
|
||||
|
||||
由于物联网终端设备在现场实时运行,需要实时控制,因此大数据平台也需要实时处理这些数据。大数据流计算引擎会从消息队列中获取数据进行实时处理。
|
||||
|
||||
对于一些简单的数据处理来说,流式计算利用配置好的规则进行计算就可以了,而复杂的处理还需要利用机器学习模型。机器学习模型是通过大数据平台离线计算得到的,而离线计算使用的数据则是流计算从消息队列中获取的。
|
||||
|
||||
流式计算的结果通常是终端设备的控制信息,这些信息通过设备管理组件被发送给智能网关,智能网关通过边缘计算,产生最终的设备控制信号,控制终端智能设备的动作。而物联网管理人员也可以通过应用程序直接远程控制设备。
|
||||
|
||||
随着5G时代的到来,终端通信速度的提升和费用的下降,物联网也许会迎来更加快速的发展。
|
||||
|
||||
## 小结
|
||||
|
||||
很多学习大数据技术的人是在学习大数据的应用。通常情况下,作为大数据技术的使用者,我们不需要开发Hadoop、Spark这类大数据低层技术产品,只需要使用、优化它们就可以了。
|
||||
|
||||
在大数据应用中,我们需要开发的是大数据平台。大数据平台的各种子系统,比如数据同步、调度管理这些,虽然都有开源的技术可以选择,但是每家公司的大数据平台都是独一无二的,因此还是要进行各种二次开发,最终平台的整合和完成都需要我们来开发。
|
||||
|
||||
而真正使数据发挥价值,使大数据平台产生效果的,其实是算法,是算法发现了数据的关联关系,挖掘出了数据的价值。因此我们应用大数据也要关注大数据算法。
|
||||
|
||||
## 思考题
|
||||
|
||||
最后给你留一道思考题吧。大数据与AI算法在计算机系统中扮演着越来越重要的角色,在你的工作中,哪些地方可以使用大数据与AI算法提高效率,优化体验?
|
||||
|
||||
欢迎你在评论区写下你的思考,我会和你一起交流,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
117
极客时间专栏/后端技术面试 38 讲/架构的核心原理/33 | 区块链技术架构:区块链到底能做什么?.md
Normal file
117
极客时间专栏/后端技术面试 38 讲/架构的核心原理/33 | 区块链技术架构:区块链到底能做什么?.md
Normal file
@@ -0,0 +1,117 @@
|
||||
<audio id="audio" title="33 | 区块链技术架构:区块链到底能做什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ea/78/ea90e1c78a819dceca6bc12c06686578.mp3"></audio>
|
||||
|
||||
在我的职业生涯中,我经历过各种各样的技术创新,见识过各种技术狂热的风潮,也看过各种技术挫折,但从来没有一种技术能像区块链技术这样跌宕起伏,具有戏剧性,吸引了各色人等。
|
||||
|
||||
区块链为什么能吸引这么多的关注?它到底能做什么?它的技术原理是什么?又为何如此曲折?
|
||||
|
||||
让我们从区块链的起源——比特币说起。
|
||||
|
||||
## 比特币与区块链原理
|
||||
|
||||
2008年11月,由中本聪设计、开发的比特币正式上线运行。我们现在都知道,比特币是一种加密数字货币。价格最高的时候,每个比特币可兑换近两万美金。一个看不见、摸不着的数字货币为什么能得到这么多的拥护,被炒到这么高的价格?
|
||||
|
||||
让我们继续追溯到传统的货币发行与交易系统。
|
||||
|
||||
传统的货币,也就是我们日常使用的钞票,是由各个国家的中央银行发行的。中央银行根据市场需求决定投放的货币数量,但是很多时候为了刺激经济发展,中央银行通常会额外多投放一些货币,这样就会出现钱越来越不值钱的情况,即通货膨胀。甚至有的时候,某些政府为了弥补自己的债务,恶意超发货币,有的国家甚至发行过面额为50万亿的钞票,导致了恶性通货膨胀。
|
||||
|
||||
于是就有人想,能不能发行一种数量有限、不会膨胀的数字货币,通过互联网在全球范围内使用呢?其实发行数字货币容易,但是得到大家的认可很难,而且货币在使用的时候,如何进行交易记账是个大问题。
|
||||
|
||||
传统上如果通过互联网进行交易转账,必须通过银行或者支付宝这样的第三方进行交易记账。但是通过互联网发行的数字货币必然得不到法定货币的地位,也就不会被银行等官方机构认可。如果没有受信任的官方机构记账,又如何完成交易呢?
|
||||
|
||||
所以比特币数字货币首先要解决的问题就是交易记账。比特币的主要思路是,构建一个**无中心、去信任的分布式记账系统**。这个记账系统和传统的银行记账不同,银行的账本由银行自己管理,银行是记账的中心,而比特币则允许任何人参与记账,没有中心,**完全分布式**的。
|
||||
|
||||
此外,传统的银行中心记账必须有个前提,就是交易者都相信银行,信任银行不会伪造、篡改交易。但是任何人都可以参与记账的比特币不可能得到大家的信任,所以这个记账系统必须从设计上实现**去信任**,也就是不需要信任记账者的身份,却可以信任这个人记的账。
|
||||
|
||||
这些不合常理,听起来就难度重重的要求,正是通过**区块链技术**实现的。
|
||||
|
||||
### 交易
|
||||
|
||||
首先,在比特币的交易系统中,所有**交易**的参与者都有一个**钱包地址**,事实上,这个钱包地址正是**非对称加密算法**中的**公钥**。非对称加密算法我在第30篇,安全性架构一篇讲过。进行交易的时候,交易的发起者需要将要交易的数字货币(一个hash值)和交易的接受方用自己的钱包**私钥**进行**签名**。
|
||||
|
||||
记账者可以使用发起者的公钥对签名进行验证,保证交易是真正发起者提交的,而不是其他人伪造的交易。
|
||||
|
||||
如下图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a3/f0/a3f036bbd264b95c2636fdceeb5a41f0.png" alt="">
|
||||
|
||||
### 区块链
|
||||
|
||||
交易签名只能保证交易不是他人伪造的,却不能阻止交易的发起者自己进行**多重交易**,即交易的发起者将一个比特币同时转账给两个人,也就是所谓的**双花**。
|
||||
|
||||
比特币的解决方案是,记账者在收到若干交易后,会将这些交易打包在一起,形成一个区块(block)。区块必须严格按照顺序产生,因此最新一个区块的记账者可以根据区块顺序得到此前所有的区块。这样,记账者就可以检查所有区块中的交易数据,是否有双花发生。
|
||||
|
||||
至于如何保证区块的严格顺序,比特币的做法是,在每个区块的头部记录他的前一个区块,也就是前驱**区块的hash值**,这样所有的区块就构成了一个链。我们知道,单向链表是有严格顺序的。
|
||||
|
||||
通过hash值链起来的区块就是所谓的**区块链**,如下图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/fe/4b88289a6198d1fbf7015dead4dbf1fe.png" alt="">
|
||||
|
||||
### 工作量证明
|
||||
|
||||
区块链的严格顺序不但可以避免双花,还可使**历史交易难以被篡改**。如果有记账者试图修改一笔过去区块中记录的交易,必然需要改变这个交易所在区块的hash值,这样就会导致下一个区块头部记录的前驱区块hash值和它不匹配,区块链就断掉了。
|
||||
|
||||
为了不让区块链断裂,篡改交易的记账者还必须要修改下一个区块的前驱hash值,而每个区块的hash值是根据所有交易信息和区块头部的其他信息(包括记录的前驱区块hash值)计算出来的。下一个区块记录的前驱hash值改变,必然导致下一个区块的hash需要重算。以此类推,也就是需要重算从篡改交易起的所有区块hash值。
|
||||
|
||||
重算所有区块的hash值虽然麻烦,但如果篡改交易能获得巨大的收益,就一定会有人去干。我前面说过,区块链是去信任的,即不需要信任记账者,却可以相信他记的账。因此,区块链必须在设计上保证记账者几乎无法重算出所有区块的hash值。
|
||||
|
||||
比特币的解决方案就是**工作量证明**,比特币要求计算出来的区块hash值必须具有一定的难度,比如hash值的前几位必须是0。具体做法是在区块头部引入一个随机数nonce值,记账者通过修改这个nonce值,不断碰撞计算区块hash值,直到算出的hash值满足难度要求。
|
||||
|
||||
因此,计算hash值不但需要大量的计算资源,GPU或者专用的芯片,还需要大量的电力支撑这样大规模的计算,在比特币最火爆的时候,计算hash值需要消耗的电量大约相当于一个中等规模的国家消耗的电量。
|
||||
|
||||
在这样的资源消耗要求下,重算所有区块的hash值几乎是不可能的,因此,比特币历史交易难以被篡改。这里用了“几乎”这个词,是因为如果有人控制了比特币超过半数的计算资源,确实可以进行交易篡改,即所谓的**51%攻击**。但是这种攻击将会导致比特币崩溃,而能控制这么多计算资源的记账者一定是比特币主要的受益者,他没有必要攻击自己。
|
||||
|
||||
### 矿工
|
||||
|
||||
前面讲到,比特币的交易通过区块链进行记账,而记账需要花费巨大的计算资源和电力,那为什么还有人愿意投入这么多资源去为比特币记账呢?
|
||||
|
||||
事实上,比特币系统为每个计算出区块hash的记账者赠送一定数量的比特币。这个赠送不是交易,而是凭空从系统中产生的,这其实就是比特币的发行机制。记账者为了得到这些比特币,愿意投入资源计算区块hash值。
|
||||
|
||||
由于计算出hash就可以得到比特币,计算hash值的过程也被形象地称作“挖矿”,相对应的,进行hash计算的记账者被称作矿工,而用来计算hash值的机器被称作矿机。
|
||||
|
||||
当“矿工们”为了争夺比特币,争相加入“挖矿”大军时,比特币区块链就变成一个**分布式账本**了。这里的分布式有两层含义:“矿工”记账时需要进行交易检查,所以需要记录从第一个区块开始的、完整的区块链,也就是说,完整的账本分布在所有的矿工的机器上;此外,每个区块是由不同矿工挖出来的,也就是说,每次交易的记账权也是分布的。
|
||||
|
||||
比特币虽然取得巨大的成功,但一直没有得到主流国家的官方支持。但是比特币使用的区块链技术却得到越来越多的认可,在企业甚至政府部门间的合作领域里,得到了越来越多的应用。
|
||||
|
||||
## 联盟链与区块链的企业级应用
|
||||
|
||||
比特币应用的区块链场景也叫做公链,因为这个区块链对所有人都是公开的。除此之外,还有一种区块链应用场景,被称作**联盟链**。
|
||||
|
||||
联盟链是由多个组织共同发起,只有组织成员才能访问的区块链,因此有时候也被称作**许可型区块链**。传统上,交易必须依赖一个中心进行,不同的组织之间进行交易,必须依赖银行这个中心进行转账。那么银行之间如何进行转账呢?没错,也需要依赖一个中心,国内的银行间进行转账,必须通过中国人民银行清算中心。
|
||||
|
||||
跨国的银行间进行转账则必须依赖一个国际的清算中心,这个中心既是跨国转账的瓶颈,又拿走了转账手续费的大头。所以当区块链技术出现以后,因为区块链的一个特点是去中心,各家银行就在想:银行之间能不能用区块链记账,而不需要这个清算中心呢?最初的联盟链技术就是由银行推动发展的。
|
||||
|
||||
目前比较知名的联盟链技术是IBM主导的Hyperledger Fabric。主要架构如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/11/85/1173035b0cacc834bfc8ceb8bb370185.jpg" alt="">
|
||||
|
||||
Peer节点负责对交易进行背书签名,Ordering节点负责打包区块,Peer节点会从Ordering节点同步数据,记录完整的区块链。而所有这些服务器节点的角色、权限都需要CA节点进行认证,只有经过授权的服务器才能加入区块链。
|
||||
|
||||
最近两年,随着区块链技术的火爆,联盟链技术也开始从银行扩展到互联网金融领域,甚至非金融领域。2018年支付宝香港和菲律宾一家互联网金融企业通过区块链进行了跨国转账,而香港和菲律宾的外汇管理局也作为联盟成员加入了区块链,使得**转账和监管在同一个系统中完成**。
|
||||
|
||||
在互联网OTA(在线旅行代理)领域,酒店房间在线销售是一块非常大的业务,但是一家酒店不可能对接所有的OTA网站,而一家OTA网站也不可能获得所有的酒店资源,于是就催生了第三方的酒店分销平台,这个平台负责对接所有的酒店,酒店房间通过该平台对外分销,而OTA网站通过该平台查找酒店房间以及预定房间。
|
||||
|
||||
于是这个平台就成为一个全行业不得不依赖的中心,一方面产生了巨大的瓶颈风险,另一方面酒店和OTA也不得不给这个中心支付高昂的手续费。
|
||||
|
||||
事实上,我们可以利用联盟链技术,将酒店和OTA企业通过区块链技术关联起来,酒店通过区块链发布房间信息,而OTA通过区块链查找房间信息以及预订房间。如下图所示,左边是传统的酒店分销模式,右边是基于区块链的酒店分销模式。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bc/06/bc5ad67185edd854b1a51eafbb7c3606.jpg" alt="">
|
||||
|
||||
上面讲到的Hyperledger 联盟链技术部署和应用都比较复杂。目前在区块链领域,社区资源比较丰富,更为易用,也更被广泛接受的区块链技术是以太坊。但是以太坊是一个公链技术,不符合联盟链受许可才能加入的要求,因此,我和一些小伙伴对以太坊进行了重构,使其符合联盟链的技术要求,你可以点击[源码地址](https://github.com/taireum/go-taireum)看一下,也可以看看[相关文档](https://mp.weixin.qq.com/s/AOqi9lz-wCZ8A-uEOyvYGg)。
|
||||
|
||||
如果你感兴趣的话,欢迎你参与开发与应用落地。
|
||||
|
||||
## 小结
|
||||
|
||||
应该说,区块链能吸引到这么多的关注,产生这么大影响,和加密数字货币的炒作是分不开的。正因为数字货币的炒作,才使得区块链技术吸引了大量的资源,更多的人才投入研发区块链相关技术,区块链技术进步与应用也吸引了大量的关注。
|
||||
|
||||
但是数字货币的投机属性又使得人们对区块链技术抱有急功近利的想法,希望区块链技术能快速带来回报。
|
||||
|
||||
在我看来,**互联网技术<strong><strong>的快速发展**</strong>是生产力革命</strong>,使得生产力数以十倍、百倍的增加。而**区块链技术是生产关系革命**,传统上,所有的交易和合作都必须依赖法律以及信任。而法律的成本非常高,很多场合无法支撑起用法律背书的成本;而跨组织,特别是互为竞争对手的组织之间又不可能产生信任。**区块链的出现,使得低成本,去信任的跨组织合作成为可能**,将重构组织间的关系,这个关系既包括企业间的关系,也包括政府和企业间的关系,还有政府部门间的关系。
|
||||
|
||||
互联网使得这个世界变得更加扁平,信息流动更加快速,但无法弥合这个世界割裂的各种关系,而区块链可以打通各种关系,将这个世界更加紧密联系在一起,使全人类成为真正的命运共同体。
|
||||
|
||||
## 思考题
|
||||
|
||||
今天我讲了一下区块链技术,它虽然火爆,但仍然处在发展之中。你能想到的利用区块链技术的场景有哪些呢?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
87
极客时间专栏/后端技术面试 38 讲/架构的核心原理/答疑丨互联网需要解决的技术问题是什么?.md
Normal file
87
极客时间专栏/后端技术面试 38 讲/架构的核心原理/答疑丨互联网需要解决的技术问题是什么?.md
Normal file
@@ -0,0 +1,87 @@
|
||||
<audio id="audio" title="答疑丨互联网需要解决的技术问题是什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5e/4e/5eda7fbe9a5e2f9d539260a70f08d14e.mp3"></audio>
|
||||
|
||||
目前互联网软件应用可以说是最主流的软件应用了,相应的,互联网分布式架构也成为最主要的系统架构方案。这个模块主要讲的就是互联网架构的一些知识内容,互联网架构技术关键点有很多,我在专栏中也试图在有限的篇幅内尽量多地覆盖这些技术关键点,但是依然有很多关键技术点未能展开讲述,文章中很多思考题其实也都是分布式系统的关键技术点,我在这里再进行一些回顾和补充。
|
||||
|
||||
## 专栏22篇分布式缓存架构的思考题
|
||||
|
||||
>
|
||||
我们讲 Memcached 路由算法讲到余数 Hash 算法,但是,这种算法在 Memcached 服务器集群扩容,也就是增加服务器的时候,会遇到较大的问题,问题是什么呢?应该如何解决?
|
||||
|
||||
|
||||
分布式缓存将多台服务器构建成一个集群,共同对外提供缓存服务,那么应用程序在读写缓存数据的时候,如何知道自己应该访问哪一台服务器呢?答案就是缓存路由算法,通过缓存路由算法计算得到缓存服务器的编号,进而和该服务器通信,读写缓存数据。
|
||||
|
||||
比较简单的路由算法就是余数Hash算法,利用 key 的 Hash 值对服务器列表长度取模,根据余数就可以确定服务器列表的下标。
|
||||
|
||||
比如说,缓存服务器集群中有3台服务器,根据Key的Hash值对3取模得到的余数一定在0、1、2三个数字之间,每一个数字都对应着一台服务器,根据这个数字查找对应的服务器IP地址就可以了。
|
||||
|
||||
使用余数取模这种方式进行路由计算非常简单,但这种算法有一个问题,就是当服务器进行扩容的时候会出现缓存无法命中的情况。比如说我们当前的服务器集群有3台服务器,当增加1台服务器的时候,对3取膜就会变成对4去取模,导致的后果就是以前对3取模的时候写入的缓存数据,对4取模的时候可能就查找不到了。
|
||||
|
||||
我们添加服务器的主要目的是提高缓存集群的处理能力,但是不正确的路由算法可能会导致整个集群都失效,大部分缓存数据都查找不到。
|
||||
|
||||
解决这个问题的主要手段是使用一致性Hash算法。一致性Hash首先是构建一个一致性Hash环的结构。一致性Hash环的大小是0到2^32-1,实际上就是我们计算机中无符号整型的取值范围,这个取值范围0和最后一个值2^32-1首尾相连,构成了一个一致性Hash环。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/44/0c/4455936c6951284c8b8d0ef63b3b2a0c.png" alt="">
|
||||
|
||||
然后我们将每个服务器的节点的Hash值放到环上,每一次进行服务器查找路由计算的时候,都是根据Key的Hash值顺时针查找距离它最近的服务器的节点。通过这种方式,Key不变的情况下找到的总是相同的服务器。这种一致性Hash算法除了可以实现像余数Hash一样的路由效果以外,对服务器的集群扩容效果同样也非常好。
|
||||
|
||||
扩容的时候,只需要将新节点的Hash值放到环上。比如图中的的NODE3放入环上以后,只影响到NODE1节点,原来需要到NODE1上查找的一部分数据改为到NODE3上查找,其余大部分数据还能正常访问。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6e/4c/6e4981953515fbb2300d0af61dd2a74c.png" alt="">
|
||||
|
||||
但是一致性Hash算法有一个致命的缺陷。Hash值是一个随机值,把一个随机值放到一个环上以后,可能是不均衡的,也就是说某两个服务器节点在环上的可能距离很近,而和其它的服务器距离很远,这个时候就会导致有些服务器的负载压力特别大,有些服务器的负载压力非常小。而且在进行扩容的时候,比如说加入一个NODE3,影响的只是NODE1,而实际上加入一个服务器节点的时候,是希望它能够分摊其他所有服务器的一部分负载压力。
|
||||
|
||||
实践中,我们需要使用虚拟节点对算法进行改进。也就是说当把一个服务器节点放入到一致性Hash环上的时候,并不是把真实的服务器的Hash值放到环上,而是将一个服务器节点虚拟成若干个虚拟节点,把这些虚拟节点的Hash值放到环上去。在实践中通常是把一个服务器节点虚拟成200个虚拟节点,然后把200个虚拟节点放到环上。Key依然是顺时针的查找距离它最近的虚拟节点,找到虚拟节点以后,根据映射关系找到真正的物理节点。
|
||||
|
||||
通过使用虚拟节点的方式,物理节点之间的负载压力相对比较均衡。加入新节点的时候,实际上是加入了200个虚拟节点,这些虚拟节点随机落在环上,会对当前环上的每个节点都有影响,原来的每个节点都会有一小部分数据访问落到新节点上。这样,既保证大部分缓存能够命中,保持缓存服务的有效性,又分摊了所有缓存服务器的负载压力,达到了集群处理能力动态伸缩的目的。
|
||||
|
||||
## 第25篇数据存储架构思考题
|
||||
|
||||
>
|
||||
分布式架构的一个最大特点是可以动态伸缩,可以随着需求变化,动态增加或者减少服务器。对于支持分片的分布式关系数据库而言,比如我们使用 MYCAT 进行数据分片,那么随着数据量逐渐增大,如何增加服务器以存储更多的数据呢?如果增加一台服务器,如何调整数据库分片,使部分数据迁移到新的服务器上?如何保证整个迁移过程快速、安全?
|
||||
|
||||
|
||||
上面我们讨论了缓存集群增加服务器的解决方案,对于分布式关系数据库而言,也需要增加服务器以增强集群负载处理能力。
|
||||
|
||||
和缓存的情况不同,缓存如果有部分数据不能通过缓存获得,还可以到数据库查找。上述的一致性Hash算法也确实会导致小部分缓存服务器中的数据无法被找到,但是大部分缓存数据能够找到,这样是不影响缓存服务正常使用的。
|
||||
|
||||
但如果分布式关系数据库中有数据无法找到,可能会导致系统严重故障。因此分布式关系数据库集群扩容,增加服务器的时候,要求扩容以后,所有数据必须正常访问,不能有数据丢失。所以数据库扩容通常要进行数据迁移,即将原来服务器的部分数据迁移到新服务器上。
|
||||
|
||||
那么哪些数据需要迁移呢?迁移过程中如何保证数据一致呢?
|
||||
|
||||
实践中,分布式关系数据库采用逻辑数据库进行分片,而不是用物理服务器进行分片。
|
||||
|
||||
比如MySQL可以在一个数据库实例上创建多个Schema,每个Schema对应自己的文件目录。数据分片的时候就可以以Schema为单位进行分片,每个数据库实例启动多个Schema。进行服务器扩容的时候,只需要将部分Schema迁移到新服务器上就可以了。路由算法完全不需要修改,因为分片不变,但是集群的服务器却增加了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5a/11/5ac8d7119acaeb1d4dc70959ae2e1d11.png" alt="">
|
||||
|
||||
而且因为MySQL有主从复制的能力,事实上,在迁移的时候,只需要将这些Schema的从库配置到新服务器上,数据就开始复制了,等数据同步完成,再将新服务器的Schema设置为主服务器,就完成的集群的扩容。
|
||||
|
||||
## 第21篇分布式架构的思考题
|
||||
|
||||
>
|
||||
互联网应用系统和传统 IT 系统面对的挑战,除了高并发,还有哪些不同?
|
||||
|
||||
|
||||
这个问题其实是分布式架构知识点的总结,互联网需要解决的技术问题是什么,解决方案是什么,带来的价值是什么,都在其中了。
|
||||
|
||||
互联网应用因为要处理大规模、高并发的用户访问,所以需要消耗巨大的计算资源,因此采用分布式技术,用很多台服务器构成一个分布式系统,共同提供计算服务,完成高并发的用户请求处理。
|
||||
|
||||
除了高并发的挑战,互联网应用还有着高可用的要求。传统的企业IT系统是给企业内部员工开发的。即使是服务外部用户的,但是只要企业员工下班了,系统就可以停机了。银行的柜员会下班,超市的收银员会下班,员工下班了,系统就可以停机维护,升级软件,更换硬件。
|
||||
|
||||
但是互联网应用要求7*24小时可用,永不停机,即使在软件系统升级的时候,系统也要对外提供服务。而且一般用户对互联网高可用的期望又特别高,如果支付宝几个小时不能使用,即使是深夜,也可能会引起很大的恐慌。
|
||||
|
||||
而一个由数十万台服务器组成,为数亿用户提供服务的互联网系统,造成停机的可能性又非常大,所以需要在架构设计的时候,专门,甚至重点考虑系统的高可用。关于高可用的架构,我主要在[第29篇](https://time.geekbang.org/column/article/191465)高可用架构一篇进行了讨论。
|
||||
|
||||
互联网应用,除了高并发的用户访问量大,需要存储的数据量也非常大。淘宝有近十亿用户、近百亿商品,如何存储这些海量的数据,也是传统IT企业不会面对的技术挑战。关于海量数据的存储技术,我主要在[25篇数据存储架构](https://time.geekbang.org/column/article/190728)进行了讨论。
|
||||
|
||||
有了海量的数据,如何在这些数据中快速进行查找,我在[26篇搜索引擎架构](https://time.geekbang.org/column/article/190702)进行了讲解。如何更好地利用这些数据,挖掘出数据中的价值,使系统具有智能化的特性,我在[31篇大数据架构](https://time.geekbang.org/column/article/193536)中进行了讨论。
|
||||
|
||||
传统企业IT系统部署在企业的局域网中,接入的电脑都是企业内部电脑,因此网络和安全环境比较简单。而互联网应用需要对全世界提供服务,任何人在任何地方都可以访问,当有人以恶意的方式访问系统的时候,就会带来安全性的问题。
|
||||
|
||||
安全性包含两个方面,一个是恶意用户以我们不期望的方式访问系统,比如恶意攻击系统,或者黄牛党、羊毛党通过不当方式获利。另一个是数据泄密,用户密码、银行卡号这些信息如果被泄漏,会对用户和企业都造成巨大的损失。[30篇安全性架构](https://time.geekbang.org/column/article/193296)讨论的就是这方面的内容。
|
||||
|
||||
传统的IT系统一旦部署上线,后面只会做一些小的bug修复或者特定的改动,不会持续对系统再进行大规模的开发了。而互联网系统部署上线仅仅意味着开始进行一个新业务的打样,随着业务的不断探索,以及竞争对手的持续压力,系统需要持续不断地进行迭代更新。
|
||||
|
||||
如何使新功能的开发更加快速,使功能间的耦合更加少,需要在软件设计的时候进行考虑,这其实是专栏第二个模块软件的设计原理主要讨论的内容。而在架构模块,主要是在[第23篇异步架构](https://time.geekbang.org/column/article/189041)以及[27篇微服务架构](https://time.geekbang.org/column/article/191438)进行了探讨。
|
||||
|
||||
互联网现在已经进入泛互联网时代,也就是说,不是只有互联网企业才能通过互联网为用户提供服务,各种传统的行业,所有为普通用户提供服务的企业都已经转向互联网了。可以说互联网重构了这个时代的商业模式,而以分布式技术为代表的互联网技术也必然重构软件开发与架构设计的技术模式。
|
Reference in New Issue
Block a user