Moving on from a previous article, a small program recently developed encountered a requirement to implement distributed transaction management

The business requirements

In the process of using the small program, users can view scenic spots and mark whether they want to go to scenic spots, regions or cities. Then they need to count the number of people who are marked at a place and record whether a user is marked as wanting to go to a place. Two tables are used to store data. A user intent sheet records whether a user marks a location as wanting to go. Since there may be multiple users marking a place at the same time, after each user clicks the “Want to go” button in the front end, the background receives a request to query the number of marks in a city from the database, add 1 again, and then update to the database. Query the number of tokens from the database, add 1, and then update to the database. The database data must be locked, and only one process can process it at a time. Otherwise, data will be out of sync

I use RedLock for distributed lock management and Spring annotated transaction management. In the implementation process, we encountered the following two profound problems: 1, distributed lock and Spring annotation transaction sharing problem; 2, lock timeout before transaction commit problem

Use distributed lock RedLock and Spring transaction implementation

The initial implementation code is as follows:

Public markScenicSpot(){// Set the lock to destId RLock lock = redisson.getLock("Afanti_markScenicSpot_updateCountwantAndCountbeenLock_" + ID); Long lockTimeOut = 30; ** Boolean SUCCESS = lock.tryLock(5, lockTimeOut, timeunit.seconds); ** if (success) {try {catch (Exception e){throw e; } finally{// unlock **lock.unlock(); **}} else {log.error(" Lock failed! Update failed!" ); throw new BizException(ErrorCodeEnum.PROCESS_DATA_ERROR); }}Copy the code

Problem: High concurrency is the lock does not take effect

The spring annotation transaction @Transactional and distributed locks cannot be used together. This is because @Transactional determines whether the transaction is rolled back or committed by whether the method throws an exception when the method has ended. But we have to release the lock before the method ends, so after the lock is released, there is no commit yet, because the lock has been released, other processes can acquire the lock and query the number of tokens from the database, but at this point the previous process has not committed. The data found by this process is not the latest data. This problem when I was trying to take a long time, because the lock release and commit the transaction between a few milliseconds, always thought that such a short time before may be the problem here, have doubted but himself and abandoned Although this process as long as a short period of time (I’m in the process of the actual test as long as the process a few milliseconds), but high concurrency will still be a problem.

Solution 1:

Since annotated transactions are not available, I changed to manual transaction management and added the following code.

Public markScenicSpot(){// Set the lock to destId RLock lock = redisson.getLock("Afanti_markScenicSpot_updateCountwantAndCountbeenLock_" + ID); Long lockTimeOut = 30; Boolean SUCCESS = lock.tryLock(5, lockTimeOut, timeunit.seconds); if(success){ **DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); / / what isolation level TransactionStatus status = transactionManager. GetTransaction (def); ** try {// business logic implementation //...... * * / / to commit the transaction transactionManager.com MIT (status); * *} the catch (Exception e) {* * / / rollback transaction transactionManager. Rollback (status); **} finally{// unlock lock.unlock(); }} else {log.error(" Failed to get lock! Update failed!" ); throw new BizException(ErrorCodeEnum.PROCESS_DATA_ERROR); }}Copy the code

Problem: lock timeout item exception

1. Lock timeout problem Synchronization problem solved after manual transaction management. However, another problem occurs where the lock times out but the transaction is not committed. Since the current process timed out but did not commit the lock, other processes can obtain the lock and query the destination landmark count from the database, but the data obtained is not the updated data, and the data obtained is wrong.

Solution 2:

In the case of lock timeout, you only need to add a judgment before the current process is submitted to determine whether it times out. If it does, an exception will be thrown to exit. Add the following code:

Public markScenicSpot(){// Set the lock to destId RLock lock = redisson.getLock("Afanti_markScenicSpot_updateCountwantAndCountbeenLock_" + ID); Long lockTimeOut = 30; Boolean SUCCESS = lock.tryLock(5, lockTimeOut, timeunit.seconds); Long getLockTime= system.currentTimemillis (); * * the if (success) {/ / transaction management DefaultTransactionDefinition def = new DefaultTransactionDefinition (); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); / / what isolation level TransactionStatus status = transactionManager. GetTransaction (def); // get the transaction state try {// business logic implementation //...... / / to commit the transaction, whether the lock timeout (System. * * if currentTimeMillis () - getLockTime < lockTimeOut * 1000) {transactionManager.com MIT (status); Log.info (" Commit transaction "); } else {log.error(" Exception: program execution time too long, lock timeout!" ); throw new BizException(ErrorCodeEnum.PROCESS_DATA_ERROR); * *}} the catch (Exception e) {/ / rollback transaction transactionManager. Rollback (status); } finally{// unlock lock.unlock(); }} else {log.error(" Failed to get lock! Update failed!" ); throw new BizException(ErrorCodeEnum.PROCESS_DATA_ERROR); }}Copy the code

conclusion

In the case of high concurrency, distributed transactions are prone to failure, so it is necessary to analyze various scenarios for possible failure, and to do adequate testing for all possible problems to ensure that the program is robust.