使用函数计算构建图片处理应用

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

这篇文章介绍了如何利用阿里云函数计算的多种独特功能来构建图片处理 Web 服务。

介绍函数计算

函数计算 是阿里云的 Serverless 计算平台,通过函数计算,工程师只需编写几行代码,即可开发互联网规模化服务。它可以无缝地进行资源管理、弹性伸缩和负载均衡,使开发人员可以专注于业务逻辑的开发,而不必费心管理底层基础架构,从而轻松构建可快速响应新信息的应用程序。在内部,我们利用容器技术并开发专有分布式算法,依托弹性扩展的资源编排用户的代码。我们已在内部开发了许多尖端技术,以求为我们的用户提供高可扩展性、可靠性和优异性能的服务。

在本指南中,我们将向您介绍展示其创新功能的分步式教程。如果这是您首次使用函数计算平台,您可以阅读此 快速入门指南 以熟悉基本的 Serverless 概念。

使用网络文件系统

我们推出的 访问 NAS 的功能,支持函数访问阿里云 文件存储服务

益处

该平台的 Serverless 性质意味着每次调用时,用户代码可以在不同的实例上运行。这也意味着,函数不能依赖其本地文件系统来存储任何中间结果。开发人员必须依赖其他云服务,如 对象存储服务,在各函数或调用之间共享处理的结果。这不是理想做法,因为引入其他分布式服务将带来额外的开发成本,并增加代码的复杂性。

为了攻克此难题,我们开发了“访问文件存储 (NAS)”功能。NAS 是另一项阿里云服务,它提供高度可扩展、可靠且可用的分布式文件系统,并支持 标准文件访问协议。我们可以将远程 NAS 文件系统挂载到用户代码借以运行的资源上,从而有效地为函数代码创建一个“本地”文件系统。

图片爬虫示例

此演示部分介绍了如何创建 Serverless Web 爬虫程序,可以从种子网页下载所有图片。在 Serverless 平台上运行该程序是一个挑战,因为它不能在给定时间限制下,通过一个函数获取所有网站信息。但是,通过使用 NAS 功能,这一问题便迎刃而解,用户可以使用 NAS 文件系统在各次函数运行之间共享数据。下面我们介绍分步式教程。 我们假定您已理解 VPC 的概念,并知道如何在 VPC 中创建 NAS 挂载点。 如果您不了解这些内容,则可在继续以下步骤之前阅读 基础 NAS 教程

使用 NAS 配置创建服务

1.登录 函数计算控制台
2.选择您的 NAS 所在的目标地域。
3.创建使用预创建的 NAS 文件系统的服务。在本演示中:
Screen Shot 2018-09-11 at 11.35.38 PM.png
1.输入服务名称描述
2.启用高级设置
3.填写VPC 配置字段,确保您选择的 VPC 是 NAS 挂载点所在的 VPC。
Screen Shot 2018-09-11 at 11.37.28 PM.png
完成VPC 配置后,会显示NAS 配置字段。

  1. 按如下示例,填写NAS 配置字段:
    NAS Config.jpg

    1.UserIdGroupId字段是函数在其下运行的 uid/gid。它们决定了在 NAS 文件系统上创建的所有文件的所有者。在此次演示中,您可以选择任何用户/设备组名,它们将在本服务中的所有函数间共享。
    2.NAS 挂载点下拉菜单列出了可从所选 VPC 访问的所有有效 NAS 挂载点。
    3.远程路径是 NAS 文件系统的一个目录,它不需要是 NAS 文件系统的根目录。请选择用于存储图片的目录。
    4.本地挂载路径是函数可以访问远程目录的本地目录,请记住您在此处选择的内容。
    5.使用 日志服务 为您所需的日志库目标配置日志。

    1. 确保您配置了 角色 以授权函数计算平台访问 VPC 和日志库。
      7.点击确定

创建一个每五分钟启动一次的函数

