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代碼
- <!-- redisson -->
- <dependency>
- <groupId>org.redisson</groupId>
- <artifactId>redisson</artifactId>
- <version>3.4.2</version>
- </dependency>
定義一個分布式鎖的回調類
Java代碼
- package com.example.demo.redis2;
- /**
- * 分布式鎖回調接口
- *
- * @author lk
- */
- public interface DistributedLockCallback<T> {
- /**
- * 調用者必須在此方法中實現需要加分布式鎖的業務邏輯
- *
- * @return
- */
- public T process();
- /**
- * 得到分布式鎖名稱
- *
- * @return
- */
- public String getLockName();
- }
分布式鎖操作模板
Java代碼
- 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實現分布式鎖模板接口
Java代碼
- 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
Java代碼- 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中
Java代碼- 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
在高並發環境下,這必定會有問題,因為在查詢之後的設值,存在對同一數據庫源的操作。
Java代碼
- 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,異步任務註解,我沒有配置線程池信息,使用的默認線程池。
Java代碼- 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);
- }
- }
- }
最後為了測試簡單跑了兩個接口
Java代碼- 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 感謝作者
spring boot 利用redisson實現redis的分布式鎖