Dělení spolehlivých služeb Service Fabric

Tento článek obsahuje úvod do základních konceptů dělení spolehlivých služeb Azure Service Fabric. Dělení umožňuje ukládání dat na místních počítačích, takže data a výpočetní prostředky je možné škálovat společně.

Tip

Kompletní ukázka kódu v tomto článku je k dispozici na GitHubu.

Dělení

Dělení není jedinečné pro Service Fabric. Ve skutečnosti se jedná o základní vzor vytváření škálovatelných služeb. V širším smyslu můžeme o dělení uvažovat jako o dělení stavu (dat) a výpočetních prostředků na menší přístupné jednotky, abychom zlepšili škálovatelnost a výkon. Známou formou dělení je dělení dat, označované také jako horizontální dělení.

Dělení bezstavových služeb Service Fabric

U bezstavových služeb můžete uvažovat o oddílu jako logické jednotce, která obsahuje jednu nebo více instancí služby. Obrázek 1 ukazuje bezstavovou službu s pěti instancemi distribuovanými napříč clusterem pomocí jednoho oddílu.

Bezstavová služba

Existují ve skutečnosti dva typy řešení bezstavových služeb. První z nich je služba, která uchovává svůj stav externě, například v databázi v Azure SQL Database (například na webu, který ukládá informace a data relace). Druhým jsou pouze výpočetní služby (jako je kalkulačka nebo miniatury obrázků), které nespravují žádný trvalý stav.

V obou případech je dělení bezstavové služby velmi vzácný scénář – škálovatelnost a dostupnost se obvykle dosahuje přidáním dalších instancí. Pro instance bezstavové služby chcete vzít v úvahu více oddílů pouze tehdy, když potřebujete splnit speciální požadavky směrování.

Představte si například případ, kdy by uživatele s ID v určitém rozsahu měla obsluhovat pouze konkrétní instance služby. Dalším příkladem, kdy můžete bezstavovou službu rozdělit na oddíly, je, když máte skutečně dělený back-end (např. horizontálně dělenou databázi v SQL Database) a chcete řídit, která instance služby by měla zapisovat do horizontálního oddílu databáze – nebo provádět jiné přípravné práce v rámci bezstavové služby, které vyžadují stejné informace o dělení, jaké se používají v back-endu. Tyto typy scénářů je také možné vyřešit různými způsoby a nemusí nutně vyžadovat dělení služby.

Zbývající část tohoto návodu se zaměřuje na stavové služby.

Dělení stavových služeb Service Fabric

Service Fabric usnadňuje vývoj škálovatelných stavových služeb tím, že nabízí prvotřídní způsob dělení stavu (dat). Koncepčně můžete oddíl stavové služby považovat za jednotku škálování, která je vysoce spolehlivá prostřednictvím replik distribuovaných a vyvážených napříč uzly v clusteru.

Dělení v kontextu stavových služeb Service Fabric odkazuje na proces určení, že určitý oddíl služby je zodpovědný za část kompletního stavu služby. (Jak už bylo zmíněno dříve, oddíl je sada replik). Na Service Fabric je skvělé, že umisťuje oddíly na různé uzly. To jim umožní růst až do limitu prostředků uzlu. S tím, jak data potřebují růst, se zvětšují oddíly a Service Fabric znovu vyrovnává oddíly mezi uzly. Tím se zajistí trvalé efektivní využívání hardwarových prostředků.

Pro příklad řekněme, že začnete s clusterem s pěti uzly a službou, která je nakonfigurovaná tak, aby měla 10 oddílů a cíl tří replik. V takovém případě by Service Fabric vyvažovaly a distribuovaly repliky napříč clusterem – a nakonec byste měli dvě primární repliky na uzel. Pokud teď potřebujete škálovat cluster na 10 uzlů, Service Fabric znovu vyrovná primární repliky napříč všemi 10 uzly. Podobně platí, že pokud byste škálovali zpět na 5 uzlů, Service Fabric by znovu vyrovnali všechny repliky mezi 5 uzly.

Obrázek 2 znázorňuje distribuci 10 oddílů před a po škálování clusteru.

Stavová služba

