【问题标题】:Best Practices for developing a multi-tenant application with Symfony2 and Doctrine2使用 Symfony2 和 Doctrine2 开发多租户应用程序的最佳实践
【发布时间】:2011-11-24 05:44:26
【问题描述】:

我正在开发一个需要支持多租户模型的应用程序。我正在使用 symfony2 php 框架和教义2。

我不确定构建此需求的最佳方式。 Symfony 的 ACL 功能是否提供了解决方案的一部分?

您能提供什么建议或想法?是否有任何可用的示例 symfony2 应用程序或开源应用程序实现了此策略?

我的第一个想法是在所有表中使用一个tenant_id 列,并将其与应用程序中的帐户对象相关联。我不确定 ACL 是否应该处理我想要做的事情,或者您是否仍然对针对您的对象的所有查询负责,这样它们就不会返回未经授权的数据。

如果我没有使用 Doctrine,可能很容易说只需将 Where tenant_id = @accountid 附加到每个查询,但我不确定这是正确的方法。

谢谢

【问题讨论】:

  • 到目前为止你做了什么?你做过哪 N 种替代实践,哪一个是你眼中的最佳实践,你遇到了哪些问题,所以你在这里问?

标签: php doctrine-orm symfony saas multi-tenant


【解决方案1】:

BEST 在不同的头脑中构建不同的通知。请更具体地提出问题。开发多租户系统的方法之一是在所有表中放置一个共同的主键来建立关系。主键的类型和性质是可靠的项目。

