SpringBoot+Redis from HelloWorld to distributed lock

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

{{o.name}}
{{m.name}}

Guess you like

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