面临的问题php
对于高并发高访问的Web应用程序来讲,数据库存取瓶颈一直是个使人头疼的问题。特别当你的程序架构仍是创建在单数据库模式,而一个数据池链接数峰 值已经达到500的时候,那你的程序运行离崩溃的边缘也不远了。不少小网站的开发人员一开始都将注意力放在了产品需求设计上,缺忽视了程序总体性能,可扩 展性等方面的考虑,结果眼看着访问量一每天网上爬,可忽然发现有一天网站由于访问量过大而崩溃了,到时候哭都来不及。因此咱们必定要未雨绸缪,在数据库还 没罢工前,千方百计给它减负,这也是这篇文章的主要议题。java
你们都知道,当有一个request过来后,web服务器交给app服务器,app处理并从db中存取相关数据,但db存取的花费是至关高昂的。特 别是每次都取相同的数据,等因而让数据库每次都在作高耗费的无用功,数据库若是会说话,确定会发牢骚,你都问了这么多遍了,难道还记不住吗?是啊,若是 app拿到第一次数据并存到内存里,下次读取时直接从内存里读取,而不用麻烦数据库,这样不就给数据库减负了?并且从内存取数据必然要比从数据库媒介取快 不少倍,反而提高了应用程序的性能。c++
所以,咱们能够在web/app层与db层之间加一层cache层,主要目的:1. 减小数据库读取负担;2. 提升数据读取速度。并且,cache存取的媒介是内存,而一台服务器的内存容量通常都是有限制的,不像硬盘容量能够作到TB级别。因此,能够考虑采用分布 式的cache层,这样更易于破除内存容量的限制,同时又增长了灵活性。web
Memcached 介绍算法
Memcached是开源的分布式cache系统,如今不少的大型web应用程序包括 facebook,youtube,wikipedia,yahoo等等都在使用memcached来支持他们天天数亿级的页面访问。经过把cache层 与他们的web架构集成,他们的应用程序在提升了性能的同时,还大大下降了数据库的负载。 具体的memcached资料你们能够直接从它的官方网站[1]上获得。这里我就简单给你们介绍一下memcached的工做原理:sql
Memcached处理的原子是每个(key,value)对(如下简称kv对),key会经过一个hash算法转化成hash-key,便于查找、对比以及作到尽量的散列。同时,memcached用的是一个二级散列,经过一张大hash表来维护。数据库
Memcached有两个核心组件组成:服务端(ms)和客户端(mc),在一个memcached的查询中,mc先经过计算key的hash值来 肯定kv对所处在的ms位置。当ms肯定后,客户端就会发送一个查询请求给对应的ms,让它来查找确切的数据。由于这之间没有交互以及多播协议,因此 memcached交互带给网络的影响是最小化的。api
举例说明:考虑如下这个场景,有三个mc分别是X,Y,Z,还有三个ms分别是A,B,C:缓存
设置kv对 X想设置key=”foo”,value=”seattle” X拿到ms列表,并对key作hash转化,根据hash值肯定kv对所存的ms位置 B被选中了 X链接上B,B收到请求,把(key=”foo”,value=”seattle”)存了起来服务器
获取kv对 Z想获得key=”foo”的value Z用相同的hash算法算出hash值,并肯定key=”foo”的值存在B上 Z链接上B,并从B那边获得value=”seattle” 其余任何从X,Y,Z的想获得key=”foo”的值的请求都会发向B
Memcached服务器(ms)
内存分配
默认状况下,ms是用一个内置的叫“块分配器”的组件来分配内存的。舍弃c++标准的malloc/free的内存分配,而采用块分配器的主要目的 是为了不内存碎片,不然操做系统要花费更多时间来查找这些逻辑上连续的内存块(其实是断开的)。用了块分配器,ms会轮流的对内存进行大块的分配,并 不断重用。固然因为块的大小各不相同,当数据大小和块大小不太相符的状况下,仍是有可能致使内存的浪费。
同时,ms对key和data都有相应的限制,key的长度不能超过250字节,data也不能超过块大小的限制 --- 1MB。 由于mc所使用的hash算法,并不会考虑到每一个ms的内存大小。理论上mc会分配几率上等量的kv对给每一个ms,这样若是每一个ms的内存都不太同样,那 可能会致使内存使用率的下降。因此一种替代的解决方案是,根据每一个ms的内存大小,找出他们的最大公约数,而后在每一个ms上开n个容量=最大公约数的 instance,这样就等于拥有了多个容量大小同样的子ms,从而提供总体的内存使用率。
缓存策略
当ms的hash表满了以后,新的插入数据会替代老的数据,更新的策略是LRU(最近最少使用),以及每一个kv对的有效时限。Kv对存储有效时限是在mc端由app设置并做为参数传给ms的。
同时ms采用是偷懒替代法,ms不会开额外的进程来实时监测过期的kv对并删除,而是当且仅当,新来一个插入的数据,而此时又没有多余的空间放了,才会进行清除动做。
缓存数据库查询 如今memcached最流行的一种使用方式是缓存数据库查询,下面举一个简单例子说明:
App须要获得userid=xxx的用户信息,对应的查询语句相似:
“SELECT * FROM users WHERE userid = xxx”
App先去问cache,有没有“user:userid”(key定义可预先定义约束好)的数据,若是有,返回数据;若是没有,App会从数据库中读取数据,并调用cache的add函数,把数据加入cache中。
当取的数据须要更新,app会调用cache的update函数,来保持数据库与cache的数据同步。
从上面的例子咱们也能够发现,一旦数据库的数据发现变化,咱们必定要及时更新cache中的数据,来保证app读到的是同步的正确数据。固然咱们可 以经过定时器方式记录下cache中数据的失效时间,时间一过就会激发事件对cache进行更新,但这之间总会有时间上的延迟,致使app可能从 cache读到脏数据,这也被称为狗洞问题。(之后我会专门描述研究这个问题)
数据冗余与故障预防
从设计角度上,memcached是没有数据冗余环节的,它自己就是一个大规模的高性能cache层,加入数据冗余所能带来的只有设计的复杂性和提升系统的开支。
当一个ms上丢失了数据以后,app仍是能够从数据库中取得数据。不过更谨慎的作法是在某些ms不能正常工做时,提供额外的ms来支持cache,这样就不会由于app从cache中取不到数据而一会儿给数据库带来过大的负载。
同时为了减小某台ms故障所带来的影响,可使用“热备份”方案,就是用一台新的ms来取代有问题的ms,固然新的ms仍是要用原来ms的IP地址,大不了数据从新装载一遍。
另一种方式,就是提升你ms的节点数,而后mc会实时侦查每一个节点的状态,若是发现某个节点长时间没有响应,就会从mc的可用server列表里 删除,并对server节点进行从新hash定位。固然这样也会形成的问题是,本来key存储在B上,变成存储在C上了。因此此方案自己也有其弱点,最好 能和“热备份”方案结合使用,就可使故障形成的影响最小化。
Memcached客户端(mc)
Memcached客户端有各类语言的版本供你们使用,包括java,c,php,.net等等,具体可参见memcached api page[2]。 你们能够根据本身项目的须要,选择合适的客户端来集成。
缓存式的Web应用程序架构 有了缓存的支持,咱们能够在传统的app层和db层之间加入cache层,每一个app服务器均可以绑定一个mc,每次数据的读取均可以从ms中取得,若是 没有,再从db层读取。而当数据要进行更新时,除了要发送update的sql给db层,同时也要将更新的数据发给mc,让mc去更新ms中的数据。
假设从此咱们的数据库能够和ms进行通信了,那能够将更新的任务统一交给db层,每次数据库更新数据的同时会自动去更新ms中的数据,这样就能够进一步减小app层的逻辑复杂度。以下图:
不过每次咱们若是没有从cache读到数据,都不得不麻烦数据库。为了最小化数据库的负载压力,咱们能够部署数据库复写,用slave数据库来完成读取操做,而master数据库永远只负责三件事:1.更新数据;2.同步slave数据库;3.更新cache。以下图:
以上这些缓存式web架构在实际应用中被证实是能有效并能极大地下降数据库的负载同时又能提升web的运行性能。固然这些架构还能够根据具体的应用环境进行变种,以达到不一样硬件条件下性能的最优化。