本文内容整理自W-Time技术分享沙龙-天津站现场演讲《一切都是分布的》,演讲者:李傲,问啊联合创始人,前中交车联网总架构。
好多人都会问什么是架构师?其实架构师的定义很宽泛,前端后端的定义都不一样。作为后端出身的架构师,我认为后端并不是大家想的封装组件,它要定义的是规划,规划模块之前的关系。在一台机器搞不定时怎么办?答:集群!这词说着很容易,但真要给你,你发现how?怎么去加?
有人问我,架构师要做什么?我认为,架构师就是要在软件起初阶段就能够从情景当中预先想到这问题,通过架构分布式解决方案,预先把问题都埋好。可能有人会说这算不算重度设计?其实所谓重度设计,要看团队的基本能力,如果团队写代码还写不利索的情况下,那么这个设计就很重要。因为不能指望一个代码都没有写过几行的人去写架构,这个是不可能的,做分布式都很难。如果团队能力属于中上层,那么有两种可能:1、团队做过十几年的代码,但架构差一些,那么也会把程序写的非常漂亮,因为对代码有把控能力,对底层的研究比较透彻;2、代码写的并不是很好,但是架构师对新技术有了解,那么也是有可能做好分布式的。如果你的团队做不到以上这些,那只有一种方法——用服务器抗。但服务器是一回事,团队能力也不能太差。
下面就我们“问啊”来说给大家做一下简单的分享,“问啊”是一款订制化IT教育平台,可以一键呼叫大牛解决IT 问题。
关于分布式存储
“问啊”利用了一些分布式的概念,也就是分布式存储。我们都知道,每一台服务器资源占用都是有限的,我们用的是云服务器,一般上来都会去买本地磁盘,但无论容量多大,总有一天会用完,当这一天到来的时候,有两种可能:1、在你不知道的时候,服务死了,怎么也起不来。可能会不运行、cpu过高,有人会问,这什么原因导致的?计算量太大了?其实不是,因为一直在做无谓的io操作;2、无从下手,有个公司叫emc的做的磁盘可以解决,可是太贵,300万左右的价格初创公司老板肯定不批,而且性能也不能保证。
但是分布式存储就把这个事解决了,将要存储的文件预先就散出去,而不是放在一个地方。拿的时候就很快的知道在哪了。举个例子,就像大家都有房子,每个房子都有地址,这个地址都是预先规划好的,就跟分布式存储一样,在规划的时候就解决了未来如果数据膨胀会怎么样的问题。
关于高速缓存
接下来再说高速缓存,这也是一个分布式缓存的概念,对存储和缓存来讲,在算法的选择上是不一样的,数据分为两种,维度数据和事实数据。维度数据可以理解为是一种属性,人也是一种维度数据,这个数据是有尽头的。事实数据就是我出门的时候可能会踩死一只蚂蚁,但我不知道我这一路会踩死多少只,这就是事实数据。包括广大的女性同胞养活了淘宝,这也是最好的例子,都是事实数据。
事实数据存储和维度数据是不一样的,大家一般按时间存储是最多的,这样数据在取得时候有一些优势——可以单独拿一段出来。在数据结构中,拿一段怎么拿?肯定得是连续存储,这样对cpu的消耗则是最小的。那维度数据呢,数据量本身就不大,我们用hash是比较靠谱的。
对于内存和磁盘的存储来讲,磁盘是一个盘片,我们在寻找一个道的时候,我们会连续去打,这样是最快的。因为不会跳针,跳针速度是最慢的。但内存不是,内存是随机存储,我们在利用存储的时候也会考虑到,如果对于连续存储来讲,内存就一定比磁盘快吗?在我看来,不一定。
关于任务队列
我之前做车联网,包括现在的滴滴打车都是一样的,大家之所以能打到车,是车的位置信息已经上传上来了。因为在同一时间内会爆发很多小高峰,导致我存数据的时候很慢,所以我不直接存储,我先存到另一层去。为什么要存到另一层呢?我们存储的目的就是为了要拿出来,简单的说:存的目的就是拿。其实存和拿是一个悖论,好存不好拿,好拿不好存。举一个例子,这么说可能可以直观一些。上大学时,放学回宿舍,书包就扔在地上。为什么?因为懒,扔的速度特别快,但是你可能转天再找就找不到了。问题就出在存储快但是没有索引。想要快速查到怎么办?那就在存的时候分门别类,细致存好。这样拿比较快,存的时候就会费一些时间。如何解决这个悖论?读写分离。存和查的时候一定要做分离。软件有一个理论叫“解偶”,如何解偶?简单来说,a层和b层之间加一个c层就是解偶。只要你发现两层之间有一个很紧密的联系,你就往里面加一个。
所以,任务队列就是这样,我们先把东西写到队列里,让它慢慢的去消费。也就是说你把数据拿到队列里面来的时候,我就认为你存了。等你取得时候,看你的消费水平,消费水平慢,最多就一个感觉:这个更新好慢,仅此而已。然后下一步就该研究怎么把这个消费做快,你是有这个机会的。但如果你这个事儿不这么做,你就会发现,在你存的时候就已经死了。这样用户的感受可能会有两种,一个是用户并不知道,他只会觉得你们公司的数据比较少;二是我知道,你们已经死了。这样的用户体验度是非常不好的。
关于高速查询
这里我要讲一下我们查询的数据结构。对于每种查询来说,数据结构都不太一样。现在有一种比较好的既能查又能存的数据结构叫LSM-TREE,就是hbase一个数据结构,大家有兴趣可以看一下。
如果你是一个应用,你可能会发现其实没有现成的东西可以用,怎么办?那就用最短的时间研发出来一个轻量级的分布式,像任务下发。这种东西其实比较占资源,当你的服务器数量不够庞大,节点数量也不够庞大的时候,你拿到的结果可能跟你最初的预想不太一样。
互联网公司都有个比较难受的事儿,就是做运维不敢动机器。我们在做“问啊”的时候也没有完全做到不停机,这个挺难的。有的时候为了尝试试错,我们需要做降级指标,其实有时候比升级指标难的多,会丢数据、丢节点,之后数据还能够均摊,这个是挺难的。我们现在降级指标还没有做到完全不停机,但升级指标已经能够做到了。其实这就是各种负载均衡,通过负载均衡来代替ha,那么这两个概念是什么概念呢?ha,高可用。负载均衡,就是就是你一个人搞不定,四五个人一起上!ha是什么?一个活着,另一个就得死。一般用负载均衡来解决高可用有个问题,就是机器不闲着。一般来讲,做机器空闲的话,总有一台机器是不干活的,这个对于初创公司来说成本比较高,配置选择就是个难题。所以各种ha方案一般就是共用,那个节点可能会分摊好多个节点的共用,但是突然间发现两个小高峰,两个死了这个也就算死了。对于loadbalance本身有个非常大的优势,就是本身来讲,只要有1/2以上活了,这服务也就认为是活的,这个是比较好保证的。但是有一点,做lb算法就一定要做到线上的配额一定要高于你目前的配额,否则你是没法做的。
下面分享一下我们用过得一些东西:
Redis
一个基于内存的存储。最大优势就是单线程处理的。肯定有人问为什么做单线程,很慢的!但是如果你测过的话就会发现,单线程多任务有时候不一定就比多线程多任务慢,多线程多任务有个空闲的概念,交替的概念,我要去调度。这个就完全省去调度,只要存储速度特别快,能让一个线程别空闲了,远比多线程多任务要快。单线程有一个不可替代的优势:无锁,可以做到一致性。多线程本身来讲只要并发对一个数据操作的时候,你就必须得加锁。你的锁如果设计不合理的话,你这个数据本来可以加到5的,结果才加到2就没了。
如果用redis完全不用考虑这些事。包括我们做订单号的话,单号取一个数,这是个最普通业务。我们对单号+1就会考虑一个线程去做,如果可以搞定的话,那速度就快了,那我为什么要去做多线程?那么做多线程有一个方法,你可以先把订单号切割,就是预计今年的订单号能有多少,你先把他分好了。然后每一个进程怎么样操作,这样不会出问题。但是订单是跳跃,永远不要用订单号去做排序,这是最不合理的。就像大家都玩过摇红包,其实都是一样的,摇红包为什么能做到这么快。其实不可能快,这个事情不是一个快的动作,有交互就不可能快。只能先把交互的红包预先切好了,存在缓存里。甚至最狠的是用一些空池,就是没奖。结果你的ip正好打到这一核了,hash到这一核里。不换ip拿不出来。
Redis本身来讲,它的存储速度包括查询速度是可以上到10万级的,是非常快的。但是我们线上测的话。可能也是因为我们用的是云平台的一个服务,它本身是基于codis做的分布式服务。我们没有时间去做自己的分布式redis,所以codis已经成为我们的瓶颈了。它大概的速度也就是1w,对于刚起步的应用来说应该够用,那如何做到更快,只有一个方法:当你对一个事儿做到不能更快了,就三个字解决:分布式。这样就肯定能解决,下一步再去琢磨这一个事该怎么解决,这个就更高深了,有机会我会再详细的为大家讲解。
Hbase
有人说Hbase是大数据,其实我觉得很诧异,我认为大数据更偏重于分析。这个东西本身就是一个nosql ,为什么要放到大数据呢?也许因为存的数据多?
一开始起步的时候是三个节点,这算大数据吗?这算是实验。其实不是,它就是一个数据库,大家不要对它有偏见,认为非要数据到多少的时候我把它迁过来,记住,数据量大的那天,你再想迁就迁不过来了。我的经验告诉我,重构可以做局部模块重构,但绝对不能做大迁移。只要做大迁移:丢数据、版本核对,动态版本核对,特别头疼。
我最怕跳槽,因为我的位置是救火的。经验告诉我这个最好提前设计好。因为到那天了你难受,有两方都不满意:第一,用户那里,一直在用,你就得天天看着,怎么还有用户用?不知道该用高兴还是该不用高兴;第二,产品经理、运营就会使劲催你问你好了吗?但是提前规划好,就可以避开这些问题。
Hbase本身来讲呢,既然它能存这么多数据,我们也不想太浪费。有两套出报表的方案,一套是大家熟知的spark,可能用的比较多。对于一个初创团队来讲,spake本身它的worker节点配置有点高,因为它专门吃内存的。还有一种。Tkeyang24:06,替代hadoop,但是它本身没有计算模块,你还是上s,逃不过去。我们的方案呢,作为一个初创团队来讲,对于运营这一块可能有点残忍,但是没有办法,没有这么大的数据量,没有必要。
那么插入数据方案呢,就两个:
1、通过集成phoenix序列化方法使用原生Hbase API插入更新数据,数据装载速率并没有降低;
2、插入数据时采用双保证,缓存和hbase均需要完全插入成功,否则记录日志进入修复队列异步保证数据同步。
高速缓存
关于高速缓存,刚刚有说过, redis最好的方案就是hash。这里说两个应用,Redis本身有个pub/sub,做数据队列的,并列时队列比较麻烦的是,如果想用它做一对一队列,你是做不了的,其实一对一有很多队列。但是作为初创团队没必要,那如何来解决这个问题呢?很简单。我们分布式里,分三个redis,我这一个应用先用三个redis,写数据的时候我在三个里面做shard,你这一个应用shard这三个,那这一个应用就可以把这三个归过来我用,如果两个应用怎么办?有两种方法,一种方法从k上,我监控不同的shard的队列,a监控a队列,b监控b队列,每个人都监控三个,没问题,但还有一个问题,redis本身来讲,提供一个key失效的概念,缓存一定会有时效时间,但是缓存失效之后,他所定义的队列的名字是写死的。是跟十六库绑定的,我们可以切16个作为拓展。如果十六不够的话,切两组作为拓展,这样的话就可以无限的扩大。B监听b的零号库。这样可以无限的扩大这样可以通过阔机器,来提升一个人的消费能力。另一方面通过纵深,来分散消费能力。
关于Redis2.8.22,可能用的比较多。3.0和3.2用的并不多。这有个毛病,TTL只要move一次库,这个TTL就废了,就是失效时间。本来是想用什么巧妙的方法来做黑科技的,结果发现2.8.23解决了,所以大家用的时候从2.8.23以上用。
我们在任务队列主要做的事是,我们应用有个红包的概念,为了让大家都有学习的机会,能够去问问题,所以我们有个红包。如果用分布式的话,会有很多坑就可以绕过去了。
Pub/sub,有一个比较恶心的事,当你消费的时候,如果消费函数,你没有消费下一条的时候,pub/sub是不往后挪数据的,所以他会大量的堆积在redis里出不来,这个一定要注意。
我推荐一个工具,现在有不少公司都在用这个:ES。这个还是比较好用的,但是现在有个问题:随机函数。它里面可以随机抽量数据,在随机抽量数据的时候我们发现两个坑,1、散不开,大部分都一样,随机不太好;2、随机速度特别慢,100w就开始特别慢了。做抽量的话,其实还有很多可以试一下。
关于我们自主研发的,主要就是以下这几点:1、针对服务节点运行数据决定任务的下发;2、任务之间没有任何牵扯;3、任务死亡可以实时分裂新任务继续执行;4、支持定时消息及红包的应用。
我发现很多人有个误区,不写代码就真的省事吗?我觉得吧,不写代码满足需求最省事,用现成的有时候还不如自己写,有什么问题也好找。
关于不停机运维
Zookeeper这个东西挺好的。我现在注册一些服务,立刻就会建上临时的链接,临时文件。比较坑人的地方就是它的存储是限量的。每个节点存储都一样。一定要注意,存储要省着使用,别写进数据,不要用它做分布式事务所。Zabbix挺好用的,我们运维现在就使用这个。