Continued from the previous article http://liaoke0123.iteye.com/blog/2375469
Some friends may ask, what if one acquires a lock and performs a time-consuming task, and the time-consuming task takes longer than the default lock release time of the lock.
In fact, redisson has automatically released locks to avoid starvation locks.
In terms of timeout, our business can not allow it, so I added a fallback strategy.
The same distributed lock callback interface
package com.example.demo.redis2; import javax.persistence.Transient; /** * Distributed lock callback interface * * @author lk */ public interface DistributedLockCallback<T> { /** * The caller must implement the business logic that requires distributed locks in this method * * @return */ public T process(); /** * Caller business * Service downgrade when timeout * used with process */ public T fallback(); /** * Get the distributed lock name * * @return */ public String getLockName(); }
You can see that I created a new fallback interface
package com.example.demo.redis2.service; import com.example.demo.redis2.DistributedLockCallback; import com.example.demo.redis2.DistributedLockTemplate; import com.example.demo.redis2.dao.TestEntityRepository; import com.example.demo.redis2.entity.TestEntity; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.transaction.Transactional; /** * Created by LiaoKe on 2017/5/22. */ @Service public class AsyncService { @Resource TestEntityRepository ts; @Resource DistributedLockTemplate distributedLockTemplate; /** * lock */ @Async @Transactional public void addAsync () { distributedLockTemplate.lock(new DistributedLockCallback<Object>(){ @Override public Object process() { add(); return null; } @Override public Object fallback() { reduce(); return null; } @Override public String getLockName() { return "MyLock"; } }); } /** * Unlocked */ @Async public void addNoAsync(){ add(); } /** * Test async methods * Without distributed locks * num numbers will be confusing */ @Async public void add(){ try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace (); } if(ts.findAll().size()==0){ TestEntity t = new TestEntity (); t.setNum (1); ts.saveAndFlush(t); }else{ TestEntity dbt = ts.findAll().get(0); dbt.setNum (dbt.getNum () + 1); ts.saveAndFlush(dbt); } } /** * fallback */ @Async private void reduce(){ if(ts.findAll().size()==0){ TestEntity t = new TestEntity (); t.setNum (1); ts.saveAndFlush(t); }else{ TestEntity dbt = ts.findAll().get(0); dbt.setNum (dbt.getNum () - 1); ts.saveAndFlush(dbt); } } }
Fallback implements the rollback of business timeouts, the number is reduced by 1, and the @Transactional transaction annotation is added to the method to prevent exceptions from occurring in fallback, but the number is missing +1
Due to the default lock settings we are using, the timeout is 5 seconds, to simulate the timeout I have the thread paused for 6 seconds in the add() method. Let's look at the effect.
package com.example.demo.redis2; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import javax.persistence.Transient; import java.util.concurrent.TimeUnit; /** * Single Instance mode distributed lock template * * @author lk */ public class SingleDistributedLockTemplate implements DistributedLockTemplate { private static final long DEFAULT_TIMEOUT = 5; private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS; private RedissonClient redisson; public SingleDistributedLockTemplate() { } public SingleDistributedLockTemplate(RedissonClient redisson) { this.redisson = redisson; } @Override public <T> T lock(DistributedLockCallback<T> callback) { return lock(callback, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT); } @Override public <T> T lock(DistributedLockCallback<T> callback, long leaseTime, TimeUnit timeUnit) { RLock lock = null; try { System.out.println("Acquire lock......"); lock = redisson.getLock(callback.getLockName()); lock.lock(leaseTime, timeUnit); T d = callback.process(); return d; } finally { if (lock != null) { if(!lock.isHeldByCurrentThread()){ System.out.println("Automatically release the lock after timeout......"); callback.fallback(); }else{ System.out.println("Release lock......"); lock.unlock(); } } } } public void setRedisson(RedissonClient redisson) { this.redisson = redisson; } }
The same url (see the previous article) access result is as follows . It can be
seen that the database is not +1, and the fallback strategy is successful.
Things must be added, let's simulate fallback failure now
/** * fallback */ @Async private void reduce(){ int i = 5 /0 ; if(ts.findAll().size()==0){ TestEntity t = new TestEntity (); t.setNum (1); ts.saveAndFlush(t); }else{ TestEntity dbt = ts.findAll().get(0); dbt.setNum (dbt.getNum () - 1); ts.saveAndFlush(dbt); } }
Added a runtimeException
and get rid of @Trabsactional
Let's see the results
However, the data in the database has increased, and the display is not acceptable.
Now we add transaction annotations
It can be seen that under the transaction, even the first +1 is not submitted, our transaction strategy is successful
Attach the demo below