随着互联网的的高速发展,大多数的公司由于一开始使用的传统的硬件/软件架构,导致在业务不断发展的同时,系统也逐渐地逼近传统结构的极限。

于是,系统也急需进行结构上的升级换代。

在服务端,系统的I/O是很大的瓶颈。其中数据库的I/O最容易成为限制系统效率的一环。在优化数据库I/O这一环中,可以从优化系统调用数据库效率、数据库自身效率等多方面入手。

一般情况下,通过升级数据库服务器的硬件是最容易达到的。但是服务器资源不可能无限扩大,于是从调用数据库的效率方面入手是目前主流的优化方向。

于是读写分离、分库分表成为了软件系统的重要一环。并且需要在传统的系统架构下,是需要做强入侵性的修改。

 

 

什么是多租户:

多租户的英文是Multiple Tenancy,很多人把多租户和Saas划上等号,其实这还是有区别的。我们今天不讨论Sass这种如此广泛的议题。

现在很多的系统都是to B的,它们面向的是组织、公司和商业机构等。每个机构会有独立的组织架构,独立的订单结构,独立的服务等级和收费。

这就造成了各个机构间的数据是天然独立的,特别是部分的公司对数据的独立和安全性会有较高要求,往往数据是需要独立存储的。

由于多租户数据的天然独立,造成了系统可以根据机构的不同进行分库分表。所以这里讨论的多租户,仅限于数据层面的!

 

写这篇文章原因

其实由于一个群的朋友问到了相关的问题,由于当时我并没有dotnet环境,所以简单地写了几句代码,我本身是不知道代码是否正确的。

在我有空的时候,试了一下原来是可实施的。我贴上当时随手写的核心代码,其中connenctionResolver是需要自己创建的。

这代码是能用的,如果对于asp.net core很熟悉的人,把这段代码放入到ConfigureServices方法内即可。

但是我还是强烈建议大家跟着我的介绍逐步实施。

1 services.AddDbContext<MyContext>((serviceProvider, options)=>
2 {
3     var connenctionResolver = serviceProvider.GetService<IConnectionResolver>();
4     options.UseSqlServer(connenctionResolver.ConnectionString);
5 });

 


实施

项目介绍

这个Demo,主要通过根据http request header来获取不同的租户的标识,从而达到区分租户最终实现数据的隔离。

项目依赖:

1. .net core app 3.1。在机器上安装好.net core SDK, 版本3.1

2. Mysql. 使用 Pomelo.EntityFrameworkCore.MySql 包

3. EF core,Microsoft.EntityFrameworkCore, 版本3.1.1。这里必须要用3.1的,因为ef core3.0是面向.net standard 2.1.  

 

项目中必须对象是什么:

1. DbContext和对应数据库对象

2. ConnenctionResolver, 用于获取连接字符串

3. TenantInfo, 用于表示租户信息

4. TenantInfoMiddleware,用于在asp.net core管道根据http的内容从而解析出TenantInfo

5. Controller, 用于实施对应的

 

实施步骤

1. 创建TenanInfo 和 TenantInfoMiddleware. TenanInfo 作为租户的信息,通过IOC创建,并且在TenantInfoMiddleware通过解析http request header,修改TenantInfo

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计

1 using System;
2 
3 namespace kiwiho.Course.MultipleTenancy.EFcore.Api.Infrastructure
4 {
5     public class TenantInfo
6     {
7         public string Name { get; set; }
8     }
9 }

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计_03

TenantInfoMiddleware

 

2. 创建HttpHeaderSqlConnectionResolver并且实现ISqlConnectionResolver接口。这里要做的事情很简单,直接同TenantInfo取值,并且在配置文件查找对应的connectionString。

其实这个实现类在正常的业务场景是需要包含逻辑的,但是在Demo里为了简明扼要,就使用最简单的方式实现了。

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计_03

ConnectionResolver

 

3. 创建类MultipleTenancyExtension,里面包含最重要的配置数据库连接字符串的方法。其中里面的DbContext并没有使用泛型,是为了更加简明点

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计_03

MultipleTenancyExtension

 

4. 在Startup类中配置依赖注入和把TenantInfoMiddleware加入到管道中。

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计_03

ConfigureServices

在Configure内,在UseRouting前把TenantInfoMiddleware加入到管道

1 app.UseMiddleware<TenantInfoMiddleware>();

 

5. 配置好DbContext和对应的数据库对象

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计_03

StoreDbContext

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计_03

Product

 

6. 创建ProductController, 并且在里面添加3个方法,分别是创建,查询所有,根据id查询。在构造函数通过EnsureCreated以达到在数据库不存在是自动创建数据库。

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计_03

ProductController

 

验证效果

1. 启动项目

netCore 租户系统架构设计 netcore 多租户_数据库_10

 

 2. 通过postman在store1中创建一个Orange,在store2中创建一个cola。要注意的是Headers仲的Tenant:store1是必须的。

图片就只截了store1的例子

netCore 租户系统架构设计 netcore 多租户_netCore 租户系统架构设计_11

 

 3. 分别在store1,store2中查询所有product

store1:只查到了Orange

netCore 租户系统架构设计 netcore 多租户_数据库_12

 

 

store2: 只查到了cola

netCore 租户系统架构设计 netcore 多租户_数据库_13

 

 

4. 通过查询数据库验证数据是否已经隔离。可能有人会觉得为什么2个id都是1。是因为Product的Id使用 [Key] ,数据库的id是自增长的。

其实这是故意为之的,为的是更好的展示这2个对象是在不同的数据库

store1:

netCore 租户系统架构设计 netcore 多租户_多租户_14

 

store2:

netCore 租户系统架构设计 netcore 多租户_数据_15