This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View 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系统面对的挑战除了高并发还有哪些不同
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流。

View 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和反向代理缓存。
**CDNContent 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和反向代理缓存通常会作为系统架构的一部分很多时候对应用程序是透明的。而应用程序在代码中主要使用的是对象缓存**对象缓存是一种旁路缓存。**
不管是通读缓存还是旁路缓存,缓存通常都是以&lt;key, value&gt;的方式存储在缓存中比如CDN和反向代理缓存中每个URL是一个key那么URL对应的文件内容就是value。而对象缓存中key通常是一个ID比如用户ID商品ID等等而value则是一个对象就是ID对应的用户对象或者商品对象。
对于&lt;key, value&gt;的数据格式我们在前面在数据结构讨论过比较快速的存取方式是使用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的服务器。
应用程序调用APIAPI调用SDK的路由算法路由算法根据缓存的key值计算这个key应该访问哪台Memcached服务器计算得到服务器的IP地址和端口号后API再调用SDK的通信模块&lt;key, value&gt;值以及缓存操作命令发送给具体的某台Memcached服务器由这台服务器完成缓存操作。
那么路由算法又是如何计算得到Memcached的服务器IP端口呢比较简单的一种方法和Hash算法一样利用key的Hash值对服务器列表长度取模根据余数就可以确定服务器列表的下标进而得到服务器的IP和端口。
## 缓存注意事项
使用缓存可以减少不必要的计算,能够带来三个方面的好处:
1. 缓存的数据通常存储在内存中,距离使用数据的应用也更近一点,因此相比从硬盘上获取,或者从远程网络上获取,它获取数据的速度更快一点,响应时间更快,性能表现更好。
1. 缓存的数据通常是计算结果数据比如对象缓存中通常存放经过计算加工的结果对象如果缓存不命中那么就需要从数据库中获取原始数据然后进行计算加工才能得到结果对象因此使用缓存可以减少CPU的计算消耗节省计算资源同样也加快了处理的速度。
1. 通过对象缓存获取数据可以降低数据库的负载压力通过CDN、反向代理等通读缓存获取数据可以降低服务器的负载压力。这些被释放出来的计算资源可以提供给其他更有需要的计算场景比如写数据的场景间接提高整个系统的处理能力。
但是缓存也不是万能的,如果不恰当地使用缓存,也可能会带来问题。
首先就是**数据脏读**的问题,缓存的数据来自数据源,如果数据源中的数据被修改了,那么缓存中的数据就变成脏数据了。
主要解决办法有两个,一个是**过期失效**,每次写入缓存中的数据都标记其失效时间,在读取缓存的时候,检查数据是否已经过期失效,如果失效,就重新从数据源获取数据。缓存失效依然可能会在未失效时间内读到脏数据,但是一般的应用都可以容忍较短时间的数据不一致,比如淘宝卖家更新了商品信息,那么几分钟数据没有更新到缓存,买家看到的还是旧数据,这种情况通常是可以接受的,这时候,就可以设置缓存失效时间为几分钟。
另一个办法就是**失效通知**,应用程序更新数据源的数据,同时发送通知,将该数据从缓存中清除。失效通知看起来数据更新更加及时,但是实践中,更多使用的还是过期失效。
此外,并不是所有数据使用缓存都有意义。在互联网应用中,大多数数据访问都是有热点的,比如热门微博会被更多阅读,热门商品会被更多浏览。那么将这些热门的数据保存在缓存中是有意义的,因为缓存通常使用内存,存储空间比较有限,只能存储有限的数据,热门数据存储在缓存中,可以被更多次地读取,缓存效率也比较高。
相反,**如果缓存的数据没有热点,写入缓存的数据很难被重复读取,那么使用缓存就不是很有必要了**。
## 小结
缓存是优化软件性能的杀手锏任何需要查询数据、请求数据的场合都可以考虑使用缓存。缓存几乎是无处不在的程序代码中可以使用缓存网络架构中可以使用缓存CPU、操作系统、虚拟机也大量使用缓存事实上缓存最早就是在CPU中使用的。对于一个典型的互联网应用而言使用缓存可以解决绝大部分的性能问题如果需要优化软件性能那么可以优先考虑哪里可以使用缓存改善性能。
除了本篇提到的系统架构缓存外客户端也可以使用缓存在App或者浏览器中缓存数据甚至都不需要消耗网络带宽资源也不会消耗CDN、反向代理的内存资源更不会消耗服务器的计算资源。
## 思考题
我们从Memcached路由算法讲到余数Hash算法但是这种算法在Memcached服务器集群扩容也就是增加服务器的时候会遇到较大的问题问题是什么呢应该如何解决
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流进步一下。

