写点什么

架构师训练营 1 期第 5 周:技术选型(一) - 总结

用户头像
piercebn
关注
发布于: 2020 年 10 月 24 日

本文主要介绍分布式缓存,消息队列,负载均衡的功能、架构以及相关实现方式,以便我们在实际应用场景中进行灵活选择和应用。

一、分布式缓存

架构原理和概念


缓存是介于数据访问者和数据源之间的一种高速存储,当数据需要多次读取的时候,用于加快读取的速度。


常见缓存应用场景如下:

  • CPU 缓存:多级缓存,解决 CPU 高速和内存访问的低速不匹配问题

  • 操作系统缓存:访问文件系统读写数据可能会使用缓存

  • 数据库缓存

  • JVM 编译缓存:字节码指令通过编译缓存

  • CDN 缓存

  • 代理与反向代理缓存

  • 前端缓存

  • 应用程序缓存

  • 分布式对象缓存


缓存数据存储的常用数据结构组织方式:Hash 表

  • 要求数据写入后可以快速的读取出来,Hash 表时间复杂度为 O(1)

  • 支持 key-value 数据格式,通过 key 来读写缓存数据,如缓存商品信息(商品 id 为 key,商品数据内容是 value)、用户数据(用户 id 为 key,用户信息是 value)、HTML 页面(页面 URL 路径为 key,HTML 内容为 value)

  • Hash 表用数组实现,通过地址和偏移量直接获取数据

  • 通过计算 key 的 hashcode(四字节正整数),然后对计算的 hashcode 进行余数 hash 算法(对 Hash 表数组长度取余),算出 Hash 表的存储索引,就可以通过索引进行数据的读写了


缓存关键指标:缓存命中率

  • 缓存是否有效依赖于能多少次重用同一个缓存响应业务请求,这个度量指标被称作缓存命中 率。 

  • 如果查询一个缓存,十次查询九次能够得到正确结果,那么它的命中率是 90%。


影响缓存命中率的主要指标

  • 缓存键集合大小:生成的唯一键越多,重用的机会越小。一定要想办法减少可能的 缓存键数量。键数量越少,缓存的效率越高

  • 缓存可使用内存空间:缓存可使用内存空间直接决定了缓存对象的平均大小和缓存对象数量。缓存键空间,在内存装载的比率,决定缓存命中率的高低

  • 缓存对象生存时间:称为 TTL( Time To Live ),对象缓存的时间越长, 缓存对象被重用的可能性就越高。数据有效时间,由数据一致性要求来决定,长期有效的数据更适合缓存

缓存实现方式

通读缓存(read-through):代理访问资源,客户端只知道通路缓存,不知道服务,通路缓存控制访问逻辑

  • 代理缓存:代理用户向外请求

  • 反向代理缓存:代理数据中心接收请求

  • 多层反向代理缓存:在请求资源的地方都可以加一层反向代理,基于 HTTP URL 协议,服务器端数据中心机房的缓存服务

  • 内容分发网络(CDN):通过域名解析找到 CDN 静态资源,离用户就近地方的运营商机房提供缓存服务

  • CDN 同时配置静态文件和动态内容:所有请求都通过 CDN 提供,如 AWS 的 CloudFront CDN


旁读缓存(cache-aside):客户端需要知道旁路缓存和服务,客户端控制访问逻辑

  • 浏览器对象缓存:浏览器本地的 WebStorage

  • 本地对象缓存:缓存数据和应用程序部署在一起

  • 缓存在应用程序内存中

  • 缓存在共享内存中,同台机器多个进程可以访问

  • 缓存在独立缓存服务应用中,与应用部署在同一服务器上

  • 本地对象缓存构建分布式集群

  • 分布式集群中每台服务器同时部署应用服务和缓存服务

  • 缓存服务自己负责集群中数据的更新同步,保证每个缓存服务数据相同

  • 此种方式很快被淘汰,主要原因是:

  • 每台服务器缓存的数据一样,有限的内存缓存同样的数据,造成资源浪费

  • 每台服务器上缓存服务进行更新同步时,需要通知其他服务器上的缓存服务进行数据更新,造成网络带宽资源被同步更新占据,浪费了资源

  • 远程分布式对象缓存

  • 应用程序部署在独立的服务器上,缓存也部署在独立的服务器上,多个缓存服务器构成缓存服务器集群,共同对外部的应用程序服务器提供缓存服务

  • 应用服务通过网络通讯远程 RPC 调用的方式访问分布式对象缓存集群中的某一台服务器读写数据获取想要的数据

  • 缓存服务器独立部署,不占用应用服务器内部资源

  • 缓存服务器独立部署,有利于线性伸缩

  • 互联网系统中主要使用的架构方案,常用的有:Memcached,Redis

