应用服务器性能优化——分布式缓存

版权声明:转载请随意! https://blog.csdn.net/qq_41723615/article/details/89016128

应用服务器就是处理网站业务的服务器,网站的业务代码都部署在这里,是网站开发最复杂,变化最多的地方, 优化手段主要有缓存、集群、异步等。

一、分布式缓存

回顾网站架构演化历程,当网站遇到性能瓶颈时, 第一个想到的解决方案就是使用缓存。在整个网站应用中, 缓存几乎无所不在, 既存在于浏览器,也存在于应用服务器和数据库服务器; 既可以对数据缓存,也可以对文件缓存,还可以对页面片段缓存。合
理使用缓存,对网站性能优化意义重大。

网站性能优化第一定律: 优先考虑使用缓存优化性能。

1 . 缓存的基本原理

缓存指将数据存储在相对较高访问速度的存储介质中,以供系统处理。一方面缓存访问速度快, 可以减少数据访问的时间,另一方面如果缓存的数据是经过计算处理得到的,那么被缓存的数据无需重复计算即可直接使用, 因此缓存还起到减少计算时间的作用。

缓存的本质是一个内存 Hash 表,网站应用中,数据缓存以一对 Key Value 的形式存储在内存Hash 表中。Hash 表数据读写的时间复杂度为o ( 1 ) 。

计算KV 对中Key 的HashCode 对应的Hash 表索引, 可快速访问Hash 表中的数据。许多语言支持获得任意对象的HashCode , 可以把HashCode 理解为对象的唯一标示符, Java语言中Hashcode 方法包含在根对象Object 中,其返回值是一个Int, 然后通过Hashcode计算Hash 表的索引下标, 最简单的是余数法,使用Hash 表数组长度对Hashcode 求余,余数即为Hash 表索引, 使用该索引可直接访问得到Hash 表中存储的KV 对。Hash 表是软件开发中常用到的一种数据结构,其设计思想在很多场景下都可以应用。

缓存主要用来存放那些读写比很高、很少变化的数据, 女口商品的类自信息, 热门词的搜索列表信息,热门商品信息等。应用程序读取数据时,先到缓存中读取, 如果读取不到或数据已失效,再访问数据库, 并将数据写入缓存。

网站数据访问通常遵循二八定律,即80%的访问落在200/0的数据上, 因此利用Hash表和内存的高速访问特性, 将这20%的数据缓存起来,可很好地改善系统性能,提高数据读取速度,降低存储访问压力。

2. 合理使用缓存

使用缓存对提高系统性能有很多好处,但是不合理使用缓存非但不能提高系统的性能, 还会成为系统的累赘,甚至风险。实践中, 缓存滥用的情景屡见不鲜一一过分依赖低可用的缓存系统、不恰当地使用缓存的数据访问特性等。

频繁修改的数据:
如果缓存中保存的是频繁修改的数据,就会出现数据写入缓存后,应用还来不及读取缓存,数据就已失效的情形, 徒增系统负担。一般说来,数据的读写比在2: 1 以上,即写入一次缓存,在数据更新前至少读取两次, 缓存才有意义。实践中, 这个读写比通常非常高,比如新浪微博的热门微博,缓存以后可能会被读取数百万次。
没有热点的访问:
缓存使用内存作为存储,内存资源宝贵而有限, 不可能将所有数据都缓存起来,只能将最新访问的数据缓存起来,而将历史数据清理出缓存。如果应用系统访问数据没有热点,不遵循二八定律,即大部分数据访问并没有集中在小部分数据上, 那么缓存就没
有意义, 因为大部分数据还没有被再次访问就已经被挤出缓存了。
数据不一致与脏读:
一般会对缓存的数据设置失效时间,一旦超过失效时间, 就要从数据库中重新加载。因此应用要容忍一定时间的数据不一致,如卖家已经编辑了商品属性,但是需要过一段时间才能被买家看到。在互联网应用中,这种延迟通常是可以接受的, 但是具体应用仍需慎重对待。还有一种策略是数据更新时立即更新缓存, 不过这也会带来更多系统开销和事务一致性的问题。
缓存可用性:
缓存是为提高数据读取性能的, 缓存数据丢失或者缓存不可用不会影响到应用程序的处理一一它可以从数据库直接获取数据。但是随着业务的发展,缓存会承担大部分数据访问的压力, 数据库已经习惯了有缓存的日子, 所以当缓存服务崩溃时, 数据库会因为完全不能承受如此大的压力而宕机, 进而导致整个网站不可用。这种情况被称作缓存雪崩, 发生这种故障, 甚至不能简单地重启缓存服务器和数据库服务器来恢复网站访问。

实践中, 有的网站通过缓存热备等手段提高缓存可用性: 当某台缓存服务器宕机时,将缓存访问切换到热备服务器上。但是这种设计显然有违缓存的初衷, 缓存根本就不应该被当做一个可靠的数据源来使用。通过分布式缓存服务器集群, 将缓存数据分布到集群多台服务器上可在一定程度上改善缓存的可用性。当一台缓存服务器宕机的时候,只有部分缓存数据丢失, 重新从数
据库加载这部分数据不会对数据库产生很大影响。

产品在设计之初就需要一个明确的定位: 什么是产品要实现的功能, 什么不是产品提供的特性。在产品漫长的生命周期中, 会有形形色色的困难和诱惑来改变产品的发展方向, 左右摇摆、什么都想做的产品, 最后有可能成为一个失去生命力的四不像。

