架构师训练营第五周课程笔记及心得

用户头像
Airs
关注
发布于: 2020 年 10 月 25 日

分布式缓存架构:架构原理与使用中的注意事项



缓存:



存储在计算机上的一个原始数的复制集,以便于访问缓存是介于数据访问者和数据源之间的一种高速存储,当数据需要多次读取的时候用于加快读取的速度



缓存(Cache)和缓冲(Buffer)的的区别:



缓存和缓冲都是一种高速存储,处于数据源与访问者中间的缓存:是为了解决多次访问数据的时候数据处理慢,而在访问者和数据源直接添加的介质缓冲:是为了写入和读取低速设备时不让io影响访问者而使用的介质



缓存无处不在,种类繁多:



CPU,数据库,操作系统,JVM编译,CDN,代理,反向代理,前端,应用程序,分布式对象缓存等等



各介质访问时间估算:

缓存,在数据存储与内存等高速介质中时,使用的存储结构,数据结构:



即使时在高速的存储介质中,合理的数据存储结构也能让数据读取写入的过程更加快速缓存就是要快,如,hash表就是快,hash表时key-value的存储结构,并且搜索处理的时间复杂度是O1,非常快速,通常我们使用的如商品信息等就是使用hash表的形式存储于内存中。hash表实质是数组,一段连续存储的内容,在内存中是一段连续的内容,通过数组下标,可以计算出来在内存中的地址,通过确定内存中存储的值的头地址,以及偏移量,来快速获取一段内存中的数据,这是hash表能快速访问的物理基础,hash表通过解决确定内存中地址位置的问题来快速定位数据在内存中的位置



缓存的关键指标:



缓存命中率:



  1. 缓存是否有效,依赖于能多少次重用同一个缓存来响应业务请求,这个度量指标被称作缓存命中率,即,不同的业务来获取缓存时是否多次访问了相同的一个缓存数据,同一个缓存被重复访问次数越多,这个缓存内的数据被缓存化之后的价值越高



缓存命中率的指标:



  1. 缓存键集合的大小:缓存中每个对象使用缓存键进行识别即Key,定位一个对象的唯一方式就是对缓存键执行精确匹配,缓存键空间就是应用能狗生成的所有键的数量,键的数量越多,从命中率计算方式中可知,会使得命中率降低;尽可能的减少缓存键的数量,来提高缓存的命中率,使放入缓存中的数据更加有价值,也使得有限的内存能更好的利用起来。

  2. 缓存可使用内存空间的大小:缓存可使用的内存空间大小直接决定了缓存对象的平均大小和缓存对象的数量。因为缓存一般是高速的存储,高速存储都是有限的,昂贵的,想要缓存更多的对象,在一定的空间中,就会需要用到删除,替换缓存的操作,像这样缓存的变动会使得之前的缓存无法被访问到,命中率就下降了,物理上,在一定空间中缓存的对象越多,缓存命中率越高。

  3. 缓存对象生存时间:也被称为TTL(Time To Live),给缓存对象赋予生存时间,可以更有效的利用缓存的空间,让一个经常不被访问的数据存在有限的缓存空间中,会使得缓存命中率的下降,反而言之就是剔除一些访问频率低的数据,让访问频率更高的数据存储在缓存中,可以提高缓存的命中率。



可能是老师阐述的角度不同(从数据的角度出发),说数据留在缓存中的时间越长命中率就越高,从缓存整体的角度来说就是,访问频率更高的数据(即有效的数据)存储在缓存中,可以提高缓存的命中率



缓存的常见实现形式:



  1. 代理缓存:代理服务器进行的缓存服务,部署在用户一端的服务,一般不考虑在服务端的系统架构中

  2. 反向代理缓存:顾名思义,与代理缓存相反,是在服务端的缓存

  3. 多层反向代理缓存:在每一层服务端前都可以做一层缓存服务,可以在所有请求之前都进行反向代理服务,通常是基于http协议url调用的

  4. 内容分发网络(CDN):在客户端访问数据中心,访问内容服务时,可以直接访问数据中心获取数据,也可以通过内容分发网络服务器来访问,当内容分发网络服务中没有需要的内容时,再访问数据中心,进行数据访问,也是起到了缓存的作用和功能。一般是对一些静态的内容进行内容分发网络的缓存,因为动态的内容数据变化频繁,容易导致数据不一致,所以一般使用内容分发网络来缓存静态内容。作为缓存,即使是静态内容如果CDN中没有,也可以访问数据中心获取。CDN缓存加快了用户的访问速度,也减少了数据中心的处理压力

  5. 内容分发网络同时配置静态内容和动态内容:既然作为一种缓存,也可以同时将动态内容和静态内容同时部署到CDN上,这时所有的请求都发到CDN上,CDN根据动态和静态内容访问的特点,将动态内容直接转发到后端云服务器等处理动态请求,但是不对返回做缓存;将静态内容转发到静态内容存储的云端服务器,直接返回并做缓存处理