Memcached 分布式对象缓存实现

Memcached 分布式对象缓存

  • 应用通过 memcached 客户端连接服务器,添加服务器集群

  • 写入数据时通过 key 计算服务器的 ip 地址,将数据写入到这个服务器里去

  • 读取数据时通过 key 计算服务器的 ip 地址,和这个服务器通信,通过服务器返回想要的数据

  • Memcached 客户端通过 key 映射缓存服务器,达到缓存复用的目的,集群中每台缓存服务中缓存的数据是不一样的,服务器间不共享信息,互相不感知对方

  • 服务器集群越大,它可以存储的数据也就越多,缓存命中率也就越高,从而实现线性伸缩,这种架构方案也被称为 Share-Nothing 架构

Memcached 分布式缓存客户端访问模型

  • 客户端访问控制:Memcached API — 路由算法路由列表 — 通讯模块

  • 路由算法如何实现?类似之前讲的 Hash 表,每个 key 都有 hashcode,通过 hashcode 对路由列表数量取模,获取余数,定位路由列表节点位置

  • 问题:使用余数 hash 算法,当路由列表增加时,余数改变了,会导致同一个 key 计算的服务器和之前的不一样,导致增加服务器后大部分数据缓存不命中,整个缓存集群失效,这些数据请求会都到达数据库,导致数据库负载压力急剧升高,可能超过数据库最大承受能力,导致数据库崩溃,应用程序失去连接和响应,应用程序也崩溃,整个系统也就崩溃了

分布式对象缓存的一致性 Hash 算法

  • 通过一致性 hash 环进行服务器的选择

  • 建立一致性 hash 环,从 0 到 2 的 32 次方-1 这样大的数字首位相连组成,也就是正整数 hashcode 的数字范围

  • 根据服务节点 Node0 名字字符串计算 hash 值,一定是在 0 到 2 的 32 次-1 数字范围之间,就可以把这个值放在环上,同样可以把 Node1,Node2 等通过计算 hash 值放到环上

  • 当我们要存取 Key-Value 时需要对服务器进行路由计算时,通过 key 计算 hash 值,直接把 key 的 hash 值放到环上,然后顺时针查找距离 key 值最新的服务器节点,离它最近的服务器就是路由选择的结果

  • Hash 环扩充节点时,可以解决增加服务器导致大量 key 不命中的问题,但仍会导致小段区间上的 key 不命中的问题,导致数据库负载压力的提高在一个可控的范围之内,过段时间会回复正常

  • 问题

  • 服务器 Node 在 hash 环上的分布是通过 hash 值计算的,hash 值实际上是一个随机数,会导致 Node0 和 Node2 很近,然后 Node1 很远,导致少量数据落到 Node2 上,Node2 访问负载压力非常低,大量数据落在 Node1 上,也就是所谓的 hash 访问负载不均衡,数据存储不均衡,访问并发量也不均衡

  • 基于虚拟节点的一致性 hash 算法:将物理节点打撒分布在环上,解决 hash 访问负载不均衡问题

  • 每个服务器虚拟成若干个虚拟节点,将节点虚拟成若干个虚拟节点,把虚拟节点放到环上,实践中每个 Node 对应 150 个虚拟节点,150 个虚拟节点就可能均匀的散在环上的各个位置,key 查找时就会近似均匀的访问到了所有的物理节点,每个物理节点的访问概率是差不多的

Redis 对比 Memcached

Redis 是在 Memcached 基础上进行优化开发出来的,它的特性和各种功能更强大一些