我们已经创建了带有 NAS 访问的服务,现在可以编写爬虫程序了。 由于爬虫程序函数必须运行多次才能完成,因此我们使用 时间触发器 每 5 分钟调用一次。
1.登录到函数计算控制台,并选择您刚刚创建的服务
2.通过单击加号,为服务创建 函数
Create Function.jpg
3.函数计算平台提供了各种函数模板,可帮助您快速构建应用程序。选择为此演示创建一个空函数,然后单击“下一步”。但是如果您有时间,也可以试用其他模板。
4.在下一页的下拉菜单中选择时间触发器。填写触发器名称,将调用间隔设置为 5 分钟,将事件留空,然后单击“下一步”。
Time Trigger.jpg
5.填写函数名称,并确保选择 java8 runtime。然后填写函数处理程序,将内存设置为 2048MB,将“超时时间”设置为 300 秒,然后单击“下一步”
java function.jpg
6.单击“下一步”并检查预览,然后单击“创建”。

在 Java 中写入爬虫程序

现在,您应该会看到函数代码页,可以写入爬虫程序了。处理程序逻辑非常简单,如下所示。

  • 解析时间触发器事件以获取爬网程序配置。
  • 根据配置创建图片爬网程序。爬网程序使用 JAVA HTML 解析器 解析 HTML 页,以识别图片和链接。
  • 读取 NAS 文件系统中已被和尚未被访问的 Web 页面列表(仅当此函数在新环境中运行时)。
  • 继续对 Web 页面的深度优先遍历,并使用爬虫程序下载这一过程中发现的任何新图片。
  • 将新找到的 Web 页面保存到 NAS 文件系统。
    下面是 Java 代码的摘录,您可以看到,我们以与本地文件系统相同的方式向 NAS 文件系统读取和写入文件。

    public class ImageCrawlerHandler implements PojoRequestHandler<TimedCrawlerConfig, CrawlingResult> {
         
      private String nextUrl() {
         
          String nextUrl;
          do {
         
              nextUrl = pagesToVisit.isEmpty() ? "" : pagesToVisit.remove(0);
          } while (pagesVisited.contains(nextUrl) );
          return nextUrl;
      }
    
      private void initializePages(String rootDir) throws IOException {
         
          if (this.rootDir.equalsIgnoreCase(rootDir)) {
         
              return;
          }
          try {
         
              new BufferedReader(new FileReader(rootDir + CRAWL_HISTORY)).lines()
                  .forEach(l -> pagesVisited.add(l));
              new BufferedReader(new FileReader(rootDir + CRAWL_WORKITEM)).lines()
                  .forEach(l -> pagesToVisit.add(l));
          } catch (FileNotFoundException e) {
         
              logger.info(e.toString());
          }
          this.rootDir = rootDir;
      }
    
      private void saveHistory(String rootDir, String justVistedPage, HashSet<String> newPages)
          throws IOException {
         
          //将爬网历史记录附加到文件末尾
          try (PrintWriter pvfw = new PrintWriter(
              new BufferedWriter(new FileWriter(rootDir + CRAWL_HISTORY, true)));
          ) {
         
              pvfw.println(justVistedPage);
          }
          //将等待爬网的工作条目附加到文件末尾
          try (PrintWriter ptfw = new PrintWriter(
              new BufferedWriter(new FileWriter(rootDir + CRAWL_WORKITEM, true)));
          ) {
         
              newPages.stream().forEach(p -> ptfw.println(p));
          }
      }
    
      @Override
      public CrawlingResult handleRequest(TimedCrawlerConfig timedCrawlerConfig, Context context) {
         
          CrawlingResult crawlingResult = new CrawlingResult();
          this.logger = context.getLogger();
          CrawlerConfig crawlerConfig = null;
          try {
         
              crawlerConfig = JSON_MAPPER.readerFor(CrawlerConfig.class)
                  .readValue(timedCrawlerConfig.payload);
          } catch (IOException e) {
         
              ....
          }
          ImageCrawler crawler = new ImageCrawler(
              crawlerConfig.rootDir, crawlerConfig.cutoffSize, crawlerConfig.debug, logger);
          int pagesCrawled = 0;
          try {
         
              initializePages(crawlerConfig.rootDir);
              if (pagesToVisit.isEmpty()) {
         
                  pagesToVisit.add(crawlerConfig.url);
              }
              while (pagesCrawled < crawlerConfig.numberOfPages) {
         
                  String currentUrl = nextUrl();
                  if (currentUrl.isEmpty()) {
         
                      break;
                  }
                  HashSet<String> newPages = crawler.crawl(currentUrl);
                  newPages.stream().forEach(p -> {
         
                      if (!pagesVisited.contains(p)) {
         
                          pagesToVisit.addAll(newPages);
                      }
                  });
                  pagesCrawled++;
                  pagesVisited.add(currentUrl);
                  saveHistory(crawlerConfig.rootDir, currentUrl, newPages);
              }
              //计算图片的总大小
             .....
          } catch (Exception e) {
         
              crawlingResult.errorStack = e.toString();
          }
    
          crawlingResult.totalCrawlCount = pagesVisited.size();
          return crawlingResult;
      }
    }
    
    public class ImageCrawler {
         
    ...
    public HashSet<String> crawl(String url) {
         
          links.clear();
          try {
         
              Connection connection = Jsoup.connect(url).userAgent(USER_AGENT);
              Document htmlDocument = connection.get();
              Elements media = htmlDocument.select("[src]");
              for (Element src : media) {
         
                  if (src.tagName().equals("img")) {
         
                      downloadImage(src.attr("abs:src"));
                  }
              }
              Elements linksOnPage = htmlDocument.select("a[href]");
              for (Element link : linksOnPage) {
         
                  logDebug("Plan to crawl `" + link.absUrl("href") + "`");
                  this.links.add(link.absUrl("href"));
              }
    
          } catch (IOException ioe) {
         
             ...
          }
          return links;
      }
    }
    

    为了简单起见,我们省略了一些细节和其他帮助类程序。如果您想要运行该代码,从您喜爱的网站上抓取图片,那么您可以从 awesome-fc github project 中获取完整代码。

