Azure Functions 的实例是否共享变量?

5

我不确定问题是否清晰,但这是我观察到的情况。我的Azure函数使用BlobTrigger处理上传到Blob Storage的PDF文件。一切正常,直到我一次性上传多个blob,在这种情况下,使用以下代码,我观察到以下情况:

  • 第一个context.getLogger()正确记录触发函数的每个blob。

  • 在Azure文件共享中,每个PDF文件都被正确保存。

  • 许多情况下,第二个context.getLogger()返回不正确的结果(来自其他文件之一),好像变量在我的函数实例之间共享。请注意,lines[19]对于每个PDF都是唯一的。

  • 我注意到类似的行为在我的代码后面也出现了,其中错误的PDF数据被记录下来。

编辑:为明确起见,我知道当多个实例并行运行时,日志不会按顺序排列。然而,当我上传10个文件时,与lines [19]相关的大部分结果都是重复的,并且随着代码的进行,这个问题会恶化,当基于X执行Y时,有10个调用中有9个产生垃圾数据。

Main.class

public class main {
   @FunctionName("veninv")
       @StorageAccount("Storage")
       public void blob(
           @BlobTrigger(
                   name = "blob",
                   dataType = "binary",
                   path = "veninv/{name}") 
               byte[] content,
           @BindingName("name") String blobname,
           final ExecutionContext context
           ) {

         context.getLogger().info("BlobTrigger by: " + blobname + "(" + content.length + " bytes)");

           //Writing byte[] to a file in Azure Functions file storage
               File tempfile = new File (tempdir, blobname);
               OutputStream os = new FileOutputStream(tempfile);
               os.write(content);
               os.close();

               String[] lines  = Pdf.getLines(tempfile);
               context.getLogger().info(lines[19]);
           }
    }

Pdf.class

   public static String[] getLines(File PDF) throws Exception {
           PDDocument doc = PDDocument.load(PDF);
           PDFTextStripper pdfStripper = new PDFTextStripper();
           String text = pdfStripper.getText(doc);
           lines = text.split(System.getProperty("line.separator"));
           doc.close();
           return lines;
   }

我不太明白这里发生了什么,所以希望能得到一些帮助。


这些文件不是在并行处理吗?你的函数可以同时为多个请求提供服务,你不能真正期望日志按顺序记录。 - Abhilash
打开你的文件并检查它们的内容。这些内容不会被分享,变量也是如此。 - Abhilash
当我进入监视器>调用并检查每个调用时,日志是否在此处保持在一起?无论如何,当我上传10个文件时,我希望打印出10个不同的行(无论它们以什么顺序出现),但有时我只收到来自同一文件的多个重复行。 - AlexanderJ
经过大量的调试,我现在确信变量会从一个实例泄漏到另一个实例。我现在有几个案例,其中多次记录相同的变量而不对其进行任何操作,显示它随机更改(基于另一个实例的数据)。我认为为了我的解决方案,我需要使用队列来防止同时处理超过1个blob。 - AlexanderJ
@AlexanderJ,经过下面几个答案的讨论,现在根本原因已经清楚了,您能否将最合适的答案标记为“已接受”,以此来结束讨论并帮助那些可能遇到类似问题的人。 - krishg
3个回答

5

是的,Azure函数调用可以共享变量。我需要看到所有的代码才能确定100%,但看起来lines对象被声明为static,它可以在调用之间共享。让我们尝试从static String[]更改为String[],看看问题是否解决?

Azure函数很容易上手,但很容易忘记执行环境。您的函数调用并不像它们看起来那样隔离。有一个父线程调用您的函数,并且许多静态变量不是“线程安全”的。静态变量表示全局状态,因此可以全局访问。此外,它没有附加到任何特定的对象实例。变量的“静态性”与其所在的内存空间有关,而不是其值。因此,在引用它的所有类实例中都可以访问相同的变量。

附注:您在此处通过减少并发来解决问题,但这可能会影响可扩展性。我建议进行负载测试。另外,静态变量是有用的,许多线程安全,您想在Azure函数中使用它们,例如httpClient或sqlClient DB连接!阅读第三个链接此处


