【実践プロジェクト】単一のデータソースと複数のデータベースでマルチテナントを実現

序文

マルチテナンシーは、単一のアプリケーションが複数のテナント (異なる組織、ユーザー、顧客など) にサービスを同時に提供できるようにすることを目的としたソフトウェア アーキテクチャの設計パターンです。各テナントは独立したデータと構成を持ち、他のテナントから分離されています。お互い。マルチテナント アーキテクチャを実装すると、企業はコストを削減し、管理を簡素化し、効率を向上させることができます。

マルチテナント システムを設計する前に、ビジネス要件と期待される目標を明確にする必要があります。テナント間を完全に分離しますか? それとも一部の共有リソースを許可しますか? 次の側面を考慮する必要があります。

データの分離: 各テナントのデータは相互に分離される必要があり、異なるテナントがデータに直接アクセスしたり共有したりすることはできません。データの分離は、別個のデータベースを使用するか、データ テーブルでテナント ID を使用して区別することによって実現できます。

ID の認証と認可: マルチテナント システムでは、正規のユーザーのみが独自のテナント リソースにアクセスできるように、各テナントの ID 認証と認可をサポートする必要があります。

水平方向の拡張: マルチテナント システムは、多数のユーザーとデータの増加という課題に直面する可能性があるため、システムの高いパフォーマンスと信頼性を維持するために水平方向に拡張する機能が必要です。

構成管理: 各テナントには、メール、支払い、ストレージなどとの統合のためのパラメーター設定など、異なる構成ニーズがある場合があります。したがって、テナントがニーズに応じてカスタマイズできるように、柔軟に構成可能なメカニズムを提供する必要があります。

セキュリティと分離: マルチテナント システムでは、潜在的なデータ漏洩やアクセス競合を防ぐために、テナント間で安全な分離が必要です。システムを設計する際には、機密データの保護とさまざまな攻撃の防止を考慮する必要があります。

リソースの使用率: リソースの使用率を向上させるために、コンピューティング リソース、ストレージ スペース、ネットワーク帯域幅などの特定の共通リソースを共有することを検討できます。これによりコストが削減され、効率が向上します。

マルチテナンシーの 4 つの実装オプション

一般的な設計ソリューションは大きく 4 つのタイプに分類されます。

1. すべてのテナントが同じデータベース内の同じデータ ソースと同じデータ テーブルを使用します (単一データ ソース、単一データベース、単一データ テーブル) 2. すべてのテナントが同じデータベース内の同じデータ ソースと異なるデータ テーブルを使用します (
単一データ3. すべてのテナントが、
同じデータ ソースの異なるデータベースで異なるデータ テーブルを使用します (単一のデータ ソース、複数のデータベース、複数のデータ テーブル) 4. すべてのテナントが、
異なるデータベースの異なるデータ テーブルを使用します。データ ソース (複数のデータ ソース、複数のデータベース、複数のデータ テーブル)
ここに画像の説明を挿入します

1 番目と 2 番目のタイプは比較的単純です。この記事では主に 3 番目のタイプについて説明します

単一のデータソース、複数のデータベース

マルチテナントの最終的な目標は、データの分離を実現することであり、複数のデータ ソースを持つ複数のデータベースでは、つまり新しいテナントが登録されたときに、データベースと対応するデータベース テーブルを作成する必要があります。
ここで、データベースを動的に作成するにはどうすればよいかという質問があります。

実装のアイデア

1. データベースを作成します: 従来の方法で SQL ステートメントを使用して作成できます
2. データベース テーブルを作成します: データベースを作成した後、SQL ステートメントを使用してテーブルを作成します。

次のコードは、SQL を使用して XML ファイルを読み取り、作成します。

コード

リソースの下に XML ファイルを作成します。
ここに画像の説明を挿入します

<?xml version="1.0" encoding="UTF-8"?>
<document>
    <statement>
        <name>create-tenant</name>
        <script>
            DROP DATABASE IF EXISTS `sys_user${
    
    tenant_id}`;
            CREATE DATABASE ${
    
    database};
            USE ${
    
    database};

            CREATE TABLE `sys_user` (
            `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
            `dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
            `user_name` varchar(30) NOT NULL COMMENT '用户账号',
            `nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
            `user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户)',
            `email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
            `phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
            `sex` char(1) DEFAULT '0' COMMENT '用户性别(012未知)',
            `avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
            `password` varchar(100) DEFAULT '' COMMENT '密码',
            `status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
            `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
            `login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
            `login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
            `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
            `create_time` datetime DEFAULT NULL COMMENT '创建时间',
            `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
            `update_time` datetime DEFAULT NULL COMMENT '更新时间',
            `remark` varchar(500) DEFAULT NULL COMMENT '备注',
            `auth_id` varchar(30) DEFAULT NULL COMMENT '其他项目用户id(积分)',
            `ding_id` varchar(50) DEFAULT NULL COMMENT '用户dingid',
            `tenant_id` varchar(100) DEFAULT NULL COMMENT '公司id',
            `open_id` varchar(50) DEFAULT NULL COMMENT '微信用户唯一标识',
            PRIMARY KEY (`user_id`) USING BTREE
            ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用户信息表';
                 </script>
    </statement>
</document>

これは、データベースを作成するための対応するステートメントです。

XML ファイルから情報を取得します。

@Component
public class ExecSqlConfig {
    
    
    private final Map<String, String> sqlContainer = new HashMap<>();

    public ExecSqlConfig() throws Exception {
    
    
        // 1.创建Reader对象
        SAXReader reader = new SAXReader();
        // 2.加载xml
        InputStream inputStream = new ClassPathResource("create-library.xml").getInputStream();
        Document document = reader.read(inputStream);
        // 3.获取根节点
        Element root = document.getRootElement();

        // 4.遍历每个statement
        List<Element> statements = root.elements("statement");
        for (Element statement : statements) {
    
    
            String name = null;
            String sql = null;
            List<Element> elements = statement.elements();
            // 5.拿到name和script加载到内存中管理
            for (Element element : elements) {
    
    
                if ("name".equals(element.getName())) {
    
    
                    name = element.getText();
                } else if ("script".equals(element.getName())) {
    
    
                    sql = element.getText();
                }
            }
            sqlContainer.put(name, sql);
        }
    }

    public String get(String name) {
    
    
        return sqlContainer.get(name);
    }

}

SQL実行スクリプト:
スクリプト内のExecSqlConfigファイルの情報を読み取ります。

@Component
public class ExecSqlUtil {
    
    
    @Autowired
    private   ExecSqlConfig execSqlConfig ;

    @Autowired
    private  DataSource dataSource ;
/**
 * @description: 把模板中的数据库名称替换为新租户的名称
 * @author: 
 * @date: 2023/9/27 15:05
 * @param: [name , replaceMap]
 * @return: void
 **/
    @SneakyThrows
    public  void execSql(String name, Map<String, String> replaceMap) {
    
    
        try {
    
    
            // 获取SQL脚本模板
            String sql = execSqlConfig.get(name);

            // 替换模板变量
            for (Map.Entry<String, String> entity : replaceMap.entrySet()) {
    
    
                sql = sql.replace(entity.getKey(), entity.getValue());
            }
            ScriptRunner scriptRunner = new ScriptRunner(dataSource.getConnection());

            // 执行SQL
           scriptRunner.runScript(new StringReader(sql));
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

    }

}

ビジネスの論理

public void addTenantLibrary(TanentModel tanentModel) {
    
    
        try {
    
    
            if (!(tanentModel.getDescription().isEmpty()||tanentModel.getTenantContact().isEmpty()||tanentModel.getTel().isEmpty()||tanentModel.getTenantName().isEmpty())){
    
    
                //判断数据源中是否已经存在该数据库
               int flag= tantentMapper.insertTantentAdmin(tanentModel.getTenantName());
               if (flag>0){
    
    
                   throw new Exception("已存在该数据库");
               }else {
    
    
                   Map<String,String> map=new HashMap<>();
                   map.put("${database}",tanentModel.getTenantName());
                   map.put("${project_name}",tanentModel.getTenantName());
                   map.put("${leader}",tanentModel.getTenantContact());
                   map.put("${phone}",tanentModel.getTel());
                   map.put("${description}",tanentModel.getDescription());
                   execSqlUtil.execSql("create-tenant",map);

               }
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
            throw new RuntimeException(e.getMessage()); // 将异常信息作为响应的一部分返回给前端
        }
    }

ビジネス ロジック コードでは、map.put(“${database}”,tanentModel.getTenantName()); は、置き換えられた XML ファイル内の変数です。

要約する

マルチテナント アーキテクチャでは、単一のデータ ソースと複数のデータベースが一般的な実装ソリューションです。異なるデータベース インスタンスまたはデータベース スキーマを使用することで、テナントごとに独立したデータ ストレージを実装し、相互の分離を維持できます。この記事では、実装オプションと、単一データ ソースの複数データベースに関するいくつかの重要な考慮事項について説明します。

要約すると、単一データ ソースのマルチデータベース ソリューションは、マルチテナント システムに次の利点をもたらします。

データの分離: 各テナントには独立したデータベースがあり、データは相互に干渉しないため、テナント データの分離とセキュリティが確保されます。

パフォーマンスの最適化: テナント データを複数のデータベースに分散することにより、単一データベースの負荷圧力が軽減され、システムのパフォーマンスとスループットが向上します。

スケーラビリティ: テナントの数が増加した場合、増大するビジネス ニーズに合わせて新しいデータベース インスタンスまたはデータベース サーバーを追加することで、システムを水平方向に拡張できます。

フォールト トレランス: 単一のデータ ソースと複数のデータベース ソリューションにより、テナント間の対話が減少します。データベースに障害が発生しても、他のデータベースは引き続き正常に実行できるため、システムのフォールト トレランスと可用性が向上します。

単一データ ソースのマルチデータベース ソリューションを実装する場合は、次の重要な要素に注意する必要があります。

データの移行と同期: 既存のマルチテナント システムの場合、既存のデータを新しいデータベースに移行するには、ある程度の作業が必要になる場合があります。同時に、データの一貫性を維持するために、複数のデータベース間でデータの同期を確保する必要があります。

テナント管理およびルーティング戦略: 各リクエストが対応するデータベースに正しくルーティングされることを保証するには、テナントの動的管理およびルーティング戦略を実装する必要があります。これは、テナントとデータベースのマッピングを維持するか、リクエストのルーティングにミドルウェアを使用することによって実現できます。

データベース接続とリソース管理: システム設計では、データベース接続の管理とリソース割り当てを考慮する必要があります。データベース接続プールを適切に構成し、データベース クエリを最適化すると、システムのパフォーマンスとリソースの使用率が向上します。

セキュリティと権限の制御: データベース アーキテクチャを設計するときは、セキュリティと権限の制御の要件を考慮する必要があります。異なるテナント間のデータ アクセスと操作が制限され、データのプライバシーと保護に関する法的要件に準拠していることを確認します。

要約すると、単一のデータ ソースと複数のデータベースは、優れたデータ分離、パフォーマンスの最適化、およびスケーラビリティを提供するマルチテナント システムを実装するための効果的なソリューションです。ただし、システムの安定性と信頼性を確保するには、実装プロセス中にデータ移行、テナント管理、リソース管理、セキュリティなどの要素を包括的に考慮する必要があります。適切な設計と実装により、単一データ ソースのマルチデータベース ソリューションは、マルチテナント システムに効率的で安全かつスケーラブルなデータ ストレージ ソリューションを提供できます。

おすすめ

転載: blog.csdn.net/weixin_45309155/article/details/133409083