各式结构化数据 动态 接入-存储-查询 的处理办法 (第二部分)

各式结构化数据的动态接入存储查询,这一需求相信有很多人都遇到过,随着实现技术路线选择的不同,遇到的问题出入大了,其解决办法也是大相径庭。数据存储在哪儿,是关系型数据库,还是NoSQL数据库,是MySQL还是Oracle,怎么建立索引,建立什么类型的索引,都是大学问。下面,我要把我对这一解决办法的思考总结一下,有成熟的也有不成熟的,希望大家一起共同探讨。

关键词:结构化数据, 动态, 接入, 存储, 查询


首先,我们得定义一下在本文中什么是结构化数据,这里的结构化数据主要是指扁平化的、可以由基础数据类型组合成的数据,例如:{"data":{"name":"wang anqi","age":28,"money":1653.35,"XX_date":"2013-12-3"}},它们是可以很容易被存入关系型数据库的,我们要讨论的就是这种数据。对应的,非结构化数据这里是指那些需要存储在文件系统中的,不是扁平化的数据。

那么,什么又是“各式结构化数据”呢,在本文中?这是一个数据集合,有可能集合中的每一条数据结构都是不尽相同的,例如:{"data":{"name":"wang anqi","age":28,"money":1653.35,"XX_date":"2013-12-3"}},和{"angel":{"address":"清凉山公园","user":289770363}}同时存在于一个数据集合中,它们结构不同,简单地说:第一条数据有四个属性,第二条数据只有两个属性,属性名称和类型都不一样。“各式”包括了不定数量的属性,不定的属性名称、不定的数据类型。

解释清楚名词了,再解释一下动词:“动态接入”。在普遍情境下,你只会遇到将固定数据结构的数据存储入库,这里的入库主要还是指MySQL一类的关系型数据库。那么你可以选择使用Hibernate等ORM工具或不使用,来进行数据的存储读取,在使用ORM工具的情况下,要首先定义好数据的数据结构,写死在xml里或是java代码里。

