分布式并发控制
我已经为此工作了几天,已经找到了几种解决方案,但是都没有一个非常简单或者轻巧的解决方案。问题基本上是这样的:我们有一个由10台计算机组成的群集,每台计算机都在多线程ESB平台上运行相同的软件。我可以相当轻松地处理同一台计算机上线程之间的并发问题,但是不同机器上同一数据上的并发又如何呢?
本质上,该软件接收请求,以通过Web服务将客户的数据从一家公司传送到另一家公司。但是,该客户可能存在或者可能不存在于另一个系统上。如果没有,我们将通过Web服务方法创建它。因此,它需要某种测试和设置,但是我需要某种信号量,以将其他计算机锁定在不会引起竞争状态的位置。我曾经遇到过这样的情况,即为单个本地客户创建了两次远程客户,这确实不是很理想。
从概念上讲,我喜欢的解决方案是:
- 使用我们的容错共享文件系统创建"锁定"文件,每台机器将根据客户检查这些文件
- 在我们的数据库中使用一个特殊的表,并锁定整个表,以便对锁定记录进行"测试并设置"。
- 使用Terracotta,这是一种开放源代码服务器软件,可帮助扩展规模,但使用中心辐射型模型。
- 使用EHCache同步复制内存中的"锁"。
我无法想象我是唯一遇到过此类问题的人。我们是如何解决的?我们是在内部做饭还是有喜欢的第三方产品?
解决方案
过去,我们会在网络上使用特定的"锁定服务器"来处理此问题。 eh
数据库服务器可能具有专门用于执行此类操作的资源。 MS-SQL Server具有可通过sp_getapplock / sp_releaseapplock过程使用的应用程序锁。
我用两种方法制作了一个简单的RMI服务:锁定和释放。两种方法都使用一个密钥(我的数据模型使用UUID作为pk,因此它也是锁定密钥)。
RMI是集中的,因此是一个很好的解决方案。我们无法使用EJB执行此操作(特别是在群集中,因为我们不知道调用将在哪台计算机上进行)。另外,这很容易。
它为我工作。
我在Coherence方面做了很多工作,它允许使用多种方法来实现分布式锁。天真的方法是请求在所有参与节点上锁定相同的逻辑对象。用Coherence术语来说,这是在复制缓存上锁定密钥。这种方法无法很好地扩展规模,因为随着添加节点,网络流量会线性增加。一种更聪明的方法是使用分布式缓存,其中群集中的每个节点自然负责一部分密钥空间,因此将密钥锁定在这种缓存中始终涉及与最多一个节点的通信。我们可以基于此想法采用自己的方法,或者更好的是获得Coherence。这确实是我们梦dream以求的可扩展性工具包。
我要补充一点,任何基于半个体面的多节点网络的锁定机制都必须相当复杂,以在发生任何网络故障时正确地采取行动。
不知道我是否了解整个上下文,但是听起来我们有1个单个数据库支持此操作?为什么不利用数据库的锁定:如果创建客户是单个INSERT,则仅此语句就可以用作锁定,因为数据库将拒绝第二个INSERT,这将违反约束之一(例如,客户名称为例如唯一)。
如果"插入客户"操作不是原子操作,而是一批语句,那么我将引入(或者使用)初始INSERT,该插入创建一些简单的基本记录来标识客户(具有必要的UNIQUEness约束),然后执行同一事务中的其他插入/更新。同样,数据库将负责保持一致性,任何并发修改都将导致其中之一失败。
我们可能要考虑使用Hazelcast分布式锁。超级轻巧易用。
java.util.concurrent.locks.Lock lock = Hazelcast.getLock ("mymonitor"); lock.lock (); try { // do your stuff }finally { lock.unlock(); }
Hazelcast分布式队列,地图,集合,列表,锁定
Terracotta更接近于"分层"模型,所有客户端应用程序都与Terracotta服务器阵列进行通信(更重要的是,从规模上讲,它们不会相互通信)。 Terracotta服务器阵列能够针对规模和可用性进行集群(为可用性而镜像),为规模而进行条带化。
在我们可能知道的任何情况下,Terracotta都可以像使用单个JVM一样,通过使用POJOsynced / wait / notify或者使用任何java.util.concurrent原语(例如ReentrantReadWriteLock)在整个JVM中表达并发性,CyclicBarrier,AtomicLong,FutureTask等。
《兵马俑食谱》中有许多简单的食谱说明了这些原语的用法。
作为示例,我将发布ReentrantReadWriteLock示例(请注意,我们没有使用普通Java ReentrantReadWriteLock的" Terracotta"版本的锁)
import java.util.concurrent.locks.*; public class Main { public static final Main instance = new Main(); private int counter = 0; private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true); public void read() { while (true) { rwl.readLock().lock(); try { System.out.println("Counter is " + counter); } finally { rwl.readLock().unlock(); } try { Thread.currentThread().sleep(1000); } catch (InterruptedException ie) { } } } public void write() { while (true) { rwl.writeLock().lock(); try { counter++; System.out.println("Incrementing counter. Counter is " + counter); } finally { rwl.writeLock().unlock(); } try { Thread.currentThread().sleep(3000); } catch (InterruptedException ie) { } } } public static void main(String[] args) { if (args.length > 0) { // args --> Writer instance.write(); } else { // no args --> Reader instance.read(); } } }
我们使用兵马俑,所以我要投票赞成。
我一直在关注Hazelcast,它看起来像是另一种很有前途的技术,但是由于我没有使用它而无法投票,并且知道它使用了基于P2P的系统,所以我真的不相信它扩展需求。
但是我也听说过来自Yahoo的Zookeeper,它正在Hadoop的保护之下。如果我们喜欢尝试一些新技术,这确实有很多希望,因为它非常精简且刻薄,着眼于协调。我喜欢这个愿景和承诺,尽管它可能仍然太绿了。
- http://www.terracotta.org
- http://wiki.apache.org/hadoop/ZooKeeper
- http://www.hazelcast.com
我打算就如何将memcached用作非常快速的分布式RAM存储来保存日志提出建议。但似乎EHCache是一个类似的项目,但更以Java为中心。
只要我们确定使用原子更新(memcached支持它们,而对EHCache一无所知),任一种方法都是可行的。这是迄今为止最具扩展性的解决方案。
作为相关的数据点,Google使用" Chubby"(一种基于RAM的快速分布式锁存储)作为多个系统(包括BigTable)的根。
如果可以设置负载平衡,以便始终将单个客户的请求映射到同一服务器,则可以通过本地同步来处理此问题。例如,以客户ID mod 10来查找要使用的10个节点中的哪一个。
即使我们通常不想这样做,节点也可以针对此特定类型的请求相互代理。
假设用户足够统一(即如果我们有大量用户),并且我们不希望在一个节点过载的地方出现热点,那么这应该仍然可以很好地扩展。
我们一直在开发一个开源的分布式同步框架,目前已实现DistributedReentrantLock和DistributedReentrantReadWrite锁,但仍处于测试和重构阶段。在我们的体系结构中,锁密钥是在存储桶中划分的,每个节点对于一定数量的存储桶都是合理的。因此,对于一个成功的锁定请求而言,只有一个网络请求有效。我们还将AbstractQueuedSynchronizer类用作本地锁定状态,因此所有失败的锁定请求都在本地处理,这大大减少了网络流量。
我们使用JGroups(http://jgroups.org)进行组通信,使用Hessian进行序列化。
有关详细信息,请访问http://code.google.com/p/vitrit/。
请给我宝贵意见。
卡姆兰