JPA使用乐观锁应对高并发方式

目录
  • JPA使用乐观锁应对高并发
    • 高并发系统的挑战
    • 悲观锁的问题
    • 乐观锁是个好东西
    • 给数据库添加乐观锁
  • 乐观锁 -业务判断 解决高并发

JPA使用乐观锁应对高并发

高并发系统的挑战

在部署分布式系统时,我们通常把多个微服务部署在内网集群中,再用API网关聚合起来对外提供。为了做负载均衡,通常会对每个微服务都启动多个运行实例,通过注册中心去调用。

那么问题来了,因为有多个实例运行都是同一个应用,虽然微服务网关会把每一个请求只转发给一个实例,但当面对高并发时,但它们仍然可能同时操作同一个数据库表,这会不会引发什么问题呢?

悲观锁的问题

比如电商中常见的商品秒杀系统,在用户抢购商品过程中,会有大量并发请求,很可能同时读写一个包含商品剩余数量的表,这种一般要给数据库加锁,否则很容易出现商品超卖错卖的情况。

如果使用数据库自带的锁机制,也就是悲观锁,在写入的时候锁定数据库,其他修改请求到来时就必须等待锁释放,但这就使效率降下来了,而且高并发场景下可能有的请求一直抢不到锁,就会长时间卡在那导致请求失败,然后有大量重试,系统可能会发生连接数耗尽等异常。

乐观锁是个好东西

查阅资料发现乐观锁是个好东西,它是为数据库表增加一个标识数据版本的version字段来实现的,读取数据时把version字段一同读出,写入数据库时比对version字段就知道数据是否被更改过,如果version不相等就说明持有的是过期数据,不能写入,如果相等就可以写入,并把version加一。

乐观锁在写入数据库的时候,才会检查数据是否冲突,如果发现冲突了,就放弃写入,返回写入失败的信息,相比于悲观锁,这是一种轻量级的对数据的锁定方式,能够应对高并发需求。

给数据库添加乐观锁

说乐观锁是个好东西,首先得说 JPA 是个好东西,因为Spring Data JPA已经内置了乐观锁的实现,给数据库表添加乐观锁很简单,添加一个整型字段,并加入@Version注解就可以了,每次提交数据时JPA会自动检查版本。

    @Entity
    @Table(name = "m_order")
    public class Order {
    ...
        @Version
        private int version;
    ...
    }  

乐观锁 -业务判断 解决高并发

在解决高并发问题时,如果是分布式系统显然我们只能够使用数据库端加锁机制来解决这个问题,但是这种同步机制或者数据库物理锁机制会牺牲一部分的性能,所以常常以另外一种方式来解决这个问题 就是乐观锁模式

银行两操作员同时操作同一账户就是典型的乐观锁模式。

比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交。最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050。这就是典型的并发问题。

乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。

读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个version字段,当前值为1;而当前帐户余额字段(balance)为1000元。假设操作员A先更新完,操作员B后更新。

  • a、操作员A此时将其读出(version=1),并从其帐户余额中增加100(1000+100=1100)。
  • b、在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并从其帐户余额中扣除50(1000-50=950)。
  • c、操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户增加后余额(balance=1100),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录version更新为2。
  • d、操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数据(balance=950),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足 “提交版本必须大于记录当前版本才能执行更新 “的乐观锁策略,因此,操作员B的提交被驳回。
  • 这样,就避免了操作员B用基于version=1的旧数据修改的结果覆盖操作员A的操作结果的可能。

操作员A操作如下:

select id, balance, version from account where id="1";
查询结果:id=1, balance=1000, version=1
update account
set balance=balance+100, version=version+1
where id="1" and version=1
select id, balance, version from account where id="1";
查询结果:id=1, balance=1100, version=2

操作员B操作如下:

select id, balance, version from account where id="1";
查询结果:id=1, balance=1000, version=1
#操作员A已修改成功,实际account.balance=1100、account.version=2,操作员B也将版本号加一(version=2)试图向数据库提交数据(balance=950),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足 “提交版本必须大于记录当前版本才能执行更新 “的乐观锁策略,因此,操作员B的提交被驳回。
update account
set balance=balance-50, version=version+1
where id="1" and version=1
select id, balance, version from account where id="1";
查询结果:id=1, balance=1100, version=2

