提示:本文较长,看完须要时间,若是你想看彩色重点标记版,请移步微信地址:这里nginx
分布式,高可用,和机器学习同样,最近几年被说起得最多的名词,听名字多牛逼,来,咱们一步一步来击破前两个名词,今天咱们首先来讲说分布式。redis
我我的感觉啊,分布式和高可用是随着最近这些年阿里的双11活动火起来的,放眼全球,好像没有哪一个公司的系统会在瞬间承接这么大的流量,而且仍是绝对不能出错的交易流量,因此阿里确实积累了全球最丰富的高可用和分布式的经验,再加上各类技术大会一分享,这两个词就变成一个互联网公司技术系统的标配了。算法
可能不少人并非很理解分布式和高可用,咱们简单来讲一下。sql
分布式的理论在上个世纪就已经比较成熟了,只是一直没有实际可用的环境来验证,直到google的出现,分布式就是单机已经没法处理了,把服务拆分红多个服务,组成一个分布式的服务来完成以前单个服务的功能。数据库
高可用实际上是一直在IT行业中存在的,最典型的就是异地灾备系统了,这就是一个标准的高可用系统。编程
好,前面的概念说完了,进入主题,如何来设计一个分布式系统呢?本篇没有高深的理论,仅会使用几个计算机编程的最基本概念,来一步一步阐述如何设计一个分布式系统,咱们仍是会以搜索引擎为例,毕竟搜索服务是一个标准的分布式场景。服务器
请耐心读完本篇,不要被前面的东西惊到,请耐心微信
不是说分布式么,怎么说到代码底层了,别急,慢慢来。网络
代码是由数据结构和算法组成的,是用来处理数据的,而数据结构和算法组合起来是什么?是函数,因此,代码就是函数,并且有函数式编程,而且从数学上也证实了纯函数式编程也能完成全部的算法。session
代码作的事情就是给一个数据做为函数的输入,而后经过函数,给出一个数据做为输出,在这个过程当中可能须要存储一些数据到外部存储器中供之后使用。
由多个代码片断(函数)组合起来,在加上一个外部的存储数据,就造成了一个服务,若是一个代码片断(函数)能够抽象成y=f(x),那么其实服务咱们抽象成这样子y= f(g(h(u(x))),也就是各个代码片断(函数)的组合。
全部的服务,最后均可以抽象成下面的样子
右边那个不是表明数据库,是表明外部的存储数据,你能够仔细想想,是否是你写的全部的服务器端程序,或者全部的程序,均可以抽象成上面这个图的样子?若是不行,很差意思,那是你想象力不够。
好,咱们以搜索引擎为例来讲一下,上面的y= f(g(h(u(x)))对应出来之后
若是是数据检索过程的话,x就是输入的query,y就是输出的结果集,u函数表示query分析,简单说也就是分词啦,h就是倒排索引检索,g就是结果集求交集并集,f就是排序。
若是是数据更新过程的话,x就是新增的那个文档,y就是更新是否成功,u函数就是分词,h就是更新内存倒排,g就是写磁盘。
因此说,一个服务,一定能够拆分红一个一个子服务,子服务还能够继续拆,拆到最后一定变成一个函数,而若是把一个或者一组函数拆出去单独变成一个服务的话,那这个服务就变成一个分布式的服务了,用时下比较流行的说法就是微服务。
为何要分布式,也就是什么状况下须要分布式。
首先咱们要搞清楚两个概念,分布式和集群。
好比上面说的搜索服务,咱们是一个单机版的搜索引擎,性能不错,每次返回数据的时间大约在10ms左右,可是因为请求量很是巨大,QPS单机已经扛不住了,因而咱们把这个单机版的变成一个像下图这样的结构,前面用一个nginx作负载均衡,后面挂N多搜索服务,这不叫分布式,这是集群。
只有当咱们发现因为排序算法变复杂了,或者数据量增多了,每次返回数据的时间由10ms变成500ms了,这个时候只能把服务给拆成多个服务才能知足业务需求了,这才叫分布式。
因此说,通常状况下,集群就已经够用了,须要进行分布式的服务其实并很少,并且集群基本上都是高可用的,风险也小,因此除非无可奈何,没有必要为了分布式而分布式,这叫过分设计,单机其实挺好的,简单易维护,抗造高可用。
好,假如咱们的搜索引擎数据量和请求时间都已经达到极限了,必需要进行分布式了,如何来作呢?通常一个服务要进行分布式的设计,会从两个方面进行。
一是服务的功能分布式,对应到代码也就是把一个1000行的函数变成两个500行的函数。
二是数据的分布式,就是数据已经太多了,单节点已经放不下了,或者单节点处理这么多数据能力已经不够了,那也得进行分布式,数据分布式其实你们见得多了,分库分表就是一种数据分布式。
正好,搜索引擎的分布式这两个方面均可以涉及到,检索的时候须要对功能进行分布式,而索引自己须要对数据进行分布式,咱们一个一个来讲。
一个标准的检索过程大体分红如下几个部分:query分析,数据检索,结果集合并,排序。以下图表示的这样
若是咱们直接把这个服务分红上面四个单独的服务,完成服务的分布式,行不行呢?能够,但并很差,由于设计一个分布式的服务仍是有一些东西须要考虑的,对于分布式的服务,通常须要注意如下三个关键点。
一个分布式服务,应该尽可能的减小网络通信上的开销,分布式的初衷是将计算能力分布到多个节点上而提升总体的响应时间,若是在网络通信上花费了不少的开销,反而会使响应时间增加而违背了设计的初衷。
上面几个模块中,数据检索,结果集合并,排序这几个模块的数据流都是搜索的结果集,通常都很是巨大,这么大量的数据在网络间传来传去,速度能快就出鬼了,因此拆分的话,也是将query分析拆出去变成一个单独的服务,拆出去之后,它知足网络开销少这个条件,他的输入就是用户的query(好比姚明有多高),也就是一个url吧,输出是一个改写好的query(姚明/身高),也就是个url吧,网络传递上开销很小。
若是是分布式服务中的一个子服务,它应该尽可能设计成无状态的,放到编程语言上来讲就是说这个服务应该是一个纯函数的服务,一个输入只会有一个输出,不会由于不一样的状态产生不一样的输出,这样主要是为了方便调用,调用服务的时候不须要去考虑状态。若是必定须要状态的话(好比用户的会话状态session),对于分布式服务,通常会将状态单独存储在某个位置(好比redis中),须要的时候去取就好了,服务自己并不保存这个状态。
咱们的query分析服务完美知足这个条件,他是无状态的,query分析是个纯算法的东西什么输入就有一个什么输出。
虽然上面咱们说了集群和分布式的区别,可是分布式的系统通常都得能知足集群的部署,若是每一个子服务能知足无状态的话,那么横向扩展就没什么问题了,由于无状态,因此对外来讲每台机器都是同样的,调用谁结果都同样,天然就能横向扩展了。
咱们再看咱们的query分析服务和主搜索服务,两个都是无状态的,都能横向扩展,那么这个搜索引擎经过一系列的功能拆解之后,将会变成这个样子
若是你设计一个分布式系统按照上面三条准则进行模块的拆分的话,那么至少方向上不会有太大的问题,系统耦合性也会比较低,若是每一个子系统都能作到以上三点的话,基本上会是一个比较好的分布式系统。
这里再说一下,对于搜索服务来讲,我这里只是为了阐述功能的分布式设计,把query分析给拆出去了,若是咱们的query分析就是个分词的话,也彻底没有必要拆分了,但若是是一个复杂的天然语言处理算法的话,仍是能够拆出去的。另外,把排序单独拆开做为一个服务的,确实很少见,或者说我没见过吧,因此搜索引擎的功能拆解,基本上就是这样了,再拆就影响性能了。
如今不少人开口就是各类技术栈,各类开源组件,各类搭配组成一个看似牛逼的分布式系统,请问,对于RestfulAPI+ZooKeeper+HDFS+KafkaMQ+Nginx+Dubbo+Mysql+Docker+...这种架构的分布式系统,无论他是干什么的系统,你见了除了懵逼难道还有第二种表情么?你的系统就那么牛逼,须要把这些技术全都用上???
因此,一个好的分布式系统,一样适用于一个系统架构吧,关键点不在你用了多少新技术,而在于对模块的拆分对不对。
服务已经进行了拆分,已经算是一个分布式系统了,可是随着搜索引擎的数据愈来愈多,单机的性能也会达到一个极限,好比五亿条数据,单机基本上比较难抗住了(若是你硬要上一台1T内存,100T硬盘,64CPU的机器,那是你任性),因此这时候的分布式就是须要进行数据层面的分布式设计了。
对服务的分布式拆分仍是比较容易的,把服务和数据一锅端到另外的节点就好了,但你要对数据进行分布式拆分,就没那么容易了,这就须要具体场景具体分析了。
先简单介绍一下搜索引擎的数据拆分,对于搜索引擎来讲,外部数据比较简单,就是一条一条的文档,数据拆分相对仍是比较简单的,通常咱们能够选一个字段作hash,将数据hash到各个节点中,也就是常常听到的索引分片了,只要字段选得好,hash函数也选的好,基本上每一个节点的数据是比较平均的,那么这么一拆解下来,这个搜索引擎就变成下图这个样子了,红框中每个节点都有一部分的索引内容,把每部分的内容合并起来就是整个结果集了。
上面的拆分有个致命的弱点,知足不了咱们上面说的每一个子服务都应该是可横向扩展的,由于在咱们加分片机器的时候,咱们没法保证相同分片机器的数据一致性,就像下图,如何保证A1和A2,A3数据的一致性。
这就是数据的拆分和服务的拆分的本质区别,要考虑的地方也不同,服务说白了就是一堆函数,不会变化,拆到哪里都是同样的,横向扩展彻底没问题,而数据是会变的,如何拆分数据不是数据分布式的重点,数据拆分须要解决的最重要的问题就是:如何保证各个相同节点的数据强一致性。这也是全部的分布式系统须要的最重要的问题,才会为了这个出现那么多一致性的理论。
看上去很简单吧,索引分片技术直观有效,数据库的分库分表这种拆分也很简单有效,但并非每一个服务的数据拆分均可以这么简单的,因此如何分片不是我要说的重点,若是不是那么能直观分片的数据如何进行分布式的拆分?
咱们先不说那么多一致性理论,好比Paxos这种高大上的算法。咱们从最最基础的提及。
若是你看到这了尚未关闭这篇文章,那么我告诉你,前面全部的都是开胃菜,咱们终于见到了这篇文章的主角了,那就是Log。
咱们再回到这张图
右边的外部数据有两种状况
彻底是静态数据,从始至终不会变化,那么这种状况随便怎么拆都好说
彻底是动态数据,会常常变化,那么这种状况就要请主角出场了
咱们说的Log不是咱们服务的日志,日志是用来记录服务的一些信息的,主要用来进行错误排查的,是给人看的,而咱们这里说的Log是给机器看的。
Log是什么,一条Log是一个带时间戳的消息,这里的消息能够任何东西,好比数据变化啊,某个事件啊,甚至是某个函数操做,任何东西均可以是Log的消息。你们熟悉的数据库的binlog,就是一种Log形式,记录的是数据的变化。
下图就是一个标准的Log,首先他带有时间戳(或者表示时间序列的ID),而后是Log的消息自己,全部Log是按时间一条一条排序好的,结构很是简单,虽然Log简单,可是Log记录了整个系统最关键的东西,那就是这个系统在某个时刻干了什么。
Log最重要的功能就是重放。A节点进行了一系列的操做,产生了5条Log,输出了一个结果R,那么将这5条Log输入到B节点,B节点最后也将输出结果R,这就是重放。
由于咱们的显示世界是个时域系统,任何状态都是由一个事件和一个时间产生出来的,而Log这种简单的数据结构刚好完美的记录了时间和事件,那么任何状态均可以用Log还原出来。
Log虽然简单而又异常强大,但不少时候咱们只须要使用不多的功能就能完成很重要的事情,好比咱们的搜索引擎,Log只须要记录一个序号,一个操做类型,一条消息就好了,像下面的结构
1 . 更新数据 . id=MMM,name=XXX,content=XXX
2 . 更新数据 . id=NNN,name=YYY,content=ZZZ
3 . 新建字段 . fieldname=title,fieldtype=string
4 . 删除数据 . id=MMM
这样,一样功能的节点经过Log就能够保证数据的一致性,不止搜索引擎,MySql数据库,Redis等的主从同步也是这么作的,一条简单的Log就达到了数据一致性的要求。
Log既然这么牛逼,为何说来讲去还只是个数据复制呢?有别的高大上点的应用么?
首先,Log记录了系统在整个时间周期中每次的状态变动,他是可重放的。
由于Log有上述特性,数据复制过程当中即使从节点挂了,重启之后根据上次的Log的编号也能重放并同步数据,
其次,咱们能够把任何结构化的数据(好比数据库表,搜索索引,KV数据库)异构成Log这种标准的数据存储格式,而且最重要的是能够经过Log这种结构再次重建任何结构化数据。
由于Log有上述特性,因此Log能够用来做为系统解耦的中间结构,如今那么多消息队列,哪一个系统没有用消息队列,你觉得是啥?就是Log啊,Kafka就是个高级的Log服务。
好了,扯了一段Log,咱们回到索引分片来,有了Log之后,咱们想怎么加机器就能怎么加机器了,反正经过Log能够保证各个相同节点的数据一致性,上面那个带问号的图经过Log就解决了。
最后说一说节点间的通信,这不是本篇重点,捎带说一下,通常不少分布式系统使用RPC这种远程调用再加上一种序列化算法来节省带宽来进行节点间通信,简单的话,用http也行,这里就不展开了。
最后,咱们画个分布式的最终图,搜索服务拆分红分布式了之后就是这样子了。
注意啊,这是个低可用的分布式搜索引擎,只有咱们第二篇出来了之后才能变成一个高可用的分布式搜索引擎:)
好了,说了这么多了,已经没体力了,不知道你看完没有,这只是第一部分,分布式的部分,后面还有一篇高可用的,还没写呢,欢迎继续阅读,若是你以为还不错,欢迎转发:)
最后,欢迎关注个人公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索中文西加加语言就行