博客
关于我
AppBoxFuture(七): 分布式外键约束
阅读量:398 次
发布时间:2019-03-05

本文共 2674 字,大约阅读时间需要 8 分钟。

存储引擎外键约束设计与实现

关系数据库与NoSQL的区别

关系数据库与NoSQL数据库的主要区别之一是外键约束的支持。尽管现代大厂在设计数据存储结构时通常不使用外键约束,转而依赖业务逻辑来保证数据完整性,但为了确保关键业务数据的完整性,我们在存储引擎层面实现了外键约束功能。

实现思路

由于存储引擎是分布式的,引用者与被引用者可能分布在不同的节点上(例如订单数据在节点1上,订单引用的产品数据在节点2上)。传统关系数据库的外键约束难以直接应用于分布式环境,因此我们设计了以下存储结构:

  • 引用索引:在RocksDB中划分一个ColumnFamily存储引用索引,记录引用者与被引用者的关系。
  • 被引用者计数器:存储被引用者的计数器,记录哪个分区引用了它,被引用了多少次。
  • 通过分布式事务,我们确保了数据与引用索引及计数器的一致性。

    插入订单

    插入操作时,存储引擎会根据实体模型元数据检查是否存在EntityRef成员。如果存在,则在同一事务内向被引用者的分区发送AddRefCommand。该命令会锁定目标记录,判断是否存在相应记录。如果不存在,则通知事务回滚。如果是同一事务内插入产品再插入订单,AddRefCommand会检测同一事务内是否存在被引用者记录。事务提交时,原子性地保存引用索引与引用计数。

    删除产品

    删除操作时,存储引擎会先检查当前记录的所有分区引用计数是否为0。如果不为0,则通知事务回滚。

    更新或删除订单

    如果引用产品发生变更,存储引擎会删除旧引用索引,并添加新引用索引;如果引用产品设为Null或删除订单,存储引擎会删除引用索引,同时通知产品分区更新引用计数。

    并发优化

    由于存储引擎的分布式事务是基于2PL协议的,如果有大量事务同时插入订单且引用同一产品,会导致事务排队执行,影响并发性能。为此,我们引入了一个优化措施,允许不同事务的AddRefCommand共享锁定被引用者,以提高并发性能。通过测试,我们发现单节点Debug模式下,带外键引用的并发插入性能为14000TPS,而不带外键引用的并发插入性能为28000TPS。

    测试

    测试引用至不存在的目标

    public async Task
    Test1()
    {
    var ou = new Entities.OrgUnit();
    ou.Name = "Name";
    ou.CreateById = Guid.Empty; // 指向不存在的目标
    await EntityStore.SaveAsync(ou);
    return "Done.";
    }

    调用此方法会返回“Insert error: ForeignKeyConstraint”,即违反了外键约束。

    测试同一事务插入

    public async Task
    Test2()
    {
    var txn = await Transaction.BeginAsync();
    try
    {
    // 先新建并保存被引用者
    var emp = new Entities.Emploee();
    emp.Name = "Batch name";
    emp.Account = emp.Name;
    emp.Birthday = new DateTime(1977, 3, 16);
    await EntityStore.SaveAsync(emp, txn);
    // 再新建并保存引用者
    var ou = new Entities.OrgUnit();
    ou.Name = "Batch ou";
    ou.CreateById = emp.Id;
    await EntityStore.SaveAsync(ou, txn);
    await txn.CommitAsync();
    }
    catch (Exception ex)
    {
    txn.Rollback();
    return $"Failed: {ex.Message}";
    }
    return "Done.";
    }

    调用此方法将返回"Done.",此时可以打开Emploee及OrgUnit的模型设计器内的"Data"栏,验证插入的数据是否正确。

    测试同一事务删除

    public async Task
    Delete()
    {
    var q1 = new TableScan
    ();
    q1.Filter(t => t.Name == "Batch ou");
    var ous = await q1.ToListAsync();
    var q2 = new TableScan
    ();
    q2.Filter(t => t.Name == "Batch name");
    var emps = await q2.ToListAsync();
    var txn = await Transaction.BeginAsync();
    try
    {
    // 先删除引用者
    await EntityStore.DeleteAsync(ous[0], txn);
    // 再删除被引用者
    await EntityStore.DeleteAsync(emps[0], txn);
    await txn.CommitAsync();
    }
    catch (Exception ex)
    {
    txn.Rollback();
    return $"Failed: {ex.Message}";
    }
    return "Done.";
    }

    调用此方法将返回"Done.",此时可以打开Emploee及OrgUnit的模型设计器内的"Data"栏,验证数据已被删除。

    小结

    本文介绍了框架集成的存储引擎如何用另类方式实现外键约束。目前已更新可测试。如果您有问题或发现Bug,请留言或提交Issue。

    转载地址:http://jfozz.baihongyu.com/

    你可能感兴趣的文章
    Nginx代理websocket配置(解决websocket异常断开连接tcp连接不断问题)
    查看>>
    Nginx代理初探
    查看>>
    nginx代理地图服务--离线部署地图服务(地图数据篇.4)
    查看>>
    Nginx代理外网映射
    查看>>
    Nginx代理模式下 log-format 获取客户端真实IP
    查看>>
    Nginx代理静态资源(gis瓦片图片)实现非固定ip的url适配网络环境映射ip下的资源请求解决方案
    查看>>
    Nginx代理静态资源(gis瓦片图片)实现非固定ip的url适配网络环境映射ip下的资源请求解决方案
    查看>>
    Nginx反向代理与正向代理配置
    查看>>
    Nginx反向代理是什么意思?如何配置Nginx反向代理?
    查看>>
    nginx反向代理解决跨域问题,使本地调试更方便
    查看>>
    nginx启动脚本
    查看>>
    Nginx在Windows下载安装启动与配置前后端请求代理
    查看>>
    Nginx多域名,多证书,多服务配置,实用版
    查看>>
    nginx开机启动脚本
    查看>>
    nginx异常:the “ssl“ parameter requires ngx_http_ssl_module in /usr/local/nginx/conf
    查看>>
    nginx总结及使用Docker创建nginx教程
    查看>>
    nginx报错:the “ssl“ parameter requires ngx_http_ssl_module in /usr/local/nginx/conf/nginx.conf:128
    查看>>
    nginx报错:the “ssl“ parameter requires ngx_http_ssl_module in usrlocalnginxconfnginx.conf128
    查看>>
    nginx日志分割并定期删除
    查看>>
    Nginx日志分析系统---ElasticStack(ELK)工作笔记001
    查看>>