序言
你什么时候会想起用缓存?提升系统访问的速度?缩短单个交易处理时长?你真的了解缓存么?这是springBoot框架的第二篇,REDIS的使用
关联文章
SpringBoot工程搭建
详解缓存Redis
详解数仓ElasticSearch
详解消息中间件Kafka
本文目录
第一,缓存的本质是,提升访问速度。缓存的读取速度介于硬盘与内存之间,内存很快,但断电即毁;硬盘太慢,磁头访问速率受限(同理,DB和缓存的访问速度比起来,还是个小弟弟)。
第二,缓存的适用场景是,“读多写少”的场景,“读多写多”为啥不能适用呢?为了读取到最新的数据,一旦发生数据写,就得删除旧缓存,有这功夫您还不如直接问问DB老哥。
第三,缓存的弊端是:
1、加大了系统的复杂度,尤其是可能带来数据不一致的问题,非常致命。首先,为了数据兜底,缓存+数据库是很常见的架构方案,但这就带来了数据”写后读“导致数据一致性的问题:无论是先更新数据库,再删缓存;还是先删缓存,再更新数据库,都需要额外的努力才能确保一致性;其次,缓存集群的架构,本质上是AP的系统,若主节点宕机时、选主未成功,期间的写入数据都会丢失;
2、缓存存储不适合复杂查询,复杂查询更适合数据库SQL执行,复杂体现在两方面:首先,数据冗余问题,例如同一条记录的查询需要基于A字段查出,也有场景需要基于B字段,那么在Redis里面只能存储基于A和基于B两份相同的数据;其次,范围查找困难,相类似的,与、或集合操作也有风险,博主曾做过两个Zset的与操作,在保证交易原子性方面需要付出额外的努力(LUA脚本)。以上两点,均可由数据库一条SQL搞定。
缓存一致性保证措施
准备工作
redis的五种数据结构介绍
数据结构 | 举例 |
---|---|
字符串 | |
Hash | |
List | |
zset | |
set |
Redis集群模式已经搭建完成
至于为何使用集群模式呢?分片、支持高并发写,上述两点足以
# Replication
role:master
connected_slaves:1
slave0:ip=10.138.37.91,port=8504,state=online,offset=195899994,lag=0
master_repl_offset:195900008
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:194851433
repl_backlog_histlen:1048576
demo演示
配置文件的修改
第一篇讲springBoot框架时,我们还在用application.properties。现在你已经是一个成熟的读者了,我们做一下升级,使用application.yml来管理应用模块的配置文件,springBoot工程默认加载配置文件application.yml。多提一句,以后我们还会介绍springCloud,一个由多个springBoot工程组成的“史前怪兽”,它会默认加载bootstrap.yml,然后加载application.yml。
server:
port: 8085
spring:
profiles:
active: @profiles.active@
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
main:
allow-bean-definition-overriding: true
application:
name: demo
freemarker:
suffix: .html
template-loader-path: classpath:/templates/
# Redis机器
redis:
cluster:
appPrefix: DEV_DEMO_
nodes: 10.138.37.91:8501,10.138.37.91:8502,10.138.37.91:8503,10.138.37.91:8504,10.138.37.91:8505,10.138.37.91:8506
POM引入
<?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.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zzd</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.0.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<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>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--lombok相关-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!-- 开发联调环境 -->
<id>dev</id>
<properties>
<profiles.active>dev</profiles.active>
<modifier>-dev</modifier>
</properties>
<activation>
<!-- 默认的,不加参数时执行这个profile -->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>
</project>
Redis工具类
RedisConfig
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
@Getter
@Setter
public class RedisConfig {
// 前缀
private String appPrefix = "";
// 设定默认值,会被配置中心的redis集群IP:PORT所覆盖
private String nodes =
"10.100.121.106:8501,10.100.121.106:8502,10.100.121.106:8503,10.100.121.106:8504,10.100.121.106:8505,"
+ "10.100.121.106:8506";
private int maxTotal = 1000;
private int maxIdle = 100;
private int minIdle = 0;
private long maxWait = 3000;
private int connectionTimeout = 6000;
private int soTimeout = 2000;
private int maxAttempts = 3;
private String pass;
@Bean
public JedisCluster jedisCluster() {
String[] redisNodes = this.nodes.split(",");
Set<HostAndPort> nodes = new HashSet<>();
for (String node : redisNodes) {
String[] arr = node.split(":");
HostAndPort hostAndPort =
new HostAndPort(arr[0], Integer.parseInt(arr[1]));
nodes.add(hostAndPort);
}
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxTotal(maxTotal);
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxWaitMillis(maxWait);
if (StringUtils.isEmpty(pass)) {
// 如果pass传进来是个空字符串,就不要传密码了
pass = null;
}
// 实例化JedisCluster
JedisCluster cluster = new JedisCluster(nodes, connectionTimeout, soTimeout, maxAttempts, pass, poolConfig);
return cluster;
}
}
RedisCacheManager
@Component
@Slf4j
public class RedisCacheManager {
private static final Long RELEASE_SUCCESS = 1L;
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 默认超时时间 5天
*/
private static final int EXP = 5 * 60 * 60;
/**
* UTF-8
*/
private static final String CHARSET = "UTF-8";
@Autowired
private RedisConfig redisConfig;
@Autowired
private JedisCluster jedisCluster;
// redis的五种数据结构
// 1、字符串
// 2、Hash>
// 3、List
// 4、zset
// 5、set
// 1、字符串
/**
* 基于字符串的 get set
*
* @param key
* @return 字符串
*/
public String getStr(String key) {
try {
if (StringUtils.isNotBlank(key)) {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
return new String(jedisCluster.get(key.getBytes()), CHARSET);
}
} catch (Exception e) {
log.error("CacheManager getStr error !", e);
}
return null;
}
public void setStr(String key, String value) {
try {
if (StringUtils.isNotBlank(key)) {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
jedisCluster.set(key.getBytes(), value.getBytes(CHARSET));
}
} catch (Exception e) {
log.error("CacheManager setStr error !", e);
}
}
public Long decr(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
return jedisCluster.decr(key.getBytes());
} catch (Exception e) {
log.error("CacheManager decr error !", e);
}
return null;
}
public Long incr(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
return jedisCluster.incr(key.getBytes());
} catch (Exception e) {
log.error("CacheManager incr error !", e);
}
return null;
}
public Boolean compareAndSet(String key, long targetValue, long incrValue) {
if (StringUtils.isBlank(key)) {
return false;
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
String script =
"if redis.call('EXISTS',KEYS[1]) == 1 then " +
" local value=redis.call('GET', KEYS[1]) " +
" local tempValue=tonumber(value) + tonumber(ARGV[2]) " +
" if tonumber(tempValue) > tonumber(ARGV[1]) then " +
" return value" +
" end" +
" return redis.call('SET', KEYS[1], tempValue)" +
"else" +
" return redis.call('SET', KEYS[1], ARGV[2])" +
"end";
Object result = jedisCluster.eval(script, Arrays.asList(key), Arrays.asList(String.valueOf(targetValue),
String.valueOf(incrValue)));
log.info("compareAndSet result is [{}]", result);
return LOCK_SUCCESS.equals(result);
} catch (Exception e) {
log.error("CacheManager compareAndSet error !", e);
}
return false;
}
// 2、Hash
public Boolean hExists(String key, String field) {
try {
if (StringUtils.isBlank(key) || StringUtils.isBlank(field)) {
return false;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
field = URLEncoder.encode(field, CHARSET);
return jedisCluster.hexists(key.getBytes(), field.getBytes());
} catch (Exception e) {
log.error("CacheManager hExists error !", e);
}
return false;
}
public void hputStr(String key, String field, String value, Integer expire) {
try {
if (StringUtils.isBlank(key) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) {
return;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
field = URLEncoder.encode(field, CHARSET);
jedisCluster.hset(key.getBytes(), field.getBytes(), value.getBytes(CHARSET));
if (expire > 0) {
jedisCluster.expire(key.getBytes(), expire);
}
} catch (Exception e) {
log.error("CacheManager hput error !", e);
}
}
public String hgetStr(String key, String field) {
try {
if (StringUtils.isBlank(key) || StringUtils.isBlank(field)) {
return null;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
field = URLEncoder.encode(field, CHARSET);
byte[] hget = jedisCluster.hget(key.getBytes(), field.getBytes());
return new String(hget, CHARSET);
} catch (Exception e) {
log.error("CacheManager hget error !", e);
}
return null;
}
public Map<String, String> hGetStrAll(String key) {
Map<String, String> result = new HashMap<>();
try {
if (StringUtils.isNotBlank(key)) {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
Map<byte[], byte[]> map = jedisCluster.hgetAll(key.getBytes());
if (map != null && map.size() > 0) {
for (Map.Entry<byte[], byte[]> entry : map.entrySet()) {
result.put(new String(entry.getKey(), CHARSET), new String(entry.getValue(), CHARSET));
}
}
}
return result;
} catch (Exception e) {
log.error("CacheManager hGetAll error !", e);
}
return result;
}
public void hdel(String key, String field) {
try {
if (StringUtils.isBlank(key) || StringUtils.isBlank(field)) {
return;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
field = URLEncoder.encode(field, CHARSET);
jedisCluster.hdel(key.getBytes(), field.getBytes());
} catch (Exception e) {
log.error("CacheManager hdel error !", e);
}
}
// 3、list
/**
* 字符串
*
* @param key
* @param value
*/
public void lpushStr(String key, String value) {
try {
if (StringUtils.isNotBlank(key)) {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
jedisCluster.lpush(key.getBytes(), value.getBytes(CHARSET));
}
} catch (Exception e) {
log.error("CacheManager lpushStr error !", e);
}
}
/**
* 字符串
*
* @param key
* @param values
*/
public void lpushStr(String key, String[] values) {
try {
if (StringUtils.isNotBlank(key)) {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
byte[][] bytes = new byte[values.length][];
for (int i = 0; i < values.length; i++) {
bytes[i] = values[i].getBytes(CHARSET);
}
jedisCluster.lpush(key.getBytes(), bytes);
}
} catch (Exception e) {
log.error("CacheManager lpushStr error !", e);
}
}
/**
* @param key
* @return 字符串
*/
public String rpopStr(String key) {
try {
if (StringUtils.isNotBlank(key)) {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
byte[] bytes = jedisCluster.rpop(key.getBytes());
if (bytes == null) {
return null;
}
return new String(bytes, CHARSET);
}
} catch (Exception e) {
log.error("CacheManager rpopStr error !", e);
}
return null;
}
/**
* 获取list的长度
*
* @param key
* @return
*/
public Long listLen(String key) {
try {
if (StringUtils.isNotBlank(key)) {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
return jedisCluster.llen(key);
}
} catch (Exception e) {
log.error("CacheManager get error !", e);
}
return -1L;
}
public Set<String> hKeys(String key) {
if (StringUtils.isBlank(key)) {
return new HashSet<>();
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
Set<byte[]> keys = jedisCluster.hkeys(key.getBytes());
Set<String> result = new HashSet<>();
for (byte[] k : keys) {
result.add(URLDecoder.decode(new String(k), CHARSET));
}
return result;
} catch (Exception e) {
log.error("CacheManager hKeys error!", e);
return new HashSet<>();
}
}
// 4、zset
public void zaddStr(String key, double score, String value) {
try {
if (StringUtils.isBlank(key) || value == null) {
return;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
jedisCluster.zadd(key.getBytes(), score, value.getBytes(CHARSET));
} catch (Exception e) {
log.error("CacheManager zaddStr error !", e);
}
}
public void zremStr(String key, String value) {
try {
if (StringUtils.isBlank(key)) {
return;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
jedisCluster.zrem(key.getBytes(), value.getBytes(CHARSET));
} catch (Exception e) {
log.error("CacheManager zremStr error !", e);
}
}
public Set<String> zrangeStr(String key, long start, long end) {
if (StringUtils.isBlank(key)) {
return new HashSet<>();
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
Set<byte[]> values = jedisCluster.zrange(key.getBytes(), start, end);
Set<String> result = new HashSet<>();
for (byte[] v : values) {
result.add(new String(v));
}
return result;
} catch (Exception e) {
log.error("CacheManager zrangeStr error!", e);
return new HashSet<>();
}
}
public Set<String> zrangeByScoreStr(String key, double lowScore, double highScore) {
if (StringUtils.isBlank(key)) {
return new HashSet<>();
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
Set<byte[]> values = jedisCluster.zrangeByScore(key.getBytes(), lowScore, highScore);
Set<String> result = new HashSet<>();
for (byte[] v : values) {
result.add(new String(v));
}
return result;
} catch (Exception e) {
log.error("CacheManager zrangeByScoreStr error!", e);
return new HashSet<>();
}
}
public Double zscore(String key, String value) {
if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
return null;
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
Double zscore = jedisCluster.zscore(key.getBytes(), value.getBytes(CHARSET));
return zscore;
} catch (Exception e) {
log.error("CacheManage zscore error!", e);
return null;
}
}
/**
* 从zset中获取满足score值的member信息
*
* @param key
* @param lowScore 最低score
* @param highScore 最高score
* @param count 需要取出的数量
* @return
*/
public List<String> zrangeByScoreAndRem(String key, double lowScore, double highScore, int count) {
if (StringUtils.isBlank(key)) {
return new ArrayList<>();
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
String script = "local members = " +
" redis.call('zrangebyscore', KEYS[1], tonumber(ARGV[1]), tonumber(ARGV[2]), " +
" 'limit', 0, tonumber(ARGV[3])); " +
" if #members > 0 then " +
" for i, v in ipairs(members) " +
" do " +
" redis.call('zrem', KEYS[1], v); " +
" end; " +
" end; " +
" return members;";
Object object = jedisCluster.eval(script, Arrays.asList(key), Arrays.asList(String.valueOf(lowScore),
String.valueOf(highScore), String.valueOf(count)));
if (object == null) {
return new ArrayList<>();
} else {
return (ArrayList) object;
}
} catch (Exception e) {
log.error("CacheManager zrangeByScoreAndRem error!", e);
}
return new ArrayList<>();
}
/**
* 使用lua脚本,封装底层方法
* 若返回null,则说明参数校验不过,或处理出现异常( keys都需要使用{}封装)
* 若返回Object,说明处理正常
*
* @param keys
* @param argv
* @param script
* @return
*/
public Object useLuaScript(List<String> keys, List<String> argv, String script) {
if (CollectionUtils.isEmpty(keys)) {
log.error("key is missed !,key:{},argv:{},script:{}", keys, argv, script);
return null;
}
try {
// 加上环境前缀
List<String> targetKeys =
keys.stream().map(s -> redisConfig.getAppPrefix() + s.trim()).collect(Collectors.toList());
log.info("targetKeys:{}", targetKeys);
return jedisCluster.eval(script, targetKeys, argv);
} catch (Exception e) {
log.error("CacheManager decrUntilLower error!", e);
return null;
}
}
/**
* 原子性操作,从zset中拿出score最小的一个元素并删除
* 返回list的第一个元素为score值
* 第二个元素为value值
*
* @param key
* @return
*/
public List<String> popInfoFromZset(String key) {
key = redisConfig.getAppPrefix() + key;
try {
// 增加环境前缀
key = URLEncoder.encode(key, CHARSET);
/**
* 出队:查找队列score值最大的记录,并删除返回
*/
String script = "local table_res=redis.call('zrange',KEYS[1],ARGV[1],ARGV[2]) \n"
+ "if next(table_res)==nil then \n"
+ " return nil \n"
+ "else \n"
+ " local zscore = redis.call('zscore',KEYS[1],table_res[1])\n"
+ " local result = {zscore, table_res[1]} \n"
+ " redis.call('zrem',KEYS[1],table_res[1]) \n"
+ " return result\n"
+ "end";
Object result = jedisCluster.eval(script, Arrays.asList(key), Arrays.asList("0", "0"));
if (result == null) {
return null;
} else {
return (ArrayList) result;
}
} catch (Exception e) {
log.error("CacheManager zpop error !", e);
}
return null;
}
public boolean searchDistributeLock(String lockKey) {
try {
lockKey = redisConfig.getAppPrefix() + lockKey;
lockKey = URLEncoder.encode(lockKey, CHARSET);
String lockResult = jedisCluster.get(lockKey);
return StringUtils.isNotEmpty(lockResult);
} catch (Exception e) {
log.error("CacheManager searchDistributeLock error !", e);
}
return false;
}
public boolean getDistributeLock(String lockKey, String uuid, int timeoutMs) {
try {
lockKey = redisConfig.getAppPrefix() + lockKey;
lockKey = URLEncoder.encode(lockKey, CHARSET);
String result = jedisCluster.set(lockKey, uuid, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, timeoutMs);
return LOCK_SUCCESS.equals(result);
} catch (Exception e) {
log.error("CacheManager getDistributeLock error !", e);
}
return false;
}
public boolean releaseDistributedLock(String lockKey, String uuid) {
try {
lockKey = redisConfig.getAppPrefix() + lockKey;
lockKey = URLEncoder.encode(lockKey, CHARSET);
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedisCluster.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(uuid));
return RELEASE_SUCCESS.equals(result);
} catch (Exception e) {
log.error("CacheManager releaseDistributedLock error !", e);
}
return false;
}
public void releaseLock(String lockKey) {
try {
this.del(lockKey);
} catch (Exception e) {
log.error("CacheManager releaseLock error !", e);
}
}
/**
* 默认过期时间5天的锁,只有key
*
* @param key
* @return
*/
public boolean getLock(String key) {
return this.getLock(key, EXP);
}
/**
* 默认过期时间5天的锁,带有key-value的
*
* @param key
* @param value
* @return
*/
public boolean getLock(String key, String value) {
return this.getDistributeLock(key, value, EXP);
}
/**
* 自定义过期时间的锁
*
* @param key
* @param timeoutMs
* @return
*/
public boolean getLock(String key, int timeoutMs) {
return this.getDistributeLock(key, UUID.randomUUID().toString(), timeoutMs);
}
/**
* 阻塞锁
*
* @param key 锁key
* @param lockTimeout 锁超时时间
* @param getLockTimeout 获取锁超时时间
* @param seconds 获取锁超时时间单位
* @return 是否获取到锁
*/
public boolean getBlockingLock(String key, int lockTimeout, long getLockTimeout, TimeUnit seconds) {
long start = System.nanoTime();
long nanoWaitForLock = seconds.toNanos(getLockTimeout);
try {
while ((System.nanoTime() - start) < nanoWaitForLock) {
if (getLock(key, lockTimeout)) {
if (log.isDebugEnabled()) {
log.debug("add RedisLock[{}].{}", key, Thread.currentThread());
}
return true;
}
//加随机时间防止活锁
TimeUnit.MILLISECONDS.sleep(10 + new Random().nextInt(10));
}
} catch (Exception e) {
log.error("{}", e.getMessage(), e);
releaseLock(key);
}
return false;
}
// 5、set
/**
* set类型插入
*
* @param key
* @param member
* @param exp
*/
public void sadd(String key, String member, Integer exp) {
try {
if (StringUtils.isBlank(key) || member == null) {
return;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
member = URLEncoder.encode(member, CHARSET);
jedisCluster.sadd(key, member);
if (exp > 0) {
jedisCluster.expire(key.getBytes(), exp);
}
} catch (Exception e) {
log.error("CacheManager sadd error !", e);
}
}
/**
* set类型插入,有的话插入,没有的话返回false
*
* @param key
* @param member
*/
public boolean saddLua(String key, String member) {
key = redisConfig.getAppPrefix() + key;
try {
// 增加环境前缀
key = URLEncoder.encode(key, CHARSET);
member = URLEncoder.encode(member, CHARSET);
/**
* 出队:查找队列score值最大的记录,并删除返回
*/
String script = "local table_res=redis.call('sismember',KEYS[1],ARGV[1]) \n"
+ "if table_res==1 then \n"
+ " return 0 \n"
+ "else \n"
+ " redis.call('sadd',KEYS[1],ARGV[1])\n"
+ " return 1\n"
+ "end";
Object result = jedisCluster.eval(script, Arrays.asList(key), Arrays.asList(member));
Long res = (Long) result;
if (res == 0L) {
return false;
} else {
return true;
}
} catch (Exception e) {
log.error("CacheManager zpop error !", e);
}
return true;
}
/**
* set类型插入
*
* @param key
* @param member
* @param exp
*/
public void saddStr(String key, String member, Integer exp) {
try {
if (StringUtils.isBlank(key) || member == null) {
return;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
jedisCluster.sadd(key, member);
if (exp > 0) {
jedisCluster.expire(key.getBytes(), exp);
}
} catch (Exception e) {
log.error("CacheManager sadd error !", e);
}
}
/**
* set类型删除
*
* @param key
* @param member
*/
public void srem(String key, String member) {
try {
if (StringUtils.isBlank(key)) {
return;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
member = URLEncoder.encode(member, CHARSET);
jedisCluster.srem(key, member);
} catch (Exception e) {
log.error("CacheManager srem error !", e);
}
}
/**
* set类型根据key值查询所有元素
*
* @param key
*/
public Set<String> members(String key) {
try {
if (StringUtils.isBlank(key)) {
return new HashSet<String>();
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
Set<String> smembers = jedisCluster.smembers(key);
return smembers;
} catch (Exception e) {
log.error("CacheManager sismember error !", e);
return new HashSet<String>();
}
}
/**
* set类型根据key值查询指定元素是否存在
*
* @param key
*/
public boolean sismember(String key, String member) {
try {
if (StringUtils.isBlank(key)) {
return false;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
member = URLEncoder.encode(member, CHARSET);
Boolean sismember = jedisCluster.sismember(key, member);
return sismember;
} catch (Exception e) {
log.error("CacheManager sismember error !", e);
return false;
}
}
// 6、对于Key的管理:失效、删除、存在、有效时间
public Long expire(String key, int timeout) {
if (StringUtils.isBlank(key)) {
return null;
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
return jedisCluster.expire(key.getBytes(), timeout);
} catch (Exception e) {
log.error("CacheManager expire error !", e);
}
return null;
}
public void del(String key) {
try {
if (StringUtils.isBlank(key)) {
return;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
jedisCluster.del(key.getBytes());
log.info("\n\n Key:{}, had delete \n\n ", key);
} catch (Exception e) {
log.error("CacheManager remove error !", e);
}
}
public void del(String... keys) {
try {
if (keys == null || keys.length <= 0) {
return;
}
byte[][] bytes = new byte[keys.length][];
for (int i = 0; i < keys.length; i++) {
String key = redisConfig.getAppPrefix() + keys[i];
bytes[i] = URLEncoder.encode(key, CHARSET).getBytes();
}
jedisCluster.del(bytes);
log.info("\n\n Key:{}, had delete \n\n ", keys);
} catch (Exception e) {
log.error("CacheManager remove error !", e);
}
}
/**
* @param key
* @return
*/
public Boolean exists(String key) {
if (StringUtils.isBlank(key)) {
return false;
}
try {
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
return jedisCluster.exists(key.getBytes());
} catch (Exception e) {
log.error("CacheManager exists error!", e);
return false;
}
}
/**
* 以秒为单位,返回给定 key 的剩余生存时间
*
* @param key
* @return
*/
public Long ttl(String key) {
try {
if (StringUtils.isBlank(key)) {
return -999L;
}
key = redisConfig.getAppPrefix() + key;
key = URLEncoder.encode(key, CHARSET);
return jedisCluster.ttl(key.getBytes());
} catch (Exception e) {
log.error("CacheManager getTtl error !", e);
return -999L;
}
}
}
test类
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class DemoApplicationTests {
private int threadCapacity = 5;
public ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(threadCapacity,
threadCapacity, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100));
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
}
@Slf4j
public class RedisTest extends DemoApplicationTests {
@Autowired
protected RedisCacheManager redisCacheManager;
@Test
public void testRedisSet() {
redisCacheManager.setStr("companyName", "Baidu");
log.info(redisCacheManager.getStr("companyName"));
}
}
验证结果
说了这么多缓存的弊端,那你是不是不敢用了呢?哈哈,博主在这举个小例子,来说明如何看待缓存的事情吧。邓爷爷曾经对我国航空航天总设计师说:我国火箭发射要0失误。航空航天总设计师回应:如此复杂的系统,怎么可能做到0失误呢?邓爷爷说:人不犯错误是不可能的,我说的0失误是要精益求精,知难而上。回过头来看缓存的事情,其实若系统QPS不大或者对一致性要求不是特别高,我说的弊端你都碰不上或者不影响你系统正常运转,REDIS自身就帮你控制住了;但是,若你有强一致数据的要求、QPS量很高时候,那就要精益求精,强调工匠精神了。