Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ If changing the configuration parameters directly in the `context.xml` file is n
* `TOMCAT_REDIS_SESSION_PASSWORD`: When the username is set, this allows you to set the required password for the connection to the Redis Server.
* `TOMCAT_REDIS_SESSION_DATABASE`: The default Redis database selected by the Jedis Client.
* `TOMCAT_REDIS_SESSION_TIMEOUT`: The default timeout for the Jedis Client when trying to connect to the Redis Server.
* `TOMCAT_REDIS_SESSION_SENTINEL_MASTER`: Enables [Redis Sentinel](https://redis.io/docs/management/sentinel/) high-availability mode. Set this to the name of the monitored master and the plugin will discover the current master through the sentinels and fail over automatically. When set, you must also provide `TOMCAT_REDIS_SESSION_SENTINELS`; otherwise the plugin falls back to a direct connection to `TOMCAT_REDIS_SESSION_HOST`/`PORT`. The configured username, password, database, SSL, and timeout are applied to the master connection.
* `TOMCAT_REDIS_SESSION_SENTINELS`: A comma-separated list of sentinel nodes (`host:port,host:port,...`) used together with `TOMCAT_REDIS_SESSION_SENTINEL_MASTER`.
* `TOMCAT_REDIS_USER_SESSION_TIMEOUT`: The default timeout, in seconds, for the user session in the browser. By default, it's set to Tomcat's Timeout, which is usually 1800 seconds -- 30 minutes.
* `TOMCAT_REDIS_SESSION_PERSISTENT_POLICIES`: The available persistence policies for determining when the user session must be persisted to Redis:
* `DEFAULT`: Selected by default. It tells the manager to persist the session **ONLY** if its current attributes differ compared to the ones from the session in Redis. This is the default behavior with the least overhead.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.dotcms</groupId>
<artifactId>tomcat-redis-session-manager</artifactId>
<version>1.8</version>
<version>2.0</version>
<packaging>jar</packaging>

<name>tomcat-redis-session-manager</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import redis.clients.jedis.ConnectionPoolConfig;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.JedisSentineled;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.UnifiedJedis;

Expand Down Expand Up @@ -1140,9 +1143,9 @@ private void initializeConfigParams() {
private void initializeRedisConnection() throws LifecycleException {
log.info("-> Initializing Redis connection...");
try {
jedisPool = null == this.username || this.username.isEmpty()
? new JedisPooled(this.connectionPoolConfig, getHost(), getPort(), getTimeout(), getPassword(), getSsl())
: new JedisPooled(this.connectionPoolConfig, getHost(), getPort(), getTimeout(), getUsername(), getPassword(), getSsl());
jedisPool = this.isSentinelConfigured()
? this.buildSentinelConnection()
: this.buildStandaloneConnection();
// Immediately check that the connection via Jedis can indeed be established
jedisPool.ping();
log.info("\n\n" +
Expand All @@ -1153,6 +1156,65 @@ private void initializeRedisConnection() throws LifecycleException {
}
}

/**
* Determines whether this Manager should connect through Redis Sentinel for high availability. Sentinel mode
* requires both a master name ({@code TOMCAT_REDIS_SESSION_SENTINEL_MASTER}) and at least one sentinel node
* ({@code TOMCAT_REDIS_SESSION_SENTINELS}); otherwise a direct (standalone) connection is used.
*
* @return {@code true} if Sentinel configuration is present and usable.
*/
private boolean isSentinelConfigured() {
return null != this.sentinelMaster && !this.sentinelMaster.isEmpty()
Comment thread
wezell marked this conversation as resolved.
&& null != this.sentinelSet
&& this.sentinelSet.stream().anyMatch(s -> null != s && !s.trim().isEmpty());
}

/**
* Builds a direct (standalone) pooled connection to a single Redis host/port.
*
* @return A {@link JedisPooled} client.
*/
private UnifiedJedis buildStandaloneConnection() {
return null == this.username || this.username.isEmpty()
? new JedisPooled(this.connectionPoolConfig, getHost(), getPort(), getTimeout(), getPassword(), getSsl())
: new JedisPooled(this.connectionPoolConfig, getHost(), getPort(), getTimeout(), getUsername(), getPassword(), getSsl());
}

/**
* Builds a Sentinel-backed connection that discovers the current master from the configured sentinel nodes and
* fails over automatically when the master changes. The master connection inherits the configured username,
* password, database, SSL, and timeout. The sentinel nodes themselves are queried with only timeout/SSL, since
* Sentinels typically have their own (or no) authentication and do not expose application databases.
*
* @return A {@link JedisSentineled} client.
*/
private UnifiedJedis buildSentinelConnection() {
final Set<HostAndPort> sentinels = new HashSet<>();
for (final String sentinel : this.sentinelSet) {
if (null != sentinel && !sentinel.trim().isEmpty()) {
sentinels.add(HostAndPort.from(sentinel.trim()));
}
}
final DefaultJedisClientConfig.Builder masterConfig = DefaultJedisClientConfig.builder()
.timeoutMillis(getTimeout())
.database(getDatabase())
.ssl(getSsl());
if (null != this.username && !this.username.isEmpty()) {
masterConfig.user(this.username);
}
if (null != this.password && !this.password.isEmpty()) {
masterConfig.password(this.password);
}
final DefaultJedisClientConfig sentinelConfig = DefaultJedisClientConfig.builder()
.timeoutMillis(getTimeout())
.ssl(getSsl())
.build();
log.info(String.format("-> Connecting through Redis Sentinel. Master: '%s', Sentinels: %s",
this.sentinelMaster, sentinels));
return new JedisSentineled(this.sentinelMaster, masterConfig.build(), this.connectionPoolConfig,
sentinels, sentinelConfig);
}

/**
* Initializes the serializer that will be used to serialize/deserialize the Session and its attributes to/from
* Redis.
Expand Down
Loading