详解Redis的使用及缓存特性

序言
你什么时候会想起用缓存?提升系统访问的速度?缩短单个交易处理时长?你真的了解缓存么?这是springBoot框架的第二篇,REDIS的使用

关联文章

SpringBoot工程搭建

详解缓存Redis

详解数仓ElasticSearch

详解消息中间件Kafka

本文目录

  • 回答三个问题
  • SpringBoot调用Redis的Demo
  • 总结

一、回答三个问题

  • 缓存的本质
  • 缓存适用的场景
  • 缓存的弊端

第一,缓存的本质是,提升访问速度。缓存的读取速度介于硬盘与内存之间,内存很快,但断电即毁;硬盘太慢,磁头访问速率受限(同理,DB和缓存的访问速度比起来,还是个小弟弟)。
第二,缓存的适用场景是,“读多写少”的场景,“读多写多”为啥不能适用呢?为了读取到最新的数据,一旦发生数据写,就得删除旧缓存,有这功夫您还不如直接问问DB老哥。
第三,缓存的弊端是:
1、加大了系统的复杂度,尤其是可能带来数据不一致的问题,非常致命。首先,为了数据兜底,缓存+数据库是很常见的架构方案,但这就带来了数据”写后读“导致数据一致性的问题:无论是先更新数据库,再删缓存;还是先删缓存,再更新数据库,都需要额外的努力才能确保一致性;其次,缓存集群的架构,本质上是AP的系统,若主节点宕机时、选主未成功,期间的写入数据都会丢失;
2、缓存存储不适合复杂查询,复杂查询更适合数据库SQL执行,复杂体现在两方面:首先,数据冗余问题,例如同一条记录的查询需要基于A字段查出,也有场景需要基于B字段,那么在Redis里面只能存储基于A和基于B两份相同的数据;其次,范围查找困难,相类似的,与、或集合操作也有风险,博主曾做过两个Zset的与操作,在保证交易原子性方面需要付出额外的努力(LUA脚本)。以上两点,均可由数据库一条SQL搞定。

缓存一致性保证措施

二、springBoot调用Redis的Demo

  • 准备工作
  • demo演示

准备工作

redis的五种数据结构介绍

数据结构 举例
字符串
Hash >
List
zset
set

Redis集群模式已经搭建完成
至于为何使用集群模式呢?分片、支持高并发写,上述两点足以
详解Redis的使用及缓存特性_第1张图片

# 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演示

  • 配置文件的修改
  • POM引入
  • Redis工具类
  • test类
  • 验证结果

先贴一张全景图详解Redis的使用及缓存特性_第2张图片

配置文件的修改
第一篇讲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 :配置并实例化 JedisCluster,一个访问redis的客户端
  • RedisCacheManager:调用jedisCluster,封装基础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类

  • 基础类
  • RedisTest
@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"));
    }
}

验证结果

工程正常启动
详解Redis的使用及缓存特性_第3张图片
test案例返回我们插入的数据
详解Redis的使用及缓存特性_第4张图片

三、总结

说了这么多缓存的弊端,那你是不是不敢用了呢?哈哈,博主在这举个小例子,来说明如何看待缓存的事情吧。邓爷爷曾经对我国航空航天总设计师说:我国火箭发射要0失误。航空航天总设计师回应:如此复杂的系统,怎么可能做到0失误呢?邓爷爷说:人不犯错误是不可能的,我说的0失误是要精益求精,知难而上。回过头来看缓存的事情,其实若系统QPS不大或者对一致性要求不是特别高,我说的弊端你都碰不上或者不影响你系统正常运转,REDIS自身就帮你控制住了;但是,若你有强一致数据的要求、QPS量很高时候,那就要精益求精,强调工匠精神了。

你可能感兴趣的:(中间件Middleware,redis,java,分布式,缓存,jedis)