Adhesive框架系列文章--Mongodb数据服务模块使用(上)

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
日志服务 SLS,月写入数据量 50GB 1个月
简介: 之前介绍的应用程序信息中心模块中所有日志、异常、性能和状态数据都依赖Mongodb数据服务,Mongodb数据服务的接口也简单的可以: public interface IMongodbInsertService : IDisposable { void Insert(object item); } 总之一点,不管什么数据,提交进来即可!在提交之前所要做的只是设计数据实体 ,并且为实体标注一些元数据Attribute,来告诉Mongodb数据服务,希望怎么样保存数据,又希望怎么样呈现数据。

之前介绍的应用程序信息中心模块中所有日志、异常、性能和状态数据都依赖Mongodb数据服务,Mongodb数据服务的接口也简单的可以:

    public interface IMongodbInsertService : IDisposable
    {
        void Insert(object item);
    }

总之一点,不管什么数据,提交进来即可!在提交之前所要做的只是设计数据实体 ,并且为实体标注一些元数据Attribute,来告诉Mongodb数据服务,希望怎么样保存数据,又希望怎么样呈现数据。由于Mongodb数据服务的使用者就是开发人员,因此在本文使用篇里会站在程序的角度详细介绍这些Attribute,后一篇文章会详细介绍一下Mongodb数据服务的通用后台。

我们首先来看一下定义在类上的MongodbPersistenceEntity特性,它定义了实体相关的配置:

image

这里首先要注意一点,所谓定义在类上是定义在直接作为提交对象的根类上的,如果一个类内嵌了子类,那么在子类上定义这个特性是没什么意义的。分别介绍一下每一个参数。

首先是CategoryName:必填,并且不能包含.和_。通过Mongodb数据后台我们来看一下分类名的作用:

image

比如我们有大量的业务和非业务数据,混合在一起查看显然不方便,那么我们可以定义Aic分类、State分类,把相关的数据进行一个归类。比如这里点击了Aic分类可以看到:

image

这里的中文名就是DisplayName而实际数据库的名字就是Name,如果不定义Name的话就是类名,如果不定义DisplayName的话就是Name,因此Name和DisplayName都不是必填的。再看一下LogInfo的定义对比一下:

[MongodbPersistenceEntity("Aic", DisplayName = "日志", Name = "Log")]

在数据库中的数据库名是以CategoryName+两个下划线+Name构成的,并且进行了按月分库:

image

按月份分库可以减少每一个数据库的数据量,也可以方便独立删除和备份每一个库来方便运维。

最后来看看ExpireDays的配置,它可以配置数据在多久后过期被自动删除。如果我们的日志数据只需要保留一个年,那么只需要设置为365天即可。1年之后过期(取决于数据中时间列的时间)的日志数据将会被删除,可以为不同的类型配置不同的过期时间,这样就方便了运维。

 

接下来看一下定义在属性上的MongodbPersistenceItem特性,它定义了设计到持久化的一些配置:

image

在这里为了方便,我们要求所有需要保存到数据库中的数据项必须是属性。来解释一下每一个参数,这里每一个参数都是可选的:

1、ColumnName:实际保存在数据库中的列名。如果回顾一下前一篇文章的话,可以发现,为了减少数据的大小,我们为很多列都定义了ColumnName使用一个简洁的表示,比如日志消息:

image

因为我们知道Mongodb是采用类Json来存储数据,每一个数据项都会保存列名,所以列名越短,占用存储空间越小。如果省略的话就用属性名作为列名了。

2、IsContextIdentityColumn:这是一个很有趣的设计。在有的时候我们在一个请求中会提交很多数据,比如2条程序日志,1条已处理异常,2条业务日志,1条Wcf客户端调用日志,1条Wcf客户端消息日志。有没有什么办法把它们联系起来?使得我们在看到一条日志的时候才能去查看其它日志?可以使用一个列并标注为上下文标识列,这样的话,这些数据可以在后台统一查看,比如应用程序信息中心的AbstractInfo中就有这么一列:

image

比如在后台,我们可以直接根据一个上下文ID来查询所有关联的数据:

image

在这里可以看到,这一条日志的相同请求中还产生了一个异常、一个慢请求的数据、一个Wcf客户端异常、一个Wcf客户端消息、一个Wcf服务端异常以及一个Wcf服务端消息(应该是FaultMessage):

image

整个请求中发生的数据记录都串接在一起了,是不是非常直观?

3、IsIgnore:是否忽略,也就是是否不保存到数据库中去。还记得之前介绍过的代码性能测量的实体吗:

image

在这里,我们的sw和threadTime只是用作计算的临时数据,不需要保持到数据库中去,因此设置为忽略。

4、IsPrimaryKey:是否是主键。定义主键很重要,否则系统就不知道在查看详细信息的时候使用哪个列。一个类型必须有且仅有一个主键。在了解了所有特性之后会统一介绍配置的约束。

5、IsTableName:是否是表名列。如果这个列是表名列,那么这个列数据会作为分表的依据。往往我们会把业务名或应用程序名作为表名实现进一步的拆分(首先是通过分类+数据类型+时间确定数据库,然后是通过TableName列确定表的拆分),如果不定义的话就会使用General作为表名也就是所有数据都存在一个表中。在后台,我们也是选择了数据库再选择表的:

image

对应到数据库中就是:

image

当然,数据库中的按照年月分库对于用户来说是透明的。

6、IsTimeColumn:是否是时间列。时间列决定了分库,也决定了统计视图下(之后我们会介绍呈现数据的几种视图)时间轴依赖的列。由于一个类中可以有多个时间列,所以必须显式标注一个时间列,这个时间列会作为分库的依据。

7、MongodbIndexOption:列索引的方式,有这么几种:

    public enum MongodbIndexOption
    {
        /// <summary>
        /// 不作索引
        /// </summary>
        None = 0,
        /// <summary>
        /// 递增索引
        /// </summary>
        Ascending = 1,
        /// <summary>
        /// 递减索引
        /// </summary>
        Descending = 2,
        /// <summary>
        /// 唯一的递增索引
        /// </summary>
        AscendingAndUnique = 3,
        /// <summary>
        /// 唯一的递减索引
        /// </summary>
        DescendingAndUnique = 4,
    }

主要有唯一和不唯一两种。比如主键就需要是唯一的,如果主键有重复,这条数据将被删除。而比如一些需要搜索和排序的字段,它建立的索引就不能是唯一的了。至于索引的方向取决于排序的方向,比如我们往往是按照日期倒序查找数据的,那么这个列的索引就应该设置为递减索引,否则效果会大打折扣。

 

最后再来看一下同样定义在属性上的MongodbPresentationItemAttribute特性,它决定了列在后台如何呈现:

image

1、Description:列的描述信息。在详细数据显示页面上会显示这个信息。

2、DisplayName:列的友好显示名。比如我们可以用一个中文来定义列的显示名:

image

这是最最常用的。

3、ShowInTableView:是否显示在列表视图中,稍候会介绍每一种视图。

image

4、MongodbCascadeFilterOption,这是一个枚举,定义如下:

    public enum MongodbCascadeFilterOption
    {
        /// <summary>
        /// 不作为级联过滤
        /// </summary>
        None = 0,
        /// <summary>
        /// 级联过滤的第一级
        /// </summary>
        LevelOne = 1,
        /// <summary>
        /// 级联过滤的第二级
        /// </summary>
        LevelTwo = 2,
        /// <summary>
        /// 级联过滤的第三级
        /// </summary>
        LevelThree = 3,
    }

对于一个数据实体平面(不能是作为属性的子类,但可以是派生类)上的所有属性,可以把其中三个设置为具有三级联动关系的分级,比如AbstractInfo中的大类和小类:

image

这样,用户就可以使用级联方式进行数据的筛选,后台效果如下:

image

5、MongodbFilterOption,这也是一个枚举,定义如下:

    public enum MongodbFilterOption
    {
        /// <summary>
        /// 不作为过滤条件
        /// </summary>
        None = 0,
        /// <summary>
        /// 下拉框单选过滤
        /// </summary>
        DropDownListFilter = 1,
        /// <summary>
        /// 复选框多选过滤
        /// </summary>
        CheckBoxListFilter = 2,
        /// <summary>
        /// 文本框搜索过滤
        /// </summary>
        TextBoxFilter = 3,
    }

这个设置决定了列可以以单选、多选或是文本方式来搜索。比如在应用程序信息中心中,我们的ExtraInfo中就有这样的一些过滤列:

image

后台效果如下:

image

6、MongodbSortOption:它决定了在列表视图中数据呈现时的排序规则,比如我们定义应用程序信息中心中的所有数据是按照日期倒序的:

image

在后台查看数据的时候看看是不是这样排序了:

image

 

之前介绍了ShowInTableView的设置,表明列是显示在列表视图中的。在这里介绍一下定义的几种后台呈现数据的视图:

1、以表格方式呈现多条数据,既然是表格,肯定不可能显示几百个列的数据,因此显示在这里的数据一般而言是精挑细选的数据,或是过滤数据,比如我们可以看一下对于一条LogInfo我们显示哪些数据:

image

在这里可以看到,除了日志消息之外,其它数据都是做了索引的过滤数据。在表格中显示这些信息足够了,在上一篇文章中列出了AbatractInfo和LogInfo的定义,可以对比一下。还可以看到由于配置了DisplayName,表格头的显示都是中文。

2、以曲线统计图方式呈现数据。如下图:

image

在这里,我们查询一周的数据量的统计,其中每一个点是一小时。这样的数据量统计对于以下场景很有用:

1)比如业务数据,我们需要浏览业务量的变化。

2)比如监控数据,我们需要浏览某种日志(比如异常日志)的数据量会不会很多。

并且需要说明的是,这里一个图上可以显示多个表的统计数据,我们如果把表名想像成业务类型或是任何其它类型的话可以进行类比统计了。

3、以分组方式进行统计并以饼图方式进行呈现,如下图:

image

比如我们这里希望知道刚才那个高峰中日志数据中到底是什么数据居多。点击进入分组统计可以看到,大部分数据都是192.168.134.187这个IP产生的错误日志。此时就一下子可以把问题定位到这个服务器上。为什么其它服务器不出问题而唯独这个服务器出现问题了?很有可能不是程序问题,而是这个服务器特有的问题,比如磁盘满了,内存满了。那么为什么会有这些分组呢?系统自动对哪些数据进行分组统计,后面会介绍。

4、状态视图。所谓状态视图就是针对状态数据的。这类数据有一个特点,就是每一条数据的内容其实是差不多的,都是反映一个状态,只不过最新的数据就是最新的状态。我们往往会查看最新的一条数据,也就是XXX应用当前的状态,并且如果这条数据很大的话,我们希望滚动条始终停留在我们关注的那些列的位置上(稍候可以看到,我们以TreeView呈现详细数据)。

5、除了上面几种直接可以选择的视图,还可以从列表视图点击进入详细视图,如下图:

image

在详细视图中可以看到这里显示的列名都是DisplayName而不是实际数据库中存储的列名,在实现篇中会介绍,我们把元数据和实际数据分开保存在数据库中,在查询数据的时候再进行组合。

 

现在再回到MongodbPersistenceItem和MongodbPresentationItem来,对于这些配置,是有一些规则的:

1、作为上下文标识的列不能超过一个

2、作为表名的列不能超过一个、不能有索引,也不能定义作为单选、多选等过滤列

3、必须有并且只有一个主键列

4、必须有并且只有一个时间列

