Adds a new configuration setting to connect to Redis via a Unix socket instead of over TCP. Disabled by default.tags/v1.85.0rc1
@@ -0,0 +1 @@ | |||||
Add Unix socket support for Redis connections. Contributed by Jason Little. |
@@ -3979,6 +3979,8 @@ This setting has the following sub-options: | |||||
* `enabled`: whether to use Redis support. Defaults to false. | * `enabled`: whether to use Redis support. Defaults to false. | ||||
* `host` and `port`: Optional host and port to use to connect to redis. Defaults to | * `host` and `port`: Optional host and port to use to connect to redis. Defaults to | ||||
localhost and 6379 | localhost and 6379 | ||||
* `path`: The full path to a local Unix socket file. **If this is used, `host` and | |||||
`port` are ignored.** Defaults to `/tmp/redis.sock' | |||||
* `password`: Optional password if configured on the Redis instance. | * `password`: Optional password if configured on the Redis instance. | ||||
* `dbid`: Optional redis dbid if needs to connect to specific redis logical db. | * `dbid`: Optional redis dbid if needs to connect to specific redis logical db. | ||||
* `use_tls`: Whether to use tls connection. Defaults to false. | * `use_tls`: Whether to use tls connection. Defaults to false. | ||||
@@ -3991,6 +3993,8 @@ This setting has the following sub-options: | |||||
_Changed in Synapse 1.84.0: Added use\_tls, certificate\_file, private\_key\_file, ca\_file and ca\_path attributes_ | _Changed in Synapse 1.84.0: Added use\_tls, certificate\_file, private\_key\_file, ca\_file and ca\_path attributes_ | ||||
_Changed in Synapse 1.85.0: Added path option to use a local Unix socket_ | |||||
Example configuration: | Example configuration: | ||||
```yaml | ```yaml | ||||
redis: | redis: | ||||
@@ -61,6 +61,9 @@ def lazyConnection( | |||||
# most methods to it via ConnectionHandler.__getattr__. | # most methods to it via ConnectionHandler.__getattr__. | ||||
class ConnectionHandler(RedisProtocol): | class ConnectionHandler(RedisProtocol): | ||||
def disconnect(self) -> "Deferred[None]": ... | def disconnect(self) -> "Deferred[None]": ... | ||||
def __repr__(self) -> str: ... | |||||
class UnixConnectionHandler(ConnectionHandler): ... | |||||
class RedisFactory(protocol.ReconnectingClientFactory): | class RedisFactory(protocol.ReconnectingClientFactory): | ||||
continueTrying: bool | continueTrying: bool | ||||
@@ -33,6 +33,7 @@ class RedisConfig(Config): | |||||
self.redis_host = redis_config.get("host", "localhost") | self.redis_host = redis_config.get("host", "localhost") | ||||
self.redis_port = redis_config.get("port", 6379) | self.redis_port = redis_config.get("port", 6379) | ||||
self.redis_path = redis_config.get("path", None) | |||||
self.redis_dbid = redis_config.get("dbid", None) | self.redis_dbid = redis_config.get("dbid", None) | ||||
self.redis_password = redis_config.get("password") | self.redis_password = redis_config.get("password") | ||||
@@ -352,7 +352,15 @@ class ReplicationCommandHandler: | |||||
reactor = hs.get_reactor() | reactor = hs.get_reactor() | ||||
redis_config = hs.config.redis | redis_config = hs.config.redis | ||||
if hs.config.redis.redis_use_tls: | |||||
if redis_config.redis_path is not None: | |||||
reactor.connectUNIX( | |||||
redis_config.redis_path, | |||||
self._factory, | |||||
timeout=30, | |||||
checkPID=False, | |||||
) | |||||
elif hs.config.redis.redis_use_tls: | |||||
ssl_context_factory = ClientContextFactory(hs.config.redis) | ssl_context_factory = ClientContextFactory(hs.config.redis) | ||||
reactor.connectSSL( | reactor.connectSSL( | ||||
redis_config.redis_host, | redis_config.redis_host, | ||||
@@ -17,7 +17,12 @@ from inspect import isawaitable | |||||
from typing import TYPE_CHECKING, Any, Generic, List, Optional, Type, TypeVar, cast | from typing import TYPE_CHECKING, Any, Generic, List, Optional, Type, TypeVar, cast | ||||
import attr | import attr | ||||
import txredisapi | |||||
from txredisapi import ( | |||||
ConnectionHandler, | |||||
RedisFactory, | |||||
SubscriberProtocol, | |||||
UnixConnectionHandler, | |||||
) | |||||
from zope.interface import implementer | from zope.interface import implementer | ||||
from twisted.internet.address import IPv4Address, IPv6Address | from twisted.internet.address import IPv4Address, IPv6Address | ||||
@@ -68,7 +73,7 @@ class ConstantProperty(Generic[T, V]): | |||||
@implementer(IReplicationConnection) | @implementer(IReplicationConnection) | ||||
class RedisSubscriber(txredisapi.SubscriberProtocol): | |||||
class RedisSubscriber(SubscriberProtocol): | |||||
"""Connection to redis subscribed to replication stream. | """Connection to redis subscribed to replication stream. | ||||
This class fulfils two functions: | This class fulfils two functions: | ||||
@@ -95,7 +100,7 @@ class RedisSubscriber(txredisapi.SubscriberProtocol): | |||||
synapse_handler: "ReplicationCommandHandler" | synapse_handler: "ReplicationCommandHandler" | ||||
synapse_stream_prefix: str | synapse_stream_prefix: str | ||||
synapse_channel_names: List[str] | synapse_channel_names: List[str] | ||||
synapse_outbound_redis_connection: txredisapi.ConnectionHandler | |||||
synapse_outbound_redis_connection: ConnectionHandler | |||||
def __init__(self, *args: Any, **kwargs: Any): | def __init__(self, *args: Any, **kwargs: Any): | ||||
super().__init__(*args, **kwargs) | super().__init__(*args, **kwargs) | ||||
@@ -229,7 +234,7 @@ class RedisSubscriber(txredisapi.SubscriberProtocol): | |||||
) | ) | ||||
class SynapseRedisFactory(txredisapi.RedisFactory): | |||||
class SynapseRedisFactory(RedisFactory): | |||||
"""A subclass of RedisFactory that periodically sends pings to ensure that | """A subclass of RedisFactory that periodically sends pings to ensure that | ||||
we detect dead connections. | we detect dead connections. | ||||
""" | """ | ||||
@@ -245,7 +250,7 @@ class SynapseRedisFactory(txredisapi.RedisFactory): | |||||
dbid: Optional[int], | dbid: Optional[int], | ||||
poolsize: int, | poolsize: int, | ||||
isLazy: bool = False, | isLazy: bool = False, | ||||
handler: Type = txredisapi.ConnectionHandler, | |||||
handler: Type = ConnectionHandler, | |||||
charset: str = "utf-8", | charset: str = "utf-8", | ||||
password: Optional[str] = None, | password: Optional[str] = None, | ||||
replyTimeout: int = 30, | replyTimeout: int = 30, | ||||
@@ -326,7 +331,7 @@ class RedisDirectTcpReplicationClientFactory(SynapseRedisFactory): | |||||
def __init__( | def __init__( | ||||
self, | self, | ||||
hs: "HomeServer", | hs: "HomeServer", | ||||
outbound_redis_connection: txredisapi.ConnectionHandler, | |||||
outbound_redis_connection: ConnectionHandler, | |||||
channel_names: List[str], | channel_names: List[str], | ||||
): | ): | ||||
super().__init__( | super().__init__( | ||||
@@ -368,7 +373,7 @@ def lazyConnection( | |||||
reconnect: bool = True, | reconnect: bool = True, | ||||
password: Optional[str] = None, | password: Optional[str] = None, | ||||
replyTimeout: int = 30, | replyTimeout: int = 30, | ||||
) -> txredisapi.ConnectionHandler: | |||||
) -> ConnectionHandler: | |||||
"""Creates a connection to Redis that is lazily set up and reconnects if the | """Creates a connection to Redis that is lazily set up and reconnects if the | ||||
connections is lost. | connections is lost. | ||||
""" | """ | ||||
@@ -380,7 +385,7 @@ def lazyConnection( | |||||
dbid=dbid, | dbid=dbid, | ||||
poolsize=1, | poolsize=1, | ||||
isLazy=True, | isLazy=True, | ||||
handler=txredisapi.ConnectionHandler, | |||||
handler=ConnectionHandler, | |||||
password=password, | password=password, | ||||
replyTimeout=replyTimeout, | replyTimeout=replyTimeout, | ||||
) | ) | ||||
@@ -408,3 +413,44 @@ def lazyConnection( | |||||
) | ) | ||||
return factory.handler | return factory.handler | ||||
def lazyUnixConnection( | |||||
hs: "HomeServer", | |||||
path: str = "/tmp/redis.sock", | |||||
dbid: Optional[int] = None, | |||||
reconnect: bool = True, | |||||
password: Optional[str] = None, | |||||
replyTimeout: int = 30, | |||||
) -> ConnectionHandler: | |||||
"""Creates a connection to Redis that is lazily set up and reconnects if the | |||||
connection is lost. | |||||
Returns: | |||||
A subclass of ConnectionHandler, which is a UnixConnectionHandler in this case. | |||||
""" | |||||
uuid = path | |||||
factory = SynapseRedisFactory( | |||||
hs, | |||||
uuid=uuid, | |||||
dbid=dbid, | |||||
poolsize=1, | |||||
isLazy=True, | |||||
handler=UnixConnectionHandler, | |||||
password=password, | |||||
replyTimeout=replyTimeout, | |||||
) | |||||
factory.continueTrying = reconnect | |||||
reactor = hs.get_reactor() | |||||
reactor.connectUNIX( | |||||
path, | |||||
factory, | |||||
timeout=30, | |||||
checkPID=False, | |||||
) | |||||
return factory.handler |
@@ -864,22 +864,36 @@ class HomeServer(metaclass=abc.ABCMeta): | |||||
# We only want to import redis module if we're using it, as we have | # We only want to import redis module if we're using it, as we have | ||||
# `txredisapi` as an optional dependency. | # `txredisapi` as an optional dependency. | ||||
from synapse.replication.tcp.redis import lazyConnection | |||||
from synapse.replication.tcp.redis import lazyConnection, lazyUnixConnection | |||||
logger.info( | |||||
"Connecting to redis (host=%r port=%r) for external cache", | |||||
self.config.redis.redis_host, | |||||
self.config.redis.redis_port, | |||||
) | |||||
if self.config.redis.redis_path is None: | |||||
logger.info( | |||||
"Connecting to redis (host=%r port=%r) for external cache", | |||||
self.config.redis.redis_host, | |||||
self.config.redis.redis_port, | |||||
) | |||||
return lazyConnection( | |||||
hs=self, | |||||
host=self.config.redis.redis_host, | |||||
port=self.config.redis.redis_port, | |||||
dbid=self.config.redis.redis_dbid, | |||||
password=self.config.redis.redis_password, | |||||
reconnect=True, | |||||
) | |||||
return lazyConnection( | |||||
hs=self, | |||||
host=self.config.redis.redis_host, | |||||
port=self.config.redis.redis_port, | |||||
dbid=self.config.redis.redis_dbid, | |||||
password=self.config.redis.redis_password, | |||||
reconnect=True, | |||||
) | |||||
else: | |||||
logger.info( | |||||
"Connecting to redis (path=%r) for external cache", | |||||
self.config.redis.redis_path, | |||||
) | |||||
return lazyUnixConnection( | |||||
hs=self, | |||||
path=self.config.redis.redis_path, | |||||
dbid=self.config.redis.redis_dbid, | |||||
password=self.config.redis.redis_password, | |||||
reconnect=True, | |||||
) | |||||
def should_send_federation(self) -> bool: | def should_send_federation(self) -> bool: | ||||
"Should this server be sending federation traffic directly?" | "Should this server be sending federation traffic directly?" | ||||