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

本文涉及的产品
函数计算FC,每月15万CU 3个月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 本文以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

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
2月前
|
运维 Kubernetes 前端开发
拥抱Knative, 合思加速Serverless化演进实践
合思信息基于阿里云容器服务Knative, 实现Serverless化演进的最佳实践。
拥抱Knative, 合思加速Serverless化演进实践
|
3月前
|
弹性计算 关系型数据库 Serverless
函数计算驱动多媒体文件处理:高效、稳定与成本优化实践
本次测评的解决方案《告别资源瓶颈,函数计算驱动多媒体文件处理》展示了如何利用阿里云函数计算高效处理多媒体文件。文档结构清晰、内容详实,适合新客户参考。方案提供了一键部署与手动部署两种方式,前者简便快捷,后者灵活性高但步骤较多。通过部署,用户可体验到基于函数计算的文件处理服务,显著提升处理效率和系统稳定性。此外,测评还对比了应用内处理文件与函数计算处理文件的不同,突出了函数计算在资源管理和成本控制方面的优势。
22716 19
|
3月前
|
前端开发 小程序 Serverless
异步任务处理系统问题之阿里云函数计算FC的应用场景有哪些
异步任务处理系统问题之阿里云函数计算FC的应用场景有哪些
|
3月前
|
数据可视化 NoSQL Serverless
现代化 Web 应用构建问题之Serverless架构的Web站点费用计算如何解决
现代化 Web 应用构建问题之Serverless架构的Web站点费用计算如何解决
43 1
|
3月前
|
Serverless 对象存储
现代化 Web 应用构建问题之配置Serverless Devs的秘钥信息如何解决
现代化 Web 应用构建问题之配置Serverless Devs的秘钥信息如何解决
40 1
|
3月前
|
运维 Kubernetes Serverless
Serverless Argo Workflows荣获信通院标杆实践案例,引领大规模离线任务处理新方法
阿里云容器服务Serverless Argo Workflows大规模离线计算工作流平台荣获2024信通院Serveless实践标杆案例。本文介绍其应用场景、平台特性以及领域实践。
|
3月前
|
前端开发 大数据 数据库
🔥大数据洪流下的决战:JSF 表格组件如何做到毫秒级响应?揭秘背后的性能魔法!💪
【8月更文挑战第31天】在 Web 应用中,表格组件常用于展示和操作数据,但在大数据量下性能会成瓶颈。本文介绍在 JavaServer Faces(JSF)中优化表格组件的方法,包括数据处理、分页及懒加载等技术。通过后端分页或懒加载按需加载数据,减少不必要的数据加载和优化数据库查询,并利用缓存机制减少数据库访问次数,从而提高表格组件的响应速度和整体性能。掌握这些最佳实践对开发高性能 JSF 应用至关重要。
70 0
|
3月前
|
Kubernetes Serverless 调度
异步任务处理系统问题之在阿里云函数计算平台上用户提交异步任务的问题如何解决
异步任务处理系统问题之在阿里云函数计算平台上用户提交异步任务的问题如何解决
|
4月前
|
域名解析 运维 Serverless
函数计算产品使用问题之设置最大实例数为1和最大并发数为20,当请求数量超过20时,系统会如何处理
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
4月前
|
运维 监控 关系型数据库
阿里云Serverless高可用架构深度评测:构建稳定高效应用的全面指南
随着云计算技术的迅猛发展,Serverless计算作为一种新兴的、以事件驱动的无服务器架构,正在逐渐改变企业构建、部署和管理应用程序的方式。阿里云,作为全球领先的云服务提供商之一,提供了全面的Serverless解决方案,包括PolarDB MySQL Serverless集群和Serverless应用引擎等产品,致力于帮助用户构建高可用、高弹性、低成本的应用系统。本文将深度评测阿里云的Serverless服务,从产品功能、使用体验、部署常见问题、文档与支持的全面性等维度出发,为开发者和企业提供实用的参考。
115 0

热门文章

最新文章

相关产品

  • 函数计算