我想如果有人问你如何构建一个XX类型网站,你该怎么做。大家往往在这方面会没有什么思路,不知道如何回答。这些东西只有经历的项目多了,自己慢慢亲身体会和吸取经验教训以后才能得出一个答案。所以,我想针对构架一个网站简单说一说如何在满足老板需求的前提下选择一个构架来搭建自己的网站,同时也说一说网站构架升级和迭代。
首先,如果你是在一个创业公司,在项目的建立之初,由于无法对这个项目做一个准确的预期,而且一般要求这个项目都需要快速上线,所以我们往往要考虑的是这个项目的成本投入,高并发与稳定性要求反而要在这之后。所以,初期的网站一般都是单体应用。那么单体应用的网站是什么样子的呢?请看下图:
所谓的单体应用就将所有的业务逻辑都在一个项目下开发出来。从框架图可以看出来,我们通过简单的nginx负载均衡多个单体应用服务,然后每一个服务都直接调用数据库与redis缓存等服务。一般来说,这种单体应用无论是构建还是开发的速度都很快。在团队初期,只有一两个人的时候,通过这个方式可以快速搭出一个服务。我们当初搭建一个报名网站,就是通过这种构架在一周内上线业务。但是随着业务的复杂度越来越高,代码大量激增,网站所需要提供的服务越来越多,开发团队人员的增加,这种构架的问题就显现出来。
主要问题如下:
1、对于越来越多的服务,单体的代码越来越多,整个服务开发维护越来越困难
2、服务的负载度越来越高,单体每次打包测试的时间也越来越长
3、上线以后,如果代码有问题,需要将整个单体应用重新打包发布,系统越来越显得笨重
4、开发人员的增多,代码的管理也因为单体而变得复杂且难于维护
5、一个单体应用宕机,整个服务全部无法使用
注意:在做单体设计开发的时候,尽量将所有的服务逻辑单独成一个一个的service,这样有利于后期的分布式改造。
后来我们公司的业务发展越来越快,于是我们开始考虑分布式的构架来搭建服务。好在我们在开发单体应用的时候,应用内部的服务逻辑相对独立,我们只需要简单的将服务先简单拆除出来就好了。比如我们的报名服务、订单服务、优惠卷服务、赛事服务、个人中心服务等等。当然,初期我们的分布式也很简单,先从单体应用升级分布式,只需要将原有的服务一点一点的分离出来就好。我们当初决定采用dubbo分布式框架,初期的分布式框架图如下:
上图这个构架是我们早期的分布式。分布式框架采用了dubbo分布式框架,注册中心是zookeeper。早期我们的分布式服务粒度还是很粗的,随着业务的发展,我们的服务粒度也会越来越细。分布式最大的好处是将应用一个一个的分离出来,相比于单体应用的优点有:
1、服务采用分布式,某一个服务宕机,其他业务仍然可以使用,整体服务不会挂掉
2、每一个服务都可以是分布式,我把任何一个服务启动多个应用。某一个应用挂掉,dubbo会自动调整整体应用仍然可用
3、可以根据业务量随时调整服务的分布式启动应用数量,可以根据业务随时扩容或者减少服务应用数量
4、分布式服务代码开发上针对服务会有单独的项目,在多团队开发上更为便利,代码也易于维护。不同团队调用其他的服务时候,也只需要关心服务的接口就好
5、为以后容器化部署打好基础
6、某一个应用上线也不会影响其他服务的使用,上线发布可以实现不断网情况下发布业务
7、以后每一个服务的数据库也可以单独拆分出来,增加数据安全性
采用了分布式的服务,对于业务的扩容提供了极大的便利,同时也能提高开发效率。但是也会有一些新的问题出现,比如:如何划分一个服务,服务的粒度设计。再有就是分布式对于事务的一致性要求。
当然,在我们公司采用分布式的初期,我们意识到了这两个问题。当时对于我们团队来说,最大的问题还是事务的一致性问题上。分布式一致性问题这里举一个例子简单说明一下:
比如用户报名参加一个比赛同时需要生成一个订单,而且用户还使用了优惠卷。业务上的期望是,如果用户在报名生成减库存、生成订单(微信或支付宝)、扣减优惠卷任何一个步骤出现问题的时候,其他的数据需要回滚。比如生成订单失败了,我们希望库存加回去,优惠卷的扣减也回滚。
这里问题如果是单体应用很简单,因为在一个事务内,数据会自动做回滚操作。但是在分布式下就麻烦了,因为报名库存管理、订单管理和优惠卷管理都是不同的服务。一旦数据在某一个服务上出现错误,如何通知其他的服务做有效的数据回滚呢。甚至后期服务的数据库都是不同的,我们如何能保持数据的一致性呢?
带着上面的问题我们来看一下如何解决。
我曾经在一次CSDN大会上听过孙玄老师(58集团-技术委员会主席)分享的58在分布式上的数据一致性解决方案。觉得很有心得。孙玄老师当时分享了他们基于异步补偿的解决方案,其实很多大型的互联网公司也是采用类似的方式解决的。如下图:
这种方式可以有效的解决数据一致性问题。
这种方式需要你的事务接口都提供一个事务补偿接口,所以很遗憾,我们团队仍然由于人员有限(当时只有三个后台工程师),所以没有采用这种方式,而是采用了不太好的,但是能快速解决问题的链式方案。就是通过一个服务调用另一个服务,通过每一个服务抛出异常到上一级服务,然后上一级后去异常对数据回滚的方式来解决数据一致性问题。其实这里并不推荐大家使用这种方式,但是当时由于人员不够,大家整体都忙于业务开发,所以不得不采用这种临时的解决方案。
当然,现在已经有很多微服务架构分布式事务解决方案。比如:
阿里刚刚开源的Fescar框架 https://github.com/alibaba/fescar
LCN框架 https://www.txlcn.org/zh-cn/docs/preface.html
EasyTransaction框架 https://github.com/QNJR-GROUP/EasyTransaction
注:各个框架的性能测试对比,可以通过后面的链接查看别人的测试报告,非常详细 =》http://springcloud.cn/view/374
好了,我们的业务框架已经经历的第二次构架,整个系统都是分布式的。在一段时间内,我们的系统业务确实得到了极大的发展。无论是业务开发效率还是承载的业务量(随时增加服务嘛)。
在做分布式构架的同时,我们还新购买了阿里云的OSS服务,用于存储我们上传的静态文件,包括:图片、视频、css文件等。购买OSS是因为我们网站有很多B端用户创建自己的赛事(就好像淘宝开店一样)需要上传大量的图片,一个赛事的页面中也有很多图片,图片如果继续放在我们自己的服务器上,每次加载会占用我们服务器的流量(流量费很贵的),同时网站访问速度变慢。将所有静态资源存放在OSS上,可以大幅减少自己服务器的流量占用,同时服务器也不需要购买大容量硬盘。而且OSS的存储费用和流量费用并不高,页面打开以后的图片加载速度也很快。同时OSS是支持做CDN的,也为以后服务拓展做好准备。
完成了以上的框架改版,我们的业务大概又撑了一年的时间。随着我们的业务量慢慢激增与业务种类的增多,我们又开始遇到了新的问题。我们遇到的问题主要有以下几点:
1. 随着一些大型赛事活动上线(往往一个大型的活动是几万人的规模),同时根据我们业务类型赛事报名有一个特性,往往在开放报名的前几个小时内能上来大部分用户报名。用户的访问量与报名量会在活动开启报名的一瞬激增。这种服务类型有点像双十一活动。大量的表单提交与数据校验,同时超高的流量并发访问量,造成业务访问缓慢。而我们的服务会有多个入口,流量带宽也是多个入口,经常出现有些服务带宽浪费,有些却不够用的情况。
2. 我们上线了团报活动,团报经常是一个用户替几百甚至上千人报名,每一个用户都要做详细的数据校验。从而造成请求表征超长请求(我们认为1秒内无法返回的数据都是超长的请求)。同时,一个用户在做团报的时候,其他用户也可能团报并提交同一个人的信息,但是赛事本身是不允许同一个人报名两次的。这种长请求操作可能造成数据重复错误。
3. 后台一些导入数据的操作也出现了超长请求的情况。
4. 我们的服务器开始受到越来越多的恶意攻击,我们经常发现有人恶意通过代理IP刷新我们的页面和业务接口。
5. 我们公司新增了大数据团队,而两个开发团队是两地的,两地人员的测试服务器无法相连,对于开发造成了极大的困扰。
于是,针对以上问题,我们有开始了对公司网络构架的改造,同时也再一次对业务的构架进行了改造。
1. 我们引入了MQ消息管理机制。将超长的请求通过MQ发送异步消息的方式。让业务起码不会在报错,同时通过MQ机制+redis锁机制解决了第二个和第三个问题。
2. 通过一台vpn服务器构建公司的VPN网络,将公司两地的环境拉在一个网络下。同时,VPN的密钥统一管理,每一个开发分配一个密钥,通过OpenVPN链接,如果有技术人员离职,可以随时废弃该密钥。 开发人员还可以在任何有网的地方登录VPN进行紧急办公,比如bug修改什么的。解决了第五个问题。
注:之前我们针对异地办公的讨论有三套解决方案。分别是:购买VPN硬件,测试服托管机房和云服务搭建OpenVPN。前两种的成本都很高,最终选择第三种方式,只需要购买一个台阿里云服务器即可解决问题。
3. 为了提高安全,同时解决带宽流量浪费的问题,我们重新规划了我们服务器的网络结构。在提升安全的同时,流量管理也统一起来,避免流量的浪费。也就是解决了第一个问题和第四个问题。
于是,公司网络环境改变如下:
从上图我们可以看出vpn服务器的好处。首先,vpn的搭建链接了所有开发人员节点,方便大家在一个办公环境内完成开发。同时该服务器也是跳板机,所有人员无法直接链接在线服务器,必须是专门指定的管理人员通过跳板机的方式链接,将在线服务器尽可能的与外网隔离。顺便做了我们的git代码管理服务器。
同时,我们的服务器构架如下:
通过上图,我们构架的方式很简单。主要改变如下:
1. 新增购买阿里云的SLB服务,将所有业务流量引入SLB,然后由SLB统一分流给自己的业务服务器。优点如下:
a)流量带宽统一管理,如果流量不够用的话,只需要统一购买SLB流量即可。
b)隐藏了自己业务服务器,所有业务都是通过SLB转发的,SLB本身的安全是阿里在做,同时支持其他阿里云安全服务,提高了自身的安全性。
2. 新增MQSserver,很多超长的处理通过消息实现。
3. 通过DTS服务(也可以自己写中间件方式)时时同步自己在线业务给大数据,减轻业务数据库压力同时提高大数据处理速度。大数据的业务也可以时时返回给业务服务使用。
4. 业务整体规划好以后,我们将应用服务和务服务整体进行了规划。将所有的业务应用和服务应用都整理好。以后业务量如果突然增加,我们可以通过简单的复制服务器进行扩容,在短时间内快速增容。同时,我们所有的服务又都是单一独立方便打包的,这一点是为以后业务量再次扩大,为服务整个做容器化(Docker)部署做准备。
这一次的业务构架改版主要是服务器网络的。本次构架改版以后,无论是业务的安全性、团队的开发效率和未来的扩容都提供了极大的方便。当然,就像前面说的,随着业务的发展,构架还会继续改变。每一次构架的改变,我们也是希望能为下一级的拓展提前做好准备,所有的框架都要从现有的业务与成本进行考量。我们每一次的构架设计都要一直在系统稳定、运行效率、开发效率和成本之间不断考量与妥协作出判断。
还好,我们一直有其他公司的经验。我们其实一直是在学习的过程中成长。我们前面有BATJ这样的巨无霸会分享各种经验,可以使我们少走很多弯路。当然,我们在这期间也遇到了很多其他的坑。这里也顺便分享一下,希望大家在做构架设计的时候能尽量避免这些问题。
遇到过的问题很多,这里只是简单提几个我能想的到的:
1. 网址从http割接为https。这个从网站设计之初就一定要想好,要开始网站可能因为成本无法购买证书,所以不是https的网址。但是后期网址一般都会割接为https的。因为https的url要求必须内部的资源也都是https的,所以一定一定想好,无论是在数据库表的设计,后台代码的设计和前端页面的开发都要提前考虑,让以后割接资源图片的时候可以方便的替换https路径。尤其是我们网站有一些富文本编辑的内容中也会有图片路径,这些都要做替换的。
2. 多语言版本支持。未来的应用很可能是要求多语言切换的。所以在做开发的时候,无论是页面上还是后台接口返回的提示,都要提前设计好,可以方便的切换多语言。当然,如果觉得自己应用不会使用就不需要考虑了。
3. redis很多地方的使用。redis的key一定要提前做管理,防止重复的key出现。另外,redis存储的value如果开发改版了,之前的redis中的数据会出现序列化错误,这点要注意。还有redis其实可以在很多地方方便的使用。比如我们做过的一个双十一活动,就是通过有效的利用redis解决了超高并发抢购商品的问题。我会在后面慢慢更新我们当初双十一通过redis的解决方案。网上也有很多分享案例。
4. dubbo接口传的数据有大小限制的,我们之前就做过超大的数据bean传输,结果内存溢出了。
5. 我们还遇到过一个问题,就是用户一下子上次几万条数据,虽然我们采用了MQ解决,但是每一次上传竟然需要几十分钟才能完成,这个在客户看是不可接受的。于是我们分析了上传方案的时间复杂度与空间复杂度,最后通过分布式计算,数据缓存二叉树快速查询等方案解决了该问题。最后上传几万条数据并最终生成excel数据报告只需要20秒左右。这个我也会在以后其他文章分享一下我们的做法。
6. 注意redis存储数据的容量
我们的构架还在不断演进,还有许多工作需要去做。比如未来的容器化部署,熔断机制等。
最后,上面的构架也只是我自己根据经验的一些浅谈。还有很多幼稚不对的地方希望大家能多提意见。^-^