V důsledku toho se dosahuje horizontálního navýšení kapacity, protože požadavky od klientů se distribuují mezi počítače, celkový výkon aplikace se zlepší a sníží se kolize o přístupu k blokům dat.

Plánování dělení

Před implementací služby byste měli vždy zvážit strategii dělení, která je nutná ke škálování na více instancí. Existují různé způsoby, ale všechny se zaměřují na to, čeho aplikace potřebuje dosáhnout. V kontextu tohoto článku se podívejme na některé z důležitějších aspektů.

Jako první krok je vhodné zamyslet se nad strukturou stavu, který je potřeba rozdělit.

Vezměme si jednoduchý příklad. Pokud byste chtěli vytvořit službu pro hlasování v celém okresu, mohli byste vytvořit oddíl pro každé město v okresu. Pak můžete uložit hlasy pro každou osobu ve městě v oddílu, který odpovídá danému městu. Obrázek 3 znázorňuje sadu lidí a město, ve kterém se nacházejí.

Jednoduchý oddíl

Vzhledem k tomu, že se počet obyvatel měst výrazně liší, můžete skončit s oddíly, které obsahují velké množství dat (např. Seattle) a další oddíly s velmi malým počtem států (např. Kirkland). Jaký je tedy dopad na oddíly s nerovnoměrným množstvím stavu?

Pokud se znovu zamyslíte nad tímto příkladem, snadno uvidíte, že oddíl, který obsahuje hlasy pro Seattle, bude mít větší provoz než kirkland. Service Fabric ve výchozím nastavení zajišťuje, že je na každém uzlu přibližně stejný počet primárních a sekundárních replik. Takže můžete skončit s uzly, které obsahují repliky, které obsluhují větší provoz, a další, které obsluhují méně provozu. Raději byste se chtěli vyhnout horkým a studeným místům, jako je tento, v clusteru.

Abyste tomu předešli, měli byste z hlediska dělení udělat dvě věci:

  • Pokuste se rozdělit stav tak, aby byl rovnoměrně distribuován napříč všemi oddíly.
  • Sestava se načítá z každé repliky služby. (Informace o tom, jak na to, najdete v tomto článku o metrikách a zatížení. Service Fabric poskytuje možnost hlásit zatížení spotřebované službami, jako je velikost paměti nebo počet záznamů. Na základě nahlášených metrik Service Fabric zjistí, že některé oddíly obsluhují vyšší zatížení než jiné, a vyrovnává cluster přesunem replik do vhodnějších uzlů, takže celkově není žádný uzel přetížen.

V některých případech nemůžete vědět, kolik dat bude v daném oddílu. Obecné doporučení je tedy provést obojí – za prvé přijetím strategie dělení, která rovnoměrně rozloží data napříč oddíly, a za druhé vykazováním zatížení. První metoda zabraňuje situacím popsaným v příkladu hlasování, zatímco druhá pomáhá vyhlazení dočasných rozdílů v přístupu nebo zatížení v průběhu času.

Dalším aspektem plánování oddílů je výběr správného počtu oddílů, se kterým začnete. Z pohledu Service Fabric nic nebrání tomu, abyste začali s vyšším počtem oddílů, než se ve vašem scénáři očekávalo. Ve skutečnosti za předpokladu, že maximální počet oddílů je platný přístup.

Ve výjimečných případech může dojít k tomu, že budete potřebovat více oddílů, než jste původně zvolili. Vzhledem k tomu, že po tomto faktu nemůžete změnit počet oddílů, budete muset použít některé pokročilé přístupy k oddílům, jako je například vytvoření nové instance služby stejného typu služby. Budete také muset implementovat určitou logiku na straně klienta, která směruje požadavky do správné instance služby na základě znalostí na straně klienta, které váš klientský kód musí udržovat.

Dalším aspektem plánování dělení jsou dostupné počítačové prostředky. Vzhledem k tomu, že ke stavu je potřeba získat přístup a uložit ho, musíte postupovat následovně:

  • Omezení šířky pásma sítě
  • Omezení systémové paměti
  • Omezení diskových úložišť

Co se tedy stane, když ve spuštěném clusteru narazíte na omezení prostředků? Odpověď je, že cluster můžete jednoduše škálovat na více instancí tak, aby vyhovoval novým požadavkům.

Průvodce plánováním kapacity nabízí pokyny, jak určit, kolik uzlů váš cluster potřebuje.

Začínáme s dělením

Tato část popisuje, jak začít s dělením služby.

Service Fabric nabízí výběr ze tří schémat oddílů:

  • Dělení v rozsahu (označované také jako UniformInt64Partition)
  • Pojmenované dělení. Aplikace používající tento model obvykle obsahují data, která je možné v rámci omezené sady vytvořit v kontejnerech. Mezi běžné příklady datových polí používaných jako pojmenované klíče oddílů patří oblasti, PSČ, skupiny zákazníků nebo jiné obchodní hranice.
  • Dělení na jednotlivé oddíly. Jednotlivé oddíly se obvykle používají v případě, že služba nevyžaduje žádné další směrování. Například bezstavové služby používají toto schéma dělení ve výchozím nastavení.

Pojmenovaná a jednoúčelová schémata dělení jsou speciální formy rozsahových oddílů. Šablony sady Visual Studio pro Service Fabric ve výchozím nastavení používají dělení s rozsahy, protože se jedná o nejběžnější a nejužitečnější dělení. Zbývající část tohoto článku se zaměřuje na schéma dělení na rozsahy.

Schéma dělení na rozsahy

Používá se k určení celočíselného rozsahu (identifikovaného pomocí nízkého a vysokého klíče) a počtu oddílů (n). Vytvoří n oddílů, z nichž každý je zodpovědný za nepřekrývající se dílčí rozsah celkového rozsahu klíčů oddílu. Například schéma dělení s rozsahem s nízkým klíčem 0, vysokým klíčem 99 a počtem 4 by vytvořilo čtyři oddíly, jak je znázorněno níže.

Dělení rozsahu

Běžným přístupem je vytvoření hodnoty hash na základě jedinečného klíče v datové sadě. Mezi běžné příklady klíčů patří identifikační číslo vozidla (VIN), ID zaměstnance nebo jedinečný řetězec. Použitím tohoto jedinečného klíče byste pak vygenerovali hashovací kód moduls rozsah klíčů, který se použije jako klíč. Můžete zadat horní a dolní hranici povoleného rozsahu klíčů.

Vyberte hashovací algoritmus.

Důležitou součástí hashování je výběr hashovací algoritmus. Zvažte, jestli je cílem seskupit podobné klíče blízko sebe (hodnota hash citlivá na lokalitu), nebo jestli by se aktivita měla distribuovat široce napříč všemi oddíly (hashování distribuce), což je častější.

Dobrým algoritmem hashování distribuce je to, že se snadno počítá, má málo kolizí a distribuuje klíče rovnoměrně. Dobrým příkladem efektivního hashovacího algoritmu je hashovací algoritmus FNV-1 .

Vhodným zdrojem pro obecné volby algoritmu hashového kódu je stránka Wikipedie o hashovacích funkcích.

Vytvoření stavové služby s více oddíly

Pojďme vytvořit vaši první spolehlivou stavovou službu s více oddíly. V tomto příkladu vytvoříte velmi jednoduchou aplikaci, do které chcete uložit všechna příjmení začínající stejným písmenem ve stejném oddílu.

Než napíšete jakýkoli kód, musíte se zamyslet nad oddíly a klíči oddílů. Potřebujete 26 oddílů (jeden pro každé písmeno v abecedě), ale co klávesy low a high? Protože chceme mít doslova jeden oddíl na každé písmeno, můžeme použít 0 jako nízký klíč a 25 jako vysoký klíč, protože každé písmeno je jeho vlastní klíč.

Poznámka

Jedná se o zjednodušený scénář, protože ve skutečnosti by rozdělení bylo nerovnoměrné. Příjmení začínající písmeny "S" nebo "M" jsou běžnější než příjmení začínající na "X" nebo "Y".

  1. Otevřetenový>projektsouboru sady>Visual Studio>.

  2. V dialogovém okně Nový projekt zvolte aplikaci Service Fabric.

  3. Zavolejte projekt "AlphabetPartitions".

  4. V dialogovém okně Vytvořit službu zvolte Stavová služba a říkejte jí "Alphabet.Processing".

  5. Nastavte počet oddílů. Otevřete soubor ApplicationManifest.xml umístěný ve složce ApplicationPackageRoot projektu AlphabetPartitions a aktualizujte parametr Processing_PartitionCount na 26, jak je znázorněno níže.

    <Parameter Name="Processing_PartitionCount" DefaultValue="26" />
    

    Musíte také aktualizovat vlastnosti LowKey a HighKey elementu StatefulService v ApplicationManifest.xml, jak je znázorněno níže.

    <Service Name="Alphabet.Processing">
      <StatefulService ServiceTypeName="Alphabet.ProcessingType" TargetReplicaSetSize="[Processing_TargetReplicaSetSize]" MinReplicaSetSize="[Processing_MinReplicaSetSize]">
        <UniformInt64Partition PartitionCount="[Processing_PartitionCount]" LowKey="0" HighKey="25" />
      </StatefulService>
    </Service>    
    
  6. Aby byla služba přístupná, otevřete koncový bod na portu přidáním prvku koncového bodu ServiceManifest.xml (umístěného ve složce PackageRoot) pro službu Alphabet.Processing, jak je znázorněno níže:

    <Endpoint Name="ProcessingServiceEndpoint" Port="8089" Protocol="http" Type="Internal" />
    

    Služba je teď nakonfigurovaná tak, aby naslouchala internímu koncovému bodu s 26 oddíly.

  7. Dále je potřeba přepsat metodu CreateServiceReplicaListeners() třídy Processing.

    Poznámka

    Pro tuto ukázku předpokládáme, že používáte jednoduchý HttpCommunicationListener. Další informace o spolehlivé komunikaci služeb najdete v tématu Model komunikace spolehlivé služby.

  8. Doporučený vzor pro adresu URL, na které replika naslouchá, je následující formát: {scheme}://{nodeIp}:{port}/{partitionid}/{replicaid}/{guid}. Proto chcete nakonfigurovat komunikační naslouchací proces tak, aby naslouchal na správných koncových bodech a s tímto vzorem.

    Na stejném počítači může být hostováno více replik této služby, takže tato adresa musí být pro repliku jedinečná. Proto je v adrese URL ID oddílu a ID repliky. HttpListener může naslouchat na více adresách na stejném portu, pokud je předpona adresy URL jedinečná.

    Další identifikátor GUID je k dispozici pro pokročilý případ, kdy sekundární repliky také naslouchají požadavkům jen pro čtení. V takovém případě se chcete ujistit, že se při přechodu z primární na sekundární použije nová jedinečná adresa, aby klienti museli adresu přeložit znovu. Jako adresa se zde používá "+", aby replika naslouchala všem dostupným hostitelům (IP adresa, plně kvalifikovaný název domény, místní hostitel atd.). Následující kód ukazuje příklad.

    protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
    {
         return new[] { new ServiceReplicaListener(context => this.CreateInternalListener(context))};
    }
    private ICommunicationListener CreateInternalListener(ServiceContext context)
    {
    
         EndpointResourceDescription internalEndpoint = context.CodePackageActivationContext.GetEndpoint("ProcessingServiceEndpoint");
         string uriPrefix = String.Format(
                "{0}://+:{1}/{2}/{3}-{4}/",
                internalEndpoint.Protocol,
                internalEndpoint.Port,
                context.PartitionId,
                context.ReplicaOrInstanceId,
                Guid.NewGuid());
    
         string nodeIP = FabricRuntime.GetNodeContext().IPAddressOrFQDN;
    
         string uriPublished = uriPrefix.Replace("+", nodeIP);
         return new HttpCommunicationListener(uriPrefix, uriPublished, this.ProcessInternalRequest);
    }
    

    Je také vhodné poznamenat, že publikovaná adresa URL se mírně liší od předpony adresy URL naslouchající. Adresa URL pro naslouchání se předá httpListeneru. Publikovaná adresa URL je adresa URL publikovaná do služby Service Fabric Naming Service, která se používá ke zjišťování služeb. Klienti budou o tuto adresu žádat prostřednictvím této služby zjišťování. Adresa, kterou klienti získají, musí mít skutečnou IP adresu nebo plně kvalifikovaný název domény uzlu, aby se mohli připojit. Proto je potřeba nahradit +IP adresou uzlu nebo plně kvalifikovaným názvem domény, jak je znázorněno výše.

  9. Posledním krokem je přidání logiky zpracování do služby, jak je znázorněno níže.

    private async Task ProcessInternalRequest(HttpListenerContext context, CancellationToken cancelRequest)
    {
        string output = null;
        string user = context.Request.QueryString["lastname"].ToString();
    
        try
        {
            output = await this.AddUserAsync(user);
        }
        catch (Exception ex)
        {
            output = ex.Message;
        }
    
        using (HttpListenerResponse response = context.Response)
        {
            if (output != null)
            {
                byte[] outBytes = Encoding.UTF8.GetBytes(output);
                response.OutputStream.Write(outBytes, 0, outBytes.Length);
            }
        }
    }
    private async Task<string> AddUserAsync(string user)
    {
        IReliableDictionary<String, String> dictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<String, String>>("dictionary");
    
        using (ITransaction tx = this.StateManager.CreateTransaction())
        {
            bool addResult = await dictionary.TryAddAsync(tx, user.ToUpperInvariant(), user);
    
            await tx.CommitAsync();
    
            return String.Format(
                "User {0} {1}",
                user,
                addResult ? "successfully added" : "already exists");
        }
    }
    

    ProcessInternalRequest přečte hodnoty parametru řetězce dotazu použitého k volání oddílu a volání AddUserAsync pro přidání příjmení do spolehlivého slovníku dictionary.

  10. Pojďme do projektu přidat bezstavovou službu, abychom zjistili, jak můžete volat konkrétní oddíl.

    Tato služba slouží jako jednoduché webové rozhraní, které přijímá příjmení jako parametr řetězce dotazu, určuje klíč oddílu a odesílá ho službě Alphabet.Processing ke zpracování.

  11. V dialogovém okně Vytvořit službu zvolte Bezstavová služba a nazvěte ji "Alphabet.Web", jak je znázorněno níže.

    Snímek obrazovky bezstavové služby

  12. Aktualizujte informace o koncovém bodu v ServiceManifest.xml služby Alphabet.WebApi tak, aby se otevřel port, jak je znázorněno níže.

    <Endpoint Name="WebApiServiceEndpoint" Protocol="http" Port="8081"/>
    
  13. Musíte vrátit kolekci ServiceInstanceListeners ve třídě Web. Opět se můžete rozhodnout implementovat jednoduchý HttpCommunicationListener.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new[] {new ServiceInstanceListener(context => this.CreateInputListener(context))};
    }
    private ICommunicationListener CreateInputListener(ServiceContext context)
    {
        // Service instance's URL is the node's IP & desired port
        EndpointResourceDescription inputEndpoint = context.CodePackageActivationContext.GetEndpoint("WebApiServiceEndpoint")
        string uriPrefix = String.Format("{0}://+:{1}/alphabetpartitions/", inputEndpoint.Protocol, inputEndpoint.Port);
        var uriPublished = uriPrefix.Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN);
        return new HttpCommunicationListener(uriPrefix, uriPublished, this.ProcessInputRequest);
    }
    
  14. Teď je potřeba implementovat logiku zpracování. HttpCommunicationListener volá ProcessInputRequest , když přijde požadavek. Pojďme tedy pokračovat a přidat níže uvedený kód.

    private async Task ProcessInputRequest(HttpListenerContext context, CancellationToken cancelRequest)
    {
        String output = null;
        try
        {
            string lastname = context.Request.QueryString["lastname"];
            char firstLetterOfLastName = lastname.First();
            ServicePartitionKey partitionKey = new ServicePartitionKey(Char.ToUpper(firstLetterOfLastName) - 'A');
    
            ResolvedServicePartition partition = await this.servicePartitionResolver.ResolveAsync(alphabetServiceUri, partitionKey, cancelRequest);
            ResolvedServiceEndpoint ep = partition.GetEndpoint();
    
            JObject addresses = JObject.Parse(ep.Address);
            string primaryReplicaAddress = (string)addresses["Endpoints"].First();
    
            UriBuilder primaryReplicaUriBuilder = new UriBuilder(primaryReplicaAddress);
            primaryReplicaUriBuilder.Query = "lastname=" + lastname;
    
            string result = await this.httpClient.GetStringAsync(primaryReplicaUriBuilder.Uri);
    
            output = String.Format(
                    "Result: {0}. <p>Partition key: '{1}' generated from the first letter '{2}' of input value '{3}'. <br>Processing service partition ID: {4}. <br>Processing service replica address: {5}",
                    result,
                    partitionKey,
                    firstLetterOfLastName,
                    lastname,
                    partition.Info.Id,
                    primaryReplicaAddress);
        }
        catch (Exception ex) { output = ex.Message; }
    
        using (var response = context.Response)
        {
            if (output != null)
            {
                output = output + "added to Partition: " + primaryReplicaAddress;
                byte[] outBytes = Encoding.UTF8.GetBytes(output);
                response.OutputStream.Write(outBytes, 0, outBytes.Length);
            }
        }
    }
    

    Pojďme si to projít krok za krokem. Kód načte první písmeno parametru lastname řetězce dotazu do znaku. Pak určí klíč oddílu pro toto písmeno odečtením šestnáctkové hodnoty A od šestnáctkové hodnoty prvního písmene příjmení.

    string lastname = context.Request.QueryString["lastname"];
    char firstLetterOfLastName = lastname.First();
    ServicePartitionKey partitionKey = new ServicePartitionKey(Char.ToUpper(firstLetterOfLastName) - 'A');
    

    Nezapomeňte, že v tomto příkladu používáme 26 oddílů s jedním klíčem oddílu pro každý oddíl. Dále získáme oddíl partition služby pro tento klíč pomocí ResolveAsync metody v objektu servicePartitionResolver . servicePartitionResolver je definován jako

    private readonly ServicePartitionResolver servicePartitionResolver = ServicePartitionResolver.GetDefault();
    

    Metoda ResolveAsync přebírá identifikátor URI služby, klíč oddílu a token zrušení jako parametry. Identifikátor URI služby pro službu zpracování je fabric:/AlphabetPartitions/Processing. Dále získáme koncový bod oddílu.

    ResolvedServiceEndpoint ep = partition.GetEndpoint()
    

    Nakonec vytvoříme adresu URL koncového bodu plus řetězec dotazu a zavoláme službu zpracování.

    JObject addresses = JObject.Parse(ep.Address);
    string primaryReplicaAddress = (string)addresses["Endpoints"].First();
    
    UriBuilder primaryReplicaUriBuilder = new UriBuilder(primaryReplicaAddress);
    primaryReplicaUriBuilder.Query = "lastname=" + lastname;
    
    string result = await this.httpClient.GetStringAsync(primaryReplicaUriBuilder.Uri);
    

    Po dokončení zpracování zapíšeme výstup zpět.

  15. Posledním krokem je testování služby. Visual Studio používá parametry aplikace pro místní a cloudové nasazení. Pokud chcete službu otestovat s 26 oddíly místně, musíte aktualizovat Local.xml soubor ve složce ApplicationParameters projektu AlphabetPartitions, jak je znázorněno níže:

    <Parameters>
      <Parameter Name="Processing_PartitionCount" Value="26" />
      <Parameter Name="WebApi_InstanceCount" Value="1" />
    </Parameters>
    
  16. Po dokončení nasazení můžete zkontrolovat službu a všechny její oddíly v Service Fabric Explorer.

    Service Fabric Explorer snímek obrazovky

  17. V prohlížeči můžete logiku dělení otestovat zadáním http://localhost:8081/?lastname=somenamepříkazu . Uvidíte, že každé příjmení, které začíná stejným písmenem, je uloženo ve stejném oddílu.

    Snímek obrazovky prohlížeče

Kompletní řešení kódu použitého v tomto článku je k dispozici zde: https://github.com/Azure-Samples/service-fabric-dotnet-getting-started/tree/classic/Services/AlphabetPartitions.

Další kroky

Další informace o službách Service Fabric: