Azure Functions:昂贵对象的单例

Azure Functions: Singleton for expensive object

我创建了一些非常简单的 Azure 函数。他们从 Couchbase 读取和写入数据(在虚拟机上的 Azure 中 运行)。

我担心我在 Azure 函数中与 Couchbase 建立的连接。我每次都创建一个 Cluster 对象。这是一项昂贵的操作,我通常只会在普通的 Web 应用程序中执行一次。但是在 Azure Functions 中,我每次都new启动它。

除了 Couchbase 之外,像这样实例化对象的成本很高。有没有办法创建 Azure Functions 可以在调用之间重用的单例或某种共享对象?

您可以使用普通的单例,即静态 属性 其中 returns 某物的单个实例。与往常一样,注意线程安全,例如按照@Jesse 的建议使用 Lazy<T>

您还可以在第一次调用您的函数之前使用静态构造函数进行初始化。根据定义,静态构造函数是线程安全的。

在这两种情况下,您都可以在同一实例(服务器)上 运行 的所有调用之间重复使用昂贵的东西。

昂贵的连接对象的静态属性可以很好地工作,但我建议将它们包装在 Lazy<> 中,这样您就可以保证开箱即用的线程安全性。

根据您链接到一个示例的示例博客 post,该示例以保证线程安全的方式使存储桶可在所有函数调用中重复使用,可能如下所示:

public class FunctionClass
{
    private static Lazy<IBucket> LazyBucket = new Lazy<IBucket>(() =>
    {
        var uri = ConfigurationManager.AppSettings["couchbaseUri"];
        var cluster = new Cluster(new ClientConfiguration
        {
            Servers = new List<Uri> { new Uri(uri) }
        });

        var bucketName = ConfigurationManager.AppSettings["couchbaseBucketName"];
        var bucketPassword = ConfigurationManager.AppSettings["couchbaseBucketPassword"];

        return cluster.OpenBucket(bucketName, bucketPassword);
    });

    // Your actual function implementation
    public static async Task Run()
    {
        // Here you are guaranteed to get back a shared connection object to your bucket that has been
        // initalized only once in a thread safe way
        var initalizedOnceBucket = LazyBucket.Value;

        // do something with the bucket
    }
}

如果应该共享的昂贵对象的构造依赖于一些异步调用(我怀疑 Couchbase C# 客户端可能具有其方法的异步版本)。您可以使用 Stephen Cleary 编写的令人敬畏的 Nito.AsyncEx Nuget 包中的 AsyncLazy<>。常规 Lazy<> 内置于 .NET 中,因此不需要任何外部依赖项。

在 Azure Functions 上处理单例时,有几个注意事项。一是全局状态 在 AF 调用之间共享。因此,如果一个函数被调用一次然后稍后再次调用(很快主机还没有卸载您的代码),那么初始化只会发生一次。另一个考虑因素是 AF 可以完全自由地同时启动多个 AF 调用 - 因此任何单例都需要是线程安全的(包括它们的初始化)。

这意味着您需要使用 Lazy<T> / AsyncLazy<T>。但是,请记住,AF(具有这些类型)将为您的下一次调用保留单例状态(post-初始化),即使它失败。这可能是云计算的一个问题,因为如果在您的 AF 启动时出现网络(或配置)错误,您希望在下一次 AF 调用时重试初始化。

总而言之,您想以线程安全的方式使用 Lazy<T> / AsyncLazy<T> 并且 不会保留失败。

Lazy<T>,这意味着you have to use the LazyThreadSafetyMode.PublicationOnly flag 将函数传递给构造函数(不只是隐式使用T的默认构造函数).请注意,这意味着您需要确保您的初始化函数本身是线程安全的,因为它可以由多个线程同时执行。

AsyncLazy<T>you have to use the AsyncLazyFlags.RetryOnFailure flag。由于 AsyncLazy<T> 本质上是一个 Lazy<Task<T>>,异步初始化任务在所有同时调用者中是 "shared",如果失败则自动替换为新的 Lazy<Task<T>> 实例。所以异步初始化函数不需要是线程安全的。

由于完全正确(尤其是对于多个单身人士)是相当复制粘贴的,所以我将其抽象出来用于 the AF project I'm working on:

完成这一点花了一些时间,但我对结果非常满意。也想写博客...

您可以使用 Azure 函数本身的 [Singleton] 属性来确保同步调用队列函数等。但是,如果线程随后休眠,则可以读取下一个队列项。所以我使用了它并在我的代码中围绕昂贵的调用实现了单例模式,以确保关键代码在任何时候只执行一次。

例如

[Singleton(Mode = SingletonMode.Function)]
[FunctionName("TimerFunction")]

单例模式:

public sealed class PowerBiCapacitySingleton
{
    private static PowerBiCapacitySingleton _instance;
    private static readonly object Padlock = new object();

    private PowerBiCapacitySingleton()
    {
    }

    public static PowerBiCapacitySingleton Instance
    {
        get
        {
            lock (Padlock)
            {
                if (_instance == null)
                {
                    _instance = new PowerBiCapacitySingleton();
                }
                return _instance;
            }
        }
    }

    // code goes here
    ...
}