Citus是基于PsotgreSQL的扩展,用于切分PsotgreSQL的数据,非常简单地实现数据“切片(sharp)”。如果不使用Citus,则需要开发者自己实现分布式数据访问层(DDAL),实现路由和结果汇总等逻辑,借助Citus可简化开发,是开发者把精力集中在具体的业务逻辑上。

  对于多租户程序来说,Citus可以帮助企业对数据进行切片,相比于传统的数据管理方式,Citus更智能,操作更为简单,运维成本更低廉。下面演示Citus的简单使用。

Step 01 安装docker和docker-compose(以Docker方式部署Citus)

curl -sSL https://get.docker.com/ | shsudo usermod -aG docker $USER && exec sg docker newgrp `id -gn`
sudo systemctl start dockersudo curl -sSL https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-composesudo chmod +x /usr/local/bin/docker-compose

Step 02 安装并启动Citus 

  Citus有3个版本Citus Community,Citus Cloud(云端版), Citus Enterprise(支持HA等高级特性),本文使用Citus Community。

curl -sSLO https://raw.githubusercontent.com/citusdata/docker/master/docker-compose.ymldocker-compose -p citus up -d

Step 03 连接postgres

docker exec -it citus_master psql -U postgres

Step 04 设置数据库用户密码

postgres=# \password postgres          #给postgres用户设置密码
Enter new password:
Enter it again: 

Step 05 创建表

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);

Step 06 告知Citus如何对数据进行切片

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

Step 07 初始化数据

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());

Step 08 新建ASP.NET Core Web应用程序,并添加引用

  • 安装“Npgsql.EntityFrameworkCore.PostgreSQL”包

  Npgsql.EntityFrameworkCore.PostgreSQL:支持Entity Framework Core操作PostgreSQL。

  • 安装“SaasKit.Multitenancy”包

  SaasKit.Multitenancy:支持ASP.NET Core开发多租户应用。

Step 09 创建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; }

}

}

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; }

}

}

using System.Collections.Generic;

namespace QuestionExchange.Models

{

public class QuestionListViewModel

{

  public IEnumerable<Question> Questions { get; set; }

}

}

Step 10 创建数据上下文

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; }

/// <summary>

/// C# classes and properties are PascalCase by convention, but your Postgres tables and columns are lowercase (and snake_case).

/// The OnModelCreating method lets you override the default name translation and let Entity Framework Core know how to find

/// the entities in your database.

/// </summary>

/// <param name="modelBuilder"></param>

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));

}

}

}

Step 11 为SaaSKit实现解析器

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 };

}

}

Step 12 修改Startup.cs

using Microsoft.AspNetCore.Builder;

using Microsoft.AspNetCore.Hosting;

using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.DependencyInjection;

using QuestionExchange.Models;

namespace QuestionExchange

{

public class Startup

{

public Startup(IConfiguration configuration)

{

Configuration = configuration;

}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.

public void ConfigureServices(IServiceCollection services)

{

var connectionString = "Server=192.168.99.102;Port=5432;Database=postgres;Userid=postgres;Password=yourpassword;";

services.AddEntityFrameworkNpgsql()

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

services.AddMultitenancy<Tenant, CachingTenantResolver>();

services.AddMvc();

}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{

if (env.IsDevelopment())

{

app.UseDeveloperExceptionPage();

app.UseBrowserLink();

}

else

{

app.UseExceptionHandler("/Home/Error");

}

app.UseStaticFiles();

app.UseMultitenancy<Tenant>();

app.UseMvc(routes =>

{

routes.MapRoute(

name: "default",

template: "{controller=Home}/{action=Index}/{id?}");

});

}

}

}

Step 13 创建View和Controller

@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>

using Microsoft.AspNetCore.Mvc;

using Microsoft.EntityFrameworkCore;

using QuestionExchange.Models;

using System.Diagnostics;

using System.Linq;

using System.Threading.Tasks;

namespace QuestionExchange.Controllers

{

public class HomeController : Controller

{

private readonly AppDbContext _context;

private readonly Tenant _currentTenant;

public HomeController(AppDbContext context, Tenant tenant)

{

_context = context;

_currentTenant = tenant;

}

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);

}

public IActionResult About()

{

ViewData["Message"] = "Your application description page.";

return View();

}

public IActionResult Contact()

{

ViewData["Message"] = "Your contact page.";

return View();

}

public IActionResult Error()

{

return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });

}

}

}

Step 14 运行站点

  首先需要修改本地Hosts文件,添加:

127.0.0.1 bufferoverflow.local127.0.0.1 dboverflow.local

  运行cmd(命令行),输入以下命令,刷新DNS:

ipconfig /flushdns

  分别使用不同Url浏览站点,可以看到之前插入的测试数据在不同租户下显示不同:

  以上,简单演示了如何基于Citus开发多租户应用。此外,Citus还比较适合开发需要快速返回查询结果的应用(比如“仪表板”等)。

  本文演示的例子比较简单,仅仅是演示了使用Citus开发多租户应用的可能。具体实践中,还涉及到具体业务以及数据库切片技巧等。建议阅读微软的《Cloud Design Patterns Book》中的Sharding模式部分,以及Citus的官方技术文档。

参考资料:

  https://github.com/citusdata/citus

  https://www.citusdata.com/blog/2018/01/22/multi-tenant-web-apps-with-dot-net-core-and-postgres

  https://docs.citusdata.com/en/v7.1/aboutcitus/what_is_citus.html

原文地址:https://www.cnblogs.com/MeteorSeed/p/8446154.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

基于Citus和ASP.NET Core开发多租户应用相关推荐

  1. 基于ASP.Net Core开发的一套通用后台框架

    基于ASP.Net Core开发一套通用后台框架 写在前面 这是本人在学习的过程中搭建学习的框架,如果对你有所帮助那再好不过.如果您有发现错误,请告知我,我会第一时间修改. 知其然,知其所以然,并非重 ...

  2. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)

    基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五) 转载于:https://github.com/Meowv/Blog 上篇文章完成了文章详情页数据查询和清除缓存 ...

  3. 送福利 | 送书5本《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发

    <ASP.NET Core项目开发实战入门>从基础到实际项目开发部署带你走进ASP.NET Core开发. ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 ...

  4. dotnet watch+vs code提升asp.net core开发效率

    在园子中,已经又前辈介绍过dotnet watch的用法,但是是基于asp.net core 1.0的较老版本来讲解的,在asp.net core 2.0的今天,部分用法已经不太一样,所以就再写一篇文 ...

  5. asp开发工具_VSCode搭建完美的asp.net core开发环境,看完这篇就够了

    引言 由于.net core的全面跨平台,我也在之前的一篇文章中介绍了如何在深度Deepin操作系统上安装并搭建.net core的开发环境,当时介绍的是安装.net core和使用Rider.net ...

  6. 基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目

    基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目 转载于:https://github.com/Meowv/Blog 既然开发完成了,还是拿出来溜溜比较好,本篇是本 ...

  7. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(九)

    基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(九) 转载于:https://github.com/Meowv/Blog 终于要接近尾声了,上一篇基本上将文 ...

  8. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(八)

    基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(八) 转载于:https://github.com/Meowv/Blog 上一篇完成了标签模块和友情链接模块 ...

  9. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)

    基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七) 转载于:https://github.com/Meowv/Blog 上一篇完成了后台分类模块的所有功能 ...

最新文章

  1. 《 硬件创业:从产品创意到成熟企业的成功路线图》——导读
  2. synchronized同时对原子性、可见性、有序性的保证
  3. JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架...
  4. 浏览器返回错误汇总分析
  5. linux系统存储文件系统,Linux文件系统的深入分析
  6. lc滤波电路电感电容值选择_滤波电容如何选择
  7. 今天需要修复的bug
  8. java设置关闭计算机,java实现电脑定时关机的方法
  9. php手机端分页加载,移动端分页加载
  10. weblogic 解决线程阻塞
  11. 终结者:使用slf4j+log4j完美构建日志
  12. 冒泡排序_Python实现
  13. SAP在采购和销售中的税务处理-增值税
  14. 读书笔记之——《谷歌和亚马逊如何做产品》
  15. 格式化后如何恢复数据?
  16. 在linux服务器上安装git
  17. 在计算机教学过程当中,案例教学中计算机基础教学的运用论文
  18. 关于C++ Boost库的使用
  19. scribed 安装
  20. iCloud Drive,简单实用的苹果原生云存储

热门文章

  1. 10 个有关 String 的面试问题
  2. 阿里云如何实现海量短视频的极速分发?答案在这里!
  3. jQuery07源码 (3803 , 4299) attr() prop() val() addClass()等 : 对元素属性的操作
  4. LeetCode:Largest Number - 求整型数组中各元素可拼合成的最大数字
  5. 第五章 MyEclipse配置hadoop开发环境
  6. 在C#中使用SQLite
  7. [第二篇]如何在ASP.Net Core的生产环境中使用OAuth保护swagger ui
  8. Envoy实现.NET架构的网关(五)集成Redis实现限流
  9. 全部换新-微软复兴.NET,C#10 .NET6 VS2022各个强势!
  10. 通过Dapr实现一个简单的基于.net的微服务电商系统