Using redis to implement distributed locks, most of the online searches are implemented using java jedis.
The distributed lock implementation officially recommended by redis is redisson http://ifeve.com/redis-lock/
The following are the steps for spring boot to implement distributed locks
Official dependencies need to be added to the project pom. I am 1.8JDK.
<!-- redisson --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.4.2</version> </dependency>
Define a callback class for a distributed lock
package com.example.demo.redis2; /** * 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(); /** * Get the distributed lock name * * @return */ public String getLockName(); }
Distributed lock operation template
package com.example.demo.redis2; import java.util.concurrent.TimeUnit; /** * Distributed lock operation template * * @author lk */ public interface DistributedLockTemplate { /** * Use distributed locks, use the lock default timeout. * * @param callback * @return */ public <T> T lock(DistributedLockCallback<T> callback); /** * Use distributed locks. Custom lock timeout * * @param callback * @param leaseTime Lock timeout. The lock is automatically released after a timeout. * @param timeUnit * @return */ public <T> T lock(DistributedLockCallback<T> callback, long leaseTime, TimeUnit timeUnit); }
Use redisson's simplest Single instance mode to implement distributed lock template interface
package com.example.demo.redis2; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; 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 { lock = redisson.getLock(callback.getLockName()); lock.lock(leaseTime, timeUnit); return callback.process(); } finally { if (lock != null) { lock.unlock(); } } } public void setRedisson(RedissonClient redisson) { this.redisson = redisson; } }
Create beans that can be managed by spring
package com.example.demo.redis2; import java.io.IOException; import java.io.InputStream; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.apache.log4j.Logger; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.FactoryBean; /** * Factory Bean for creating distributed lock template instances * * @author lk */ public class DistributedLockFactoryBean implements FactoryBean<DistributedLockTemplate> { private Logger logger = Logger.getLogger(DistributedLockFactoryBean.class); private LockInstanceMode mode; private DistributedLockTemplate distributedLockTemplate; private RedissonClient redisson; @PostConstruct public void init() { String ip = "127.0.0.1"; String port = "6379"; Config config=new Config(); config.useSingleServer().setAddress(ip+":"+port); redisson=Redisson.create(config); System.out.println("successfully connected to Redis Server"+"\t"+"connected"+ip+":"+port+"server"); } @PreDestroy public void destroy() { logger.debug("Destroy the distributed lock template"); redisson.shutdown(); } @Override public DistributedLockTemplate getObject() throws Exception { switch (mode) { case SINGLE: distributedLockTemplate = new SingleDistributedLockTemplate(redisson); break; } return distributedLockTemplate; } @Override public Class<?> getObjectType() { return DistributedLockTemplate.class; } @Override public boolean isSingleton() { return true; } public void setMode(String mode) { if (mode==null||mode.length()<=0||mode.equals("")) { throw new IllegalArgumentException("dlm.redisson.mode configuration item not found"); } this.mode = LockInstanceMode.parse(mode); if (this.mode == null) { throw new IllegalArgumentException("Unsupported distributed lock mode"); } } private enum LockInstanceMode { SINGLE; public static LockInstanceMode parse(String name) { for (LockInstanceMode modeIns : LockInstanceMode.values()) { if (modeIns.name().equals(name.toUpperCase())) { return modeIns; } } return null; } } }
Configure into spring boot
package com.example.demo.redis2; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Created by LiaoKe on 2017/5/22. */ @Configuration public class BeanConfig { @Bean public DistributedLockFactoryBean distributeLockTemplate(){ DistributedLockFactoryBean d = new DistributedLockFactoryBean(); d.setMode("SINGLE"); return d; } }
It is available so far.
In order to verify whether the lock is successful, I made the following example.
First, a database entity (using JPA) is created to simulate the number of purchased items. When purchased, num+1
In a high-concurrency environment, this is bound to be a problem, because there are operations on the same database source after the query is set.
package com.example.demo.redis2.entity; import org.hibernate.annotations.GenericGenerator; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; /** * Test class entity * Created by LiaoKe on 2017/5/22. */ @Entity public class TestEntity { @Id @GeneratedValue(generator = "system-uuid") @GenericGenerator(name = "system-uuid", strategy = "uuid") private String id; private Integer num; public String getId() { return id; } public void setId(String id) { this.id = id; } public Integer getNum() { return num; } public void setNum (Integer num) { this.num = num; } }
For specific database operations, locking and unlocking operations, it should be noted that I used @Async, asynchronous task annotation, I did not configure thread pool information, and used the default thread pool.
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; /** * Created by LiaoKe on 2017/5/22. */ @Service public class AsyncService { @Resource TestEntityRepository ts; @Resource DistributedLockTemplate distributedLockTemplate; /** * lock */ @Async public void addAsync () { distributedLockTemplate.lock(new DistributedLockCallback<Object>(){ @Override public Object process() { add(); 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 private void add(){ 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); } } }
Finally, two interfaces were simply run for testing
package com.example.demo; import com.example.demo.redis2.DistributedLockTemplate; import com.example.demo.redis2.service.AsyncService; import oracle.jrockit.jfr.StringConstantPool; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @SpringBootApplication @RestController @EnableAsync public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Resource AsyncService as; @GetMapping("") public void test(){ for(int i = 0 ;i<10000;i++){ as.addNoAsync(); } } @GetMapping("lock") public void test2(){ for(int i = 0 ;i<10000;i++){ as.addAsync (); } } }
Visit localhost:8888 and localhost:8888/lock
without locking
The database has exploded
The final data is strange
Using locked access
It can be seen that the stock increase is absolutely correct.
No database locks are used here, and based on redis, locking can be implemented on different network nodes.
This is just a simple implementation. In the real production environment, there are many problems that need to be paid attention to. The timing of timeout and lock release needs to be carefully studied. It is inconvenient to post the real project code here.
Reference blog: http://layznet.iteye.com/blog/2307179 Thanks to the author