Hibernate、JPA等ORM框架或者实现,是使用版本号,再判断UPDATE后返回的数值

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java中数据库常用的两把锁之乐观锁和悲观锁

    在写入数据库的时候需要有锁,比如同时写入数据库的时候会出现丢数据,那么就需要锁机制. 数据锁分为乐观锁和悲观锁,那么它们使用的场景如下: 1. 乐观锁适用于写少读多的情景,因为这种乐观锁相当于JAVA的CAS,所以多条数据同时过来的时候,不用等待,可以立即进行返回. 2. 悲观锁适用于写多读少的情景,这种情况也相当于JAVA的synchronized,reentrantLock等,大量数据过来的时候,只有一条数据可以被写入,其他的数据需要等待.执行完成后下一条数据可以继续. 他们实现的方式上有所

  • java高并发锁的3种实现示例代码

    初级技巧 - 乐观锁 乐观锁适合这样的场景:读不会冲突,写会冲突.同时读的频率远大于写. 以下面的代码为例,悲观锁的实现: public Object get(Object key) { synchronized(map) { if(map.get(key) == null) { // set some values } return map.get(key); } } 乐观锁的实现: public Object get(Object key) { Object val = null; if((

  • Hibernate实现悲观锁和乐观锁代码介绍

    四种隔离机制不要忘记:(1,2,4,8) 1.read-uncommitted:能够去读那些没有提交的数据(允许脏读的存在) 2.read-committed:不会出现脏读,因为只有另一个事务提交才会读取来结果,但仍然会出现不可重复读和幻读现象. 4.repeatable read: MySQL 默认.可重复读,读数据读出来之后给它加把锁,其他人先别更新,等我用完了你再更新.你的事务没完,其他事务就不可能改这条记录. 8.serializable:序列化,最高级别.一个一个来,不去并发.效率最低

  • JPA使用乐观锁应对高并发方式

    目录 JPA使用乐观锁应对高并发 高并发系统的挑战 悲观锁的问题 乐观锁是个好东西 给数据库添加乐观锁 乐观锁 -业务判断 解决高并发 JPA使用乐观锁应对高并发 高并发系统的挑战 在部署分布式系统时,我们通常把多个微服务部署在内网集群中,再用API网关聚合起来对外提供.为了做负载均衡,通常会对每个微服务都启动多个运行实例,通过注册中心去调用. 那么问题来了,因为有多个实例运行都是同一个应用,虽然微服务网关会把每一个请求只转发给一个实例,但当面对高并发时,但它们仍然可能同时操作同一个数据库表,这

  • 浅谈mybatis 乐观锁实现,解决并发问题

    情景展示: 银行两操作员同时操作同一账户就是典型的例子. 比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050.这就是典型的并发问题. 乐观锁机制在一定程度上解决了这个问题.乐观锁,大多是基于数据版本(Version)记录机制实现.何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 "

  • Windows服务器应对高并发和DDOS攻击的配置方法

    windows系统本身就有很多机制可以用来提高性能和安全,其中有不少可以用来应对高并发请求和DDOS攻击的情况. 通过以下配置可以改善windows服务器性能: 一.应对高并发请求: 1.TCP连接延迟等待时间 TcpTimedWaitDelay: 这是设定TCP/IP 可释放已关闭连接并重用其资源前,必须经过的时间.关闭和释放之间的此时间间隔通称 TIME_WAIT状态或两倍最大段生命周期(2MSL)状态.在此时间内,重新打开到客户机和服务器的连接的成本少于建立新连接.减少此条目的值允许 TC

  • PHP利用Mysql锁解决高并发的方法

    前面写过利用文件锁来处理高并发的问题的,现在我们说另外一个处理方式,利用Mysql的锁来解决高并发的问题 先看没有利用事务的时候并发的后果 创建库存管理表 CREATE TABLE `storage` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `number` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=l

  • 如何利用Redis锁解决高并发问题详解

    redis技术的使用: redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动... redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力. 为什么redis的地位越来越高,我们为何不选择memcache,这是因为memcache只能存储字符串,而redis存储类型很丰富(例如有字符串.LIST.SET等),memcache每个值最大只能存储1M,存储资源非常有限,十分消耗内

  • Java常见面试题之多线程和高并发详解

    volatile 对 volatile的理解 volatile 是一种轻量级的同步机制. 保证数据可见性 不保证原子性 禁止指令重排序 JMM JMM(Java 内存模型)是一种抽象的概念,描述了一组规则或规范,定义了程序中各个变量的访问方式. JVM运行程序的实体是线程,每个线程创建时 JVM 都会为其创建一个工作内存,是线程的私有数据区域.JMM中规定所有变量都存储在主内存,主内存是共享内存.线程对变量的操作在工作内存中进行,首先将变量从主内存拷贝到工作内存,操作完成后写会主内存.不同线程间

  • mybatis plus乐观锁及实现详解

    目录 乐观锁 一.什么是乐观锁 二.MP来实现乐观锁 1.在实体类里增加对于的字段,并且加上自动填充(你也可以每次手动填充) 2. 配置插件 3.测试乐观锁 乐观锁 乐观锁”这个词以前我也没听过.上次在测试需求的时候,查询数据库发现有一个version字段,于是请教开发这个字干嘛使 人家回复我:乐观锁,解决并发更新用的.当时大家都忙,咱也不敢多问. 今天就来折腾一下“乐观锁”. 一.什么是乐观锁 乐观锁其实用一句话来形容其作用就是:当要更新一条记录的时候,希望这条记录没有被别人更新,从而实现线程

  • Java 高并发九:锁的优化和注意事项详解

    摘要 本系列基于炼数成金课程,为了更好的学习,做了系列的记录. 本文主要介绍: 1. 锁优化的思路和方法 2. 虚拟机内的锁优化 3. 一个错误使用锁的案例 4. ThreadLocal及其源码分析 1. 锁优化的思路和方法 在[高并发Java 一] 前言中有提到并发的级别. 一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点. 这里提到的锁优化,是指在阻塞式的情况下,如何让性能不要变得太差.但是再怎么优化,一般来说性能都会比无锁的情况差一点. 这里要注意的是,在[高并

随机推荐