使用Docker和Elasticsearch搭建全文本搜索引擎应用(上)

喜欢记得转发关注哟

给应用添加快速、灵活的全文本搜索对谁都不是一件容易的事情。许多主流数据库,如PostgreSQL和MongoDB,受限于查询和索引结构,只提供基础文本搜索能力。为了提供高效全文本搜索一般都需要一个独立的数据库。Elasticsearch正是这样一个能够提供灵活性和快速全文本搜索能力的开源数据库。

本文采用Docker来设置依赖环境。Docker是目前最常见的容器化引擎,Uber、Spotify、ADP和Paypal都是用这个技术,它的优势在于与操作系统无关,可以运行在Windows、macOS和Linux之上——写操作指南很容易。如果从来没有用过Docker也没问题,本文会详细提供配置文件。

本文也分别采用Node.js采(用Koa框架)和Vue.js创建搜索API和前端Web应用。

1. 什么是Elasticsearch

现代应用中全文本检索是高请求负载的应用。搜索功能也是比较困难完成的功能(许多大众网站都有subpar功能,但不是返回很慢就是返回结果不准确),大部分原因是因为底层数据库:许多标准关系型数据库只能提供基本字符串匹配功能,而对CONTAINS或者LIKE SQL查询只能提供有限支持。

而本文提供的搜索应用能够提供:

  1. 快速:查询结果应该实时返回,提高用户体验。

  2. 灵活:根据不同数据和使用场景,可以调整搜索过程。

  3. 最佳建议:对于输入错误,返回最可能的结果。

  4. 全文本:除了搜索关键词和标签之外,希望能够搜索到所有匹配文本。

实现以上要求的搜索应用,最好采用一个为全文本检索优化的数据库,这也是本文采用Elasticsearch的原因。Elasticsearch是一个用Java开发的,开源的内存数据库,最开始是包含在Apache Lucene库中。以下是一些官方给出的Elasticsearch使用场景:

  1. Wikipedia使用Elasticsearch提供全文检索,提供高亮显示、search-as-you-type和did-you-mean建议等功能。

  2. Guardian使用Elasticsearch将访问者社交数据整合反馈给作者。

  3. Stack Overflow将位置信息和more-like-this功能与全文本检索整合提供相关问题和答案。

  4. GitHub使用Elasticsearch在一千三百亿行代码中进行搜索。

Elasticsearch有什么独特之处

本质上,Elasticsearch通过使用反向索引提供快速和灵活的全文本搜索。

“索引”是一种在数据库中提供快速查询和返回的数据结构。数据库一般将数据域和相应表位置生成索引信息。将索引信息存放在一个可搜索的数据结构中(一般是B-Tree),数据库可以为优化数据请求获得线性搜索响应(例如“Find the row with ID=5”)。

可以把数据库索引看做学校图书馆卡片分类系统,只要知道书名和作者,就可以准确告诉查找内容的入口。数据库表一般都有多个索引表,可以加速查询(例如,对name列的索引可以极大加速对特定name的查询)。

而反向索引工作原理与此完全不同。每行(或者每个文档)的内容被分拆,每个入口(本案例中是每个单词)反向指向包含它的文档。

反向索引数据结构对查询“football”位于哪个文档这种查询非常迅速。Elasticsearch使用内存优化反向索引,可以实现强大和客制化全文本检索任务。

2. 项目安装

2.0 Docker

本文使用Docker作为项目开发环境。Docker是一个容器化引擎,应用可以运行在隔离环境中,不依赖于本地操作系统和开发环境。因为可以带来巨大灵活性和客制化,许多互联网公司应用都已经运行在容器中。

对于作者来说,Docker可以提供平台一致性安装环境(可以运行在Windows、macOS和Linux系统)。一般Node.js、Elasticsearch和Nginx都需要不同安装步骤,如果运行在Docker环境中只需要定义好不同配置文件,就可以运行在任何Docker环境。另外,由于应用各自运行在隔离容器中,与本地宿主机关系很小,因此类似于“但是我这可以运行啊”这种排错问题就很少会出现。

2.1 安装Docker和Docker-Compose

本项目只需要Docker和Docker-Compose环境。后者是Docker官方工具,在单一应用栈中编排定义多个容器配置。

安装Docker——https://docs.docker.com/engine/installation/

安装Docker Compose——https://docs.docker.com/compose/install/

2.2 设置项目安装目录

创建一个项目根目录(例如guttenberg_search),在其下定义两个子目录:

  • /public——为前端 Vue.js webapp存放数据。

  • /server——服务器端Node.js 源文件。

2.3 添加Docker-Compose配置文件

下一步,创建docker-compose.yml文件,定义应用栈中每个容器的配置:

  1. gs-api——Node.js 容器后端应用逻辑.

  2. gs-frontend——为前端webapp提供服务的Nginx容器

  3. gs-search——存储搜索数据的Elasticsearch容器

