百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

在ASP.NET Core微服务架构下使用数据库切分和扩展

bigegpt 2024-08-26 11:07 2 浏览

文链接:https://itnext.io/how-to-use-database-sharding-and-scale-an-asp-net-core-microservice-architecture-22c24916590f

微服务的一大优点是,它们可以独立扩展。本文展示了扩展一个微服务及其数据库的好处和挑战

您将创建一个示例应用程序并手动实现应用程序层分片。它展示了如何根据用例和数据模型选择分片Key。这有助于将相同的原理应用到具有集成扩展(如MongoDB等)的DBMS上。

1.用例和数据模型

示例应用程序由一个User和Post微服务组成。它们通过消息交流:

User微服务处理添加和修改用户。Post微服务处理查看和添加帖子。因为与Post微服务的交互要多得多,所以,当应用程序的负载增加时,Post微服务将成为第一个需要扩展的微服务。

作者的名字是PostService绑定上下文的一部分,因此也是Post微服务的一部分。在User微服务中添加和修改作者。User微服务在添加新用户或更改用户名时发送事件。

PostService的逻辑数据模型

用户可以分类写文章。他们还可以按类别阅读帖子,包括作者姓名。最新的帖子在上面。分类是固定的,很少改变。

基于这些用例,我决定按类别划分数据库分片:

2.实现微服务

创建解决方案并添加名为“PostService”的ASP.NET Core 5 Web API项目。禁用HTTPS并激活OpenAPI支持。

安装以下NuGet软件包:

  • Microsoft.EntityFrameworkCore.Tools
  • MySql.EntityFrameworkCore
  • Newtonsoft.Json

创建实体

Post实体的索引可以加快检索某个类别中最新的帖子:

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;

namespace PostService.Entities
{
    [Index(nameof(PostId), nameof(CategoryId))]
    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int UserId { get; set; }
        public User User { get; set; }

        [Required]
        public string CategoryId { get; set; }
        public Category Category { get; set; }
    }
}

User实体中的版本稍后将帮助处理无序消息:

namespace PostService.Entities
{
    public class User
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Version { get; set; }
    }
}

namespace PostService.Entities
{
    public class Category
    {
        public string CategoryId { get; set; }
    }
}

创建PostServiceContext

using Microsoft.EntityFrameworkCore;

namespace PostService.Data
{
    public class PostServiceContext : DbContext
    {
        private readonly string _connectionString;

        public PostServiceContext(string connectionString)
        {
            _connectionString = connectionString;
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseMySQL(_connectionString);
        }

        public DbSet<PostService.Entities.Post> Post { get; set; }
        public DbSet<PostService.Entities.User> User { get; set; }
        public DbSet<PostService.Entities.Category> Category { get; set; }
    }
}

在appsettings.Development.json中添加连接字符串(在调试期间将使用两个分片)

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "PostDbConnectionStrings": {
    "Shard0": "server=localhost; port=3310; database=post; user=root; password=pw; Persist Security Info=False; Connect Timeout=300",
    "Shard1": "server=localhost; port=3311; database=post; user=root; password=pw; Persist Security Info=False; Connect Timeout=300"    
  }
}

添加DataAccess代码

GetConnectionString(string category)计算CategoryId的哈希值。哈希的第一部分将配置的分片数(连接字符串)取模,从而确定给定类别的分片。

InitDatabase删除并重新创建所有分片中的所有表,并插入虚拟用户和类别。

其他方法用于创建和加载帖子。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using PostService.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace PostService.Data
{
    public class DataAccess
    {
        private readonly List<string> _connectionStrings = new List<string>();

        public DataAccess(IConfiguration configuration)
        {
            var connectionStrings = configuration.GetSection("PostDbConnectionStrings");
            foreach(var connectionString in connectionStrings.GetChildren())
            {
                Console.WriteLine("ConnectionString: " + connectionString.Value);
                _connectionStrings.Add(connectionString.Value);
            }
        }

        public async Task<ActionResult<IEnumerable<Post>>> ReadLatestPosts(string category, int count)
        {
            using var dbContext = new PostServiceContext(GetConnectionString(category));
            return await dbContext.Post.OrderByDescending(p => p.PostId).Take(count).Include(x => x.User).Where(p => p.CategoryId == category).ToListAsync();
        }

        public async Task<int> CreatePost(Post post)
        {
            using var dbContext = new PostServiceContext(GetConnectionString(post.CategoryId));
            dbContext.Post.Add(post);
            return await dbContext.SaveChangesAsync();
        }

        public void InitDatabase(int countUsers, int countCategories)
        {
            foreach (var connectionString in _connectionStrings)
            {
                using var dbContext = new PostServiceContext(connectionString);
                dbContext.Database.EnsureDeleted();
                dbContext.Database.EnsureCreated();
                for (int i = 1; i <= countUsers; i++)
                {
                    dbContext.User.Add(new User { Name = "User" + i, Version = 1 });
                    dbContext.SaveChanges();
                }
                for (int i = 1; i <= countCategories; i++)
                {
                    dbContext.Category.Add(new Category { CategoryId = "Category" + i });
                    dbContext.SaveChanges();
                }
            }
        }

        private string GetConnectionString(string category)
        {
            using var md5 = MD5.Create();
            var hash = md5.ComputeHash(Encoding.ASCII.GetBytes(category));
            var x = BitConverter.ToUInt16(hash, 0) % _connectionStrings.Count;
            return  _connectionStrings[x];
        }
    }
}

在Startup.cs中将DataAccess注册为单例

public class Startup
{
    ...

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "PostService", Version = "v1" });
        });

        services.AddSingleton<DataAccess>();
    }

    ---

创建PostController

它使用DataAccess类

using Microsoft.AspNetCore.Mvc;
using PostService.Data;
using PostService.Entities;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace PostService.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PostsController : ControllerBase
    {
        private readonly DataAccess _dataAccess;

        public PostsController(DataAccess dataAccess)
        {
            _dataAccess = dataAccess;
        }

        [HttpGet]
        public async Task<ActionResult<IEnumerable<Post>>> GetLatestPosts(string category, int count)
        {
            return await _dataAccess.ReadLatestPosts(category, count);
        }

        [HttpPost]
        public async Task<ActionResult<Post>> PostPost(Post post)
        {
            await _dataAccess.CreatePost(post);
            return NoContent();
        }

        [HttpGet("InitDatabase")]
        public void InitDatabase([FromQuery] int countUsers, [FromQuery] int countCategories)
        {
            _dataAccess.InitDatabase(countUsers, countCategories);
        }
    }
}

3. 用PostService访问数据库

安装Docker Desktop。

创建两个MySql容器

C:\dev>docker run -p 3310:3306 --name=mysql1 -e MYSQL_ROOT_PASSWORD=pw -d mysql:5.6
C:\dev>docker run -p 3311:3306 --name=mysql2 -e MYSQL_ROOT_PASSWORD=pw -d mysql:5.6

在Visual Studio中启动Post服务。浏览器在打开http://localhost:5001/swagger/index.html

使用swagger UI与服务交互:

初始化包含100个用户和10个类别的数据库:

在“Category1”下增加一个帖子:

{
  "title": "MyTitle",
  "content": "MyContent",
  "userId": 1,
  "categoryId": "Category1"
}

阅读“Category1”中排名前10位的帖子:

连接到数据库容器并验证哪个数据库包含新的帖子

C:\dev>docker container exec -it mysql1 /bin/sh

使用密码“pw”登录MySql并读取帖子:

第二个实例不包含任何帖子:

C:\dev>docker container exec -it mysql2 /bin/sh

4.最后的想法和展望

您创建了一个可工作的应用程序,实现了应用程序层分片,并使用了分片Key的概念。

这只是一个示例应用程序。您必须调整代码才能在生产环境中使用它。

在第二部分中,您将缩放并运行微服务和数据库的多个容器实例。您将使用docker compose和负载平衡器。然后,您将运行JMeter负载测试,以查看应用程序在使用不同数量的实例时是如何伸缩的。最后,您将通过RabbitMQ模拟来自用户微服务的用户事件。

相关推荐

Docker篇(二):Docker实战,命令解析

大家好,我是杰哥上周我们通过几个问题,让大家对于Docker有了一个全局的认识。然而,说跟练往往是两个概念。从学习的角度来说,理论知识的学习,往往只是第一步,只有经过实战,才能真正掌握一门技术所以,本...

docker学习笔记——安装和基本操作

今天学习了docker的基本知识,记录一下docker的安装步骤和基本命令(以CentOS7.x为例)一、安装docker的步骤:1.yuminstall-yyum-utils2.yum-con...

不可错过的Docker完整笔记(dockerhib)

简介一、Docker简介Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,...

扔掉运营商的 IPTV 机顶盒,全屋全设备畅看 IPTV!

其实现在看电视节目的需求确实大大降低了,折腾也只是为了单纯的让它实现,享受这个过程带来的快乐而已,哈哈!预期构想家里所有设备直接接入网络随时接收并播放IPTV直播(电信点播的节目不是太多,但好在非常稳...

第五节 Docker 入门实践:从 Hello World 到容器操作

一、Docker容器基础运行(一)单次命令执行通过dockerrun命令可以直接在容器中执行指定命令,这是体验Docker最快捷的方式:#在ubuntu:15.10容器中执行ech...

替代Docker build的Buildah简单介绍

Buildah是用于通过较低级别的coreutils接口构建OCI兼容镜像的工具。与Podman相似,Buildah不依赖于Docker或CRI-O之类的守护程序,并且不需要root特权。Builda...

Docker 命令大全(docker命令大全记录表)

容器生命周期管理run-创建并启动一个新的容器。start/stop/restart-这些命令主要用于启动、停止和重启容器。kill-立即终止一个或多个正在运行的容器rm-于删除一个或...

docker常用指令及安装rabbitMQ(docker安装rabbitmq配置环境)

一、docker常用指令启动docker:systemctlstartdocker停止docker:systemctlstopdocker重启docker:systemctlrestart...

使用Docker快速部署Storm环境(docker部署confluence)

Storm的部署虽然不是特别麻烦,但是在生产环境中,为了提高部署效率,方便管理维护,使用Docker来统一管理部署是一个不错的选择。下面是我开源的一个新的项目,一个配置好了storm与mono环境的D...

Docker Desktop安装使用指南:零基础教程

在之前的文章中,我多次提到使用Docker来安装各类软件,尤其是开源软件应用。鉴于不少读者对此有需求,我决定专门制作一期关于Docker安装与使用的详细教程。我主要以Macbook(Mac平台)为例进...

Linux如何成功地离线安装docker(linux离线安装httpd)

系统环境:Redhat7.2和Centos7.4实测成功近期因项目需要用docker,所以记录一些相关知识,由于生产环境是不能直接连接互联网,尝试在linux中离线安装docker。步骤1.下载...

Docker 类面试题(常见问题)(docker面试题目)

Docker常见问题汇总镜像相关1、如何批量清理临时镜像文件?可以使用sudodockerrmi$(sudodockerimages-q-fdanging=true)命令2、如何查看...

面试官:你知道Dubbo怎么优雅上下线的吗?你:优雅上下线是啥?

最近无论是校招还是社招,都进行的如火如荼,我也承担了很多的面试工作,在一次面试过程中,和候选人聊了一些关于Dubbo的知识。Dubbo是一个比较著名的RPC框架,很多人对于他的一些网络通信、通信协议、...

【Docker 新手入门指南】第五章:Hello Word

适合人群:完全零基础新手|学习目标:30分钟掌握Docker核心操作一、准备工作:先确认是否安装成功打开终端(Windows用户用PowerShell或GitBash),输入:docker--...

松勤软件测试:详解Docker,如何用portainer管理Docker容器

镜像管理搜索镜像dockersearch镜像名称拉取镜像dockerpullname[:tag]列出镜像dockerimages删除镜像dockerrmiimage名称或id删除...