缓存预热
缓存中存放的是热点数据, 热点数据又是缓存系统利用LRU (最近最久未用算法)对不断访问的数据筛选淘汰出来的, 这个过程需要花费较长的时间。新启动的缓存系统如果没有任何数据, 在重建缓存数据的过程中, 系统的性能和数据库负载都不太好, 那么最好在缓存系统启动时就把热点数据加载好, 这个缓存预加载手段叫作缓存预热( warmup )。对于一些元数据如城市地名列表、类自信息, 可以在启动时加载数据库中全部数据到缓存进行预热。
缓存穿透
如果因为不恰当的业务、或者恶意攻击持续高并发地请求某个不存在的数据, 由于缓存没有保存该数据, 所有的请求都会落到数据库上, 会对数据库造成很大压力, 甚至崩溃。一个简单的对策是将不存在的数据也缓存起来(其value 值为null )。

3. 分布式缓存架构
分布式缓存指缓存部署在多个服务器组成的集群中,以集群方式提供缓存服务,其
架构方式有两种,一种是以JBoss Cache 为代表的需要更新同步的分布式缓存, 一种是以Memcached 为代表的不互相通信的分布式缓存。
JBoss Cache 的分布式缓存在集群中所有服务器中保存相同的缓存数据,当某台服务器有缓存数据更新的时候,会通知集群中其他机器更新缓存数据或清除缓存数据。JBoss Cache 通常将应用程序和缓存部署在同一台服务器上,应用程序可从本地快速获取缓存数据,但是这种方式带来的问题是缓存数据的数量受限于单一服务器的内存空间,而且当集群规模较大的时候,缓存更新信息需要同步到集群所有机器,其代价惊人。因而这种方案更多见于企业应用系统中,而很少在大型网站使用。

大型网站需要缓存的数据量一般都很庞大,可能会需要数TB 的内存做缓存,这时候就需要另一种分布式缓存,如图4.1 0 所示。Memcached 采用一种集中式的缓存集群管理,也被称作互不通信的分布式架构方式。缓存与应用分离部署, 缓存系统部署在一组专门
的服务器上,应用程序通过一致性Hash 等路由算法选择缓存服务器远程访问缓存数据,缓存服务器之间不通信, 缓存集群的规模可以很容易地实现扩容, 具有良好的可伸缩性。

4. Memcached
Memcached 曾一度是网站分布式缓存的代名词,被大量网站使用。其简单的设计优异的性能、互不通信的服务器集群、海量数据可伸缩的架构令网站架构师们趋之若鹫。

简单的通信协议
远程通信设计需要考虑两方面的要素, 一是通信协议, 即选择TCP 协议还是UDP 协议,抑或HTTP 协议; 一是通信序列化协议, 数据传输的两端, 必须使用彼此可识别的数据序列化方式才能使通信得以完成, 如XML 、JSON 等文本序列化协议,或者GoogleProtobuffer 等二进制序列化协议。Memcached 使用TCP 协议( UDP 也支持)通信, 其序列化协议则是一套基于文本的自定义协议, 非常简单, 以一个命令关键字开头, 后面是一组命令操作数。例如读取一个数据的命令协议是get <key> 0 Memcached 以后, 许多NoSQL 产品都借鉴了或直接支持这套协议。
丰富的客户端程序
Memcached 通信协议非常简单, 只要支持该协议的客户端都可以和Memcached 服务器通信, 因此Memcached 发展出非常丰富的客户端程序,几乎支持所有主流的网站编程语言, Java 飞C/C ++/C# 、Perl 、Python 、PHP 、Ruby 等, 因此在混合使用多种编程语言的网站, Memcached 更是如鱼得水。

高性能的网络通信
Memcached 服务端通信模块基于Libevent , 一个支持事件触发的网络通信程序库。Libevent 的设计和实现有许多值得改善的地方,但它在稳定的长连接方面的表现却正是Memcached 需要的。
高效的内存管理
内存管理中一个令人头痛的问题就是内存碎片管理。操作系统、虚拟机垃圾回收在这方面想了许多办法: 压缩、复制等。Memcached 使用了一个非常简单的办法一一固定空间分配。Memcached 将内存空间分为一组slab , 每个slab 里又包含一组chunk , 同一个slab 里的每个chunk 的大小是固定的,拥有相同大小chunk 的slab 被组织在一起,叫作slab class。存储数据时根据数据的Size 大小,寻找一个大于Size 的最小chunk 将数据写入。这种内存管理方式避免了内存碎片管理的问题, 内存的分配和释放都是以chunk 为单位的。和其他缓存一样, Memcached 采用LRU 算法释放最近最久未被访问的数据占用的空间, 释放的chunk 被标记,为未用, 等待下一个合适大小数据的写入。
当然这种方式也会带来内存浪费的问题。数据只能存入一个比它大的chunk 里,而一个chunk 只能存一个数据,其他空间被浪费了。如果启动参数配置不合理,浪费会更加惊人,发现没有缓存多少数据,内存空间就用尽了。

内存管理:

互不通信的服务器集群架构
如上所述,正是这个特性使得Memcached 从JBoss Cache 、OSCache 等众多分布式缓存产品中脱颖而出,满足网站对海量缓存数据的需求。而其客户端路由算法一致性Hash更成为数据存储伸缩性架构设计的经典范式。事实上,正是集群内服务器互不通信使得集群可以做到几乎无限制的线性伸缩,这也正是目前流行的许多大数据技术的基本架构特点。
虽然近些年许多NoSQL 产品层出不穷,在数据持久化、支持复杂数据结构、甚至性能方面有许多产品优于Memcached , 但Memcached 由于其简单、稳定、专注的特点, 仍然在分布式缓存领域占据着重要地位。

猜你喜欢

转载自blog.csdn.net/qq_41723615/article/details/89016128