确定分布策略 中,

我们讨论了在多租户用例中使用 Citus 所需的与框架无关的数据库更改。

当前部分研究如何构建与 Citus 存储后端一起使用的多租户 ASP.NET 应用程序。

示例应用

为了使这个迁移部分具体化,

让我们考虑一个简化版本的 StackExchange。

供参考,最终结果存在于 Github 上。

Schema

我们将从两张表开始:


CREATE TABLE tenants (
id uuid NOT NULL,
domain text NOT NULL,
name text NOT NULL,
description text NOT NULL,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL
); CREATE TABLE questions (
id uuid NOT NULL,
tenant_id uuid NOT NULL,
title text NOT NULL,
votes int NOT NULL,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL
); ALTER TABLE tenants ADD PRIMARY KEY (id);
ALTER TABLE questions ADD PRIMARY KEY (id, tenant_id);

我们 demo 应用程序的每个租户都将通过不同的域名进行连接。

ASP.NET Core 将检查传入请求并在 tenants 表中查找域。

您还可以按子域(或您想要的任何其他 scheme)查找租户。

注意 tenant_id 是如何存储在 questions 表中的。

这将使 :ref:colocate <colocation> 数据成为可能。

创建表后,使用 create_distributed table 告诉 Citus 对租户 ID 进行分片:


SELECT create_distributed_table('tenants', 'id');
SELECT create_distributed_table('questions', 'tenant_id');

接下来包括一些测试数据。


INSERT INTO tenants VALUES (
'c620f7ec-6b49-41e0-9913-08cfe81199af',
'bufferoverflow.local',
'Buffer Overflow',
'Ask anything code-related!',
now(),
now()); INSERT INTO tenants VALUES (
'b8a83a82-bb41-4bb3-bfaa-e923faab2ca4',
'dboverflow.local',
'Database Questions',
'Figure out why your connection string is broken.',
now(),
now()); INSERT INTO questions VALUES (
'347b7041-b421-4dc9-9e10-c64b8847fedf',
'c620f7ec-6b49-41e0-9913-08cfe81199af',
'How do you build apps in ASP.NET Core?',
1,
now(),
now()); INSERT INTO questions VALUES (
'a47ffcd2-635a-496e-8c65-c1cab53702a7',
'b8a83a82-bb41-4bb3-bfaa-e923faab2ca4',
'Using postgresql for multitenant data?',
2,
now(),
now());

这样就完成了数据库结构和示例数据。 我们现在可以继续设置 ASP.NET Core。

ASP.NET Core 项目

如果您没有安装 ASP.NET Core,请安装 Microsoft 的 .NET Core SDK

这些说明将使用 dotnet CLI,

但如果您使用的是 Windows,

也可以使用 Visual Studio 2017 或更高版本。

使用 dotnet new 从 MVC 模板创建一个新项目:

dotnet new mvc -o QuestionExchange
cd QuestionExchange

如果您愿意,可以使用 dotnet run 预览模板站点。

MVC 模板几乎包含您开始使用的所有内容,但 Postgres 支持并不是开箱即用的。

你可以通过安装 Npgsql.EntityFrameworkCore.PostgreSQL 包来解决这个问题:

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL

此包将 Postgres 支持添加到 Entity Framework Core、ASP.NET Core 中的默认 ORM 和数据库层。

打开 Startup.cs 文件并将这些行添加到 ConfigureServices 方法的任意位置:

var connectionString = "connection-string";

services.AddEntityFrameworkNpgsql()
.AddDbContext<AppDbContext>(options => options.UseNpgsql(connectionString));

您还需要在文件顶部添加这些声明:

using Microsoft.EntityFrameworkCore;
using QuestionExchange.Models;

connection-string 替换为您的 Citus 连接字符串。我的看起来像这样:

Server=myformation.db.citusdata.com;Port=5432;Database=citus;Userid=citus;Password=mypassword;SslMode=Require;Trust Server Certificate=true;

您可以使用 Secret

Manager
来避免将数据库凭据存储在代码中(并意外将它们检入源代码控制中)。

接下来,您需要定义一个数据库上下文。

添加 Tenancy(租赁) 到 App

定义 Entity Framework Core 上下文和模型

数据库上下文类提供代码和数据库之间的接口。

Entity Framework Core 使用它来了解您的 data

schema
是什么样的,

因此您需要定义数据库中可用的表。

在项目根目录中创建一个名为 AppDbContext.cs 的文件,并添加以下代码:

using System.Linq;
using Microsoft.EntityFrameworkCore;
using QuestionExchange.Models;
namespace QuestionExchange
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
} public DbSet<Tenant> Tenants { get; set; } public DbSet<Question> Questions { get; set; }
}
}

两个 DbSet 属性指定用于对每个表的行建模的 C# 类。

接下来您将创建这些类。在此之前,请在 Questions 属性下方添加一个新方法:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var mapper = new Npgsql.NpgsqlSnakeCaseNameTranslator();
var types = modelBuilder.Model.GetEntityTypes().ToList(); // Refer to tables in snake_case internally
types.ForEach(e => e.Relational().TableName = mapper.TranslateMemberName(e.Relational().TableName)); // Refer to columns in snake_case internally
types.SelectMany(e => e.GetProperties())
.ToList()
.ForEach(p => p.Relational().ColumnName = mapper.TranslateMemberName(p.Relational().ColumnName));
}

C# 类和属性按惯例是 PascalCase,但 Postgres 表和列是小写的(和 snake_case)。

OnModelCreating 方法允许您覆盖默认名称转换并让 Entity Framework Core 知道如何在数据库中查找实体。

现在您可以添加代表租户和问题的类。

在 Models 目录中创建一个 Tenant.cs 文件:

using System;

namespace QuestionExchange.Models
{
public class Tenant
{
public Guid Id { get; set; } public string Domain { get; set; } public string Name { get; set; } public string Description { get; set; } public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset UpdatedAt { get; set; }
}
}

还有一个 Question.cs 文件,也在 Models 目录中:

using System;

namespace QuestionExchange.Models
{
public class Question
{
public Guid Id { get; set; } public Tenant Tenant { get; set; } public string Title { get; set; } public int Votes { get; set; } public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset UpdatedAt { get; set; }
}
}

注意 Tenant 属性。

在数据库中,问题表包含一个 tenant_id 列。

Entity Framework Core 足够聪明,可以确定此属性表示租户和问题之间的一对多关系。

稍后在查询数据时会用到它。

到目前为止,您已经设置了 Entity Framework Core 和与 Citus 的连接。

下一步是向 ASP.NET Core 管道添加多租户支持。

安装 SaasKit

SaasKit 是一款优秀的开源 ASP.NET Core 中间件。

该软件包使您的 Startup 请求管道 租户感知(tenant-aware) 变得容易,

并且足够灵活以处理许多不同的多租户用例。

安装 SaasKit.Multitenancy 包:

dotnet add package SaasKit.Multitenancy

SaasKit 需要两件事才能工作:租户模型(tenant model)和租户解析器(tenant resolver)。

您已经有了前者(您之前创建的 Tenant 类),因此在项目根目录中创建一个名为 CachingTenantResolver.cs 的新文件:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using SaasKit.Multitenancy;
using QuestionExchange.Models; namespace QuestionExchange
{
public class CachingTenantResolver : MemoryCacheTenantResolver<Tenant>
{
private readonly AppDbContext _context; public CachingTenantResolver(
AppDbContext context, IMemoryCache cache, ILoggerFactory loggerFactory)
: base(cache, loggerFactory)
{
_context = context;
} // Resolver runs on cache misses
protected override async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
var subdomain = context.Request.Host.Host.ToLower(); var tenant = await _context.Tenants
.FirstOrDefaultAsync(t => t.Domain == subdomain); if (tenant == null) return null; return new TenantContext<Tenant>(tenant);
} protected override MemoryCacheEntryOptions CreateCacheEntryOptions()
=> new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromHours(2)); protected override string GetContextIdentifier(HttpContext context)
=> context.Request.Host.Host.ToLower(); protected override IEnumerable<string> GetTenantIdentifiers(TenantContext<Tenant> context)
=> new string[] { context.Tenant.Domain };
}
}

ResolveAsync 方法完成了繁重的工作:给定传入请求,它会查询数据库并查找与当前域匹配的租户。

如果找到,它会将 TenantContext 传回给 SaasKit。

所有租户解析逻辑完全取决于您 - 您可以按子域、路径或任何其他您想要的方式分隔租户。

此实现使用 租户缓存策略(tenant caching strategy) <http://benfoster.io/blog/aspnet-core-multi-tenancy-tenant-lifetime>__,

因此您不会在每个传入请求上都使用租户查找来访问数据库。

第一次查找后,租户将被缓存两个小时(您可以将其更改为任何有意义的内容)。

准备好租户模型(tenant model)和租户解析器(tenant resolver)后,

打开 Startup 类并在 ConfigureServices 方法中的任何位置添加此行:

services.AddMultitenancy<Tenant, CachingTenantResolver>();

接下来,将此行添加到 Configure 方法中,在 UseStaticFiles 下方但在 UseMvc 上方

app.UseMultitenancy<Tenant>();

Configure 方法代表您的实际请求管道,因此顺序很重要!

更新视图

现在所有部分都已就绪,您可以开始在代码和视图中引用当前租户。

打开 Views/Home/Index.cshtml 视图并用这个标记替换整个文件:

@inject Tenant Tenant
@model QuestionListViewModel @{
ViewData["Title"] = "Home Page";
} <div class="row">
<div class="col-md-12">
<h1>Welcome to <strong>@Tenant.Name</strong></h1>
<h3>@Tenant.Description</h3>
</div>
</div> <div class="row">
<div class="col-md-12">
<h4>Popular questions</h4>
<ul>
@foreach (var question in Model.Questions)
{
<li>@question.Title</li>
}
</ul>
</div>
</div>

@inject 指令从 SaasKit 获取当前租户,并且

@model 指令告诉 ASP.NET Core,

此视图将由新模型类(您将创建)支持。

在 Models 目录中创建 QuestionListViewModel.cs 文件:


using System.Collections.Generic; namespace QuestionExchange.Models
{
public class QuestionListViewModel
{
public IEnumerable<Question> Questions { get; set; }
}
}

查询数据库

HomeController 负责渲染您刚刚编辑的索引视图。打开它并用这个替换 Index() 方法:

public async Task<IActionResult> Index()
{
var topQuestions = await _context
.Questions
.Where(q => q.Tenant.Id == _currentTenant.Id)
.OrderByDescending(q => q.UpdatedAt)
.Take(5)
.ToArrayAsync(); var viewModel = new QuestionListViewModel
{
Questions = topQuestions
}; return View(viewModel);
}

此查询获取此租户的最新五个问题(当然,现在只有一个问题)并填充视图模型。

对于大型应用程序,您通常会将数据访问代码放在 service 或 repository 层中,
并将其置于 controller 之外。 这只是一个简单的例子!

您添加的代码需要 _context_currentTenant,这在 controller 中尚不可用。

您可以通过以下方式提供这些向类添加构造函数:

public class HomeController : Controller
{
private readonly AppDbContext _context;
private readonly Tenant _currentTenant; public HomeController(AppDbContext context, Tenant tenant)
{
_context = context;
_currentTenant = tenant;
} // Existing code...

为避免编译器报错,请在文件顶部添加以下声明:

using Microsoft.EntityFrameworkCore;

测试应用程序

您添加到数据库的测试租户与(fake)域 bufferoverflow.localdboverflow.local 相关联。

您需要 编辑 hosts 文件 以在本地计算机上测试这些:

127.0.0.1 bufferoverflow.local
127.0.0.1 dboverflow.local

使用 dotnet run 或单击 Visual Studio 中的 Start 启动项目,

应用程序将开始侦听 localhost:5000 之类的 URL。

如果您直接访问该 URL,您将看到一个错误,因为您尚未设置任何 默认租户行为

相反,访问 http://bufferoverflow.local:5000

您将看到您的多租户应用程序的一个租户!

切换到 http://dboverflow.local:5000 查看其他租户。

添加更多租户现在只需在 tenants 表中添加更多行即可。

更多

探索 Python/Django 支持分布式多租户数据库,如 Postgres+Citus

ASP.NET Core + SaasKit + PostgreSQL + Citus 的多租户应用程序架构示例的更多相关文章

  1. Asp.Net Core WebAPI+PostgreSQL部署在Docker中

     PostgreSQL是一个功能强大的开源数据库系统.它支持了大多数的SQL:2008标准的数据类型,包括整型.数值值.布尔型.字节型.字符型.日期型.时间间隔型和时间型,它也支持存储二进制的大对像, ...

  2. ASP.NET Core:CMD命令行+记事本 创建Console程序和Web Application

    今天看了Scott关于ASP.NET Core的介绍视频,发现用命令行一步一步新建项目.添加Package.Restore.Build.Run 执行的实现方式,更让容易让我们了解.NET Core的运 ...

  3. Asp.Net Core中使用MongoDB的入门教程,控制台程序使用 MongoDB

    内容来源  https://blog.csdn.net/only_yu_yy/article/details/78882446 首先,创建一个.Net Core的控制台应用程序.然后使用NuGet导入 ...

  4. ASP.NET Core如何使用压缩中间件提高Web应用程序性能

    前言 压缩可以大大的降低我们Web服务器的响应速度,压缩从而提高我们网页的加载速度,以及节省一定的带宽. 何时使用相应压缩中间件 在IIS,Apache,Nginx中使用基于服务端的响应压缩技术.中间 ...

  5. asp.net core 开发的https证书服务-agilelabs.net

    创建证书-生成CSR(Certificate Sign Request): 填写证书基本信息 接下来我们就可以看到创建的证书签名请求信息(CSR): 为我们刚才创建的CSR签名: 签名的意思是说通过证 ...

  6. Docker & ASP.NET Core (5):Docker Compose

    第一篇:把代码连接到容器 第二篇:定制Docker镜像 第三篇:发布镜像 第四篇:容器间的连接 Docker Compose简介 Compose是一个用来定义和运行多容器Docker应用的工具.使用C ...

  7. Jexus 5.8.2 正式发布为Asp.Net Core进入生产环境提供平台支持

    Jexus 是一款运行于 Linux 平台,以支持  ASP.NET.PHP 为特色的集高安全性和高性能为一体的 WEB 服务器和反向代理服务器.最新版 5.8.2 已经发布,有如下更新: 1,现在大 ...

  8. CentOs7 +Jexus 5.8.2部署Asp.Net Core WebApi 1.0生产环境

    Jexus 是一款运行于 Linux 平台,以支持  ASP.NET.PHP 为特色的集高安全性和高性能为一体的 WEB 服务器和反向代理服务器.最新版 5.8.2 已经发布,有如下更新: 1,现在大 ...

  9. ASP.NET Core 中文文档 第三章 原理(1)应用程序启动

    原文:Application Startup 作者:Steve Smith 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay) ASP.NET Core 为你的应用程 ...

随机推荐

  1. Effective Java —— 覆盖equals时遵守通用约定

    本文参考 本篇文章参考自<Effective Java>第三版第十条"Obey the general contract when overriding equals" ...

  2. c语言实现两数交换的三种方法

    实现变量的值互相交换的三种不同方法 方法一:利用第三个变量来实现数值的交换 int tmp; tmp = a; a = b; b = tmp; 此方法直观,简易.不易出错,推荐使用 方法二:利用两个变 ...

  3. 知网上的硕士和博士论文怎么下载pdf格式

    文献管理使用的EndNote,阅读习惯使用Drawboard,在下载硕士和博士论文时在知网上只能下载caj格式,于是就想找一种能下载pdf的方式. 知乎中有篇文章介绍的如何下载pdf的方法,很管用也很 ...

  4. 从零开始画自己的DAG作业依赖图(四)--节点连线优化版

    概述 上个版本简单的连线在一些复杂场景,尤其层级比较多,连线跨层级比较多的情况下,会出现线条会穿过矩形的情况,这一讲就是在这个基础上,去优化这个连线. 场景分析 在下面几种情况下,简单版本的画法已经没 ...

  5. Array.fill()函数的用法

    ES6,Array.fill()函数的用法   ES6为Array增加了fill()函数,使用制定的元素填充数组,其实就是用默认内容初始化数组. 该函数有三个参数. arr.fill(value, s ...

  6. 关于表达式&& 和 || 有多项的时候的取值

    && 表达式只有两项的时候,如果表达式为false, 返回为false 的那一个 ,为true的时候    返回最后一个值 ||  只有两项的时候,返回为true 的那一个;都为fal ...

  7. Qt 实现配置 OpenCV 环境,并实现打开图片与调用摄像头

    一.说明 所用QT版本:5.9.1 电脑配置:win10,64位系统 调用的是编译好的:OpenCV-MinGW-Build-4.1.0(稍后放链接) 在大学期间,由于项目需求需要用到QT+openc ...

  8. 从零开始的 Hexo 生活(一)入门安装篇

    目录 前言 一.Hexo 是什么 1.什么是静态网站 2.为什么选择静态网站 3.为什么选择 Hexo 二.Markdown 是什么 1.为什么要学 Markdown 2.怎么学 Markdown 三 ...

  9. Java对象和多态

    Java对象和多态 (面向对象) 面向对象基础 面向对象程序设计(Object Oriented Programming) 对象基于类创建,类相当于一个模板,对象就是根据模板创建出来的实体(就像做月饼 ...

  10. Mysql集群搭建-实操

    集群安装--准备工作 官网地址 https://dev.mysql.com/doc/refman/5.7/en/mysql-cluster-install-linux-binary.html 一.环境 ...