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
启动完成示意图
当然这里什么都没配置,重点是怎么去实现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分布式事物锁就完成了。