ext_redis.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. from typing import Any, Union
  2. import redis
  3. from redis.cluster import ClusterNode, RedisCluster
  4. from redis.connection import Connection, SSLConnection
  5. from redis.sentinel import Sentinel
  6. from configs import dify_config
  7. from dify_app import DifyApp
  8. class RedisClientWrapper:
  9. """
  10. A wrapper class for the Redis client that addresses the issue where the global
  11. `redis_client` variable cannot be updated when a new Redis instance is returned
  12. by Sentinel.
  13. This class allows for deferred initialization of the Redis client, enabling the
  14. client to be re-initialized with a new instance when necessary. This is particularly
  15. useful in scenarios where the Redis instance may change dynamically, such as during
  16. a failover in a Sentinel-managed Redis setup.
  17. Attributes:
  18. _client (redis.Redis): The actual Redis client instance. It remains None until
  19. initialized with the `initialize` method.
  20. Methods:
  21. initialize(client): Initializes the Redis client if it hasn't been initialized already.
  22. __getattr__(item): Delegates attribute access to the Redis client, raising an error
  23. if the client is not initialized.
  24. """
  25. def __init__(self):
  26. self._client = None
  27. def initialize(self, client):
  28. if self._client is None:
  29. self._client = client
  30. def __getattr__(self, item):
  31. if self._client is None:
  32. raise RuntimeError("Redis client is not initialized. Call init_app first.")
  33. return getattr(self._client, item)
  34. redis_client = RedisClientWrapper()
  35. def init_app(app: DifyApp):
  36. global redis_client
  37. connection_class: type[Union[Connection, SSLConnection]] = Connection
  38. if dify_config.REDIS_USE_SSL:
  39. connection_class = SSLConnection
  40. redis_params: dict[str, Any] = {
  41. "username": dify_config.REDIS_USERNAME,
  42. "password": dify_config.REDIS_PASSWORD,
  43. "db": dify_config.REDIS_DB,
  44. "encoding": "utf-8",
  45. "encoding_errors": "strict",
  46. "decode_responses": False,
  47. }
  48. if dify_config.REDIS_USE_SENTINEL:
  49. assert dify_config.REDIS_SENTINELS is not None, "REDIS_SENTINELS must be set when REDIS_USE_SENTINEL is True"
  50. sentinel_hosts = [
  51. (node.split(":")[0], int(node.split(":")[1])) for node in dify_config.REDIS_SENTINELS.split(",")
  52. ]
  53. sentinel = Sentinel(
  54. sentinel_hosts,
  55. sentinel_kwargs={
  56. "socket_timeout": dify_config.REDIS_SENTINEL_SOCKET_TIMEOUT,
  57. "username": dify_config.REDIS_SENTINEL_USERNAME,
  58. "password": dify_config.REDIS_SENTINEL_PASSWORD,
  59. },
  60. )
  61. master = sentinel.master_for(dify_config.REDIS_SENTINEL_SERVICE_NAME, **redis_params)
  62. redis_client.initialize(master)
  63. elif dify_config.REDIS_USE_CLUSTERS:
  64. assert dify_config.REDIS_CLUSTERS is not None, "REDIS_CLUSTERS must be set when REDIS_USE_CLUSTERS is True"
  65. nodes = [
  66. ClusterNode(host=node.split(":")[0], port=int(node.split(":")[1]))
  67. for node in dify_config.REDIS_CLUSTERS.split(",")
  68. ]
  69. # FIXME: mypy error here, try to figure out how to fix it
  70. redis_client.initialize(RedisCluster(startup_nodes=nodes, password=dify_config.REDIS_CLUSTERS_PASSWORD)) # type: ignore
  71. else:
  72. redis_params.update(
  73. {
  74. "host": dify_config.REDIS_HOST,
  75. "port": dify_config.REDIS_PORT,
  76. "connection_class": connection_class,
  77. }
  78. )
  79. pool = redis.ConnectionPool(**redis_params)
  80. redis_client.initialize(redis.Redis(connection_pool=pool))
  81. app.extensions["redis"] = redis_client