缓存基础:Guava Cache + Redis

缓存基础

Guava Cache

Guava Cache 介绍:是 Google 提供的一套 Java 工具包,是一套非常完善的本地缓存机制(JVM缓存)。Guava Cache 的设计来源于 CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值,且保持很高的并发读写能力。

pom.xml

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>

Guava Cache 的两种创建方式

CacheLoader 方式
@Test
void GuavaCache1() throws ExecutionException {

    // CacheLoader 方式创建一个缓存
    LoadingCache<String, List<User>> cache = CacheBuilder.newBuilder().build(new CacheLoader<>() {
        @Override
        public List<User> load(String key) {
            // 读取数据源
            return userRepository.findAll();
        }
    });

    // 如果缓存没有则读取数据源;userList 是 缓存的 key;
    List<User> userList = cache.get("userList");
    System.out.println(cache.size()); // 1
    System.out.println(userList); // [User(id=1, username=zhangsan, password=123456), User(id=2, username=lisi, password=123456), User(id=3, username=wangwu, password=123456)]
}
Callable 方式
@Test
void GuavaCache2() throws ExecutionException {
    // 创建缓存
    Cache<String, List<User>> cache = CacheBuilder.newBuilder().build();

    // Callable 方式创建缓存
    // 在 get 的时候;如果没有则读取数据源
    List<User> userList = cache.get("userList", new Callable<List<User>>() {
        @Override
        public List<User> call() throws Exception {
            return userRepository.findAll();
        }
    });

    System.out.println(cache.size());
    System.out.println(userList);
}

缓存的参数

@Test
void GuavaCache3() {
    Cache<String, List<User>> cache = CacheBuilder.newBuilder()
        // 缓存的最大个数
        .maximumSize(3)
        // 隔多长时间后没有被访问过的key被删除
        .expireAfterAccess(3, TimeUnit.SECONDS)
        // 写入多长时间后过期;一般选择
        .expireAfterWrite(3, TimeUnit.SECONDS).build();

    // 清空当前 key 的缓存
    cache.invalidate("userList");

    // 根据 key 批量清空缓存
    cache.invalidateAll(Arrays.asList("userList", "userList1"));

    // 清空缓存
    cache.invalidateAll();
}

Redis

Reids 的安装

# wget 下载 reids 包
wget http://download.redis.io/releases/redis-6.0.6.tar.gz
# 解压到当前目录
tar xzf redis-6.0.6.tar.gz
# 进入解压后的文件
cd redis-6.0.6
# 进行编译
make
安装中出现的错误及解决办法
  1. 错误一:
/bin/sh: cc: command not found
make[1]: *** [adlist.o] Error 127
make[1]: Leaving directory `/app/redis-6.2.6/src'
make: *** [all] Error 2

解决办法:

# 安装 gcc
yum install -y gcc
  1. 错误二:
In file included from adlist.c:34:0:
zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such file or directory
 #include <jemalloc/jemalloc.h>
                               ^
compilation terminated.
make[1]: *** [adlist.o] Error 1
make[1]: Leaving directory `/app/redis-6.2.6/src'
make: *** [all] Error 2

解决办法:

# 安装 jemalloc
yum install -y jemalloc

最后进行编译:

make MALLOC=/usr/local/jemalloc/lib

Redis 远程连接

# 注释掉本地绑定
#bind 127.0.0.1 -::1
# 关掉保护模式
protected-mode no
# 开启后台守护模式
daemonize yes
# 设置密码
requirepass [123456]

Springboot 操作 Redis

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
  redis:
    port: 6379
    host: 127.0.0.1
    password: 123456
    database: 1
string
赋值操作
// 设置 string 类型的值
redisTemplate.opsForValue().set("key", "value");

// 设置 value 时;同时设置过期时间
// redisTemplate.opsForValue().set("key", "hello", 1, TimeUnit.DAYS);
redisTemplate.opsForValue().set("key", "hello", Duration.ofHours(1));

