Redis分布式锁详解

1. 分布式所概述

1.1 分布式锁

学习分布式锁之前一直在想,在单机环境下,那些多线程相关的技术synchronized,volatile,lock,ReentrantLock,ReentrantReadWriteLock,countdownLatch,CyclicBarrier,Semaphore,在单机环境下通过标识,锁计数器来控制访问临界资源的同步性,如今分布式微服务横行,很少碰到单机环境下的并发,这些究竟还有没有用,看完分布式锁觉着也说得通吧,首先技术的演进迭代也不是一蹴而就的,没有单机的演进哪来的分布式微服务,其次思想原理都是相同的,尤其是这次单机版锁与分布式锁,只不过一个是在同一jvm下实现的,一个放到了第三方实现的,底层实现都是一样的。

这是之前写的单机版的并发编程,Java JUC并发编程详解:
https://blog.csdn.net/m0_37583655/article/details/119831421

作为新时代的农民工,希望跟大家一起开心的造轮子,不足之处,敬请指点。

2. 缓存数据库Redis

2.1 redis简介

Redis是基于内存的,Key-Value形式的非关系型数据库。

这是之前写的redis相关技术,
https://blog.csdn.net/m0_37583655/article/details/118696330

2.2 Springboot整合Redis两种方式

首先我们要知道,Springboot整合Redis有两种方式,分别是Jedis和RedisTemplate,这两者有何区别?

Jedis是Redis官方推荐的面向Java的操作Redis的客户端,而RedisTemplate是SpringDataRedis中对JedisApi的高度封装。其实在Springboot的官网上我们也能看到,官方现在推荐的是SpringDataRedis形式,相对于Jedis来说可以方便地更换Redis的Java客户端,其比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache。

3. 实现验证

上文概念梳理完毕,开始我们的造轮子之路!!!

3.1 环境准备

就不废话了,这是下载安装教程,超详细。
https://www.redis.com.cn/redis-installation.html

3.2 项目概述

项目验证了通过RedisTemplate实现缓存的操作以及redis实现的分布式锁的几种方案,以及通过Redisson实现的几种分布式锁操作。

3.3 环境准备

jdk:1.8
Redis-x64-3.0.504
springboot:2.5.4
spring-boot-starter-data-redis:2.5.4
redisson:3.6.1

3.4 实现验证

在这里插入图片描述

3.4.1 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zrj</groupId>
    <artifactId>redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- springboot整合redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.16.1</version>
        </dependency>

        <!--常用工具-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1.1-jre</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3.4.2 application.properties

# redis配置
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=10000ms

#连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0


3.4.3 RedisApplication

package com.zrj.redis;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Redis
 *
 * @author zrj
 * @since 2021/8/28
 */
@SpringBootApplication
public class RedisApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class, args);
    }

}

3.4.4 RedisConfig

package com.zrj.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

/**
 * redis配置
 *
 * @author zrj
 * @since 2021/8/27
 **/
@Configuration
public class RedisConfig {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置RedisTemplate
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // key采用序列化方式
        template.setKeySerializer(jackson2JsonRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的key也采用的序列化方式
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

3.4.5 RedissonConfig

package com.zrj.redis.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.redisson.config.Config;

import java.io.IOException;

/**
 * Redisson配置
 *
 * @author zrj
 * @since 2021/8/27
 **/
@Configuration
public class RedissonConfig {
    /**
     * RedissonClient
     *
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod = "shutdown") // 服务停止后调用 shutdown 方法。
    public RedissonClient redisson() throws IOException {
        // 1.创建配置
        Config config = new Config();
        // 集群模式
        // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");

        // 2.根据 Config 创建出 RedissonClient 示例。
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
}

3.4.6 Goods

package com.zrj.redis.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author zrj
 * @since 2021/8/27
 **/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Goods implements Serializable {
    private static final long serialVersionUID = -2063354304693750063L;

    private String name;

    private double price;

    private String brand;

    private String Category;
}

3.4.7 Response

package com.zrj.redis.entity;

import java.io.Serializable;

/**
 * 结果集
 *
 * @author Jerry
 * @since 2021/8/7
 **/
final public class Response<T extends Serializable> implements Serializable {

    private static final long serialVersionUID = 1L;

    private boolean success = true;

    private String code;

    private String message;

    private T data;

    public Response() {
        super();
    }

    public Response(String code, String message) {
        this(true, code, message);
    }

    public Response(boolean success, String code, String message) {
        this(success, code, message, null, null);
    }

    public Response(boolean success, String code, String message, T data) {
        this(success, code, message, data, null);
    }

    public Response(boolean success, String code, String message, T data, Throwable cause) {
        this.success = success;
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T extends Serializable> Response<T> success(String code, String message, T data) {
        Response rest = new Response();
        rest.setCode(code);
        rest.setMessage(message);
        rest.setData(data);
        rest.setSuccess(true);
        return rest;
    }

    public static <T extends Serializable> Response<T> success(String message, T data) {
        Response rest = new Response();
        rest.setCode("200");
        rest.setMessage(message);
        rest.setData(data);
        rest.setSuccess(true);
        return rest;
    }

    public static <T extends Serializable> Response<T> success(T data) {
        Response rest = new Response();
        rest.setCode("200");
        rest.setMessage("success");
        rest.setData(data);
        rest.setSuccess(true);
        return rest;
    }

    public static <T extends Serializable> Response<T> fail(String code, String message) {
        Response<T> rest = new Response<T>();
        rest.setCode(code);
        rest.setMessage(message);
        rest.setSuccess(false);
        return rest;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

3.4.8 RedisLock

package com.zrj.redis.utils;

import cn.hutool.core.lang.UUID;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * redis分布式锁
 *
 * @author zrj
 * @since 2021/8/27
 **/
@Slf4j
public class RedisLock {
    @Resource
    private StringRedisTemplate redisTemplate;

    /**
     * 青铜方案
     * 执行流程:抢锁成功,执行业务,释放锁
     * 问题:死锁问题
     */
    @SneakyThrows
    public void redisLockRronze() {
        // 1.先抢占锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123");
        if (lock) {
            // 2.抢占成功,执行业务
            log.info("[青铜方案]抢占成功,执行业务");
            Thread.sleep(1000);
            // 3.解锁
            redisTemplate.delete("lock");
            return;
        } else {
            // 4.休眠一段时间
            Thread.sleep(1000);
            // 5.抢占失败,等待锁释放
            return;
        }
    }

    /**
     * 白银方案
     * 设置超时时间,超时释放锁,避免死锁
     * 因为占锁和设置过期时间是分两步执行的,所以如果在这两步之间发生了异常,则锁的过期时间根本就没有设置成功。
     */
    @SneakyThrows
    public void redisLockSilver() {
        // 1.先抢占锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123");
        if (lock) {
            // 2.在 10s 以后,自动清理 lock
            redisTemplate.expire("lock", 10, TimeUnit.SECONDS);
            // 3.抢占成功,执行业务
            log.info("[白银方案]抢占成功,执行业务");
            Thread.sleep(1000);
            // 4.解锁
            redisTemplate.delete("lock");
            return;
        }
    }

    /**
     * 黄金方案
     * 将两步放在一步中执行:占锁+设置锁过期时间。
     */
    @SneakyThrows
    public void redisLockGold() {
        // 1.先抢占锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123", 10, TimeUnit.SECONDS);
        if (lock) {
            // 2.在 10s 以后,自动清理 lock
            redisTemplate.expire("lock", 10, TimeUnit.SECONDS);
            // 3.抢占成功,执行业务
            log.info("[黄金方案]抢占成功,执行业务");
            Thread.sleep(1000);
            // 4.解锁
            redisTemplate.delete("lock");
            return;
        }
    }

    /**
     * 铂金方案
     */
    @SneakyThrows
    public void redisLockPlatinum() {
        // 1.生成唯一 id
        String uuid = UUID.randomUUID().toString();
        // 2. 抢占锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
        if (lock) {
            System.out.println("抢占成功:" + uuid);
            // 3.抢占成功,执行业务
            log.info("[铂金方案]抢占成功,执行业务");
            Thread.sleep(1000);

            // 4.获取当前锁的值
            String lockValue = redisTemplate.opsForValue().get("lock");
            // 5.如果锁的值和设置的值相等,则清理自己的锁
            if (uuid.equals(lockValue)) {
                System.out.println("清理锁:" + lockValue);
                redisTemplate.delete("lock");
            }
        } else {
            System.out.println("抢占失败,等待锁释放");
            // 4.休眠一段时间
            Thread.sleep(1000);
            // 5.抢占失败,等待锁释放
            return;
        }
    }


    /**
     * 钻石方案
     * 分两步:先定义脚本;用 redisTemplate.execute 方法执行脚本。
     * 而这段 Redis 脚本是由 Redis 内嵌的 Lua 环境执行的,所以又称作 Lua 脚本。
     */
    @SneakyThrows
    public void redisLockDiamond() {
        // 1.生成唯一 id
        String uuid = UUID.randomUUID().toString();
        // 2. 抢占锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
        if (lock) {
            System.out.println("抢占成功:" + uuid);
            // 3.抢占成功,执行业务
            log.info("[钻石方案]抢占成功,执行业务");
            Thread.sleep(1000);

            // 4.获取当前锁的值
            String lockValue = redisTemplate.opsForValue().get("lock");

            // 5.脚本解锁,如果锁的值和设置的值相等,则清理自己的锁
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);
        } else {
            System.out.println("抢占失败,等待锁释放");
            // 4.休眠一段时间
            Thread.sleep(1000);
            // 5.抢占失败,等待锁释放
            return;
        }
    }

}

3.4.9 RedissonLock

package com.zrj.redis.utils;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Redisson分布式锁
 *
 * @author zrj
 * @since 2021/8/27
 **/
@Slf4j
@Component
public class RedissonLock {

    @Resource
    private RedissonClient redissonClient;

    /**
     * 分布式可重入锁
     * 1.可重入锁是阻塞的吗?
     * Redisson 的可重入锁(lock)是阻塞其他线程的,需要等待其他线程释放的。
     * <p>
     * 2.服务停了,锁会释放吗?
     * 会的, Redisson 在停机后,占用的锁会自动释放,默认30秒
     * <p>
     * 3.看门狗原理
     *
     * @param
     * @return java.lang.String
     */
    public String distributedRedissonLock() {
        // 1.获取锁,只要锁的名字一样,获取到的锁就是同一把锁。
        RLock lock = redissonClient.getLock("lock");
        // 2.加锁
        lock.lock();
        // 手动设置过期时间,则按照设置时间自动释放,但是执行业务的时间一定要小于锁的自动过期时间,否则就会报错。
        //lock.lock(15, TimeUnit.SECONDS);
        try {
            System.out.println("加锁成功,执行后续代码。线程 ID:" + Thread.currentThread().getId());
            Thread.sleep(10000);
        } catch (Exception e) {
            System.err.println("系统异常:" + e);
        } finally {
            // 3.解锁
            lock.unlock();
            System.out.println("释放锁成功。线程 ID:" + Thread.currentThread().getId());
        }
        return Thread.currentThread().getId() + " lock ok";
    }

    /**
     * 分布式读写锁
     * 读锁 + 读锁:相当于没加锁,可以并发读。
     * 读锁 + 写锁:写锁需要等待读锁释放锁。
     * 写锁 + 写锁:互斥,需要等待对方的锁释放。
     * 写锁 + 读锁:读锁需要等待写锁释放。
     * <p>
     * 获取锁的三种方式:
     * 1.直接获取读锁或者写锁
     * 2.设置超时时间,超时自动释放锁
     * 3.增加尝试获取锁,即自旋次数
     *
     * @return
     */
    public String distributedReadWriteLock() {

        RReadWriteLock rwlock = null;
        try {
            // 获取指定的可重入读写锁
            rwlock = redissonClient.getReadWriteLock("RWLock");

            // 1. 获取读锁,获取写锁
            rwlock.readLock().lock();
            rwlock.writeLock().lock();

            // 2. 10秒钟以后自动解锁,无需调用unlock方法手动解锁
            rwlock.readLock().lock(10, TimeUnit.SECONDS);
            rwlock.writeLock().lock(10, TimeUnit.SECONDS);

            // 3. 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
            boolean res1 = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
            boolean res2 = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);

        } catch (Exception e) {
            System.out.println("系统异常:" + e);
        } finally {
            // 解锁读锁或者写锁
            rwlock.readLock().unlock();
            rwlock.writeLock().unlock();
        }
        return Thread.currentThread().getId() + " lock ok";
    }

    /**
     * 分布式信号量
     *
     * @return
     */
    public String distributedSemaphore() {
        RSemaphore park = null;
        try {
            // 获取信号量(停车场)
            park = redissonClient.getSemaphore("park");
            // 获取一个信号(停车位)
            park.acquire();

        } catch (Exception e) {
            System.out.println("系统异常:" + e);
        } finally {
            // 释放一个信号(停车位)
            park.release();
        }
        return Thread.currentThread().getId() + " lock ok";
    }
}

3.4.10 RedisUtil

package com.zrj.redis.utils;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * redis工具类
 *
 * @author zrj
 * @since 2021/8/27
 **/
@Component
public class RedisUtil {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 指定缓存失效时间
     *
     * @param key
     * @param time 时间(秒)
     * @return object
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * @param key
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 删除键值
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }

    /**
     * 获取键值
     *
     * @param key
     * @return object
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 设置键值
     *
     * @param key
     * @param value
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 设置键值同时指定过期时间
     *
     * @param key
     * @param value
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 键值递增
     *
     * @param key
     * @param delta 要增加几(大于0)
     * @return long
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 键值递减
     *
     * @param key
     * @param delta 要减少几(小于0)
     * @return long
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    /**
     * 获取HashGet
     *
     * @param key
     * @param item
     * @return Object
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 设置HashGet
     *
     * @param key
     * @return Map
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 设置多个HashGet
     *
     * @param key
     * @param map
     * @return boolean
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            System.err.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 设置多个HashGet时指定过期时间
     *
     * @param key
     * @param map
     * @param time 时间(秒)
     * @return boolean
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            System.err.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key
     * @param item
     * @param value
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key
     * @param item
     * @param value
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key
     * @param item
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否存在item
     *
     * @param key
     * @param item
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key
     * @param item
     * @param by   要增加的值(大于0)
     * @return object
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key
     * @param item
     * @param by   要减少的值(小于0)
     * @return object
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    /**
     * 获取set的内容
     *
     * @param key
     * @return object
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return null;
        }
    }

    /**
     * 判断set中是否存在某个值
     *
     * @param key
     * @param value
     * @return object
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 设置set
     *
     * @param key
     * @param values
     * @return object long
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return 0;
        }
    }

    /**
     * 设置set同时指定过期时间
     *
     * @param key
     * @param time   时间(秒)
     * @param values
     * @return object
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return 0;
        }
    }

    /**
     * 获取set的元素个数
     *
     * @param key
     * @return object
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return 0;
        }
    }

    /**
     * 从set中移除元素
     *
     * @param key
     * @param values
     * @return object
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return 0;
        }
    }

    /**
     * 获取List的内容
     *
     * @param key
     * @param start 开始索引
     * @param end   结束索引 0 到 -1代表所有值
     * @return object
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return null;
        }
    }

    /**
     * 获取list的长度
     *
     * @param key
     * @return object
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return object
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return null;
        }
    }

    /**
     * 设置List
     *
     * @param key
     * @param value
     * @return object
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 设置List
     *
     * @param key
     * @param value
     * @param time  时间(秒)
     * @return object
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 设置List
     *
     * @param key
     * @param value
     * @return object
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 设置List同时指定过期时间
     *
     * @param key
     * @param value
     * @param time  时间(秒)
     * @return object
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key
     * @param index
     * @param value
     * @return object
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key
     * @param count
     * @param value
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            System.out.println("系统异常:" + e);
            return 0;
        }
    }
}

3.4.11 ThreadPool

package com.zrj.redis.utils;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.concurrent.*;

/**
 * 线程池
 *
 * @author zrj
 * @since 2021/8/28
 **/
public class ThreadPool {
    //线程池的核心线程数
    private static int corePoolSize = 30;
    //能容纳的最大线程数
    private static int maximumPoolSize = 200;
    //空闲线程存活时间
    private static long keepAliveTime = 0L;
    //空闲线程存活时间 单位
    private static TimeUnit unit = TimeUnit.MILLISECONDS;
    //创建线程的工厂类,自定义线程名称
    private static String threadName = "thread-local-pool-%d";
    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(threadName).build();
    //存放提交但未执行任务的队列
    private static BlockingQueue<Runnable> threadFactory = new LinkedBlockingQueue<>(1024);
    //等待队列满后的拒绝策略
    private static RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

    // 定义线程池
    private static ExecutorService pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, threadFactory, namedThreadFactory, handler);

    /**
     * 获取线程池
     *
     * @param
     * @return java.util.concurrent.ExecutorService
     */
    public static ExecutorService getPool() {
        return pool;
    }
}

3.4.12 RedisController

package com.zrj.redis.controller;

import com.zrj.redis.entity.Goods;
import com.zrj.redis.entity.Response;
import com.zrj.redis.utils.RedisUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.*;

/**
 * 缓存控制器
 * Jedis是Redis官方推荐的面向Java的操作Redis的客户端,而RedisTemplate是SpringDataRedis中对JedisApi的高度封装。
 * 其实在SpringBoot的官网上我们也能看到,官方现在推荐的是SpringDataRedis形式,
 * 相对于Jedis来说可以方便地更换Redis的Java客户端,其比Jedis多了自动管理连接池的特性,
 * 方便与其他Spring框架进行搭配使用如:SpringCache。
 *
 * @author zrj
 * @since 2021/8/27
 **/
@RestController
@RequestMapping("/redis")
public class RedisController {

    @Resource
    private RedisUtil redisUtil;

    /**
     * 字符串(String)二进制安全的字符串
     */
    @GetMapping("/getStr")
    public Response getStr() {
        String phoneKey = "string_phone_key";
        String phoneValue = "hello honor";
        redisUtil.set(phoneKey, phoneValue);
        String hello = (String) redisUtil.get(phoneKey);
        return Response.success("字符串查询成功", hello);
    }

    /**
     * 对象
     */
    @GetMapping("/getObj")
    public Response getObj() {
        String goodsKey = "goods_key";
        Goods build = Goods.builder().name("手机").price(666).brand("诺基亚").build();
        redisUtil.set(goodsKey, build);

        Goods goods = (Goods) redisUtil.get(goodsKey);
        return Response.success("对象查询成功", goods);
    }

    /**
     * 列表(Lists):按照插入顺序排列的字符串元素的集合。从根本上说它们是 Linked Lists。
     */
    @GetMapping("/getList")
    public Response getList() {
        String phoneKey = "list_phone_key";
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("honor");
        linkedList.add("real");

        redisUtil.set(phoneKey, linkedList);
        LinkedList hello = (LinkedList) redisUtil.get(phoneKey);
        return Response.success("列表查询成功", hello);
    }

    /**
     * 集合(Sets):唯一、无序的字符串元素的集合。
     */
    @GetMapping("/getSets")
    public Response getSets() {
        String phoneKey = "set_phone_key";
        Set<String> set = new HashSet<>();
        set.add("apollo");
        set.add("real");
        set.add("real");

        redisUtil.set(phoneKey, set);
        HashSet hello = (HashSet) redisUtil.get(phoneKey);
        return Response.success("集合查询成功", hello);
    }

    /**
     * 有序集合(Sorted sets): 类似于集合,但每个字符串元素都与一个称为score的浮点数值相关联。
     * 元素总是按分数排序,因此与集合不同,可以检索一系列元素(例如,您可能会问:给出前10个或后10个)。
     */
    @GetMapping("/getSortedSets")
    public Response getSortedSets() {
        String phoneKey = "sorted_phone_key";
        Set<String> set = new HashSet<>();
        set.add("dell");
        set.add("real");
        set.add("apollo");

        redisUtil.set(phoneKey, set);
        HashSet hello = (HashSet) redisUtil.get(phoneKey);
        return Response.success("有序集合查询成功", hello);
    }

    /**
     * 哈希(Hashes): 它是由字段和值组成的map。字段和值都是字符串。这和Ruby或Python的hashes非常相似。
     */
    @GetMapping("/getHashes")
    public Response getHashes() {
        String phoneKey = "hashes_phone_key";
        Map<String, Object> hashMap = new HashMap<>();
        hashMap.put("honor", "998");
        hashMap.put("real", "997");
        hashMap.put("apollo", "996");

        redisUtil.set(phoneKey, hashMap);
        HashMap hello = (HashMap) redisUtil.get(phoneKey);
        return Response.success("哈希查询成功", hello);
    }

}

3.4.13 RedissonController

package com.zrj.redis.controller;

import com.zrj.redis.entity.Response;
import com.zrj.redis.utils.RedissonLock;
import com.zrj.redis.utils.ThreadPool;
import org.redisson.api.RedissonClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * RedissonClient
 *
 * @author zrj
 * @since 2021/8/27
 **/
@RestController
@RequestMapping("/redisson")
public class RedissonController {

    @Resource
    private RedissonLock redissonLock;

    /**
     * 测试服务
     */
    @GetMapping("/redissonLock")
    public Response redissonLock() {

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            ThreadPool.getPool().execute(()->{
                String result = redissonLock.distributedRedissonLock();
                System.out.println("第"+ finalI +"个线程进入: "+result);
            });
        }
        return Response.success("分布式可重入锁加锁成功", null);
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值