1. Redis simple installation and startup
Download the latest stable version of reids
wget http://download.redis.io/releases/redis-4.0.8.tar.gz
decompress
tar xzf redis-4.0.8.tar.gz
compile
cd redis-4.0.8
make
Start the Reids service
src/redis-server
Schematic diagram of startup completion
Of course, nothing is configured here, the focus is on how to implement redis distributed locks.
2. SpringBoot and Redis integration
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 is used here
simple configuration file
application.yml
spring:
redis:
host: 192.168.1.113
port: 6379
password: test123
Only the most brief configuration is used here. The system defaults are used for configuration information such as the number of connection pools, timeout configuration, and specified database.
Attachment: setting of reids password
The newly installed redis has no password, how to set the password
Execute, connect to client (cli==> command line interface)
src/redis-cli
set password
config set requirepass test123
query password
config get requirepass
It will prompt (error) ERR operation not permitted, no operation permission
check password
auth test123
At this time, you have the operation authority, and the password is set successfully.
3. Implement a simple Hello World!
Write a simple Redis service
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);
}
service implementation
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();
}
}
Write a controller to do a simple test
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!";
}
}
At this point, SpringBoot has been perfectly integrated with Redis, and the next step is to implement Redis distributed transaction lock through AOP.
Principle reference: https://my.oschina.net/u/3551926/blog/1600167
4 Implement Redis distributed transaction lock
1. Improve the redis service
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();
}
}
add necessary methods
2. AOP implements annotation method interception
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;
}
}
Join the test service
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--;
}
}
Create 3 threads through the controller to test directly
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());
}
}
At this point, a simple Redis distributed transaction lock is completed.
Source address: https://github.com/makejavas/Redis-sync