运行爬虫程序

现在,代码已经编写好,可以运行了。步骤如下。

  • 我们使用 maven 来执行依赖关系和版本管理。在与 Maven 库同步后(假定您已安装 Maven),只需键入以下命令,即可创建要上传的 jar 文件。
mvn clean package
  • 在函数页面中选择“代码”选项卡。通过控制台上传在上一步中创建的 jar 文件(以依赖项的名称结尾)。
    java function.jpg
  • 在函数页面中选择“触发器”选项卡。单击时间触发器链接,以 Json 格式输入事件。Json 事件将被序列化到爬网程序配置并传递到函数。单击“确定”。
    timer event.jpg
  • 时间触发器每五分钟调用一次爬虫程序功能。 每次调用时,处理程序将拾取仍需要访问的 URL 列表,并从第一个开始。
  • 您可以选择“日志”选项卡来搜索爬虫程序执行日志。

创建 Serverless 服务

我们推出的第二项 功能 允许任何人发送 HTTP 请求,以直接触发函数执行。

益处

现在,我们已经有了装有从 Web 下载的图片的文件系统,我们希望找到一种方式,来通过 Web 服务提供这些图片。传统方法是将 NAS 挂载到虚拟机,然后在上面启动 Webserver。这既会浪费资源(如果该服务较少使用),也无法在数据流量大时扩展。作为替代方案,您可以编写一个 Serverless 函数,来读取存储在 NAS 文件系统上的图片,并通过 HTTP 访问域名提供图片。通过这种方式,您可以享受函数计算平台提供的即时可扩展性,同时仍只支付实际使用费用。

图片处理服务示例

此演示说明如何编写图片处理服务。

创建带有 HTTP 触发器的函数

1.登录到函数计算控制台,并选择与爬网程序函数相同的服务。
2.单击加号,为服务创建函数
3.选择创建空的 python2.7 函数,然后单击“下一步”。
4.在下拉菜单中选择 HTTP 触发器,并确保它支持 GETPOST 调用方法,然后单击“下一步”。
HTTP Trigger.jpg
5.完成该步骤的剩余部分,然后单击“确定”。
6.从同一 github 库中获取 文件,然后将目录上传到该函数。
Upload Dir.jpg

使用 Python 处理图片

函数计算平台的 python 运行时附带了许多可供使用的内置模块。在本示例中,我们同时使用 [opencv] 和 wand 来执行图片转换。

