再谈大型网站技术应用——上篇
0. 前言
本文可以当做《浅谈大型网站技术应用及适用场景》的细化版,我们将更加深入的介绍前文中各个技术应用。因为篇幅过长,我将分两到三篇文章叙述完。
1. 分布式缓存
1.1 定义
缓存就连接访问者和数据源之间的高速存储介质,用来提高多次读取数据的性能。
注意两点:
高速存储介质:访问缓存要比直接访问数据源更快,例如访问内存比硬盘快,内存就是缓存。
多次读取:这是缓存的核心要素,使用缓存就是为了多次读取。
1.2 优点
使用缓存可以显著提升系统读取数据的性能,降低缓存后数据源压力。
1.3 缓存的关键指标
上面提到了,缓存的目的就是提升多次读取性能,所有衡量缓存的关键指标就是缓存命中率。
缓存命中率=命中次数/总查询次数
缓存命中率越高,缓存效果越好,系统读数据性能提升越明显。
1.4 影响缓存命中率三要素
三点要素:
缓存的键定义及集合的大小:因为通常缓存是以key/value的形式定义,所以key的定义对获取value至关重要。定义合理的key通常数量较少且复用率很高。例如我们以省为维度缓存数据通常比以市为维度缓存的数据键集合小且复用率高(一个省中所有市都复用同一个key)。当然我们要结合业务场景选择合适的key值定义。
缓存的空间大小:这个很好理解,缓存的数据越多,缓存被再次读取的概率越大。
缓存对象的生命周期:这个也很好理解,一个缓存对象在缓存中存活时间越长,他再次被读取的概率就越大。
所以我们要提升缓存的命中率也可以从以上三点着手:
定义好键值,在符合业务的前提下尽量让键的集合小。
尽量延长缓存的生命周期,需要提升数据不一致的容忍度。
为缓存提供更大的存储空间,单机向多机分布式集群转换。
1.5 系统中的各种缓存应用
正向代理服务器:代理内网用户访问外网请求,可以缓存页面等静态数据。位于客户侧,无法应用。
反向代理服务器:代理系统内网服务器相应外用用户请求,可以缓存页面等静态数据。
多级反向代理服务器:
前端缓存:位于前端服务器之前缓存前端请求内容
后端缓存:位于后端服务器之前缓存后端请求内容(RESTful格式接口调用更加有利于后端服务请求缓存)
CDN:位于各个网络接入商节点机房中,离用户较近。
静态资源缓存
动态访问链路加速
本地对象缓存:例如我们程序中使用HashMap(ConcurrentHashMap)做缓存应用。
优点:实现简单,访问快速,适应程序内小场景
缺点:缓存空间和程序空间同一内存空间,缓存空间有限,难以扩展。分布式场景下缓存共享困难。
本地分布式缓存:例如Tomcat中session同步机制
优点:访问快速,某种意义上达到了缓存共享的目的
缺点:一个节点有全部系统数据,缓存空间浪费,且没有起到分散存储和访问压力的效果。
远端分布式缓存:memcached和Redis等
优点:缓存分布式存储(无数据同步),既分担了缓存访问压力,又提升了缓存容量
缺点:因为有网络请求,性能就本地存储略差
本地远端多级缓存:按照缓存命中率将数据存储于本地和远端
优点:即利用了本地缓存访问效率高,又利用了远端存储容量大、方便扩展的优势,适合对性能有极端要求的场景。
缺点:实现逻辑复杂,除非极端场景否则不推荐使用。
1.6 缓存的两类使用方法
通路缓存
客户程序只和缓存交互不和后端数据源交互,缓存自己处理缓存相关的逻辑(缓存命中读取,初次回源读取等)。
Nginx和CDN都属于通路缓存使用方法。
旁路缓存
客户程序既和缓存交互又和后端数据源交互,客户程序自己处理缓存相关的逻辑(缓存命中读取,初次回源读取等)。
通常我们程序中使用缓存的方式都是旁路缓存。
1.7 使用缓存过程中的问题
错误的使用场景:
数据频繁修改:读写比过低,无法发挥缓存多次读的优势。
没有热点的数据:没有热点也就没有命中率,缓存意义也不大。
数据不一致问题:缓存数据和源数据不一致,见(缓存失效策略)
缓存雪崩:缓存出现问题造成后端数据源压力激增,由后到前分布宕机最终造成系统整体不可用。实际工作中体恒缓存服务器高可用,避免此类场景发生。
缓存穿透:访问数据源中没有的数据,致使缓存无法存储数据每次请求都穿透缓存直达数据源。可以将null数据放入缓存,避免这种情况。
缓存预热:缓存重启或新增缓存节点,需要一定时间让数据放入的缓存中。实际工作中,我们可以将某些固定的数据(网站目录,产品分类,地域信息等)主动放入到缓存,无需等待用户请求被动输入。
1.8 缓存失效策略
缓存失效是为了解决数据源变化而缓存数据不变造成数据不一致问题。
到期后失效:设置缓存的到期时间,到期后失效
优点:简单
缺点:一定时间内数据不同步
源数据更新后失效:在源数据更新的时候删除缓存
优点:数据时效性更好
缺点:增加了更新数据源的业务逻辑
日常工作中我们可以根据数据不一致的容忍度,数据源更新的频率选择合适的失效策略。
1.9 缓存淘汰算法
缓存空间不能无限递增,当达到空间限制后,我们需要将旧有的缓存清理,为新缓存腾出空间,主要有三种淘汰策略。
FIFO:先进先出,最公平,但是也最不符合实际使用的场景。
LFU:最少使用频率,关注使用次数,淘汰使用次数少的。假设使用频率多的数据被再次使用的概率大。
LRU:最近最少使用,关注使用时间,淘汰长时间未使用的,假设刚刚使用的数据被再次使用的概率大。
LFU和LRU基于不同的假设场景,现实中两种场景都可能存在,但LRU时效性更好,更符合热点数据特点。实践中应用更多。
2. 异步系统和消息队列
2.1 定义
同步调用:线程阻塞,调用方和被调用方采用同步调用的模式,调用方线程需要等待被调用方方法完成并返回后才能继续执行后续操作。
异步调用:线程不阻塞,调用方和被调用方采用异步调用的模式,调用方线程无需等待被调用方方法完成,直接返回执行后续操作。
注意里面说的异步调用不是通过调用方新建线程的方式实现,而是通过调用方向消息队列发送消息的方式实现。
异步调用后回调:异步调用后,被调用方通过回调的方式通知调用方操作完成。
回调可以调用掉调用callback函数,也可以通过发送消息的方式。
2.2 消息队列的角色
生产者:消息发送方
消费者:消息处理方
消息队列:从生产者接收消息,发送给消费者处理。
2.3 消息队列的模式
点对点模式:一个消息只能被一个消费者处理一次。
发布订阅模式:一个消息可以被任意消费者订阅,订阅消息的消费都可以消费消息一次。
2.4 消息队列的使用场景及好处
异步:实现异步处理,提升系统性能
解耦:
业务解耦:生产者无需关心消息由谁处理,消费者也无需关心消息由谁发送,他们只通过消息驱动业务流程。(EDA)
调用方式解耦:方法调用方和被调用方无需在定义方法签名,出入参、返回异常等诸多耦合项,通过定义简单的消息来交互。
失败隔离和自我恢复:生产者和消费者相互解耦,不相互依赖,一台消费者服务器异常不会影响系统业务。
消峰:在流量洪峰到来时,将请求缓冲于消息队列中,由后端消费者延时处理,显著提升系统写数据性能,保证系统在大流量冲击下不被压垮。
2.5 实际中应用
对于日常项目,SOA和微服务架构比较常见,即使系统整体没有较高的性能要求,也建议使用消息队列作为子系统之间或各个服务之间解耦的工具,系统间通过消息通知的方式驱动业务流转,比直接调用接口有更好的扩展性和灵活性。
3. 负载均衡架构
3.1 作用
负载均衡通常位于服务器集群(通常服务器无状态)之前,将请求根据相应的规则分发到不同的服务器上。
3.2 关键点
转发方式:以什么方式和技术手段将前端请求转发到后端服务器
路由算法:转发时选择后端服务器的规则算法
3.3 转发方式
3.3.1 HTTP重定向负载均衡
负载均衡服务器只返回所有请求的相应为302,浏览器会重定向到相应的地址。
优点:
实现简单
负载均衡服务器不承接实际网络请求,负载较低。
缺点:
客户端每次需要两次HTTP请求,效率不高
后端服务器必须向公网暴露,容易受到攻击
3.3.2 DNS负载均衡
缺点:
后端服务器必须向公网暴露,容易受到攻击
DNS解析地址是被缓存的,后端服务器失效时,无法及时更新DNS配置
适用场景:
因为以上两点通常作为两级负载均衡的第一级,DNS做数据中心维度的一级负载均衡,DNS解析的IP地址指向某个数据中心,后续交由数据中心的负载均衡继续处理。
3.3.3 反向代理负载均衡
使用类似Nginx等反向代理服务器做负载均衡服务
七层负载均衡
缺点:
Http协议比较中,协议包位于最内侧,解析性能不高。
负载均衡全程代理用户整个http请求,高并发的情况可以成为瓶颈。(Nginx适用非阻塞IO,性能相对好点)
适用场景:
通常中小规模网站适用,用户访问量不是很大且后端应用服务器集群规模很小(几十台左右)
3.3.4 IP层负载均衡
三层负载均衡
通过修改用户源IP和目标IP实现负载均衡
优点:
IP协议包在HTTP外层,解析性能更好
缺点:
用户的请求和相应都会经过负载均衡服务器,在响应包比较大的情况下,响应出口带宽可能成为瓶颈
3.3.5 数据链路层负载均衡
二层负载均衡
通过修改用户源IP的Mac地址实现负载均衡,IP地址不变,用户和目标机器的TCP链接畅通,目标机器响应信息可以直接返回给用户,无需经过负载均衡服务器。
3.3.6 实际使用场景
大型网站通常多级负载均衡方式,使用DNS(GeoDNS)作为区分数据中心的第一级负载均衡,数据中心内使用IP和数据链路层负载均衡作为二级负载均衡,LVS同时支持IP和数据链路层负载均衡,也是首选工具。
3.4 负载均衡算法
轮询:请求被依次发送到每台服务器,使用与集群内机器配置相同的场景
加权轮询:集群内服务器配置不同内可以使用,高配机器权重高。
随机:最简单方案,访问随机性较好。
最少链接:需要维护连接记录
源地址散列:可以保证统一请求总是发送到统一目标服务器。可以实现会话黏连
实际场景中轮询和随机都比较常用
3.5 集群场景下Session管理
集群内服务器应该无状态,但是Web容器需要存储Session信息,这样就无法做到无状态,通常有以下解决方案。
3.5.1 Session复制
多台应用服务器同步复制Session信息,保证每台服务器都有所有session,访问任意一台服务器均可。
缺点:
每一台服务器都有集群中所有session数据,没有起到多机分担访问请求和数据存储的优势。
3.5.2 Session绑定
通过负载均衡源地址散列实现,相同IP的请求永远发送到同一台机器。
缺点:
添加或删除后端服务器节点都会造成请求地址的大规模变化(余数哈希的通病),指示用户找不到session而登出,影响用户体验。
除了平时扩容缩容场景,版本发布时候也会暂时删除服务器节点避免影响用户,但是在此场景下,无法实现。
3.5.3 Cookie记录Session
类似于JWT等解决方案,使用Cookie存储Session信息,cookie存储于浏览器端而不是后端。
缺点:
Cookie可能增加带宽负载
Cookie存储于客户端,有安全问题
Cookie客户端禁用问题
令牌延期、失效问题
3.5.4 Session服务器
将session存储于分布式缓存中(Redis),应用服务器依然是无状态的。
此种方案是通常最常用的解决方案
4. 分布式数据库
4.1 主从复制模式
客户端程序向主库写入数据,从从库读取数据,从库冲主库同步数据。
优点:
分摊读数据负载:读写分离,多个读取数据源可以分摊数据读取压力
专机专用:BI读一个从库,后台管理员应用读一个从库,前台用户应用读一个从库,按照应用划分使用,性能上互不影响。
提供备份能力:实时复制主库数据,提供数据冗余。
从库高可用:多台从库避免单点,保证高可用
问题:
主从同步延时问题:
从库复制延时,造成写入后立即读取数据不可见。通常解决方案是在数据强一致场景下不从从库读取而直接从主库读取。
主库高可用问题
因为只有主库可以承载写入服务,当主库宕机的时候,存储系统整体不可用。所以需要主主复制模式,提供主库高可用。
4.2 主主复制模式
正常情况下客户端向主库A写入数据,主库B实时从主库A复制数据。一旦主库A异常,客户端开始向主库B写入数据。我们人工修复主库A后,主库A开始从B复制数据,此时主库A成为了主库B的备机,等待下一次故障切换。
实际工作中通常使用主主模式,既提升读数据性能,又保证了主库的高可用。
4.3 复制使用过程中注意事项:
主主复制两个数据库不能并发写入
并发写入会产生数据冲突,例如表的主键和唯一索引等,在并发写入的情况下,有插入同一个值的可能性,违反了数据约束。
复制这种技术手段只能增加了并发读的能力,无法增加并发写入和存储能力
写入性能没有提升,存储能力也没有提升,因为写入的主库依然只能有一个。
更新表结构会造成巨大的同步延时
通常我们在修改表结构的前先关闭复制功能,手动修改主库和从库的表结构后打开复制功能。
主从数据不一致问题
主库突然宕机未同步数据会造成主从(主主)数据库数据不一致。是否能从根本上解决需要关注最新动态。
5. 总结
本文介绍了分布式缓存,分布式数据库,消息队列及负载均衡,后续继续介绍其他内容。
版权声明: 本文为 InfoQ 作者【Jerry Tse】的原创文章。
原文链接:【http://xie.infoq.cn/article/9c5f80e5fe22db37ed38f8b52】。文章转载请联系作者。
评论