Vis alle tilgjengelige Redis-taster

Java Top

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

1. Oversikt

Samlinger er en viktig byggestein som vanligvis ses i nesten alle moderne applikasjoner. Så det er ingen overraskelse Redis tilbyr en rekke populære datastrukturer som lister, sett, hashes og sorterte sett som vi kan bruke.

I denne opplæringen lærer vi hvordan vi effektivt kan lese alle tilgjengelige Redis-taster som samsvarer med et bestemt mønster.

2. Utforsk samlinger

La oss forestille oss at vår applikasjonen bruker Redis til å lagre informasjon om baller brukt i forskjellige idretter. Vi burde kunne se informasjon om hver ball tilgjengelig fra Redis-samlingen. For enkelhets skyld vil vi begrense datasettet vårt til bare tre baller:

  • Cricket ball med en vekt på 160 g
  • Fotball med en vekt på 450 g
  • Volleyball med en vekt på 270 g

Som vanlig, la oss først fjerne det grunnleggende ved å jobbe med en naiv tilnærming til å utforske Redis-samlinger.

3. Naiv tilnærming ved bruk av redis-cli

Før vi begynner å skrive Java-kode for å utforske samlingene, bør vi ha en god ide om hvordan vi gjør det ved hjelp av redis-cli grensesnitt. La oss anta at vår Redis-forekomst er tilgjengelig på 127.0.0.1 på havn 6379, for oss å utforske hver samlingstype med kommandolinjegrensesnittet.

3.1. Koblet liste

La oss først lagre datasettet vårt i en Redis-koblet liste baller i formatet av sports-navn_ball-vekt ved hjelp av rpush kommando:

% redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379> RPUSH baller "cricket_160" (heltall) 1 127.0.0.1:6379> RPUSH baller "football_450" (heltall) 2 127.0.0.1:6379> RPUSH baller "volleyball_270" (heltall) 3

Det kan vi merke en vellykket innsetting i listen gir den nye lengden på listen. I de fleste tilfeller vil vi imidlertid være blinde for aktiviteten for datainnsetting. Som et resultat kan vi finne ut lengden på den koblede listen ved hjelp av llen kommando:

127.0.0.1:6379> llen baller (heltall) 3

Når vi allerede vet lengden på listen, er det praktisk å bruke orden kommando for å hente hele datasettet enkelt:

127.0.0.1:6379> organisere baller 0 2 1) "cricket_160" 2) "fotball_450" 3) "volleyball_270"

3.2. Sett

La oss deretter se hvordan vi kan utforske datasettet når vi bestemmer oss for å lagre det i et Redis-sett. For å gjøre det, må vi først fylle ut datasettet vårt i et Redis-sett som heter baller ved hjelp av trist kommando:

127.0.0.1:6379> sad baller "cricket_160" "football_450" "volleyball_270" "cricket_160" (heltall) 3

Beklager! Vi hadde en duplikatverdi i kommandoen vår. Men siden vi la til verdier i et sett, trenger vi ikke å bekymre oss for duplikater. Selvfølgelig kan vi se antall elementer som er lagt til fra utgangssvarverdien.

Nå kan vi utnytte smembers kommando for å se alle settmedlemmene:

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

3.3. Hash

La oss nå bruke Redis hash-datastruktur til å lagre datasettet vårt i en hash-nøkkel med navnet baller slik at hash-feltet er sportsnavnet og feltverdien er ballens vekt. Vi kan gjøre dette ved hjelp av hmset kommando:

127.0.0.1:6379> hmset balls cricket 160 fotball 450 volleyball 270 OK

For å se informasjonen som er lagret i hasjen vår, kan vi bruke hgetall kommando:

127.0.0.1:6379> hgetallballer 1) "cricket" 2) "160" 3) "fotball" 4) "450" ​​5) "volleyball" 6) "270"

3.4. Sortert sett

I tillegg til en unik medlemsverdi, lar oss sorterte sett ha en poengsum ved siden av dem. Vel, i vårt brukstilfelle kan vi beholde navnet på sporten som medlemsverdien og vekten av ballen som poengsummen. La oss bruke zadd kommando for å lagre datasettet vårt:

127.0.0.1:6379> zadd baller 160 cricket 450 fotball 270 volleyball (heltall) 3

Nå kan vi først bruke zcard kommando for å finne lengden på det sorterte settet, etterfulgt av zrange kommando for å utforske hele settet:

127.0.0.1:6379> zcard baller (heltall) 3 127.0.0.1:6379> zrange balls 0 2 1) "cricket" 2) "volleyball" 3) "football"

3.5. Strenger