View 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显示最终的订单结果信息。
## 思考题
异步架构中最主要的技术就是消息队列,目前主要的消息队列产品有哪些?各有什么优缺点?
欢迎你在评论区说说你对消息队列产品的了解,我会和你一起交流,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。

View File

@@ -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
欢迎你在评论区写下你的答案,我会和你一起交流,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。

View 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为偶数的用户记录存储到服务器1ID为奇数的用户记录存储到服务器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 HBaseApache 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进行数据分片那么随着数据量逐渐增大如何增加服务器以存储更多的数据呢如果增加一台服务器如何调整数据库分片使部分数据迁移到新的服务器上如何保证整个迁移过程快速、安全
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。

View 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值呢
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。

View 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)。
## 思考题
最后留一个问题吧。现在中台的概念比较火,中台和微服务的关系也比较密切,你理解的中台是什么样的,有什么样的价值,满足什么样的需求,和微服务的关系是什么呢?
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流进步一下。

View 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)》
此外,还可以使用线程池、连接池等对象池化技术,复用资源,减少资源的创建。当然最重要的还是利用各种设计模式和设计原则,开发清晰、易维护的代码。因为一团糟的代码里面有什么性能问题谁也搞不清楚,也没办法优化。
## 小结
性能优化的一般步骤是:首先进行性能测试,根据测试结果进行性能分析,寻找性能的瓶颈点,然后针对瓶颈进行优化,优化完成后继续进行性能测试,观察性能是否有所改善,是否达到预期的性能目标,如果没有达到目标,继续分析新的瓶颈点,不断迭代优化。
性能优化的一个前提是需要进行性能测试,了解系统的性能指标,才能有目标地进行性能优化。另一个前提是,必须要了解系统的内部结构,能够分析得到引起性能问题的原因所在,并能够解决问题。
因此性能优化是对一个架构师技能和经验的全面挑战,是架构师的必备技能之一。
## 思考题
除了文中提到的这些性能优化手段,还有哪些优化手段?
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流。

View File

@@ -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是测试环境不能发现而需要到预发布服务器上才能发现的呢
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流。