通读缓存(read-through):



  1. 上面所提到的各种缓存都称为通读缓存

  2. 通读缓存给客户端返回缓存资源,并再请求未命中缓存是获取实际数据

  3. 客户端链接的是通读缓存而不是生成响应的原始服务器



旁路缓存(cache-aside):



  1. 对象缓存是一种旁路缓存,旁路缓存通常是一个独立了的键值对(key-value)存储

  2. 旁路缓存需要客户端单独访问并查询结果,不会像通读缓存一样直接转发请求到后端进行请求处理,不代理数据源的访问,如果缓存中存在需要数据则返回,如果没有需要的数据,则客户端需要自己连接主数据源来组装对象,并将其保存在对象缓存中以便再次使用



应用程序中使用的缓存一般使用的是对象缓存,即key-value形式存储的旁路缓存



本地对象缓存:



  1. 对象直接缓存在本地应用程序中

  2. 对象存储在共享内存,同一台机器的多个进程可以访问他们

  3. 缓存服务其作为独立应用和应用程序部署在同一个服务器上



例:JBoss本地Cache



远程分布式对象缓存:目前常用的分布式缓存组件形式,缓存服务单独部署在一个或多个缓存服务器上。远程分布式缓存有利于伸缩



例:Memcached缓存



一致性hash算法:



像memcached一样使用余数hash算法时,由于是通过取模来实现的,在扩容,增加节点等问题的时候会出现取模的数两错误,导致缓存读写失败为了解决余数hash算法存在的问题,目前经常使用的分布式对象缓存的hash算法为一致性hash算法



一致性hash算法实现:



  1. 建立一致性hash环:环为0到2的32次方减1的数字首尾相连组成,起始位置0,终点位置是2的32次方减1(即正整数数字范围),作为hashcode的数字范围

  2. 将服务器节点进行hash计算,将得到的hash值放到hash环上

  3. 需要进行路由计算的时候,将key的值进行hash计算,将得到的hash值放到hash环上,然后顺时针查找离这个hash值最近的节点



一致性hash的问题:



  1. 使用一致性hash算法的缓存,在增加节点的时候也是略有影响的,增加的节点到上一个节点的hash值的区间内的key,原本查找的是新增节点下一个几点上的数据,但是在新增节点上是不存在的。旁路缓存在使用过程中会将找不到的key重新写入,一段时间之后这些数据又会恢复,虽然有影响但是不影响整体的使用。

  2. hash值相当于一个随机数,这时,节点分布时会使得两个相邻节点可能距离较近,或者没有距离,这时会出现hash值分布不均,也使得key的分布不均,导致各个节点访问压力不均,当没有距离时,两个相邻节点,有一台是没有key的,也就是相当于不工作的,没有起到缓存作用的,出现了资源的浪费

  3. 当使用扩容来减少原先服务器压力时,只能减少新增节点在hash环上后续节点的压力,hash环上前面节点的压力还是没有减少的。这样扩容的收益就变的非常低了。



基于虚拟节点的一致性hash算法:



现在使用的一致性hash环大多都不是直接将节点的hash值放在hash换上而是通过虚拟若干个节点,将节点,再将虚拟节点放到hash环上,通过大量的虚拟节点,使得节点在hash换上分布的更加均匀,使得上述问题产生的影响变小。



技术栈各个层次的缓存的效果:



下图中的百分比只是一个示意,层次越多,越靠近后端的缓存效率会越低;所以,尽量让缓存存在于请求的前部,来减少后部的压力,提升性能。

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



  1. 缓存数据来自于内存,比磁盘上的数据有更快的访问速度。

  2. 缓存的数据是数据的最终结果形态,可以直接使用数据,不需要过多的计算,减少了cpu的资源消耗。

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