// 在 key 不存在时设置 key 的值;若存在则返回 false
Boolean flag = redisTemplate.opsForValue().setIfAbsent("key", "hello");

// 在 key 存在时设置 key 的值;若不存在则返回 false
Boolean flag = redisTemplate.opsForValue().setIfPresent("key", "hello");

// 设置 并返回原值;若没有原值 则只设置 返回 null
String source = redisTemplate.opsForValue().getAndSet("key", "hello");

// 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。hello -> hhello;setRange 命令
redisTemplate.opsForValue().set("key", "hello", 1);

// 往 value 后面追加值;返回值是追加后的字符串长度 valueredis
Integer length = redisTemplate.opsForValue().append("key", "redis");

// 如果 set 时是一个数值类型的 value 返回值是计算后的值
// redisTemplate.opsForValue().decrement() --操作
Long num = redisTemplate.opsForValue().increment("num", 1); // ++ 操作

// 批量设置
HashMap<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
redisTemplate.opsForValue().multiSet(map);

// 在所有 key 都不存在时进行设置;存在一个都会插入失败 返回 false
Boolean flag = redisTemplate.opsForValue().multiSetIfAbsent(map);
取值操作
// 根据 key 获取 value
String key = redisTemplate.opsForValue().get("key");

// 根据 key 返回原 value 并设置过期时间
String key = redisTemplate.opsForValue().getAndExpire("key", 1, TimeUnit.HOURS);

// 根据 key 获取 value 的同时进行截取;value -> alu 索引从0开始;getRange 命令
String key = redisTemplate.opsForValue().get("key", 1, 3);

// 批量获取 mGet 命令;返回值的长度和 key 的长度一致;若查询的 key 没有结果则返回 null 放入集合
List<String> keyList = Arrays.asList("key1", "key2", "key3", "key4");
List<String> resultList = redisTemplate.opsForValue().multiGet(keyList);

// 根据 key 获取 value 字符串长度;若不存在则返回 0
Long key = redisTemplate.opsForValue().size("key");
list
赋值操作
// 将 value 插入 list 集合中;返回值是 list 的长度
Long length = redisTemplate.opsForList().leftPush("list", "value");

// 批量插入;返回值是 list 的长度
List<String> valueList = Arrays.asList("1", "2", "3");
Long length = redisTemplate.opsForList().leftPushAll("list",valueList);

// 在列表的某个元素前插入 value;返回值是 list 的长度
Long length = redisTemplate.opsForList().leftPush("list1", "3", "value");

// 将一个值插入到[已存在]的列表头部;返回值是 list 的长度;若列表不存在则不会插入返回 0;
Long length = redisTemplate.opsForList().leftPushIfPresent("list", "4");

// 从一个list中将头部或尾部元素 移动到另一个集合的头部或尾部;返回值是移动的元素
String document = redisTemplate.opsForList().move("list1", RedisListCommands.Direction.LEFT,
                                                  "list2", RedisListCommands.Direction.LEFT);
String document = redisTemplate.opsForList().move(ListOperations.MoveFrom.fromHead("list1"),
                                                  ListOperations.MoveTo.toHead("list2"));

// 通过索引设置列表元素的值 lSet;若列表里没有所以会报:ERR index out of range
redisTemplate.opsForList().set("list",0,"5");

// 从list1的尾部插入list2的头部;功能同 move;返回移动的元素
String document = redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2");
取值操作
// 通过索引获取列表中的元素 下标从 0 开始
String document = redisTemplate.opsForList().index("list", 0);

// 查询某个元素第一次出现的位置 从 0 开始;
Long first = redisTemplate.opsForList().indexOf("list", "3");

// 查询某个元素最后一次出现的位置
Long last = redisTemplate.opsForList().lastIndexOf("list", "3");

// 移出并获取列表的第一个元素
String first = redisTemplate.opsForList().leftPop("list");

