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);
}
}