程序员社区

大规模架构简介

大规模架构简介插图

原文地址

很少有计算机科学或软件开发课程会教授构建可扩展的系统模块。相反,系统架构通常是通过经历开发产品的痛苦,或者通过与已经经历了这个痛苦过程的工程师一起工作来获得的。

在这篇文章中,我将尝试记录一些我在工作中学习到的可扩展架构的经验。以下内容将维持一个图片使用习惯:

  • 绿色代表一个外部客户端的请求(来自浏览器的HTT请求等)
  • 紫色代表业务代码(例如Django应用,或订阅RabbitMQ的python脚本等)
  • 粉红色代表基础软件(MySQL、Redis、RabbitMQ等)

负载均衡

理想情况下系统容量随硬件的增加而线性增加。在这样的系统中,如果你有一台机器,再加上另一台,你的容量就会翻倍。如果你有三台,再加一台,你的容量会增加33%。我们称之为水平可扩展。在故障方面,一个理想的系统不会因为服务器故障而中断。一台服务器故障只会导致系统容量的减少,与增加服务器时增加的总容量相同。我们称之为冗余。

水平扩展和冗余一般都是通过负载均衡来实现的。 (本文不介绍垂直扩展了,大型系统一般不会选择垂直扩展)因为总会有一个临界点,增加服务器比扩展单台服务器成本更低。而且垂直扩展是不能实现冗余的 。

大规模架构简介插图1

负载均衡是根据某些算法(随机、轮询、根据计算机器容量等)和服务端的当前状态(服务可用性、未响应、高错误率等)将请求分散到多个后端服务的过程。

负载需要在用户请求和web服务器之间实现平衡,但也必须在每个阶段实现平衡,以实现系统的完全可伸缩性和冗余。中等规模的系统可以在三个层面均衡负载:

  • 用户到web服务器之间
  • web服务器到内部平台层
  • 内部平台层到数据库
    有许多实现负载均衡的方法。

智能客户端

在数据库(缓存、服务等)客户端中添加负载均衡功能对开发人员来说通常是一个很有吸引力的解决方案。它吸引人是因为它是最简单的解决方案吗?通常,不是。那么它之所以诱人是因为它是最健壮的吗?很遗憾,也不是。它诱人是因为它易于重用吗?很不幸,还不是。

开发人员倾向于智能客户端,因为他们是开发人员,所以他们习惯于编写软件来解决他们的问题,而智能客户就是软件。

记住这一点,什么是智能客户端?它是一个客户端,它获取一个服务主机池,并在它们之间平衡负载,检测宕机的主机并避免发送请求(他们还必须检测恢复的主机,处理添加新主机等,使他们工作生效但设置很麻烦)。

硬件负载均衡

最昂贵但性能非常高的负载均衡解决方案是购买一个专用的硬件负载均衡器(类似于Citrix NetScaler,F5)。虽然硬件解决方案可以解决大量问题,但硬件非常昂贵,而且配置起来也“不简单”。
因此,通常情况下,即使是资金雄厚的大公司也会避免使用专用硬件来满足所有负载均衡需求;相反,只将它们用在从用户请求到其基础设施的入口处,并使用其他机制(智能客户端或下面讨论的混合方法)对其网络中的流量进行负载均衡。

软件负载均衡

如果您想避免创建智能客户端的痛苦,而购买专用硬件又贵,那么可以提供一种混合的工具:软件负载均衡器。
HAProxy是这种方法的一个很好的例子。它在您的每个机器上本地运行,并且您想要进行负载均衡的每个服务都有一个本地绑定端口。例如,您可以通过localhost:9000访问平台机器,在localhost:9001访问数据库读请求,在localhost:9002访问数据库写请求。HAProxy还管理服务的健康检查,并将根据您的配置删除不可用机器,以及对这些机器之间负载进行平衡。
对于大多数系统,我建议从软件负载均衡器开始,然后在特殊需要的情况下转移到智能客户机或硬件负载平衡。

缓存

负载均衡可以帮助您在不断增加的服务器数量上进行水平扩展,但缓存将使您能够更好地利用已有的资源,并使无法达到的产品需求变得可行。

缓存包括:预计算结果(例如,前一天每个域的访问量),提前生成索引(例如,根据用户的点击历史做推荐),以及将频繁访问数据的副本存储在更快的后端(例如,用Memcache代替PostgreSQL)。实践中,在早期开发缓存比负载均衡更重要,使用一致的缓存策略将节省您的时间。

应用程序VS.数据库存储

有两种主要的缓存方法:应用程序缓存和数据库缓存(大多数系统严重依赖这两种方法)。

大规模架构简介插图2

应用程序缓存需要在应用程序代码本身中进行显式集成。通常它会检查一个值是否在缓存中;如果不在,从数据库检索;然后将该值写入缓存(如果您使用的缓存基于最近最少使用的缓存算法,则此值是特别常使用到的)。代码通常看起来如下所示(特别是read- through型缓存的读请求,因为它从数据库读取值到缓存中,如果缓存没命中):