5、排序列不能超过两个

6、主键列必须具有唯一索引

7、时间列必须具有不唯一索引,并且类型必须为DateTime

8、排序列和上下文标识列必须具有不唯一索引

9、作为搜索条件的列必须具有索引(否则性能会很差)

 

最后,在这里说明一下对于复杂的实体使用上的一些规则。首先,Mongodb数据服务是支持列表和字典属性的,并且还支持自定义的数据。比如简单类型的:

        [MongodbPersistenceItem(ColumnName = "DictionaryColumn22")]
        [MongodbPresentationItem(DisplayName = "Test.DictionaryColumn", Description = "子类中的字典列")]
        public Dictionary<int, string> DictionaryColumn2 { get; set; }

        [MongodbPersistenceItem(ColumnName = "ListColumn22")]
        [MongodbPresentationItem(DisplayName = "Test.ListColumn", Description = "子类中的列表列")]
        public List<int> ListColumn2 { get; set; }

原始数据是这样的:

 ListColumn2 = Enumerable.Range(1, 2).ToList(),
                            DictionaryColumn2 = new Dictionary<int, string> { { 1, "x" }, { 2, "y" } },

前者在后台呈现时候的样子是这样的:

image

对于列表列,我们使用列名+类型名+0、1、2这样的索引号来表示列名,而对于字典,我们使用列名+类型名+Key来表示列名。

再来看一个自定义类型的例子:

        [MongodbPersistenceItem(ColumnName = "ExtListColumn11")]
        [MongodbPresentationItem(DisplayName = "TestBase.ExtListColumn", Description = "基类中的扩展列表列")]
        public List<ExtItem> ExtListColumn1 { get; set; }

        [MongodbPersistenceItem(ColumnName = "ExtDictionaryColumn11")]
        [MongodbPresentationItem(DisplayName = "TestBase.ExtDictionaryColumn", Description = "基类中的扩展字典列")]
        public Dictionary<string, ExtItem> ExtDictionaryColumn1 { get; set; }

原始数据是这样的:

                             ExtListColumn3 = new List<ExtItem>
                    {
                        new ExtItem
                        {
                            NormalColumn4 = 100,
                            DictionaryColumn4 = new Dictionary<int, string>  { { 1, "x" }, { 2, "y" } },
                            ListColumn4 = Enumerable.Range(1, 2).ToList(),

                            EnumColumn4 = (Enum4)rnd.Next(1, 4),
                            IgnoreColumn4 = "asdasdas",
                        },
                        new ExtItem
                        {
                            NormalColumn4 = 200,
                            DictionaryColumn4 = new Dictionary<int, string>  { { 1, "x" }, { 2, "y" } },
                            ListColumn4 = Enumerable.Range(1, 2).ToList(),
                    
                            EnumColumn4 = (Enum4)rnd.Next(1, 4),
                            IgnoreColumn4 = "asdasdas",
                        },
                    },
                                ExtDictionaryColumn3 = new Dictionary<string, ExtItem>
                    {
                        { "Key1", new ExtItem
                            {
                                NormalColumn4 = 100,
                                DictionaryColumn4 = new Dictionary<int, string>  { { 1, "x" }, { 2, "y" } },
                                ListColumn4 = Enumerable.Range(1, 2).ToList(),
                          
                                EnumColumn4 = (Enum4)rnd.Next(1, 4),
                                IgnoreColumn4 = "asdasdas",
                            }
                        },
                        { "Key2", new ExtItem
                            {
                                NormalColumn4 = 100,
                                DictionaryColumn4 = new Dictionary<int, string>  { { 1, "x" }, { 2, "y" } },
                                ListColumn4 = Enumerable.Range(1, 2).ToList(),
                              
                                EnumColumn4 = (Enum4)rnd.Next(1, 4),
                                IgnoreColumn4 = "asdasdas",
                            }
                        },
                    }

后台呈现的时候是这样的:

image