使用 Python 中的 HTTP 触发器

即使对于图片处理函数,我们也需要设置网站以处理请求。一般情况下,需要使用另一种服务(如 API 网关)来处理 HTTP 请求。在本演示中,我们将使用函数计算平台的 HTTP 触发器 功能,允许 HTTP 请求直接触发函数执行。使用 HTTP 触发器,HTTP 请求中的报头/路径/查询会全部直接传递至函数处理程序,函数可以动态返回 HTML 内容。

基于这两种功能,处理程序代码非常简单,步骤如下。

  • 从系统 environ 变量获取 HTTP 路径和查询。
  • 使用 HTTP 路径加载 NAS 文件系统上的图片。
  • 基于查询 action 应用不同的图片处理技术。
  • 将转换后的图片插入预构建的 HTML 文件并返回。

下面是处理程序逻辑的摘录,我们可以看到,wand 像加载本地系统上的常规文件一样,加载存储在 NAS 上的图片。

import cv2
from wand.image import Image

TEMPLATE = open('/code/index.html').read()
NASROOT = '/mnt/crawler'
face_cascade = cv2.CascadeClassifier('/usr/share/opencv/lbpcascades/lbpcascade_frontalface.xml')

def handler(environ, start_response):
    logger = logging.getLogger()
    context = environ['fc.context']
    path = environ.get('PATH_INFO', "/")
    fileName = NASROOT + path

    try:
        query_string = environ['QUERY_STRING']
        logger.info(query_string)
    except (KeyError):
        query_string = " "

    action = query_dist['action']

    if (action == "show"):
        with Image(filename=fileName) as fc_img:
            img_enc = base64.b64encode(fc_img.make_blob(format='png'))

    elif (action == "facedetect"):
        img = cv2.imread(fileName)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(gray, 1.03, 5)
        for (x, y, w, h) in faces:
            cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 1)
        cv2.imwrite("/tmp/dst.png", img)
        with open("/tmp/dst.png") as img_obj:
            with Image(file=img_obj) as fc_img:
                img_enc = base64.b64encode(fc_img.make_blob(format='png'))
    elif (action == "rotate"):
        assert len(queries) >= 2
        angle = query_dist['angle']
        logger.info("Rotate " + angle)
        with Image(filename=fileName) as fc_img:
            fc_img.rotate(float(angle))
            img_enc = base64.b64encode(fc_img.make_blob(format='png'))
    else:
        # demo, mixed operation

    status = '200 OK'
    response_headers = [('Content-type', 'text/html')]
    start_response(status, response_headers)
    return [TEMPLATE.replace('{fc-py}', img_enc)]

后续步骤

现在我们已准备好函数和 HTTP 触发器,我们可以尝试 [图片旋转] 或像 [面部检测]之类的高级转换。

  • URL 基于您的accountID/地域/服务/函数名构建,可参考 文章
  • 使用指向图片的本地 NAS 挂载目录的相对路径。您可以通过爬网程序日志找到 NAS 系统上的所有文件。
  • 您可以直接在函数计算控制台上编辑 Python 代码,添加更多不同的图片转换程序并乐在其中。

结论

  • 您可以阅读 博客 以进一步了解 函数计算 平台的功能。
  • 您还可以阅读官方 NAS 教程 和其他函数计算平台 文档,以了解更多令人兴奋的新功能。
  • 请通过我们的官方函数计算 论坛 或官方阿里云 Slack 频道提供反馈或建议。
