问题背景
在实际项目中,我们需要支持两种 redis 部署方式(集群和主从),但 spring boot 默认只允许一种 redis 连接池配置,且配置受限于 lettuce 包,不够灵活。为了解决这个问题,我深入研究了 spring boot 的源码,自定义了 redis 配置方案,实现了多数据源支持。这一调整让我们可以在同一项目中灵活适配不同的 redis 部署方式,解决了配置的局限性,让系统更具扩展性和灵活性。
源码分析
lettuceconnectionconfiguration 的核心配置
lettuceconnectionconfiguration 位于 org.springframework.boot.autoconfigure.data.redis 包内,使其作用域被限制,无法直接扩展。为支持集群和主从 redis 部署,我们需要通过自定义配置,绕过该限制。
lettuceconnectionfactory 是 redis 连接的核心工厂,依赖于 defaultclientresources,并通过 lettuceclientconfiguration 设置诸如连接池、超时时间等基础参数。配置如下:
class lettuceconnectionconfiguration extends redisconnectionconfiguration {
@bean(destroymethod = "shutdown")
@conditionalonmissingbean(clientresources.class)
defaultclientresources lettuceclientresources(objectprovider<clientresourcesbuildercustomizer> customizers) {
defaultclientresources.builder builder = defaultclientresources.builder();
customizers.orderedstream().foreach((customizer) -> customizer.customize(builder));
return builder.build();
}
@bean
@conditionalonmissingbean(redisconnectionfactory.class)
lettuceconnectionfactory redisconnectionfactory(
objectprovider<lettuceclientconfigurationbuildercustomizer> buildercustomizers,
clientresources clientresources) {
lettuceclientconfiguration clientconfig = getlettuceclientconfiguration(buildercustomizers, clientresources,
getproperties().getlettuce().getpool());
return createlettuceconnectionfactory(clientconfig);
}
}
lettuceclientresources 方法定义了 clientresources,作为单例供所有 redis 连接工厂复用。因此,自定义 lettuceconnectionfactory 时可以直接使用这个共享的 clientresources。
客户端配置与初始化解析
lettuceclientconfiguration 的获取:getlettuceclientconfiguration 方法用以构建 lettuce 客户端配置,应用基本参数并支持连接池:
private lettuceclientconfiguration getlettuceclientconfiguration(
objectprovider<lettuceclientconfigurationbuildercustomizer> buildercustomizers,
clientresources clientresources, pool pool) {
lettuceclientconfigurationbuilder builder = createbuilder(pool);
applyproperties(builder);
if (stringutils.hastext(getproperties().geturl())) {
customizeconfigurationfromurl(builder);
}
builder.clientoptions(createclientoptions());
builder.clientresources(clientresources);
buildercustomizers.orderedstream().foreach((customizer) -> customizer.customize(builder));
return builder.build();
}
创建 lettuceclientconfigurationbuilder:createbuilder 方法生成 lettuceclientconfigurationbuilder,并判断是否启用连接池。若启用,poolbuilderfactory 会创建包含连接池的配置,该连接池通过 genericobjectpoolconfig 构建。
private static final boolean commons_pool2_available = classutils.ispresent("org.apache.commons.pool2.objectpool",
redisconnectionconfiguration.class.getclassloader());
private lettuceclientconfigurationbuilder createbuilder(pool pool) {
if (ispoolenabled(pool)) {
return new poolbuilderfactory().createbuilder(pool);
}
return lettuceclientconfiguration.builder();
}
protected boolean ispoolenabled(pool pool) {
boolean enabled = pool.getenabled();
return (enabled != null) ? enabled : commons_pool2_available;
}
private static class poolbuilderfactory {
lettuceclientconfigurationbuilder createbuilder(pool properties) {
return lettucepoolingclientconfiguration.builder().poolconfig(getpoolconfig(properties));
}
private genericobjectpoolconfig<?> getpoolconfig(pool properties) {
genericobjectpoolconfig<?> config = new genericobjectpoolconfig<>();
config.setmaxtotal(properties.getmaxactive());
config.setmaxidle(properties.getmaxidle());
config.setminidle(properties.getminidle());
if (properties.gettimebetweenevictionruns() != null) {
config.settimebetweenevictionruns(properties.gettimebetweenevictionruns());
}
if (properties.getmaxwait() != null) {
config.setmaxwait(properties.getmaxwait());
}
return config;
}
}
参数应用与超时配置:applyproperties 方法用于配置 redis 的基础属性,如 ssl、超时时间等。
private lettuceclientconfigurationbuilder applyproperties(
lettuceclientconfiguration.lettuceclientconfigurationbuilder builder) {
if (getproperties().isssl()) {
builder.usessl();
}
if (getproperties().gettimeout() != null) {
builder.commandtimeout(getproperties().gettimeout());
}
if (getproperties().getlettuce() != null) {
redisproperties.lettuce lettuce = getproperties().getlettuce();
if (lettuce.getshutdowntimeout() != null && !lettuce.getshutdowntimeout().iszero()) {
builder.shutdowntimeout(getproperties().getlettuce().getshutdowntimeout());
}
}
if (stringutils.hastext(getproperties().getclientname())) {
builder.clientname(getproperties().getclientname());
}
return builder;
}
redis 多模式支持
在创建 lettuceconnectionfactory 时,根据不同配置模式(哨兵、集群或单节点)构建连接工厂:
private lettuceconnectionfactory createlettuceconnectionfactory(lettuceclientconfiguration clientconfiguration) {
if (getsentinelconfig() != null) {
return new lettuceconnectionfactory(getsentinelconfig(), clientconfiguration);
}
if (getclusterconfiguration() != null) {
return new lettuceconnectionfactory(getclusterconfiguration(), clientconfiguration);
}
return new lettuceconnectionfactory(getstandaloneconfig(), clientconfiguration);
}
- 哨兵模式:
getsentinelconfig()返回哨兵配置实例,实现高可用 redis。 - 集群模式:若启用集群,
getclusterconfiguration()返回集群配置以支持分布式 redis。 - 单节点模式:默认单节点配置,返回
standaloneconfig。
getclusterconfiguration 的方法实现:
protected final redisclusterconfiguration getclusterconfiguration() {
if (this.clusterconfiguration != null) {
return this.clusterconfiguration;
}
if (this.properties.getcluster() == null) {
return null;
}
redisproperties.cluster clusterproperties = this.properties.getcluster();
redisclusterconfiguration config = new redisclusterconfiguration(clusterproperties.getnodes());
if (clusterproperties.getmaxredirects() != null) {
config.setmaxredirects(clusterproperties.getmaxredirects());
}
config.setusername(this.properties.getusername());
if (this.properties.getpassword() != null) {
config.setpassword(redispassword.of(this.properties.getpassword()));
}
return config;
}
- 集群节点与重定向:配置集群节点信息及最大重定向次数。
- 用户名与密码:集群连接的身份验证配置。
redis 多数据源配置思路
通过以上的源码分析,我梳理出了 lettuceconnectionfactory 构建的流程:
1.lettuceclientconfiguration 初始化:
- 连接池初始化
- redis 基础属性配置
- 设置
clientresource
2.redisconfiguration 初始化,官方支持以下配置:
redisclusterconfigurationredissentinelconfigurationredisstaticmasterreplicaconfigurationredisstandaloneconfiguration
redis 多数据源配置实战
复用 lettuceclientconfiguration 配置
/**
* 这里与源码有个不同的是返回了builder,因为后续实现的主从模式的lettuce客户端仍有自定义的配置,返回了builder则相对灵活一点。
*/
private lettuceclientconfiguration.lettuceclientconfigurationbuilder getlettuceclientconfiguration(clientresources clientresources, redisproperties.pool pool) {
lettuceclientconfiguration.lettuceclientconfigurationbuilder builder = createbuilder(pool);
applyproperties(builder);
builder.clientoptions(createclientoptions());
builder.clientresources(clientresources);
return builder;
}
/**
* 创建lettuce客户端配置构建器
*/
private lettuceclientconfiguration.lettuceclientconfigurationbuilder createbuilder(redisproperties.pool pool) {
if (ispoolenabled(pool)) {
return lettucepoolingclientconfiguration.builder().poolconfig(getpoolconfig(pool));
}
return lettuceclientconfiguration.builder();
}
/**
* 判断redis连接池是否启用
*/
private boolean ispoolenabled(redisproperties.pool pool) {
boolean enabled = pool.getenabled();
return (enabled != null) ? enabled : commons_pool2_available;
}
/**
* 根据redis属性配置创建并返回一个通用对象池配置
*/
private genericobjectpoolconfig<?> getpoolconfig(redisproperties.pool properties) {
genericobjectpoolconfig<?> config = new genericobjectpoolconfig<>();
config.setmaxtotal(properties.getmaxactive());
config.setmaxidle(properties.getmaxidle());
config.setminidle(properties.getminidle());
if (properties.gettimebetweenevictionruns() != null) {
config.settimebetweenevictionruns(properties.gettimebetweenevictionruns());
}
if (properties.getmaxwait() != null) {
config.setmaxwait(properties.getmaxwait());
}
return config;
}
/**
* 根据redis属性配置构建lettuce客户端配置
*
* @param builder lettuce客户端配置的构建器
* @return 返回配置完毕的lettuce客户端配置构建器
*/
private lettuceclientconfiguration.lettuceclientconfigurationbuilder applyproperties(
lettuceclientconfiguration.lettuceclientconfigurationbuilder builder) {
if (redisproperties.isssl()) {
builder.usessl();
}
if (redisproperties.gettimeout() != null) {
builder.commandtimeout(redisproperties.gettimeout());
}
if (redisproperties.getlettuce() != null) {
redisproperties.lettuce lettuce = redisproperties.getlettuce();
if (lettuce.getshutdowntimeout() != null && !lettuce.getshutdowntimeout().iszero()) {
builder.shutdowntimeout(redisproperties.getlettuce().getshutdowntimeout());
}
}
if (stringutils.hastext(redisproperties.getclientname())) {
builder.clientname(redisproperties.getclientname());
}
return builder;
}
/**
* 创建客户端配置选项
*/
private clientoptions createclientoptions() {
clientoptions.builder builder = initializeclientoptionsbuilder();
duration connecttimeout = redisproperties.getconnecttimeout();
if (connecttimeout != null) {
builder.socketoptions(socketoptions.builder().connecttimeout(connecttimeout).build());
}
return builder.timeoutoptions(timeoutoptions.enabled()).build();
}
/**
* 初始化clientoptions构建器
*/
private clientoptions.builder initializeclientoptionsbuilder() {
if (redisproperties.getcluster() != null) {
clusterclientoptions.builder builder = clusterclientoptions.builder();
redisproperties.lettuce.cluster.refresh refreshproperties = redisproperties.getlettuce().getcluster().getrefresh();
clustertopologyrefreshoptions.builder refreshbuilder = clustertopologyrefreshoptions.builder()
.dynamicrefreshsources(refreshproperties.isdynamicrefreshsources());
if (refreshproperties.getperiod() != null) {
refreshbuilder.enableperiodicrefresh(refreshproperties.getperiod());
}
if (refreshproperties.isadaptive()) {
refreshbuilder.enablealladaptiverefreshtriggers();
}
return builder.topologyrefreshoptions(refreshbuilder.build());
}
return clientoptions.builder();
}
复用 redis 集群初始化
/**
* 获取redis集群配置
*/
private redisclusterconfiguration getclusterconfiguration() {
if (redisproperties.getcluster() == null) {
return null;
}
redisproperties.cluster clusterproperties = redisproperties.getcluster();
redisclusterconfiguration config = new redisclusterconfiguration(clusterproperties.getnodes());
if (clusterproperties.getmaxredirects() != null) {
config.setmaxredirects(clusterproperties.getmaxredirects());
}
config.setusername(redisproperties.getusername());
if (redisproperties.getpassword() != null) {
config.setpassword(redispassword.of(redisproperties.getpassword()));
}
return config;
}
自定义 redis 主从配置
@getter
@setter
@configurationproperties(prefix = "spring.redis")
public class redispropertiesextend {
private masterreplica masterreplica;
@getter
@setter public static class masterreplica {
private string masternodes;
private list<string> replicanodes;
}
}
/**
* 获取主从配置
*/
private redisstaticmasterreplicaconfiguration getstaticmasterreplicaconfiguration() {
if (redispropertiesextend.getmasterreplica() == null) {
return null;
}
redispropertiesextend.masterreplica masterreplica = redispropertiesextend.getmasterreplica();
list<string> masternodes = charsequenceutil.split(masterreplica.getmasternodes(), strpool.colon);
redisstaticmasterreplicaconfiguration config = new redisstaticmasterreplicaconfiguration(
masternodes.get(0), integer.parseint(masternodes.get(1)));
for (string replicanode : masterreplica.getreplicanodes()) {
list<string> replicanodes = charsequenceutil.split(replicanode, strpool.colon);
config.addnode(replicanodes.get(0), integer.parseint(replicanodes.get(1)));
}
config.setusername(redisproperties.getusername());
if (redisproperties.getpassword() != null) {
config.setpassword(redispassword.of(redisproperties.getpassword()));
}
return config;
}
注册 lettuceconnectionfactory
@primary
@bean(name = "redisclusterconnectionfactory")
@conditionalonmissingbean(name = "redisclusterconnectionfactory")
public lettuceconnectionfactory redisclusterconnectionfactory(clientresources clientresources) {
lettuceclientconfiguration clientconfig = getlettuceclientconfiguration(clientresources,
redisproperties.getlettuce().getpool())
.build();
return new lettuceconnectionfactory(getclusterconfiguration(), clientconfig);
}
@bean(name = "redismasterreplicaconnectionfactory")
@conditionalonmissingbean(name = "redismasterreplicaconnectionfactory")
public lettuceconnectionfactory redismasterreplicaconnectionfactory(clientresources clientresources) {
lettuceclientconfiguration clientconfig = getlettuceclientconfiguration(clientresources,
redisproperties.getlettuce().getpool())
.readfrom(readfrom.replica_preferred)
.build();
return new lettuceconnectionfactory(getstaticmasterreplicaconfiguration(), clientconfig);
}
应用
@bean
@conditionalonmissingbean(name = "redistemplate")
public redistemplate<object, object> redistemplate(@qualifier("redisclusterconnectionfactory") redisconnectionfactory redisconnectionfactory) {
redistemplate<object, object> template = new redistemplate<>();
template.setconnectionfactory(redisconnectionfactory);
return template;
}
@bean
@conditionalonmissingbean(name = "masterreplicaredistemplate")
public redistemplate<object, object> masterreplicaredistemplate(@qualifier("redismasterreplicaconnectionfactory") redisconnectionfactory redisconnectionfactory) {
redistemplate<object, object> template = new redistemplate<>();
template.setconnectionfactory(redisconnectionfactory);
return template;
}
最后
深入理解 spring boot redis 自动配置源码是实现灵活多数据源支持的关键。通过源码分析,我们能够在保持框架兼容性的同时,精准定制和扩展功能,从而满足复杂的业务需求。
以上就是一文详解springboot redis多数据源配置的详细内容,更多关于springboot redis多数据源配置的资料请关注代码网其它相关文章!
发表评论