当前位置: 首页 > 科技观察

什么?老板让我开发一个上亿流量的大型网站

时间:2023-03-19 16:19:46 科技观察

我们常见的大型网站,如百度、淘宝、京东等,都是分布式系统。如此复杂的系统不是一天建成的,每个系统都经历了漫长的演进过程。图片来自Pexels对于一个大型网站来说,主要有以下几个特点:支持海量数据和非常高的访问量在一个大型网站中,其核心功能是计算和存储。因此,系统演化过程主要围绕这两点展开。单机系统刚启动时,数据量和访问量都很小。通常,只有一台应用服务器就足够了。单机部署方案启动时,我们将所有资源打包成一个部署文件(如XXX.war),包括:class文件、依赖jar等js、css、images等静态资源。对于用户上传文件的场景,直接在服务器上新建一个目录,将上传的文件放在该目录下。然后,将打包好的release包放到一个web容器中,比如Tomcat,最后启动容器直接对外提供服务。这种部署策略具有以下特点:用户通过浏览器直接与Java应用程序(通常是Tomcat)交互。Java应用程序通过JDBC与本地数据库(如MySQL)进行交互。如果有文件读写的需求,Java应用程序直接通过文件接口对文件进行操作。这时候可能有人会问,Java应用直接对外暴露会不会出现一些安全或者性能问题?是的,像Tomcat这样的Web容器维护链接的能力很弱。当有大量链接时,性能会很快下降。同时Tomcat也不擅长处理静态资源。为此,我们可以引入Nginx来缓解Tomcat的压力。先进的单机部署方案我们在单机部署的基础上增加了Nginx,我们有一个先进的方案:该方案具有以下特点:用户不再直接与Java应用交互,而是与Nginx交互。Tomcat挂在Nginx后面处理动态请求。对于静态资源的访问,直接通过Nginx访问文件系统。当有文件写入需求时,直接通过Java应用写入磁盘。至此,架构清晰多了,但是我们发现了一个问题,系统对静态资源和动态资源的处理方式完全不同。静态资源的处理比较简单,就是简单的文件读写。但是随着业务的发展,动态请求(也就是我们的服务承载者)会变得越来越复杂。动静分离部署方案由于静态请求和动态请求采用不同的处理策略,我们可以将它们分离。该部署方案具有以下特点:动态请求和静态请求通过不同的域名进行分离。新增静态资源服务器处理静态请求,服务器上部署Java应用处理文件写入需求;Nginx只负责文件读取操作。动态请求独立部署,应用将文件写入请求转发给静态服务器处理。静态资源服务器功能单一,部署繁琐。有更好的策略吗?答案是云服务。例如阿里云的OSS提供静态资源存储服务。CDN提供访问加速服务。两者结合使用,就得到了一个海量、超强性能的静态资源服务器(集群)。结合OSS和CDN,静态请求不会成为系统的瓶颈,所以接下来只讨论动态请求。随着系统访问量的增加,动态请求有明显的瓶颈。应用集群部署由于所有的动态请求都由一个应用服务器来处理,当访问量增加时,这个服务就成为系统的瓶颈。此时,我们需要将系统中的多个组件部署到不同的服务器上。新部署有以下特点:Nginx独立部署,形成Web集群。独立部署Java应用,形成应用集群。独立部署数据库。Web集群和应用集群通过HTTP协议进行交互。应用集群通过JDBC协议与数据库进行交互。应用集群会面临很多挑战,主要关注的是如何有效分配用户请求。DNS轮询首先要解决的问题是用户如何向不同的Nginx发送请求。最常见的方式是DNS轮询。大多数域名注册商都支持多条A记录的解析。其实这就是DNS轮询。DNS服务器按照A记录的顺序,将解析请求一一分配给不同的IP,从而完成简单的负载均衡。负载均衡器这里的负载均衡器主要是指Nginx的反向代理功能。当用户向Nginx发送请求时,Nginx需要决定将请求转发给哪个应用服务器。反向代理(ReverseProxy)是指使用代理服务器接受Internet上的连接请求,然后将请求转发给内网的服务器,并将从服务器得到的结果返回给请求连接的客户端互联网。此时代理服务器对外充当反向代理服务器。Nginx在后台服务器配置上更加灵活,可以同时配置多台服务器,根据负载策略将请求分发给后台服务器。Session问题在单机时代,我们的请求只会发到同一台机器上,不存在session问题。当部署一个应用集群时,多个用户请求会被发送到不同的应用服务器。此时,如何同步session就是一个棘手的问题。①SessionSticky主要由Nginx处理,让同一个Session请求每次都发送到同一个服务器处理。Nginx会将同一用户的请求发送到同一应用服务器。这是最简单的策略,但是存在一定的问题:Web服务器重启,Session丢失。负载均衡需要应用层分析(Layer7),性能损失较大。负载均衡器成为有状态的点,不容易进行容灾。②SessionReplicationSession问题的根源在于Session是由多个应用维护的。我们可以通过一定的机制来同步多个Web服务之间的Session数据。SessionSynchronizer用于完成各个Java应用之间的Session同步,最终所有用户的Session数据都存在于各个服务器中。此解决方案的问题:导致网络开销。每个web服务器保存所有session,内存开销大。③CentralizedSession我们可以从Web服务中提取Session,集中存储。将Session信息保存到Session存储集群中,Java应用程序不再负责Session存储。此解决方案的问题:读取会话会引入网络开销。影响应用程序的存储设施问题。④CookieBasedSession也可以将Session数据放在Cookie中,然后在Web服务器上从Cookie中生成相应的Session数据。将Session数据编码到Cookie中,Java应用程序每次使用Session时,都从Cookie中重建Session。此解决方案的问题:受cookie大小的限制。存在安全问题。每次都携带巨大的cookies,占用大量带宽。每次都要进行session数据恢复,增加了应用服务器的负担。随着系统访问量的不断增加,数据库在面对大量的数据读取请求时有些不堪重负。此时,我们需要优化数据库。数据库读写分离通常,数据库的读取会成为系统的瓶颈。对此,我们可以利用数据库主从机制,通过增加多个从库来降低读取压力。与之前的部署相比,该架构只是在数据库中增加了几个从库:对数据库实施主从部署策略。数据写入请求只能在主库上进行。对于数据读取请求,可以在任何从库上执行。主库与从库之间,通过数据库同步策略进行数据同步。由于主库和从库之间的数据同步需要时间,因此可能会出现数据不一致的情况。这是做生意需要慎重考虑的一点。随着业务越来越复杂,对功能和性能的要求也越来越高。最常见的是数据库类语句的性能已经不能满足需求;对于一些热点数据的访问,其性能也迅速下降。此时,我们需要有针对性地引入其他组件来解决问题。引入针对数据库的类似语句的搜索和缓存,通常通过引入搜索引擎来解决;而热点数据的访问加速则通过引入缓存服务来解决。该架构的特点如下:添加搜索集群,提高数据检索性能。添加缓存集群,提升热点数据的访问性能。数据查询优化后,系统的写入性能慢慢成为瓶颈。此时需要扩展数据的写入性能。数据库分库分表随着数据量的增长和写入请求的增加,数据库写入逐渐成为瓶颈。常规的写性能优化是将数据库分为数据库和表。垂直拆分将不同的业务数据放入不同的数据库实例中。水平拆分是将同一张表中的数据拆分到多个数据库中。随着研发团队规模的增大,大家同时在一个项目中开发,冲突频繁,相互影响。此时,整个应用会按照功能模块进行拆分,从而形成多个子网站或子频道。应用垂直拆分面对一个庞大的应用,就像面对一团羊毛,总有一种无从下手的感觉。对此,可以拆分成多个应用,每个应用独立开发,独立部署,独立维护。这种部署方案更加灵活,大大降低了维护成本:通过不同的域名或URL将整个系统分解为多个子系统。用户通过浏览器将各个子系统拼接成一个完整的系统。系统之间很少或没有交互。问题逐渐出现了。系统之间的公共部分没有统一的维护点,相同的功能和代码分布在各个系统中。当然,我们可以通过发布jar包来共享功能代码;但是升级jar时需要所有子系统同步升级,运维开销巨大。此时,我们需要引入面向服务的架构。面向服务的架构我们可以将常用的功能封装成一个服务,自主开发,自主部署,自主维护。在该方案中,我们进一步拆分了业务逻辑:将各个系统之间的公共业务功能进行组织,封装成服务来承载核心业务逻辑,构建服务集群。原来的子系统或者子通道变成了一个薄层,不承载核心业务,只是按照业务流程来安排业务服务。应用服务和业务服务通过HTTP或其他协议进行通信,常见的有Dubbo、Thrift等。服务化解决了系统之间直接调用的问题,也就是常说的RPC,整个系统的协调点全部由应用来完成服务。这种架构适用于多种场景,但在一些需要异步处理的极端场景下显得力不从心。此时,就需要引入消息中间件了。消息队列服务的引入解决了直接调用的问题。对于异步调用,最常见的是消息中间件。与之前的架构相比,变化很小,只是在各个业务服务之间增加了一种调用方式。总结冰冻三尺非一蹴而就,大系统建设也非一蹴而就。我们需要根据业务情况、数据量情况、请求量情况合理规划系统。记住,架构不是越复杂越好,而是“适合你的才是最好的”。