Maak een lijst van alle beschikbare Redis-sleutels

Java Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

Collecties zijn een essentiële bouwsteen die doorgaans in bijna alle moderne toepassingen wordt aangetroffen. Het is dus geen verrassing dat Redis biedt een verscheidenheid aan populaire datastructuren zoals lijsten, sets, hashes en gesorteerde sets die we kunnen gebruiken.

In deze zelfstudie leren we hoe we alle beschikbare Redis-sleutels die overeenkomen met een bepaald patroon, effectief kunnen lezen.

2. Verken collecties

Laten we ons voorstellen dat onze applicatie gebruikt Redis om informatie over ballen op te slaan gebruikt in verschillende sporten. We zouden informatie over elke bal uit de Redis-collectie moeten kunnen zien. Voor de eenvoud beperken we onze dataset tot slechts drie ballen:

  • Cricketbal met een gewicht van 160 g
  • Voetbal met een gewicht van 450 g
  • Volleybal met een gewicht van 270 g

Laten we zoals gewoonlijk eerst onze basis opruimen door te werken aan een naïeve benadering van het verkennen van Redis-collecties.

3. Naïeve benadering met behulp van redis-cli

Voordat we beginnen met het schrijven van Java-code om de collecties te verkennen, moeten we een goed idee hebben van hoe we het zullen doen met behulp van de redis-cli koppel. Laten we aannemen dat onze Redis-instantie beschikbaar is op 127.0.0.1 op de haven 6379, zodat we elk type verzameling kunnen verkennen met de opdrachtregelinterface.

3.1. Gekoppelde lijst

Laten we eerst onze dataset opslaan in een aan Redis gekoppelde lijst met de naam ballen in het formaat van sportnaam_balgewicht met behulp van de spoed opdracht:

% redis-cli -h 127.0.0.1 -p 6379127.0.0.1:6379> RPUSH-ballen "cricket_160" (geheel getal) 1127.0.0.1:6379> RPUSH-ballen "football_450" (geheel getal) 2127.0.0.1:6379> RPUSH-ballen "volleyball_270" (geheel getal) 3

Dat merken we een succesvolle invoeging in de lijst geeft de nieuwe lengte van de lijst weer. In de meeste gevallen zijn we echter blind voor het invoegen van gegevens. Als gevolg hiervan kunnen we de lengte van de gekoppelde lijst achterhalen met behulp van de llen opdracht:

127.0.0.1:6379> llen ballen (geheel getal) 3

Als we de lengte van de lijst al weten, is het handig om gebruik de lrange opdracht om de volledige dataset eenvoudig op te halen:

127.0.0.1:6379> lrange balls 0 2 1) "cricket_160" 2) "football_450" 3) "volleyball_270"

3.2. Set

Laten we vervolgens kijken hoe we de dataset kunnen verkennen wanneer we besluiten deze op te slaan in een Redis-set. Om dit te doen, moeten we eerst onze dataset vullen in een Redis-set met de naam ballen met behulp van de verdrietig opdracht:

127.0.0.1:6379> sadd balls "cricket_160" "football_450" "volleyball_270" "cricket_160" (geheel getal) 3

Oeps! We hadden een dubbele waarde in onze opdracht. Maar aangezien we waarden aan een set toevoegden, hoeven we ons geen zorgen te maken over duplicaten. Natuurlijk kunnen we het aantal toegevoegde items zien aan de hand van de output response-waarde.

Nu kunnen we gebruik maken van de smembers commando om alle ingestelde leden te zien:

127.0.0.1:6379> smembers balls 1) "volleyball_270" 2) "cricket_160" 3) "football_450"

3.3. Hash

Laten we nu de hash-datastructuur van Redis gebruiken om onze dataset op te slaan in een hash-sleutel met de naam balls, zodat het veld van de hash de sportnaam is en de veldwaarde het gewicht van de bal. Dit kunnen we doen met behulp van hmset opdracht:

127.0.0.1:6379> hmset ballen cricket 160 voetbal 450 volleybal 270 OK