相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
5天前
|
人工智能 Serverless API
尽享红利,Serverless构建企业AI应用方案与实践
本次课程由阿里云云原生架构师计缘分享,主题为“尽享红利,Serverless构建企业AI应用方案与实践”。课程分为四个部分:1) Serverless技术价值,介绍其发展趋势及优势;2) Serverless函数计算与AI的结合,探讨两者融合的应用场景;3) Serverless函数计算AIGC应用方案,展示具体的技术实现和客户案例;4) 业务初期如何降低使用门槛,提供新用户权益和免费资源。通过这些内容,帮助企业和开发者快速构建高效、低成本的AI应用。
42 12
|
23天前
|
运维 Serverless 测试技术
通义灵码 x 函数计算:构建高效开发流程,加速项目交付
本方案基于通义大模型的通义灵码,提供代码生成、补全、优化及单元测试生成等能力,提升编码效率和质量。结合云效和函数计算 FC 进行代码管理、持续集成、部署发布,加速项目交付,为开发者提供智能编码、CI/CD、部署上线体验,加快产品迭代速度。
|
6月前
|
Java Serverless 应用服务中间件
Serverless 应用引擎操作报错合集之部署python项目时,构建过程报错,怎么解决
Serverless 应用引擎(SAE)是阿里云提供的Serverless PaaS平台,支持Spring Cloud、Dubbo、HSF等主流微服务框架,简化应用的部署、运维和弹性伸缩。在使用SAE过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
3月前
|
Cloud Native 关系型数据库 Serverless
基于阿里云函数计算(FC)x 云原生 API 网关构建生产级别 LLM Chat 应用方案最佳实践
本文带大家了解一下如何使用阿里云Serverless计算产品函数计算构建生产级别的LLM Chat应用。该最佳实践会指导大家基于开源WebChat组件LobeChat和阿里云函数计算(FC)构建企业生产级别LLM Chat应用。实现同一个WebChat中既可以支持自定义的Agent,也支持基于Ollama部署的开源模型场景。
691 30
|
4月前
|
数据可视化 NoSQL Serverless
现代化 Web 应用构建问题之Serverless架构的Web站点费用计算如何解决
现代化 Web 应用构建问题之Serverless架构的Web站点费用计算如何解决
53 1
|
4月前
|
Serverless 对象存储
现代化 Web 应用构建问题之配置Serverless Devs的秘钥信息如何解决
现代化 Web 应用构建问题之配置Serverless Devs的秘钥信息如何解决
50 1
|
5月前
|
运维 监控 关系型数据库
阿里云Serverless高可用架构深度评测:构建稳定高效应用的全面指南
随着云计算技术的迅猛发展,Serverless计算作为一种新兴的、以事件驱动的无服务器架构,正在逐渐改变企业构建、部署和管理应用程序的方式。阿里云,作为全球领先的云服务提供商之一,提供了全面的Serverless解决方案,包括PolarDB MySQL Serverless集群和Serverless应用引擎等产品,致力于帮助用户构建高可用、高弹性、低成本的应用系统。本文将深度评测阿里云的Serverless服务,从产品功能、使用体验、部署常见问题、文档与支持的全面性等维度出发,为开发者和企业提供实用的参考。
125 0
|
5月前
|
Serverless Docker Python
函数计算操作报错合集之使用Kaniko构建镜像时遇到了报错,该怎么办
在使用函数计算服务(如阿里云函数计算)时,用户可能会遇到多种错误场景。以下是一些常见的操作报错及其可能的原因和解决方法,包括但不限于:1. 函数部署失败、2. 函数执行超时、3. 资源不足错误、4. 权限与访问错误、5. 依赖问题、6. 网络配置错误、7. 触发器配置错误、8. 日志与监控问题。
|
5月前
|
数据采集 JSON Serverless
通过百炼大模型+FC函数计算构建小红书图文工作流
使用阿里云函数服务和百炼平台,快速构建小红书图文创作工作流。通过两步轻松创建: 1) 在函数计算中利用Puppeteer构建卡片生成服务; 2) 在百炼平台上创建工作流,整合大模型、脚本和函数计算节点,实现图文内容的自动化处理和生成。此方案适合高效创作小红书内容。
1517 6
|
5月前
|
人工智能 运维 Serverless
基于 Serverless 计算快速构建AI应用开发陪跑班开课啦!
云端问道第8期开课啦!参与直播间动手实操即可获得保温杯,参与直播间活动可抽奖无线充电器!!您将在课程中学习到基于Serverless技术函数计算FC实现基于Serverless 构建企业级AI应用的解决方案,降低 GPU的使用成本、减少企业或个人创业的试错成本、简化开发运维,让人人都可以拥有自己“专属”的AIGC环境成为可能!阿里云技术专家将手把手带您实操,还将针对实操中的问题进行一对一答疑!机会难得,快来参加吧!
278 9

相关产品

  • 函数计算