为什么要在分布式环境下实现单体
通常的Web项目中,会有多个进程执行完全相同的程序(如多个Aapache进程), 这些进程又分出在不同的机器之上。这些进程会同时访问某个资源,比如cache的数据,如果该资源不存在(cache失效), 则进程会访问DB进行读取并写入到cache中。
在这个过程中,大量并发的进程会同时做相同的事情,在瞬时对数据库造成极大的访问压力。而这些进程做的事情又是完全相同的。从最终结果看,我们其实只需要1个进程完成cache的更新就可以,只要有1个进程更新了cache,其他进程就可以不用读数据库了。
为了解决这个问题,必然需要这些处于不同机器上的Apache进程协同工作,找出1个“单体”来完成这件事情,其他进程等待这个“单体”进程处理完毕后直接使用数据就可以了。
单机环境下是如何实现单体模式
在单机环境下,不同进程访问相同的资源时,OS提供了相当多的机制来保证互斥:如互斥量、1/0信号灯、锁等。进程在OS的管理下通过这些机制来完成对资源的互斥操作,以避免出现不可预料的问题。
从单机环境到分布式环境
看一下单机环境下单体模式的几个关键点:
- 访问资源的进程
- 资源
- 对资源进行保护的“对象”
- 对“对象”进行管理维护的OS
- 因此,如果能够在分布式环境下模拟出这4个关键点,就可以解决分布式环境下的单体模式了。
- 访问资源的进程:多机环境中的进程与单机中的进程本质是相同的,同属资源的访问者
- 资源:被访问的资源从本机移动了其他机器,但访问方式无本质区别
- 对资源进行保护的“资源保护对象”:缺失
- 对“对象”进行管理维护的OS:缺失
四个因素中,分布式环境缺失了后面两个因素。如何模拟出来呢?
- 我们可以新增加一个角色,该角色提供“资源保护对象”和对“资源保护对象”的管理。所有的进程在更新真实资源之前均向该角色申请访问权限,由该角色负责是否授予权限。
- 所有的进程在访问“资源”之前首先通过外部的"OS“访问资源保护对象,只有获取了“资源保护对象”的访问权限后,才能访问“资源”。如果不能获得权限则等待,一直等待到具有访问权限开始。
应用:如何使用单体模式解决Cache穿透问题
使用MEMCACHE的ADD操作模拟“资源保护对象”
Memcached提供的add操作为原子性操作,当两个进程同时一个Key进行add操作时,memcached会保证只有最先到达的操作写成功,后续到达的请求会立刻返回false. 基于这个特性,在访问“资源”之前,先对该资源对应的key进行add操作,只有当add操作成功后再更新资源。如果add失败,表明当前正在有其它进程正在更新资源,当前进行可以sleep一小段时间再次重试,这样就避免了Cache穿透问题。这个模式timyang 称之为 Memcache mutex 设计模式。timyang 提供的示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 | if (memcache.get(key) == null) { // 3 min timeout to avoid mutex holder crash if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { value = db.get(key); memcache.set(key, value); memcache.delete(key_mutex); } else { sleep(50); retry(); } } |
使用文件的写入排它锁模拟“资源保护对象”
Memcache mutex中,需要使用一个单独的Memcached实例,有没有不需要使用Memcache的方式呢? 草屋主人 提供了另外的一种实现方式:机遇文件锁得方式。并提供了该模式的PHP实现: phplock 。
这种方式中,通过本地文件锁的方式同步了不同进程之间的资源访问请求。其优点是不需要增加memcached的开销,缺点是只能同步单一机器的进程,不能同步不同机器上的进程。因此 草屋主人 说: “phplock是单机版本的,对于多台机器使用的话,可能会有最大机器数的请求穿透的服务器,如果你的web服务器不是非常多的话,也不会有什么问题。”