这里可以注意到Key1和Key2是字典的Key。

由于ExtItem这个类型又有自己的子属性,这个子属性还可以是字典列和列表列,所以这是可以是无限级的。我们来看一下ExtItem的定义:

    public class ExtItem
    {
        [MongodbPersistenceItem(ColumnName = "NormalColumn44")]
        [MongodbPresentationItem(DisplayName = "ExtItem.NormalColumn", Description = "项扩展类中普通的列")]
        public int NormalColumn4 { get; set; }

        [MongodbPersistenceItem(ColumnName = "EnumColumn44")]
        [MongodbPresentationItem(DisplayName = "ExtItem.EnumColumn", Description = "项扩展类中枚举的列")]
        public Enum4 EnumColumn4 { get; set; }

        [MongodbPersistenceItem(ColumnName = "IgnoreColumn44", IsIgnore = true)]
        [MongodbPresentationItem(DisplayName = "ExtItem.IgnoreColumn", Description = "项扩展类中忽略的列")]
        public string IgnoreColumn4 { get; set; }

        [MongodbPersistenceItem(ColumnName = "DictionaryColumn44")]
        [MongodbPresentationItem(DisplayName = "ExtItem.DictionaryColumn", Description = "项扩展类中的字典列")]
        public Dictionary<int, string> DictionaryColumn4 { get; set; }

        [MongodbPersistenceItem(ColumnName = "ListColumn44")]
        [MongodbPresentationItem(DisplayName = "ExtItem.ListColumn", Description = "项扩展类中的列表列")]
        public List<int> ListColumn4 { get; set; }
    }
}

细心的话可以发现DisplayName中的.都替换为了_,这是因为显示名我们限制了不能包含.(原因是作为数据源绑定到DataGrid的时候,如果列名包含.则不是一个有效的属性名)。

除了支持复杂的自定义类型之外,我们的数据还支持枚举,比如LogInfo有一个LogLevel的枚举,在呈现数据的时候,我们看到的是枚举的名字而不是值:

image

并且在搜索的时候我们在下拉框中看到的也是枚举的名字:

image

 

总结一下,其实从使用上来说,对于Mongodb数据服务,我们只需要:

1、定义需要存储到数据库的实体类型(可以内嵌复杂的自定义类型,也可以是枚举等)

2、按照自己的需要定义一些Attribute来决定持久化(比如列名)和呈现的规则(比如如何查询数据)

3、调用MongodbService.MongodbInsertService.Insert(info)语句插入数据

然后,在后台就可以这些数据了,并不需要关心其它的。在Adhesive内部所有涉及到大数据存储的地方我们也都使用的是Mongodb数据服务,对于框架外部,如果有需要保存业务数据或其它监控数据也可以直接使用。下一篇我们会完整介绍一下Mongodb数据服务的后台。

作者: lovecindywang
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
2月前
|
存储 NoSQL MongoDB
数据的存储--MongoDB文档存储(二)
数据的存储--MongoDB文档存储(二)
77 2
|
2月前
|
存储 NoSQL MongoDB
基于阿里云数据库MongoDB版,微财数科“又快又稳”服务超7000万客户
选择MongoDB主要基于其灵活的数据模型、高性能、高可用性、可扩展性、安全性和强大的分析能力。
|
2月前
|
NoSQL MongoDB 数据库
使用NimoShake将数据从AWS DynamoDB迁移至阿里云MongoDB
使用NimoShake将数据从AWS DynamoDB迁移至阿里云MongoDB
|
2月前
|
存储 NoSQL 关系型数据库
数据的存储--MongoDB文档存储(一)
数据的存储--MongoDB文档存储(一)
109 3
|
4月前
|
持续交付 jenkins Devops
WPF与DevOps的完美邂逅:从Jenkins配置到自动化部署,全流程解析持续集成与持续交付的最佳实践
【8月更文挑战第31天】WPF与DevOps的结合开启了软件生命周期管理的新篇章。通过Jenkins等CI/CD工具,实现从代码提交到自动构建、测试及部署的全流程自动化。本文详细介绍了如何配置Jenkins来管理WPF项目的构建任务,确保每次代码提交都能触发自动化流程,提升开发效率和代码质量。这一方法不仅简化了开发流程,还加强了团队协作,是WPF开发者拥抱DevOps文化的理想指南。
101 1
|
4月前
|
存储 NoSQL JavaScript
MongoDB存储过程实战:聚合框架、脚本、最佳实践,一文全掌握!
【8月更文挑战第24天】MongoDB是一款备受欢迎的文档型NoSQL数据库,以灵活的数据模型和强大功能著称。尽管其存储过程支持不如传统关系型数据库,本文深入探讨了MongoDB在此方面的最佳实践。包括利用聚合框架处理复杂业务逻辑、封装业务逻辑提高复用性、运用JavaScript脚本实现类似存储过程的功能以及考虑集成其他工具提升数据处理能力。通过示例代码展示如何创建订单处理集合并定义验证规则,虽未直接实现存储过程,但有效地演示了如何借助JavaScript脚本处理业务逻辑,为开发者提供更多实用指导。
90 2
|
4月前
|
NoSQL 安全 MongoDB
【MongoDB深度揭秘】你的更新操作真的安全了吗?MongoDB fsync机制大起底,数据持久化不再是谜!
【8月更文挑战第24天】MongoDB是一款备受欢迎的NoSQL数据库,以其灵活的文档模型和强大的查询能力著称。处理关键业务数据时,数据持久化至关重要。本文深入探讨MongoDB的写入机制,特别是更新操作时的fsync行为。MongoDB先将数据更新至内存以提升性能,而非直接写入磁盘。fsync的作用是确保数据从内存同步到磁盘,但MongoDB并非每次更新后都立即执行fsync。通过设置不同的写入关注级别(如w:0、w:1和w:majority),可以平衡数据持久性和性能。
56 1
|
4月前
|
持续交付 jenkins C#
“WPF与DevOps深度融合:从Jenkins配置到自动化部署全流程解析,助你实现持续集成与持续交付的无缝衔接”
【8月更文挑战第31天】本文详细介绍如何在Windows Presentation Foundation(WPF)项目中应用DevOps实践,实现自动化部署与持续集成。通过具体代码示例和步骤指导,介绍选择Jenkins作为CI/CD工具,结合Git进行源码管理,配置构建任务、触发器、环境、构建步骤、测试及部署等环节,显著提升开发效率和代码质量。
96 0
|
4月前
|
持续交付 C# 敏捷开发
“敏捷之道:揭秘WPF项目中的快速迭代与持续交付——从需求管理到自动化测试,打造高效开发流程的全方位指南”
【8月更文挑战第31天】敏捷开发是一种注重快速迭代和持续交付的软件开发方法,通过短周期开发提高产品质量并快速响应变化。本文通过问题解答形式,探讨在Windows Presentation Foundation(WPF)项目中应用敏捷开发的最佳实践,涵盖需求管理、版本控制、自动化测试及持续集成等方面,并通过具体示例代码展示其实施过程,帮助团队提升代码质量和开发效率。
77 0
|
6天前
|
存储 JSON NoSQL
学习 MongoDB:打开强大的数据库技术大门
MongoDB 是一个基于分布式文件存储的文档数据库,由 C++ 编写,旨在为 Web 应用提供可扩展的高性能数据存储解决方案。它与 MySQL 类似,但使用文档结构而非表结构。核心概念包括:数据库(Database)、集合(Collection)、文档(Document)和字段(Field)。MongoDB 使用 BSON 格式存储数据,支持多种数据类型,如字符串、整数、数组等,并通过二进制编码实现高效存储和传输。BSON 文档结构类似 JSON,但更紧凑,适合网络传输。
31 15