第一篇 架构套路总纲
一、网站架构的目标
对架构师来说,无论架构大型网站,还是网站的某个子系统,都要考虑下面几个目标:
高性能、高可用、可伸缩、可扩展、安全。
1、高性能(performance):能够对于海量并发访问快速响应
网站的性能优化,有下面常用的招
1.1、架构层面
1.1.1、缓存
浏览器缓存、CDN缓存、应用服务器缓存(本地缓存和分布式缓存
1.1.2、异步
本地队列(LinkedBlockingQueue)、分布式队列(Kafka, RocketMQ
1.2、代码层面
1.2.1、使用多线程
如通过线程池,使用CUP核数*2个线程来处理异步队列
1.2.2、改善内存使用
用空间换时间:如聊天室消息容器从TreeMap改成SkipList
1.2.3、尽量避免锁
1、能不用锁的地方尽量不用锁
2、必须用锁的地方,要控制好锁粒度;同时尽量使用无锁框架,如CAS, putIfAbsent等
1.2.4、关注时间复杂度
1、选择合适的数据结构,重点关注其时间复杂度,如TreeMap vs SkipList
2、使用JDK自带的API时,要看底层的具体实现,从而明确地知道其执行的时间复杂度。
3、尽量避免非必要的循环:循环是增加时间复杂度的关键
1.3、DB层面
索引、缓存、SQL优化、NoSQL
1.4、衡量网站的性能指标
1.4.1、响应时间
接收到请求到返回结果的时间
1.4.2、TPS & QPS
每秒写入和读取的次数
1.4.3、系统性能参数
1、CUP load average(排队等待单个CUP处理的线程数,理想值1–3)
2、CUP user usage(用户占用CUP百分比,理想值为70以内)
3、内存使用情况
4、内网外网带宽
5、磁盘IOPS等
2、高可用(availability):保证服务一直是可用的
2.1、通过冗余保证系统的高可用
冗余分2类:
1、数据存储冗余
2、依赖服务冗余:避免某依赖服务挂了没有后路
2.2、服务限流、降级、熔断
1、限流:避免非预期激增流量把服务打死
限流分自动限流和人工限流2种:
1)自动限流:在Nginx配置插件,限定每秒钟最多通过多少次请求
2)人工限流配置:如聊天室消息的通知拉取频率,可根据访问量情况动态配置
2、降级:当依赖服务故障时,系统可自动或人工降级,去除对依赖服务的依赖。
3、熔断:当依赖服务故障时,系统检测失败数或失败百分比达到阈值后,自动不再调用该故障服务,称为熔断。
2.3、故障自愈
1、如健康检查,自动重启服务
2、通过监控CPU Load指标,自动降级(降低通知拉取频率,禁用日志打印等),指标正常后配置自动恢复
2.4、借助容器自动弹性扩缩容
这里分有状态服务和无状态服务。
有状态服务:如redis多租户容器云平台,通过redis operator做redis有状态服务的槽位迁移
2.5、自动化运维
如自动化发布、自动化部署、自动化摘除故障节点等
2.6、自动化测试
每次更新上线前,通过跑全量的自动化测试脚本,避免出现regression回归问题
2.7、预发布验证
发布到pre环境验证,避免线上配置和测试环境不同而导致的风险
2.8、灰度发布
通过灰度发布,避免出问题时影响面过大。
3、可伸缩(scalability):可动态增加和减少集群节点
3.1、应用服务器
若应用服务器上不保存数据(即无状态服务),则可通过集群扩缩容节点保证可伸缩
3.2、缓存服务器
加入新的服务器节点会使原来的路由失效,虽然缓存数据可以从DB重新加载,但如果应用服务严重依赖缓存,可能会造成网站崩溃。所以要使用改进的路由算法来保证缓存数据的可访问性。例如:取模/2的算法,例如,原来有1,2,3,4服务节点,index为5的落在了5对4取模=1, 现在扩容了5,6,7,8, 扩容后index为5的落在第5个节点上,若取不到,则用8/2, 再用5对4取模,这样可以找到1节点,缺点是需要访问2次。
3.3、关系型数据库
关系数据库虽然支持数据备份,主从热备份,但很难做到大规模集群的可伸缩性,因此关系型数据库的集群方案必须从数据库之外实现。
例如:rongcloud的新加的ssdb集群,通过metadata服务集群,来存储ip对应的server地址,来确保原来的路由不会失效。
对于关系型数据库,也有类似的导航层策略。
3.4、NoSQL
NoSQL为海量数据而生,伸缩性天生就很好。
4、易扩展(extensible):新增功能模块,不会影响到原来的系统使用
4.1、扩展性关注点
网站增加新业务时,是否可实现对现有产品无影响,即要做到不同产品之间低耦合。如chatroom和chatroomhistorymessage.
4.2、保证易扩展有2招:通过队列解耦、分布式微服务
4.2.1、通过队列解耦
通过消息队列实现事件驱动架构:通过消息队列,将生产者和消费者完全解耦,这样,就可以透明地增加生产者服务和消费者服务。
4.2.2、分布式微服务
例如消息服务和group, groupmanager服务。新增产品如新加个单聊产品,可复用消息服务,而不会对现有服务(如消息服务,group, group manager)造成影响;而可复用服务如消息服务升级,再提供新版本的同时,仍可保留旧版本可用,这样将不会影响现有的依赖于它的服务。
5、安全(security):避免被攻击和信息泄露
二、大型网站架构演进进程
任何大型网站都是从小型网站发展来的,不可能开始就有海量用户。网站处于不同的阶段适合使用不同的架构模式,不是最先进的架构模式就是最好的,适合才是最好的。因为使用招数越多的架构,也会有相应的负面代价,只有在有必要用这些招的时候再用这些招。
阶段1、存储和应用在一台服务器上
采用Apache+Linux+PHP+MySQL
这时,应用程序、文件、DB等都在一个服务器上。
阶段2、存储和数据分离
随着网站的发展,一台服务器不够用,这时将应用和数据分离。
这时,应用程序、文件、DB分别放到三台服务器上。
阶段3、添加缓存,提升DB访问性能。
如Moto的SNP, 访问DB成为系统瓶颈,所以当时Moto正处于该阶段。
缓存分两种:
1、跟应用服务器放在同一个server上(使用本地缓存),如JVM内存缓存
2、独立出缓存服务器,可部署缓存集群。如redis
本地缓存的优点:速度快,因为避免了网络交互
缺点:受限于本地内存限制,无法动态扩展。
通过使用缓存,解决了DB的访问瓶颈
阶段4、分布式集群
当网站受到海量用户的并发访问时,单台应用程序的server肯定扛不住。如rongcloud的chatroom单点。
这时,需要使用应用服务器集群。通过负载均衡调度服务器(zookeeper),将用户的请求平均分配到不同的应用服务器。
阶段5、数据库读写分离
目前的互联网应用都是写少读多。如chatroom, 上行为写,分发为读。
使用了缓存后,大部分DB请求都被缓存挡住,但一部分仍然会到达DB, 如缓存过期、缓存未命中、系统重启初始化时。
这时,当DB访问再次成为瓶颈,需要对DB进行读写分离。
目前,绝大部分DB都支持热备份,即对DB配置主从。写数据时写到主DB, 然后主DB采用复制机制,将数据复制到从DB的集群中,然后读取时,根据负载均衡算法,从不同的读DB中读取数据。
阶段6、采用反向代理和CDN,加速网站响应
反向代理和CDN都是缓存的一种。
- CDN部署在网络提供商的机房,用户的访问首先从该机房获取数据
- 反向代理服务器部署在网站公司的中心机房,用户请求先访问反向代理,若找到想要的数据,则直接返回。
阶段7、分布式DB系统和分布式文件系统
单个DB总会有不够用的时候,这时可使用集群,可按照业务进行分库、分表。
阶段8、使用NoSQL和搜索引擎
当数据量达到一定规模时,采用NoSQL和搜索引擎比传统SQL检索要快得多。
阶段9、业务拆分
将不同的业务,拆分部署到不同的服务器集群。如rongcloud的chatroom, group , groupmanager的每个应用独立部署,然后可以共用DB,以此来构建一个相互关联的完整系统。
阶段10、分布式微服务
可将独立部署子系统中的共用的功能,如group、groupmanager、单聊中的消息功能统一使用message服务,以此来构成一个分布式服务。
进而可以开设云平台,提供云服务给其他网站使用。
三、网站的架构模式
为了解决大型网站架构的目标:高可用、高性能、可扩展、可伸缩、安全,目前的大牛公司实践出了一些成熟好用的模式,可供复用,招数如下。
1、横向拆分:将同一个feature分成相互独立的部分,每部分有自己单一的职责。
通常分为:应用层、服务层、数据层
1)应用层:负责具体的业务,如group, groupmanager仅负责自己的业务
2)服务层:为应用层提供公用服务支持,如rongcloud的message服务
3)数据层:提供数据访问服务,如DB, ssdb, 缓存服务器,文件服务器,搜索引擎
2、纵向拆分:将一个大业务拆分成多个不同的相对独立的小业务
如rongcloud的chatroom, 属于应用层(虽然chatroom没有使用服务层),将chatroom纵向拆分成了chatroomsendmessage, chatroom, chatroomhistorymessage,chatroommessage等可独立部署的服务。
3、分布式:横向拆分和纵向拆分的目的,是为了拆分后的模块可以独立部署,然后通过远程访问协同工作,即分布式。
3.1、分布式的好处
可以使用更多的CPU, 内存,存储资源等,以此来应对海量用户访问。
3.2、分布式的缺点
1、不同服务间通信要通过网络,造成性能下降
2、影响网站可用性:一个服务不可用,可能会造成多个服务不可用。如chatroom内存泄露导致CMP宕机,进而全部服务挂掉。
3、影响数据一致性:如群组的加入群组行为,DB服务写入失败,造成内存中的进程内缓存和DB数据不一致,当服务重启后,提示用户不在群组
综上,分布式由于有上述缺点,只有在必须用分布式时再用分布式,别为了分步式而分布式。
3.3、分布式常用的模式
3.3.1、将应用和服务分布式
如rongcloud的群组、讨论组和消息服务,好处是可复用共同的消息服务。
3.3.2、分布式静态资源
可将网站的静态资源Js, CSS, 图片等资源独立部署,并采用独立的域名,即常说的“动静分离”。
1、采用独立的域名,可以加速浏览器的加载速度
2、静态资源独立部署,可减轻服务器压力,这就是动静分离的好处。
3.3.3、将数据和存储分布式
大型网站数据是以P为单位的,单个服务器无法存储这样大的数据,所以要分开存储。可将传统DB进行分布式部署,而NoSQL产品几乎都是天生分布式的。
3.3.4、分布式计算
对于网站产生的海量数据的统计,计算规模非常庞大,通常使用Hadoop的MapReduce分布式框架来做此类计算,其特点是“移动计算”而不是“移动数据”,将计算程序分发到数据所在的位置进行计算,然后再汇总。
3.3.5、分布式配置、分布式锁、分布式文件系统等。
1、分布式配置:如配置中心
2、分布式锁:如要在chatroomsendmessage服务(以userId作为targetId) 上发消息,同一个聊天室中每个msgId都要采用当时的时间来生成唯一ID, 则需要分布式锁。但分布式锁很影响性能,所以目前生成msgId的代码仍然放到了chatroom服务,宁可单点,也不要影响性能。
3、分布式文件系统:可支持云存储,未来考虑使用云存储来保存历史Log
3.4、集群
将系统横向拆分和纵向拆分后,对于单个的模块,仍需要使用集群来分担用户的海量访问,如部署多台chatroomsendmessage服务,集群的另一个 好处是保证高可用性,一台chatroomsendmessage服务挂掉,可以将请求转移给其他的chatroomemssage服务器处理。
所以,即使是小的网站,也要做至少2台集群部署,目的就是为了实现高可用。例子是chatroomsendmessage这样的无状态服务。对于chatroom这样的有状态服务,仅仅采用集群还是不够的,还需要使用冗余。
3.5、缓存
缓存就是把数据放到最近的地方,加快存取速度。
缓存分下面几种方式:
3.5.1、CDN(Content Delivery Network):即内容分发网络。
将静态资源(较少变化的数据)部署在网络提供商服务器,用户请求将从距离最近的网络提供商那里获取数据。例子:视频网站和门户网站,都将用户访问量大的热点数据缓存在CDN.
3.5.2、反向代理
反向代理属于网站前端架构的一部分,当用户请求到达网站数据中心时,先到达反向代理服务器,可以将网站的静态资源缓存在反向代理服务器,这样用户请求无需访问真正的服务器即可返回。
3.5.3、本地缓存
public static变量及本地redis缓存,即进程内缓存和进程外缓存
3.5.4、分布式缓存
如redis集群
使用缓存有2个前提
1、数据访问热点不均衡,某些数据会被更频繁地访问。例如,创建聊天室和查询聊天室信息,不会很频繁地被访问,不必放到缓存中 — 这个case也不能放到缓存中,因为创建和查询可能落到不同的节点上。
2、数据在某个时间段内有效,不会很快过期,否则会读取脏数据。例如,融云读取appInfo中的每个appId的配置信息,2小时同步一次,由于App_Registration表的数据不会很频繁的被更新,所以读脏数据的几率很低。
3.6、异步
异步架构是典型的生产者-消费者模式。
异步的使用有2种:
1)单一服务器内部:多线程共享一个队列(LinkedBlockingQueue)
2)多个服务器集群通过分布式消息队列实现异步。分布式消息队列可以看作是内存队列的分布式部署。
采用异步消息队列的好处:
1)提高系统可用性:万一消费者服务挂掉,那么数据可以在消息队列服务器中堆积,生产者服务器可以不受影响,继续处理业务请求。当消费者服务器恢复正常后,可继续处理消息队列中的数据。
2)提供网站响应速度:生产者服务只要将数据写入队列后,即可给client返回。
3)消除并发访问高峰:当网站举办某活动时,可能会瞬时海量并发请求,造成响应延迟,严重时造成服务宕机,使用消息队列,将突然增加的访问量放到队列中,可以慢慢处理。
采用异步方式的缺点:可能会影响用户体验。例如,用户做完写操作,马上查询,用户写操作是异步的,但读操作是同步的,所以可能查询时,还没写到DB中,造成查不到刚写入数据的情况。
3.7、冗余
网站通常都是7*24小时不间断运行,单个服务器故障随时可能出现,要保证服务器宕机时网站仍然可用,且不丢失数据,就需要一定程度的服务器冗余运行,数据冗余备份,这样做,才能当服务器宕机时,将其上的服务和数据转移到其他机器上。
即使是访问和负载都很小的服务,也必须部署至少两台服务器,构成一个集群,这样可以保证服务高可用。
那么,怎样保证数据也高可用呢?答案是使用公用的(不仅是本机的)持久化存储集群(如redis或者DB)。
DB除了定期备份,存档保存,实现冷备份之外,为了保证数据高可用,还需要实现DB主从分离,实时同步实现热备份。
问题:目前chatroom单点,如果他挂了,聊天室中的数据就全部没了,即使转到其他chatroom服务器,那么当时聊天室中的数据也全丢了。但因为聊天室中的数据是有状态的,例如聊天室中的人,所以建议把聊天室中的成员持久化存储到redis,即使单点chatrom挂了,仍然可以从Redis中恢复聊天室中的成员数据。聊天室中的消息丢就丢了,没关系。
建议单独申请一台所有聊天室服务公用的redis, 然后把聊天室用户实时通过队列写入redis, 这样,
1)当聊天室进行扩容、缩容,以及从公有云和专属相互切换的时候,都可以保证聊天室中的用户不丢;
2)可以解决chatroom的单点问题,即查询聊天室所有用户的操作,可以放到chatroommessage中从公用的redis中读取,避免了从chatroom单点查询聊天室用户的瓶颈。
3)可以解决频繁调用加入、退出时,chatroom单点的瓶颈,因为可以不必从chatroommessage同步到chatroom服务了。
这样,chatroom服务只剩下发消息服务相关的了,其瓶颈在于msgId. 也就是说,chatroom只剩下发消息、禁言、禁止分发、白名单这4个功能点了。都不会有很高的并发量。所以,彻底消除了chatroom单独仅剩的性能瓶颈。
解决chatroommessage下历史消息冗余存储的问题的方案:因为发消息的入口在chatroom节点,所以在chatroom上存储每个聊天室的最新10条历史消息。然后拉取历史消息,从chatroom上拉取,而不是chatroommessage,区分二者时,可在CMP上添加业务逻辑区分。— 这样不行,这样造成的性能瓶颈,比加入、退出更厉害。如果放在公用的redis中,其读取速度肯定不如内存快,有可能出现消息乱序的情况,即之后拉取的速度比从redis读的速度快。
8、自动化
网站的自动化架构主要应用于运维发布上线。
8.1、发布过程自动化
可以减少故障。例如,rongcloud如果可以一键实现代码的版本编译、部署,则会避免许多现实中发生的问题(如使用错误的代码版本造成编译错误,使用错误的部署脚本造成上线错误)
8.2、自动化代码管理
为每个工程师自动创建分支,自动merge
8.3、性能监控自动报警
对于rongcloud来说,可以指标做的更细节化。
8.4、服务器自动化失效转移
rongcloud已做。由CMP通过zookeeper实现
8.5、自动化降级
当高并发超过了系统能够承载的最大负载量时,要自动关闭一部分不重要的服务。例如,需要自动关闭chatroommessage服务,让出资源给chatroom服务。
9、安全
保证网站安全常用的“招”如下。
1)通过密码和手机校验码进行身份认证。
2)登陆、交易等操作,需要对网络通信进行加密。
3)网站服务器上存储的敏感数据,如用户信息也要进行加密;防止机器人程序使用它们用来攻击网站。
4)对于常见的攻击方式,如XSS攻击、SQL注入等,要进行编码转换等相应处理
5)对于垃圾信息,敏感词信息等进行过滤。
第二篇 架构五大点
一、高性能
1、网站性能分析
1.1、网站性能需要关注的KPI
1.1.1、响应时间
从发出请求到收到请求数据的时间。响应速度是网站快慢的最直观感受,所以也是性能的最重要的指标。
测试这些数据的时候,可以执行1万次,用结果除以1万。
1.2、并发数:并发访问数、在线用户数
这两个数据是活动要来之前,决定部署多少台server的关键指标。
测试程序通过多线程模拟并发用户来测试系统处理并发的能力,为了真实模拟用户的行为,测试程序要在每次请求之间加一个睡眠时间,这个时间被称作思考时间。
1.3、吞吐量
TPS (Transaction Per Second), 每秒处理的事务数
HPS (HTTP Per Second), 每秒Http请求数
QPS (Query Per Second), 每秒查询数
在系统并发数由小到大逐渐增大的过程中,系统资源也在逐步耗尽,系统的吞吐量首先是逐渐增加,在达到一个极限后,随著并发数的增加而下降,达到系统崩溃点时,系统资源耗尽,吞吐量为0
在这个过程中,响应时间先是小幅度增加,到达吞吐量极限后,时间快速加长,在达到崩溃点后,系统失去响应。
系统的吞吐量、并发数、响应时间,可以比喻成高速公路的通行状况:
1、吞吐量:收费站收取的通过车辆的高速费
2、并发数:高速路上行驶的车辆数
3、响应时间:车速
当路上车辆少时,车速可以很快,但高速费(吞吐量)也相应较少;
随着高速路上车辆增多,车速受到影响,车速变慢,但收到的高速费逐渐增加;
车辆越来越多,车速越来越慢,高速堵车,收费钱数反而减少;
车辆继续增多,到达某个极限值后,任何偶然因素都会导致高速公路全部瘫痪,车走不动了,收费也收不到了。
1.4、系统性能KPI
描述服务器操作系统的关键性能指标,包括System Load、对象与线程数、内存使用、CPU使用、磁盘与网络IO等指标
1.4.1、CPU Load Average
当前正在被CPU执行和等待被CPU执行的进程数(不是线程数)总和,如果是多核CPU, 在top命令中看到的值,要除以CPU数目,才是单个CPU的值。
CPU Load Average的理想值是等于CPU的核数。
当Load低于CPU核数,表示CPU有浪费;
当Load值高于CPU核数,表示进程在排队等待被CPU执行,表示系统资源不足。
Linux的top命令看到的Load Average表示最近1分钟、5分钟、15分钟的运行队列的平均线程数
注意:如果双核CPU的Load Average为2时,换成单核CPU, 则Load Average将会大于2, 因为此时存在竞争CPU的情况。举个例子,一个活分给10个人,每个人1小时能完成;但如果把这一个活分给一个人,他10小时也完不成,原因就是他超负荷了。
1.4.2、CPU User Usage
用户占用CPU的百分比,理想值在70%以内。
1.2、性能测试的方法
在系统上线前,要对系统的性能做测试评估,以便决定为哪个客户部署多少台server.
负载压力测试:对系统不断增加并发请求数,直到系统的某项或者多项性能指标达到安全临界值,如某种资源已经达到饱和状态,这时继续增加压力,则系统的处理能力不升反降。此时的负载为系统的最大负载。
1.3、性能优化的步骤
通过监控工具分析,查看系统KPI(CPU, 内存、磁盘IO、网络IO、DB访问等)找出系统性能瓶颈,然后分析是代码问题还是架构设计问题(如chatroom单点问题就是系统架构的问题,除了改架构,怎样优化代码都没用),然后采用相应的方法进行优化。
2、Web层性能优化的招
2.1、浏览器优化的招
2.1.1、减少Http请求数。
Http协议是无状态协议,每个Http请求,Server端都要建立一个独立的线程去处理,因此,减少Http请求数可以降低服务器负载。
可以合并CSS, 合并JavaScript、合并图片。即将浏览器一次访问需要的JavaScript, CSS合成一个文件,这样只需要一次请求即可;图片也可以多张图片合成一张。
2.1.2、使用浏览器缓存
对于网站而言,CSS, JavaScript,Logo图片等静态资源更新概率低,可将他们缓存在浏览器中,可通过HTTP头中的Cache-Control和Expires属性设置缓存过期时间,可以几天甚至几个月。
在某种场景,静态资源文件变化需要及时应用到客户端浏览器,这时,可以通过改变JavaScript文件名,即生成新的JavaScript文件并更新Html中的引用,而不是更新现有的JavaScript文件内容。
使用浏览器缓存策略,当需要更新静态文件时,比如需要更新10个图片,不要一次把10个全更新,而是逐个更新,每个中间有一定的间隔时间,避免用户的浏览器缓存突然大量失效。如果缓存集中更新,会造成服务器负责骤增,网络发生阻塞。
2.1.3、启用压缩
在服务器端对客户端要用的文件进行压缩,然后浏览器收到后再解压缩,这样可以减少通信传输的数据大小。
压缩方式的优点:节约网络带宽
缺点:给服务器和浏览器造成一定的负担,因为需要压缩和解压缩。
所以使用该方法要权衡舍弃带宽还是舍弃服务器和浏览器的资源。
2.1.4、CSS放在页面最上面,JavaScript放在页面的最下面
只有在加载完毕所有CSS后才会使用;但JavaScript一定被加载就会被使用。所以把CSS放到前面先加载,然后再加载JavaScript。但如果有需求要求JavaScript必须先执行,就不能用这招了。
2.1.5、减少Cookie传输
Cookie: 可以保持登录信息到用户下次与服务器的会话,即下次访问同一网站时,用户会发现不必输入用户名和密码就已经登录了。
Cookie信息包含在每次请求和响应中,所以要尽量减小Cookie中的数据量,对于某些静态资源的访问,发送Cookie没意义,所以可以把静态资源放到独立域名的服务器中,避免请求静态资源的时候发送Cookie, 减少Cookie的传输的次数。
2.2、CDN缓存
CDN (Content Distribute Network), 将静态资源缓存在网络服务提供商的CDN服务器,用户通过浏览器请求时就可以从距离最近的网络运营商CDN服务器上加载
即静态资源从CDN服务器上取,而动态资源从网站服务器上取。
CDN一般缓存的静态资源有:CSS、JavaScript、图片、文件、静态网页等。这些静态资源的特点是访问频率高。所以将其缓存在CDN上面可以极大加速网页的打开速度。
2.3、反向代理
作用1、缓存静态资源和动态资源
当用户第一次访问反向代理的时候,会将请求到的静态资源缓存到反向代理服务器;这样,当其他用户也访问该静态资源的时候,就可以直接从反向代理服务器返回,不用访问真正的服务器了。
有些动态资源也可以缓存到反向代理服务器,当这些缓存的动态资源有更新时,服务器会来更新反向代理服务器上的数据。
作用2、用来做负载均衡
例如我在rongcloud用Netty实现的反向代理服务器,就是用来做负载均衡的(将负载均衡的算法写在反向代理服务器)
再比如nginx
作用3、安全
在真正服务器之前添加了一层屏障,保障网站真正服务器的安全。
- 应用服务器层的优化:主要的招有:缓存、集群、异步
招1. 缓存
网站性能优化第一定律:优先考虑缓存
1)缓存的原理:把要访问的数据放到可访问速度最快的介质中。
《1》可减少访问时间
《2》需要计算的数据,可以不用重复计算
缓存的本质:一张内存的Hash表,Hash表的读取的时间复杂度是O(1).
3、应用服务器层的优化:主要的招有:缓存、集群、异步
招1、缓存
网站性能优化第一定律:优先考虑缓存
1、缓存的原理:把要访问的数据放到可访问速度最快的介质中。
1)可减少访问时间
2)需要计算的数据,可以不用重复计算
缓存的本质:一张内存的Hash表,Hash表的读取的时间复杂度是O(1).
Key/Value中的Key的HashCode对应Hash表的索引,可快速访问到Hash表中的数据。可以把HashCode理解为对象的唯一标识,Java语言中的对象Object的Hashcode方法,返回值为int, 所以通过hashcode方法可以计算到Hash表的索引下标,然后该索引对应的元素即为KV对。最常用的算法是取余数
注意:通过索引下标找到的元素里面包含的是KV, 并不仅仅是Value.
缓存主要存放读多写少,且变化很少的数据。
缓存的流程:应用程序先去缓存中读取,若没有,则去DB中读取,然后将其写入缓存,下次从缓存中读取。
2、缓存使用的负面教材,即不适合使用缓存的场景:
1)缓存频繁修改的数据:被缓存的数据在还没有被读取的情况下,就被修改了,这样的话徒增系统的负担。通常来说,读写比要在2:1以上,缓存才有意义。
2)没有热点的数据:例如查询聊天室信息。并没有遵循二八定律:即80%的访问落在20%的数据上,内存资源宝贵,不可能缓存全部数据,而仅缓存最近用的数据,把历史的数据清除,这样缓存没多大意义。
3)数据不一致与脏读
缓存一般有失效时间,一旦缓存失效,则从DB中重新加载。这样要容忍一定时间的数据不一致。如融云读取CC_AppRegistration.
另一种缓存更新策略是缓存与DB同步更新,融云的大部分缓存都是同步更新,不过这样带来的问题是系统开销大,和事务一致性问题(如缓存更新成功,但DB没有写入成功,重启后缓存消失,会出现系统行为不一致情况)
4)缓存可用性
一旦缓存大量失效,则会从DB中读取数据,这时DB服务器会因为无法承受如此大的读取压力而宕机,进而导致整个网站挂掉,这种情况称为缓存雪崩。发生这种故障时,甚至不能简单地通过重启缓存服务器或者数据库服务器来恢复网站。
可通过使用缓存分布式集群,将缓存数据分布到多台服务器上,可一定程度保证缓存的可用性,当一台缓存服务器宕机的时候,重新从DB中加载这部分数据,不会对DB服务器造成很大冲击。
在融云的工作中,有一次服务器由于并发大,服务器撑不住了,然后limiao重启服务器后,从DB中全部加载CC_AppRegistration的信息,由于缓存数据量大,读取DB写入缓存较慢,造成线程池积压,导致内存泄露(当然内存泄露的根本原因还是线程不安全导致的)。这是从DB中全部加载缓存的负面案例,
5)缓存预热
缓存中存放的数据都是LRU的数据,在启动启动时,可以把一些热点数据先从DB加载进来,这个过程叫缓存预热
6)缓存穿透
如果用户不恰当地调用,或者恶意地攻击,持续地请求某个不存在的数据,由于缓存没有保存该数据,则所有的请求都会落到DB上,造成DB压力过大,甚至崩溃。
简单的对策是把即时不存在的数据也缓存起来(其值为null)
在融云的工作中,使用putIfAbsent方法,其实也是一种有效防止缓存穿透的策略。
3、分布式缓存架构
分布式缓存:指缓存部署在多个服务器组成的机器中,以集群的方式对外提供服务。
分布式缓存分2种:
第一种:需要所有服务器同步更新的缓存,如JBoss Cache
在集群中的所有服务器,都保存相同的数据,当某台服务器有缓存数据更新时,会通知集群中的所有服务器更新缓存数据,通常将JBoss Cache跟应用服务器部署在同一台机器,但这样受限于本台机器的内存,且这样当集群规模很大的时候,代价也太高,所以互联网很少用这种架构,仅用于企业应用系统。
第二种:多个服务器之间不互相通信的分布式缓存,如Memcached
该种缓存与应用服务器独立部署,应用程序通过一致性Hash等路由算法,找到缓存服务器,缓存服务器之间不相互通信,这样缓存集群伸缩性良好。
招2. 异步
通过消息队列实现异步,异步的好处:
1)提供良好的扩展性
2)加快响应速度
3)削减访问峰值
招3. 集群
通过多台服务器的集群来分担请求压力,避免单点瓶颈
招4. 优化代码
1、多线程
为什么要用多线程?IO和多CPU
原因1. IO
无论是磁盘IO还是网络IO, 都要花费较长时间,而进行IO处理时,会释放CPU等待IO完成。此时,其他线程可使用CPU.
原因2. 多CPU
多核CPU的服务器,只有启用多线程,才能充分利用CPU.
一台服务器上启动多少个线程合适呢?
启动线程数 = [任务执行时间 / (任务执行时间 - IO等待时间)] * CPU内核数
所以启动的最佳线程数和CPU内核数成正比,与IO阻塞时间成反比
如果都是CPU计算型任务,那么线程数不操作CPU内核数,因为启动再多线程,CPU也来不及调度
如果是需要等待磁盘操作或网络响应,那么启动多线程对系统会提升吞吐量。
在融云工作中,向redis/DB中写数据或者向其他service发送请求时,每个task也做磁盘或者网络IO,这时启用的线程池的线程数为CPU核数*2
而对于发送Notify的消费者线程,队列数组的个数等于CPU核数,因为队列的操作就是Put和get, 没有IO操作。
多线程编程要注意线程安全问题,“灵异现象”通常都是由于多线程引起的。
2、资源复用
要尽量减少开销很大的系统资源频繁地被创建和销毁。比如数据库连接、网络通信连接、线程、复杂对象等。
资源复用有两种模式:单例 和 对象池
数据库连接池也是对象池的一种。数据库连接的创建和关闭,都需要花费较长时间,所以实际应用中都会使用数据库连接池(Connection Pool), 数据库连接对象创建好之后,放到对象池(连接池)容器中,应用程序要使用时,就从对象池中获取一个空闲的连接使用,用完再归还,而下一个请求可复用该对象,不需要创建新的连接。
对于每一个web请求(Http Request), 应用服务器都需要单独创建一个线程去处理,这时,应用服务器可以使用线程池(Thread Pool)的方式。
这些所谓的对象池、连接池、线程池,本质上都是对象池。即连接、线程都是对象,“池”的管理方式也基本相同。
3、数据结构
程序 = 数据结构 + 算法
在不同场景下,合理使用各种数据结构,会提升数据的读写和计算性能。
例如,Hash表,Hash表的读写性能很大程度上依赖于HashCode的随机性。即HashCode越随机散列,Hash表的冲突就越少,读写性能也就越高。
目前比较好的字符串Hash散列算法用Time33算法,即对字符串逐个字符迭代乘以33,求Hash值,算法原型为: Hash(i) = Hash(i-1) * 33 + str[i];
虽然time33能较好地解决冲突,但相似字符串的Hashcode也比较接近,如”AA”的Hashcode是2210, 而”AB”的Hashcode是2211, 这在某些场景下是不能接受的。解决办法是对字符串取信息指纹,在对信息指纹求Hashcode(即MD5算法), 由于字符串微小的变化就可以引起信息指纹的巨大不同,因此可以获得较好的随机散列。