spring boot 利用redisson实现redis的分布式锁
程序员文章站
2022-03-24 22:22:10
...
利用redis实现分布式锁,网上搜索的大部分是使用java jedis实现的。
redis官方推荐的分布式锁实现为redisson http://ifeve.com/redis-lock/
以下为spring boot实现分布式锁的步骤
项目pom中需要添加官方依赖 我是1.8JDK固为
<!-- redisson --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.4.2</version> </dependency>
定义一个分布式锁的回调类
package com.example.demo.redis2; /** * 分布式锁回调接口 * * @author lk */ public interface DistributedLockCallback<T> { /** * 调用者必须在此方法中实现需要加分布式锁的业务逻辑 * * @return */ public T process(); /** * 得到分布式锁名称 * * @return */ public String getLockName(); }
分布式锁操作模板
package com.example.demo.redis2; import java.util.concurrent.TimeUnit; /** * 分布式锁操作模板 * * @author lk */ public interface DistributedLockTemplate { /** * 使用分布式锁,使用锁默认超时时间。 * * @param callback * @return */ public <T> T lock(DistributedLockCallback<T> callback); /** * 使用分布式锁。自定义锁的超时时间 * * @param callback * @param leaseTime 锁超时时间。超时后自动释放锁。 * @param timeUnit * @return */ public <T> T lock(DistributedLockCallback<T> callback, long leaseTime, TimeUnit timeUnit); }
使用redisson最简单的Single instance mode实现分布式锁模板接口
package com.example.demo.redis2; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import java.util.concurrent.TimeUnit; /** * Single Instance mode 分布式锁模板 * * @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; } }
创建可以被spring管理的 Bean
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; /** * 创建分布式锁模板实例的工厂Bean * * @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("成功连接Redis Server"+"\t"+"连接"+ip+":"+port+"服务器"); } @PreDestroy public void destroy() { logger.debug("销毁分布式锁模板"); 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配置项"); } this.mode = LockInstanceMode.parse(mode); if (this.mode == null) { throw new IllegalArgumentException("不支持的分布式锁模式"); } } 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; } } }
配置进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; } }
目前为止已经可以使用。
为了验证锁是否成功,我做了如下例子。
首先建立了一个数据库实体(使用的JPA),模拟被购买的商品数量,当被购买后,num+1
在高并发环境下,这必定会有问题,因为在查询之后的设值,存在对同一数据库源的操作。
package com.example.demo.redis2.entity; import org.hibernate.annotations.GenericGenerator; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; /** * 测试类实体 * 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; } }
具体数据库操作,加锁和不加锁的操作,要注意我使用了@Async,异步任务注解,我没有配置线程池信息,使用的默认线程池。
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; /** * 加锁 */ @Async public void addAsync(){ distributedLockTemplate.lock(new DistributedLockCallback<Object>(){ @Override public Object process() { add(); return null; } @Override public String getLockName() { return "MyLock"; } }); } /** * 未加锁 */ @Async public void addNoAsync(){ add(); } /** * 测试异步方法 * 在不加分布式锁的情况下 * num数目会混乱 */ @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); } } }
最后为了测试简单跑了两个接口
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(); } } }
访问localhost:8888 及 localhost:8888/lock
在不加锁的情况下
数据库已经爆炸了
最后得到的数据奇奇怪怪
使用加锁后的访问
可以看到库存增加绝对正确。
此处并未使用任何数据库锁,并且基于redis,可在不同的网络节点实现上锁。
这只是简单的实现,在真正的生产环境中,还要注意许多问题,超时和放锁时机需要好好研究,在此不便贴真正项目代码。
参考博客:http://layznet.iteye.com/blog/2307179 感谢作者