当下在互联网技术架构中,最流行的莫过于分布式架构了。为什么大家纷纷都采用分布式架构呢?
1、高效低廉,将部署在高性能机的程序分散在多个小型机中部署;
2、扩展性强,可随着业务的扩展而横向扩展系统的性能;
3、可靠性强,当系统中一台或几台出现故障时,仍然有其它机器在提供服务;
4、并发性强,各台机器同时运作提供服务。
分布式,真香!
不过使用分布式架构也会存在一些问题,最严重的问题便是数据一致性问题。因为业务是部署在多台机器上,由于时间空间的不一致,从而导致数据会不一样,分布式的CAP理论已经告诉我们“分布式系统无法同时满足一致性Consistency、可用性Availability、分区容错性Partitiontolerance,最多满足两项”。对于数据不一致的问题,互联网有几种思考,比如BASE服务基本可用,牺牲暂时的数据不一致,只要数据最终一致即可;采用分布式事务进行解决;采用分布式锁进行解决。而今天我们要介绍的便是分布式锁的解决方案。
首先来看一个具体的case解释为什么需要分布式锁。在电商业务采用分布式架构后,程序部署在3个Tomcat容器中(1个tomcat容器代表一个服务器,3个tomcat可理解在北京上海深圳都有部署电商服务),成员变量A代表商品数量。在北京的Alice,上海的Bob,深圳的Tom,都分别发起了购买或取消iphone12的用户请求,经过Nginx负载均衡将Alice的请求发给了北京服务器,Bob的请求发给了上海服务器,Tom的请求发给了深圳服务器,这时候每台服务器都会对iPhone12这个商品数量进行更改,Alice的请求是将商品数量加到200,Bob的请求是将商品数量减少100,Tom的请求是将商品数量加1,如果对于商品数量的修改没有任何限制,整体就会乱起来,可能Bob的先减少,Tom的在增加,数据就完全乱了,所以需要分布式锁解决方案。
锁的概念并不是在分布式中才存在,传统互联网的开发中也存在锁。比如在多进程处理请求时,内存资源就会不足,这时候操作系统会使用信号量来解决资源的抢夺,如果信号量的值大于0,则将信号量数值减1,同时分配内存资源,如果信号量的值小于0,则进程处于等待状态,有其他进程操作执行完毕后,信号量数值加1,唤醒等待的进程。
总结一下,实现锁有三个要素:
1、有存储锁的空间,在多进程中,内存就是存储锁的空间,通过对锁的控制实现不同进程的访问控制。2、能唯一标识,不同的空间用不同的锁保护,那必须要唯一标识。
3、有状态,即存在、不存在。在分布式系统环境中,分布式锁就是一个变量一个方法在同一时间只能被一个机器的一个线程执行,对分布式锁的实现也提出了更高的要求,即需要高性能高可用的获取与释放锁,需要锁超时机制,避免死锁出现。
那么如何实现分布式锁呢?业内有三种实现方式:
1、基于数据库;
2、基于redis;
3、基于Zookeeper。
对于第一种实现方案,很简单,我们知道在传统数据库中是有ACID事务原子性、一致性、持久性、可用性规则的,如果基于数据库实现分布式锁,只需要在数据库中创建一个表,表中包含方法名,对方法名加上唯一索引,想要执行该方法时,就使用这个方法名向表中插入数据,插入时,其它数据都没法插入,等于获得锁,成功插入后,删除对应的数据释放锁。这种方案的好处就是简单,但存在的问题是对数据库要求高,因为数据库的可用性、性能会直接影响分布式锁的可用性,数据库可能需要主从部署、读写分离。
对于第二种实现方案,只需要使用redis的命令setnx、expire、delete就可以了(请允许我再感叹一下,redis真的太好用了,又简单性能又好),setnxkeyvalue就会给某个变量赋予一个值,返回1,当业务请求来时,如返回key值为1,线程获得锁,如果key值为0,线程抢锁失败。
对于第三种实现方案,我们知道zookeeper是一个分布式协调服务,它内部是一个分层的文件系统目录树结构,同一个目录下只能有一个唯一文件名,因此当实现分布式锁时,只需要创建一个目录,线程想要获取锁就在目录下创建临时顺序节点,然后遍历获取是否存在比自己小的节点,如果存在则获取锁失败,如果不存在则获取锁成功,缺点就是会频繁的创建节点。
通过本文的介绍,认真阅读的小伙伴又获得了分布式架构使用的一个技巧。在分布式环境中,对资源的上锁非常重要,通过分布式锁解决了数据的一致性问题,小伙伴们可以根据自己业务实际情况选择合适的分布式锁方案噢~