【讨论】:

    【解决方案2】:

    为什么不为每个客户尝试不同的数据库,以保持数据分离并为它们提供您应用的唯一入口点。例如:http://client1.project.net 与路由系统将映射到 client1 数据库。不好的一面:更复杂的数据库更改,因为每个客户端的所有数据库都需要升级。

    【讨论】:

    • 这违背了多租户的目的!
    • 混合方式可能更好;但我认为不同的数据库对长期有好处。单个 DB 变得太大而无法管理,并且经常发生表锁。
    【解决方案3】:

    我们已经这样做了一段时间(虽然不是使用 symfony 和教义,但问题仍然存在) - 我们从一个巨大的数据库开始,并在每个表的每行中实现了一个“环境标识符”。这样架构迁移很容易:所有代码都是统一的,因此架构更改是对代码和架构的一次更改。

    然而,这会导致速度(大型表)、敏捷性(移动/备份等大型数据集比许多较小的数据集更密集)以及更容易破坏的环境,因为单个故障将拉低所有数据集系统...

    然后我们切换到多个数据库;每个环境都有自己的架构。通过利用 Doctrine 提供的迁移(在我们的例子中为 1),我们能够快速更新整个应用程序或单个环境。此外,在 tentants 之间移动特定更改的能力可以提高速度和优化的精度。

    我的建议是:创建一个扩展到不同租户的单个核心,并为每个租户保留本地自定义数据库配置。 (在类似 app.ini 的结构中)

    /
        apps/
            tentant1/
                models/ <--- specific to the tenant
                libraries/ <--- specific to the tenant
                config/
                    app.ini <--- specific configuration
            tentant2/
            /**/ etc
        core/
            models/ <--- system wide models
            libraries/ <--- system wide libraries (i.e. doctrine)
            core.ini <--- system wide configuration
    

    这可以使一切井井有条。我们甚至正在为每个租户提供完整的核心结构。因此能够覆盖特定于租户的“核心”的各个方面。

    【讨论】:

      【解决方案4】:

      我们使用我们工作中的主要解决方案之一来做到这一点,这当然是可能的。我们使用 Symfony2 的包创建一个“基础”包,然后由其他每个客户端包扩展。

      也就是说,我们正在考虑在未来摆脱这种方式。采用多租户的决定不适合我们,因为我们的许多客户对他们的数据与其他所有人的数据位于同一个数据库表中感到不舒服。这完全不包括随着表的增长而导致性能下降的潜在问题。

      我们还发现 Doctrine 2 存在一些非常严重的问题,除非它得到很好的控制。虽然这可能是结构不良的代码和数据库逻辑的副作用,但我觉得 ORM 能够达到抛出致命错误的地步是一个漏洞,因为它使用了太多内存 - 特别是当它使用这么多内存的唯一原因是因为它正在对 SQL 查询进行批处理,以便使它们“更高效”。

      当然,这纯粹是我的观点 :) 对我们不起作用的东西可能对您有用,但我确实认为您最好为每个客户端保留单独的数据库,即使它们都已存储在同一台服务器上。

      【讨论】:

        【解决方案5】:

        这也是我一直试图弄清楚的事情。我能想出的最好办法(尚未实现,但在理论上)是这样的:为每个租户提供自己的数据库登录名并使用视图来防止他们看到其他人的数据。

        I ran across this link that describes a way of doing this for just plain old MySQL (not with Symfony/Doctrine).

        基本上,您拥有实际的数据库表,但每个表都有一个列,用于存储创建该行的数据库用户的名称。然后创建始终按此列过滤的视图,因此每当用户登录数据库(通过管理工具甚至通过 Symfony/Doctrine 连接)时,它们只会返回与它们直接关联的记录。这使您可以将数据“分开”,但仍保留在一个数据库中。当提取数据时(比如 Symfony 中的实体),您是从过滤视图与实际数据库表中提取数据。

        现在,这个解决方案并不完全对 Symfony/Doctrine 友好。我之前能够对此运行进行非常快速和基本的测试; Doctrine 能够很好地使用数据库视图(它可以从视图中插入、编辑和删除条目没有问题)。然而,在做诸如创建/更新模式之类的事情时,它并不好玩。诚然,Symfony/Doctrine 看起来相当可扩展,所以我相信有办法让它自动化,但这种设置不支持开箱即用。需要告知 Doctrine 更新表,始终将用于保存用户名的列附加到它创建的实体表中,更新视图等等。(您还需要一种方法来在您的Symfony 应用程序,主要是作为服务器的不同登录名和其他东西是相同的。)但是,如果可以克服这一点,您的应用程序本身可以运行这些多个租户,完全“不知道”其他人的数据位于数据库。

        【讨论】:

        • MySQL 视图非常非常有限。你不会这样。
        【解决方案6】:

        我认为,使用 symfony 2/3 管理多租户多数据库。 我们可以为教义的 ORM 配置auto_mapping: false。 文件:config.yml

        doctrine:
            dbal:
                default_connection: master
                connections:
                    master:
                        driver:   pdo_mysql
                        host:     '%master_database_host%'
                        port:     '%master_database_port%'
                        dbname:   '%master_database_name%'
                        user:     '%master_database_user%'
                        password: '%master_database_password%'
                        charset:  UTF8
                    tenant:
                        driver:   pdo_mysql
                        host:     '%tenant_database_host%'
                        port:     '%tenant_database_port%'
                        dbname:   '%tenant_database_name%'
                        user:     '%tenant_database_user%'
                        password: '%tenant_database_password%'
                        charset:  UTF8
        
            orm:
                default_entity_manager: master
                auto_generate_proxy_classes: "%kernel.debug%"
                entity_managers:
                    master:
                        connection: master
                        auto_mapping: false
                        mappings:
                            AppBundle:
                                type: yml
                                dir: Resources/master/config/doctrine
                    tenant:
                        connection: tenant
                        auto_mapping: false
                        mappings:
                            AppBundle:
                                type: yml
                                dir: Resources/tenant/config/doctrine
        

        之后,我们无法通过覆盖 request_listener 中的连接信息来处理每个租户的连接,如文章:http://mohdhallal.github.io/blog/2014/09/12/handling-multiple-entity-managers-in-doctrine-the-smart-way/ 我希望,这种做法可以帮助使用多租户的人

        问候,

        王阮

        【讨论】:

          猜你喜欢
          • 2023-04-06
          • 1970-01-01
          • 2010-10-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-12-03
          相关资源
          最近更新 更多