很少有计算机科学或软件开发课程会教授构建可扩展的系统模块。相反,系统架构通常是通过经历开发产品的痛苦,或者通过与已经经历了这个痛苦过程的工程师一起工作来获得的。
在这篇文章中,我将尝试记录一些我在工作中学习到的可扩展架构的经验。以下内容将维持一个图片使用习惯:绿色代表一个外部客户端的请求(来自浏览器的HTT请求等)
紫色代表业务代码(例如Django应用,或订阅RabbitMQ的python脚本等)
粉红色代表基础软件(MySQL、Redis、RabbitMQ等)
负载均衡理想情况下系统容量随硬件的增加而线性增加。在这样的系统中,如果你有一台机器,再加上另一台,你的容量就会翻倍。如果你有三台,再加一台,你的容量会增加33%。我们称之为水平可扩展。在故障方面,一个理想的系统不会因为服务器故障而中断。一台服务器故障只会导致系统容量的减少,与增加服务器时增加的总容量相同。我们称之为冗余。水平扩展和冗余一般都是通过负载均衡来实现的。(本文不介绍垂直扩展了,大型系统一般不会选择垂直扩展)因为总会有一个临界点,增加服务器比扩展单台服务器成本更低。而且垂直扩展是不能实现冗余的。负载均衡是根据某些算法(随机、轮询、根据计算机器容量等)和服务端的当前状态(服务可用性、未响应、高错误率等)将请求分散到多个后端服务的过程。负载需要在用户请求和web服务器之间实现平衡,但也必须在每个阶段实现平衡,以实现系统的完全可伸缩性和冗余。中等规模的系统可以在三个层面均衡负载:用户到web服务器之间
web服务器到内部平台层
内部平台层到数据库有许多实现负载均衡的方法。
智能客户端在数据库(缓存、服务等)客户端中添加负载均衡功能对开发人员来说通常是一个很有吸引力的解决方案。它吸引人是因为它是最简单的解决方案吗?通常,不是。那么它之所以诱人是因为它是最健壮的吗?很遗憾,也不是。它诱人是因为它易于重用吗?很不幸,还不是。开发人员倾向于智能客户端,因为他们是开发人员,所以他们习惯于编写软件来解决他们的问题,而智能客户就是软件。记住这一点,什么是智能客户端?它是一个客户端,它获取一个服务主机池,并在它们之间平衡负载,检测宕机的主机并避免发送请求(他们还必须检测恢复的主机,处理添加新主机等,使他们工作生效但设置很麻烦)。硬件负载均衡最昂贵但性能非常高的负载均衡解决方案是购买一个专用的硬件负载均衡器(类似于CitrixNetScaler,F5)。虽然硬件解决方案可以解决大量问题,但硬件非常昂贵,而且配置起来也“不简单”。因此,通常情况下,即使是资金雄厚的大公司也会避免使用专用硬件来满足所有负载均衡需求;相反,只将它们用在从用户请求到其基础设施的入口处,并使用其他机制(智能客户端或下面讨论的混合方法)对其网络中的流量进行负载均衡。软件负载均衡如果您想避免创建智能客户端的痛苦,而购买专用硬件又贵,那么可以提供一种混合的工具:软件负载均衡器。HAProxy是这种方法的一个很好的例子。它在您的每个机器上本地运行,并且您想要进行负载均衡的每个服务都有一个本地绑定端口。例如,您可以通过localhost:访问平台机器,在localhost:访问数据库读请求,在localhost:访问数据库写请求。HAProxy还管理服务的健康检查,并将根据您的配置删除不可用机器,以及对这些机器之间负载进行平衡。对于大多数系统,我建议从软件负载均衡器开始,然后在特殊需要的情况下转移到智能客户机或硬件负载平衡。缓存负载均衡可以帮助您在不断增加的服务器数量上进行水平扩展,但缓存将使您能够更好地利用已有的资源,并使无法达到的产品需求变得可行。缓存包括:预计算结果(例如,前一天每个域的访问量),提前生成索引(例如,根据用户的点击历史做推荐),以及将频繁访问数据的副本存储在更快的后端(例如,用Memcache代替PostgreSQL)。实践中,在早期开发缓存比负载均衡更重要,使用一致的缓存策略将节省您的时间。应用程序VS.数据库存储有两种主要的缓存方法:应用程序缓存和数据库缓存(大多数系统严重依赖这两种方法)。应用程序缓存需要在应用程序代码本身中进行显示集成。通常它会检查一个值是否在缓存中;如果不在,从数据库检索;然后将该值写入缓存(如果您使用的缓存基于最近最少使用的缓存算法,则此值是特别常使用到的)。代码通常看起来如下所示(特别是read-through型缓存的读请求,因为它从数据库读取值到缓存中,如果缓存没命中):key="user.%s"%user_iduser_blob=memcache.get(key)ifuser_blobisNone:user=mysql.query("SELECT*FROMusersWHEREuser_id=\"%s\"",user_id)ifuser:memcache.set(key,json.dumps(user))returnuserelse:returnjson.loads(user_blob)另一方面是数据库缓存。当你打开数据库时,你会得到一些默认配置,这些配置将提供一定程度的缓存和性能。这些初始设置将针对一个通用用例进行优化,通过调整它们以适应系统的访问模式,通常可以获得很大的性能改进。数据库缓存的美妙之处在于,你的应用程序代码可以“免费”变得更快,一个有能力的DBA或操作工程师可以在你的代码没有任何改变的情况下提升相当大的性能(我的同事RobColi最近花了一些时间优化了Cassandra行缓存的配置,他花了一周时间用图表来显示I/O负载大幅下降,请求延迟也大幅提高)。内存缓存就最初的性能而言,您将遇到的最有效的缓存是那些将整个数据集存储在内存中的缓存。Memcached和Redis都是内存缓存的例子(注意:Redis可以配置为将一些数据存储到磁盘上)。这是因为访问RAM要比访问磁盘快几个数量级。另一方面,通常可用的RAM要比磁盘空间少得多,因此需要一种策略,只将热点数据保存在内存缓存中。最直接的策略是淘汰最近最少使用的数据,Memcache也使用了这个策略(Redis在2.2中也可以配置为使用这个策略)。LRU的工作原理是将不太常用的数据逐出,而优先选择使用更频繁的数据,这是很符合实际使用的缓存策略。CDN对于提供大量静态媒体服务的网站来说,有一种特殊类型的缓存(有些人可能会反对这个术语的使用,但我觉得它很合适),那就是内容分发网络。CDN将服务的静态文件重担从应用服务器上卸下(应用服务器通常用于优化服务动态页面而不是静态页面),并提供地理分布。总的来说,您的静态资源加载速度会更快,对服务器的压力也会更小(但会带来新的业务负担)。在一个典型的CDN设置中,一个请求首先访问您的CDN获取静态内容,CDN将提供该内容,如果它本地有可用的内容(HTTP头用于配置CDN如何缓存给定的内容块)。如果它不可用,CDN将查询您的服务器上的文件,然后在本地缓存,并将它提供给请求用户(在这个配置中,CDN是一个read-through缓存)。如果你的网站还没达到使用自身CND的规模,作为过渡你可以使用一个子域名(例如:static.example.