序文
Redis は優れたオープンソースで効率的なインメモリ データベースとして、さまざまなプロジェクトで使用されており、Redis を使いこなすことはプログラマーにとって必要なスキルの 1 つです。このブログ シリーズでは、アプリケーション シナリオを組み合わせて、Redis のインストールから使用、入門から上級までの関連コンテンツを説明します。
このブログでは、Spring プロジェクトの Redis に Java オブジェクトを格納する方法と、使用中に発生する問題を紹介します。
その他の関連する Redis ブログは次のとおりです。
Redis とは何か、Docker の Redis のインストール、Redis の基本データ型 + 共通コマンド、SpringBoot の Redis の事前統合
redis.conf 構成ファイルと組み合わせることで、Redis の 2 つのデータ永続化ソリューション、RDB と AOF について深く理解できます。
Redis データ永続性 & CAP 分散理論 (高可用性) & Redis マスター/スレーブ構築 & Redis センチネル メカニズム
Linux 上の Docker コンテナ Redis に基づいて 1 つのマスター、2 つのスレーブ、3 つのセンチネルを構築し、Redis センチネルを SpringBoot と統合します
Redis の不正アクセスの脆弱性と脆弱性の部分的な再発を理解し、接続パスワードを設定し、他の Redis コマンドを学習する
Redis プロジェクト アプリケーション (1): 検証コード -> UUID から Snowflake ID と JMeter の高同時実行テストとダウンロード、インストール、使用
Redis プロジェクト アプリケーション (2): 書籍の購入ラッシュ -> Redis の同時実行性の高さの問題と分散ロックの使用 Redission
Redisプロジェクト応用(4):キャッシュ予熱、ユーザー登録を例に→登録処理とキャッシュ予熱方法、quartz方式/@Schedule方式
Redis的项目应用(五):缓存自动更新 —>Canal管道 & MySQL配置+安装canal & 入门案例 & Canal的项目应用
Redis的项目应用(六):布隆过滤器—白名单 ----> Reids的问题,雪崩/ 击穿 / 穿透【重要】& 布隆过滤器
Redis数据一致性 & 用Java代码加锁解决一致性 & 采用lua脚本实现减1的原子性 & 分布式环境下的setnex锁及其问题 & Redission框架的使用
IDEA启动两个Tomcat服务的方式 & 使用nginx进行反向代理 & JMeter测试分布式情况下synchronized锁失效
目录
引出
1.之前的Redis相关博客的汇总;
2.redis里面存java对象的解决方案;
3.在实际使用中遇到的问题及处理;
如何在redis里面存java对象
1.本文的项目依赖
使用的是最基础的springboot的redis依赖
由于代码中用了布隆过滤器,因此这里引用了hutool工具包
<!-- redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>
2.核心配置类
核心是存对象的Redistemplate配置,
还包括了Redisson的配置,lua脚本的配置。
package com.tianju.fresh.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Bean;
import org.redisson.config.Config;
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.script.RedisScript;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redisson的配置,lua脚本的配置,redis序列化存对象的配置
*/
@Configuration
public class RedisConfig {
/**
* Redisson的配置,
* Redisson框架,分布式环境下redis数据一致性
* @return
*/
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379")
.setPassword("5672");
return Redisson.create(config);
}
/**
* lua脚本的配置类,让减库存-1操作原子化
* @return
*/
@Bean
public RedisScript<Long> redisScript(){
DefaultRedisScript redisScript = new DefaultRedisScript<>();
redisScript.setResultType(Long.class);
// lua脚本的位置
redisScript.setLocation(
new ClassPathResource("/lua/goods-unstock.lua") // 关联lua脚本
);
return redisScript;
}
/**
* redis序列化的相关配置,可以存对象
* @return
*/
@Bean
public RedisTemplate redisTemplateInit(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置序列化Key的实例化对象
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
/**
*
* 设置Hash类型存储时,对象序列化报错解决
*/
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
3.封装RedisUtil工具类
在实际应用redis中形成的自定义的Redis工具类,主要把Redistemplate 和 StringRedistemplate 依赖注入,然后调用这两个的方法;
其中Redistemplate 用来存java对象,而StringRedistemplate 用于普通的操作;
package com.tianju.fresh.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void saveObjectToRedis(String key,Object json){
redisTemplate.opsForValue().set(key,json);
}
/**
* 从redis里面获取json对象,如果没有,返回null
* @param key
* @return
*/
public Object getJsonFromRedis(String key){
return redisTemplate.opsForValue().get(key);
}
/**
* 删除redis里面的值,键
* @param key 删除的键
* @return
*/
public Boolean deleteKey(String key){
return redisTemplate.delete(key);
}
/**
* 存到Redis里面的 set 中
* @param key 存的键
* @param value 存到set的值
*/
public void saveSetToRedis(String key,String... value){
for (String s:value){
if (s!=null && !s.equals("")){
stringRedisTemplate.opsForSet().add(key, s);
}
}
}
/**
* 判断是否在redis的set中
* @param key
* @param val
* @return
*/
public Boolean isInSet(String key,String val){
return stringRedisTemplate.opsForSet().isMember(key, val);
}
/**
* 获得set中的值
* @param key 键
* @return
*/
public Set<String> getSet(String key){
return stringRedisTemplate.opsForSet().members(key);
}
/**
* 从redis里面的set删除数据
* @param key
* @param val
*/
public void removeFromSet(String key,String val){
stringRedisTemplate.opsForSet().remove(key, val);
}
/**
* 获得存到redis里面的键对应的string类型的值
* @param key 键
* @return
*/
public String getStringValue(String key){
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 保存到redis里面string
* @param key
* @param timeout 过期时间,传的是多少s之后过期
* @param val
*/
public void saveStringValue(String key,String val,Integer... timeout){
if (timeout==null){
stringRedisTemplate.opsForValue().set(key,val);
}else {
stringRedisTemplate.opsForValue().set(key,val,timeout[0], TimeUnit.SECONDS);
}
}
/**
* 看某个键是否存在于Redis中
* @param key 待检验的键
* @return
*/
public Boolean isKeyInRedis(String key){
return stringRedisTemplate.hasKey(key);
}
}
4.工具类的使用
下面以一个缓存预热的代码为例,使用工具类时依赖注入,然后调用工具类里面的方法
package com.tianju.fresh.autho.bloomFilter;
import com.tianju.fresh.entity.Customer;
import com.tianju.fresh.mapper.CustomerMapper;
import com.tianju.fresh.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 将用户名,手机号码,邮箱存到redis里面,
* 作用:
* 1.登陆的时候先到redis里面查询;
* 2.用户注册的时候先从redis里面看用户名是否重复
*/
@Component
@Slf4j
public class LoginKeyPreHot {
private final String LOGIN_KEY = "loginKey";
@Autowired
private CustomerMapper customerMapper;
@Autowired
private RedisUtil redisUtil;
/**
* 每天半夜 2点 跟新布隆过滤器和缓存
*/
@Scheduled(cron = "0 0 2 * * ?")
public void perHot(){
// 1.先清除缓存中的数据
// 2.然后更新一下redis里面的loginKey;
// 3.同时把loginKey放到布隆过滤器中
redisUtil.deleteKey(LOGIN_KEY); // 删除redis里面的key
Set set = new HashSet();
List<Customer> customers = customerMapper.selectList(null);
customers.forEach(c->{
// 更新Redis里面的set
redisUtil.saveSetToRedis(LOGIN_KEY, c.getUsername(),c.getEmail(),c.getContactTel());
// 更新布隆过滤器
BloomFilterUtil.addBloom(c.getUsername(),c.getEmail(),c.getContactTel());
// 记录一下谁进了布隆过滤器中
set.add(c.getUsername());
set.add(c.getContactTel());
set.add(c.getEmail());
});
// TODO:手动在布隆过滤器中放一个Arya, 模拟骗过了布隆过滤器,但没有骗过redis的情况
BloomFilterUtil.addBloom("Arya");
log.debug("缓存预热 + 布隆过滤器初始化成功 >>>" +set);
}
}
在实际应用中遇到的bug
1.问题描述
如果在存redis的时候,用的是实现存java对象的序列化的Redistemplate,则此时就无法用SREM命令删除。
2.问题解决
stringRedisTemplateを使用して保存するだけです
要約する
1. これまでの Redis 関連ブログのまとめ;
2. Java オブジェクトを Redis に保存するための解決策;
3. 実際に使用する際に遭遇した問題とその解決策; 5. Redis に Javaオブジェクトを格納するための解決策