Elasticsearch:使用 Node.js 将实时数据提取到 Elasticsearch 中(一)

·  阅读 2641

Elasticsearch 是一个强大的 RESTful 搜索和分析引擎,能够处理越来越多的用例。 它将集中存储你的数据,以实现闪电般的快速搜索、微调相关性以及可轻松扩展的强大分析。 关于如何使用 Elastic Stack(又名 ELK 堆栈)将数据摄取到 Elasticsearch 的资源有很多。在今天的文章中,我将详细介绍如何使用 Node.js 从零开始来把地震的实时数据采集到 Elasticsearch 中。

如果你选择的编程语言是 JavaScript,并且你需要使用 RESTful API 方法从第三方应用程序获取数据,那么使用 Node.js 获取数据是一个不错的选择。 你还可以托管服务器,让它持续实时摄取数据。 该演示将向您展示如何设置一个 Node.js + Express.js 服务器,该服务器实时将数据提取到 Elasticsearch 中,然后可以对这些数据进行分析并以有意义的方式采取行动。

对于此演示,我们将使用 USGS 实时发布的公开可用的全球地震数据。

准备工作

Elasticsearch 及 Kibana

如果你还没有安装好自己的 Elasticsearch 及 Kibana 的话,那么请参考我之前的文章:

在今天的展示中,我将使用 Elastic Stack 8.x 来进行展示。在安装的时候,请参考相应的 Elastic Stack 8.x 的文章来进行安装。

Node.js

你需要安装好自己的 Node.js 来完成下面的练习。你可以参考 Node.js 链接来进行相应的安装。

实时数据

根据 USGS 网上所提供的信息,我们可以在地址  earthquake.usgs.gov/earthquakes… 找到相应的地震信息数据。我们可以通过如下的命令来进行查看:

curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson | jq .
复制代码

如上所示,它是一个以 JSON 格式给出来的数据信息。这个数据会实时发生变化,我们可以通过反复访问这个接口来得到所需要的地震信息。在这里,我们需要注意的是:

  • "time"****: 1672471359610,这是一个时间信息,可以作为我们的 timestamp 来对它进行分析。我们将最终把它存入到 @timestamp 里。

  • "id"****: "nc73827101",这是一个地震特有的 id,我们将以这个 id 成为数据的 id。

  • "geometry",这个是地震发生的地理位置。我们可以需要在 Elasticsearch 中为它定一下为 geo_point 数据类型。我们将把它变为:

虽然数据有很多,但是我们最终需要的数据格式是这样的:



1.  {
2.    "mag": 1.13,
3.    "place": "11km ENE of Coachella, CA",
4.    "@timestamp": 2022-05-02T20:07:53.266Z,
5.    "url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci40240408",
6.    "sig": 20,
7.    "type": "earthquake",
8.    "depth": 2.09,
9.    "coordinates": {
10.      "lat": 33.7276667,
11.      "lon": -116.0736667
12.    }
13.  }


复制代码

在接下来的步骤里,我来详细介绍如何达到我们最终的目的。

创建 Node.js 应用

创建最基本的 express 应用

我们将从 0 开始一步一步地创建 Node.js 应用。首先我们在自己的电脑中创建一个目录:

mkdir earthquake_app
复制代码


1.  $ pwd
2.  /Users/liuxg/demos
3.  $ mkdir earthquake_app
4.  $ cd earthquake_app/


复制代码

我们进入到该目录中,并打入如下的命令:

npm init -y
复制代码
`

1.  $ npm init -y
2.  Wrote to /Users/liuxg/demos/earthquake_app/package.json:

4.  {
5.    "name": "earthquake_app",
6.    "version": "1.0.0",
7.    "description": "",
8.    "main": "index.js",
9.    "scripts": {
10.      "test": "echo \"Error: no test specified\" && exit 1"
11.    },
12.    "keywords": [],
13.    "author": "",
14.    "license": "ISC"
15.  }

18.  $ ls
19.  package.json

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

上述命令生成一个叫做 package.json 的文件。在以后安装的 packages,它也会自动添加到这个文件中。默认的设置显然不是我们想要的。我们需要对它做一些修改。

在接下来的代码中,我们将会使用如下的一些 packages:

  • @elastic/elasticsearch
  • axios
  • config
  • cors
  • express
  • log-timestamp
  • nodemon

我们可以通过如下的命令来进行安装:

npm i @elastic/elasticsearch axios config cors express log-timestamp nodemon
复制代码


1.  $ npm i @elastic/elasticsearch axios config cors express log-timestamp nodemon
2.  npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
3.  npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/

5.  added 118 packages in 17s

7.  11 packages are looking for funding
8.    run `npm fund` for details


复制代码

由于我之前已经安装过,所以我上面显示的信息和你的可能会有所不同。我们再次来查看 package.json 文件:

`

