Spring boot uses redisson to implement distributed locks for redis

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326251811&siteId=291194637