version: '3'services:api: # Node.js Appcontainer_name: gs-apibuild: .ports: - "3000:3000" # Expose API port - "9229:9229" # Expose Node process debug port (disable in production)environment: # Set ENV vars - NODE_ENV=local - ES_HOST=elasticsearch - PORT=3000volumes: # Attach local book data directory - ./books:/usr/src/app/booksfrontend: # Nginx Server For Frontend Appcontainer_name: gs-frontendimage: nginxvolumes: # Serve local "public" dir - ./public:/usr/share/nginx/htmlports: - "8080:80" # Forward site to localhost:8080elasticsearch: # Elasticsearch Instancecontainer_name: gs-searchimage: docker.elastic.co/elasticsearch/elasticsearch:6.1.1volumes: # Persist ES data in seperate "esdata" volume - esdata:/usr/share/elasticsearch/dataenvironment: - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - discovery.type=single-nodeports: # Expose Elasticsearch ports - "9300:9300" - "9200:9200"volumes: # Define seperate volume for Elasticsearch dataesdata:

此文件定义应用栈,而不需要在本地宿主机安装Elasticsearch、Node.js、或者Nginx。每个容器都对宿主机开放相应端口,以便从宿主机访问和排错Node API,Elasticsearch实例和前端应用。

2.4 添加Dockerfile

本文使用官方的Nginx和Elasticsearch镜像,但是需要重新为Node.js创建自己的镜像。

在应用根目录定义一个简单的Dockerfile配置文件。

# Use Node v8.9.0 LTSFROM node:carbonSetup app working directoryWORKDIR /usr/src/appCopy package.json and package-lock.jsonCOPY package*.json ./Install app dependenciesRUN npm installCopy sourcecodeCOPY . .Start appCMD [ "npm", "start" ]

此Docker配置文件中将应用源码拷贝进来,安装了NPM依赖包,形成了自己的镜像。同样需要添加一个.dockerignore文件,避免不需要的文件被拷入。

node_modules/npm-debug.logbooks/public/
注意:不需要将node_modules拷入,因为我们后续要用npm install来安装这些进程。如果拷贝node_modules到容器中容易引起兼容性问题。例如在macOS上安装bcrypt包,如果将此module拷入Ubuntu容器就会引起操作系统不匹配问题。

2.5 添加基础文件

测试配置文件前,还需要往应用目录拷入一下占位文件。在public/index.html中加入如下基础配置信息:

Hello World From The Frontend Container

下一步,在server/app.js中加入Node.js的应用文件。

const Koa = require('koa')const app = new Koa()app.use(async (ctx, next) => {ctx.body = 'Hello World From the Backend Container'})const port = process.env.PORT || 3000app.listen(port, err => {if (err) console.error(err)console.log(`App Listening on Port ${port}`})

最后,加入package.json节点配置文件:

{"name": "guttenberg-search","version": "0.0.1","description": "Source code for Elasticsearch tutorial using 100 classic open source books.","scripts": {"start": "node --inspect=0.0.0.0:9229 server/app.js"},"repository": {"type": "git","url": "git+https://github.com/triestpa/guttenberg-search.git"},"author": "patrick.triest@gmail.com","license": "MIT","bugs": {"url": "https://github.com/triestpa/guttenberg-search/issues"},"homepage": "https://github.com/triestpa/guttenberg-search#readme","dependencies": {"elasticsearch": "13.3.1","joi": "13.0.1","koa": "2.4.1","koa-joi-validate": "0.5.1","koa-router": "7.2.1"}}

此文件定义应用开始命令和Node.js依赖包。

注意:不需要特意运行npm install,容器创建时候会自动安装依赖包。

2.6 开始测试

都准备好了,接下来可以测试了。从项目根目录开始,运行docker-compose,会自动创建Node.js容器应用。

运行docker-compose up启动应用:

注意:这一步可能会运行时间比较长,因为Docker可能需要下载基础镜像。以后执行速度会很快,因为本地已经有了基础镜像。

访问localhost:8080,应该看到如下图输出“hello world”。

访问localhost:3000验证服务器端返回“hello world”信息。

最后,访问localhost:9200确认Elasticsearch是否运行,如果正常,应该返回如下输出:

{"name" : "SLTcfpI","cluster_name" : "docker-cluster","cluster_uuid" : "iId8e0ZeS_mgh9ALlWQ7-w","version" : {"number" : "6.1.1","build_hash" : "bd92e7f","build_date" : "2017-12-17T20:23:25.338Z","build_snapshot" : false,"lucene_version" : "7.1.0","minimum_wire_compatibility_version" : "5.6.0","minimum_index_compatibility_version" : "5.0.0"},"tagline" : "You Know, for Search"}

如果所有URL输出都正常,恭喜,整个应用框架可以正常工作,下面开始进入真正有趣的部分了。

相关资讯: