spring boot 利用redisson实现redis的分布式锁

Posted 起个名字好难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring boot 利用redisson实现redis的分布式锁相关的知识,希望对你有一定的参考价值。

原文:http://liaoke0123.iteye.com/blog/2375469

 

利用redis实现分布式锁,网上搜索的大部分是使用java jedis实现的。

 

redis官方推荐的分布式锁实现为redisson http://ifeve.com/redis-lock/

 

以下为spring boot实现分布式锁的步骤

 

项目pom中需要添加官方依赖 我是1.8JDK固为

 

Pom代码  技术分享图片
  1. <!-- redisson -->  
  2. <dependency>  
  3.     <groupId>org.redisson</groupId>  
  4.     <artifactId>redisson</artifactId>  
  5.     <version>3.4.2</version>  
  6. </dependency>  

 定义一个分布式锁的回调类

 

 

Java代码  技术分享图片
  1. package com.example.demo.redis2;  
  2.   
  3. /** 
  4.  * 分布式锁回调接口 
  5.  * 
  6.  * @author lk 
  7.  */  
  8. public interface DistributedLockCallback<T> {  
  9.   
  10.     /** 
  11.      * 调用者必须在此方法中实现需要加分布式锁的业务逻辑 
  12.      * 
  13.      * @return 
  14.      */  
  15.     public T process();  
  16.   
  17.     /** 
  18.      * 得到分布式锁名称 
  19.      * 
  20.      * @return 
  21.      */  
  22.     public String getLockName();  
  23. }  

 分布式锁操作模板

 

 

Java代码  技术分享图片
  1. package com.example.demo.redis2;  
  2. import java.util.concurrent.TimeUnit;  
  3.   
  4. /** 
  5.  * 分布式锁操作模板 
  6.  * 
  7.  * @author lk 
  8.  */  
  9. public interface DistributedLockTemplate {  
  10.     /** 
  11.      * 使用分布式锁,使用锁默认超时时间。 
  12.      * 
  13.      * @param callback 
  14.      * @return 
  15.      */  
  16.     public <T> T lock(DistributedLockCallback<T> callback);  
  17.   
  18.     /** 
  19.      * 使用分布式锁。自定义锁的超时时间 
  20.      * 
  21.      * @param callback 
  22.      * @param leaseTime 锁超时时间。超时后自动释放锁。 
  23.      * @param timeUnit 
  24.      * @return 
  25.      */  
  26.     public <T> T lock(DistributedLockCallback<T> callback, long leaseTime, TimeUnit timeUnit);  
  27. }  

 

 

使用redisson最简单的Single instance mode实现分布式锁模板接口

 

Java代码  技术分享图片
  1. package com.example.demo.redis2;  
  2.   
  3. import org.redisson.api.RLock;  
  4. import org.redisson.api.RedissonClient;  
  5.   
  6. import java.util.concurrent.TimeUnit;  
  7.   
  8. /** 
  9.  * Single Instance mode 分布式锁模板 
  10.  * 
  11.  * @author lk 
  12.  */  
  13. public class SingleDistributedLockTemplate implements DistributedLockTemplate {  
  14.     private static final long     DEFAULT_TIMEOUT   = 5;  
  15.     private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;  
  16.   
  17.     private RedissonClient    redisson;  
  18.   
  19.     public SingleDistributedLockTemplate() {  
  20.     }  
  21.   
  22.     public SingleDistributedLockTemplate(RedissonClient redisson) {  
  23.         this.redisson = redisson;  
  24.     }  
  25.   
  26.     @Override  
  27.     public <T> T lock(DistributedLockCallback<T> callback) {  
  28.         return lock(callback, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT);  
  29.     }  
  30.   
  31.     @Override  
  32.     public <T> T lock(DistributedLockCallback<T> callback, long leaseTime, TimeUnit timeUnit) {  
  33.         RLock lock = null;  
  34.         try {  
  35.             lock = redisson.getLock(callback.getLockName());  
  36.             lock.lock(leaseTime, timeUnit);  
  37.             return callback.process();  
  38.         } finally {  
  39.             if (lock != null) {  
  40.                 lock.unlock();  
  41.             }  
  42.         }  
  43.     }  
  44.   
  45.     public void setRedisson(RedissonClient redisson) {  
  46.         this.redisson = redisson;  
  47.     }  
  48.   
  49. }  

创建可以被spring管理的 Bean

Java代码  技术分享图片
  1. package com.example.demo.redis2;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5.   
  6. import javax.annotation.PostConstruct;  
  7. import javax.annotation.PreDestroy;  
  8.   
  9. import org.apache.log4j.Logger;  
  10. import org.redisson.Redisson;  
  11. import org.redisson.api.RedissonClient;  
  12. import org.redisson.config.Config;  
  13. import org.springframework.beans.factory.FactoryBean;  
  14.   
  15. /** 
  16.  * 创建分布式锁模板实例的工厂Bean 
  17.  * 
  18.  * @author lk 
  19.  */  
  20. public class DistributedLockFactoryBean implements FactoryBean<DistributedLockTemplate> {  
  21.     private Logger                  logger = Logger.getLogger(DistributedLockFactoryBean.class);  
  22.   
  23.     private LockInstanceMode        mode;  
  24.   
  25.     private DistributedLockTemplate distributedLockTemplate;  
  26.   
  27.     private RedissonClient redisson;  
  28.   
  29.     @PostConstruct  
  30.     public void init() {  
  31.         String ip = "127.0.0.1";  
  32.         String port = "6379";  
  33.         Config config=new Config();  
  34.         config.useSingleServer().setAddress(ip+":"+port);  
  35.         redisson=Redisson.create(config);  
  36.         System.out.println("成功连接Redis Server"+"\t"+"连接"+ip+":"+port+"服务器");  
  37.     }  
  38.   
  39.     @PreDestroy  
  40.     public void destroy() {  
  41.         logger.debug("销毁分布式锁模板");  
  42.         redisson.shutdown();  
  43.     }  
  44.   
  45.     @Override  
  46.     public DistributedLockTemplate getObject() throws Exception {  
  47.         switch (mode) {  
  48.             case SINGLE:  
  49.                 distributedLockTemplate = new SingleDistributedLockTemplate(redisson);  
  50.                 break;  
  51.         }  
  52.         return distributedLockTemplate;  
  53.     }  
  54.   
  55.     @Override  
  56.     public Class<?> getObjectType() {  
  57.         return DistributedLockTemplate.class;  
  58.     }  
  59.   
  60.     @Override  
  61.     public boolean isSingleton() {  
  62.         return true;  
  63.     }  
  64.   
  65.     public void setMode(String mode) {  
  66.         if (mode==null||mode.length()<=0||mode.equals("")) {  
  67.             throw new IllegalArgumentException("未找到dlm.redisson.mode配置项");  
  68.         }  
  69.         this.mode = LockInstanceMode.parse(mode);  
  70.         if (this.mode == null) {  
  71.             throw new IllegalArgumentException("不支持的分布式锁模式");  
  72.         }  
  73.     }  
  74.   
  75.     private enum LockInstanceMode {  
  76.         SINGLE;  
  77.         public static LockInstanceMode parse(String name) {  
  78.             for (LockInstanceMode modeIns : LockInstanceMode.values()) {  
  79.                 if (modeIns.name().equals(name.toUpperCase())) {  
  80.                     return modeIns;  
  81.                 }  
  82.             }  
  83.             return null;  
  84.         }  
  85.     }  
  86. }  

 配置进spring boot中

Java代码  技术分享图片
  1. package com.example.demo.redis2;  
  2.   
  3. import org.springframework.context.annotation.Bean;  
  4. import org.springframework.context.annotation.Configuration;  
  5.   
  6. /** 
  7.  * Created by LiaoKe on 2017/5/22. 
  8.  */  
  9. @Configuration  
  10. public class BeanConfig {  
  11.   
  12.     @Bean  
  13.     public DistributedLockFactoryBean distributeLockTemplate(){  
  14.         DistributedLockFactoryBean d  = new DistributedLockFactoryBean();  
  15.         d.setMode("SINGLE");  
  16.         return d;  
  17.     }  
  18. }  

 

目前为止已经可以使用。

为了验证锁是否成功,我做了如下例子。

首先建立了一个数据库实体(使用的JPA),模拟被购买的商品数量,当被购买后,num+1

在高并发环境下,这必定会有问题,因为在查询之后的设值,存在对同一数据库源的操作。

 

Java代码  技术分享图片
  1. package com.example.demo.redis2.entity;  
  2.   
  3. import org.hibernate.annotations.GenericGenerator;  
  4.   
  5. import javax.persistence.Entity;  
  6. import javax.persistence.GeneratedValue;  
  7. import javax.persistence.Id;  
  8.   
  9. /** 
  10.  * 测试类实体 
  11.  * Created by LiaoKe on 2017/5/22. 
  12.  */  
  13. @Entity  
  14. public class TestEntity {  
  15.     @Id  
  16.     @GeneratedValue(generator = "system-uuid")  
  17.     @GenericGenerator(name = "system-uuid", strategy = "uuid")  
  18.     private String id;  
  19.     private Integer num;  
  20.   
  21.     public String getId() {  
  22.         return id;  
  23.     }  
  24.   
  25.     public void setId(String id) {  
  26.         this.id = id;  
  27.     }  
  28.   
  29.     public Integer getNum() {  
  30.         return num;  
  31.     }  
  32.   
  33.     public void setNum(Integer num) {  
  34.         this.num = num;  
  35.     }  
  36. }  

 

 

具体数据库操作,加锁和不加锁的操作,要注意我使用了@Async,异步任务注解,我没有配置线程池信息,使用的默认线程池。

Java代码  技术分享图片
  1. package com.example.demo.redis2.service;  
  2.   
  3. import com.example.demo.redis2.DistributedLockCallback;  
  4. import com.example.demo.redis2.DistributedLockTemplate;  
  5. import com.example.demo.redis2.dao.TestEntityRepository;  
  6. import com.example.demo.redis2.entity.TestEntity;  
  7. import org.springframework.scheduling.annotation.Async;  
  8. import org.springframework.stereotype.Service;  
  9.   
  10. import javax.annotation.Resource;  
  11.   
  12. /** 
  13.  * Created by LiaoKe on 2017/5/22. 
  14.  */  
  15. @Service  
  16. public class AsyncService {  
  17.   
  18.     @Resource  
  19.     TestEntityRepository ts;  
  20.   
  21.   
  22.     @Resource  
  23.     DistributedLockTemplate distributedLockTemplate;  
  24.   
  25.     /** 
  26.      * 加锁 
  27.      */  
  28.     @Async  
  29.     public void addAsync(){  
  30.         distributedLockTemplate.lock(new DistributedLockCallback<Object>(){  
  31.             @Override  
  32.             public Object process() {  
  33.                 add();  
  34.                 return null;  
  35.             }  
  36.   
  37.             @Override  
  38.             public String getLockName() {  
  39.                 return "MyLock";  
  40.             }  
  41.         });  
  42.     }  
  43.   
  44.     /** 
  45.      * 未加锁 
  46.      */  
  47.     @Async  
  48.     public void addNoAsync(){  
  49.         add();  
  50.     }  
  51.   
  52.     /** 
  53.      * 测试异步方法 
  54.      * 在不加分布式锁的情况下 
  55.      * num数目会混乱 
  56.      */  
  57.     @Async  
  58.     private void add(){  
  59.         if(ts.findAll().size()==0){  
  60.             TestEntity  t  =  new TestEntity();  
  61.             t.setNum(1);  
  62.             ts.saveAndFlush(t);  
  63.         }else{  
  64.             TestEntity dbt = ts.findAll().get(0);  
  65.             dbt.setNum(dbt.getNum()+1);  
  66.             ts.saveAndFlush(dbt);  
  67.         }  
  68.     }  
  69.   
  70.   
  71. }  

 

最后为了测试简单跑了两个接口

Java代码  技术分享图片
  1. package com.example.demo;  
  2.   
  3. import com.example.demo.redis2.DistributedLockTemplate;  
  4. import com.example.demo.redis2.service.AsyncService;  
  5. import oracle.jrockit.jfr.StringConstantPool;  
  6. import org.springframework.boot.SpringApplication;  
  7. import org.springframework.boot.autoconfigure.SpringBootApplication;  
  8. import org.springframework.context.annotation.Bean;  
  9. import org.springframework.scheduling.annotation.EnableAsync;  
  10. import org.springframework.stereotype.Controller;  
  11. import org.springframework.web.bind.annotation.GetMapping;  
  12. import org.springframework.web.bind.annotation.RestController;  
  13.   
  14. import javax.annotation.Resource;  
  15.   
  16. @SpringBootApplication  
  17. @RestController  
  18. @EnableAsync  
  19. public class DemoApplication {  
  20.   
  21.     public static void main(String[] args) {  
  22.         SpringApplication.run(DemoApplication.class, args);  
  23.     }  
  24.   
  25.   
  26.     @Resource  
  27.     AsyncService as;  
  28.   
  29.   
  30.     @GetMapping("")  
  31.     public void test(){  
  32.         for(int i = 0 ;i<10000;i++){  
  33.             as.addNoAsync();  
  34.         }  
  35.     }  
  36.   
  37.     @GetMapping("lock")  
  38.     public void  test2(){  
  39.         for(int i = 0 ;i<10000;i++){  
  40.             as.addAsync();  
  41.         }  
  42.     }  
  43. }  

 

 访问localhost:8888 及 localhost:8888/lock

在不加锁的情况下


技术分享图片
 数据库已经爆炸了

最后得到的数据奇奇怪怪


技术分享图片
 

使用加锁后的访问


技术分享图片
 可以看到库存增加绝对正确。

此处并未使用任何数据库锁,并且基于redis,可在不同的网络节点实现上锁。

 

这只是简单的实现,在真正的生产环境中,还要注意许多问题,超时和放锁时机需要好好研究,在此不便贴真正项目代码。

 

参考博客:http://layznet.iteye.com/blog/2307179   感谢作者







以上是关于spring boot 利用redisson实现redis的分布式锁的主要内容,如果未能解决你的问题,请参考以下文章

spring-boot-data-redis,使用redisson作为redis客户端

redisson spring boot starter 做分布式锁

Redis系列:Spring Boot整合Redisson

SpringBoot中使用 Redisson 版本冲突导致的类找不到

Spring Boot + Redis 实现各种操作 #yyds干货盘点#

Spring Boot + Redis 实现各种操作,写得太好了吧!