六个可以替代传统事务解决并发问题的建议

增删改查是大部分框架的功能,如果有两个并发请求修改同一个数据,这个时候,你会怎么办?或者插入本来应该是唯一却重复的数据时应该怎么办?再或者插入和修改时有其它辅助动作比如保存到另外的表,这些情况,有什么好的解决方案?

我想最开始,你会首先想到“事务”,事务确实能够让一组操作一起可靠安的全执行,他们要么全部执行,要么一个也别想执行。但如果有两个同时发生的并发事务怎么办?使用事务隔离级别,这是ACID中的定义,关系数据库内部机制中就是这么做的。

但是,如果使用隔离级别,比如可串行化serializable (以及可重复读),你的系统会变得很慢,依赖于不同关系数据库,同时发生的事务也许需要应用代码编码指定重试几次,这就很复杂,其他不是很严格的隔离级别则会带来更新丢失或幻读(幻读是不可重复读的一种特殊场景,当事务没有获取范围锁的情况下执行SELECT … WHERE操作就可能会发生幻读)。

即使你正确地设置了合适的隔离级别,你也能用代码正确处理了事务的失败错误情况,但是隔离并不能解决所有并发问题,比如应用级别的数据约束,也就是说,一种复杂的业务逻辑约束或规则,很难使用数据库的表键约束来实现的;单纯使用数据库技术也不能解决重复插入的问题;更不能解决应用级别的并发问题;不能解决数据并发等问题。也许你试图通过获得数据库锁来解决这些问题,但是锁是可怕的,锁有写锁、读锁和排他锁,如何避免死锁?不是每个程序员能够有经验和锁打交道的。

双重提交就是个很好的佐证,它说明了不是所有问题都可以通过数据库方式单独解决的。双重提交很多人的解决办法是:使用一个token代表每个请求,并存储在数据库,使用数据库的唯一键约束,这样,重复记录就无法插入,这种问题使用API比较复杂,因为你得根据API的用户才能产生合适的token。另外,虽然你使用数据库唯一约束,但是还得在应用代码中进行检查,因为两行记录虽然键不同但是值相同还是可能被插入的。

大部分并发是运行在单机上,这可以使用语言的并发特性来确保执行的串行化,双重重复不可能发生,但是当你部署应用在几台机器以上,并发问题变得困难。

下面是不使用事务而使用并发的解决思路:

  1. 类似Hazelcast之类提供分布式锁,整个集群都遵循锁语义如同单机一样,但是适用场景不多。
  2. 使用消息队列– 将所有请求推入消息队列,队列会被单个异步worker处理,但是可能不适合业务上需要立即返回给用户的场景。
  3. 使用Akka和其集群,能保证一个actor(可看成一个服务)一次只处理一个消息,但是因为akka完全改变了使用范式,难以使用和跟踪调试,而且和语言平台特点有关。
  4. 使用数据库的应用级别锁,比如关系数据库Postgre提供 advisory锁,MySQL也有类似的get_lock。使用关系数据库作为分布式锁机制,锁是被应用管理,不需要表库做任何事,只要请求为entityType, entityId字段请求一个锁,保证没有其他应用线程只有在获得数据库锁的情况下才能执行应用中指定一段代码,相当于用数据库锁替代语言同步锁,然后使用Spring的 @Before 之类AOP方式拦截服务的方法。
  5. 使用CRDT,它是一种幂等的数据结构,不管操作其之上的操作顺序,最终都是同样的结果状态。但是完全幂等的操作在实际中也是很少碰到。
  6. 使用“insert-only”只追加模型。像Datomic之类数据库内部使用这种模型,你可以在任何数据库中使用这种模型,只有新增追加,没有删除和更新,每次使用新的版本号插入新记录. 这样版本号的唯一性保证不会有重复记录。你不会丢失数据,相当于免费得到一个校订日志(banq注:实际是EventSourcing 事件流日志)。

上面列出的方法都是在不损失性能情况下如何串行化请求,包括了各种锁机制 队列和非堵塞I/O。

另外,与本文内容无关。再推荐一个开源的并发框架Disruptor,它能够在无锁的情况下实现网络的Queue并发操作。Martin Fowler曾在自己网站上写了一篇LMAX架构的文章,在文章中他介绍了LMAX是一种新型零售金融交易平台,它能够以很低的延迟产生大量交易。这个系统是建立在JVM平台上,其核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单。业务逻辑处理器完全是运行在内存中,使用事件源驱动方式。而业务逻辑处理器的核心是Disruptor,点击阅读原文链接(送你一个很不错的PPT),了解关于Disruptor的更多信息。

今天再给大家推荐一个不错的网站Jdon.com,JDON自2002年建站以来,内容一直在持续更新,致力于跟踪国际最新软件设计前沿,吸引软件专家和思想领袖参与探索、分享、交流、解惑、传道;主要方向包括有关软件建模、领域驱动设计、设计模式、云计算架构、敏捷开发等方面实战经验和原创思想 。本文也是出自JDON创始人板桥先生之手。

时间: 2024-11-10 01:31:07

六个可以替代传统事务解决并发问题的建议的相关文章

Disruptor——一种可替代有界队列完成并发线程间数据交换的高性能解决方案

本文翻译自LMAX关于Disruptor的论文,同时加上一些自己的理解和标注.Disruptor是一个高效的线程间交换数据的基础组件,它使用栅栏(barrier)+序号(Sequencing)机制协调生产者与消费者,从而避免使用锁和CAS,同时还组合使用预分配内存机制.缓存行机制(cache line).批处理效应(batch effect)来达到高吞吐量和低时延的目标.目前Disruptor版本已经迭代至3.0,本论文是基于Disruptor1.0写就,在新版本中,相对与1.0版本,其核心设计

乐观锁与悲观锁——解决并发问题

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁.传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁. 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制.乐观锁适用于

关于事务与并发

今天看到一个技术群里面聊到事务与并发的关系,发现好多开发者的理解不一样,想把自己再工作中遇到的例子在这里呈现出来,以告诉大家自己所理解的事务与并发,先简单说下流程吧! 我们公司的A,B系统通信使用的是ActiveMQ,主要是订单状态的通信,之前遇到的一个问题是A系统通过一个事务修改了订单的状态要通过ActiveMQ通知B系统,B系统收到通知并处理后会再通过ActiveMQ告知A系统通知完毕,再这个过程中订单在A系统里面有一个字段来标示订单状态,简要的步骤就是: A系统处理一个事务后修改订单状态

数据库中的事务和并发问题探讨

数据库中的事务和并发问题探讨 引子 最近有同事写了段代码,负责创建订单的逻辑,代码审查时发现可能会有并发的问题.同事并不认同,他认为他的逻辑是写在存储过程中的,应该没有问题. 代码的逻辑大概是(伪代码): begin transaction if 查询到客户存在进行中的订单 rollback transaction if 查询到设备存在进行中的订单 rollback transaction 插入订单 commit transaction 下面针对这个逻辑进行分析,为什么这个事务会出现并发问题.

SQL锁表解决并发性

在数据库开发过程中,不得不考虑并发性的问题,因为很有可能当别人正在更新表中记录时,你又从该表中读数据,那你读出来的数据有可能就不是你希望得到的数据.可以说有些数据同时只能有一个事物去更新,否则最终显示给用户的数据不是数据库中现存的数据.锁表就限制不同的事物在同一时间内不允许同时操作一张表,实例很简单,可以用select来锁定整张表,那别人就不可能更新或是读取表的记录.select * from dbo.Employee with(holdlock); with关键字来设置锁表的方式.下面是wit

乐观锁与悲观锁——解决并发问题(转)

引言 为什么需要锁(并发控制)? 在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突.这就是著名的并发性问题. 典型的冲突有: 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失.例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新. 脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取.例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6. 为了解决这些并发带来的问题. 我们需要引入并发控制机制. 并发控制机制

(转)乐观锁与悲观锁——解决并发问题

引言 为什么需要锁(并发控制)? 在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突.这就是著名的并发性问题. 典型的冲突有: 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失.例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新. 脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取.例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6. 为了解决这些并发带来的问题. 我们需要引入并发控制机制. 并发控制机制

网络编程三---多线程/进程解决并发问题

前文列出的代码给大家展示了一个最简单的网络程序,但正如文章末尾所提的,这个最简单的网络程序最大的缺点是服务端一次只能服务一个客户端,就比如说你去吃饭,饭店只有一个服务员, 而且服务员在客户离开之前只能为一个客户服务,也就是说你只能等待你前面的客户吃好饭离开了,然后你才能进去吃饭,而在你吃饭的时候时候,你后面来的人都得等你吃完饭才能轮到你后面一个人吃饭.这种模式的缺点很明显,因为在你进去点好菜到买单前的这段时间,这个服务员都是空闲的,为什么不让服务员在这个空闲时间让其他客户进来服务员为他点菜呢?在

Microsoft SQL Server中的事务与并发详解

本篇索引: 1.事务 2.锁定和阻塞 3.隔离级别 4.死锁 一.事务 1.1 事务的概念 事务是作为单个工作单元而执行的一系列操作,比如查询和修改数据等. 事务是数据库并发控制的基本单位,一条或者一组语句要么全部成功,对数据库中的某些数据成功修改; 要么全部不成功,数据库中的数据还原到这些语句执行之前的样子. 比如网上订火车票,要么你定票成功,余票显示就减一张; 要么你定票失败获取取消订票,余票的数量还是那么多.不允许出现你订票成功了,余票没有减少或者你取消订票了,余票显示却少了一张的这种情况