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

利用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   感谢作者

猜你喜欢

转载自liaoke0123.iteye.com/blog/2375469