导读:
最近发现某个项目的用户流水和帐款金额出现了并发问题, 而后使用乐观锁解决了这个问题, 可是由于有跑批任务 在同一时刻 同一用户的帐款 会增长多条流水因而就出现:redis
的异常, 虽然 帐款的问题是解决了 (单笔没问题后面的批处理仍是会把失败的流水继续推过来) 但仍是不是一个比较好且简单的解, 开动脑筋,在不改动主要业务逻辑的状况下如何对其打补丁呢?
最简单的方式就是将并行改成串行 而后就在数据库查询修改的方法外面加了synchronized 关键字, 但加上以后并行查询的数据仍是老数据 synchronized 竟然失效了, 不该该啊, 而后想到了应该是事务未提交致使的,可是方法上有事务注解啊, 接着想到了 事务是经过动态代理实现的显然动态代理的方法并无synchronized关键字修饰.
这是一个例子:spring
@Service class WalletApplicationSerive{ @Transactional(rollbackFor = Exception.class) public synchronized void pay(accountId, amount, outerCode){ // 数据库查询以后修改 } }
当咱们使用事务的时候 其背后的实现是动态代理结合IOC容器获取出来的WalletApplicationSerive 的实例已经被Spring 换成了(spring 实现的更复杂, 为了方便理解这里以静态代理为例 ,这只是一个简单的示例)数据库
class WalletApplicationSeriveProxy{ private WalletApplicationSerivce tagert; public void pay(accountId, amount, outerCode){ tx.begin() try{ tagert.pay(accountId, amount, outerCode) }catch(Exception e){ tx.rollback() throw e; } tx.commit() } }
动态代理:并发
// 目标对象 Object target ; Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 带有@Transcational注解的方法都会被拦截 tx.begin(); try{ method.invoke(target, args); }catch(Exception e){ tx.rollback(); throw e; } tx.commit(); return null; } });
一切都变得简单了 这也就是synchronized与ReentrantLock在spring 事务中失效了的缘由.
要如何解决呢? 很简单 在 代理类外面增长上事务.ide
@Service class WalletApplicationSerive{ @Autowired InnerWalletApplicationSerive inner; public synchronized void pay(accountId, amount, outerCode){ inner.pay(accountId, amount, outerCode) } @Service static class InnerWalletApplicationSerive{ @Transactional(rollbackFor = Exception.class) public void pay(accountId, amount, outerCode){ // 数据库查询以后修改 } } }
问题解决, 但这里锁的粒度太粗了, 能够在对锁进行更细的粒度改造:ui
@Service class WalletApplicationSerive{ @Autowired InnerWalletApplicationSerive inner; public void pay(accountId, amount, outerCode){ synchronized(WalletLockManger.getLock(accountId)){ inner.pay(accountId, amount, outerCode) } } @Service static class InnerWalletApplicationSerive{ @Transactional(rollbackFor = Exception.class) public void pay(accountId, amount, outerCode){ // 数据库查询以后修改 } } } class WalletLockManger { private static final Map<String, String> lockMap = new ConcurrentHashMap<>(); public static String getLock(String accountId) { return lockMap.computeIfAbsent(accountId, Function.identity()); } }
synchronized(WalletLockManger.getLock(accountId)) 这个里有很大的改造空间, 后面若是 要部署多个实例的时候 能够将这里换成redis的锁.代理