概述我之前参与过一个库存系统。由于其业务的复杂性,我开发了很多应用程序来支持它。这样,一条库存数据可能会有多个应用同时修改库存数据。比如计划任务域xx.cron,SystemA域和SystemB域有几个JAVA应用程序,可能同时修改同一个库存数据。如果没有协调,就会有脏数据。跨JAVA进程的线程协调,可以使用DB或Redis等外部环境。下面介绍如何使用DB实现分布式锁。设计本文设计的分布式锁的交互方式如下:根据业务字段生成transaction_id,并线程安全地创建锁资源根据transaction_id申请锁释放锁动态创建锁资源使用synchronized关键字时,你必须指定一个锁对象。synchronized(obj){}进程中的线程可以根据obj进行同步。obj在这里可以理解为锁对象。线程要想进入synchronized代码块,必须先持有obj对象的锁。这种锁是JAVA中内置的锁,创建它的过程是线程安全的。那么借助DB,如何保证创建锁的过程是线程安全的呢?您可以在数据库中使用UNIQUEKEY功能。一旦出现重复键,由于UNIQUEKEY的唯一性,会抛出异常。在JAVA中是SQLIntegrityConstraintViolationException。createtabledistributed_lock(idBIGINTUNSIGNEDPRIMARYKEYAUTO_INCREMENTCOMMENT'自增主键',transaction_idvarchar(128)NOTNULLDEFAULT''COMMENT'事务id',last_update_timeTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPNOTNULLCOMMENT'最后更新时间',create_timeTIMESTAMPDEFAULT'0000-00-0000:00:00'NOTNULLCOMMENT'创建时间',UNIQUEKEY`idx_transaction_id`(`transaction_id`))transaction_id是交易ID。比如可以用仓库+条码+销售模式组合成一个transaction_id,表示某个仓库在某个销售模式下的某个条码资源。不同的条形码,当然有不同的transaction_ids。如果两个应用创建相同transaction_id的锁资源,则只能创建一个应用成功。如果成功插入一条distributed_lock记录,则表示成功创建了一个锁资源。DB连接池列表设计在写操作频繁的业务系统中,通常会进行数据库的划分,以降低单库写的压力,提高写操作的吞吐量。如果采用分库,业务数据自然会分配到各个库中。在这个水平拆分的多数据库上使用DB分布式锁,可以自定义一个DataSource列表。并暴露一个getConnection(StringtransactionId)方法,根据transactionId找到对应的Connection。实际代码如下:packagedlock;importcom.alibaba.druid.pool.DruidDataSource;importorg.springframework.stereotype.Component;importjavax.annotation.PostConstruct;importjava.io.FileInputStream;importjava.io.IOException;importjava.sql.Connection;importjava。util.ArrayList;importjava.util.List;importjava.util.Properties;@ComponentpublicclassDataSourcePool{privateListdlockDataSources=newArrayList<>();@PostConstructprivatevoidinitDataSourceList()throwsIOException{Propertiesproperties=newProperties();FileInputStreamfis=newFileInputStream("db.properties");properties.load(fis);IntegerlockNum=Integer.valueOf(properties.getProperty("DLOCK_NUM"));for(inti=0;i();每次调用lock方法时,将Connection放在ThreadLocal中。我们修改锁方法。publicbooleanlock(StringtransactionId)throwsException{Connectionconnection=null;PreparedStatementpreparedStatement=null;ResultSetresultSet=null;try{connection=dataSourcePool.getConnection(transactionId);threadLocalConn.set(connection);preparedStatement=connection.prepareStatement("SELECT*FROMdistributed_lockidWHERE?transaction);preparedStatement.setString(1,transactionId);resultSet=preparedStatement.executeQuery();if(!resultSet.next()){connection.rollback();threadLocalConn.remove();returnfalse;}returntrue;}catch(Exceptione){if(connection!=null){connection.rollback();threadLocalConn.remove();}抛出;}最后{if(preparedStatement!=null){preparedStatement.close();}if(resultSet!=null){resultSet.close();}if(connection!=null){connection.close();}}}这样,在获取到Connection时,将其设置为ThreadLocal,如果lock方法异常,则将其移除来自ThreadLocal。通过这几步,我们可以实现解锁操作。我们在DistributedLoc中添加一个unlock方法k.publicvoidunlock()throwsException{Connectionconnection=null;try{connection=threadLocalConn.get();if(!connection.isClosed()){connection.commit();connection.close();threadLocalConn.remove();}}catch(Exceptione){if(connection!=null){connection.rollback();connection.close();}threadLocalConn.remove();throwe;}}缺点是毕竟要用DB实现分布式锁,仍然会对DB造成一定的压力。当时考虑使用DB进行分发的一个重要原因是我们的应用是后端应用,通常流量不会很大,但关键是要保证库存数据的正确性。对于前端库存系统等操作,比如添加购物车占用库存,最好不要使用DB实现分布式锁。进一步考虑如何锁定多个数据副本。例如,对于库存操作,您需要修改物理库存和虚拟库存。当你想锁定实物库存时,你也需要锁定虚拟库存。其实并不难。参考lock方法,写一个multiLock方法,提供多个transactionId入参,for循环处理。这个后续有时间补上。