使用缓存的优势、特点:



  1. 技术简单

  2. 性能提升显著

  3. 应用场景多



合理的使用缓存:



缓存虽然可以很好的提升应用的性能,但是不合理的使用也会导致应用性能不增反降,还会可能变成应用的累赘。事件中缓存滥用,使得应用过分依赖缓存、不合理的数据访问增多等特点。



不适合使用缓存的数据以及缓存可能带来的问题:



  1. 频繁修改的数据:由于数据修改频繁,缓存的存在时间段,就会让缓存命中率降低,使得这个数据在缓存中的意义不大,频繁的修改,更可能使得数据频繁需要在缓存中更新,一方面增加数据库的压力,一方面增加缓存的压力,一般来说,数据的读写比在2:1以上,缓存才有意义

  2. 没有热点的访问:缓存是高速存储,一般来说是内存,内存的资源是宝贵的,并不能将所有数据都缓存起来,如果应用系统访问数据没有热点,不遵循二八定律,即大部分数据访问,不是集中在小部分数据上,那么缓存是没有意义的,因为大部分数据还没有被再次访问就已经被挤出缓存了。

  3. 数据的不一致与脏读:缓存中的数据一般是有失效时间的,一旦超过失效时间,就要从数据库中冲i性能加载,因此,这些数据要能容忍一定时间内的数据不一致。可以在数据更新时就删除或失效缓存中的数据,虽然稳妥,但是也会使得缓存的开销增大

  4. 缓存雪崩:缓存是为了提高数据读取性能的,但是当系统中长时间使用缓存后,大部分的数据请求都由缓存来承担了,后端数据库这个时候很可能没有跟上访问量的增长,这时,当缓存崩溃,大量的数据访问请求被送到数据库,可能就引发数据库宕机,使得整个系统瘫痪,并且雪崩可能不是单纯的重启缓存和数据库服务就能解决的。

  5. 缓存预热:缓存中存放的是热点数据,热点数据是用LRU算法得出的一些数据,LRU算法的一个特点就是,获得最近,最久,没有使用的数据是要经过一段时间的运行才能得到的,这个过程需要较长时间,在这段时间内,系统的性能和数据库的负载可能就并不会非常好。这就可以在缓存启动时候就直接将一些肯定是热点的数据认为的预加载到缓存中,这样就省去了一部分LRU算法优化缓存数据的时间,这个过程就叫预热

  6. 缓存穿透:如果不恰当的业务或者恶意攻击持续高并发的请求不存在的数据,应为缓存没有加载这个数据,这时所有的请求就会到数据库上,会对数据造成压力甚至雪崩,这时可以将不存在数据(不存在的key,或者value为null的数据)也放入缓存,并且设定一个较短的失效时间。



Redis VS Memcached:



  1. Redis支持复杂的数据结构

  2. Redis支持多路复用异步I/O

  3. Redis支持主从复制高可用

  4. Redis原生集群与share nothing集群模式



Redis集群原理:



  1. Redis集群预先分配好16384个桶(slot),当需要在Redis中放置一个Key-Value时,更具CRC16(key)mod16384的值(对key使用CRC16算法并取模),决定将一个key放到哪个桶中。

  2. redis-cluster把所有的物理节点映射到[0-16384]slot上(不一定是平均分配,从表述上容易产生歧义,应该是把所有的slot和所有物理节点建立映射关系,即把16384个slot分配到不同的物理节点上,slot更像是索引并不是对应单个key,slot是用来区分物理节点的),cluster负责维护slot与服务器的映射关系。

  3. 客户端与redis节点直连,客户端不需要链接集群所有的节点,链接集群中任何一个可用的节点即可。

  4. 所有的redis节点彼此互联



消息队列:如何避免系统故障传递?



缓存是优化多次读性能的,消息队列是用来优化写性能的



同步调用VS异步调用:



同步调用是客户端阻塞式的调用,客户端在请求的过程中是阻塞式的需要等待返回的才能结束一次事务;异步调用是不需要等待完全的返回的,只要发送完成消息,当前事务就结束返回了。



异步调用存在的问题:



一般通过消息队列来实现异步调用,可以通过队列来实现前后端的分离操作,但是分离的同时,客户端也无法之后最后的执行结果是什么了,这是消息队列的一个问题,这时可以通过一个有回调的异步调用,来实现,消息队列会回调客户端的服务器给前端的服务器发送一个消息处理结果,然后再由客户端的服务来判断如何处理异步调用失败或成功的结果



