niedziela, 5 października 2014

Wylosuj nagrodę z Redisem

Od czasu istnienia tego blogu koncentrowałam się na bazach relacyjnych. Tym razem przyszedł czas na Redis. Ta NoSQL baza danych została stworzona specjalnie po to aby podpowiedzieć na wymagania bardzo szybkiego odpowiadania lub obsługi bardzo szybko zmieniających się danych. Z satysfakcją jest używana w miejscach gdzie wcześniej był stosowany Memcache bo zawiera w sobie wszystkie te same cechy i o wiele więcej.

Główne cechy Redisa:
  • napisany w języku C
  • niesamowicie szybki
  • używany protokół jest telneto-podobny, binarnie bezpieczny
  • dane są przechowywane w pamieci
  • pojemność bazy jest ograniczona do pojemności RAM maszyny
  • replikacja master-slave z automatycznym failoverem
  • podstawowa struktura danych to klucz - wartość
  • bardzo duża ilość różnych operacji, które można wykonywać na danych
  • posiada możliwość zapisywania: 
    • list (lists)- kolekcja elementów
    • zbiorów (sets) - kolekcja unikalnych elementów
    • posortowanych zbiorów (sorted sets) - jak wyżej z dodatkowym sortowaniem
    • słowników - (hashes) - kolekcja typu klucz - wartość
  • posiada transakcje - zapytania są serializowane co powoduje, że pojedyncze operacje są izolowane
  • możliwe jest ustawienie czasu po jakim wartość zostanie automatycznie usunięta (TTL)
  • pub/sub - implementacja messagingu
  • open-sursowy, oparty na licencji BSD

Instalacja bazy danych Redis wiąże się tylko z kilkoma krokami (dobrze jest wykonać testy aby mieć pewność, że wszystko poszło jak należy). Ostatni krok to uruchomienie serwera.

$ wget http://download.redis.io/releases/redis-2.8.17.tar.gz
$ tar xzf redis-2.8.17.tar.gz
$ cd redis-2.8.17
$ make
$ make test
$ src/redis-server


Sprawdźmy najpierw użycie niektórych poleceń we wbudowanym kliencie redis-cli:

ela@skyler:~/work/redis-2.8.17$ src/redis-cli
127.0.0.1:6379>

W momencie gdy logujemy się do Redisa, jesteśmy automatycznie zalogowani do bazy danych 0. Zmiana na inną bazę danych powodowana jest przez polecenie 'SELECT ID' (Tutaj znajdziecie wszystkie polecenia).

ela@skyler:~/work/redis-2.8.17$ src/redis-cli
127.0.0.1:6379> select 10                              # przełączenie się na index (bazę danych ID=10)
OK
127.0.0.1:6379[10]> sadd tokens 0f9661323              # dodanie do zbioru (i tak 1000 razy)
(integer) 1
127.0.0.1:6379[10]> keys '*'                           # wyświetlenie wszystkich kluczy, które pasują do wzorca
1) "tokens"
127.0.0.1:6379[10]> scard tokens                       # zliczenie memberów (elementów) zbioru
(integer) 1000
127.0.0.1:6379[10]> srandmember tokens 10              # wyświetlenie 10 randomowo wybranych elementów zbioru
 1) "0f9661323"
 2) "aa942ab2b"
 3) "0fcbc61ac"
 4) "08b255a5d"
 5) "96b9bff01"
 6) "49182f81e"
 7) "8e98d81f8"
 8) "23ce18513"
 9) "52720e003"
10) "7f6ffaa6b"
127.0.0.1:6379[10]> flushall                           # usunięcie wszystkich kluczy ze wszystkich baz danych
OK
127.0.0.1:6379[10]> keys '*'
(empty list or set)

Aby przedstawić choć część działania Redis napisałam system losowania tokenów. Tokeny są trzymane w zbiorze 'tokens'. Gdy użytkownik wysyła request o token, jest on losowo wybierany i usuwany ze zbioru przy pomocy polecenia spop. Wybrany token jest wstawiany do zbioru used. System został napisany w ruby przy użyciu gemów: redis (klient Redis), sinatra (framework web).

Zaczęłam od wygenerowania 1000 tokenów i załadowania ich do redis:

#!/usr/bin/env ruby -w

require 'redis'
require 'digest/md5'

# redis connection
REDIS_HOST = '192.168.0.10'
REDIS_PORT = 6379
REDIS_DATABASE_ID = 10

TOKENS_COUNT = 1000
REDIS_SET_KEY = 'tokens'

redis = Redis.new(:host => REDIS_HOST, :port => REDIS_PORT, :db => REDIS_DATABASE_ID)
redis.flushdb


TOKENS_COUNT.times do |i|
 md5 = Digest::MD5.hexdigest(i.to_s)
 redis.sadd REDIS_SET_KEY, md5[0..8]
end

Przy pomocy gemu sinatra stworzyłam prosty interface webowy do pobierania i wyświetlania informacji o tokenach:

[ela:~/work/ruby ]$ cat get_token_page.rb
#!/usr/bin/env ruby -w

require 'sinatra'
require 'redis'

# redis connection
REDIS_HOST = '192.168.0.10'
REDIS_PORT = 6379
REDIS_DATABASE_ID = 10
REDIS_TOKENS_AVALIABLE_SET_KEY = 'tokens'
REDIS_TOKENS_USED_SET_KEY = 'used'

redis = Redis.new(:host => REDIS_HOST, :port => REDIS_PORT, :db => REDIS_DATABASE_ID)

get '/' do
 %(Click <a href="/token">here</a> to get a token)
end

get '/token' do
 token = redis.spop REDIS_TOKENS_AVALIABLE_SET_KEY
 redis.sadd REDIS_TOKENS_USED_SET_KEY, token
 %(This is your token: #{token})
end

get '/tokens-used' do
 tokens = redis.smembers REDIS_TOKENS_USED_SET_KEY
 token_list = tokens.map{|t| %(<li>#{t}</li>)}
 %(<ul>#{token_list.join}</ul>)
end

get '/stats' do
 tokens_avaiable_count = redis.scard REDIS_TOKENS_AVALIABLE_SET_KEY
 tokens_used_count = redis.scard REDIS_TOKENS_USED_SET_KEY
 %(# Tokens avaliable :#{tokens_avaiable_count} )+
 %(# Tokens used :#{tokens_used_count} )+
 %(# All Tokens :#{tokens_avaiable_count + tokens_used_count} )
end


Strona wygląda następująco:





Podsumowując, w programie użyłam zbioru aby mieć pewność unikalnych tokenów. Redis dla zbiorów zapewnia funkcję do wybierania losowego elementu i jego usunięcia - polecenie spop. Jest to operacja atomowa, dzięki czemu mam pewność, że każdy token zostanie użyty tylko raz. Inne polecenia:

  • sadd - dodanie nowego elementu do zbioru jeśli jeszcze nie istniał
  • smembers - pobranie wszystkich elementów zbioru
  • scard - pobranie ilości elementów w zbiorze
  • flushdb - usunięcie wszystkich kluczy i wartości aktualnie wybranej bazy bez usunięcia samej bazy 

Redis posiada także możliwość definiowania transakcji. Gdzie użylibyście jej w moim programie?


Brak komentarzy:

Prześlij komentarz