BloomFilterPenetrateProperties
@ConfigurationProperties(prefix = BloomFilterPenetrateProperties.PREFIX)
: 指定这个类的属性会从配置文件中读取,前缀为 framework.cache.redis.bloom-filter.default
。
java
/**
* 每个元素的预期插入量
*/
private Long expectedInsertions = 64L;
/**
* 预期错误概率
*/
private Double falseProbability = 0.03D;
这两个参数都会直接影响布隆过滤器创建的数组长度。错误概率还会影响使用的哈希算法的个数。
概率设置过大会显著增加误判几率,过小会显著增加判断时间,性能较差。需要合理的去设置。
RedisKeySerializer
@RequiredArgsConstructor接收所有final字段的参数并按照
java
private final String keyPrefix;
private final String charsetName;
private Charset charset;
为顺序将参数值传入。
RedisKeySerializer
类是一个自定义的 Redis 键序列化器,实现了 RedisSerializer<String>
接口,用于处理 Redis 中键的序列化和反序列化。
InitializingBean
: 这个接口要求实现afterPropertiesSet()
方法,在 Spring 容器初始化 Bean 后被调用。RedisSerializer<String>
: 这个接口定义了序列化和反序列化的方法,用于处理 Redis 中的键。
private final String keyPrefix;
- Redis 键的前缀,用于在序列化时为每个键添加特定的前缀,以避免键冲突。
private final String charsetName;
- 字符集名称,用于在反序列化时将字节数组转换为字符串。
private Charset charset;
Charset
对象,用于在反序列化时指定字符集。
serialize(String key)
- 功能: 将给定的字符串键序列化为字节数组。
- 实现 : 在序列化过程中,将
keyPrefix
和传入的key
连接起来,然后调用getBytes()
方法将其转换为字节数组。
deserialize(byte[] bytes)
- 功能: 将字节数组反序列化为字符串。
- 实现: 使用指定的字符集将字节数组转换为字符串。、
afterPropertiesSet()
- 功能: 在 Bean 初始化后调用,用于设置字符集。
- 实现 : 根据
charsetName
初始化charset
字段。
RedisDistributedProperties
RedisDistributedProperties
类是一个用于配置 Redis 相关属性的 Java 类,使用了 Spring Boot 的 @ConfigurationProperties
注解。这个类的主要目的是将配置文件中的 Redis 相关属性映射到 Java 对象中,以便在应用程序中使用。
public static final String PREFIX
: 定义了配置属性的前缀,便于在其他地方引用。private String prefix
: Redis 键的前缀,默认为空字符串。这个前缀可以用于区分不同模块或应用的数据,避免键冲突。private String prefixCharset
: Key 前缀的字符集,默认为"UTF-8"
。这个属性指定了在序列化和反序列化过程中使用的字符集。private Long valueTimeout
: 默认的超时时间,默认为30000L
(30秒)。这个属性用于设置 Redis 中存储数据的过期时间。private TimeUnit valueTimeUnit
: 时间单位,默认为TimeUnit.MILLISECONDS
。这个属性指定了valueTimeout
的单位,可以是秒、毫秒等。
在 application.yml
或 application.properties
文件中,可以通过以下方式配置 Redis 的属性:
XML
framework:
cache:
redis:
prefix: "myapp:"
prefixCharset: "UTF-8"
valueTimeout: 60000 # 60秒
valueTimeUnit: SECONDS
CacheAutoConfiguration
@AllArgsConstructor
@AllArgsConstructor
注解时,Lombok 会自动生成一个构造函数,该构造函数包含类中所有的字段作为参数。
在 Spring 应用中,使用 @AllArgsConstructor
可以方便地创建带有依赖的 Bean。例如,在配置类中注入多个属性时,可以使用构造函数注入,确保所有依赖项在创建时都被提供。
@EnableConfigurationProperties
- 启用对
RedisDistributedProperties
和BloomFilterPenetrateProperties
类的配置属性支持。这意味着这两个类中定义的属性可以通过配置文件进行设置。
创建 Redis Key 序列化器,可自定义 Key Prefix
java
@Bean
public RedisKeySerializer redisKeySerializer() {
String prefix = redisDistributedProperties.getPrefix();
String prefixCharset = redisDistributedProperties.getPrefixCharset();
return new RedisKeySerializer(prefix, prefixCharset);
}
创建默认布隆过滤器的 Bean
@ConditionalOnProperty
注解 : 这个注解用于条件性地创建 Bean。它检查配置文件中是否存在特定的属性,并根据该属性的值决定是否创建该 Bean。在这里,它检查framework.cache.redis.bloom-filter.default.enabled
是否设置为true
。只有在该属性为true
时,cachePenetrationBloomFilter
方法才会被调用。redissonClient.getBloomFilter(bloomFilterPenetrateProperties.getName())
: 使用 Redisson 客户端获取布隆过滤器的实例。- 布隆过滤器的名称、预期插入量和误判概率是通过
BloomFilterPenetrateProperties
获取的。
这个默认的布隆过滤器其实在项目中并没有实际的使用,只有用户管理模块使用了一个重新定义的
userRegisterCachePenetrationBloomFilter,专门用于用户注册相关的逻辑。
stringRedisTemplateProxy
java
@Bean
// 静态代理模式: Redis 客户端代理类增强
public StringRedisTemplateProxy stringRedisTemplateProxy(RedisKeySerializer redisKeySerializer,
StringRedisTemplate stringRedisTemplate,
RedissonClient redissonClient) {
stringRedisTemplate.setKeySerializer(redisKeySerializer);
return new StringRedisTemplateProxy(stringRedisTemplate, redisDistributedProperties, redissonClient);
}
java
stringRedisTemplate.setKeySerializer(redisKeySerializer);
- 设置键序列化器 : 这行代码将
redisKeySerializer
设置为stringRedisTemplate
的键序列化器。这意味着在进行 Redis 操作时,所有的键都会使用这个序列化器进行序列化和反序列化。
java
return new StringRedisTemplateProxy(stringRedisTemplate, redisDistributedProperties, redissonClient);
返回对象交给spring容器管理
CacheGetFilter<T>
接口
java
boolean filter(T param);
解决布隆过滤器的不能删除的问题,但是目前没有实现具体逻辑。这是我的实现方法。
- 当用户注销时,将用户名添加到第一层布隆过滤器。
- 当用户注册时,先检查第二层布隆过滤器,如果用户名不在第二层过滤器中,则允许注册。
- 即使一个已注销的用户名被重新使用,也允许注册。
- 每天从数据库中检查已注销用户,重构第一层布隆过滤器。
CacheGetIfAbsent<T>接口
同样没有实现类,用于缓存未命中时,允许用户根据具体需求实现自定义的逻辑。
CacheLoader<T>接口
主要用于定义加载缓存的逻辑。它的设计目的是提供一种机制来从某个数据源(如数据库、外部服务等)加载数据并将其放入缓存中。
java
public class UserCacheLoader implements CacheLoader<User> {
private final UserService userService; // 假设这是一个服务类,用于查询用户数据
public UserCacheLoader(UserService userService) {
this.userService = userService;
}
@Override
public User load() {
// 从数据库加载用户数据
return userService.findUserByUsername("exampleUser"); // 示例用户名
}
}
CacheUtil
buildKey
方法
java
public static String buildKey(String... keys) {
Stream.of(keys).forEach(each -> Optional.ofNullable(Strings.emptyToNull(each)).orElseThrow(() -> new RuntimeException("构建缓存 key 不允许为空")));
return Joiner.on(SPLICING_OPERATOR).join(keys);
}
1. Stream.of(keys)
- 功能 :将传入的可变参数
keys
转换为一个流(Stream
)。这使得可以对这些键进行流式操作。 - 用途:流式操作提供了一种更简洁和可读的方式来处理集合或数组中的元素。
2. forEach(each -> ...)
- 功能:对流中的每个元素(每个键)执行指定的操作。
- 参数 :
each
是流中当前处理的元素(即当前的键)。
3. Optional.ofNullable(Strings.emptyToNull(each))
Strings.emptyToNull(each)
:- 这个方法来自 Guava 库,它的作用是将空字符串转换为
null
。如果each
是空字符串,则返回null
;如果不是空字符串,则返回原始字符串。
- 这个方法来自 Guava 库,它的作用是将空字符串转换为
Optional.ofNullable(...)
:- 将可能为
null
的值包装在Optional
对象中。如果each
为null
,则创建一个空的Optional
;如果each
不为null
,则创建一个包含该值的Optional
。
- 将可能为
4. orElseThrow(...)
- 功能 :如果
Optional
是空的(即each
为null
或空字符串),则抛出异常。 - 逻辑 :在这里,使用
orElseThrow
方法来指定一个异常的生成逻辑。如果Optional
中没有值,则抛出RuntimeException
,并提供错误消息"构建缓存 key 不允许为空"
5.Joiner.on(SPLICING_OPERATOR).join(keys)
使用 Joiner.on(SPLICING_OPERATOR).join(keys)
将所有键连接成一个字符串,使用 _
作为分隔符。
java
public static boolean isNullOrBlank(Object cacheVal) {
return cacheVal == null || (cacheVal instanceof String && Strings.isNullOrEmpty((String) cacheVal));
}
- 功能 :判断给定的值是否为
null
或空字符串。 - 参数 :接受一个
Object
类型的参数cacheVal
。 - 逻辑 :
- 检查
cacheVal
是否为null
,或者如果是字符串,检查其是否为空。
- 检查
- 返回值 :返回一个布尔值,
true
表示cacheVal
为null
或空字符串,false
表示不是。
CacheUtil
类提供了两个实用的静态方法,buildKey
用于构建缓存标识,isNullOrBlank
用于判断缓存值的有效性。
FastJson2Util
FastJson2Util
是一个工具类,主要用于构建 Java 泛型类型(Type
)的实例,以便在使用 FastJSON 进行序列化和反序列化时能够正确处理泛型。
Cache
接口
java
Object getInstance();
获取底层缓存组件的实例。
DistributedCache
接口
DistributedCache
接口扩展了 Cache
接口,提供了一组用于分布式缓存的操作方法。这个接口主要用于处理缓存的获取、存储和管理,特别是在高并发和分布式环境中。
StringRedisTemplateProxy
StringRedisTemplateProxy
类是一个实现了 DistributedCache
接口的具体类,主要用于封装 Redis 操作,提供了一系列用于缓存的功能。这个类结合了 Redisson 和 Spring Data Redis 的功能,允许开发者在高并发环境下安全地管理缓存。
java
private <T> T loadAndSet(String key, CacheLoader<T> cacheLoader, long timeout, TimeUnit timeUnit, boolean safeFlag, RBloomFilter<String> bloomFilter) {
T result = cacheLoader.load();
if (CacheUtil.isNullOrBlank(result)) {
return result;
}
if (safeFlag) {
safePut(key, result, timeout, timeUnit, bloomFilter);
} else {
put(key, result, timeout, timeUnit);
}
return result;
}
从数据源加载数据并将其存入布隆过滤器。
java
@Override
public Boolean putIfAllAbsent(@NotNull Collection<String> keys) {
DefaultRedisScript<Boolean> actual = Singleton.get(LUA_PUT_IF_ALL_ABSENT_SCRIPT_PATH, () -> {
DefaultRedisScript redisScript = new DefaultRedisScript();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(LUA_PUT_IF_ALL_ABSENT_SCRIPT_PATH)));
redisScript.setResultType(Boolean.class);
return redisScript;
});
Boolean result = stringRedisTemplate.execute(actual, Lists.newArrayList(keys), redisProperties.getValueTimeout().toString());
return result != null && result;
}
- Lua 脚本:使用 Lua 脚本来实现原子操作。Lua 脚本在 Redis 中执行,可以确保在执行过程中不被其他操作打断。
Singleton.get(...)
:通过单例模式获取 Lua 脚本,确保脚本只被加载一次,提高性能。setScriptSource
:设置 Lua 脚本的来源,这里通过ClassPathResource
加载脚本文件。setResultType
:指定脚本执行后返回的结果类型,这里为Boolean.class
。- 执行脚本 :通过
stringRedisTemplate.execute(...)
方法执行 Lua 脚本。- 参数 :
actual
:要执行的 Lua 脚本。Lists.newArrayList(keys)
:将传入的键集合转换为列表,以便传递给脚本。redisProperties.getValueTimeout().toString()
:将超时时间作为参数传递给脚本(具体使用取决于脚本的实现)。
- 参数 :
- 返回值 :
result
是脚本执行的结果,表示所有键是否成功放入缓存。
Lua
for i, v in ipairs(KEYS) do
if (redis.call('exists', v) == 1) then
return nil;
end
end
- 遍历
KEYS
:使用ipairs
遍历KEYS
表中的每个键。 - 检查键是否存在 :对于每个键
v
,使用redis.call('exists', v)
检查它是否存在于 Redis 中。- 如果
exists
命令返回1
,表示键存在,则立即返回nil
,表示操作失败。
- 如果
Lua
for i, v in ipairs(KEYS) do
redis.call('set', v, 'default');
redis.call('pexpire', v, ARGV[1]);
end
- 遍历
KEYS
:再次遍历KEYS
表中的每个键。 - 设置键值对 :对于每个键
v
,使用redis.call('set', v, 'default')
将其设置为默认值(这里使用'default'
)。 - 设置过期时间 :使用
redis.call('pexpire', v, ARGV)
为键设置毫秒级过期时间。这里的ARGV
是一个参数,表示过期时间。