Om de informatie te zien die is opgeslagen in onze hash, kunnen we gebruik de hgetall opdracht:

127.0.0.1:6379> hgetall ballen 1) "cricket" 2) "160" 3) "voetbal" 4) "450" ​​5) "volleybal" 6) "270"

3.4. Gesorteerde set

Naast een unieke ledenwaarde, stellen gesorteerde sets ons in staat om er een score naast te houden. Welnu, in ons geval kunnen we de naam van de sport behouden als de ledenwaarde en het gewicht van de bal als de score. Laten we de zadd commando om onze dataset op te slaan:

127.0.0.1:6379> zadd ballen 160 cricket 450 voetbal 270 volleybal (geheel getal) 3

Nu kunnen we eerst de zcard commando om de lengte van de gesorteerde set te vinden, gevolgd door de zrange commando om de complete set te verkennen:

127.0.0.1:6379> zcard-ballen (geheel getal) 3127.0.0.1:6379> zrange-ballen 0 2 1) "cricket" 2) "volleybal" 3) "voetbal"

3.5. Snaren

We kunnen ook de gebruikelijke key-value strings als een oppervlakkige verzameling items. Laten we eerst onze dataset vullen met de mset opdracht:

127.0.0.1:6379> mset ballen: cricket 160 ballen: voetbal 450 ballen: volleybal 270 OK

We moeten opmerken dat we het voorvoegsel 'ballen:zodat we deze sleutels kunnen identificeren van de rest van de sleutels die mogelijk in onze Redis-database liggen. Bovendien stelt deze naamgevingsstrategie ons in staat om de sleutels commando om onze dataset te verkennen met behulp van voorvoegselpatronen:

127.0.0.1:6379> toetsenballen * 1) "ballen: cricket" 2) "ballen: volleybal" 3) "ballen: voetbal"

4. Naïeve Java-implementatie

Nu we een basisidee hebben ontwikkeld van de relevante Redis-opdrachten die we kunnen gebruiken om verzamelingen van verschillende typen te verkennen, is het tijd dat we onze handen vuil maken met code.

4.1. Afhankelijkheid van Maven

In deze sectie zullen we zijn de ... gebruiken Jedis client bibliotheek voor Redis in onze implementatie:

 redis.clients jedis 3.2.0 

4.2. Redis-client

De Jedis-bibliotheek wordt geleverd met de Redis-CLI-naam-achtige methoden. Het wordt echter aanbevolen dat we maak een wrapper Redis-client, die intern Jedis-functieaanroepen aanroept.

Wanneer we met de Jedis-bibliotheek werken, moeten we dat in gedachten houden een enkele Jedis-instantie is niet thread-safe. Daarom kunnen we dat, om een ​​Jedis-bron in onze applicatie te krijgen gebruikmaken van JedisPool, dat is een threadveilige pool van netwerkverbindingen.

En aangezien we niet willen dat er tijdens de levenscyclus van onze applicatie meerdere exemplaren van Redis-clients rondzweven, moeten we onze RedisClient klasse volgens het principe van het singleton-ontwerppatroon.

Laten we eerst een privé-constructor voor onze klant maken die intern het JedisPool wanneer een exemplaar van RedisClient klasse is gemaakt:

privé statische JedisPool jedisPool; private RedisClient (String ip, int port) {try {if (jedisPool == null) {jedisPool = new JedisPool (new URI ("//" + ip + ":" + port)); }} catch (URISyntaxException e) {log.error ("Verkeerd opgemaakt serveradres", e); }}

Vervolgens hebben we een toegangspunt nodig naar onze singleton-client. Laten we dus een statische methode maken getInstance () Voor dit doeleinde:

privé statische vluchtige RedisClient-instantie = null; openbare statische RedisClient getInstance (String ip, final int port) {if (instance == null) {synchronized (RedisClient.class) {if (instance == null) {instance = nieuwe RedisClient (ip, poort); }}} terugkeerinstantie; }

Laten we tot slot kijken hoe we een wrapper-methode kunnen maken bovenop die van Jedis lrange-methode:

openbare lijst lrange (laatste String-sleutel, laatste lange start, laatste lange stop) {try (Jedis jedis = jedisPool.getResource ()) {return jedis.lrange (key, start, stop); } catch (Exception ex) {log.error ("Exception gevangen in lrange", ex); } retourneer nieuwe LinkedList (); }

Natuurlijk kunnen we dezelfde strategie volgen om de rest van de wrapper-methoden te maken, zoals lpush, hmset, hgetall, verdrietig, smembers, sleutels, zadd, en zrange.

4.3. Analyse

Alle Redis-opdrachten die we kunnen gebruiken het verkennen van een verzameling in één keer zal in het beste geval natuurlijk een O (n) time-complexiteit hebben.

We zijn misschien een beetje liberaal en noemen deze benadering naïef. In een real-life productie-instantie van Redis is het vrij gebruikelijk om duizenden of miljoenen sleutels in één verzameling te hebben. Verder brengt de single-threaded aard van Redis meer ellende met zich mee, en onze aanpak zou catastrofaal andere operaties met een hogere prioriteit kunnen blokkeren.

Dus we moeten er een punt van maken dat we onze naïeve benadering beperken om alleen te worden gebruikt voor foutopsporingsdoeleinden.

5. Iterator Basics

De grootste fout in onze naïeve implementatie is dat we Redis vragen om ons alle resultaten voor onze enkele ophaalopdracht in één keer te geven. Om dit probleem op te lossen, kunnen we onze oorspronkelijke ophaalquery opsplitsen in meerdere opeenvolgende ophaalquery's die werken op kleinere delen van de gehele dataset.

Laten we aannemen dat we een boek van 1000 pagina's hebben dat we zouden moeten lezen. Als we onze naïeve benadering volgen, zullen we dit grote boek in één keer zonder pauzes moeten lezen. Dat is fataal voor ons welzijn, omdat het onze energie zal afvoeren en ons ervan weerhoudt andere activiteiten met een hogere prioriteit te doen.

De juiste manier is natuurlijk om het boek over meerdere leessessies uit te lezen. In elke sessie we gaan verder waar we gebleven waren in de vorige sessie - wij kunnen volg onze voortgang door een bladwijzer voor pagina's te gebruiken.

Hoewel de totale leestijd in beide gevallen van vergelijkbare waarde zal zijn, is de tweede benadering toch beter omdat deze ons ruimte geeft om te ademen.

Laten we eens kijken hoe we een iterator-gebaseerde benadering kunnen gebruiken om Redis-verzamelingen te verkennen.

6. Redis Scan

Redis biedt verschillende scanstrategieën om sleutels uit verzamelingen te lezen met behulp van een cursorgebaseerde benadering, die in principe vergelijkbaar is met een paginabladwijzer.

6.1. Scan strategieën

We kunnen de hele sleutelwaardecollectie-winkel doorzoeken met behulp van de Scannen opdracht. Als we onze dataset echter willen beperken tot verzamelingstypen, kunnen we een van de varianten gebruiken:

  • Sscan kan worden gebruikt voor het doorlopen van sets
  • Hscan helpt ons door paren van veldwaarde in een hash te herhalen
  • Zscan staat een iteratie toe door leden die zijn opgeslagen in een gesorteerde set

We moeten opmerken dat we hebben niet echt een server-side scanstrategie nodig die specifiek is ontworpen voor de gelinkte lijsten. Dat komt omdat we toegang hebben tot leden van de gekoppelde lijst via indexen met behulp van de lindex of lrange opdracht. Bovendien kunnen we het aantal elementen en het gebruik achterhalen lrange in een simpele lus om de hele lijst in kleine stukjes te herhalen.

Laten we de SCANNEN opdracht om sleutels van het stringtype te scannen. Om de scan te starten, moeten we de cursorwaarde gebruiken als "0", bijpassende patroontekenreeks als "bal *":