Vi kan også se vanlige nøkkelverdistrenger som en overfladisk samling av gjenstander. La oss først fylle ut datasettet vårt ved hjelp av mset kommando:

127.0.0.1:6379> mset baller: cricket 160 baller: fotball 450 baller: volleyball 270 OK

Vi må merke oss at vi la til prefikset “baller:slik at vi kan identifisere disse nøklene fra resten av nøklene som kan ligge i vår Redis-database. Videre lar denne navngivningsstrategien oss bruke nøklene kommando for å utforske datasettet vårt ved hjelp av prefiks mønster matching:

127.0.0.1:6379> nøkkelballer * 1) "baller: cricket" 2) "baller: volleyball" 3) "baller: fotball"

4. Naiv Java-implementering

Nå som vi har utviklet en grunnleggende ide om de aktuelle Redis-kommandoene som vi kan bruke til å utforske samlinger av forskjellige typer, er det på tide for oss å skitne hendene med kode.

4.1. Maven avhengighet

I denne delen vil vi være det bruker Jedis klientbibliotek for Redis i vår implementering:

 redis.clients jedis 3.2.0 

4.2. Redis klient

Jedis-biblioteket leveres med navnene Redis-CLI. Det anbefales imidlertid at vi opprett en Redis-klient for innpakning, som internt vil påkalle Jedis-funksjonsanrop.

Når vi jobber med Jedis-biblioteket, må vi huske på det en enkelt Jedis-forekomst er ikke trådsikker. Derfor, for å få en Jedis-ressurs i applikasjonen vår, kan vi benytte seg av JedisPool, som er et trådsikkert basseng av nettverkstilkoblinger.

Og siden vi ikke vil ha flere forekomster av Redis-klienter som flyter rundt til enhver tid i løpet av livssyklusen til applikasjonen vår, bør vi lage vår RedisClient klasse på prinsippet om singleton design mønster.

Først, la oss lage en privat konstruktør for klienten vår som vil initialisere internt JedisPool når en forekomst av RedisClient klasse er opprettet:

privat statisk JedisPool jedisPool; privat RedisClient (streng ip, int-port) {prøv {if (jedisPool == null) {jedisPool = ny JedisPool (ny URI ("//" + ip + ":" + port)); }} fange (URISyntaxException e) {log.error ("Feilformet serveradresse", e); }}

Deretter trenger vi et tilgangspunkt til singleton-klienten vår. Så la oss lage en statisk metode getInstance () for dette formålet:

privat statisk ustabil RedisClient-forekomst = null; offentlig statisk RedisClient getInstance (streng ip, siste int-port) {if (forekomst == null) {synkronisert (RedisClient.class) {hvis (forekomst == null) {forekomst = ny RedisClient (ip, port); }}} returner forekomst; }

Til slutt, la oss se hvordan vi kan lage en innpakningsmetode på toppen av Jedis organisere metoden:

public List lrange (final String key, final long start, final long stop) {try (Jedis jedis = jedisPool.getResource ()) {return jedis.lrange (key, start, stop); } catch (Exception ex) {log.error ("Exception fanget i lrange", ex); } returner ny LinkedList (); }

Selvfølgelig kan vi følge den samme strategien for å lage resten av innpakningsmetodene som lpush, hmset, hgetall, trist, smembers, nøklene, zadd, og zrange.

4.3. Analyse

Alle Redis-kommandoene som vi kan bruke til utforske en samling på en gang vil naturlig nok ha en O (n) tidskompleksitet i beste fall.

Vi er kanskje litt liberale og kaller denne tilnærmingen som naiv. I en reell produksjon av Redis er det ganske vanlig å ha tusenvis eller millioner av nøkler i en enkelt samling. Videre gir Redis ensartede natur mer elendighet, og vår tilnærming kan katastrofalt blokkere andre operasjoner med høyere prioritet.

Så vi bør gjøre det til et poeng at vi begrenser den naive tilnærmingen vår til kun å brukes til feilsøkingsformål.

5. Grunnleggende om Iterator

Den største feilen i vår naive implementering er at vi ber Redis om å gi oss alle resultatene for vår eneste hentingsforespørsel på en gang. For å overvinne dette problemet kan vi dele vår opprinnelige hentingsforespørsel i flere sekvensielle hentingssøk som fungerer på mindre biter av hele datasettet.

La oss anta at vi har en bok på 1000 sider som vi skal lese. Hvis vi følger vår naive tilnærming, må vi lese denne store boken i en enkelt sitting uten pauser. Det vil være dødelig for vårt velvære, da det vil tømme energien vår og hindre oss i å gjøre andre aktiviteter med høyere prioritet.

