计算机领域多处地方用到缓存,好比说为了缓解CPU和内存之间的速度不匹配问题,咱们每每经过增长一级、二级、三级缓存,CPU先从缓存中取指令,若是取不到,再从内存中取,并更新缓存,同时,根据程序的局部性原理,使得大部分状况下缓存都会命中。node
目前,Web应用的核心数据一般存放在数据库中,好比说用户信息、订单信息、交易信息等,同时,数据库和编程语言是无关的,经过SQL交互,Java、Php等语言写的程序须要访问数据库,执行业务逻辑,展现结果给用户。可是数据库有必定的局限性,譬如:1.数据库链接是很是 "昂贵 "的资源,为了复用这些资源,目前采用链接池技术,2. 链接池的链接数是有限的,若是用户过多,势必要等待,3. 读写数据时须要加锁。web
经过上述介绍,咱们知道一个大型系统中数据库每每会成为瓶颈,咱们不能每次访问都访问数据库,尤为是在多用户,大并发的状况下。面对这种状况,咱们一般采用何种方法呢?在计算机行业中的全部问题,均可以经过增长一个抽象层来解决。那么,针对数据库这个瓶颈,咱们能够在应用层和数据库层增长一层,即缓存层。算法
若是你是某某大型公司的首席架构师,如今公司须要自研一套缓存系统,你应该怎么设计呢?我想在设计以前应该想好如下几个问题:数据库
目前常见的数据格式有序列化对象、XML、JSON、字符串(key,value)和基本的数据结构,其中针对Java语言的序列化对象有序列化和反序列化,而Google研发的protobuf是和语言无关的,好比说Python将某对象序列化,Java能将这个对象进行反序列化。编程
考虑到公司有不少后端小组,而且使用不一样的编程语言,这就要求咱们自研的缓存系统应该和编程语言无关,基于此,咱们须要制定一套协议来支持各类语言。客户端如何使用这套协议?最多见的就是客户端/服务器模型。首先,服务器监听请求;接着,客户端发送请求,得到响应,其中客户端发送的请求就是协议;最后,基于Socket通讯。好比说:set 'name' 'mukedada'
、get name
。后端
缓存服务器端在启动的时候,应该设置缓存大小,当缓存被沾满时,采用LRU算法。缓存
对于大型应用服务器,单机的缓存服务器是支撑不了的。那么,就须要对缓存服务器进行水平扩展(即增删服务器,当活动结束后,就须要考虑删减服务器),那么用什么算法让数据相对平均的分配到每台服务器?同时,这个算法应该放在客户端仍是服务端呢?服务器
它的典型应用是Memcached,Memcached使用的是一致性Hash算法,在介绍它以前,咱们先来看一下余数算法。对于用户要存储的(key,value),计算key的整数哈希值,而后对服务器的数目求余,这样来肯定存储服务器。这个方法存在一个致命的问题:当服务器个数发送变化时,余数会发生变化,这样一来须要从新到数据库获取数据。 为了加深你们的理解,举个具体的实例:假设有3台服务器0、一、2,key一、key2的hash值分别是100,99,对应的余数分别是1和0,也就是说它们分别存放于编号为1和0的服务器中,如今若是增长一台服务器3,那么它们的余数也会随之发生变化,100%4 = 0,99%4 = 3,可是它们在0、3号服务器却找不到对应的数据。 微信
为了解决余数算法存在的问题,咱们的先辈们提出了分布式一致性hash算法。它思路就是当服务器个数发生变化时,尽量的减小影响。譬如:当咱们新增node5,只影响局部范围内的key,而余数算法则影响全局。 数据结构
可是它也存在分布不均匀的问题,致使有的服务器上缓存的数据多,有的少。一种方法就是虚拟节点,也就是说让一个服务器化身为多个虚拟节点,分布到环上。Memcache采用的就是这种方法。
代理实现 代理程序放在服务器端,它的典型案例有Twemproxy和Codis。它的基本思想:应用程序向Proxy发送请求,Proxy经过必定算法计算获得数据应该从哪一个节点获取,而且返回响应给客户端。为了防止Proxy出现单点故障,能够经过集群等方式实现Proxy高可用。
路由实现 它的典型案例就是Redis。它的基本思想是应用程序能够将请求发送到任意一个节点,当节点包含该请求数据,则直接返回响应给应用程序,当节点不包含该请求数据时,则告诉它跳转到其余节点中取数据,其中,客户端程序库须要解析相应的指令。例如:当node1中没有数据,会让客户端程序访问node3,这相似于web中的重定向,缺点: node1须要知道其余节点的数据,即node1和其余节点是相互通讯的。
CRC16(key)%16384
求余,最终会落到0~16383这个区间的槽中。
可是,每当新增一个节点时,须要从原先的每一个节点中获取hash槽,这时须要涉及数据迁移的过程。若是在数据迁移的过程当中有一个用户请求,这个时候该怎么办?目前一种解决方法是让node1和node4的持有相同的槽,可是设置不一样的状态,例如node1的槽的状态设置为正在迁移,而node4的状态是正在导入,首先将请求交给node1,若是node1中有数据则直接返回,若是没有则交给ndoe4。以下图所示。
同时,咱们注意到node一、node2等存在单点故障,为增长可用性,咱们对每一个node使用主从模式。数据首先写入到master节点,以后有两种方式,方式一,直接将结果返回给客户端,而后将master节点数据同步到slave从节点中,这样作的好处就是响应周期短,缺点是可能存在数据不一致的状况,即master节点将结果返回给客户端以后,还没来得及将数据同步到slave节点中就发生故障,那么这部分数据就会丢失。方式二,数据写入到master节点以后,须要将数据同步到slave节点成功以后,再将结果返回给客户端,这种方式保证了数据强一致性,可是用户须要更长的时间来等待。
用户每次访问缓存都没有命中,致使每次请求都要访问数据库,这就是缓存击穿问题,出现这种状况致使缓存没起效果,反而增长了系统消耗。针对这个问题,通常诸如双十一等活动都会在活动开始以前将用户信息预先存放到缓存中。
欢迎关注微信公众号:木可大大,全部文章都将同步在公众号上。