key = "user.%s" % user_id
user_blob = memcache.get(key)
if user_blob is None:
    user = mysql.query("SELECT * FROM users WHERE user_id=\"%s\"", user_id)
    if user:
        memcache.set(key, json.dumps(user))
    return user
else:
    return json.loads(user_blob)

另一方面是数据库缓存。

大规模架构简介插图3

当你打开数据库时,你会得到一些默认配置,这些配置将提供一定程度的缓存和性能。这些初始设置将针对一个通用用例进行优化,通过调整它们以适应系统的访问模式,通常可以获得很大的性能改进。

数据库缓存的美妙之处在于,你的应用程序代码可以“免费”变得更快,一个有能力的DBA或操作工程师可以在你的代码没有任何改变的情况下提升相当大的性能(我的同事Rob Coli最近花了一些时间优化了Cassandra行缓存的配置,他花了一周时间用图表来显示I/O负载大幅下降,请求延迟也大幅提高)。

内存缓存

就最初的性能而言,您将遇到的最有效的缓存是那些将整个数据集存储在内存中的缓存。Memcached和Redis都是内存缓存的例子(注意:Redis可以配置为将一些数据存储到磁盘上)。这是因为访问RAM要比访问磁盘快几个数量级。

另一方面,通常可用的RAM要比磁盘空间少得多,因此需要一种策略,只将热点数据保存在内存缓存中。最直接的策略是淘汰最近最少使用的数据,Memcache也使用了这个策略(Redis在2.2中也可以配置为使用这个策略)。LRU的工作原理是将不太常用的数据逐出,而优先选择使用更频繁的数据,这是很符合实际使用的缓存策略。

CDN

对于提供大量静态媒体服务的网站来说,有一种特殊类型的缓存(有些人可能会反对这个术语的使用,但我觉得它很合适),那就是内容分发网络。

大规模架构简介插图4

CDN将服务的静态文件重担从应用服务器上卸下(应用服务器通常用于优化服务动态页面而不是静态页面),并提供地理分布。总的来说,您的静态资源加载速度会更快,对服务器的压力也会更小(但会带来新的业务负担)。

在一个典型的CDN设置中,一个请求首先访问您的CDN获取静态内容,CDN将提供该内容,如果它本地有可用的内容(HTTP头用于配置CDN如何缓存给定的内容块)。如果它不可用,CDN将查询您的服务器上的文件,然后在本地缓存,并将它提供给请求用户(在这个配置中,CDN是一个read-through缓存)。

如果你的网站还没达到使用自身CND的规模,作为过渡你可以使用一个子域名(例如:static.example.com)基于nginx来搭建一个CND功能的服务器。 然后将DNS从您的服务器切换到CDN。

缓存失效

虽然缓存很好,但它需要你保持缓存和实际数据存储(即数据库)之间的一致性。解决这个问题的方法就是缓存失效。

如果你是处理一个数据中心,往往是一个简单的问题,但是如果你有多个代码路径去写数据库和缓存的话,很容易引入错误(你在编写程序的时候不注意缓存策略的话,错误是必然发生的)。解决办法:每次更新数据库时,将新值写入缓存(这称为write- through缓存)或简单地从缓存中删除当前值,并允许一个read- through缓存稍后填充它(在read-through和write- through缓存之间的选择取决于您的应用程序的细节,但通常我更喜欢read- through缓存,因为可以减少后端数据库发生混乱的可能性)。
缓存失效在模糊查询,或者更新数量不可知情况下会变得更困难。

离线处理

随着系统变得越来越复杂,几乎总是需要处理,不能在线处理的客户请求,因为请求处理的时间过长(例如,你想要在社交网络中传播用户的某个动作)或因为请求需要周期性地执行(如创建每日汇总分析)。

消息队列

如果你想用内联方式处理请求,但速度太慢,最简单的办法是创建一个消息队列(例如RabbitMQ)。消息队列允许您的web应用程序快速将消息发布到队列中,并让其他消费者进程在客户端请求的范围和时间轴之外执行处理。
将工作划分为由用户处理的离线工作和由web应用程序完成的在线工作,这完全取决于你向用户展示的界面。通常会:
1、对消费者端几乎不执行任何工作(仅仅是调度一个任务),并通知用户任务将离线执行,通常使用轮询机制在任务完成后更新接口(例如,在云端创建一个新的虚拟机就遵循此模式),或者
2、执行足够的内联工作,让用户觉得任务已经完成,然后把请求包含的一些后端操作异步执行。

大规模架构简介插图5

消息队列还有另一个好处,它允许您创建一个单独的集群来执行离线处理任务,而不是加重web应用服务器的负担。这允许您针对当前的性能或吞吐量瓶颈增加资源,而不是在瓶颈和非瓶颈系统之间统一增加资源。

调度周期性任务

几乎所有的大型系统都需要定时任务,但不幸的是,还不存在一个让大众接受的能支持冗余的解决方案。与此同时,您可能仍然需要使用定时任务,但您可以使用cronjobs将消息发布给消费者,这意味着cron机器只负责调度,而不需要执行所有的处理。

赞(0) 打赏
未经允许不得转载:IDEA激活码 » 大规模架构简介

一个分享Java & Python知识的社区