Selvfølgelig er den rette måten å fullføre boka over flere lesesessioner. I hver økt, vi fortsetter fra der vi slapp i forrige økt - vi kan spore fremgangen vår ved å bruke et sidebokmerke.

Selv om den totale lesetiden i begge tilfeller vil være av sammenlignbar verdi, er den andre tilnærmingen likevel bedre da den gir oss rom til å puste.

La oss se hvordan vi kan bruke en iteratorbasert tilnærming for å utforske Redis-samlinger.

6. Redis-skanning

Redis tilbyr flere skannestrategier for å lese nøkler fra samlinger ved hjelp av en markørbasert tilnærming, som i prinsippet ligner på et sidebokmerke.

6.1. Skann strategier

Vi kan skanne gjennom hele butikken for nøkkelverdiinnsamling ved hjelp av Skann kommando. Men hvis vi vil begrense datasettet vårt etter samlingstyper, kan vi bruke en av variantene:

  • Sscan kan brukes til å gjenta gjennom sett
  • Hscan hjelper oss med å itere gjennom par feltverdi i en hash
  • Zscan tillater en iterasjon gjennom medlemmer lagret i et sortert sett

Vi må merke oss at vi trenger egentlig ikke en skannestrategi på serversiden spesielt designet for de lenkede listene. Det er fordi vi kan få tilgang til medlemmer av den koblede listen gjennom indekser ved hjelp av lindex eller orden kommando. I tillegg kan vi finne ut antall elementer og bruk orden i en enkel løkke for å gjenta hele listen i små biter.

La oss bruke SKANN kommando for å skanne over nøkler av strengtype. For å starte skanningen må vi bruke markørverdien som "0", matchende mønsterstreng som “ball *”:

127.0.0.1:6379> mset baller: cricket 160 baller: fotball 450 baller: volleyball 270 OK 127.0.0.1:6379> SCANN 0 MATCH ball * TELL 1 1) "2" 2) 1) "baller: cricket" 127.0.0.1 : 6379> SCAN 2 MATCH-ball * TELL 1 1) "3" 2) 1) "baller: volleyball" 127.0.0.1:6379> SCAN 3 MATCH-ball * ANTALL 1 1) "0" 2) 1) "baller: fotball "

For hver fullførte skanning får vi neste markørverdi som skal brukes i den etterfølgende iterasjonen. Til slutt vet vi at vi har skannet gjennom hele samlingen når neste markørverdi er "0".

7. Skanning med Java

Nå har vi forståelse for vår tilnærming til at vi kan begynne å implementere den i Java.

7.1. Skannestrategier

Hvis vi titter inn i kjernefunksjonen for skanning Jedis klasse finner vi strategier for å skanne forskjellige samlingstyper:

offentlig ScanResult-skanning (endelig strengmarkør, endelig ScanParams-parameter); offentlig ScanResult sscan (endelig strengnøkkel, endelig strengmarkør, endelig ScanParams-parameter); offentlig ScanResult hscan (endelig strengnøkkel, siste strengmarkør, endelig ScanParams-parameter); offentlig ScanResult zscan (siste strengnøkkel, endelig strengmarkør, endelig ScanParams-parameter);

Jedis krever to valgfrie parametere, søkemønster og resultatstørrelse for effektiv kontroll av skanningen - ScanParams får dette til å skje. For dette formålet er det avhengig av kamp() og telle() metoder, som er løst basert på byggmønsteret:

offentlig ScanParams-kamp (endelig strengmønster); offentlig antall ScanParams (endelig antall heltal);

Nå som vi har gjennomvåt den grunnleggende kunnskapen om Jedis er skanningstilnærming, la oss modellere disse strategiene gjennom en ScanStrategy grensesnitt:

offentlig grensesnitt ScanStrategy {ScanResult scan (Jedis jedis, String cursor, ScanParams scanParams); }

La oss først jobbe med det enkleste skanne strategi, som er uavhengig av samlingstypen og leser nøklene, men ikke verdien av nøklene:

offentlig klasse Scan implementerer ScanStrategy {public ScanResult scan (Jedis jedis, String cursor, ScanParams scanParams) {return jedis.scan (cursor, scanParams); }}

La oss deretter hente hscan strategi, som er skreddersydd for å lese alle feltnøklene og feltverdiene til en bestemt hash-nøkkel:

offentlig klasse Hscan implementerer ScanStrategy {privat strengnøkkel; @ Overstyr offentlig ScanResult scan (Jedis jedis, String cursor, ScanParams scanParams) {return jedis.hscan (key, cursor, scanParams); }}

Til slutt, la oss bygge strategiene for sett og sorterte sett. De sscan strategi kan lese alle medlemmene i et sett, mens zscan strategi kan lese medlemmene sammen med poengene sine i form av Tuples:

