简体   繁体   中英

How do you update TableServiceClient in ASP.NET Core, to point to a secondary region, when using Azure geo redundant (RA-GRS) table storage?

I am using the latest Azure.Data.Tables nuget package, version 12.3.0 to connect to Azure table storage in an ASP.NET Core C# Application.

My application needs to failover to a secondary region for reads if the primary region fails.

Currently the setup the of TableServiceClient is done in the Startup.cs as follows:

public void ConfigureServices(IServiceCollection services)
{     
   services.AddSingleton(new TableServiceClient(new Uri("PrimaryRegionConnectionURL"), new DefaultAzureCredential()));
}

How do would I update the current instance of TableServiceClient with an instance pointed to the secondary region? Is there a better approach to achieve this failover?

Just to Clarify: I am aware that the client doesn't support failing over and the team have created a ticket to look at this feature in future. I realize I need to have a new instance of TableServiceClient .

I am just not sure how I would replace the one created at startup with a new instance pointed to the secondary instance at the time of failure.

Here is that code that consumes the TableServiceClient

    public class TableRepository : ITableStorageRepository
{
    readonly TableServiceClient _serviceClient;

    public TableRepository(TableServiceClient serviceClient)
    {
        _serviceClient = serviceClient;
    }

    public async Task<ICollection<T>> GetPartitionEntities<T>(string partitionKey, string tableName)
        where T : class, ITableEntity, new()
    {
        var listOfEntities = new List<T>();

        var tableClient = _serviceClient.GetTableClient(tableName);

        var queryResults = tableClient.QueryAsync<T>(filter => filter.PartitionKey == partitionKey);

        await foreach (var row in queryResults) 
        {
            listOfEntities.Add(row);
        }

        return listOfEntities;
    }
}

Automatic failover using the same client is not currently supported; to use the secondary region, a separate TableServiceClient would be needed. More context is available here: https://github.com/Azure/azure-sdk-for-net/issues/25456

Work for adding support is being tracked here: https://github.com/Azure/azure-sdk-for-net/issues/25710

Not sure if this is the best way to accomplish it, but this is how I would have done it considering I have to handle the logic of switching between primary and secondary endpoint myself.

First, I would have created two instances of TableServiceClient - one for the primary and other for secondary.

public void ConfigureServices(IServiceCollection services)
{  
    Dictionary<string, TableServiceClient> tableServiceClients = new Dictionary()
    {
      "Primary",  new TableServiceClient(new Uri("PrimaryRegionConnectionURL"), new DefaultAzureCredential()),
      "Secondary",  new TableServiceClient(new Uri("SecondaryRegionConnectionURL"), new DefaultAzureCredential())
    }
    services.AddSingleton(tableServiceClients);
}

Next, I would have extracted the logic for fetching the entities in a separate function and passed the client to that function (let's call that GetPartitionEntitiesImpl ).

Then in GetPartitionEntities method I would have tried to get the entities from the primary endpoint and catch the exception. If the exception indicates primary endpoint failure, I would have called GetPartitionEntitiesImpl function again and try to fetch the entities from the secondary endpoint.

public class TableRepository : ITableStorageRepository
{
    readonly TableServiceClient _primaryServiceClient, _secondaryServiceClient;

    public TableRepository(Dictionary<string, TableServiceClient> tableServiceClients)
    {
        _primaryServiceClient = tableServiceClients["Primary"];
        _secondaryServiceClient = tableServiceClients["Secondary"];
    }

    public async Task<ICollection<T>> GetPartitionEntities<T>(string partitionKey, string tableName)
        where T : class, ITableEntity, new()
    {
        try
        {
            return await GetPartitionEntitiesImpl(_primaryServiceClient, partitionKey, tableName);
        }
        catch (Exception exception)
        {
          //Check if there is a need for failover
          if (shouldTrySecondaryEndpoint)
          {
              return await GetPartitionEntitiesImpl(_secondaryServiceClient, partitionKey, tableName);
          }
        }
    }

    private async Task<ICollection<T>> GetPartitionEntitiesImpl<T>(TableServiceClient serviceClient, string partitionKey, string tableName)
        where T : class, ITableEntity, new()
    {
        var listOfEntities = new List<T>();

        var tableClient = serviceClient.GetTableClient(tableName);

        var queryResults = tableClient.QueryAsync<T>(filter => filter.PartitionKey == partitionKey);

        await foreach (var row in queryResults) 
        {
            listOfEntities.Add(row);
        }

        return listOfEntities;
    }

}

Also, please take a look at the code of older Azure Storage SDK (version 9.x) regarding the logic for switching between primary and secondary endpoints. That SDK handles this scenario rather well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM