【无私分享:ASP.NET CORE 项目实战(第十三章)】Asp.net Core 使用MyCat分布式数据库,实现读写分离

 

目录索引 

 

【无私分享:ASP.NET CORE 项目实战】目录索引

 

简介

 

  

  MyCat2.0版本很快就发布了,关于MyCat的动态和一些问题,大家可以加一下MyCat的官方QQ群:106088787。我们今天主要介绍一下,在我们的Asp.net Core中如何使用Mycat,这源于一个大神(Amamiya Yuuko)的分享,但是,这中间还是有少许的 坑 :

  首先,因为大神是比较忙的,而且主要分享关键技术,所以有些地方很简略,而往往这些简略的地方容易造成新手的困惑。

 

  其次,在尝试了N次失败后,我发现大神的代理程序是有问题的,具体是什么问题,我们待会详细解释,经过修改之后的代理程序,可以正常运行。

  因此,我觉得很有必要写这篇文章,一步一步搭建、测试、运行。

 

 

什么是MyCat?

 

   

  

  从定义和分类来看,它是一个开源的分布式数据库系统,是一个实现了MySQL协议的服务器,前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原生协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为N个小表,存储在后端MySQL服务器里或者其他数据库里。

 

  MyCat发展到目前的版本,已经不是一个单纯的MySQL代理了,它的后端可以支持MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储。而在最终用户看来,无论是那种存储方式,在MyCat里,都是一个传统的数据库表,支持标准的SQL语句进行数据的操作,这样一来,对前端业务系统来说,可以大幅降低开发难度,提升开发速度。

 

  此外,开发者可以根据不同的需求将表分配不同的数据节点,比如Table A存放在关系型数据库中(如MySQL),而Table B可能更适合NoSQL(如MongoDB),在MyCAT中开发者只需要简单配置,即可让MyCAT完成这一系列操作的路由。

 

   关于更多MyCat,大家可以参照官网:http://www.mycat.org.cn/

 

 

MyCat的优势

  

 

  • 基于阿里巴巴的开源项目Cobar,具备良好的稳定性、可靠性、优良的结构和优良的性能,拥有许多的项目案例。站在巨人的肩膀上,MyCAT将走的更远。
  • 广泛借鉴最好的开源项目和创新的理念,让这些融入MyCAT的基因,使MyCAT成为领先其他电商等类似的开源项目,甚至超过了一些企业级应用。
  • MyCAT技术团队的参与者都经历过至少五多年的项目经验,团队中包括一些高级软件工程师、架构师、DBA。由精英们组成的MyCAT技术团队将确保产品质量。
  • MyCAT是完全独立的社区,不依附于任何企业,遵循着完全开放、免费、开源的原则。它不像一些开源项目,重要的功能封闭在其商业产品,并使开源项目像一个装饰。
  • 支持超过60种的数据库作为数据节点,如MySQL、SQL Server、Oracle、MongoDB、DB2等

 

 

 

MyCat与ADO.NET

 

  由于MyCAT与MySQL协议有些许差异,开发者可能不能够直接使用Oracle官方提供的Mysql.Data(ADO层)来与MyCAT直接连接,因此有大神专门为.NET开发者设计了针对MyCAT优化过的ADO层驱动,即 Pomelo.Data.MyCat,开发者通过使用MyCatConnection、MyCatCommand类可以实现对MyCAT的连接与查询。

 

 

MyCat 与 Entity Framework Core

 

  由于MyCAT的协议与MySQL有所差异,导致了无论是Java平台下的Hibernate还是.NET下的Entity Framework都无法使用MySQL的provider直接来操作MyCAT。这点在EF中的Migrations以及Code First上表现的尤为明显,此外开发者不仅需要手动建库,还需要配置分片规则等,操作繁琐。

因此我们使用第三方专门为MyCAT量身打造的Entity Framework Core Provider,即 Pomelo.EntityFrameworkCore.MyCat

 

 安装Java8

 

  MyCat 需要 java cdk的支持,安装方法网上很多很详细,这里我们就不罗列了。

 

