在数据库上并发访问资源的算法

时间:2014-11-15 09:04:47

标签: database algorithm concurrency locks

前段时间,我们实施了一个仓库管理应用程序,用于跟踪我们在商店中拥有的每种产品的数量。我们解决了使用数据库锁(选择更新)并发访问数据的问题,但是当许多客户尝试从同一个商店消耗产品数量时,这种方法导致性能不佳。请注意,我们只管理一小组产品类型(少于10个),因此并发程度可能很高(同样,我们也不关心库存重新填充)。我们考虑将每个资源数量分成较小的#34;桶#34;但是这种方法可能导致客户试图消耗大于每个桶容量的数量的饥饿:我们应该管理桶合并等等。 .. 我的问题是:这个问题有一些广泛接受的解决方案吗?我也寻找学术文章,但主题似乎太宽泛了。

P.S。 1: 我们的应用程序在集群环境中运行,因此我们不能依赖于应用程序并发控制。 问题的目的是找到一种算法,以不同于单行的方式构建和管理数据,但保留db事务(使用锁或不使用锁)的所有优点。

P.S。 2:对于您的信息,我们管理着大量类似的仓库,示例侧重于单个仓库,但我们将所有数据保存在一个db中(价格都相同,等等)。

1 个答案:

答案 0 :(得分:0)

编辑:如果您使用可以在多个进程/服务器之间进行协调的排队程序,则下面的设置仍将在群集上运行,例如: RabbitMQ

您还可以使用仅使用数据库的更简单的排队算法,其缺点是需要轮询(而像RabbitMQ这样的系统允许线程阻塞,直到消息可用)。创建一个Requests表,其中包含用作唯一requestId的列(例如,随机UUID),该列充当主键,timestamp列,respourceType列和整数{{1列。您还需要一个日志表,其中包含唯一的requestedQuantity列作为主键,requestId列,timestamp列,整数resourceType列,以及布尔/ tinyint /无论requestQuantity列。

当客户端请求一定数量的ResourceX时,它会生成一个随机UUID,并使用UUID作为requestId将一行添加到Requests表中,然后轮询Logs表以获取requestId。如果success列为true,则请求成功,否则失败。

具有数据库的服务器为每个资源分配一个线程或进程,例如ProcessX负责ResourceX。 ProcessX从“请求”表中检索所有行,其中success按时间戳排序,然后从“请求”中删除它们;然后按顺序处理每个请求,为每个成功请求递减内存计数器,并在处理请求结束时更新Resources表上的ResourceX数量。然后,它将每个请求及其resourceType = ResourceX状态写入Logs表。然后,它再次检索来自success等请求的请求的所有请求。

使用自动增量整数作为Requests主键,并使ProcessX按主键而不是按时间戳排序可能稍微高效。


一个选项是为每个资源分配一个requestType = RequestX - 这个线程是唯一访问该资源的数据库表的东西,因此在数据库级别没有锁定。 DAOThread(例如Web会话)使用并发队列请求资源数量 - 下面的示例使用Java BlockingQueue,但大多数语言都有某种可以使用的并发队列实现。

Worker

Workers将资源请求发送到相应的DAOThread队列; DAOThread按顺序处理这些请求,如果请求的值不超过数量并返回Success,则更新本地资源数量,否则保持数量不变并返回Failure。数据库仅在十次更新后更新,以减少IO的数量; MAX_CHANGES越大,从系统故障中恢复就越复杂。您还可以使用专用的IOThread来执行所有数据库写入 - 这样您就不需要复制任何日志记录或计时(例如,每隔几秒就应该有一个Timer将当前数量刷新到数据库)。

Worker使用SynchronousQueue等待来自DAOThread的响应(SynchronousQueue是一个只能容纳一个项目的BlockingQueue);如果Worker在自己的线程中运行,您可能希望用标准的多项BlockingQueue替换它,以便Worker可以按任何顺序处理ReturnMessages。

有一些数据库,例如Riak具有对计数器的本机支持,因此这可以提高您的IO吞吐量并减少或消除对MAX_CHANGES的需求。

您可以通过引入public class Request { final int value; final BlockingQueue<ReturnMessage> queue; } public class ReturnMessage { final int value; final String resourceType; final boolean isSuccess; } public class DAOThread implements Runnable { private final int MAX_CHANGES = 10; private String resourceType; private int quantity; private int changeCount = 0; private DBTable table; private BlockingQueue<Request> queue; public DAOThread(DBTable table, BlockingQueue<Request> queue) { this.table = table; this.resourceType = table.select("resource_type"); this.quantity = table.select("quantity"); this.queue = queue; } public void run() { while(true) { Requester request = queue.take(); if(request.value <= quantity) { quantity -= request.value; if(++changeCount > MAX_CHANGES) { changeCount = 0; table.update("quantity", quantity); } request.queue.offer(new ReturnMessage(request.value, resourceType, true)); } else { request.queue.offer(new ReturnMessage(request.value, resourceType, false)); } } } } public class Worker { final Map<String, BlockingQueue<Request>> dbMap; final SynchronousQueue<ReturnMessage> queue = new SynchronousQueue<>(); public class WorkerThread(Map<String, BlockingQueue<Request>> dbMap) { this.dbMap = dbMap; } public boolean request(String resourceType, int value) { dbMap.get(resourceType).offer(new Request(value, queue)); return queue.take(); } } 来缓冲对BufferThread的请求来进一步提高吞吐量。

DAOThread

工作人员将他们的请求发送到BufferThreads,然后他们等待缓冲public class BufferThread implements Runnable { final SynchronousQueue<ReturnMessage> returnQueue = new SynchronousQueue<>(); final int BUFFERSIZE = 10; private DAOThread daoThread; private BlockingQueue<Request> queue; private ArrayList<Request> buffer = new ArrayList<>(BUFFERSIZE); private int tempTotal = 0; public BufferThread(DAOThread daoThread, BlockingQueue<Request> queue) { this.daoThread = daoThread; this.queue = queue; } public void run() { while(true) { Request request = queue.poll(100, TimeUnit.MILLISECONDS); if(request != null) { tempTotal += request.value; buffer.add(request); } if(buffer.size() == BUFFERSIZE || request == null) { daoThread.queue.offer(new Request(tempTotal, returnQueue)); ReturnMessage message = returnQueue.take(); if(message.isSuccess()) { for(Request request: buffer) { request.queue.offer(new ReturnMessage(request.value, daoThread.resourceType, message.isSuccess)); } } else { // send unbuffered requests to DAOThread to see if any can be satisfied for(Request request: buffer) { daoThread.queue.offer(request); } } buffer.clear(); tempTotal = 0; } } } } 请求或等待100ms请求通过缓冲区(BUFFERSIZE),此时他们将缓冲的消息转发给Request request = queue.poll(100, TimeUnit.MILLISECONDS)。每个DAOThread可以有多个缓冲区 - 而不是向工作人员发送DAOThread而是发送Map<String, BlockingQueue<Request>>,每个Map<String, ArrayList<BlockingQueue<Request>>>一个队列,工作人员使用计数器或随机数生成器,以确定发送请求的BufferThread。请注意,如果BufferThread太大和/或如果BUFFERSIZE太多,那么当等待缓冲区填满时,工作人员会遇到很长的暂停时间。