分布式开发杂谈

分布式杂谈

分布式模型的理念自计算机诞生之日就已经出现。然而计算机发展初期,由于设备数量稀少且价格昂贵,再加上摩尔定律的影响,与其实现一套复杂的分布式架构系统,直接提升单台机器的硬件性能要更加简单可行得多。由此导致的现象就是上世纪40到80年代之间,计算机行业一直被大型机所主导。

经过多年的演变,计算机硬件发展遭遇瓶颈,通过分布式架构来水平提升计算能力成为主流。今天,借助已有的开源技术,你可以非常容易搭建起属于自己的分布式服务,大大降低了分布式应用开发的门槛;另一方面,近几年微服务理念的兴起掀起了新一轮的技术热潮。

根据平台和技术框架的不同,分布式开发的具体实现会存在差异,不过各自的架构理念以及技术原理仍是大同小异,连集成度极高的大型主机平台,也早在1990年就提出了sysplex集群技术。

以Web系统为例,一套完整的分布式系统通常包含以下几个组件:

  1. 负载均衡服务器
  2. 应用服务器组
  3. 缓存集群
  4. 消息队列
  5. 数据库集群
  6. 文件服务器集群
    在这里插入图片描述

与单机应用开发相比,分布式系统的逻辑结构几乎没有变化,依然是分应用服务、缓存、数据库、文件系统等各个模块,不同的是各个逻辑模块内部以多节点形式构成,模块间交互依然认为彼此是一个完整的个体,遵循高内聚低耦合的理念。
对于分布式开发人员来说,大部分情况下编写代码并不需要关注分布式架构的具体细节,模块已经屏蔽了底层细节只保留接口,而接口通常是架构无关的。因此,如果把开发单机应用的逻辑套用在分布式开发过程中,编译器并不会报错,单机调试也大概率无异常,但最后把代码部署到分布式环境中执行时,往往会出现意料之外的问题。架构差异带来的影响,编译器还不能帮我们发现并排除,更多需要依靠的程序设计规划来解决。

分布式开发常见的问题主要有以下几种:

  1. 锁问题
    分布式环境下,除线程同步、进程同步外,还额外增加了服务器同步的考虑。常见的临界区、信号量等技术手段只能限制本机程序,当程序跑在不同的机器上时,彼此便失去了关联。
  2. 事务问题
    写数据库时,我们知道为了保证一致性,可以打开一个事务进行提交,确保事务内的修改动作要么全执行,要么全回退。但如果A机器的事务执行过程中调用了B机器的服务,在A机器回滚事务时,如何让B机器也跟着一起撤销修改。
  3. 全局序列问题
    A机器在12:00:01时刻写了一条日志到本地,假定同时刻B机器也写了一条本地日志。日志归并阶段,应该怎么判断A的日志顺序在前还是B的日志?需要有一个全局的序列号发生器确保顺序性以及唯一性,类似的还有时钟同步问题。
  4. session同步问题
    系统将用户甲的请求分配给了A机器,一番操作后留下了一批session信息。不巧A机器故障了,用户甲请求改分配到了B机器,B机器怎么知道用户刚刚操作了什么内容?
  5. 负载均衡问题
    通常我们希望用户甲的请求被一旦分派给了机器A,后续用户甲的请求都交给机器A来处理。但假定各个用户请求都成功分派给了已有机器,这时候再在集群新加入一台机器,会出现没有请求进入新节点的情况。
  6. CAP取舍:
    CAP理论说明一致性、可用性、分区容错性三者不能同时兼备,而C、A、P三者孰优孰劣没有统一标准,分布式开发过程必须有所取舍,如何权衡则需要自行考虑。

分布式锁的一些思考

对公共资源加锁会限制系统并发,如同将宽敞的多车道高速路合并成单车道,容易引发拥堵。因此能够不加锁解决的的问题,我们都尽量避免加锁。

以缓存操作为例,我们通常通过以下方法避免加锁:

场景一:查询时缓存命中
缓存命中,直接返回缓存数据
在这里插入图片描述
场景二:查询时缓存击穿
缓存发生击穿,此时需要访问数据库取得请求结果,写入缓存后返回
在这里插入图片描述
场景三:更新操作
数据发生更新时,先更新数据库值,其次直接删除旧缓存,返回操作结果
在这里插入图片描述
更新场景下不直接修改缓存,而是将缓存删除,让后续查询请求发生缓存击穿,并进而更新缓存,这样做法的好处是保证了缓存内容和数据库内容的最终一致性。如果将删除缓存动作调整为更新缓存,当数据更新和缓存击穿同时发生时,有可能缓存击穿查询到的是修改前的脏数据,而回写存时缓存击穿写入在后,导致最后缓存内容和数据库值不一致。

对于复杂的业务场景,缓存加锁不可避免。正如上面提到的,普通的进程同步机制无法控制跨服务器进程,这时需要引入分布式锁的概念。分布式锁同样是实现公共资源的互斥访问,主要的区别是它将控制范围从单机扩展到了集群。

以常见的“秒杀”活动为例,看看分布式锁如何发挥作用。
场景四:商品秒杀
在这里插入图片描述

  1. 接收商品请求后,首先检查当前库存量;
  2. 若库存量满足,首先尝试获取分布式锁,表明要对库存进行调整;
  3. 锁定成功,则更新数据库以及缓存库存,最后释放锁;
  4. 锁定失败,则说明秒杀失败;

加入分布式锁后,秒杀活动带来的流量压力转移到了缓存读取以及分布式锁的排队请求中,避免了海量更新请求直接落到数据库层。

在目前主流的分布式框架中并没有直接提供分布式锁的接口,相比之下分布式锁更接近一种逻辑概念,由开发人员自行设计实现。常见的分布式锁方案基于redis或Zookeeper实现,也有基于数据库的设计。

结尾

这几年来微服务的兴起和容器技术的发展,让分布式理念愈加渗透到各个领域。不过不管技术如何演变,任何时候抛开量级谈设计都是不靠谱的行为,单机设计不一定就比分布式要差,选择合适的才是最优解。

作者:阮伟聪

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章