前一篇文章描述了数据的获取(采集)和预处理,本文介绍数据的存储
存储设计
为了实现更加快速便捷的数据访问,需要对数据存储方式进行一定的设计。虽然目前只有251个景点,但是系统应该采用适用考虑更大规模数据的设计。
设计目标
存储的核心是索引,索引设计的关键就是选择合适的索引技术。根据我们的目标:
- 景点查找,需求可以理解为根据名称、地区、排名、是否可用等条件进行检索
- 附近景点:即为需要根据经纬度进行查找
- 我是否来过?这个最简单,需要一张记录表。
总的来说,系统需要支持根据关键词(名称或地区)、时间范围、true/false、经纬度等查询。显然,这需要用到全文索引、范围索引、经纬度索引,当然还有哈希索引,这基本是所有系统必备的,它实现的就是根据ID进行查找。
存储选型
传统的关系数据库基本上支持范围索引和哈希索引,尽管新版本也开始支持全文,但是毕竟这些数据库大部分是国外的,对中文支持不好。
ElasticSearch(简称ES,别和EcmaScript搞混了)是一个很直接的选择,能够支持所有四种索引,并且建索引的过程是高度可控的。ES提供了RESTFul访问接口,可以方便快速构建Web应用。
数据库准备
ES安装部署
安装非常简单,到官网下载压缩包,解压后即可运行。
启动:
bin\elasticsearch.bat
通常需要改一下配置文件 config/elasticsearch.yml。主要配置项如下:
cluster.name: es7-2019
node.name: node-1
path.data: data
path.logs: logs
network.host: 127.0.0.1
http.port: 9200
xpack.ml.enabled: false
http.cors.enabled: true
http.cors.allow-origin: "*"
最底下的http.cors. 配置是为了后面安装的head插件跨域访问的,该插件相当于是ES的管理界面。
head插件安装
在2.x以前版本,head插件是放在ES的plugins目录下,使用http://localhost:9200/_plugin/head访问。版本5以后head插件需要单独运行。
参考这里 下载安装运行head插件。该项目基于npm+grunt,需要提前安装nodejs。
由于前面已经配置了ES支持跨域请求,所以可以看到看到界面:
这是我已经写入数据(place库)后的样子。可以看到系统中有两个索引(相当于数据库)place和bank。集群健康状态是黄色,因为bank索引声明了1一个副本,但是集群只有一个节点(node-1),只有一份数据。place索引是完全正常的,它分为5块(12345),没有副本。
图中显示了采用基本采用,loc字段匹配“延庆”的结果。这正式全文索引实现的效果,因为它会对“延庆区”、“北京延庆”等进行拆分,获得“延庆”词条。检索时是基于该词条进行查找的。
分词插件
ES 7版本自带了一个中文分词器 smartcn,效果还未检验。之前一般用IK分词器,这个插件需要单独安装。安装比较简单,参考https://github.com/medcl/elasticsearch-analysis-ik
数据处理入库
基于存储设计,现在来实现数据入库建索引。
文档结构
基于之前采集的信息和需要查询的字段,设计如下文档结构:
{ "loc": "北京.丰台",
"name": "北戏书馆",
"img": "http://zglynk.com/ITS/upload/areaIcon/3512bab5-3437-4821-bb48-edea8a185dfc.jpg",
"url": "http://zglynk.com/ITS/wechatPortalInfo/goAreaDetail.action?id=370",
"price": 20,
"stop": false,
"detail": "不限次 2018.11.1-2019.12.31(每周六接待)",
"rank": null,
"times": 0,
"phone": "010-67572221-2197、18612500357",
"time": "每周六19:00-21:00 2018.11.1-2019.12.31",
"geo": [116.377354, 39.85597 ]
}
ES中对于记录唯一标识_id是在整个文档的外层。_id采用原网站的id,如果不存在,由ES生成一个UUID。
ES Mapping
ES默认会自动识别文档字段类型,建立相应的索引,但是对于一些文本和经纬度,由于检索方式多样,因此需要自己定制。索引设置index-setting.json
如下:
{
"properties":{
"name": {"type": "text", "analyzer": "ik_smart"},
"detail": {"type": "text", "analyzer": "smartcn"},
"loc": {"type": "text", "analyzer": "ik_smart"},
"img": {"type": "text", "index": false},
"url": {"type": "text" , "index": false},
"rank": {"type": "text", "analyzer": "keyword"},
"geo": {"type": "geo_point"}
}
}
这里对name/detail/loc三个字段采用了全文检索。不过为了试验分词器效果,我分别采用了smartcn和IK。对于img和url字段,由于不需要检索,因此不建索引。对于rank字段,它本身就是一个词(2A 3A),因此类型是keyword。geo字段设置类型为geo_point,ES为该类型简历geohash索引,支持经纬度范围查询。
注意,一般的数值、bool字段不需要特殊设置,ES会自动建立合适的索引。另外,ES还有很多参数,包括_all _source、多字段、嵌套字段、数组等,要想用好,通读一遍官方文档很重要!
创建索引
索引基本设置:index-setting.json
{
"settings" : {
"index" : {
"number_of_shards" : 5,
"number_of_replicas" : 0
}
}
}
本自己本机做测试,没有副本,分区也较少。在数据规模较大时,应该设置较多的shard数。同时为了数据安全,应该设置1-2个副本(前提是分布式集群部署)
通过curl命令创建:
host=localhost:9200
indexn=place
HEADER="Content-Type: application/json"
PREFIX="http://$host/$indexn"
curl -XPUT -H "$HEADER" $PREFIX -d "@index-setting.json"
curl -XPUT -H "$HEADER" ${PREFIX}/_mapping -d "@index-mapping.json"
小技巧:使用curl命令的 -d "@filename" 可以将复杂的body参数写在文件里面。
另外,我用的ES 7版本提示必须要加Header,默认的Content-Type: application/x-www-form-urlencoded 是不支持的。
数据处理与提交
将之前生成获取的数据,可以很容易地生成相应的文档格式。ES入库可以一条一条执行PUT请求,也可以使用它的bulk API。
为了使用bulk API,先将数据生成xjson格式:
{"index": {"_id": "370", "_index": "place"}}
{"loc": "北京.丰台", "name": "北戏书馆", "img": "http://zglynk.com/ITS/upload/areaIcon/3512bab5-3437-4821-bb48-edea8a185dfc.jpg", "url": "http://zglynk.com/ITS/wechatPortalInfo/goAreaDetail.action?id=370", "price": 20, "stop": false, "detail": "不限次 2018.11.1-2019.12.31(每周六接待)", "rank": null, "times": 0, "phone": "010-67572221-2197、18612500357", "time": "每周六19:00-21:00 2018.11.1-2019.12.31", "geo": [116.377354, 39.85597]}
{"index": {"_index": "place"}}
{"loc": "丰台区", "name": "丰台雪乡冰雪嘉年华", "price": 120, "stop": true, "detail": "接待结束", "rank": null, "times": 0}
创建索引时,每条数据分为2行,第一行指明操作及索引名和_id,第二行是需要写入的文档。换行采用\n。
为了生成这个格式,python函数:
def writeJsonArray(arr, filename):
with open(filename, 'w') as f:
for item in arr:
f.write(json.dumps(item, ensure_ascii = False)) #不要格式化
f.write('\n') #添加换行符
最后,提交整个文件给ES:
curl -s -H "Content-Type: application/x-ndjson" -XPOST localhost:9200/_bulk --data-binary "@list2.json"
小节
通过ES可以很方便地检索查询数据。不过ES本身是近实时的,采用最终一致性模型,因此不适合做一些事务类的业务,因此主要用于数据查询分析,尤其是全文检索、日志分析等。
下一步
下一步将基于vue.js框架,构建基本查询检索界面。通过openlayers或地图平台(如高德)API 进行地图展示。