Redis 原生集群:基于 Hash Slot 算法

  • Redis 集群所有节点要共享桶的分布

  • Redis 通过桶分配 key,集群预先分配桶,桶分布在集群节点上,每次对 key 计算的时候,先算桶,对桶个数取模,决定了 key 放到哪个桶中(通过余数 hash 映射桶),然后桶对应哪个服务器,就需要去 Redis 的服务器上进行查找,客户端需要和服务器进行通讯,返回服务器和桶的映射关系,定位桶对应的服务器,进行读写操作

  • Redis 集群扩容时,增加新服务器时,集群之间进行重新桶的分配,每个服务器上会分一部分桶到新的服务器上;也可以实现类似一致性 hash 的效果,影响很小

  • key 通过余数 hash 映射到桶,并不是真实的服务器,真实的服务器是映射关系,映射关系调整的时候,可以进行平衡调整,每个服务器都可以调整一部分桶到新的服务器上,只有少部分的 key 找不到

  • 服务器之间它们互相要进行通信,它们都要彼此知道,桶在每台服务器上如何分配的,客户端可以连接任意一台服务器获得桶和服务器之间的映射关系

缓存的作用和使用方式

技术栈各层次的缓存

  • 尽量前置缓存数据,在前端返回数据

  • 缓存主要优化的是读性能

缓存为什么能显著提升性能

  • 缓存数据通常来自内存,比磁盘上的数据有更快的访问速度。

  • 缓存存储数据的最终结果形态,不需要中间计算,减少 CPU 资源的消耗。

  • 缓存降低数据库、磁盘、网络的负载压力,使这些 I/O 设备获得更好的响应特性。

合理使用缓存

  • 频繁修改的数据不适合缓存,一般说来,数据的读写比在 2:1 以上,缓存才有意义,我们大部分场景是一次写入,多次读取的,如微博,朋友圈,淘宝上发布的商品

  • 没有热点的访问不适合缓存,大部分数据的访问不是集中在小部分数据上,会导致缓存不被命中,频繁换出,缓存没有意义

  • 使用 LRU 算法(最近最久未用)删除非热点数据,访问的数据放到 LRU 队列尾部,未访问的数据保留在头部,尾部的数据总是最热的

  • 数据不一致与脏读,数据源的数据更新了,但缓存不知道,通过缓存返回的数据就是和数据源不一致的数据,会产生脏读;解决方法是给缓存设置失效时间,需要应用能够容忍一段时间的数据不一致(如卖家更新商品属性,需要明确提示卖家数据更新成功,生效需要一段时间);如果应用不能接受短暂的数据不一致(如商品价格),就需要采用另外一种策略,及时更新缓存,使缓存中的数据失效,这种方法数据的一致性会高点,带来的问题是增加了系统开销,同时还会存在缓存失效更新和数据的更新的事务不一致的问题

  • 缓存雪崩,不正确的路由算法导致大部分缓存失效,请求全部到达数据库,导致数据库负载超过它的承受能力,数据库崩溃,最后应用程序崩溃,系统也崩溃了;缓存服务器管理不善,大量服务器进程挂掉,缓存下线,也会导致数据库负载比较高,也会导致缓存雪崩

  • 缓存预热,应用程序启动时,可以提前把缓存数据加载好,这种缓存预加载手段叫缓存预热(warm up)

  • 缓存穿透,由于业务逻辑或恶意攻击,导致频繁访问数据库中不存在的数据,它在缓存中也就不会存在(如访问不存在的商品),导致每次访问检查数据库,数据库压力增大,最后导致系统雪崩;解决缓存穿透的方式是,如果访问一个不存在的 key,那么这个 key 也要写入到缓存中,value 存成 null 值,应用访问缓存时,不存在的商品也会返回 null 值,这样就不会访问到数据库上了

二、消息队列


同步调用 VS 异步调用

  • 同步调用:同步等待返回结果,对个耗时的同步调用,阻塞应用线程

  • 异步调用:直接返回,不等待结果,结果可通过回调方式异步返回,对次异步调用不阻塞应用线程


消息队列构建异步调用架构

  • 由消息生产者,消息队列,消息消费者组成

  • 点对点模型:每个消息只被消费处理一次,当消息队列中消息较多时,可以通过增加更多的消费者从消息队列获取消息进行处理,更容易的进行线性的扩容和伸缩

  • 发布订阅模型:生产者创建消息,消息可以通过主题(Topic)被订阅,消息一次生产,多次消费,可实现事件驱动架构


消息队列的好处

  • 实现异步处理,提升写的处理性能

  • 更好的伸缩性,消费端容易扩容和伸缩

  • 削峰填谷:高峰期消息堆积在消息队列中,消费端按照自己处理能力来处理消息

  • 失败隔离和自我修复:生产者和消费者互相不受对方失败影响,隔离了失败,可以分别进行自我修复

  • 解耦:发布者和消费者之间调用解耦


