在Python中管理连接创建?
应用程序通常需要连接到其他服务(数据库、缓存、API等)。出于理智和干爽的考虑,我们希望将所有这些连接保留在一个模块中,以便我们的代码库的其余部分可以共享连接 为了减少样板文件,下游使用应简单:在Python中管理连接创建?,python,design-patterns,architecture,database-connection,Python,Design Patterns,Architecture,Database Connection,应用程序通常需要连接到其他服务(数据库、缓存、API等)。出于理智和干爽的考虑,我们希望将所有这些连接保留在一个模块中,以便我们的代码库的其余部分可以共享连接 为了减少样板文件,下游使用应简单: # app/do_stuff.py from .connections import AwesomeDB db = AwesomeDB() def get_stuff(): return db.get('stuff') # app/cli.py or some other main ent
# app/do_stuff.py
from .connections import AwesomeDB
db = AwesomeDB()
def get_stuff():
return db.get('stuff')
# app/cli.py or some other main entry point
from .connections import AwesomeDB
db = AwesomeDB()
db.init(username='stuff admin') # Or os.environ['DB_USER']
设置连接也应该很简单:
# app/do_stuff.py
from .connections import AwesomeDB
db = AwesomeDB()
def get_stuff():
return db.get('stuff')
# app/cli.py or some other main entry point
from .connections import AwesomeDB
db = AwesomeDB()
db.init(username='stuff admin') # Or os.environ['DB_USER']
Django和Flask这样的Web框架做了类似的事情,但感觉有点笨重:
这样做的一个大问题是,我们希望引用实际的连接对象,而不是代理,因为我们希望在iPython和其他开发环境中保留制表符完成
那么什么是正确的方法(tm)呢?经过几次迭代后,我的想法如下:
#app/connections.py
from awesome_database import AwesomeDB as RealAwesomeDB
from horrible_database import HorribleDB as RealHorribleDB
class ConnectionMixin(object):
__connection = None
def __new__(cls):
cls.__connection = cls.__connection or object.__new__(cls)
return cls.__connection
def __init__(self, real=False, **kwargs):
if real:
super().__init__(**kwargs)
def init(self, **kwargs):
kwargs['real'] = True
self.__init__(**kwargs)
class AwesomeDB(ConnectionMixin, RealAwesomeDB):
pass
class HorribleDB(ConnectionMixin, RealHorribleDB):
pass
改进空间:将初始连接设置为通用ConnectionProxy,而不是None,这将捕获所有属性访问并引发异常
我已经在这里和各种OSS项目中做了很多探索,但还没有看到类似的东西。它感觉非常可靠,尽管它确实意味着一堆模块将在导入时实例化连接对象作为副作用。这会在我脸上爆炸吗?这种方法还有其他负面影响吗?首先,从设计角度来看,我可能遗漏了一些东西,但我不明白为什么您需要重混合+单体机制,而不仅仅是定义这样的助手:
_awesome_db = None
def awesome_db(**overrides):
global _awesome_db
if _awesome_db is None:
# Read config/set defaults.
# overrides.setdefault(...)
_awesome_db = RealAwesomeDB(**overrides)
return _awesome_db
此外,还有一个bug可能看起来不像是受支持的用例,但无论如何:如果连续进行以下两次调用,即使传递了不同的参数,也会错误地获取相同的连接对象两次:
db = AwesomeDB()
db.init(username='stuff admin')
db = AwesomeDB()
db.init(username='not-admin') # You'll get admin connection here.
一个简单的解决方法是使用输入参数上的连接记录
现在,关于问题的实质
我认为答案取决于“连接”类的实际实现方式
我认为您的方法的潜在缺点是:
- 在多线程环境中,从多个线程对全局连接对象的非同步并发访问可能会出现问题,除非它已经是线程安全的。如果您关心这一点,您可以稍微更改代码和接口,并使用线程局部变量
- 如果一个进程在创建连接后分叉怎么办?Web应用程序服务器倾向于这样做,这可能不安全,这同样取决于底层连接
- 连接对象是否具有状态?如果连接对象变得无效(由于连接错误/超时),会发生什么情况?您可能需要用新连接替换断开的连接,以便在下次请求连接时返回
- 从连接池
- 在连接上
- 如果连接失败,客户端将停止连接
- 在任何情况下,最终都是这样,它可以被后续调用或其他线程重用
# app/connections.py
def redis_client(**kwargs):
# Maybe read configuration/set default arguments
# kwargs.setdefault()
return redis.Redis(**kwargs)
类似地,SQLAlchemy也可以使用
总而言之,我的理解是:
- 如果您的客户端库支持连接池,那么您不需要做任何特殊的事情来共享模块甚至线程之间的连接。您可以定义一个类似于
的助手来读取配置或指定默认参数redis\u client()
- 如果客户端库只提供低级连接对象,则需要确保对它们的访问是线程安全和fork安全的。此外,您需要确保每次返回有效连接时(如果无法建立或重用现有连接,则引发异常)