实践篇:利用函数计算轻松构建全文检索系统

本文涉及的产品
简介: 本文以OSS作为云存储服务的例子,OpenSearch作为搜索服务的例子,通过阿里云函数计算,实现一个简单高效的针对文本文档的全文检索系统。

前言

随着云存储的广泛使用,文档数量与日俱增,越来越多的同学提出了这样的疑问:如何在众多文档中,快速定位到自己想找的文档呢?如何能快速搭建起基于存储服务的全文搜索系统呢?如何让搜索服务及时反映文档的增删改呢?

这一切,函数计算都可以轻松帮你实现。

本文以OSS作为云存储服务的例子,OpenSearch作为搜索服务的例子,通过阿里云函数计算,实现一个简单高效的针对文本文档的全文检索系统。

技术方案

Picture1

具体实现

1.开通阿里云对象存储(Object Storage Service,简称OSS)
阿里云对象存储服务(OSS)为用户提供基于网络的数据存取服务,用户可以通过网络随时存储和调用包括文本,图片,音频和视频等在内的各种非结构化数据文件。具体开通方式请参考阿里云OSS快速入门
本示例中,开通OSS之后在“华北2”区域新建名为“fc-search-demo”的bucket,类型为标准存储,如下图所示。更多配置选项,请参考创建存储空间以及具体需求选择。

Picture2

2.开通阿里云开放搜索(OpenSearch)
阿里云开放搜索(OpenSearch)是一款结构化数据搜索托管服务,为用户提供简单,高效,稳定,低成本和可扩展的搜索解决方案。具体开通方式请参考开放搜索快速入门
本示例中,开通OpenSearch之后在“华北2”区域新建了名为“oss_fc_search”的应用,类型为高级版,如下图所示。更多配置选项,请参考应用类型以及具体需求选择。

Picture3

应用创建成功后,根据业务场景编辑您的应用结构,包括定义数据表,字段以及分词类型。详细配置说明请参考字段类型和分词类型
本示例是针对文本文档创建索引,创建了一个main数据表,采用常规的字段,如title,author,content等等,并使用中文基础分词。如下图所示:
Picture4

3.开通函数计算(Function Compute)
函数计算是一个事件驱动的全托管计算服务,用户编写代码上传到函数计算,然后通过SDK或者RESTful API来触发执行函数,也可以通过云产品的事件来触发执行函数。具体开通方式请参考函数计算快速入门
本示例开通函数服务后,在“华北2”区域新建名为“oss-fc-search”的服务,如下图所示:
Picture5
服务创建成功后,开始创建函数。将本文提供的java代码,pom文件build成jar包上传。

package SearchDemo;

import com.aliyun.fc.runtime.*;
import com.aliyun.opensearch.DocumentClient;
import com.aliyun.opensearch.OpenSearchClient;
import com.aliyun.opensearch.sdk.dependencies.com.google.common.collect.Maps;
import com.aliyun.opensearch.sdk.generated.OpenSearch;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchResult;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.OSSObject;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import java.io.*;
import java.util.*;

public class EventHandler implements StreamRequestHandler {

    private static final String OSS_ENDPOINT = "YourOSSEndpoint";
    private static final String OPENSEARCH_APP_NAME = "YourOpenSearchAppName";
    private static final String OPENSEARCH_HOST = "YourOpenSearchHost";
    private static final String OPENSEARCH_TABLE_NAME = "YourOpenSearchTableName";
    private static final String ACCESS_KEY_ID = "YourAccessKeyId";
    private static final String ACCESS_KEY_SECRET = "YourAccessSecretId";
    private static final String DOC_URL_FORMAT = "http://%s.%s/%s";

    private static final List<String> addEventList = Arrays.asList(
            "ObjectCreated:PutObject", "ObjectCreated:PostObject");
    private static final List<String> updateEventList = Arrays.asList(
            "ObjectCreated:AppendObject");
    private static final List<String> deleteEventList = Arrays.asList(
            "ObjectRemoved:DeleteObject", "ObjectRemoved:DeleteObjects");

