zoukankan      html  css  js  c++  java
  • 基于Citus和ASP.NET Core开发多租户应用

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

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

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

    curl -sSL https://get.docker.com/ | sh
    sudo usermod -aG docker $USER && exec sg docker newgrp `id -gn`
    sudo systemctl start docker
    
    sudo curl -sSL https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
    sudo 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.yml
    docker-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.local
    127.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

      

      

  • 相关阅读:
    AI 最小二乘法
    AI 前馈神经网络
    AI 雅可比(Jacobian)矩阵
    AI 递归神经网络(RNN)
    AI 循环神经网络(RNN)
    js操作地址栏
    JS如何判断是否为ie浏览器的方法(包括IE10、IE11在内)
    php实现的三个常用加密解密功能函数示例
    php 获取URL 各部分参数
    PHP实现队列及队列原理
  • 原文地址:https://www.cnblogs.com/MeteorSeed/p/8446154.html
Copyright © 2011-2022 走看看