时间驱动架构 EDA

  • 生产者发布事件,消费者通过消息队列获取这个事件,然后去进行处理,当事件产生到达的时候,因为事件而驱动后续消费者程序的执行。低耦合(仅消息格式依赖),异步处理,易于功能扩展。


主要 MQ 产品对比

  • RabbitMQ 的主要特点是性能好,社区活跃,但是 RabbitMQ 用 Erlang 开发,对不熟悉 Erlang 的同学而言不便于二次开发和维护。

  • ActiveMQ 影响比较广泛,可以跨平台,使用 Java 开发,对 Java 比较友好。

  • RocketMQ 是阿里推出的一个开源产品,也是使用 Java 开发,性能比较好,可靠性也 比较高。

  • Kafka ,LinkedIn 出品的,Scala 开发,专门针对分布式场景进行了优化,因此分布 式的伸缩性会比较好。

三、负载均衡

基于 HTTP 的 Web 应用服务器如何实现集群:HTTP 负载均衡架构

  • 用户发送的 HTTP 请求,发送到负载均衡服务器,通过负载均衡服务器将请求转发给应用服务器集群中的某一台服务器,这些应用服务器部署着相同的程序,提供着相同的处理能力,这些服务器构成集群,当有高并发的用户请求访问到达负载均衡服务器时,它都可以将这些请求分发给不同的应用服务器,使这些应用服务器均匀的分摊这些用户访问的请求压力


HTTP 请求如何分发:请求如何到了负载均衡服务器被转发到其他应用服务器

  • HTTP 重定向负载均衡:实现简单,但性能低,安全性差

  • DNS 负载均衡:DNS 域名服务商提供配置多个 A 记录的负载均衡方式,配置更简单,但存在安全性问题

  • 大型互联网应用经常使用

  • 两层负载均衡,避免安全问题:DNS 负载均衡 — 应用服务器负载均衡 — 应用服务器集群

  • 反向代理负载均衡:在请求分发时实现负载均衡,配置简单,但效率较低,性能较差

  • 实践中比较常见,特别是规模比较小的应用系统(几台或十几台的规模)

  • 转发的是完整的 HTTP 请求,需要进行应用层协议转换,构建完整的应用层协议包,HTTP 协议包,才能向后进行转发

  • Nginx 反向代理服务器,一方面可以配置缓存实现反向代理,另一方面也可以配置负载均衡

  • IP 负载均衡:基于低层的数据包进行请求的转发,通过修改数据包 IP 地址进行转发,需要记录 IP 映射表。

  • 处理能力大,计算压力和性能压力小。

  • 响应数据包可能会很大,数据包量很大,IP 转换频繁,可能导致网络带宽不够用,响应瓶颈

  • 数据链路层负载均衡,主要记录网络服务器网卡 MAC 地址,可以不处理响应内容转换

  • 负载均衡服务器 IP 地址和目标应用服务虚拟 IP 地址一样,请求时改变 MAC 地址,响应时由于 IP 地址不变不需要修改直接返回请求端,不再经过负载均衡服务器,从而降低了负载均衡服务器的负载压力和网卡的带宽压力

  • 这样负载均衡方式也被称为三角模式

  • 大型互联网应用中最主要的负载均衡实现方式


负载均衡算法:每一次请求如何选择服务器进行分发的

  • 轮询

  • 加权轮询

  • 随机

  • 最少连接

  • 源地址散列,Hash 也是一种随机计算,可以实现同一个来源分发给同一个服务器,实现会话粘停滞


集群环境下 Session 管理

  • Session 复制:应用端 Session 复制,复制操作提升了应用服务器的负载压力,很难提高性能,很难进行大规模伸缩,很少使用

  • Session 绑定:源地址散列,绑定目标服务器,很少使用,无法实现高可用,服务器失效宕机(或应用重新发布),Session 会丢失

  • 利用 Cookie 记录 Session:不依赖应用服务器,但 Cookie 大小有限,有可能会被浏览器禁止

  • Session 服务器:共享 Session 服务器集群,应用服务器为无状态服务器,不共享任何信息,Share Nothing 分布式架构


发布于: 2020 年 10 月 24 日阅读数: 52
用户头像

piercebn

关注

还未添加个人签名 2019.07.24 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营 1 期第 5 周:技术选型(一) - 总结