Inleiding tot sla - de Java Redis-client

1. Overzicht

Dit artikel is een inleiding tot Lettuce, een Redis Java-client.

Redis is een sleutelwaardeopslag in het geheugen die kan worden gebruikt als database, cache of message broker. Gegevens worden toegevoegd, opgevraagd, gewijzigd en verwijderd met opdrachten die werken op toetsen in de datastructuur in het geheugen van Redis.

Lettuce ondersteunt zowel synchroon als asynchroon communicatiegebruik van de volledige Redis API, inclusief de datastructuren, pub / sub-berichten en serververbindingen met hoge beschikbaarheid.

2. Waarom sla?

We hebben Jedis besproken in een van de vorige berichten. Wat maakt sla anders?

Het belangrijkste verschil is de asynchrone ondersteuning via de Java 8's Voltooiingsfase interface en ondersteuning voor Reactive Streams. Zoals we hieronder zullen zien, biedt Lettuce een natuurlijke interface voor het maken van asynchrone verzoeken van de Redis-databaseserver en voor het maken van streams.

Het gebruikt ook Netty voor communicatie met de server. Dit zorgt voor een "zwaardere" API, maar maakt het ook beter geschikt om een ​​verbinding met meer dan één thread te delen.

3. Installatie

3.1. Afhankelijkheid

Laten we beginnen met het aangeven van de enige afhankelijkheid die we nodig hebben in het pom.xml:

 io.sla sla-kern 5.0.1.VRIJGAVE 

De nieuwste versie van de bibliotheek kan worden gecontroleerd op de Github-repository of op Maven Central.

3.2. Redis-installatie

We zullen ten minste één instantie van Redis moeten installeren en uitvoeren, twee als we de clustering of de sentinel-modus willen testen (hoewel de sentinel-modus drie servers nodig heeft om correct te functioneren). Voor dit artikel gebruiken we 4.0.x - de laatste stabiele versie op dit moment.

Meer informatie over hoe u aan de slag kunt met Redis, vindt u hier, inclusief downloads voor Linux en MacOS.

Redis ondersteunt officieel geen Windows, maar er is hier een poort van de server. We kunnen Redis ook in Docker uitvoeren, wat een beter alternatief is voor Windows 10 en een snelle manier om aan de slag te gaan.

4. Verbindingen

4.1. Verbinding maken met een server

Verbinding maken met Redis bestaat uit vier stappen:

  1. Een Redis-URI maken
  2. Gebruik de URI om verbinding te maken met een RedisClient
  3. Een Redis-verbinding openen
  4. Een set van RedisCommands

Laten we de implementatie bekijken:

RedisClient redisClient = RedisClient .create ("redis: // [email protected]: 6379 /"); StatefulRedisConnection verbinding = redisClient.connect ();

EEN StatefulRedisConnection is hoe het klinkt; een threadveilige verbinding met een Redis-server die de verbinding met de server behoudt en indien nodig opnieuw verbinding maakt. Zodra we een verbinding hebben, kunnen we deze gebruiken om Redis-opdrachten synchroon of asynchroon uit te voeren.

RedisClient gebruikt aanzienlijke systeembronnen, aangezien het Netty-bronnen bevat voor communicatie met de Redis-server. Toepassingen die meerdere verbindingen vereisen, moeten een enkele RedisClient.

4.2. Redis-URI's

We creëren een RedisClient door een URI door te geven aan de statische fabrieksmethode.

Lettuce maakt gebruik van een aangepaste syntaxis voor Redis-URI's. Dit is het schema:

redis: // [[e-mail beveiligd]] host [: poort] [/ database] [? [time-out = time-out [d | h | m | s | ms | us | ns]] [& _database = database_]] 

Er zijn vier URI-schema's:

  • redis - een zelfstandige Redis-server
  • herhalen - een zelfstandige Redis-server via een SSL-verbinding
  • redis-aansluiting - een zelfstandige Redis-server via een Unix-domeinsocket
  • redis-sentinel - een Redis Sentinel-server

De Redis-database-instantie kan worden opgegeven als onderdeel van het URL-pad of als een aanvullende parameter. Als beide zijn opgegeven, heeft de parameter een hogere prioriteit.

In het bovenstaande voorbeeld gebruiken we een Draad vertegenwoordiging. Sla heeft ook een Herontdekking klasse voor het bouwen van verbindingen. Het biedt de Bouwer patroon:

RedisURI.Builder .redis ("localhost", 6379) .auth ("wachtwoord") .database (1) .build (); 

En een constructeur:

nieuwe RedisURI ("localhost", 6379, 60, TimeUnit.SECONDS); 

4.3. Synchrone opdrachten

Net als bij Jedis biedt Lettuce een complete Redis-commandoset in de vorm van methoden.

Lettuce implementeert echter zowel synchrone als asynchrone versies. We bekijken de synchrone versie kort en gebruiken de asynchrone implementatie voor de rest van de training.

Nadat we een verbinding hebben gemaakt, gebruiken we deze om een ​​commandoset te maken:

RedisCommands syncCommands = connection.sync (); 

Nu hebben we een intuïtieve interface om met Redis te communiceren.

We kunnen gaan zitten en krijgen String-waarden:

syncCommands.set ("key", "Hallo, Redis!"); Tekenreekswaarde = syncommands.get ("sleutel"); 

We kunnen met hashes werken:

syncCommands.hset ("recordName", "FirstName", "John"); syncCommands.hset ("recordName", "LastName", "Smith"); Map record = syncCommands.hgetall ("recordName"); 

We zullen later in het artikel meer Redis behandelen.

De synchrone API van Lettuce maakt gebruik van de asynchrone API. Het blokkeren gebeurt voor ons op commandoniveau. Dit betekent dat meer dan één client een synchrone verbinding kan delen.

4.4. Asynchrone opdrachten

Laten we eens kijken naar de asynchrone opdrachten:

RedisAsyncCommands asyncCommands = connection.async (); 

We halen een set op van RedisAsyncCommands van de verbinding, vergelijkbaar met hoe we de synchrone set hebben opgehaald. Deze opdrachten retourneren een RedisFuture (wat een is CompletableFuture intern):

RedisFuture resultaat = asyncCommands.get ("key"); 

Een gids voor het werken met een CompletableFuture vind je hier.

4.5. Reactieve API

Laten we tot slot eens kijken hoe we kunnen werken met niet-blokkerende reactieve API:

RedisStringReactiveCommands reactiveCommands = connection.reactive (); 

Deze opdrachten retourneren resultaten die zijn verpakt in een Mono of een Flux van Project Reactor.

Een handleiding voor het werken met Project Reactor vindt u hier.

5. Redis-gegevensstructuren

We hebben hierboven kort gekeken naar strings en hashes, laten we eens kijken hoe Lettuce de rest van de datastructuren van Redis implementeert. Zoals we zouden verwachten, heeft elke Redis-opdracht een methode met dezelfde naam.

5.1. Lijsten

Lijsten zijn lijsten van Snaren met behoud van de volgorde van inbrengen. Waarden worden aan beide uiteinden ingevoegd of opgehaald:

asyncCommands.lpush ("taken", "firstTask"); asyncCommands.lpush ("taken", "secondTask"); RedisFuture redisFuture = asyncCommands.rpop ("taken"); String nextTask = redisFuture.get (); 

In dit voorbeeld volgendeTask is gelijk aan "firstTask“. Lpush duwt waarden naar de kop van de lijst, en dan rpop haalt waarden aan het einde van de lijst.

We kunnen ook elementen van de andere kant laten knallen:

asyncCommands.del ("taken"); asyncCommands.lpush ("taken", "firstTask"); asyncCommands.lpush ("taken", "secondTask"); redisFuture = asyncCommands.lpop ("taken"); String nextTask = redisFuture.get (); 

We beginnen het tweede voorbeeld door de lijst met te verwijderen del. Vervolgens voegen we dezelfde waarden opnieuw in, maar we gebruiken lpop om de waarden uit de kop van de lijst te halen, dus de volgendeTask houdt "secondTask”Tekst.

5.2. Sets

Redis-sets zijn ongeordende verzamelingen van Snaren vergelijkbaar met Java Sets; er zijn geen dubbele elementen:

asyncCommands.sadd ("pets", "dog"); asyncCommands.sadd ("pets", "cat"); asyncCommands.sadd ("pets", "cat"); RedisFuture pets = asyncCommands.smembers ("bijnamen"); RedisFuture exist = asyncCommands.sismember ("pets", "dog"); 

Wanneer we de Redis-set ophalen als een Set, de grootte is twee, aangezien het duplicaat "kat" werd genegeerd. Wanneer we Redis vragen naar het bestaan ​​van "hond" met sismember, het antwoord is waar.

5.3. Hashes

We hebben eerder kort gekeken naar een voorbeeld van hashes. Ze zijn een korte uitleg waard.

Redis Hashes zijn records met Draad velden en waarden. Elk record heeft ook een sleutel in de primaire index:

asyncCommands.hset ("recordName", "FirstName", "John"); asyncCommands.hset ("recordName", "LastName", "Smith"); RedisFuture lastName = syncCommands.hget ("recordName", "LastName"); RedisFuture record = syncCommands.hgetall ("recordName"); 

We gebruiken hset om velden aan de hash toe te voegen, door de naam van de hash, de naam van het veld en een waarde door te geven.

Vervolgens halen we een individuele waarde op met hget, de naam van het record en het veld. Ten slotte halen we het hele record op als een hash met hgetall.

5.4. Gesorteerde sets

Gesorteerde sets bevatten waarden en een rangorde, waarop ze zijn gesorteerd. De rangorde is een 64-bits drijvende-kommawaarde.

Items worden toegevoegd met een rangorde en opgehaald in een reeks:

asyncCommands.zadd ("gesorteerde set", 1, "een"); asyncCommands.zadd ("gesorteerde set", 4, "nul"); asyncCommands.zadd ("gesorteerde set", 2, "twee"); RedisFuture valuesForward = asyncCommands.zrange (key, 0, 3); RedisFuture valuesReverse = asyncCommands.zrevrange (key, 0, 3); 

Het tweede argument voor zadd is een rang. We halen een bereik op volgorde op met zrange voor oplopende volgorde en zrevrange om af te dalen.

We hebben toegevoegd "nul”Met een rang van 4, dus het verschijnt aan het einde van waardenForward en aan het begin van waardenReverse.

6. Transacties

Transacties maken de uitvoering van een reeks opdrachten in een enkele atomaire stap mogelijk. Deze commando's worden gegarandeerd in volgorde en exclusief uitgevoerd. Commando's van een andere gebruiker worden pas uitgevoerd als de transactie is voltooid.

Ofwel worden alle commando's uitgevoerd, of geen van hen. Redis voert geen rollback uit als een van deze mislukt. Een keer exec () wordt aangeroepen, worden alle opdrachten uitgevoerd in de opgegeven volgorde.

Laten we naar een voorbeeld kijken:

asyncCommands.multi (); RedisFuture result1 = asyncCommands.set ("key1", "value1"); RedisFuture result2 = asyncCommands.set ("key2", "value2"); RedisFuture result3 = asyncCommands.set ("key3", "value3"); RedisFuture execResult = asyncCommands.exec (); TransactionResult transactionResult = execResult.get (); String firstResult = transactionResult.get (0); String secondResult = transactionResult.get (0); String thirdResult = transactionResult.get (0); 

De oproep naar multi start de transactie. Wanneer een transactie wordt gestart, worden de volgende opdrachten pas uitgevoerd exec () wordt genoemd.

In de synchrone modus keren de commando's terug nul. In asynchrone modus keren de commando's terug RedisFuture . Exec geeft een terug TransactionResult die een lijst met antwoorden bevat.

Sinds de RedisFutures ontvangen ook hun resultaten, asynchrone API-clients ontvangen het transactieresultaat op twee plaatsen.

7. Batching

Onder normale omstandigheden voert Lettuce opdrachten uit zodra ze worden aangeroepen door een API-client.

Dit is wat de meeste normale applicaties willen, vooral als ze vertrouwen op het serieel ontvangen van opdrachtresultaten.

Dit gedrag is echter niet efficiënt als toepassingen niet onmiddellijk resultaten nodig hebben of als grote hoeveelheden gegevens in bulk worden geüpload.

Asynchrone applicaties kunnen dit gedrag opheffen:

commands.setAutoFlushCommands (false); Lijst futures = nieuwe ArrayList (); for (int i = 0; i <iterations; i ++) {futures.add (commands.set ("key-" + i, "value-" + i);} commands.flushCommands (); boolean result = LettuceFutures.awaitAll (5, TimeUnit.SECONDS, futures.toArray (nieuwe RedisFuture [0])); 

Met setAutoFlushCommands ingesteld op falsemoet de applicatie bellen flushCommands handmatig. In dit voorbeeld hebben we er meerdere in de wachtrij geplaatst set commando en vervolgens het kanaal doorgespoeld. AwaitAll wacht op alle RedisFutures vervolledigen.

Deze status wordt per verbinding ingesteld en is van invloed op alle threads die de verbinding gebruiken. Deze functie is niet van toepassing op synchrone opdrachten.

8. Publiceer / abonneer

Redis biedt een eenvoudig berichtensysteem voor publiceren / abonneren. Abonnees gebruiken berichten van kanalen met de abonneren opdracht. Berichten worden niet bewaard; ze worden alleen aan gebruikers geleverd wanneer ze op een kanaal zijn geabonneerd.

Redis gebruikt het pub / sub-systeem voor meldingen over de Redis-gegevensset, waardoor klanten gebeurtenissen kunnen ontvangen over sleutels die worden ingesteld, verwijderd, verlopen, enz.

Zie de documentatie hier voor meer details.

8.1. Abonnee

EEN RedisPubSubListener ontvangt pub / sub-berichten. Deze interface definieert verschillende methoden, maar we laten hier alleen de methode zien voor het ontvangen van berichten:

public class Listener implementeert RedisPubSubListener {@Override public void message (String channel, String message) {log.debug ("Got {} on channel {}", message, channel); message = nieuwe String (s2); }} 

Wij gebruiken de RedisClient om een ​​pub / subkanaal aan te sluiten en de luisteraar te installeren:

StatefulRedisPubSubConnection-verbinding = client.connectPubSub (); connection.addListener (nieuwe Listener ()) RedisPubSubAsyncCommands async = connection.async (); async.subscribe ("kanaal"); 

Als er een listener is geïnstalleerd, halen we een set RedisPubSubAsyncCommands en abonneer je op een kanaal.

8.2. Uitgever

Publiceren is gewoon een kwestie van een Pub / Sub-kanaal verbinden en de commando's ophalen:

StatefulRedisPubSubConnection-verbinding = client.connectPubSub (); RedisPubSubAsyncCommands async = connection.async (); async.publish ("kanaal", "Hallo, Redis!"); 

Publiceren vereist een kanaal en een bericht.

8.3. Reactieve abonnementen

Lettuce biedt ook een reactieve interface voor het abonneren op pub- / subberichten:

StatefulRedisPubSubConnection-verbinding = client .connectPubSub (); RedisPubSubAsyncCommands reactive = verbinding .reactive (); reactive.observeChannels (). subscribe (bericht -> {log.debug ("Kreeg {} op kanaal {}", bericht, kanaal); bericht = nieuwe String (s2);}); reactive.subscribe ("kanaal"). subscribe (); 

De Flux geretourneerd door observerenChannels ontvangt berichten voor alle kanalen, maar aangezien dit een stream is, is filteren eenvoudig uit te voeren.

9. Hoge beschikbaarheid

Redis biedt verschillende opties voor hoge beschikbaarheid en schaalbaarheid. Volledig begrip vereist kennis van Redis-serverconfiguraties, maar we zullen een kort overzicht geven van hoe Lettuce ze ondersteunt.

9.1. Master / Slave

Redis-servers repliceren zichzelf in een master / slave-configuratie. De masterserver stuurt de slaaf een stroom opdrachten die de mastercache naar de slaaf repliceren. Redis ondersteunt geen bidirectionele replicatie, dus slaves zijn alleen-lezen.

Lettuce kan verbinding maken met Master / Slave-systemen, ze opvragen voor de topologie en vervolgens slaves selecteren voor leesbewerkingen, wat de doorvoer kan verbeteren:

RedisClient redisClient = RedisClient.create (); StatefulRedisMasterSlaveConnection verbinding = MasterSlave.connect (redisClient, nieuwe Utf8StringCodec (), RedisURI.create ("redis: // localhost")); connection.setReadFrom (ReadFrom.SLAVE); 

9.2. Sentinel

Redis Sentinel bewaakt master- en slave-instances en orkestreert failovers naar slaves in het geval van een master-failover.

Sla kan verbinding maken met de Sentinel, deze gebruiken om het adres van de huidige master te achterhalen en er vervolgens een verbinding mee maken.

Om dit te doen, bouwen we een ander Herontdekking en verbind onze RedisClient ermee:

RedisURI redisUri = RedisURI.Builder .sentinel ("sentinelhost1", "clusternaam") .withSentinel ("sentinelhost2"). Build (); RedisClient-client = nieuwe RedisClient (redisUri); RedisConnection-verbinding = client.connect (); 

We hebben de URI gebouwd met de hostnaam (of adres) van de eerste Sentinel en een clusternaam, gevolgd door een tweede schildwachtadres. Wanneer we verbinding maken met de Sentinel, vraagt ​​Lettuce het over de topologie en retourneert een verbinding met de huidige masterserver voor ons.

De volledige documentatie is hier beschikbaar.

9.3. Clusters

Redis Cluster gebruikt een gedistribueerde configuratie om hoge beschikbaarheid en hoge doorvoer te bieden.

Clusters shardsleutels over maximaal 1000 knooppunten, daarom zijn transacties niet beschikbaar in een cluster:

RedisURI redisUri = RedisURI.Builder.redis ("localhost") .withPassword ("authenticatie"). Build (); RedisClusterClient clusterClient = RedisClusterClient .create (rediUri); StatefulRedisClusterConnection verbinding = clusterClient.connect (); RedisAdvancedClusterCommands syncCommands = verbinding .sync (); 

RedisAdvancedClusterCommands bevat de set Redis-opdrachten die door het cluster worden ondersteund en leidt deze naar de instantie die de sleutel bevat.

Een volledige specificatie is hier beschikbaar.

10. Conclusie

In deze tutorial hebben we gekeken hoe we Lettuce kunnen gebruiken om vanuit onze applicatie verbinding te maken en een Redis-server op te vragen.

Lettuce ondersteunt de volledige set Redis-functies, met als bonus een volledig threadveilige asynchrone interface. Het maakt ook uitgebreid gebruik van Java 8's Voltooiingsfase interface om applicaties fijnmazige controle te geven over hoe ze gegevens ontvangen.

Codevoorbeelden zijn, zoals altijd, te vinden op GitHub.