踏上架构之路(二)——系统技术架构的演变-程序员宅基地

"Believe it or not, the bigger problem isn't scaling, it's getting to the point where you have to scale. Without the first problem you won't have the second."

—— 《Getting Real - The smarter, faster, easier way to build a successful web application》, Chapter 04, "Scale Later"

 

请原谅我再一次把这句话作为题头,这句话在本系列开篇《踏上架构之路(一)——一个初创公司的产品架构思路》一文中已经提到过,但是它对我的影响实在是太大,也许它并不是100%正确,但是到目前为止,我仍然认为它适用于绝大多数的情况。它直接决定了我是怎么规划自己的产品架构的,尤其是后台服务的架构。这篇我就来讲讲从零开始到现在差不多10个月的时间里,我们这个初创产品的后台技术架构变化历程(事实上核心演变过程大概是6个月)。

首先再简要介绍一下整个项目的业务需求 —— 这是一个典型的B2B2C的平台服务系统:

整个平台逻辑上分为BBC三大块:第一个B,是商务合作伙伴方,第二个B就是平台本身,第三个C是终端用户。可以参考京东的模式。而从系统的业务核心分为内容服务和交易服务两大类。

Step 1:从一无所有到雏形 (第0天到第2个月)

这是起步阶段,一切以快速原型开发为目的,所以开发环境下技术架构很简单:

没什么好说的,一台Web服务器,一台数据库服务器。所有业务都是在这台Web服务器上完成。

 

Step 2:开始分库(第3个月)

已经提到过到这个阶段的时候,系统的核心业务流程已经基本走通,这个时候重点解决的问题是前期为了开发的简单而建立的单库系统将会带来诸多的问题。针对两大核心业务:内容和交易,必须要将数据库分离。即方便将来做读写分离,也方便基于数据不同的特征进行不同的优化。在《我是怎样做B2B2C交易流程》的文章中,有关于这个部分的细节。

所以这个阶段,数据库一分为2:

 

Step 3:云部署,区分环境(第3个月)

分库之后, 这个阶段大部分业务模块的代码原型已经基本成型,这个时候开始考虑将开发环境向生产测试环境转移。生产测试环境必须要提供外网链接能力,所以必须在云服务器上部署。部署到云服务器之前,考虑到不同的测试应用场景,我一共规划了4个不同的环境,分别对应4个不同的版本:

  • Dev:开发环境。在公司内部服务器搭建。是最简单的开发验证环境,相当于上述的Step 2结构。
  • Beta:开发版外测环境,云服务器。是最新开发版的云端部署映射,主要用于C端用户外测。
  • Pre Release:稳定版外测环境,云服务器。是基于Beta之上的稳定测试版,主要用于B端用户和平台端测试,同时可以领先于移动端的进度提前获取B端用户的最新内容数据。
  • Release:正式发布版,云服务器。是最终的生产环境,一部分内容数据从Pre Release环境里导出,另一部分则开始接收用户最新正式上传数据。

可以看到,Pre Release环境下的数据,是需要选择性的向Beta和Release环境导出的,并且由于有4个环境,数据库数据的迁移不可避免,并且这是一件非常繁琐并谨慎的操作。一开始阶段我们也非常头疼这件事情,后来终于有招入一个开发专门投入精力处理这方面的事情,将绝大多数的迁移工作固化成脚本,给整个迁移过程节省了大量的精力和时间,错误率也大大降低。这里需要指出,为了尽可能降低迁移的失败率,MySQL数据库表的设计和修改需要尽可能的谨慎,对于新的需求和功能,尽可能的做增加,避免删除字段操作,尤其是删除列字段;对于修改列属性的需求,尽可能的使用兼容Type,并提前做好预留字段。如果做不到,需要针对性的手工开发迁移脚本,做copy和对应的映射修改,否则Migration是一件非常坑人的事情。这部分工作,如果深入并铺开研究的话,其实是一个相当大的工程,有能力的公司和团队,应当在这个方面提前做足够的框架设计。

我们选择的是阿里云,对应于服务器和数据库,阿里云有ECS和RDS。与此同时,处于Beta测试和业务的需要,开始需要反向代理将不同的域名导入到后台不同的服务器,于是很自然的增加了Nginx服务器。于是现在的结构就是这样:

 

Step 4:文件服务器分离 (第4个月)

系统的主要内容数据为图片和文本。尤其是图片,因为业务的原因,90%以上的内容数据为高清图片,所以随着测试数据和内容数据的增大,文件服务的分离不可避免。考虑到成本和图片压缩的原因,云图片服务是不二之选。目前国内著名的主流云存储服务提供商都有图片处理服务,最著名的应该是七牛和阿里两家。网上有很多关于七牛和阿里的云存储方案的对比文章,这里有一篇示例:《云存储:阿里和七牛的比较》。基本上来说,我个人的体验是,如果是单纯的多媒体存储服务,阿里和七牛功能上没有太大区别,但是七牛的API相对而言比较简洁方便。另外有一点,七牛每个月有定额的免费容量,阿里则没有,所有的存储容量都按照时间收费(或者包月包年)。但是如果考虑到业务服务器需要频繁调用这些资源动态生成内容,而你的服务器正好又是部署在阿里云上,那么阿里的云存储OSS就有天生的优势了。最终我们项目的文件访问架构正好是这两种情况的结合:B端的内容使用阿里的OSS,C端的内容使用七牛。系统将B端提供的初始内容全部存储在阿里进行管理,B端的内容流量通过后台服务器输出;内容审核系统经过筛选检查之后将有效内容传送到七牛存储,C端系统的所有内容请求将分离至七牛处理,后台服务器不再承担任何终端用户的请求流量。通过这种双云端存储的分离,将并发访问量很低的B端请求放在后台系统内部,由阿里内部网络消化,即方便了各个服务部门之间的数据传输,又能够很好的利用阿里的图片处理服务给B端用户提供高质量内容管理服务;而把终端用户可能产生的高并发访问需求全部导流至七牛云服务,充分利用七牛CDN的优势。这里唯一需要重视和处理的部分,就是B端阿里云和C端七牛云之间的内容同步问题,我们单独写了一套发布模块来解决这个问题,在此不细表。也就是说,现在的系统架构是这样:

其中Nginx在这反向代理的作用非常明显,C端内容请求走cimg.domain.com子域名,B端内容请求走bimg.domain.com子域名,所有API请求走www.domain.com/* 主域名路径。 

到目前为止,这套分离的双存储架构在我们的项目里运行的非常良好。 

 

Step 5:缓存,动静分离 (第5个月)

小到手机客户端,大到大型分布式系统,缓存无处不在。但是什么时候设置缓存,如何设置缓存,每个系统都不同。一般而言,只要系统有大规模的明确的静态数据请求,那么都是可以设置缓存来提高系统的响应速度的;其次,对于一个典型的系统而言,缓存是多级的:应用层缓存,数据层缓存,网络层缓存。对于Web服务器而言,网络层缓存是可以最先考虑的,因为绝大多数请求的内容都是重复的静态数据,如CSS,Html,JS文件,而在网络层使用静态文件缓存一般而言是非常直接简单的操作,比如Nginx就自带了功能强大的静态文件缓存功能;其次,数据层缓存是要重点考虑的,因为非运算密集型的应用,绝大多数情况下系统性能瓶颈都是在数据库上,尤其是当数据库数据读写比例非常大的场景,因为并发读写数据库都需要同步,不管是在应用层加锁还是在数据层加锁,还是使用其他的锁同步措施,都会对读数据造成阻塞,所以数据库缓存是提高高并发读性能最直接也是最方便的方案(当然读写分离是另一种常规措施,这个后面也会提到)。而应用层缓存则是根据不同的业务不同的系统有针对性设计的,一般内嵌在具体业务逻辑里。

对于我们的系统而言,首先考虑的是Nginx的静态文件缓存,把所有的静态页面相关的CSS/JS/html文件全部从Web服务器上分离出来到Nginx上,提供单独的路由路径:www.domain.com/static/*; 其次把体积较大的JS文件/主页图片/图标文件等移到七牛云上,让服务器彻底解放出来只做类似渲染Web页面,响应API请求任务上。

其次,增加数据库缓存,将C端请求中内容相对固定的API放入缓存中,只有当请求内容变化时才请求数据库返回新数据。在我们的系统中,和B端相关的商品和资讯内容相对而言是固定不变的(或者变化极其缓慢的),对于匿名用户而言,这部分是几乎不会变化完全可以独立出来做缓存的;而和登录用户相关的数据一般是变化频繁的且没有共同性的,这部分不做缓存,直接请求数据库。在这里,如何设计API缓存的键值是一件挺有意思的事情,键值太复杂会造成匹配开销太大,键值太短会形成太多冲突。但是在系统初期,访问压力不大的情况下,可以优先考虑减少冲突而牺牲一定的键值匹配复杂度。我们的数据库缓存是使用阿里的OCS服务(现在改名叫云数据库 Memcache 版-AliCloudDB for Memcache),它的优势是自动进行负载均衡和伸缩,可用性也比较强。我们可能是阿里的OCS最早的一批实验客户之一,我们的后台是python的,在早期使用OCS的时候猜测其python客户端接口是建立在bmemcached的版本上的(不知道现在是不是这样)。OCS有个最大的问题(其实也是Memcache的问题)就是不能够遍历当前所有的有效key,这在实际的测试和开发过程中是带来了一定的不便的。但是那个时候还没有Redis版本,现在阿里同时推出了云数据库Redis版,如果对缓存有高要求的环境,应该直接使用云缓存的Redis版本。另外需要注意的是,OCS使用懒删除机制,所有失效的key会延迟到次日凌晨2点左右统一做删除,而在此之前,系统内的缓存key数量不会变化。

于是系统现在的样子是这样:

 

Step 6:分布式任务和队列(第5个月)

稍具规模的系统,都会有大量的非实时响应服务:比如离线统计,消息推送,延时状态更新等等。这些服务一般来说响应时间比较长,要么是因为运算量较大,要么是因为第三方服务接入网络延迟等等,总之它们不适合直接在业务请求中实时响应给用户,否则会造成非常长的等待时间,甚至连接被强制中断。在这个阶段中,我们的项目需要接入大量的第三方服务,比如短信推送,快递查询,还有一些自身业务要求的离线更新计算。这些任务封装后都交给离线服务器在后台独立处理。考虑到可扩展性,应用服务器是集群,离线任务服务器也可能是集群,那么这里实际上存在两个问题:分布式任务和进程间通信。我们的解决方案是使用Celery处理分布式任务,用RabbitMQ负责通信队列实现。于是现在系统有了新的扩展,具备分布式任务处理能力:

 

Step 7:主从数据库 (第6个月之后)

这个阶段整个系统的业务部分已经基本完成了。现在是开始搭建辅助系统的时候,在第一篇《踏上架构之路(一)——一个初创公司产品的系统架构思路》中已经提到过,一个完整的平台系统,除了主业务服务之外,还有管理监控,财务客服,统计报表等等诸多外围服务。对于我们而言,统计监控和财务系统是比较大的挑战。首先统计监控对性能的要求比较高,我们需要在API层级插入统计代码,但是又不能显著影响API的响应速度。这就需要完全基于缓存上做统计功能,我在第一篇中已经详细提到过这个问题,为了能够自己实现统计功能,新建了Redis缓存服务器来给整个系统提供内部逻辑缓存服务,而阿里的OCS主要用来提供内容数据缓存。后来这个Redis服务在我们的竞价系统中也起到了关键的作用,这在文章《架构和产品的制衡——说说竞价拍卖那点事》中有提到。

而对于财务客服等系统,我在《我是怎样做B2B2C交易流程的》一文中提到,由于交易类数据非常敏感,处于保护的目的,我不希望这些辅助系统和主业务系统同时混在一起对数据库进行操作。

出于以上考虑,我们对数据库进行主从备份。外围系统都主要操作从库,而主业务系统负责处理主库。并且财务系统和客服系统则在主从库上进行读写分离,所有只读操作都在从库中进行,当财务需要对交易数据进行更新时(比如更新分账,退款审核,汇款审核操等等操作),才将直接操作主数据库。统计系统顺其自然的使用从库进行数据存储。不对主库进行任何干扰。这样,当系统业务压力增大时,虽然监控统计的压力也会增大,但是他们会分离在两个独立的数据库中进行。

于是系统最终扩展成主从库模式,增加Redis缓存(队列)支持:

 

Step 8:API优化

到此为止,上述一直讨论的都是整个系统的体系架构。那么细化到具体的接口设计上,有没有结构上的调整呢?答案是肯定的,但是这个多半情况涉及业务细节,在此暂不表述。不过有时间的话我会再写一篇聊一聊项目后期,为了提高系统缓存的效率和改善移动客户端代码的结构,我们对系统的主要API接口做的重构和优化。这些优化设计并不是万能的,也并一定就是绝对正确的,只是在特定的阶段为了特定的目的做的改动。“存在即合理”,这个世界上任何一种设计都有自己存在理由和价值,只不过是不是适用具体场合罢了。

但是在这写上Step 8的标题的目的是想说,虽然本文主要描述的是技术架构上的变化(其实更多偏重的是部署体系架构),但其实系统架构的含义很广,不同的层次具有不同的意义。系统层面有本文描述的技术架构,业务层面有模块设计的架构,代码层面有API的架构等等。但是它们都反应了一个事实:任何项目的成熟,都不开各个层面架构的不断更新和完善,有时候甚至是彻底的重构。但是,在恰当的阶段做最恰当的设计,用最小的资源去完成最需要完成的任务,是我做架构设计的核心宗旨。

 

结语:

开篇的那句引用,其实在它之前还有另一句话,我想拿来做结语:

“In the beginning, make building a solid core product your priority instead of obsessing over scalability and server farms.
Create a great app and then worry about what to do once it's wildly successful.
Otherwise you may waste energy, time, and money fixating on something that never even happens.”
—— 《Getting Real - The smarter, faster, easier way to build a successful web application》, Chapter 04, "Scale Later"

子曰:“君子不器”。架构是器,产品是心。真正需要打磨的,是心。

 

希望此篇能够帮助到你,也希望大家批评指正。

 

2016年3月,完稿于南京

 

转载于:https://www.cnblogs.com/wizardguy/p/architecture-improvement.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33878457/article/details/93206935

智能推荐

计算机小学数学辅助教学缺点及对策 论文,小学数学第二学段图形与几何领域的作业设计研究...-程序员宅基地

文章浏览阅读2.6k次。本研究的学段是第二学段。在教学实施过程中采用的教材是北京师范大学出版社出版的《义务教育教科书》数学(教育部审定2013)。下面是第二学段图形与几何领域教学内容图形与几何(一)图形的认识1.结合实例了解线段、射线和直线。2.体会两点间所有连线中线段最短,知道两点间的距离。3.知道平角与周角,了解周角、平角、钝角、直角、锐角之间的大小关系。4.结合生活情境了解平面上两条直线的平行和相交(包括垂直)关系..._小学数学第二学段“图形与几何”非书面作业设计与研究的重难点

计算机网络与通信的心得体会,计算机网络与通信技术课程学习心得.doc-程序员宅基地

文章浏览阅读4.6k次。计算机网络与通信技术课程学习心得,计算机课程学习心得,计算机通信专业课程,课程游戏化学习心得,网络课程学习心得,课程学习心得,课程学习心得体会,学习微课程心得体会,微课程学习心得,信息技术课程学习心得计算机网络与通信技术课程学习心得2012级机电工程学院机电一体化4班姓名:徐丹丹学号:201216010439经过这学期的学习,这门课终于顺利得以结课,在这里有很多话想说,在学习这门课后,感觉有更多的..._通信系统学习心得

实训总结_CSDN Java班 乔晓松_java实训报告拨号系统-程序员宅基地

文章浏览阅读5.7k次。Csdn实训总结 CSDN Java班 乔晓松 111307156 为期7天的暑假实训结束了,我感觉我的收获很大。实训让我了解到了以前不曾了解的信息,使我重新确立了我的目标,不像以前那样迷茫。实训更加的让我体会到了团队精神,我身处其中,我很欣慰、很开心,享受团队的力量。实训前5天,是企业老师给我们授课,讲的是android的开发,都是一些基本知识,让我们有个初步的了解,暑假有时_java实训报告拨号系统

Python+selenium+360浏览器实现自动测试_driver = webdriver.chrome(service=service, options-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏8次。Python结合selenium,360浏览器实现自动测试常见操作,textbox,select,datepicker,input赋值,自动登录,单击菜单_driver = webdriver.chrome(service=service, options=options)

Docker 制作jdk镜像_docker制作jdk镜像-程序员宅基地

文章浏览阅读4k次,点赞3次,收藏7次。一、 编写Dockerfie如下,Dockerfile 与 jdk 的gz包在同一级目录FromcentosMAINTAINERgnauhsilRUNmkdir/usr/local/jdk8ADDjdk-8u191-linux-x64.tar.gz/usr/local/jdk8ENVJAVA_HOME/usr/local/jdk8/jdk1.8.0_191ENVJRE_HOME/usr/local/jdk8/jdk1.8.0_191/jreENVPATH..._docker制作jdk镜像

迷宫最短路径求解_迷宫寻路求解最短路径-程序员宅基地

文章浏览阅读2.2k次。上节中只是提到了如何找到迷宫的通路,但是很显然有些迷宫不止一条通路,所以怎样才能求得迷宫最短路径呢?要找到最短路径,那必然需要知道所有的路径,才能找出最短路径。所以最重要的还是利用栈和回溯法,同时和递归结合起来。思路:首先从入口点开始,查找下一个结点的时候要与当前结点进行比较,因为所有路过的结点都是被标记的,表示的是从入口点到当前点所走的长度,而且都是递增。所以有可能被比较的对_迷宫寻路求解最短路径

随便推点

R语言︱异常值检验、离群点分析、异常值处理_r语言异常值处理-程序员宅基地

文章浏览阅读10w+次,点赞43次,收藏393次。笔者寄语:本文是《R语言数据分析与挖掘实战》异常值处理一般分为以下几个步骤:异常值检测、异常值筛选、异常值处理。其中异常值检测的方法主要有:箱型图、简单统计量(比如观察极值)异常值处理方法主要有:删除法、插补法、替换法。提到异常值不得不说一个词:鲁棒性。就是不受异常值影响,一般是鲁棒性高的数据,比较优质。一、异常值检验异常值大概包括缺失值、离群值、重复值_r语言异常值处理

Risc-v 技术架构_risc-v开放架构设计之道-程序员宅基地

文章浏览阅读2k次。RISC-V ISA 是一种可扩展的、模块化的指令集,它被设计为适用于各种各样的硬件和软件场景。RISC-V 的成功不仅在于其优秀的设计,更在于它的开放性和社区。通过开放的标准和活跃的社区,RISC-V 能够吸引全球各地的人才和资源,使得其在硬件和软件领域的应用越来越广泛。高效性:由于 RISC-V 指令集的简洁性和灵活性,它可以轻松地进行优化,以实现高效的处理能力和低功耗的设计。可扩展性:RISC-V 的指令集具有可扩展性,可以通过添加新的扩展指令集,从而实现更多的功能和性能提升。_risc-v开放架构设计之道

EndNoteX9 使用笔记_endnote 9笔记-程序员宅基地

文章浏览阅读2.6k次,点赞3次,收藏15次。EndNoteX9 使用笔记 文章目录 EndNoteX9 使用笔记1、endnote导入文献的方式1.1. 直接检索1.2.网站输出1.3. PDF导入1.4. 手动导入1.5. 软件之间数据的交换 2、endnote的详细功能2.1. 实现功能2.2. 管理功能 3、编辑参考文献格式3.1. 如何插入参考文献3.2. 如何编辑参考文献格式3.3. 如何手动修改参考文献格式 4、利用endnot..._endnote 9笔记

exe4j打包成的exe中提取jar包_exe4jlib.jar-程序员宅基地

文章浏览阅读3.4k次。以前碰到过的打包的java程序都可以用winrar解压得到jar文件从而进行进一步的反编译,然而今天碰到一个用exe4j打包的程序,无法直接用winrar解压,上网查资料未果。后来想到这个程序的原理是将jar全部解压然后调用java运行库执行,于是到temp文件夹中搜索*.jar,果然找到了目标程序。很简单的原理,供后来者参考。_exe4jlib.jar

强化学习在电商环境下的若干应用与研究-程序员宅基地

文章浏览阅读538次。背景随着搜索技术的持续发展,我们已经逐渐意识到监督学习算法在搜索场景的局限性:搜索场景中,只有被当前投放策略排到前面的商品,才会获得曝光机会,从而形成监督学习的正负样本,而曝光出来的商品,只占总的召回商品中的很小一部分,训练样本是高度受当前模型的bias影响的。监督学习的损失函数,和业务关注的指标之间,存在着不一致性用户的搜索、点击、购买行为,..._电商 算法 reward

【NLP】什么是语义搜索以及如何实现 [Python、BERT、Elasticsearch]-程序员宅基地

文章浏览阅读3k次,点赞6次,收藏14次。语义搜索是一种先进的信息检索技术,旨在通过理解搜索查询和搜索内容的上下文和含义来提高搜索结果的准确性和相关性。总体而言,NLP 语义搜索提供了更复杂和上下文感知的搜索功能,使其在各种应用中都很有价值,包括网络搜索引擎、企业搜索、电子商务、聊天机器人和虚拟助理,在这些应用中,理解和满足用户的意图至关重要。与传统方法相比,BERT 的上下文理解可以显着提高搜索结果的质量。自然语言处理(NLP)上下文中的语义搜索是指应用NLP技术通过理解搜索查询和正在搜索的内容的含义和上下文来增强搜索结果的准确性和相关性。_语义搜索

推荐文章

热门文章

相关标签