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

      

      

  • 相关阅读:
    RUST实践.md
    redis.md
    opencvrust.md
    aws rds can't connect to mysql server on 'xx'
    Foundation ActionScript 3.0 With Flash CS3 And Flex
    Foundation Flash Applications for Mobile Devices
    Flash Mobile Developing Android and iOS Applications
    Flash Game Development by Example
    Actionscript 3.0 迁移指南
    在SWT中非UI线程控制界面
  • 原文地址:https://www.cnblogs.com/MeteorSeed/p/8446154.html
Copyright © 2011-2022 走看看