所以这与Azure Function无关。显然,“静态”字段会在类的多个实例之间共享时引起此问题。而Function绝不声称每个执行都具有“完全”隔离。感谢详细回复。 - krishg
没错。这很可能是一个“静态”的问题。但是,我认为指出这些类型的问题不太直观,因为Azure函数和“无服务器”通常鼓励我们不去考虑服务器(执行环境)。我见过一些开发人员(包括我自己)由于“无服务器”的“神奇”而忽略了静态变量。这很棒也很有趣,但也很容易忘记确实有一个服务器(共享环境)在调用您的函数。 - Troy Witthoeft
嗯,我只是想给OP一点喘息时间。这确实是一个复杂的话题。不使用静态变量是一个好的经验法则,但这是一个过度简化的说法。这实际上是关于线程安全性的问题,有些静态变量是不安全的,例如static String[],而其他的如static HttpClient则是安全的!除非查看它们的源代码,否则谁知道呢?但我不知道你怎么想,反正我周五不会这么做。:D对于一些良好使用Azure函数静态变量(包括DB连接)的方法,请参见此处的第3条= https://medium.com/@raunaknarooka/tips-and-tricks-for-azure-functions-c9db11aee3dc。 - Troy Witthoeft
浪费了一天时间进行调试 :-( - Valentin Petkov
@Valentin_Petkov。学习这个并不好玩。自从两年前的2020年写下这个答案后,看起来dotnet 5和dotnet 6可以让您使用隔离的函数来减轻这种情况。 - Troy Witthoeft
显示剩余4条评论

1

不,很难相信函数会有如此严重的问题。我看到一些潜在的问题可能是导致你这种情况的原因:

  1. 你确定每次上传文件时都为每个文件上传到不同的唯一 Blob 吗?您可以通过记录 blobname 参数来进行检查。
  2. 由于您将文件存储在临时目录中 File tempfile = new File (tempdir, blobname);,如果 blob 名称与#1中提到的相同,则会覆盖最后一个写入。如果可能直接从字节或流构造pdf,您可以考虑使用它来代替在文件系统中创建中间文件。如果我没记错,您正在使用 PDFBox,它支持从 byte[] https://pdfbox.apache.org/docs/2.0.3/javadocs/index.html?org/apache/pdfbox/pdmodel/PDDocument.html 加载(检查接受 byte[] 的 load 方法重载)。我还回答了您的另一个问题与此相关。
  3. 检查是否有静态字段导致此问题。
  4. 您不需要使用您想要引入的单独队列。如果实际问题已经解决,则根本不需要它,Blob 触发器已经使用内部队列,默认并发性为 24,但是您可以在 host.json 中进行配置。 https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob-trigger?tabs=java#concurrency-and-memory-usage

更新:

看起来在你的pdf类中,你在方法外部声明了一个'static'的'lines'变量,这是问题产生的根本原因。这和函数本身没有关系,而是与'static'有关 :)

以下是正确的代码(注意现在'lines'变量是方法本地变量):

public static String[] getLines(File PDF) throws Exception {
           PDDocument doc = PDDocument.load(PDF);
           PDFTextStripper pdfStripper = new PDFTextStripper();
           String text = pdfStripper.getText(doc);
           String[] lines = text.split(System.getProperty("line.separator"));
           doc.close();
           return lines;
   }

1
  1. 所有的 Blob 都有一个唯一的名称,代表着独特的文件。当我运行我的程序时,对于 x 个 Blob,我得到了 x 个预期的独特文件在 tempdir 中。
  2. 谢谢,我之前不知道,我会尝试直接加载 byte[] 并查看是否有任何变化。
  3. 是的,我稍早就看到了那份文档,我肯定会考虑将其添加到我的函数中,因为我想限制并发 SQL 查询和 Sharepoint 上传的数量。
- AlexanderJ
对于回复晚了表示歉意,非常感谢,这正是我需要的!我还不是一个经验丰富的程序员,在我的类中使用静态方法时并没有完全理解它的功能。下次我一定会更加注意这些细节 :) - AlexanderJ
没问题。很高兴你加入了社区。即使是经验丰富的开发人员,'静态'有时也会让他们感到困惑,不知所措 :)。 - krishg

0

想要分享一下,将host.json更改为以下内容,以停止并发函数调用,似乎已经解决了我的问题:

{
    "version": "2.0",
    "extensions": {
        "queues": {
            "batchSize": 1,
            "newBatchThreshold": 0
        }
    }
}

非常感谢 @KrishnenduGhosh-MSFT 的帮助。我仍然不确定为什么并发函数调用会导致我遇到的问题,但考虑到我的程序还连接到一个 SQL 数据库和 Sharepoint 网站(都被限流了),顺序处理是最好的解决方案。


很高兴听到这个好消息,谢谢。但如果能找出并发 Blob 触发器实际问题的根本原因,我会更加开心。我会尝试从你在问题中分享的代码(希望那是全部)中模拟你的场景,并看看是否能够捕捉到魔鬼 :)。 - krishg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接