1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ ls
4.  node_modules      package-lock.json package.json
5.  $ cat package.json 
6.  {
7.    "name": "earthquake_app",
8.    "version": "1.0.0",
9.    "description": "",
10.    "main": "index.js",
11.    "scripts": {
12.      "test": "echo \"Error: no test specified\" && exit 1"
13.    },
14.    "keywords": [],
15.    "author": "",
16.    "license": "ISC",
17.    "dependencies": {
18.      "@elastic/elasticsearch": "^8.5.0",
19.      "axios": "^1.2.2",
20.      "config": "^3.3.8",
21.      "cors": "^2.8.5",
22.      "express": "^4.18.2",
23.      "log-timestamp": "^0.3.0",
24.      "nodemon": "^2.0.20"
25.    }
26.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

很显然,我们最新安装的 packages 已经自动添加到 package.json 文件中了。

我们接下来创建一个叫做 server 的子目录,并在它里面创建一个叫做 server.js 的文件:



1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ mkdir server
4.  $ touch server/server.js


复制代码

在上面,我们创建了一个叫做 server.js 的文件。这个将来就是我们需要运行的 server 脚本。为了能够让我们的 package.json 文件的配置能让 npm 进行运行,我们需要对它进行修改。

`

1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ cat package.json 
4.  {
5.    "name": "earthquake_app",
6.    "version": "1.0.0",
7.    "description": "",
8.    "main": "sever.js",
9.    "scripts": {
10.      "start": "nodemon server/server.js",
11.      "test": "echo \"Error: no test specified\" && exit 1"
12.    },
13.    "keywords": [],
14.    "author": "",
15.    "license": "ISC",
16.    "dependencies": {
17.      "@elastic/elasticsearch": "^8.5.0",
18.      "axios": "^1.2.2",
19.      "config": "^3.3.8",
20.      "cors": "^2.8.5",
21.      "express": "^4.18.2",
22.      "log-timestamp": "^0.3.0",
23.      "nodemon": "^2.0.20"
24.    }
25.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

很多人可能会奇怪,为啥使用 nodemon 来启动脚本。它的好处是当我们修改好 server.js 里的脚本,那么它会自动重新启动服务器的运行,而不需要我们每次都需要打入如下的命令:

npm start
复制代码

接下为了验证我们的 express 应用是否能成功地运行,我们修改 server.js 为如下的代码:

server/server.js



1.  onst express = require('express');

3.  const app = express();

5.  const port = 5001;

7.  app.get('/', (req, res) => {
8.      res.send('Hello World!')
9.    })

11.  app.listen(port, () => console.log(`Server listening at http://localhost:${port}`));


复制代码

我们接下来使用如下的命令来进行启动:

npm start
复制代码


1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ npm start

5.  > earthquake_app@1.0.0 start
6.  > nodemon server/server.js

8.  [nodemon] 2.0.20
9.  [nodemon] to restart at any time, enter `rs`
10.  [nodemon] watching path(s): *.*
11.  [nodemon] watching extensions: js,mjs,json
12.  [nodemon] starting `node server/server.js`
13.  Server listening at http://localhost:5001


复制代码

我们可以看到服务器已经成功地运行起来了,并且它运行于 5001 端口上。我们可以通过浏览器来进行访问它的网址:

上面显示我们的服务器运行正常。

安全地连接 Node.js 服务器到 Elasticsearch

接下来,我们需要创建代码来安全地连接 Node.js 服务到我们本地部署的 Elasticsearch 中。我们可以参考之前的文章 “Elasticsearch:使用最新的 Nodejs client 8.x 来创建索引并搜索”。我们可以在项目的更目录下创建如下的两个子目录:



1.  mkdir config
2.  mkdir -p server/elasticsearch


复制代码


1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ mkdir config
4.  $ mkdir -p server/elasticsearch
5.  $ ls -d */
6.  config/       node_modules/ server/


复制代码

在 config 子目录下,我们创建如下的一个叫做 default.json 的文件。这个是用来配置如何连接到 Elasticsearch 的:

config/default.json



1.  {
2.    "elastic": {
3.      "elasticsearch_endpoint": "https://localhost:9200",
4.      "username": "elastic",
5.      "password": "-pK6Yth+mU8O-f+Q*F3i",
6.      "apiKey": "eVBKOFhJVUJUN1gwSDQyLU5halY6R1BVRjNOUmpRYUtkTmpXTUZHdWZVUQ==",
7.      "certificate": "/Users/liuxg/elastic/elasticsearch-8.5.3/config/certs/http_ca.crt",
8.      "caFingerprint": "E3D36275D9FA80CF96F74E6537FC74E7952511A75E01605EBCFB8FC9F08F598C"
9.    }
10.  }


复制代码

我们先不要着急来了解这些配置参数。有些我们可能并不一定要用到。这些设置针对我们每个人的 Elasticsearch 的安装的不同而不同。在上面的参数解释如下:

  • elasticsearch_endpoint:这个是 Elasticsearch 的访问地址
  • username:这个是访问 Elasticsearch 的用户名,你可以不选用超级用户 elastic,而且在生产环境中,也不是推荐的方法
  • password:这个是上面 username 账号的密码
  • apiKey:这个是访问 Elasticsearch 所需要的 apiKey。你可以参考  “Elasticsearch:使用最新的 Nodejs client 8.x 来创建索引并搜索” 来了解如何进行生产。在下面的代码中,我们也可以使用 code 来进行生成
  • certificate:这个是证书的位置。每个 Elasticsearch 集群都会有一个生成的证书位置。我们需要填入这个位置信息
  • caFingerprint:这个是证书的 fingerprint 信息。我们可以采用 fingerprint 来进行连接。在本演示中,我将不使用这种方式。更多信息,请参考 Connecting | Elasticsearch JavaScript Client [master] | Elastic

我们在 elasticsearch 目录下创建一个叫做 client.js 的文件:

server/elasticsearch/client.js

`1.  const { Client } = require('@elastic/elasticsearch');
2.  const config = require('config');
3.  const fs = require('fs')

5.  const elasticConfig = config.get('elastic');

7.  // const client = new Client ( {
8.  //   node: elasticConfig.elasticsearch_endpoint, 
9.  //    auth: {
10.  //      apiKey: elasticConfig.apiKey
11.  //    }, 
12.  //    tls: {
13.  //     ca: fs.readFileSync(elasticConfig.certificate),
14.  //     rejectUnauthorized: true
15.  //    }     
16.  // });

18.  const client = new Client ( {
19.    node: elasticConfig.elasticsearch_endpoint,
20.    auth: { 
21.      username: elasticConfig.username,
22.      password: elasticConfig.password
23.     }, 
24.     tls: {
25.      ca: fs.readFileSync(elasticConfig.certificate),
26.      rejectUnauthorized: true
27.     }
28.  });

31.  client.ping()
32.    .then(response => console.log("You are connected to Elasticsearch!"))
33.    .catch(error => console.error("Elasticsearch is not connected."))

35.  module.exports = client;` ![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

在上面,我使用了两种方法来连接到 Elasticsearch。一种是通过 username/password 的方式来进行连接:



1.  const client = new Client ( {
2.    node: elasticConfig.elasticsearch_endpoint,
3.    auth: { 
4.      username: elasticConfig.username,
5.      password: elasticConfig.password
6.     }, 
7.     tls: {
8.      ca: fs.readFileSync(elasticConfig.certificate),
9.      rejectUnauthorized: true
10.     }
11.  });


复制代码

而另外一种就是被注释掉的那个方法:



1.  const client = new Client ( {
2.    node: elasticConfig.elasticsearch_endpoint, 
3.     auth: {
4.       apiKey: elasticConfig.apiKey
5.     }, 
6.     tls: {
7.      ca: fs.readFileSync(elasticConfig.certificate),
8.      rejectUnauthorized: true
9.     }     
10.  });


复制代码

这个也是被推荐的方法。在实际的使用中,我们更推荐使用 API key 来进行连接。

我们首先来使用 username/password 的方式来进行连接。我们需要修改我们的 server.js 来进行验证:

server/server.js



1.  const express = require('express');
2.  const client = require('./elasticsearch/client');

4.  const app = express();

6.  const port = 5001;

8.  app.get('/', (req, res) => {
9.      res.send('Hello World!')
10.    })

12.  app.listen(port, () => console.log(`Server listening at http://localhost:${port}`));


复制代码

我们重新运行服务器。我们可以看到如下的输出:



1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ npm start

5.  > earthquake_app@1.0.0 start
6.  > nodemon server/server.js

8.  [nodemon] 2.0.20
9.  [nodemon] to restart at any time, enter `rs`
10.  [nodemon] watching path(s): *.*
11.  [nodemon] watching extensions: js,mjs,json
12.  [nodemon] starting `node server/server.js`
13.  Server listening at http://localhost:5001
14.  You are connected to Elasticsearch!


复制代码

上面的输出表明我们已经能够成功地连接到 Elasticsearch 了。

使用代码获取 API key

我们接下来可以通过代码来获得 API key,尽管我们可以通过其它的方法来获得。请详细阅读 “Elasticsearch:创建 API key 接口访问 Elasticsearch”。在这里,我们可以使用 Node.js 代码来动态地生成一个 API key。我们在 server 目录下创建如下的一个文件:

sever/create-api-key.js

``

1.  const client = require('./elasticsearch/client');

3.  async function generateApiKeys(opts) {
4.    const body = await client.security.createApiKey({
5.      body: {
6.        name: 'earthquake_app',
7.        role_descriptors: {
8.          earthquakes_example_writer: {
9.            cluster: ['monitor'],
10.            index: [
11.              {
12.                names: ['earthquakes'],
13.                privileges: ['create_index', 'write', 'read', 'manage'],
14.              },
15.            ],
16.          },
17.        },
18.      },
19.    });
20.    return Buffer.from(`${body.id}:${body.api_key}`).toString('base64');
21.  }

23.  generateApiKeys()
24.    .then(console.log)
25.    .catch((err) => {
26.      console.error(err);
27.      process.exit(1);
28.    });

``![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

我们使用如下的命令来运行这个 Node.js 的代码:



1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ ls server/create-api-key.js 
4.  server/create-api-key.js
5.  $ node server/create-api-key.js 
6.  You are connected to Elasticsearch!
7.  emZJSGFZVUJUN1gwSDQyLWRLaS06LVpHaXR1bm5RQnEybE4zOWoyd0g5Zw==


复制代码

我们可以把上面命令生成的 API key 写入到之前的 default.json 文件中。这样我们也可以通过 API key 的方式来访问 Elasticsearch 了,如果我们需要的话。这样 client.js 实际上可以写成:

server/elasticsearch/client.js

`1.  const { Client } = require('@elastic/elasticsearch');
2.  const config = require('config');
3.  const fs = require('fs')

5.  const elasticConfig = config.get('elastic');

7.  const client = new Client ( {
8.    node: elasticConfig.elasticsearch_endpoint, 
9.     auth: {
10.       apiKey: elasticConfig.apiKey
11.     }, 
12.     tls: {
13.      ca: fs.readFileSync(elasticConfig.certificate),
14.      rejectUnauthorized: true
15.     }     
16.  });

18.  // const client = new Client ( {
19.  //   node: elasticConfig.elasticsearch_endpoint,
20.  //   auth: { 
21.  //     username: elasticConfig.username,
22.  //     password: elasticConfig.password
23.  //    }, 
24.  //    tls: {
25.  //     ca: fs.readFileSync(elasticConfig.certificate),
26.  //     rejectUnauthorized: true
27.  //    }
28.  // });

30.  client.ping()
31.    .then(response => console.log("You are connected to Elasticsearch!"))
32.    .catch(error => console.error("Elasticsearch is not connected."))

34.  module.exports = client;` ![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

我们重新运行 server.js,我们可以看到如下的输出:



1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ npm start

5.  > earthquake_app@1.0.0 start
6.  > nodemon server/server.js

8.  [nodemon] 2.0.20
9.  [nodemon] to restart at any time, enter `rs`
10.  [nodemon] watching path(s): *.*
11.  [nodemon] watching extensions: js,mjs,json
12.  [nodemon] starting `node server/server.js`
13.  Server listening at http://localhost:5001
14.  You are connected to Elasticsearch!


复制代码

很显然,我们的 API key 方式是成功的。使用 API key 的好处是我们不必要暴露用户的密码在代码中,而且,我们甚至可以为这个 API key 来设置有效时间及权限。可以授予最小所需要的权限,以确保安全。

设置 RESTful API 调用以从源检索数据

现在我们的服务器正在运行并且 Elasticsearch 已连接,我们需要测试对 USGS 的 API 调用以接收初始数据。 在项目的根目录下,创建一个名为 routes 的文件夹和一个名为 api 的子文件夹。 在 api 文件夹中,创建一个名为 data.js 的文件并添加以下代码:



1.  $ pwd
2.  /Users/liuxg/demos/earthquake_app
3.  $ mkdir -p server/routes/api


复制代码

我在 routes/api 目录下创建一个如下的 data.js 文件:

server/routes/api/data.js

``

1.  require('log-timestamp');
2.  const express = require('express');
3.  const router = express.Router();
4.  const axios = require('axios')
5.  const client = require('../../elasticsearch/client');
6.  const URL = `https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson`;

8.  router.get('/earthquakes', async function (req, res) {
9.      console.log('Loading Application...');

11.      //======= Check that Elasticsearch is up and running =======\\
12.      pingElasticsearch = async () => {
13.          await client.ping(
14.              function(error,res) {
15.                  if (error) {
16.                      console.error('elasticsearch cluster is down!');
17.                  } else {
18.                      console.log('Elasticsearch Ready');
19.                  }
20.              }
21.          );
22.      }

24.      // ====== Get Data From USGS and then index into Elasticsearch
25.      indexAllDocs = async () => {
26.          try {
27.              console.log('Getting Data From Host')

29.              const EARTHQUAKES = await axios.get(`${URL}`,{
30.                  headers: {
31.                      'Content-Type': [
32.                          'application/json',  
33.                          'charset=utf-8' 
34.                      ]
35.                  }
36.              });

38.              console.log('Data Received!')

40.              results = EARTHQUAKES.data.features

42.              console.log('Indexing Data...')

44.              console.log(results)
45.              res.json(results)

47.              if (EARTHQUAKES.data.length) {
48.                  indexAllDocs();
49.              } else {
50.                  console.log('All Data Has Been Indexed!');
51.              };
52.          } catch (err) {
53.              console.log(err)
54.          };

56.          console.log('Preparing For The Next Data Check...');
57.      }

59.      console.log("Ping the Elasticsearch server");
60.      pingElasticsearch()

62.      console.log("Get data from USGS");
63.      indexAllDocs()
64.  });

66.  module.exports = router;

``![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

上面的代码使用 npm 包 Axios 对 USGS 地震 API 进行异步 API 调用。 收到数据后,它将显示为 JSON。 你还可以看到我们在页面顶部导入了一个名为 log-timestamp 的依赖项。 这将允许我们将时间戳添加到每个 console.log。

我们接下来修改 server.js 如下:

server/server.js

``

1.  const express = require('express');
2.  const client = require('./elasticsearch/client');

4.  const app = express();

6.  const port = 5001;

8.  //Define Routes
9.  const data = require('./routes/api/data')
10.  app.use('/api/data', data);

12.  app.get('/', (req, res) => {
13.      res.send('Hello World!')
14.    })

16.  app.listen(port, () => console.log(`Server listening at http://localhost:${port}`));

``![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

重新运行我们的 server.js。我们通过 Postman 或者其它的工具来对我们的 REST 接口进行访问:

localhost:5000/api/data/earthquakes
复制代码

从上面的输出中,我们可以看出来设计的 REST 接口工作是正常的。它含有一些收集来的数据。在所收集来的数据中,有一些数据是我们并不需要的。我们最终需要的数据是这样的:



1.  {
2.    "mag": 1.13,
3.    "place": "11km ENE of Coachella, CA",
4.    "time": 2022-05-02T20:07:53.266Z,
5.    "url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci40240408",
6.    "sig": 20,
7.    "type": "earthquake",
8.    "depth": 2.09,
9.    "coordinates": {
10.      "lat": 33.7276667,
11.      "lon": -116.0736667
12.    }
13.  }


复制代码

也就是说我们可以删除一下不需要的字段,并且我们需要转换一些字段,比如把 time 字段转换为我们想要的 @timestamp 字段。另外在写入 Elasticsearch 时,我们需要预先针对 coodinates 字段进行定义。它是一个 geo_point 类型的字段。

定义 mapping 及 pipeline

如上所示,我们需要的字段如上。我们可以如下的一个 earthquakes 索引。我们在 Kibana 的 console 中打入如下的命令:

`

1.  PUT earthquakes
2.  {
3.    "mappings": {
4.      "properties": {
5.        "@timestamp": {
6.          "type": "date"
7.        },
8.        "coordinates": {
9.          "type": "geo_point"
10.        },
11.        "depth": {
12.          "type": "float"
13.        },
14.        "mag": {
15.          "type": "float"
16.        },
17.        "place": {
18.          "type": "text",
19.          "fields": {
20.            "keyword": {
21.              "type": "keyword"
22.            }
23.          }
24.        },
25.        "sig": {
26.          "type": "short"
27.        },
28.        "type": {
29.          "type": "keyword"
30.        },
31.        "url": {
32.          "enabled": false
33.        }
34.      }
35.    }
36.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

在上面,我们针对索引的字段类型做如下的说明:

  • @timestamp:这是一个 date 字段类型的字段。我们希望的格式是 2022-05-02T20:07:53.266Z 而不是以 EPOC 形式显示的值,比如 1672471359610。这个字段有 time 转换而来
  • coordinates:这个是一个 geo_point 的字段。是地震发生的地理位置
  • place:这是一个 multi-field 字段。我们希望对这个字段进行统计,也可以针对它进行搜索
  • sig:这字段我们使用 short 类型,而不是 long。这样可以省去存储空间
  • type:这是一个 keyword 类型的字段。它只可以做数据分析统计之用
  • url:这个字段,我们既不想对它进行搜索,也不想对它进行统计,所有设置 enabled 为 false。这样可以省去分词的时间,从而提高摄入数据的速度

为此,我们可以针对上面的 data.js 做更进一步的修改:

server/routes/api/data.js

``

1.  const express = require('express');
2.  const router = express.Router();
3.  const axios = require('axios')
4.  const client = require('../../elasticsearch/client');
5.  const URL = `https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson`;

7.   //======= Check that Elasticsearch is up and running =======\\
8.  function pingElasticsearch() {
9.      console.log("ping .....")
10.      client.ping({
11.          requestTimeout: 30000,
12.        }, function(error,res) {
13.          if (error) {
14.              console.error('elasticsearch cluster is down!');
15.          } else {
16.              console.log('Elasticsearch Ready');
17.          }
18.      });
19.  };

21.  // ====== Get Data From USGS and then index into Elasticsearch
22.  indexAllDocs = async () => {
23.      try {
24.          const EARTHQUAKES = await axios.get(`${URL}`,{
25.              headers: {
26.                  'Content-Type': [
27.                      'application/json',  
28.                      'charset=utf-8' 
29.                  ]
30.              }
31.          });

33.          console.log('Getting Data From Host')

35.          results = EARTHQUAKES.data.features    

37.          results.map(
38.              async (results) => (
39.                (earthquakeObject = {
40.                  place: results.properties.place, //
41.                  time: results.properties.time,   //
42.                  url: results.properties.url,     //
43.                  sig: results.properties.sig,     //
44.                  mag: results.properties.mag,     //
45.                  type: results.properties.type,   //
46.                  longitude: results.geometry.coordinates[0], //
47.                  latitude: results.geometry.coordinates[1],  //
48.                  depth: results.geometry.coordinates[2],  //
49.                }),
50.                await client.index({
51.                  index: 'earthquakes',
52.                  id: results.id,
53.                  body: earthquakeObject
54.                })
55.              )
56.          );

58.          if (EARTHQUAKES.data.length) {
59.              indexAllDocs();
60.          } else {
61.              console.log('All Data Has Been Indexed!');
62.          };
63.      } catch (err) {
64.          console.log(err)
65.      };

67.      console.log('Preparing For The Next Data Check...');
68.  }

71.  //================== Official API Call ==================\\
72.  router.get('/earthquakes', function (req, res) {
73.      res.send('Running Application...');
74.      console.log('Loading Application...')

76.      indexAllDocs(res);

78.  });

80.  module.exports = router;

``![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

在上面,我们添加了把文档写入 Elasticsearch 的代码部分。我们使用地震数据的 id 作为 Elasticsearch 文档的 id。等服务器运行起来后,我们需要在 terminal 中打入如下的命令:

curl -XGET http://localhost:5001/api/data/earthquakes
复制代码

我们可以在 Kibana 中通过如下的命令来查看文档:

GET earthquakes/_search?filter_path=**.hits
复制代码

我们可以看到如下的结果:

`

1.  {
2.    "hits": {
3.      "hits": [
4.        {
5.          "_index": "earthquakes",
6.          "_id": "nc73827281",
7.          "_score": 1,
8.          "_source": {
9.            "place": "10km S of Laytonville, CA",
10.            "time": 1672505649740,
11.            "url": "https://earthquake.usgs.gov/earthquakes/eventpage/nc73827281",
12.            "sig": 63,
13.            "mag": 2.02,
14.            "type": "earthquake",
15.            "longitude": -123.4981689,
16.            "latitude": 39.5991669,
17.            "depth": 4.59
18.          }
19.        },
20.     ...

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

很显然,这个文档的 source 和我们之前的想要的格式还是不太一样。为了能够使的 time 转换为 @timestamp,我们可以在 Node.js 的代码中进行相应的转换。我们也可以采用 ingest pipeline 来实现相应的操作。我们定义如下的 ingest pipeine。

`

1.  POST _ingest/pipeline/_simulate
2.  {
3.    "pipeline": {
4.      "description": "This is for data transform for earthquake data",
5.      "processors": [
6.        {
7.          "date": {
8.            "field": "time",
9.            "formats": [
10.              "UNIX_MS"
11.              ]
12.          }
13.        }
14.        ]
15.    },
16.    "docs": [
17.      {
18.        "_source": {
19.          "place": "16km N of Borrego Springs, CA",
20.          "time": 1672507053210,
21.          "url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci40152271",
22.          "sig": 10,
23.          "mag": 0.81,
24.          "type": "earthquake",
25.          "longitude": -116.368,
26.          "latitude": 33.4013333,
27.          "depth": 2.91
28.        }
29.      }
30.    ]
31.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

在上面的命令中,我们使用 date processor 来把 time 转换为所需要的格式,并在默认的情况下把 target 设置为 @timestamp。上面命令运行的结果为:

`

1.  {
2.    "docs": [3.      {4.        "doc": {5.          "_index": "_index",6.          "_id": "_id",7.          "_version": "-3",8.          "_source": {9.            "sig": 10,10.            "mag": 0.81,11.            "depth": 2.91,12.            "@timestamp": "2022-12-31T17:17:33.210Z",13.            "latitude": 33.4013333,14.            "place": "16km N of Borrego Springs, CA",15.            "time": 1672507053210,16.            "type": "earthquake",17.            "url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci40152271",18.            "longitude": -116.36819.          },20.          "_ingest": {21.            "timestamp": "2023-01-01T00:31:03.544821Z"22.          }23.        }24.      }25.    ]
26.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

从上面的输出中,我们可以看出来 @timestamp 字段已经生成。它的值由 time 字段转换而来。我们还发现 latitude 及 longitude 并不是按照我们需要的格式来显示的。我们需要把它转化为另外一个像如下的对象:

我们可以通过 rename processor 来操作:

`

1.  POST _ingest/pipeline/_simulate
2.  {
3.    "pipeline": {
4.      "description": "This is for data transform for earthquake data",
5.      "processors": [
6.        {
7.          "date": {
8.            "field": "time",
9.            "formats": [
10.              "UNIX_MS"
11.              ]
12.          }
13.        },
14.        {
15.          "rename": {
16.            "field": "latitude",
17.            "target_field": "coordinates.lat"
18.          }
19.        },
20.        {
21.          "rename": {
22.            "field": "longitude",
23.            "target_field": "coordinates.lon"
24.          }
25.        }
26.      ]
27.    },
28.    "docs": [
29.      {
30.        "_source": {
31.          "place": "16km N of Borrego Springs, CA",
32.          "time": 1672507053210,
33.          "url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci40152271",
34.          "sig": 10,
35.          "mag": 0.81,
36.          "type": "earthquake",
37.          "longitude": -116.368,
38.          "latitude": 33.4013333,
39.          "depth": 2.91
40.        }
41.      }
42.    ]
43.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

在上面的命令中,我们通过 rename processor 来重新命名 longitude 及 latitude 两个字段。运行上面的代码,我们可以看到如下的结果:

`

1.  {
2.    "docs": [3.      {4.        "doc": {5.          "_index": "_index",6.          "_id": "_id",7.          "_version": "-3",8.          "_source": {9.            "sig": 10,10.            "mag": 0.81,11.            "depth": 2.91,12.            "@timestamp": "2022-12-31T17:17:33.210Z",13.            "coordinates": {14.              "lon": -116.368,15.              "lat": 33.401333316.            },17.            "place": "16km N of Borrego Springs, CA",18.            "time": 1672507053210,19.            "type": "earthquake",20.            "url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci40152271"21.          },22.          "_ingest": {23.            "timestamp": "2023-01-01T00:38:42.729604Z"24.          }25.        }26.      }27.    ]
28.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

很显然,我们看到了一个新的 coordinates 的字段。它是一个 object。我们发现有一个多余的字段叫做 time。这个并不是我们所需要的。我们可以通过 remove processor 来删除这个字段。

`

1.  POST _ingest/pipeline/_simulate
2.  {
3.    "pipeline": {
4.      "description": "This is for data transform for earthquake data",
5.      "processors": [
6.        {
7.          "date": {
8.            "field": "time",
9.            "formats": [
10.              "UNIX_MS"
11.              ]
12.          }
13.        },
14.        {
15.          "rename": {
16.            "field": "latitude",
17.            "target_field": "coordinates.lat"
18.          }
19.        },
20.        {
21.          "rename": {
22.            "field": "longitude",
23.            "target_field": "coordinates.lon"
24.          }
25.        },
26.        {
27.          "remove": {
28.            "field": "time"
29.          }
30.        }
31.      ]
32.    },
33.    "docs": [
34.      {
35.        "_source": {
36.          "place": "16km N of Borrego Springs, CA",
37.          "time": 1672507053210,
38.          "url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci40152271",
39.          "sig": 10,
40.          "mag": 0.81,
41.          "type": "earthquake",
42.          "longitude": -116.368,
43.          "latitude": 33.4013333,
44.          "depth": 2.91
45.        }
46.      }
47.    ]
48.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

我们运行上面的命令。我们再次查看输出的结果:

`

1.  {
2.    "docs": [3.      {4.        "doc": {5.          "_index": "_index",6.          "_id": "_id",7.          "_version": "-3",8.          "_source": {9.            "sig": 10,10.            "mag": 0.81,11.            "depth": 2.91,12.            "@timestamp": "2022-12-31T17:17:33.210Z",13.            "coordinates": {14.              "lon": -116.368,15.              "lat": 33.401333316.            },17.            "place": "16km N of Borrego Springs, CA",18.            "type": "earthquake",19.            "url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci40152271"20.          },21.          "_ingest": {22.            "timestamp": "2023-01-01T00:44:46.919265Z"23.          }24.        }25.      }26.    ]
27.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

很显然这个时候,我们的 time 字段不见了。

在上面,我们通过 _simulate 的端点测试好了我们的 ingest pipeline。接下来,是我们使用命令来创建这个 pipeline 的时候了。我们使用如下的命令来创建这个 pipeline:

`

1.  PUT _ingest/pipeline/earthquake_data_pipeline
2.  {
3.    "description": "This is for data transform for earthquake data",
4.    "processors": [
5.      {
6.        "date": {
7.          "field": "time",
8.          "formats": [
9.            "UNIX_MS"
10.          ]
11.        }
12.      },
13.      {
14.        "rename": {
15.          "field": "latitude",
16.          "target_field": "coordinates.lat"
17.        }
18.      },
19.      {
20.        "rename": {
21.          "field": "longitude",
22.          "target_field": "coordinates.lon"
23.        }
24.      },
25.      {
26.        "remove": {
27.          "field": "time"
28.        }
29.      }
30.    ]
31.  }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

运行上面的命令。这样我们就创建了一个叫做 earthquake_data_pipeline 的 ingest pipeline。

接下来,我们需要删除之前所创建的索引,因为它包含我们不需要的一些字段:

DELETE earthquakes
复制代码

我们再次运行之前创建索引 earthquakes 的命令:

`

1.  PUT earthquakes
2.      {
3.        "mappings": {
4.          "properties": {
5.            "@timestamp": {
6.              "type": "date"
7.            },
8.            "coordinates": {
9.              "type": "geo_point"
10.            },
11.            "depth": {
12.              "type": "float"
13.            },
14.            "mag": {
15.              "type": "float"
16.            },
17.            "place": {
18.              "type": "text",
19.              "fields": {
20.                "keyword": {
21.                  "type": "keyword"
22.                }
23.              }
24.            },
25.            "sig": {
26.              "type": "short"
27.            },
28.            "type": {
29.              "type": "keyword"
30.            },
31.            "url": {
32.              "enabled": false
33.            }
34.          }
35.        }
36.      }

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

我们接下来需要修改 data.js 文件来使用这个 ingest pipeline:

server/routes/api/data.js

``

1.  const express = require('express');
2.  const router = express.Router();
3.  const axios = require('axios')
4.  const client = require('../../elasticsearch/client');
5.  const URL = `https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson`;

7.   //======= Check that Elasticsearch is up and running =======\\
8.  function pingElasticsearch() {
9.      console.log("ping .....")
10.      client.ping({
11.          requestTimeout: 30000,
12.        }, function(error,res) {
13.          if (error) {
14.              console.error('elasticsearch cluster is down!');
15.          } else {
16.              console.log('Elasticsearch Ready');
17.          }
18.      });
19.  };

21.  // ====== Get Data From USGS and then index into Elasticsearch
22.  indexAllDocs = async () => {
23.      try {
24.          const EARTHQUAKES = await axios.get(`${URL}`,{
25.              headers: {
26.                  'Content-Type': [
27.                      'application/json',  
28.                      'charset=utf-8' 
29.                  ]
30.              }
31.          });

33.          console.log('Getting Data From Host')

35.          results = EARTHQUAKES.data.features    

37.          results.map(
38.              async (results) => (
39.                (earthquakeObject = {
40.                  place: results.properties.place, 
41.                  time: results.properties.time,   
42.                  url: results.properties.url,     
43.                  sig: results.properties.sig,     
44.                  mag: results.properties.mag,     
45.                  type: results.properties.type,   
46.                  longitude: results.geometry.coordinates[0], 
47.                  latitude: results.geometry.coordinates[1], 
48.                  depth: results.geometry.coordinates[2], 
49.                }),
50.                await client.index({
51.                  index: 'earthquakes',
52.                  id: results.id,
53.                  body: earthquakeObject,
54.                  pipeline: 'earthquake_data_pipeline'
55.                })
56.              )
57.          );

59.          if (EARTHQUAKES.data.length) {
60.              indexAllDocs();
61.          } else {
62.              console.log('All Data Has Been Indexed!');
63.          };
64.      } catch (err) {
65.          console.log(err)
66.      };

68.      console.log('Preparing For The Next Data Check...');
69.  }

72.  //================== Official API Call ==================\\
73.  router.get('/earthquakes', function (req, res) {
74.      res.send('Running Application...');
75.      console.log('Loading Application...')

77.      setInterval(() => { 
78.          pingElasticsearch()
79.          indexAllDocs(res);
80.      }, 120000);

82.  });

84.  module.exports = router;

``![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

在上面的代码中,我对一下的两处做了修改:

我们再次使用如下的命令来启动对数据的采集:

curl -XGET http://localhost:5001/api/data/earthquakes
复制代码

稍等一点时间(超过2分钟),我们到 Kibana 中来查看数据:

GET earthquakes/_search
复制代码

我们可以看到如下的数据:

`

1.  {
2.    "took": 0,
3.    "timed_out": false,
4.    "_shards": {
5.      "total": 1,
6.      "successful": 1,
7.      "skipped": 0,
8.      "failed": 0
9.    },
10.    "hits": {
11.      "total": {
12.        "value": 9,
13.        "relation": "eq"
14.      },
15.      "max_score": 1,
16.      "hits": [
17.        {
18.          "_index": "earthquakes",
19.          "_id": "us7000j1cr",
20.          "_score": 1,
21.          "_source": {
22.            "sig": 340,
23.            "mag": 4.7,
24.            "depth": 181.449,
25.            "@timestamp": "2023-01-01T06:39:45.239Z",
26.            "coordinates": {
27.              "lon": 70.8869,
28.              "lat": 36.5351
29.            },
30.            "place": "36 km S of Jurm, Afghanistan",
31.            "type": "earthquake",
32.            "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us7000j1cr"
33.          }
34.        },
35.    ...

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

从上面,我们可以看出来有9个地震数据已经被写入。我们可以让应用运行一段时间,它可能会有更多的数据进来。比如:

`

1.  {
2.    "took": 0,
3.    "timed_out": false,
4.    "_shards": {
5.      "total": 1,
6.      "successful": 1,
7.      "skipped": 0,
8.      "failed": 0
9.    },
10.    "hits": {
11.      "total": {
12.        "value": 10,
13.        "relation": "eq"
14.      },
15.      "max_score": 1,
16.      "hits": [
17.        {
18.          "_index": "earthquakes",
19.          "_id": "nc73827436",
20.          "_score": 1,
21.          "_source": {
22.            "sig": 252,
23.            "mag": 4.04,
24.            "depth": 4.51,
25.            "@timestamp": "2023-01-01T06:49:08.930Z",
26.            "coordinates": {
27.              "lon": -121.220665,
28.              "lat": 36.5789986
29.            },
30.            "place": "9km NW of Pinnacles, CA",
31.            "type": "earthquake",
32.            "url": "https://earthquake.usgs.gov/earthquakes/eventpage/nc73827436"
33.          }
34.        },

`![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
复制代码

我们可以看到10个数据。

从上面的数据中,我们可以看到最终的数据结构就是我们想要的数据结构。

在接下来的文章中,我将详细描述如何对这个数据进行可视化。我将使用 Kibana 来进行展示,也会使用 Web 来进行搜索。请参阅文章 “Elasticsearch:使用 Node.js 将实时数据提取到 Elasticsearch 中(二)

为了方便大家的学习,我把源代码放在这里:github.com/liu-xiao-gu…

参考:

【1】medium.com/@webdevmark…

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改