127.0.0.1:6379> mset ballen: cricket 160 ballen: voetbal 450 ballen: volleybal 270 OK 127.0.0.1:6379> SCAN 0 WEDSTRIJDbal * COUNT 1 1) "2" 2) 1) "ballen: cricket" 127.0.0.1 : 6379> SCAN 2 WEDSTRIJDbal * COUNT 1 1) "3" 2) 1) "ballen: volleybal" 127.0.0.1:6379> SCAN 3 WEDSTRIJDbal * COUNT 1 1) "0" 2) 1) "ballen: voetbal "

Bij elke voltooide scan krijgen we de volgende waarde van de cursor die in de volgende iteratie moet worden gebruikt. Uiteindelijk weten we dat we door de hele verzameling hebben gescand wanneer de volgende cursorwaarde "0" is.

7. Scannen met Java

We hebben inmiddels voldoende kennis van onze aanpak om deze in Java te implementeren.

7.1. Strategieën voor scannen

Als we een kijkje nemen in de belangrijkste scanfunctionaliteit die wordt aangeboden door het Jedis klasse, zullen we strategieën vinden om verschillende soorten collecties te scannen:

openbare ScanResult-scan (laatste tekenreekscursor, laatste ScanParams-parameters); openbare ScanResult sscan (laatste String-toets, laatste String-cursor, laatste ScanParams-params); openbare ScanResult hscan (laatste String-toets, laatste String-cursor, laatste ScanParams-params); openbare ScanResult zscan (laatste String-toets, laatste String-cursor, laatste ScanParams-params);

Jedis vereist twee optionele parameters, zoekpatroon en resultaatgrootte, om het scannen effectief te beheersen - ScanParams maakt dit mogelijk. Voor dit doel vertrouwt het op de bij elkaar passen() en tellen () methoden, die losjes gebaseerd zijn op het ontwerppatroon van de bouwer:

openbare ScanParams-match (definitief String-patroon); openbare ScanParams-telling (uiteindelijke aantal gehele getallen);

Nu we hebben gedrenkt in de basiskennis over Jedis's scanbenadering, laten we deze strategieën modelleren door middel van een ScanStrategy koppel:

openbare interface ScanStrategy {ScanResult scan (Jedis jedis, String cursor, ScanParams scanParams); }

Laten we eerst aan de eenvoudigste werken scannen strategie, die onafhankelijk is van het verzameltype en de sleutels leest, maar niet de waarde van de sleutels:

public class Scan implementeert ScanStrategy {public ScanResult scan (Jedis jedis, String cursor, ScanParams scanParams) {return jedis.scan (cursor, scanParams); }}

Laten we vervolgens het hscan strategie, die is afgestemd op het lezen van alle veldsleutels en veldwaarden van een bepaalde hash-sleutel:

openbare klasse Hscan implementeert ScanStrategy {private String-sleutel; @Override openbare ScanResult scan (Jedis jedis, String cursor, ScanParams scanParams) {return jedis.hscan (sleutel, cursor, scanParams); }}

Laten we tot slot de strategieën voor sets en gesorteerde sets bouwen. De sscan strategie kan alle leden van een set lezen, terwijl de zscan strategie kan de leden samen met hun scores lezen in de vorm van Tuples:

public class Sscan implementeert ScanStrategy {private String key; openbare ScanResult-scan (Jedis jedis, String cursor, ScanParams scanParams) {return jedis.sscan (key, cursor, scanParams); }} public class Zscan implementeert ScanStrategy {private String key; @Override openbare ScanResult-scan (Jedis jedis, String cursor, ScanParams scanParams) {return jedis.zscan (sleutel, cursor, scanParams); }}

7.2. Redis Iterator

Laten we vervolgens de bouwstenen schetsen die nodig zijn om onze HerdisIterator klasse:

  • Tekenreeks-gebaseerde cursor
  • Scanstrategie zoals scannen, sscan, hscan, zscan
  • Tijdelijke aanduiding voor het scannen van parameters
  • Toegang tot JedisPool om een Jedis bron

We kunnen nu doorgaan en deze leden definiëren in onze HerdisIterator klasse:

privé finale JedisPool jedisPool; privé ScanParams scanParams; privé String-cursor; privé ScanStrategy-strategie;

Onze stage is helemaal klaar om de iteratorspecifieke functionaliteit voor onze iterator te definiëren. Daarvoor is onze HerdisIterator class moet de Iterator koppel:

public class RedisIterator implementeert Iterator { }

Uiteraard moeten we de hasNext () en De volgende() methoden geërfd van de Iterator koppel.

Laten we eerst het laaghangende fruit plukken - de hasNext () methode - aangezien de onderliggende logica ongecompliceerd is. Zodra de cursorwaarde "0" wordt, weten we dat we klaar zijn met de scan. Laten we dus eens kijken hoe we dit in slechts één regel kunnen implementeren:

@Override openbare boolean hasNext () {return! "0" .equals (cursor); }

Laten we vervolgens werken aan het De volgende() methode die het zware werk van scannen doet:

@Override openbare lijst next () {if (cursor == null) {cursor = "0"; } probeer (Jedis jedis = jedisPool.getResource ()) {ScanResult scanResult = strategy.scan (jedis, cursor, scanParams); cursor = scanResult.getCursor (); retour scanResult.getResult (); } catch (uitzondering ex) {log.error ("uitzondering gevangen in next ()", ex); } retourneer nieuwe LinkedList (); }

Dat moeten we opmerken Scan resultaat geeft niet alleen de gescande resultaten weer, maar ook de volgende cursorwaarde nodig voor de volgende scan.

Ten slotte kunnen we de functionaliteit inschakelen om onze HerdisIterator in de RedisClient klasse:

openbare RedisIterator-iterator (int initialScanCount, String-patroon, ScanStrategy-strategie) {retourneer nieuwe RedisIterator (jedisPool, initialScanCount, patroon, strategie); }

7.3. Lees met Redis Iterator

Omdat we onze Redis-iterator hebben ontworpen met behulp van de Iterator interface, het is vrij intuïtief lees de collectiewaarden met behulp van de De volgende() methode zolang hasNext () geeft terug waar.

Voor de volledigheid en eenvoud slaan we eerst de dataset met betrekking tot de sportballen op in een Redis-hash. Daarna gebruiken we onze RedisClient om een ​​iterator te maken met Hscan scanstrategie. Laten we onze implementatie testen door dit in actie te zien:

@Test openbare ongeldig testHscanStrategy () {HashMap hash = nieuwe HashMap (); hash.put ("cricket", "160"); hash.put ("voetbal", "450"); hash.put ("volleybal", "270"); redisClient.hmset ("ballen", hash); Hscan scanStrategy = nieuwe Hscan ("ballen"); int iterationCount = 2; RedisIterator iterator = redisClient.iterator (iterationCount, "*", scanStrategy); Lijst results = nieuwe LinkedList(); while (iterator.hasNext ()) {results.addAll (iterator.next ()); } Assert.assertEquals (hash.size (), results.size ()); }

We kunnen hetzelfde denkproces volgen met weinig aanpassingen om de resterende strategieën voor het scannen en lezen van de sleutels in verschillende soorten collecties te testen en te implementeren.

8. Conclusie

We zijn deze tutorial begonnen met de bedoeling om te leren hoe we alle overeenkomende sleutels in Redis kunnen lezen.

We kwamen erachter dat Redis een eenvoudige manier biedt om sleutels in één keer te lezen. Hoewel eenvoudig, hebben we besproken hoe dit de middelen belast en daarom niet geschikt is voor productiesystemen. Toen we dieper graven, kwamen we erachter dat er een iterator-gebaseerde benadering voor scannen door middel van overeenkomende Redis-sleutels voor onze leesquery.

Zoals altijd is de volledige broncode voor de Java-implementatie die in dit artikel wordt gebruikt, beschikbaar op GitHub.

Java onderkant

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

$config[zx-auto] not found$config[zx-overlay] not found