在开发一个基于 RuoYi-Vue-Plus 构建的项目时,发现 Lock4j 的缓存锁有时候不起作用。
经测试发现,只有同时通过不同的 Application 发起同一个 key 的锁时,才会出现这个问题。虽然 key 是相同的,但是两边都可以同时获取到锁。经过排查发现虽然代码中 key 是相同的,但是最终请求到 redis 中的 key 并不相同。一个是带租户的,一个是不带租户的,但是两个应用的租户都是启用了的。
项目中的 Lock4j 使用的是 lock4j-redisson-spring-boot-starter ,是基于 RedissonClient
实现的。在启用多租户时,TenantConfig
中会自动注册一个 RedissonAutoConfigurationCustomizer
类型的 Bean,通过设置一个自定义的 nameMapper
来实现缓存数据的租户隔离。
在注册 RedissonClient
Bean 时,会调用已注册的 RedissonAutoConfigurationCustomizer
Bean 的 customize
方法,对 Redisson 的配置进行设置。
java
public class RedissonAutoConfiguration {
@Autowired(
required = false
)
private List<RedissonAutoConfigurationCustomizer> redissonAutoConfigurationCustomizers;
@Bean(
destroyMethod = "shutdown"
)
@ConditionalOnMissingBean({RedissonClient.class})
public RedissonClient redisson() throws IOException {
// ...
if (this.redissonAutoConfigurationCustomizers != null) {
for(RedissonAutoConfigurationCustomizer customizer : this.redissonAutoConfigurationCustomizers) {
customizer.customize(config);
}
}
return Redisson.create(config);
}
}
问题就出现在这里。在 RedisConfig
和 TenantConfig
中各有一个 RedissonAutoConfigurationCustomizer
类型的 Bean 定义。上面的 List<RedissonAutoConfigurationCustomizer>
中会自动注入这两个 Bean。
两个 Bean 都可以正常注入,关键在于注入的顺序。如果没有额外的设置,默认按照 Bean 的注册顺序将其添加到 List
,而默认情况下 Bean 的注册顺序有一定的不确定性 。如果 TenantConfig
中定义的 RedissonAutoConfigurationCustomizer
方法先被执行,则会导致其定义的租户专用的配置被覆盖掉了。
解决方案就是通过 @Order
注解指定 Bean 的注册顺序,值越小的优先级越高,注册顺序越靠前。
RedisConfig
java
@Bean
@Order(1)
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
return config -> {
ObjectMapper om = objectMapper.copy();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
// 组合序列化 key 使用 String 内容使用通用 json 格式
CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads())
.setCodec(codec);
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
config.useSingleServer()
// 设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setTimeout(singleServerConfig.getTimeout())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
}
// 集群配置方式 参考下方注释
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) {
config.useClusterServers()
// 设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setTimeout(clusterServersConfig.getTimeout())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
}
log.info("初始化 redis 配置");
};
}
TenantConfig
java
@Bean
@Order(2)
public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
return config -> {
TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
// 设置多租户 redis key前缀
singleServerConfig.setNameMapper(nameMapper);
ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig);
}
ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
// 集群配置方式 参考下方注释
if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 设置多租户 redis key前缀
clusterServersConfig.setNameMapper(nameMapper);
ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig);
}
};
}