异步调用优点:



  1. 可以在多次调用中减少每次调用的阻塞时间,客户端的性能可以得到提升,实现异步处理,提升处理性能。

  2. 使用消息队列还能让应用有更好的伸缩性。

  3. 削峰填谷

  4. 失败隔离和自我修复,消息处理逻辑有问题时,不会将问题传递给生产者,减少了生产者对前端用户的影响。

  5. 解耦



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



生产者-消息队列-消息队列



两种消息队列模型:



  1. 点对点模型:多个消息队列,多个消息消费者,放到消息队列中,每条消息只消费一次,消费完成删除消息

  2. 发布订阅模型:生产者和消费者都是多个,但是消息并不是消费一次,不同的消费者根据自己的需要来消费一次或多次消息。发布订阅模型也可以理解为不同的消费形式,对应不同的消费队列,消费者通过各自的需求来消费同一条消息。



利用消息队列,实现事件驱动架构EDA:



通过事件,来驱动服务的提供过程,整个过程可以通过消息队列实现高性能,低耦合的形式。事件驱动架构,可以让耦合的表面积更少:

负载均衡架构



什么是负载均衡:



针对分布式服务器集群的请求,通过负载均衡服务器,将客户端的请求,通过负载均衡服务器,均匀的分布到各个分布式服务器集群中,使各个服务器的压力得到均衡。



请求是如何分发的:



  1. Http重定向负载均衡:请求先到http重定向负载均衡服务器,然后http负载均衡服务器通过路由计算,重定向一个地址返回给客户端,客户端获取重定向地址后,再访问真是的后端服务器,http重定向负载均衡服务器自己并不实际处理请求,只是通过路由计算返回一个重定向地址。

  2. DNS负载均衡:通过域名进行访问请求时,先要通过DNS服务器进行域名解析,解析出实际IP地址后再进行请求访问,DNS负载均衡就是在域名解析时,通过解析出不同的IP地址来将请求负载均衡到不同的服务器上。

  3. 反向代理负载均衡:反向代理服务器,可以做缓存也可以用于请求分发,这时可以做一个负载均衡(7层负载)

  4. IP负载均衡:负载均衡服务器基于TCP/IP层的协议,将TCP请求的源和目标地址修改后直接向下转发,并建立映射表来处理响应的请求。(4层负载)

  5. 数据链路层负载均衡:比IP层还要第一层的负载均衡,通过一样的虚拟ip地址,只修改链路层的Mac地址将请求转发到后端服务,并且,在响应时,由于IP地址没有被修改过,使得负载均衡服务器不需要处理响应请求,而是后端服务器直接会将数据响应给客户端的源IP。(三角模式)



负载均衡算法:



  1. 轮询:依次分发到后端每台服务器上

  2. 加权轮询:根据后端服务器的配置,将请求按照权重分配到后端服务器上

  3. 随机:请求随机分配到后端服务器,好的随机数本身是均衡的,并且也可以很方便的使用加权来优化随机

  4. 最少链接:记录每个应用服务器正在处理的连接数(请求数),将新的请求分发到最少链接的服务器上,最符合负载均衡定义的算法

  5. 源地址散列:根据请求来源地址IP进行Hash计算,得到应用服务器,可以保证同一个源的会话始终保持在同一个后端服务器上,实现会话保持(会话粘滞;并不常见)



应用请求的会话管理应该怎么做:



  1. session复制:每个应用服务器构建一个session,当一台服务器上的session发生改变的时候,同步复制到其他服务器上,保证用户请求到每一台服务器上都会有相同的session上下文

  2. session绑定:使用源地址散列,来自同一个ip地址的请求,会分发到同一台服务器上,只有这一台服务器保存,维护了对应ip地址请求的session上下文

  3. 利用cookie记录session:请求中都是有cookie信息的,通过cookie携带session信息,并在客户端和服务器之间不停的交互,在交互过程中维护session的上下文信息

  4. session服务器:应用不记录session,通过session服务器(集群)来统一进行session管理,当session这种带状态的信息,被独立统一管理后,应用就变成了无状态服务器,share nothing的服务器,可以有很好的伸缩性



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

Airs

关注

Emmmmmmm 2018.02.28 加入

Emmmmmmm

评论

发布
暂无评论
架构师训练营第五周课程笔记及心得