// 移出并获取列表的从第一个开始的多个元素
List<String> firstList = redisTemplate.opsForList().leftPop("list", 2);

// 让列表只保留指定下标区间内的元素,不在指定下标区间之内的元素都将被删除
redisTemplate.opsForList().trim("list",0,2);

// 获取列表长度
Long length = redisTemplate.opsForList().size("list");

// 获取列表指定范围内的元素 下标从 0 开始
List<String> rangeList = redisTemplate.opsForList().range("list", 0, 1);

// 移除列表中与参数 VALUE 相等的元素 数量为 COUNT 正数代表从表头开始
Long removeLength = redisTemplate.opsForList().remove("list", 10, "4");
hash
赋值操作
// 单个赋值
redisTemplate.opsForHash().put("hashKey", "key1", "value1");

// 多个赋值
HashMap<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
redisTemplate.opsForHash().putAll("hashKey", map);

// 如果 key1 不存在;则插入 value1;若存在则返回false
Boolean flag = redisTemplate.opsForHash().putIfAbsent("hashKey", "key1", "value1");

// 当 value 是数值类型的时候
Long increment = redisTemplate.opsForHash().increment("hashKey1", "hello", 3);
Long decrement = redisTemplate.opsForHash().increment("hashKey1", "hello", -3);
取值操作
// 单个取值
Object o = redisTemplate.opsForHash().get("hashKey", "key1");

// 批量取值
Map<Object, Object> hashKey = redisTemplate.opsForHash().entries("hashKey");

// 随机获取一个 map
Map.Entry<Object, Object> hashKey = redisTemplate.opsForHash().randomEntry("hashKey");

// 随机获取 count 个 map
Map<Object, Object> hashKey = redisTemplate.opsForHash().randomEntries("hashKey", 2);

// 随机获取一个 key
Object hashKey = redisTemplate.opsForHash().randomKey("hashKey");

// 随机获取 count 个 key
List<Object> hashKey = redisTemplate.opsForHash().randomKeys("hashKey", 2);

// 返回所有的 key
Set<Object> ketSet = redisTemplate.opsForHash().keys("hashKey");

// 返回所有的 value
List<Object> valueList = redisTemplate.opsForHash().values("hashKey");

// 返回 hash 的长度
Long length = redisTemplate.opsForHash().size("hashKey");

// 返回当前 key的 value 元素的字符串长度
Long length = redisTemplate.opsForHash().lengthOfValue("hashKey", "key1");

// 删除 key;返回值是真正删除的key的个数
Long delete = redisTemplate.opsForHash().delete("hashKey1", "hashKey2", "hashKey3");
set
赋值操作
// 向集合添加一个或多个成员;返回值是 set 的长度 相同元素会自动排除
Long length = redisTemplate.opsForSet().add("set1", "a", "b", "c", "d");
Long length = redisTemplate.opsForSet().add("set2", "x", "y", "c", "d");

List<String> setList = Arrays.asList("set1", "set2");
// 差集 返回第一个集合与其他集合之间的差异 以 set1 为主 [a,b]
Set<String> difference = redisTemplate.opsForSet().difference("set1", "set2");
// 返回差集并进行存储 返回值是新 set 的长度
Long length = redisTemplate.opsForSet().differenceAndStore(setList, "set3");

// 交集
Set<String> intersect = redisTemplate.opsForSet().intersect(setList);
// 返回交集并进行存储 返回值是新 set 的长度
Long length = redisTemplate.opsForSet().intersectAndStore(setList, "set3");

// 并集
Set<String> union = redisTemplate.opsForSet().union(setList);
// 返回并集并进行存储 返回值是新 set 的长度
Long length = redisTemplate.opsForSet().unionAndStore(setList, "set3");

// 将某个元素从 set1 中删除 移到 set2 中
Boolean flag = redisTemplate.opsForSet().move("set1", "a", "set2");
取值操作
// 查询单个元素是否在集合中
Boolean flag = redisTemplate.opsForSet().isMember("set", "a");

// 查询多个元素是否在集合中
Map<Object, Boolean> flagMap = redisTemplate.opsForSet().isMember("set1", "a", "y");

// 移除并返回集合中的一个随机元素
String document = redisTemplate.opsForSet().pop("set1");

// 移除并返回集合中 count 个随机元素
List<String> popList = redisTemplate.opsForSet().pop("set1", 2);

// 返回集合中的一个随机元素
String document = redisTemplate.opsForSet().randomMember("set1");

// 返回集合中的 count 个随机元素;count 是次数和 set 长度无关
List<String> randomList = redisTemplate.opsForSet().randomMembers("set1", 10);

// 直接删除 set 中的元素 返回值是删除成功的个数
Long length = redisTemplate.opsForSet().remove("set1", "a", "b", "c");

// 查询集合的长度
Long length = redisTemplate.opsForSet().size("set1");
zSet
赋值操作
// 向 zSet 中添加元素 返回值表示是否是新增
Boolean flag = redisTemplate.opsForZSet().add("zSet1", "a", 100);
Boolean flag = redisTemplate.opsForZSet().add("zSet1", "b", 200);
Boolean flag = redisTemplate.opsForZSet().add("zSet1", "c", 300);

// 向 zSet 中添加一个元素 如果不存在则添加成功 存在则添加失败
Boolean flag = redisTemplate.opsForZSet().addIfAbsent("zSet1", "d", 200);

// 批量添加元素 返回值是新增的个数;若 key 存在会被覆盖
Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
tuples.add(ZSetOperations.TypedTuple.of("a", 600D));
tuples.add(ZSetOperations.TypedTuple.of("b", 400D));
tuples.add(ZSetOperations.TypedTuple.of("d", 400D));
Long length = redisTemplate.opsForZSet().add("zSet2", tuples);

// 有序集合中对指定成员的分数加上增量 increment 返回值是计算后的值 a=>300
Double score = redisTemplate.opsForZSet().incrementScore("zSet1", "a", 200);

// zDiff 返回差集 [c]
Set<String> difference = redisTemplate.opsForZSet().difference("zSet1", "zSet2");

// 返回差集 并存储到 zSet3 中;返回值是 zSet3 的长度
List<String> setList = Arrays.asList("zSet1");
Long aLong = redisTemplate.opsForZSet().differenceAndStore("zSet2", setList, "zSet3");

// zDiffWithScores 返回差集带分数
Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet().differenceWithScores("zSet1", "zSet2");
for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
    String value = typedTuple.getValue();
    Double score = typedTuple.getScore();
}

// 返回交集
Set<String> intersect = redisTemplate.opsForZSet().intersect("zSet1", "zSet2");

// 返回交集带分数
// Aggregate 设置交集的方式
// Weights 设置分数的比例
Set<ZSetOperations.TypedTuple<String>> zset3 = redisTemplate.opsForZSet().intersectWithScores("zset3",
zset1, RedisZSetCommands.Aggregate.SUM, RedisZSetCommands.Weights.of(0.5, 1));

// 返回交集带分数并存储到 zSet3;交集元素的分数默认相加进行存储
Long length = redisTemplate.opsForZSet().intersectAndStore("zSet1", "zSet2", "zSet3");

// 分数的存储方式:RedisZSetCommands.Aggregate.SUM;MIN;MAX 
// 每个 key 里面分数的比例:RedisZSetCommands.Weights.of(0.5, 1) 参数的长度要和 "zSet2" + setList 的长度相同
Long length = redisTemplate.opsForZSet().intersectAndStore("zSet2", setList, "zSet3", RedisZSetCommands.Aggregate.SUM, RedisZSetCommands.Weights.of(0.5, 1));

// 返回并集
Set<String> union = redisTemplate.opsForZSet().union("zSet1", "zSet2");
// redisTemplate.opsForZSet().unionAndStore();
// redisTemplate.opsForZSet().unionWithScores();
取值操作
// 根据 value 获取 score
Double score = redisTemplate.opsForZSet().score("zSet1", "a");

// 计算在有序集合中指定区间分数的成员数 zCount 左闭右闭
Long length = redisTemplate.opsForZSet().count("zSet1", 300, 400);

// 返回并[删除]最高分的元素 可能有多个
ZSetOperations.TypedTuple<String> zSet1 = redisTemplate.opsForZSet().popMax("zSet1");
ZSetOperations.TypedTuple<String> zSet1 = redisTemplate.opsForZSet().popMax("zSet1",2);

// 返回并[删除]最低分的元素 可能有多个
ZSetOperations.TypedTuple<String> zSet1 = redisTemplate.opsForZSet().popMin("zSet1");
ZSetOperations.TypedTuple<String> zSet1 = redisTemplate.opsForZSet().popMin("zSet1",2);

// 分数默认从小到大排序 倒序以后就是从大到小
Set<String> zSet1 = redisTemplate.opsForZSet().range("zSet1", 0, 2);
Set<String> zSet1 = redisTemplate.opsForZSet().reverseRange("zSet1", 0, 2);
Long rank = redisTemplate.opsForZSet().rank("zSet1", "a");

Set<String> zSet1 = redisTemplate.opsForZSet().rangeByScore("zSet1", 100, 800);
Set<String> zSet1 = redisTemplate.opsForZSet().reverseRangeByScore("zSet1", 100, 800);
Long rank = redisTemplate.opsForZSet().reverseRank("zSet1", "a");

Set<ZSetOperations.TypedTuple<String>> zSet1 = redisTemplate.opsForZSet().rangeByScoreWithScores("zSet1", 100, 800);
Set<ZSetOperations.TypedTuple<String>> zSet1 = redisTemplate.opsForZSet().reverseRangeByScoreWithScores("zSet1", 100, 800);

// 根据 value 删除成员;返回值是删除成功的成员个数
Long length = redisTemplate.opsForZSet().remove("zSet1", "a", "b","x");
// 根据下标删除成员;返回值是删除成功的成员个数
Long length = redisTemplate.opsForZSet().removeRange("zSet1", 0, 2);
// 根据分数区间删除成员;返回值是删除成功的成员个数
Long length = redisTemplate.opsForZSet().removeRangeByScore("zSet1", 0, 100);

// zSet 成员个数
Long length = redisTemplate.opsForZSet().size("zSet1");

Lua 操作 Rdis

基本操作:

EVAL "return 'hello world'" 0
"hello world"

和 redis 一起的操作:

hget hashKey hello
"redis"
EVAL "return redis.call('hget',KEYS[1],ARGV[1])" 1 hashKey hello 
"redis"
EVAL "if redis.call('hget',KEYS[1],ARGV[1]) == 1 then return 2; else return 3; end;" 1 hashKey hello
"3"
springboot 操作 lua 语句
@Test
void lua1() {
    String script = "if redis.call('hget',KEYS[1],ARGV[1]) == 1 then return 2; else return 3; end;";
    RedisScript<Long> defaultRedisScript = new DefaultRedisScript<>(script, Long.class);
    Long execute = redisTemplate.execute(defaultRedisScript, Collections.singletonList("hashKey"), "hello");
    System.out.println(execute); // 3
}

参考git:https://gitee.com/zhangyizhou/learning-cache-redis-demo.git

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二级缓存是指在应用程序中同时使用两种不同的缓存技术,通常是将本地缓存和分布式缓存结合使用,以提高缓存的效率和可靠性。GuavaRedis都是常用的缓存库,下面介绍它们如何实现二级缓存。 1. Guava实现二级缓存 Guava是一个开源的Java工具库,其中包含了许多常用的工具类和数据结构,包括本地缓存Guava本地缓存是指将数据存储在应用程序内存中的缓存,可以用于提高应用程序的性能和响应速度。但是,本地缓存的生命周期受到应用程序的生命周期限制,一旦应用程序结束,缓存中的数据也就不存在了。为了解决这个问题,我们可以将Guava本地缓存和分布式缓存结合使用,实现二级缓存。 具体实现方法如下: 1)创建Guava本地缓存 ```java LoadingCache<String, Object> localCache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(new CacheLoader<String, Object>() { @Override public Object load(String key) throws Exception { // 从数据库或其他数据源中加载数据 return loadDataFromDatabase(key); } }); ``` 2)创建Redis分布式缓存 ```java JedisPool jedisPool = new JedisPool("localhost", 6379); Jedis jedis = jedisPool.getResource(); ``` 3)在应用程序中使用二级缓存 ```java public Object getObject(String key) { Object value = null; try { // 先从本地缓存中获取数据 value = localCache.get(key); } catch (Exception e) { e.printStackTrace(); } if (value == null) { // 如果本地缓存中没有数据,则从Redis缓存中获取数据 byte[] bytes = jedis.get(key.getBytes()); if (bytes != null) { value = SerializationUtils.deserialize(bytes); // 将数据存储到本地缓存中 localCache.put(key, value); } } if (value == null) { // 如果Redis缓存中也没有数据,则从数据库或其他数据源中加载数据 value = loadDataFromDatabase(key); // 将数据存储到Redis缓存和本地缓存中 byte[] bytes = SerializationUtils.serialize(value); jedis.set(key.getBytes(), bytes); localCache.put(key, value); } return value; } ``` 2. Redis实现二级缓存 Redis是一个开源的内存数据库,可以用于存储和管理缓存数据。Redis分布式缓存的优点是可以存储大量的数据,并且可以跨多个应用程序共享数据,但是它的缺点是需要额外的硬件和网络资源来支持,同时也存在单点故障的风险。为了解决这个问题,我们可以将Redis缓存和本地缓存结合使用,实现二级缓存。 具体实现方法如下: 1)创建Redis缓存客户端 ```java JedisPool jedisPool = new JedisPool("localhost", 6379); Jedis jedis = jedisPool.getResource(); ``` 2)创建Guava本地缓存 ```java LoadingCache<String, Object> localCache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(new CacheLoader<String, Object>() { @Override public Object load(String key) throws Exception { // 从Redis缓存中加载数据 byte[] bytes = jedis.get(key.getBytes()); if (bytes != null) { return SerializationUtils.deserialize(bytes); } // 如果Redis缓存中没有数据,则从数据库或其他数据源中加载数据 return loadDataFromDatabase(key); } }); ``` 3)在应用程序中使用二级缓存 ```java public Object getObject(String key) { Object value = null; try { // 先从本地缓存中获取数据 value = localCache.get(key); } catch (Exception e) { e.printStackTrace(); } if (value == null) { // 如果本地缓存中没有数据,则从Redis缓存中获取数据 byte[] bytes = jedis.get(key.getBytes()); if (bytes != null) { value = SerializationUtils.deserialize(bytes); // 将数据存储到本地缓存中 localCache.put(key, value); } } if (value == null) { // 如果Redis缓存中也没有数据,则从数据库或其他数据源中加载数据 value = loadDataFromDatabase(key); // 将数据存储到Redis缓存和本地缓存中 byte[] bytes = SerializationUtils.serialize(value); jedis.set(key.getBytes(), bytes); localCache.put(key, value); } return value; } ``` 以上就是GuavaRedis实现二级缓存的方法。需要注意的是,二级缓存的实现需要综合考虑应用程序的性能、复杂度、可靠性和安全性等方面的因素,选择合适的缓存技术和策略,才能达到最优的效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值