Django多数据库:如果从库宕机,切换到主库

18

我为Django设置了MySQL数据库后端的主从复制。 目前,我仅对主数据库进行读写,但我的仪表板需要进行相当多的查询。 我正在寻找一个选项,可以像下面这样定义: DATABASES

DATABASES = {
'default_slave': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '3306',
    },
'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '', 
        'PORT': '3306',
    },
}  

对于仪表板、报告和其他各种应用程序,我想做的是:

尝试连接: default_slave:如果可以使用default_slave则使用default_slave,否则使用default

也就是说,如果从数据库启动,则从从数据库本身获取报告,否则从主数据库获取报告。

关键是,从数据库可能正常或异常,并且我希望基于可达性动态选择要使用哪个数据库来获取报告。

这可能吗?我能事先测试连接然后继续吗?

有了这个,我会在主库中编写和同步数据库,并始终从从库中读取(如果从库处于启动状态)。

需要一些原始查询ORM查询的解决方案/提示

路由器的概念似乎不错,但是无法回退到无法访问从库,我不知道是否可能。

更新

如何处理多个数据库

数据库

DATABASES = {
'default_slave': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '3306',
    },
'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '', 
        'PORT': '3306',
    },
}  
'linux': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '', 
        'PORT': '3306',
    },
}  
'linux_slave': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '', 
        'PORT': '3306',
    },
}  
'mac': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '', 
        'PORT': '3306',
    },
}  
'mac_slave': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '', 
        'PORT': '3306',
    },
}  
'pc': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '', 
        'PORT': '3306',
    },
}  
'pc_slave': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '', 
        'PORT': '3306',
    },
}  

现在我有以下两种数据:

  1. 静态数据
  2. 动态数据

静态数据必须存储在“default”中,然后会被复制到“default_slave”中。

对于动态数据,查询首先需要确定它可能位于哪里:在“mac”、“pc”或“linux”中。

为此,我在“static table”中添加了一个字段:“query_on”,其中包含['mac'或 'linux' 或 'pc']的值之一。

现在,在使用查询集时,我只需编写如下代码:

static = Static.objects.get(pk=1)
query_on = static.query_on
dynamic = Dynamic.objects.get(static=static).using(alias=query_on)

这很有效,查询路由到需要执行的数据库。在这里需要判断:

  1. 如果<'query_on'>_slave的连接已经建立,则使用<'query_on'>_slave
  2. 如果<'query_on'>_slave的连接未建立,则使用<'query_on'>

如何处理呢?

应用程序的进一步细节如下:

  1. 有一个数据库:默认(配置和分析数据库):用于维护配置数据和报告分析数据
  2. 有20个数据库(原始数据库):例如mac,linux,rhel,windows,pc(示例名称):用于收集未经过分析的原始数据
  3. 每个数据库都有一个或多个从属节点,命名约定为:default_slave_0,default_slave_1,default_slave_2等

现在需要每5分钟、30分钟、1小时查询一次分析数据,并将查询发送到特定的数据库,因为不是每个数据库都将包含所需的特定数据集。

为了实现这一点,我们需要:

  1. 从默认(或任何一个从属节点)获取配置数据
  2. 一旦获取了配置信息,就可以轻松查找“原始”数据可能位于哪里
  3. 查询原始数据,收集结果并进行分析 --> 存储在“default”数据库中。

现在所有30个(原始)和1个默认数据库都需要同步,因为我们在所有节点上维护相同的数据结构。

现在,在所有数据库上查看CPU峰值是有意义的,因此使用“从属”数据库查询“原始”数据是有帮助的。因此需要使用using。在这里,我无法想象路由器如何有所帮助?

1个回答

19

使用路由器是正确的做法。我假设你两个数据库定义相同只是一个打字错误。

(顺便说一句,我将使用更为敏感的 master->follower 表示数据库层级结构)

在你的 db_for_read() 函数中,你可以检查 follower 的连接状态。这可能会增加一些开销,但这是具备自动故障转移功能的数据库所需的代价。一个示例数据库定义如下:

DATABASES = {
'follower': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'follower',
        'USER': 'root',
        'HOST': '54.34.65.24',
        'PORT': '3306',
    },
'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'application',
        'USER': 'root',
        'HOST': '54.34.65.23',
        'PORT': '3306',
    },
}  

你可以像这个示例一样使用快速的try/except测试连接。使用此方法执行所需操作的路由器如下:

from django.conf import settings
import socket


def test_connection_to_db(database_name):
    try:
        db_definition = getattr(settings, 'DATABASES')[database_name]
        s = socket.create_connection((db_definition['HOST'], db_definition['PORT']), 5)
        s.close()
        return True
    except (AttributeError, socket.timeout) as e:
        return False


class FailoverRouter(object):
    """A router that defaults reads to the follower but provides a failover back to the default"""

    def db_for_read(self, model, **hints):
        if test_connection_to_db('follower'):
            return 'follower'
        return 'default'

    def db_for_write(self, model, **hints):
        "Point all writes to the default db"
        return 'default'

    def allow_syncdb(self, db, model):
        "Make sure only the default db allows syncdb"
        return db == 'default'

这仍然会在主数据库中进行像您想要的那样的同步。此外,您可以使db_for_read()db_for_write()的逻辑更加复杂(例如,仅为查询报告的某些模型选择从属数据库)。

我不知道每次读取时使用test_connection()会导致多少开销,因为这将取决于MySQL服务器和超时时间。也许更好的架构是使用memcached缓存这些报告,或者解决从属数据库挂掉的问题并先更新设置中的数据库定义。


谢谢。那应该可以工作。 我的计划是,.using(alias = defualt_slave) 如果连接成功,很好,如果不行,就用 using= default(与 _slave 分开)。这将是一个快速的解决方案。 - Joddy
请检查问题中的更新。当“查询集”使用“using”时,使用路由器没有意义,我还无法解决这个问题。@krimkus - Joddy
首先,对于答案的编辑我很抱歉。那是一个错误。我已经修改了问题,请检查一下。@krimkus - Joddy
如果您想针对特定数据库收集“原始”数据,我不认为使用.using()修饰符会有任何问题。这将命中您打算使用的确切数据库,然后您可以使用路由器将该信息存储在默认数据库中。 - krimkus
如果我理解正确的话,这个解决方案是关于在每个 get_querysetusing 调用时对服务器进行 ping 测试,是吗? - Alexander Klimenko
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接