offentlig klasse Sscan implementerer ScanStrategy {privat strengnøkkel; offentlig ScanResult-skanning (Jedis jedis, strengmarkør, ScanParams scanParams) {return jedis.sscan (nøkkel, markør, scanParams); }} offentlig klasse Zscan implementerer ScanStrategy {privat strengnøkkel; @ Override offentlig ScanResult-skanning (Jedis jedis, strengmarkør, ScanParams scanParams) {return jedis.zscan (nøkkel, markør, scanParams); }}

7.2. Redis Iterator

La oss deretter skissere byggesteinene som er nødvendige for å bygge våre RedisIterator klasse:

  • Strengbasert markør
  • Skannestrategi som skanne, sscan, hscan, zscan
  • Plassholder for skanning av parametere
  • Tilgang til JedisPool å få en Jedis ressurs

Vi kan nå gå videre og definere disse medlemmene i vårt RedisIterator klasse:

privat finale JedisPool jedisPool; private ScanParams scanParams; privat strengmarkør; privat ScanStrategy-strategi;

Scenen vår er klar til å definere iteratorspesifikk funksjonalitet for iteratoren. For det, vår RedisIterator klassen må implementere Iterator grensesnitt:

offentlig klasse RedisIterator implementerer Iterator { }

Naturligvis kreves det at vi overstyrer hasNext () og neste () metoder arvet fra Iterator grensesnitt.

La oss først velge den lavthengende frukten - den hasNext () metode - da den underliggende logikken er rett frem. Så snart markørverdien blir “0”, vet vi at vi er ferdige med skanningen. Så la oss se hvordan vi kan implementere dette på bare en linje:

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

La oss deretter jobbe med neste () metode som løfter tungt ved skanning:

@Override public List neste () {if (cursor == null) {cursor = "0"; } prøv (Jedis jedis = jedisPool.getResource ()) {ScanResult scanResult = strategi.scan (jedis, markør, scanParams); markør = scanResult.getCursor (); returner scanResult.getResult (); } catch (Exception ex) {log.error ("Unntak fanget i neste ()", ex); } returner ny LinkedList (); }

Vi må merke oss det ScanResult gir ikke bare de skannede resultatene, men også neste markørverdi nødvendig for den påfølgende skanningen.

Til slutt kan vi aktivere funksjonaliteten til å lage vår RedisIterator i RedisClient klasse:

offentlig RedisIterator-iterator (int initialScanCount, strengmønster, ScanStrategy-strategi) {returner ny RedisIterator (jedisPool, initialScanCount, mønster, strategi); }

7.3. Les med Redis Iterator

Som vi har designet vår Redis iterator ved hjelp av Iterator grensesnitt, er det ganske intuitivt å lese samleverdiene ved hjelp av neste () metoden så lenge hasNext () returnerer ekte.

Av hensyn til fullstendighet og enkelhet lagrer vi først datasettet relatert til sportsballene i en Redis-hash. Etter det bruker vi vår RedisClient for å lage en iterator ved hjelp av Hscan skannestrategi. La oss teste implementeringen vår ved å se dette i aksjon:

@Test offentlig ugyldig testHscanStrategy () {HashMap hash = ny HashMap (); hash.put ("cricket", "160"); hash.put ("fotball", "450"); hash.put ("volleyball", "270"); redisClient.hmset ("baller", hasj); Hscan scanStrategy = ny Hscan ("baller"); int iterationCount = 2; RedisIterator iterator = redisClient.iterator (iterationCount, "*", scanStrategy); Liste resultater = ny LinkedList(); mens (iterator.hasNext ()) {results.addAll (iterator.next ()); } Assert.assertEquals (hash.size (), results.size ()); }

Vi kan følge den samme tankeprosessen med liten endring for å teste og implementere de gjenværende strategiene for å skanne og lese nøklene som er tilgjengelige i forskjellige typer samlinger.

8. Konklusjon

Vi startet denne opplæringen med en intensjon om å lære om hvordan vi kan lese alle matchende nøkler i Redis.

Vi fant ut at det er en enkel måte som Redis tilbyr å lese nøklene på en gang. Selv om det var enkelt, diskuterte vi hvordan dette setter en belastning på ressursene og derfor ikke er egnet for produksjonssystemer. Da vi gravde dypere, ble vi kjent med at det er et iteratorbasert tilnærming for skanning gjennom samsvarende Redis-taster for vår lesespørring.

Som alltid er den komplette kildekoden for Java-implementeringen som brukes i denne artikkelen tilgjengelig på GitHub.

Java bunn

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET