SpringBoot+Redis 从HelloWorld到分布式锁

1.Redis简单安装启动

下载reids最新稳定版本

wget http://download.redis.io/releases/redis-4.0.8.tar.gz

 解压

tar xzf redis-4.0.8.tar.gz

 编译

cd redis-4.0.8
make

 启动Reids服务

src/redis-server

 启动完成示意图

113558_mwYp_3551926.png

当然这里什么都没配置,重点是怎么去实现redis分布式锁。

 

2.SpringBoot与Redis整合

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

这里用到了aop+web+devtool+redis+lombok

简单的配置文件

application.yml

spring:
  redis:
    host: 192.168.1.113
    port: 6379
    password: test123

这里只用了最简介的配置,关于连接池数量,超时配置,指定数据库等等配置信息均使用系统默认。

附:reids密码的设置

刚刚安装的redis是没有密码的,如何设置密码

执行,连接到客户端(cli==> command line interface)

src/redis-cli

设置密码

config set requirepass test123

查询密码

config get requirepass

会提示(error) ERR operation not permitted,没有操作权限

校验密码

auth test123

这时就有操作权限了,密码就设置成功了。

3.实现一个简单的Hello World!

编写一个简单的Redis服务

package com.example.demo.service;

public interface IRedisService {
    String get(String key);
    void set(String key, Object val);
    <T> T get(String key, Class<T> cls);
}

服务实现

package com.example.demo.service.impl;

import com.example.demo.service.IRedisService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class IRedisServiceImpl implements IRedisService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Override
    public String get(String key) {
        return get(key, String.class);
    }

    @Override
    public void set(String key, Object val) {
        redisTemplate.opsForValue().set(key, val);
    }

    @Override
    @SuppressWarnings({"unchecked", "ConstantConditions"})
    public <T> T get(String key, Class<T> cls) {
        Object val = redisTemplate.opsForValue().get(key);
        if (val==null){
            return null;
        }
        if (val.getClass().isAssignableFrom(cls)) {
            return (T) val;
        }
        throw new IllegalArgumentException();
    }
}

写一个控制器做一个简单测试

package com.example.demo.controller;

import com.example.demo.service.IRedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

@Controller
public class RedisController {
    private final static Logger logger = LoggerFactory.getLogger(RedisController.class);
    @Resource
    private IRedisService iRedisService;

    @GetMapping("/get")
    @ResponseBody
    public String test() {
        return iRedisService.get("hello");
    }

    @GetMapping("/set")
    @ResponseBody
    public String set() {
        iRedisService.set("hello", "world");
        return "Set Ok!";
    }
}

到这里SpringBoot已经与Redis完美整合了,下一步通过AOP实现Redis分布式事物锁。

原理参考:https://my.oschina.net/u/3551926/blog/1600167

4实现Redis分布式事物锁

1.完善redis服务

package com.example.demo.service;

public interface IRedisService {
    Boolean setnx(String key, Object val);
    Object getset(String key, Object val);
    Boolean del(String key);
    String get(String key);
    void set(String key, Object val);
    <T> T get(String key, Class<T> cls);
}

 

package com.example.demo.service.impl;

import com.example.demo.service.IRedisService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class IRedisServiceImpl implements IRedisService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public Boolean setnx(String key, Object val) {
        return redisTemplate.opsForValue().setIfAbsent(key, val);
    }

    @Override
    public Object getset(String key, Object val) {
        return redisTemplate.opsForValue().getAndSet(key, val);
    }

    @Override
    public Boolean del(String key) {
        return redisTemplate.delete(key);
    }

    @Override
    public String get(String key) {
        return get(key, String.class);
    }

    @Override
    public void set(String key, Object val) {
        redisTemplate.opsForValue().set(key, val);
    }

    @Override
    @SuppressWarnings({"unchecked", "ConstantConditions"})
    public <T> T get(String key, Class<T> cls) {
        Object val = redisTemplate.opsForValue().get(key);
        if (val==null){
            return null;
        }
        if (val.getClass().isAssignableFrom(cls)) {
            return (T) val;
        }
        throw new IllegalArgumentException();
    }
}

加入必要的方法

2.AOP实现注解方法拦截

