1</span><span style="color: #008000">/**</span><span style="color: #008000"> 2 * 加锁 3 </span><span style="color: #008000">*/</span> 4<span style="color: #0000ff">boolean</span><span style="color: #000000"> lock(String outerSerialNo, String custNo, LockSource source); 5 6</span><span style="color: #008000">/**</span><span style="color: #008000"> 7 * 解锁 8 </span><span style="color: #008000">*/</span> 9<span style="color: #0000ff">void</span><span style="color: #000000"> unLock(String outerSerialNo, String custNo, LockSource source, LockStatus targetStatus);
}</span></pre>
<div class="cnblogs\_code\_toolbar"><span class="cnblogs\_code\_copy"><a href="javascript:void(0);" onclick="copyCnblogsCode(this)" title="复制代码"><img src="https://my.oschina.net//common.cnblogs.com/images/copycode.gif" alt="复制代码"></a></span></div></div> <span class="cnblogs\_code\_collapse" style="display: inline;">View Code</span></div> <p>锁接口实现类:DbLockManagerImpl.java</p> <div class="cnblogs\_code"><img id="code\_img\_closed\_e79c666f-0f72-4273-add4-9382bc8c369c" class="code\_img\_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code\_img\_opened\_e79c666f-0f72-4273-add4-9382bc8c369c" class="code\_img\_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt=""> <div id="cnblogs\_code\_open\_e79c666f-0f72-4273-add4-9382bc8c369c" class="cnblogs\_code\_hide"> <pre><span style="color: #008000">/\*\*</span><span style="color: #008000"> \* \* 数据库锁实现<br> \* \* </span><span style="color: #808080">@author</span><span style="color: #008000"> fugaoyang \* </span><span style="color: #008000">\*/</span><span style="color: #000000"> @Service </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> DbLockManagerImpl <span style="color: #0000ff">implements</span><span style="color: #000000"> DbLockManager {1</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">final</span> Logger LOG = LoggerFactory.getLogger(<span style="color: #0000ff">this</span><span style="color: #000000">.getClass()); 2 3@Autowired 4</span><span style="color: #0000ff">private</span><span style="color: #000000"> DbSyncLockMapper lockMapper; 5 6@Transactional 7</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">boolean</span><span style="color: #000000"> lock(String outerSerialNo, String custNo, LockSource source) { 8 9 </span><span style="color: #0000ff">boolean</span> isLock = <span style="color: #0000ff">false</span><span style="color: #000000">; 10 TradeSyncLock lock </span>= <span style="color: #0000ff">null</span><span style="color: #000000">; 11 </span><span style="color: #0000ff">try</span><span style="color: #000000"> { 12 lock </span>=<span style="color: #000000"> lockMapper.find(outerSerialNo, custNo, source.getCode()); 13 14 </span><span style="color: #0000ff">if</span> (<span style="color: #0000ff">null</span> ==<span style="color: #000000"> lock) { 15 lock </span>= <span style="color: #0000ff">new</span><span style="color: #000000"> TradeSyncLock(); 16 createLock(lock, outerSerialNo, custNo, source); 17 18 </span><span style="color: #0000ff">int</span> num =<span style="color: #000000"> lockMapper.insert(lock); 19 </span><span style="color: #0000ff">if</span> (num == 1<span style="color: #000000">) { 20 isLock </span>= <span style="color: #0000ff">true</span><span style="color: #000000">; 21 } 22 23 LOG.info(ThreadLogUtils.getLogPrefix() </span>+ "加入锁,客户号[{}],锁类型[{}]"<span style="color: #000000">, custNo, source.getCode()); 24 </span><span style="color: #0000ff">return</span><span style="color: #000000"> isLock; 25 } 26 27 </span><span style="color: #008000">//</span><span style="color: #008000"> 根据交易类型进行加锁</span> 28 isLock =<span style="color: #000000"> switchSynsLock(lock, source); 29 LOG.info(ThreadLogUtils.getLogPrefix() </span>+ "更新锁,客户号[{}],锁类型[{}]"<span style="color: #000000">, custNo, source.getCode()); 30 31 } </span><span style="color: #0000ff">catch</span><span style="color: #000000"> (Exception e) { 32 LOG.error(ThreadLogUtils.getLogPrefix() </span>+ "交易加锁异常, 客户号:" +<span style="color: #000000"> custNo, e); 33 } 34 </span><span style="color: #0000ff">return</span><span style="color: #000000"> isLock; 35} 36 37@Transactional 38</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span><span style="color: #000000"> unLock(String outerSerialNo, String custNo, LockSource source, LockStatus targetStatus) { 39 40 </span><span style="color: #0000ff">try</span><span style="color: #000000"> { 41 TradeSyncLock lock </span>=<span style="color: #000000"> lockMapper.find(outerSerialNo, custNo, source.getCode()); 42 43 </span><span style="color: #0000ff">if</span> (<span style="color: #0000ff">null</span> !=<span style="color: #000000"> lock) { 44 lockMapper.update(lock.getId(), targetStatus.getName(), LockStatus.P.getName(), 45 ThreadLogUtils.getCurrThreadUuid(), ThreadLogUtils.getCurrThreadUuid()); 46 } 47 48 LOG.info(ThreadLogUtils.getLogPrefix() </span>+ "释放锁,客户号[{}],锁类型[{}]"<span style="color: #000000">, custNo, source.getCode()); 49 } </span><span style="color: #0000ff">catch</span><span style="color: #000000"> (Exception e) { 50 LOG.error(ThreadLogUtils.getLogPrefix() </span>+ "释放锁异常, 客户号:{}"<span style="color: #000000">, custNo, e); 51 } 52} 53 54</span><span style="color: #008000">/**</span><span style="color: #008000"> 55 * 匹配加锁 56 </span><span style="color: #008000">*/</span> 57<span style="color: #0000ff">private</span> <span style="color: #0000ff">boolean</span><span style="color: #000000"> switchSynsLock(TradeSyncLock lock, LockSource source) { 58 </span><span style="color: #0000ff">boolean</span> isLock = <span style="color: #0000ff">false</span><span style="color: #000000">; 59 60 </span><span style="color: #0000ff">switch</span><span style="color: #000000"> (source) { 61 </span><span style="color: #0000ff">case</span><span style="color: #000000"> WITHDRAW: 62 ; 63 isLock </span>=<span style="color: #000000"> tradeSynsLock(lock); 64 </span><span style="color: #0000ff">break</span><span style="color: #000000">; 65 </span><span style="color: #0000ff">case</span><span style="color: #000000"> WITHDRAW_RETRY: 66 ; 67 isLock </span>=<span style="color: #000000"> retrySynsLock(lock); 68 </span><span style="color: #0000ff">break</span><span style="color: #000000">; 69 </span><span style="color: #0000ff">default</span><span style="color: #000000">: 70 ; 71 } 72 </span><span style="color: #0000ff">return</span><span style="color: #000000"> isLock; 73} 74 75</span><span style="color: #008000">/**</span><span style="color: #008000"> 76 * 交易同步锁 77 </span><span style="color: #008000">*/</span> 78<span style="color: #0000ff">private</span> <span style="color: #0000ff">boolean</span><span style="color: #000000"> tradeSynsLock(TradeSyncLock lock) { 79 </span><span style="color: #008000">//</span><span style="color: #008000"> 处理中的不加锁,即不执行交易操作</span> 80 <span style="color: #0000ff">if</span><span style="color: #000000"> (LockStatus.P.getName().equals(lock.getStatus())) { 81 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">; 82 } 83 84 </span><span style="color: #0000ff">int</span> num =<span style="color: #000000"> lockMapper.update(lock.getId(), LockStatus.P.getName(), LockStatus.S.getName(), 85 ThreadLogUtils.getCurrThreadUuid(), </span><span style="color: #0000ff">null</span><span style="color: #000000">); 86 </span><span style="color: #0000ff">if</span> (num == 1<span style="color: #000000">) { 87 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">true</span><span style="color: #000000">; 88 } 89 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">; 90} 91 92</span><span style="color: #008000">/**</span><span style="color: #008000"> 93 * 补偿同步锁 94 </span><span style="color: #008000">*/</span> 95<span style="color: #0000ff">private</span> <span style="color: #0000ff">boolean</span><span style="color: #000000"> retrySynsLock(TradeSyncLock lock) { 96 </span><span style="color: #008000">//</span><span style="color: #008000"> 处理中或处理完成的不加锁,即不执行补偿操作</span> 97 <span style="color: #0000ff">if</span> (LockStatus.P.getName().equals(lock.getStatus()) ||<span style="color: #000000"> LockStatus.S.getName().equals(lock.getStatus())) { 98 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">; 99 } 100 101 </span><span style="color: #0000ff">int</span> num =<span style="color: #000000"> lockMapper.update(lock.getId(), LockStatus.P.getName(), LockStatus.F.getName(), 102 ThreadLogUtils.getCurrThreadUuid(), </span><span style="color: #0000ff">null</span><span style="color: #000000">); 103 </span><span style="color: #0000ff">if</span> (num == 1<span style="color: #000000">) { 104 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">true</span><span style="color: #000000">; 105 } 106 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">; 107} 108 109</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span><span style="color: #000000"> createLock(TradeSyncLock lock, String outerSerialNo, String custNo, LockSource source) { 110 lock.setOuterSerialNo(outerSerialNo); 111 lock.setCustNo(custNo); 112 lock.setSourceCode(source.getCode()); 113 lock.setThreadNo(ThreadLogUtils.getCurrThreadUuid()); 114 lock.setStatus(LockStatus.P.getName()); 115 lock.setRemark(source.getDesc()); 116}
}</span></pre>
</div> <span class="cnblogs\_code\_collapse">View Code</span></div> <p>获取当前线程号以及打印uuid工具类ThreadLogUtils.Java</p> <div class="cnblogs\_code"><img id="code\_img\_closed\_25ad7b32-da05-4059-b8e6-077baabc33d8" class="code\_img\_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code\_img\_opened\_25ad7b32-da05-4059-b8e6-077baabc33d8" class="code\_img\_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt=""> <div id="cnblogs\_code\_open\_25ad7b32-da05-4059-b8e6-077baabc33d8" class="cnblogs\_code\_hide"> <pre><span style="color: #008000">/\*\*</span><span style="color: #008000"> \* \* 线程处理<br> \* \* </span><span style="color: #808080">@author</span><span style="color: #008000"> fugaoyang \* </span><span style="color: #008000">\*/</span> <span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span><span style="color: #000000"> ThreadLogUtils {1</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> ThreadLogUtils instance = <span style="color: #0000ff">null</span><span style="color: #000000">; 2 3</span><span style="color: #0000ff">private</span><span style="color: #000000"> ThreadLogUtils() { 4 setInstance(</span><span style="color: #0000ff">this</span><span style="color: #000000">); 5} 6 7</span><span style="color: #008000">//</span><span style="color: #008000"> 初始化标志</span> 8<span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">final</span> Object __noop = <span style="color: #0000ff">new</span><span style="color: #000000"> Object(); 9</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> ThreadLocal<Object> __flag = <span style="color: #0000ff">new</span> InheritableThreadLocal<Object><span style="color: #000000">() { 10 @Override 11 </span><span style="color: #0000ff">protected</span><span style="color: #000000"> Object initialValue() { 12 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">null</span><span style="color: #000000">; 13 } 14}; 15 16</span><span style="color: #008000">//</span><span style="color: #008000"> 当前线程的UUID信息,主要用于打印日志;</span> 17<span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> ThreadLocal<String> currLogUuid = <span style="color: #0000ff">new</span> InheritableThreadLocal<String><span style="color: #000000">() { 18 @Override 19 </span><span style="color: #0000ff">protected</span><span style="color: #000000"> String initialValue() { 20 </span><span style="color: #0000ff">return</span> UUID.randomUUID().toString()<span style="color: #008000">/*</span><span style="color: #008000"> .toUpperCase() </span><span style="color: #008000">*/</span><span style="color: #000000">; 21 } 22}; 23 24</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> ThreadLocal<String> currThreadUuid = <span style="color: #0000ff">new</span> ThreadLocal<String><span style="color: #000000">() { 25 @Override 26 </span><span style="color: #0000ff">protected</span><span style="color: #000000"> String initialValue() { 27 </span><span style="color: #0000ff">return</span><span style="color: #000000"> UUIDGenerator.getUuid(); 28 } 29}; 30 31</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span><span style="color: #000000"> clear(Boolean isNew) { 32 </span><span style="color: #0000ff">if</span><span style="color: #000000"> (isNew) { 33 34 currLogUuid.remove(); 35 36 __flag.remove(); 37 38 currThreadUuid.remove(); 39 40 } 41} 42 43</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span><span style="color: #000000"> String getCurrLogUuid() { 44 </span><span style="color: #0000ff">if</span> (!<span style="color: #000000">isInitialized()) { 45 </span><span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> IllegalStateException("TLS未初始化"<span style="color: #000000">); 46 } 47 48 </span><span style="color: #0000ff">return</span><span style="color: #000000"> currLogUuid.get(); 49} 50 51</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span><span style="color: #000000"> String getCurrThreadUuid() { 52 </span><span style="color: #0000ff">return</span><span style="color: #000000"> currThreadUuid.get(); 53} 54 55</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span><span style="color: #000000"> clearCurrThreadUuid() { 56 currThreadUuid.remove(); 57} 58 59</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span><span style="color: #000000"> String getLogPrefix() { 60 </span><span style="color: #0000ff">if</span> (!<span style="color: #000000">isInitialized()) { 61 </span><span style="color: #0000ff">return</span> ""<span style="color: #000000">; 62 } 63 64 </span><span style="color: #0000ff">return</span> "<uuid=" + getCurrLogUuid() + ">"<span style="color: #000000">; 65} 66 67</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">boolean</span><span style="color: #000000"> isInitialized() { 68 </span><span style="color: #0000ff">return</span> __flag.get() != <span style="color: #0000ff">null</span><span style="color: #000000">; 69} 70 71</span><span style="color: #008000">/**</span><span style="color: #008000"> 72 * 初始化上下文,如果已经初始化则返回false,否则返回true<br/> 73 * 74 * </span><span style="color: #808080">@return</span> 75 <span style="color: #008000">*/</span> 76<span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">boolean</span><span style="color: #000000"> initialize() { 77 </span><span style="color: #0000ff">if</span><span style="color: #000000"> (isInitialized()) { 78 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">; 79 } 80 81 __flag.set(__noop); 82 </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">true</span><span style="color: #000000">; 83} 84 85</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span><span style="color: #000000"> setInstance(ThreadLogUtils instance) { 86 ThreadLogUtils.instance </span>=<span style="color: #000000"> instance; 87} 88 89</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span><span style="color: #000000"> ThreadLogUtils getInstance() { 90 </span><span style="color: #0000ff">return</span><span style="color: #000000"> instance; 91}
}</span></pre>
</div> <span class="cnblogs\_code\_collapse">View Code</span></div> <p>两种锁的实现的大致思路如下:</p> <p><strong>1.交易同步锁</strong></p> <p>当一个客户来取现,第一次进入时,会插入一条当前线程,状态是P,操作是取现的锁,取现成功后根据当前线程号会更新成功;</p> <p>当一个客户同时多个取现操作时,只有一个取现操作会加锁成功,其它会加锁失败;</p> <p>当一个客户已经在取现中,这时数据库已经有一条状态P的锁,该客户同时又做了取现,这个取现动作会尝试加锁而退出;</p> <p><strong>2.交易重试补偿锁</strong></p> <p>1.当一个客户取现加锁成功,因调用第三方支付接口超时时,后台会对该笔交易重新发起重试打款操作,这时会新加一条当前交易流水号,当前线程号,状态是P,操作是取现重试的锁,重试的支付结果是成功的话,更新该条锁数据为Y状态,否则更新该条数据为F状态;</p> <p>2.当重试支付失败后,再去重试打款时,发现锁的状态是F,这时把F更新为P,继续重试,根据重试结果更新锁状态。</p> <p>上面实现的是一个最基本的数据库分布式锁,满足的并发量也是基于数据库所能扛得住的,性能基本可以满足普通的交易量。</p> <p><strong>后续可以优化的部分:</strong></p> <p>1.当一个用户同时多次获取lock时,因为目前是用的乐观锁,只会有一个加锁成功,可以优化成加入while(true)循环获取lock,当失败次数到达指定次数时退出,当前的操作结束。</p> <p>2.当锁表数据量随着时间增大时,可以考虑按用户对锁表进行分表分库,以减小数据库方面的压力。</p> <p>3.对锁的操作可以抽象出来,作为抽象实现,比如具体的取现操作只关心取现这个业务实现。</p> <p> </p> <p>因为时间有限,写的比较仓促,希望大家有问题可以提出,相互探讨~~</p> <p>完整示例代码后续会更新到github。</p> <p> </p> <style>p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco }</style> <style>p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco }</style> <style>p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco }</style></div>