一般情况下,你是不会遇到这样的需求的:对于不能事先确定数据结构的数据,我要把它们存储到关系型数据库中,并提供“合法性检验”、“更新”、“查询”等基本数据操作。要说的是,如果要把它们存储到HBase这种NoSQL数据库中,那是再好不过的了,配合着HBase与Solr的集成(详见之前的博客:大数据架构-使用HBase和Solr将存储与索引放在不同的机器上 http://www.cnblogs.com/wgp13x/p/3927979.html),搜索也不是件难事,唯一可能出现的难点在于:Solr对于Schema中filedName的配置,因为结构是动态的,所以fildName也是动态的,这其实也是很好处理的,有位微软的同学已经跟我咨询过这个问题了;事实上,这样的例子是很常见的。

但是往往,事与愿违,很有可能存在着其它的约束条件制约着你:必须使用关系型数据库,那么一整套解决办法是需要设计的。因为当你使用Hibernate时,你不能再把一个数据结构写死在代码里,因为它不是固定的,你该如何入库,该如何查询数据,这都是问题。

要处理好“各式结构化数据动态接入管理”,应该分成以下几步:一、定义数据;二、动态管理;三、数据接入;四、数据查询。其中一二步在之前的博客

各式结构化数据 动态 接入-存储-查询 的处理办法 (第一部分)http://www.cnblogs.com/wgp13x/p/4019600.html 中已经说明。

三、数据接入


因为相关数据提供者可能很多,他们的存储机制、传输方式、使用语言也都不一样,但是需要让他们提供成一致的数据格式,这就需要跟他们协商好一个统一的接口来进行数据解析。在这里我设计了一个统一的数据格式来进行数据接入,即在接入前将各种数据一致化。我采用的是Json定义的通用数据结构,使用Jackson来进行解析,具体的使用方法还需察看我之前写的一篇博客:

使用Json让Java和C#沟通的方法 http://www.cnblogs.com/wgp13x/p/0effafd9f5283cbf36e62b4fb5e94c81.html

下面的就是我定义的Datas数据结构,它按照《基础数据定义文档》(见博客:

各式结构化数据 动态 接入-存储-查询 的处理办法 (第一部分)http://www.cnblogs.com/wgp13x/p/4019600.html

)屏蔽了如int、string、date等数据类型,在colummns中可以说明数据中各字段的数据类型,也可以省略这一colummns说明;在data中,统一把数值转化为string类型。


/**

* 数据

*

* @author 王安琪

* @since 2014-9-30下午4:13:07

*/

public class Datas implements Serializable

{

@JsonProperty("dataType")

private String dataType;//数据类型名称

@JsonProperty("columns")

private Map<String, String> columns;//这里可以为空,属性名-属性类型 键值对

@JsonProperty("datas")

private List<Data> datas;//多行数据
}


/**

* 一行数据

*

* @author 王安琪

* @since 2014-9-30下午2:16:06

*/

public class Data implements Serializable

{

@JsonProperty("data")

private Map<String, String> data;//属性名-属性值 键值对
}

好了,传入的数据就像这样:{"dataType":"angel","datas":[{"data":{"name":"wang anqi","age":28,"money":1653.35,"XX_date":"2013-12-3"}},{"data":{"name":"王安琪","age":28,"money":16533.5}}]}。为简便起见,这里省略了columns属性。

在这一步需要产生一个文档《统一数据格式定义》,共享给各“干系人”,毕竟数据是可能是由其它部门、其它人提供的,他们依据这里的定义来产生规定格式的数据。

我们接收到数据后,就要依次进行下面的操作了:1、数据格式验证;2、数据入库;3、执行其它业务逻辑。

数据格式验证可以通过在属性表(TBL_ATTRIBUTE)中配置的属性约束正则表达式,来保证接入数据的正确性,它还是比较容易的,较难的是,判断完成后进行的后继操作。比如:一条数据中,只有一列下的数据格式验证不正确,则应该如何处理,是整条丢弃还是这一列的数据内容丢弃,还是其它的方案......后继操作的选择,是由你的业务需要来确定的,通常与技术无关,这时,你就需要拿起电话跟你的“干系人们”进行沟通了。

数据入库,我使用的是直接拼SQL语句,sql = "insert into ** values ***",SQLQuery query = session.createSQLQuery(sql.toString()); query.setProperties(data); query.executeUpdate(); 这样的方式来入库的,表建立起来了,入库还是比较简单的。

四、数据查询


数据查询也要做得很灵活,因为数据结构不定,因此查询条件也不定。数据查询经常需要,对所有类型的数据,它所有的属性,进行 (包含)like 或 (等于)= 或 (大于)> 或 (小于)< 等等条件的查询,甚至还有可能进行组合查询,如(或者)OR (并且)AND,以及它们的嵌套。从查询条件的数据结构来看,它是一个树型结构数据。

动态查询条件,相信很多人在项目中有这样的需要。

为此,我设计了一个统一的查询条件的格式,前端提交的查询条件都需要遵循它。它同样采用Json来定义,使用Jackson来解析。定义的各类如下所示,Addable 是一个空接口,里面没有任何方法。


/**

* 查询条件

*

* @author 王安琪

* @since 2014-9-30下午3:45:23

*/

public class Search implements Serializable

{

@JsonProperty("area")

private List<String> area;

@JsonProperty("order")

private Order order;

@JsonProperty("condition")

private Condition condition;
}


/**

* 查询排序条件

*

* @author 王安琪

* @since 2014-7-30下午4:03:46

*/

public class Order implements Serializable

{

@JsonProperty("front")

private int front;

@JsonProperty("end")

private int end;

@JsonProperty("sequences")

private Map<String, String> sequences; // "age", "desc"; "name", "asc"
}


/**

* 查询约束组合

*

* @author 王安琪

* @since 2014-9-30下午4:04:41

*/

public class Condition implements Addable, Serializable

{

@JsonProperty("relation")

private String relation;

@JsonProperty("terms")

private List<Term> terms;

@JsonProperty("conditions")

private List<Condition> conditions;
}


/**

* 单一查询约束

*

* @author 王安琪

* @since 2014-9-30下午3:45:39

*/

public class Term implements Addable, Serializable

{

@JsonProperty("field")

private String field;

@JsonProperty("type")

private String type;

@JsonProperty("oper")

private String oper;

@JsonProperty("values")

private List<String> values;
}

传入的查询条件就像这样:{"area":[{"angel"}],"condition":{"terms":[{"field":"age","type":"int","oper":"between","values":["28","48"]}]}},需要注意的是oper字段的内容,它也是事先定义好的,你需要跟你的“干系人”们协商好可能存在的查询操作符,相关业务需求可能已经规定好了你的查询的可能性,比如对于数据型要有大于小于等于,字符串型要提供有(包含)like 或 (等于)等,日期型要有(大于)> 或 (小于)< 等,这一步,你也需要产生一个文档《数据查询条件定义》。

光设计好了查询条件格式还远远不够,你肯定需要将它解析,继而用它来进行数据库查询,因为这里用的是关系型数据库,所以要把它转成一个SQL语句。下面是各类中实现这一功能的代码段。这里为简化,没有把排序条件order写出。

Term 类 Term 类

/**

* 将Term转变为SQL语句

*

* @return SQL语句

*/

public String toSQL()

{

StringBuilder sql = new StringBuilder();

String valuesSQL = getSqlValues(type, oper, values);

String sqlOper = getSqlOper(oper);

sql.append(field).append(" ").append(sqlOper).append(" ")

.append(valuesSQL);

if (sqlOper.equals("like"))

{

sql.append(" ESCAPE ‘!‘");

}

return sql.toString();

}


private String getSqlValues(String type, String oper, List<String> values)

{

StringBuilder sqlValue = new StringBuilder();

if (values.size() == 1)

{

sqlValue.append(getSqlValue(type, oper, values.get(0)));

}

else if (values.size() > 1)// between

{

for (String value : values)

{

sqlValue.append(getSqlValue(type, oper, value)).append(" and ");

}

sqlValue.delete(sqlValue.length() - 5, sqlValue.length());

}

return sqlValue.toString();

}


private String getSqlValue(String type, String oper, String value)

{

StringBuilder sqlValue = new StringBuilder();

value = StringEscapeUtils.escapeSql(value);

sqlValue.append("‘");

switch (oper)

{

case "like":

sqlValue.append("%").append(escapeLikeSql(value)).append("%");

break;

default:

sqlValue.append(value);

break;

}

sqlValue.append("‘");

return sqlValue.toString();

}


private String escapeLikeSql(String likeValue)

{

String str = StringUtils.replace(likeValue, "!", "!!");

str = StringUtils.replace(str, "%", "!%");

str = StringUtils.replace(str, "*", "!*");

str = StringUtils.replace(str, "?", "!?");

str = StringUtils.replace(str, "_", "!_");

return str;

}

private String getSqlOper(String oper)

{

String sqlOper;

switch (oper)

{

case "like":

sqlOper = "like";

break;

default:

sqlOper = oper;

break;

}

return sqlOper;

}

Condition 类 Search 类

/**

* 产生SQL语句

*

* @return SQL语句

*/

public String toSQL()

{

StringBuilder sql = new StringBuilder();

String rlt = (relation == null || relation.isEmpty()) ? Constants.SQL_AND

: relation;

if (terms != null)

{

for (Term term : terms)

{

sql.append("(").append(term.toSQL()).append(")").append(rlt);

}

sql.delete(sql.length() - rlt.length(), sql.length());

}

if (conditions != null)

{

for (Condition condition : conditions)

{

sql.append("(").append(condition.toSQL()).append(")")

.append(rlt);

}

sql.delete(sql.length() - rlt.length(), sql.length());

}

return sql.toString();

}


public String toSQL()

{

if (this.condition == null)

{

return "1 = 1";

}

return this.condition.toSQL();

}

通过调用Search.toSQL()拿到使用以上代码生成的SQL查询条件语句后,你就可以使用Hibernate提供的

Query query = m_sessionFactory.getCurrentSession().createSQLQuery(sql.toString()).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);        List result = query.list(); 来进行数据库查询了,你甚至可以依照《统一数据格式定义》中定义的Datas类型,来生成一个Datas对象。