package com.example.demo.annotions;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisSync {
    //方法执行超时时间(默认2秒)
    long timeout() default 2000;
    //等待执行时间(默认0.05秒)
    int waitTime() default 50;
}

 

package com.example.demo.aop;

import com.example.demo.annotions.RedisSync;
import com.example.demo.service.IRedisService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Objects;

@Aspect
@Component
public class RedisSyncAop {
    private static final Logger logger = LoggerFactory.getLogger(RedisSyncAop.class);
    @Resource
    private IRedisService iRedisService;

    @Pointcut("@annotation(com.example.demo.annotions.RedisSync)")
    private void anyMethod(){
    }

    @Around("anyMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;
        //获得锁
        Method method = ((MethodSignature)pjp.getSignature()).getMethod();
        String key = method.toString();
        RedisSync redisSync = method.getAnnotation(RedisSync.class);
        long waitTime = redisSync.waitTime();
        long currTime = System.currentTimeMillis();
        Boolean state = iRedisService.setnx(key, currTime);
        long saveTime = 0L;
        while (!state) {
            Long tempSaveTime = iRedisService.get(key, Long.class);
            //锁被释放
            if (tempSaveTime==null) {
                state = iRedisService.setnx(key, currTime);
                continue;
            }
            //锁被重新获取
            if (!tempSaveTime.equals(saveTime)){
                currTime = System.currentTimeMillis();
                saveTime=tempSaveTime;
            }
            //判断是否超时
            if (saveTime+redisSync.timeout()<currTime) {
                //超时,直接获得锁
                Object tempTime = iRedisService.getset(key, currTime);
                if(tempTime==null){
                    state = iRedisService.setnx(key, currTime);
                    continue;
                }
                //判断锁被是否被释放或未被抢先获取
                if (Objects.equals(saveTime, tempTime)) {
                    logger.warn("方法:{},执行超时,已被强制解锁!", key);
                    break;
                }
            }
            //等待
            if(waitTime>0) {
                try {
                    Thread.sleep(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            state = iRedisService.setnx(key, currTime);
        }
        //执行
        result = pjp.proceed();
        Long currSaveTime = iRedisService.get(key, Long.class);
        //判断锁未被判定为超时
        if (currSaveTime!=null && Objects.equals(currSaveTime, currTime)) {
            //释放锁
            iRedisService.del(key);
        }
        return result;
    }
}

 

加入测试服务

package com.example.demo.service;

public interface SyncService {
    int getIndex();
}

 

package com.example.demo.service.impl;

import com.example.demo.annotions.RedisSync;
import com.example.demo.service.SyncService;
import org.springframework.stereotype.Service;

import java.util.Random;

@Service
public class SyncServiceImpl implements SyncService {
    private static int COUNT = 100;
    private static final Random RANDOM = new Random();
    @Override
    @RedisSync(waitTime = 0)
    public int getIndex() {
        try {
            //随机获取等待时间(该时间仅供参考,准确时间还需加上代码执行时间)
            long time = 500+RANDOM.nextInt(3000);
            System.out.println("COUNT("+COUNT+"),sleep:"+time);
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (COUNT<=0) {
            return 0;
        }
        return COUNT--;
    }
}

 

通过控制器创建3个线程直接测试

package com.example.demo.controller;

import com.example.demo.service.IRedisService;
import com.example.demo.service.SyncService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

@Controller
public class RedisController {
    private final static Logger logger = LoggerFactory.getLogger(RedisController.class);
    @Resource
    private IRedisService iRedisService;
    @Resource
    private SyncService syncService;

    @GetMapping("/get")
    @ResponseBody
    public String test() {
        return iRedisService.get("hello");
    }

    @GetMapping("/set")
    @ResponseBody
    public String set() {
        iRedisService.set("hello", "world");
        return "Set Ok!";
    }

    @GetMapping("/sync")
    @ResponseBody
    public String sync() {
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(syncService.getIndex());
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(syncService.getIndex());
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(syncService.getIndex());
            }
        }).start();
        return String.valueOf(syncService.getIndex());
    }
}

 

到此一个简单的Redis分布式事物锁就完成了。

源码地址:https://github.com/makejavas/Redis-sync

转载于:https://my.oschina.net/u/3551926/blog/1786237

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值