    @Override
    public void handleRequest(
            InputStream inputStream, OutputStream outputStream, Context context) throws IOException {

        /*
         * Preparation
         * Init logger, oss client, open search document client.
         */
        FunctionComputeLogger fcLogger = context.getLogger();
        OSSClient ossClient = getOSSClient(context);
        DocumentClient documentClient = getDocumentClient();

        /*
         * Step 1
         * Read oss event from input stream.
         */
        JSONObject ossEvent;
        StringBuilder inputBuilder = new StringBuilder();
        BufferedReader streamReader = null;
        try {
            streamReader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = streamReader.readLine()) != null) {
                inputBuilder.append(line);
            }
            fcLogger.info("Read object event success.");
        } catch(Exception ex) {
            fcLogger.error(ex.getMessage());
            return;
        } finally{
            closeQuietly(streamReader, fcLogger);
        }
        ossEvent = JSONObject.fromObject(inputBuilder.toString());
        fcLogger.info("Getting event: " + ossEvent.toString());

        /*
         * Step 2
         * Loop every events in oss event, and generate structured docs in json format.
         */
        JSONArray events = ossEvent.getJSONArray("events");
        for(int i = 0; i < events.size(); i++) {

            // Get event name, source, oss object.
            JSONObject event = events.getJSONObject(i);
            String eventName = event.getString("eventName");
            JSONObject oss = event.getJSONObject("oss");

            // Get bucket name and file name for file identifier.
            JSONObject bucket = oss.getJSONObject("bucket");
            String bucketName = bucket.getString("name");
            JSONObject object = oss.getJSONObject("object");
            String fileName = object.getString("key");

            // Prepare fields for commit to open search
            Map<String, Object> structuredDoc = Maps.newLinkedHashMap();
            BufferedReader objectReader = null;
            UUID uuid = new UUID(bucketName.hashCode(), fileName.hashCode());
            structuredDoc.put("identifier", uuid);

            try {
                // For delete event, delete by identifier
                if (deleteEventList.contains(eventName)) {
                    documentClient.remove(structuredDoc);
                } else {
                    OSSObject ossObject = ossClient.getObject(bucketName, fileName);

                    // Non delete event, read file content and more field you need
                    StringBuilder fileContentBuilder = new StringBuilder();
                    objectReader = new BufferedReader(
                            new InputStreamReader(ossObject.getObjectContent()));

                    String contentLine;
                    while ((contentLine = objectReader.readLine()) != null) {
                        fileContentBuilder.append('\n' + contentLine);
                    }
                    fcLogger.info("Read object content success.");

                    // You can put more fields according to your scenario
                    structuredDoc.put("title", fileName);
                    structuredDoc.put("content", fileContentBuilder.toString());
                    structuredDoc.put("subject", String.format(DOC_URL_FORMAT, bucketName, OSS_ENDPOINT, fileName));

                    if (addEventList.contains(eventName)) {
                        documentClient.add(structuredDoc);
                    } else if (updateEventList.contains(eventName)) {
                        documentClient.update(structuredDoc);
                    }
                }
            } catch (Exception ex) {
                fcLogger.error(ex.getMessage());
                return;
            } finally {
                closeQuietly(objectReader, fcLogger);
            }
        }

        /*
         * Step 3
         * Commit json docs string to open search
         */
        try {
            OpenSearchResult osr = documentClient.commit(OPENSEARCH_APP_NAME, OPENSEARCH_TABLE_NAME);
            if(osr.getResult().equalsIgnoreCase("true")) {
                fcLogger.info("OSS Object commit to OpenSearch success.");
            } else {
                fcLogger.info("Fail to commit to OpenSearch.");
            }
        } catch (OpenSearchException ex) {
            fcLogger.error(ex.getMessage());
            return;
        } catch (OpenSearchClientException ex) {
            fcLogger.error(ex.getMessage());
            return;
        }
    }

    protected OSSClient getOSSClient(Context context) {
        Credentials creds = context.getExecutionCredentials();
        return new OSSClient(
                OSS_ENDPOINT, creds.getAccessKeyId(), creds.getAccessKeySecret(), creds.getSecurityToken());
    }

    protected DocumentClient getDocumentClient() {
        OpenSearch openSearch = new OpenSearch(ACCESS_KEY_ID, ACCESS_KEY_SECRET, OPENSEARCH_HOST);
        OpenSearchClient serviceClient = new OpenSearchClient(openSearch);
        return new DocumentClient(serviceClient);
    }

    protected void closeQuietly(BufferedReader reader, FunctionComputeLogger fcLogger) {
        try {
            if (reader != null) {
                reader.close();
            }
        } catch (Exception ex) {
            fcLogger.error(ex.getMessage());
        }
    }
}

pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>YourGroupId</groupId>
    <artifactId>YourArtifactid</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>com.aliyun.fc.runtime</groupId>
            <artifactId>fc-java-core</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>com.aliyun.fc.runtime</groupId>
            <artifactId>fc-java-event</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>com.aliyun.opensearch</groupId>
            <artifactId>aliyun-sdk-opensearch</artifactId>
            <version>3.1.3</version>
        </dependency>
    </dependencies>


</project>

4.新建触发器并授权,参考创建触发器并授权
Picture6

使用效果

1.在所有的服务、触发器都创建好后,我们来看使用效果。首先准备两个文本文档(文档内容如下),并上传到OSS:
Picture7

Picture8

Picture9

2.进入开放搜索控制台,搜索测试:
搜索“杭州”,西湖.txt和阿里巴巴.txt都出现在搜索结果中,因为两个文档的内容中都包含“杭州”这个关键词。
Picture10

搜索“电子商务”, 返回一个结果,只有阿里巴巴.txt中包含“电子商务”
Picture11

3.在OSS删除文档后,OpenSearch中的数据也全部删除。
Picture12

搜索“杭州”关键词,没有文档返回。
Picture13

相关实践学习
基于函数计算一键部署掌上游戏机
本场景介绍如何使用阿里云计算服务命令快速搭建一个掌上游戏机。
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
1月前
|
人工智能 NoSQL Serverless
基于函数计算3.0 Stable Diffusion Serverless API 的AI艺术字头像生成应用搭建与实践的报告
本文主要分享了自己基于函数计算3.0 Stable Diffusion Serverless API 的AI艺术字头像生成应用搭建与实践的报告
459 6
基于函数计算3.0 Stable Diffusion Serverless API 的AI艺术字头像生成应用搭建与实践的报告
|
22天前
|
消息中间件 编解码 运维
阿里云 Serverless 异步任务处理系统在数据分析领域的应用
本文主要介绍异步任务处理系统中的数据分析,函数计算异步任务最佳实践-Kafka ETL,函数计算异步任务最佳实践-音视频处理等。
174882 337
|
2月前
|
弹性计算 Serverless 开发者
Serverless 应用引擎问题之镜像构建失败如何解决
在进行Serverless应用开发和部署时,开发者可能会遇到不同类型的报错信息;本合集着重收录了Serverless环境中常见的报错问题及其解决策略,以助于开发者迅速诊断和解决问题,保证服务的连续性和可用性。
446 2
|
6月前
|
编解码 人工智能 运维
课时9:典型案例2:函数计算在音视频场景实践(三)
典型案例2:函数计算在音视频场景实践
491 0
|
2月前
|
关系型数据库 Serverless 数据库
基于DTS Serverless构建一站式实时数据集成服务
在企业的数字化转型背景下,企业需要数据中台数据实时集成,提升数据分析的时效性。DTS推出了从数据库将业务数据实时同步到数据仓库的解决方案,帮助客户挖掘商机,调整商业策略。同时,为了解决在客户业务负载多变的情况下灵活稳定支持数据传输的问题,DTS推出了Serverless版本,支持按需自动弹性伸缩链路规格,客户可以按需付费,无需关心底层资源。此外,DTS还提供了数据校验的增值能力,帮助检验数据一致性,以免影响客户业务决策的准确性
50204 1
|
3月前
|
机器学习/深度学习 运维 安全
阿里云 ACK One Serverless Argo 助力深势科技构建高效任务平台
阿里云 ACK One Serverless Argo 助力深势科技构建高效任务平台
101163 8
|
3月前
|
运维 中间件 Java
淘宝权益玩法平台的Serverless化实践
淘宝权益玩法平台的Serverless化实践
214 0
|
3月前
|
SQL 弹性计算 运维
畅捷通的 Serverless 探索实践之路
畅捷通的 Serverless 探索实践之路
|
3月前
|
运维 Cloud Native Serverless
MSE Serverless 正式商用,构建低成本高弹性的微服务架构
MSE Serverless 正式商用,构建低成本高弹性的微服务架构
70467 76
|
3月前
|
关系型数据库 Serverless OLAP
构建高效数据流转的 ETL 系统:数据库 + Serverless 函数计算的最佳实践
构建高效数据流转的 ETL 系统:数据库 + Serverless 函数计算的最佳实践
79679 2

热门文章

最新文章

相关产品

  • 函数计算