下载MyCat Server

 

  废话不多说,我们来开始我们的MyCat之旅:

  我们演示使用的服务器是:CentOS7 当然,Windows 也是支持的,我们以 CentOS为例。

 

  首先,我们在服务器安装 MySQL ,这里我安装的是 MySQL 5.7,关于 MySQL的安装,我们这里就不演示了,没有装过的朋友,网上有大片的详细安装步骤。值得注意的是,安装完MySQL之后,我们需要编辑 my.cnf 文件,将MySQL的默认编码设置为 UTF8 

 

  我们下载 MyCat Server :我是通过 wget XXXXXXXX;大家也可以直接下载下来,然后通过 FTP 传到服务器上。

 

  

 

  因为我们是Linux,所以我选择 linux.tar.gz,使用 Windows的朋友可以选择 win.tar.gz

  

  

  [root@bogon ~]# wget https://github.com/MyCATApache/Mycat-download/blob/master/1.5-RELEASE/Mycat-server-1.5.1-RELEASE-20160816173057-linux.tar.gz

 

  

 

   解压 MyCat Server 压缩包[root@bogon ~]# tar -xzvf Mycat-server-1.5.1-RELEASE-20160816173057-linux.tar.gz

 

  

 

  将文件移动至 /usr/local/mycat 目录下[root@bogon ~]# mv mycat /usr/local/mycat

   

  

 

  这样,我们的 MyCat Server,就完成了,我们来运行一下:

  打开mycat/bin目录,运行 ./mycat start ,通过 ./mycat status 查看 mycat的运行情况

 

  

 

  

  启动:./mycat start    重新启动:./mycat restart  停止:./mycat stop  状态:./mycat status

 

 

 

下载 运行代理程序 

 

  我们使用的代理程序是由 Amamiya Yuuko 写的 Pomelo.EntityFrameworkCore.MyCat.Proxy

  

  这个代理程序是由问题的,我们前面说过了,我们先来尝试一下,对于出现的问题和解决方式,我们再讲解。

 

  我们先下载这个代理程序,下在方法跟上面 MyCat Server 一样,可以使用多种方式,大家可以直接下载到本地,解压完成后,把解压的文件通过FTP上传至proxy目录,我这里还是使用 wget

 

  

 

  

  [root@bogon ~]# wget https://github.com/PomeloFoundation/Entity-Framework-Core-MyCat-Proxy/releases/download/1.0.0-alpha2/MyCat-Entity-Framework-Core-Proxy.1.0.0-alpha2-netcore100.zip

   

 

  创建一个目录 proxy ,将压缩包移动到此目录下

  [root@bogon ~]# mkdir proxy

  [root@bogon ~]# mv MyCat-Entity-Framework-Core-Proxy.1.0.0-alpha2-netcore100.zip proxy/MyCat-Entity-Framework-Core-Proxy.1.0.0-alpha2-netcore100.zip

 

  进入 proxy ,并解压 MyCat-Entity-Framework-Core-Proxy.1.0.0-alpha2-netcore100.zip 压缩包:

  [root@bogon ~]# cd proxy

    [root@bogon proxy]# unzip MyCat-Entity-Framework-Core-Proxy.1.0.0-alpha2-netcore100.zip

 

   编辑 config.json 文件,修改 MyCatRoot为"/usr/local/mycat"

  

 

 

 

安装.NET Core SDK for CentOS

 

  详见:【(第十章)】发布项目到 Linux 上运行 Core 项目

 

 

运行代理程序

  

  我们来运行一下代理程序:[root@bogon proxy]# dotnet Pomelo.EntityFrameworkCore.MyCat.Proxy.dll

 

  

 

  我们先不管在程序中是怎么实现MyCat的,这个后面我们再讲,我们先来测试一下这个代理程序,不先解决这个代理的问题,到后面都很难知道到底是程序出错还是服务器的问题还是代理的问题:

  我在VS2015的控制台里执行:Add-Migration  MySQL_Init(也可以使用命令:dotnet ef Migrations add  MySQL_Init

  

 

  出错了。。。这个错误是操作超时了,这极有可能是连接不上MyCat服务器,这个问题曾困扰了我一段时间,后来突然发现忽略了一个重要的东西,防火墙。。。

  我们的代理使用的是7066端口,我们来开启一下防火墙端口,并重启防火墙:

  [root@bogon ~]# firewall-cmd --zone=public --add-port=7066/tcp --permanent

   [root@bogon ~]# systemctl restart firewalld

  

 

   我们再来执行 Add-Migration  MySQL_Init

  

 

  创建数据库: Update-Database -Verbosedotnet ef database update

   

 

  

  创建成功,看似没有什么问题。

    我们来插入几条数据看下:出错了 Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.

   超时,服务器没有响应。这是怎么回事呢,我们通过 Navicat 来连接一下 MyCat(同样服务器防火墙要开启 8066 端口,默认MyCat 是8066 端口,方法同上):

  

 

  首先,我们来检查一下我们的MyCat状态:   

  

 

  MyCat Server 关闭了。。。我们重启:

  

  

  重启之后,总是自动关闭。这是什么原因?我们看下日志(/usr/local/mycat/logs/wrapper.log):

   

 

  这下看清楚了,是因为rule columns 有多个 values值,这个是什么东西呢,这是就是配置MyCat 规则的拆分的列的名称。

  我们来看下代理生成的rule(/usr/local/mycat/conf/rule.xml):

  

 

   也就是说:MyCat 这个拆分规则 只支持单个,但是我们的代理生成了多个,导致 MyCat Server 错误

  我这里修改一下,改成<columns>ID</columins>,MyCat 启动就没有问题了,但是问题来了,我这里有四个实体类,其中有三个的主键都是ID,但是有一个的主键是UserID,那么只根据ID拆分显然是不合理的

  我们来看下 schema (/usr/local/mycat/conf/schema.xml): 

   

 

  大家注意 table 标签的 rule 属性,都是 db_wkmvc_rule 跟 rule.xmltableRule 标签的 name 属性是对应的。也就是说,这个表,按照 rule=""的规则,根据columns的值(列名,这里是主键也就是ID或UserID)拆分数据。

 

  如何解决?

  我想到的方案就是,每个表一个单独的规则,每个规则的columns值就是当前这个表的主键。

  我们来看下代理程序的源码:

  根据方法名称,我们可以看出这两个方法应该就是修改 rule.xml 和 schema.xml

   

 

   我们来看下GenerateRuleXml()

  

 

  这一段就是生成xml内容的,为什么我们的 rule.xml 中的 columns 会生成 诸如:ID,ID,ID,UserID 呢?我们可以很清楚的看出 <columns>{ string.Join(",", s.Keys.First()) }</columns>

  在 foreach 循环 schema.xml 中 table 标签的时候,把他们的 Key (primaryKey) 通过 string.join 组合起来了,并且只生成了一个 tableRule 

  那么,按照我们的方案,应该是每个表(在 schema.xml 中体现就是 一个 table 标签),单独生成一个 规则。

  

  修改起来也很简单:

  我们把循环中的 ruleXml="" 改为 rulexml+="",然后把 <columns>{ string.Join(",", s.Keys.First()) }</columns> 改为当前表的 primaryKey :<columns>{ s.Keys.First() }</columns>

  

  这是修改后的完整的 public void GenerateRuleXml(List<MyCatTable> Schema, List<MyCatDataNode> DataNodes, string database, bool IsUnder16)

  public void GenerateRuleXml(List<MyCatTable> Schema, List<MyCatDataNode> DataNodes, string database, bool IsUnder16)
  {
    foreach(var s in Schema)
    {
      if (s.Keys.Count() != 1)
              continue;

      string ruleXml = "";
      string funcXml = "";
      if (IsUnder16)
      {
        ruleXml += $@"
              <tableRule name=""{ database }_{ s.TableName }_rule"">
              <rule>
              <columns>{ s.Keys.First() }</columns>
              <algorithm>{ database }_{ s.TableName }_func</algorithm>
              </rule>
              </tableRule>
              ";

        funcXml += $@"
              <function name=""{ database }_{ s.TableName }_func"" class=""org.opencloudb.route.function.PartitionByMod"">
              <property name=""count"">{ s.DataNodes.Count() }</property>
              </function>
              ";
      }
      else // 1.6+
      {

        ruleXml += $@"
              <tableRule name=""{ database }_{ s.TableName }_rule"">
              <rule>
              <columns>{ s.Keys.First() }</columns>
              <algorithm>{ database }_{ s.TableName }_func</algorithm>
              </rule>
              </tableRule>
              ";

        funcXml += $@"
              <function name=""{ database }_{ s.TableName }_func"" class=""io.mycat.route.function.PartitionByMod"">
              <property name=""count"">{ s.DataNodes.Count() }</property>
              </function>
              ";

      }

      using (var reader = XmlReader.Create(Path.Combine(MyCatRoot, "conf", "rule.xml"), new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }))
      {
        var xml = new XmlDocument();
        xml.Load(reader);
        var mycatRule = xml.ChildNodes.Cast<XmlNode>().Single(x => x.Name == "mycat:rule");
        foreach (var x in mycatRule.ChildNodes.Cast<XmlNode>().Where(x => (x.Name == "tableRule" && x.Attributes["name"].Value == $"{ database }_{ s.TableName }_rule") || (x.Name == "function" &&         x.Attributes["name"].Value == $"{ database }_{ s.TableName }_func")).ToList())
        mycatRule.RemoveChild(x);
        mycatRule.InnerXml = ruleXml + mycatRule.InnerXml;
        mycatRule.InnerXml += funcXml;
        reader.Dispose();
        File.WriteAllText(Path.Combine(MyCatRoot, "conf", "rule.xml"), PatchXml("rule", xml.OuterXml));
      }
    }
  }

 

  我们重新编译生成,发布出来上传至服务器,替换掉我们之前的代理程序。然后重新运行一下,我们再来看下 rule.xml schema.xml

  

  

 

   是不是按照我们的预期,我们看下MyCat Server的状态:

   

 

  MyCat 连接也没问题了

  

 

   (后记:关于这个代理的问题,已经提交给作者了,这个代理程序我测试有一段时间了,当时还是alpha1版本,写文章的时候,我看到 alpha2版本貌似改进了,但是源码没有修改。)

 

 

 

如何在Asp.net Core中使用 Mycat

 

  分两部分,第一部分就是最简洁的应用第二部分是我在项目中的应用,大家自行参考。

 

   第一部分,最简洁的应用(作者 Demo):

  

   首先,我们新建一个控制台程序

  

 

  并将 Pomelo.EntityFrameworkCore.MyCat 添加至 project.json 中(Nuget:Install-Package Pomelo.EntityFrameworkCore.MyCat -Pre

  

 

  创建一个Blog模型:  

  public class Blog
  {
    public long Id { get; set; }

    public string Title { get; set; }

    public string Content { get; set; }

    public DateTime Time { get; set; }

  }

  

  创建DbContext类,并重写DbContext中的OnConfiguring方法,配置数据节点以及MyCAT服务器信息

  public class SampleContext : DbContext
  {
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      base.OnConfiguring(optionsBuilder);

      optionsBuilder.UseMyCat("server=IP;port=8066;uid=test;pwd=test;database=blogs")
                .UseDataNode("IP", "blogs_1", "用户名", "密码")

              .UseDataNode("IP", "blogs_2", "用户名", "密码")

              .UseDataNode("IP", "blogs_3", "用户名", "密码");
    }
  }

  

  注意:

  .UseMyCat()中的 Server 是 MyCat 服务器的地址 Uid 和 Pwd 是 MyCat 的 用户名和密码,不要和数据的混了。 

  .UseDataNode()中的 Server 是节点数据库的地址 Uid 和 Pwd 是节点数据库的 用户名和密码

  如何查看 MyCat 的用户名和密码:

    打开mycat目录下的 conf 目录(我们这里是:/usr/local/mycat/conf)

     查看 server.xml 文件:

    

    

 

 

  上面插入一段,一般使用mycat的应该都知道这些文件分别是干什么的,我们这里啰嗦一下。

  我把整个的 Program.cs 内容给大家展示一下:

  

 

  然后,就不多说了,你可以使用控制台命令 Add-Migration 和 Update-database 也可以使用命令 dotnet ef migrations add 和 dotnet ef database update 执行迁移和其它操作了。

 

  

 

 

  第二部分,我在项目中的应用:

  

  首先,在配置文件(siteconfig.json)中添加一个是否使用 MyCat 的配置:

  

 

  然后,同样在配置文件(siteconfig.json)中添加数据库节点配置:

  

 

  在 【(第十二章)】添加对SqlServer、MySql、Oracle的支持 中,我们有个数据库选择类 DataBaseProvider.cs 

  

 

 

  这里面已经写了一个 _isUseMyCat 和 GetMyCatConnect(),是否使用 MyCat 这个 跟 数据库选择一样,我们从配置文件中读取这个配置返回即可:

  

 

  我们来重写一下 GetMyCatConnect 返回 MyCat 和 节点数据库连接的 DbContextOptionsBuilder

  

  public DbContextOptionsBuilder GetMyCatConnect(string connectionStr, DbContextOptionsBuilder _options)
  {
    _options.UseMyCat(connectionStr);

    List<MyCatDatabaseHost> hosts = new Services.ConfigServices.AppConfigurtaionServices().GetListAppSettings<MyCatDatabaseHost>("MyCatDatabaseHost");

    foreach(var host in hosts)
    {
      _options.UseDataNode(host.Host, host.Database, host.Username, host.Password, host.Port);
    }

    return _options;
  }

 

  对于读取配置的方法,我们在 【(第十二章)】添加对SqlServer、MySql、Oracle的支持 中已经完整的贴出。

 

  修改一下 Startup.cs

  

   

 

  这样,我们就实现了对 MyCat的支持和切换,当然,在实际项目中,我们可能不需要这些,使用什么,是否有必要使用 MyCat 这些可能是早就定好的,因为我这个是测试学习的项目,所以罗列了各种东西。

 

 

 

 

 

 

希望跟大家一起学习Asp.net Core 

刚开始接触,水平有限,很多东西都是自己的理解和翻阅网上大神的资料,如果有不对的地方和不理解的地方,希望大家指正!

虽然Asp.net Core 现在很火热,但是网上的很多资料都是前篇一律的复制,所以有很多问题我也暂时没有解决,希望大家能共同帮助一下!

 

原创文章 转载请尊重劳动成果 http://yuangang.cnblogs.com

 

posted @ 2016-09-02 16:09  果冻布丁喜之郎  阅读(18344)  评论(26编辑  收藏  举报