共用方式為


管理 Azure AI 搜尋服務中的並行

管理如索引及資料來源的 Azure AI 搜尋服務資源時,能夠安全地更新資源是一件很重要的事,尤其是在應用程式中有不同的元件正在同時存取資源時。 當兩個用戶端在未經協調的情況下同時更新資源時,便可能發生競爭情形。 為了避免這個問題,Azure AI 搜尋服務會使用「開放式並行存取模型」。 資源上不會有鎖定的情形。 每個資源都會有一個能識別資源版本的 ETag,使您可以制定能避免意外覆寫的要求。

運作方式

開放式同步存取的實作方式,是透過對寫入索引、索引子、資料來源、技能及 synonymMap 資源的 API 呼叫進行存取條件檢查。

所有資源都有能提供物件版本資訊的實體標記 (ETag)。 透過先檢查 ETag 並確保資源的 ETag 符合您的本機複本,將可以避免在一般工作流程 (取得,於本機修改,更新) 中發生同時更新。

每次更新資源時,該資源的 ETag 都會自動變更。 當您實作並行管理時,所做的就是為更新要求設置前置條件,要求遠端資源的 ETag 必須與您在用戶端上所修改之資源複本的 ETag 相同。 如果另一個流程變更遠端資源,則 ETag 不符合前置條件,且要求失敗並出現 HTTP 412。 如果您是使用 .NET SDK,此失敗會顯示為例外狀況,其中 IsAccessConditionFailed() 擴充方法會傳回 true。

注意

並行只有一種機制。 無論使用哪個 API 或 SDK 進行資源更新,都只會使用這個機制。

範例

下列程式碼示範更新作業的開放式並行存取。 因為前一個更新變更了物件的 ETag,所以第二次更新會失敗。 更具體來說,當要求標頭中的 ETag 不再符合物件的 ETag 時,搜尋服務會傳回狀態碼 400 (不正確的要求),而且更新會失敗。

using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
using System;
using System.Net;
using System.Threading.Tasks;

namespace AzureSearch.SDKHowTo
{
    class Program
    {
        // This sample shows how ETags work by performing conditional updates and deletes
        // on an Azure Search index.
        static void Main(string[] args)
        {
            string serviceName = "PLACEHOLDER FOR YOUR SEARCH SERVICE NAME";
            string apiKey = "PLACEHOLDER FOR YOUR SEARCH SERVICE ADMIN API KEY";

            // Create a SearchIndexClient to send create/delete index commands
            Uri serviceEndpoint = new Uri($"https://{serviceName}.search.windows.net/");
            AzureKeyCredential credential = new AzureKeyCredential(apiKey);
            SearchIndexClient adminClient = new SearchIndexClient(serviceEndpoint, credential);

            // Delete index if it exists
            Console.WriteLine("Check for index and delete if it already exists...\n");
            DeleteTestIndexIfExists(adminClient);

            // Every top-level resource in Azure Search has an associated ETag that keeps track of which version
            // of the resource you're working on. When you first create a resource such as an index, its ETag is
            // empty.
            SearchIndex index = DefineTestIndex();

            Console.WriteLine(
                $"Test searchIndex hasn't been created yet, so its ETag should be blank. ETag: '{index.ETag}'");

            // Once the resource exists in Azure Search, its ETag is populated. Make sure to use the object
            // returned by the SearchIndexClient. Otherwise, you will still have the old object with the
            // blank ETag.
            Console.WriteLine("Creating index...\n");
            index = adminClient.CreateIndex(index);
            Console.WriteLine($"Test index created; Its ETag should be populated. ETag: '{index.ETag}'");


            // ETags prevent concurrent updates to the same resource. If another
            // client tries to update the resource, it will fail as long as all clients are using the right
            // access conditions.
            SearchIndex indexForClientA = index;
            SearchIndex indexForClientB = adminClient.GetIndex("test-idx");

            Console.WriteLine("Simulating concurrent update. To start, clients A and B see the same ETag.");
            Console.WriteLine($"ClientA ETag: '{indexForClientA.ETag}' ClientB ETag: '{indexForClientB.ETag}'");

            // indexForClientA successfully updates the index.
            indexForClientA.Fields.Add(new SearchField("a", SearchFieldDataType.Int32));
            indexForClientA = adminClient.CreateOrUpdateIndex(indexForClientA);

            Console.WriteLine($"Client A updates test-idx by adding a new field. The new ETag for test-idx is: '{indexForClientA.ETag}'");

            // indexForClientB tries to update the index, but fails due to the ETag check.
            try
            {
                indexForClientB.Fields.Add(new SearchField("b", SearchFieldDataType.Boolean));
                adminClient.CreateOrUpdateIndex(indexForClientB);

                Console.WriteLine("Whoops; This shouldn't happen");
                Environment.Exit(1);
            }
            catch (RequestFailedException e) when (e.Status == 400)
            {
                Console.WriteLine("Client B failed to update the index, as expected.");
            }

            // Uncomment the next line to remove test-idx
            //adminClient.DeleteIndex("test-idx");
            Console.WriteLine("Complete.  Press any key to end application...\n");
            Console.ReadKey();
        }


        private static void DeleteTestIndexIfExists(SearchIndexClient adminClient)
        {
            try
            {
                if (adminClient.GetIndex("test-idx") != null)
                {
                    adminClient.DeleteIndex("test-idx");
                }
            }
            catch (RequestFailedException e) when (e.Status == 404)
            {
                //if an exception occurred and status is "Not Found", this is working as expected
                Console.WriteLine("Failed to find index and this is because it's not there.");
            }
        }

        private static SearchIndex DefineTestIndex() =>
            new SearchIndex("test-idx", new[] { new SearchField("id", SearchFieldDataType.String) { IsKey = true } });
    }
}

設計模式

實作開放式並行存取的設計模式應包含一個迴圈,其會重新嘗試存取條件檢查、測試存取條件,並選擇性擷取更新資源,然後再嘗試重新套用變更。

此程式碼片段說明如何將 synonymMap 新增至已存在的索引。

該程式碼片段會取得 "hotels" 索引,檢查更新作業的物件版本,在條件失敗的情況下擲回例外狀況,然後重試該作業 (最多三次),並從自伺服器擷取索引以取得最新版本開始。

private static void EnableSynonymsInHotelsIndexSafely(SearchServiceClient serviceClient)
{
    int MaxNumTries = 3;

    for (int i = 0; i < MaxNumTries; ++i)
    {
        try
        {
            Index index = serviceClient.Indexes.Get("hotels");
            index = AddSynonymMapsToFields(index);

            // The IfNotChanged condition ensures that the index is updated only if the ETags match.
            serviceClient.Indexes.CreateOrUpdate(index, accessCondition: AccessCondition.IfNotChanged(index));

            Console.WriteLine("Updated the index successfully.\n");
            break;
        }
        catch (Exception e) when (e.IsAccessConditionFailed())
        {
            Console.WriteLine($"Index update failed : {e.Message}. Attempt({i}/{MaxNumTries}).\n");
        }
    }
}

private static Index AddSynonymMapsToFields(Index index)
{
    index.Fields.First(f => f.Name == "category").SynonymMaps = new[] { "desc-synonymmap" };
    index.Fields.First(f => f.Name == "tags").SynonymMaps = new[] { "desc-synonymmap" };
    return index;
}

另請參閱