View 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;”转义为“&amp;gt”HTML显示的时候还是正常的“&gt;”,但是这样的脚本无法在浏览器上执行,也就无法达到攻击的目的。
由于HTTP攻击必须以HTTP请求的方式提交到服务器因此可以在服务器的入口统一进行拦截对含有危险信息的请求比如drop tableJS脚本等进行消毒转义或者直接拒绝请求。即设置一个Web应用防火墙将危险请求隔离。
针对Web应用防火墙我们可以自己开发一个统一的请求过滤器进行拦截也可以使用ModSecurity[http://www.modsecurity.org/](http://www.modsecurity.org/)这样的开源WAFWeb Application Firewall
## 小结
硬件指令和操作系统可能会有漏洞我们使用的各种框架和SDK可能也有漏洞这些漏洞从被发现到被公开再到官方修复漏洞可能会经过一个或长或短的时间这个时间内就可能被掌握这些漏洞的黑客利用攻击系统。
这种漏洞在官方修复之前,我们基本没有办法应对。但是黑客攻击也不是无意义的攻击,而是为了各种利益而来,很多时候是针对数据而来,做好数据加密存储与传输,即使是数据泄露了,黑客无法对数据解密,利用数据获利,也可以保护我们的数据资产。
同时加强请求的合法性检查避免主要的HTTP攻击及时更新生产环境的各种软件版本修复安全漏洞提高黑客攻击的难度使其投入产出不成比例从而营造一个相对安全的生产环境。
## 思考题
除了文中提到的HTTP攻击方式还有哪些比较常见的HTTP攻击对应的防护手段有哪些
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流。

View 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优先读取本地数据进行计算计算后输出一个&lt;key, value&gt;集合。另一个是reduce过程MapReduce在每个服务器上都启动多个reduce进程然后对所有map输出的&lt;key, value&gt;集合进行shuffle操作。所谓shuffle就是将相同的key发送到同一个reduce进程在reduce中完成数据关联计算。
我们以经典的WordCount也就是统计所有数据中相同单词的词频数据为例看看map和reduce的处理过程。
<img src="https://static001.geekbang.org/resource/image/1b/5b/1b7b317b5863743112b3f45f7ae9025b.png" alt="">
假设原始数据有两个数据块MapReduce框架启动两个map进程进行处理分别读入数据。map函数对输入数据进行分词处理然后针对每个单词输出&lt;单词, 1&gt;这样的&lt;key, value&gt;结果。然后MapReduce框架进行shuffle操作相同的key发送给同一个reduce进程reduce的输入就是&lt;key, value列表&gt;这样的结构即相同key的value合并成一个value列表。
在这个例子中这个value列表就是很多个1组成的列表。reduce对这些1进行求和操作就得到每个单词的词频结果了。具体的MapReduce程序如下
```
public class WordCount {
public static class TokenizerMapper
extends Mapper&lt;Object, Text, Text, IntWritable&gt;{
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&lt;Text,IntWritable,Text,IntWritable&gt; {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable&lt;IntWritable&gt; 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是1reduce函数对相同的行记录也就是相同的key的value集合进行求和计算就得到最终的SQL输出结果了。
那么Hive要做的就是将SQL翻译成MapReduce程序代码实际上Hive内置了很多Operator每个Operator完成一个特定的计算过程Hive将这些Operator构造成一个有向无环图DAG然后根据这些Operator之间是否存在shuffle将其封装到map或者reduce函数就可以提交给MapReduce执行了。Operator组成的DAG图示例如下这是一个包含where查询条件的SQLwhere查询条件对应一个FilterOperator。
<img src="https://static001.geekbang.org/resource/image/cb/a6/cb1236ad035ca01cffbb9df47fa88fa6.jpg" alt="">
Hive整体架构如下Hive的表数据存储在HDFS。表的结构比如表名、字段名、字段之间的分隔符等存储在Metastore。用户通过Client提交SQL到DriverDriver请求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(&quot;hdfs://...&quot;)
val counts = textFile.flatMap(line =&gt; line.split(&quot; &quot;))
.map(word =&gt; (word, 1))
.reduceByKey(_ + _)
counts.saveAsTextFile(&quot;hdfs://...&quot;)
```
首先从HDFS读取数据构建出一个RDD textFile。然后在这个RDD上执行三个操作将输入数据的每一行文本用空格拆分成单词将每个单词进行转换word→(word, 1),生成&lt;Key, Value&gt;的结构相同的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上加载的RDDA经过groupBy分组统计转换函数后得到RDD BC经过map转换函数后得到RDD DD和E经过union合并转换函数后得到RDD FB和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="">

View 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算法提高效率优化体验
欢迎你在评论区写下你的思考,我会和你一起交流,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。

View 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>,使得生产力数以十倍、百倍的增加。而**区块链技术是生产关系革命**,传统上,所有的交易和合作都必须依赖法律以及信任。而法律的成本非常高,很多场合无法支撑起用法律背书的成本;而跨组织,特别是互为竞争对手的组织之间又不可能产生信任。**区块链的出现,使得低成本,去信任的跨组织合作成为可能**,将重构组织间的关系,这个关系既包括企业间的关系,也包括政府和企业间的关系,还有政府部门间的关系。
互联网使得这个世界变得更加扁平,信息流动更加快速,但无法弥合这个世界割裂的各种关系,而区块链可以打通各种关系,将这个世界更加紧密联系在一起,使全人类成为真正的命运共同体。
## 思考题
今天我讲了一下区块链技术,它虽然火爆,但仍然处在发展之中。你能想到的利用区块链技术的场景有哪些呢?
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。

View 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)进行了探讨。
互联网现在已经进入泛互联网时代,也就是说,不是只有互联网企业才能通过互联网为用户提供服务,各种传统的行业,所有为普通用户提供服务的企业都已经转向互联网了。可以说互联网重构了这个时代的商业模式,而以分布式技术为代表的互联网技术也必然重构软件开发与架构设计的技术模式。