List<Data> datas = new ArrayList<Data>();

for (Object oResult : result)

{

Map mResult = (Map) oResult;

Map<String, String> values = new HashMap<String, String>();

for (Object o : mResult.keySet())

{

if (o == null || mResult.get(o) == null)

{

continue;

}

String name = String.valueOf(o);

Object vo = mResult.get(name);

String value;

{

value = String.valueOf(mResult.get(name)).trim();

}

if (value == null || value.equals(""))

{

continue;

}

values.put(name, value);

}

Data data = new Data(values);

datas.add(data);

}

Datas ret = new Datas(search.getArea().get(0), null, datas);

存在问题



1、大数据查询问题

如果,你要接入的数据量巨大,现在经过以上的步骤,也都正确存入了关系型数据库中了,可能一张表中存储了千万级的数据,现在提交了一次查询请求,这下好了,这次查询请求连接到数据库查询数据占用了十来分钟的时间(这跟查询条件有关,对于查不到的数据,查询就很慢),也就是说十多分钟后此链接才能释放,那么问题来了,如果你多提交了几次这样的查询请求,只有十次,数据库就卡死了,大量的链接Client Connections停滞在Sending dataStatistics状态,再来多少请求,无论是占长时间的查询还是很短时间就能处理的查询,统统都不能立即返回结果了,用户直接的反应就是你的后台不工作了,虽然耐心等待十多分钟后还能正常查询。

处理办法:

a、建立索引。因为是各式结构化数据动态接入,所以对所有的数据表,所有的字段,根据不同的数据类型,需要建立(不同的)索引,这很繁琐,而且索引占用磁盘空间,经过测试,索引建完后,查询速度是提升了不少,但仍不可接受。这是一个大问题,也许对于大数据,也许经过MySQL的性能优化能够稍微好些,但MySQL能做的只有这了。你有什么好的调优手段?

b、悬而未决,你的建议。

2、Date类型转化问题

通过Transformers.ALIAS_TO_ENTITY_MAP查询出来的结果,value = String.valueOf(mResult.get(name)).trim();结果对于日期、时间类型的数据都是Date类型的,数据库中的时间可能是yyyy-MM-dd HH:mm:ss样式的,然而value却是2014-10-11 15:50:30.0这样的,精确度不一致,用户也有意见。

处理办法:

a、悬而未决,你的建议。

来自http://www.cnblogs.com/wgp13x/

时间: 2024-12-19 14:30:34

各式结构化数据 动态 接入-存储-查询 的处理办法 (第二部分)的相关文章

杉岩数据:对象存储是企业海量非结构化数据存储的最佳选择

海量数据的爆炸式增长,使存储技术近五年的发展速度远超过去n年的发展历程.C端用户一个明显的感觉就是:U盘存储容量从过去物以稀为贵的几十M迅速发展到今天几十G.甚至TB级,家用电脑硬盘容量更是TB级标配. 那么,企业级又迎来了怎样的变化? IDC数据显示,到2020年,企业数据总体将达到44ZB,其中80%的数据将会是非结构化数据(图片.视频.归档以及企业级备份等各种数据).显然,海量数据的产生正在促使企业级存储从需求到产品形态都发生了改变. "相对于NAS.SAN这种传统企业级存储解决方案,对象

海量非结构化数据存储难题 ,杉岩数据对象存储完美解决

"过去几年,大数据产业更多关注的是如何处理海量.多源和异构的数据,但我们必须承认这些只是冰山一角.目前,结构化数据仅占到全部数据量的20%,其余80%都是以文件形式存在的非结构化和半结构化数据.伴随非结构化数据呈现爆发之势,对象存储市场近两年保持强劲增长,IDC预计,软件定义存储(SDS)市场未来五年复合增长率将达到28.8%." 传统IT架构渐成"过去式" 非结构化数据倒逼存储变革 今天,许多企业已经意识到,结构化数据仅仅是企业所拥有数据的一小部分.与业务信息系统

结构化、半结构化、非结构化数据

结构化数据.非结构化数据以及半结构化数据是对存储形式的一种数据类型分析 结构化数据.非结构化数据以及半结构化数据对比 类别 结构化数据 半结构化数据 非结构化数据 数据特征 数据结构字段含义确定,清晰 具有一定结构,但语义不够确定:自描述,数据结构和内容混杂在一起 杂乱无章的数据,很难按照一个概念去进行抽取,无规律性 典型例子 数据库中的表结构 邮件.HTML.报表.资源库 视频.音频.图片.图像.文档.文本等 数据模型 二维表 树.图 无 存储方案 高速存储应用需求.数据备份需求.数据共享需求

如何保护非结构化数据

简单地说,数据可分为结构化数据和非结构化数据.对于非结构化的数据保护你知道怎么做吗? 下面先跟大家区分一下结构化和非结构化数据. 结构化数据,简单来说就是数据库,如MySQL\Oracle\MS SQL Server\PostgreSQL\.结合到典型场景中更容易理解,比如企业ERP.财务系统;教育一卡通;政府行政审批;其他核心数据库等.这些应用需要哪些保护方案呢?基本包括高速存储应用需求.数据备份需求.数据共享需求以及数据容灾需求. 非结构化数据,简单来说就是文档类:pdf/doc/ppt/x

非结构化数据的存储与查询

当今信息化时代充斥着大量的数据.海量数据存储是一个必然的趋势.然而数据如何的存储和查询,尤其是当今非结构化数据的快速增长,对其数据的存储,处理,查询.使得如今的 关系数据库存储带来了巨大的挑战.分布存储技术是云计算的基础,主要研究如何存储.组织和管理数据中心上的大规模海量数据.由于面临的数据规模和用户规模更加庞大,在可扩展性.容错性以及成本控制方面面临着更加严峻的挑战[1]. 对于大量的半结构化数据(semi-structure data)和非结构化数据,对其存储和并发计算以及扩展能力而设计出了

MySQL 5.7:非结构化数据存储的新选择

本文转载自:http://www.innomysql.net/article/23959.html (只作转载, 不代表本站和博主同意文中观点或证实文中信息) 工作10余年,没有一个版本能像MySQL 5.7那样令我激动与期盼,10月MySQL 5.7 GA版本的发布,意味着MySQL数据库终于有能力在传统企业中向商业数据库发起挑战,开源的Linux操作系统干掉了封闭的Unix系统,MySQL会不会再一次逆袭商业产品?目前来看,或许很难,但是机会已经掌握在自己上手,后面的发展就看MySQL团队能

利用Gson和SharePreference存储结构化数据

问题的导入 Android互联网产品通常会有很多的结构化数据需要保存,比如对于登录这个流程,通常会保存诸如username.profile_pic.access_token等等之类的数据,这些数据可以组成一个bean,比如就叫做User: public class User { String username; String access_token; String profile_pic; public String getUsername() { return username; } pub

非结构化数据存储方案

ceph 官方网站:http://docs.ceph.org.cn/ 简单介绍: Ceph是一个开源的分布存储系统,同时提供对象存储.块存储和文件存储.linux内核2.6.34将ceph加入到内核中,红帽基于ceph出了redhat ceph storage. 支持TB级存储 支持高可用.容灾备份(当然了,这是存储的常见功能) 支持上百节点的负载均衡 系统架构: OpenStack Swift 官方网站:https://docs.openstack.org/swift/latest/ 简单介绍

静态结构化数据全文检索的实现思路

这里的静态结构化数据,是指从关系型数据库中抽取存储在NoSQL.文本文件或者其他格式文件的数据,并且数据不再变化. 全文检索是指任意字段的精确查询.模糊查询&范围查询.字母或数字模糊查询只支持前导查询,中文模糊查询则不受限制. 首先在抽取数据时,数据量特别大的数据表,按分区按ROWID分片分文件存储. 其次,实现数据查询. 思路一: 最常规的方法,在所有的字段上创建索引,由于事先不清楚数据的分布如何,因此都创建B+树索引. 中文模糊查询的实现是采用中文分词,把中文所有可能的分词组合起来创建索引.