Translate

неділю, 24 липня 2016 р.

Redis Basics

Redis (REmote DIctionary Server) - високопродуктивна нереляційна база даних типу ключ-значення, розробку якої наразі фінансує Redis Labs, а раніше - Pivotal Software та VMware. Це програмне забезпечення з відкритим сирцевим кодом написане на мові C. Redis досить зрілий проект і має набір готових біндингів до різноманітних мов програмування.

Redis дуже часто розглядають як заміну Memcached, адже доступ до значень також відбувається по ключу. Проте він, на відміну від останнього, володіє купою переваг, серед яких:
  • Можливість з певною частотою зберігати дані з оперативної в постійну пам'ять. Тож у разі падіння демона дані будуть наявні після рестарту. У останніх версіях Redis є можливість вести лог операцій запису. Щось на зразок бінлогу в MySQL.
  • Більше можливих типів даних, котрі можуть зберігатись. Redis, окрім рядків (strings), підтримує збереження масивів (lists), хеш-таблиць (hash tables), множин (sets), сортованих множин (sorted sets), масиви біт (bitmaps) та HyperLogLog. Останні по-суті базуються на рядках (strings), які мають свою власну семантику. Детальніше про них далі.
  • Реплікація (built-in), кластеризація (Redis Sentinel, Redis Cluster), шардинг (Redis Cluster), висока доступність (Redis Sentinel, Redis Cluster). Цього всього немає в ванільному Memcached, але воно так необхідне при створенні інфраструктур високої доступності.
  • Підтримка транзакцій. Команди можуть бути виконані групою, і у разі невиконання однієї із них - відбудеться повернення на попередній стан. Також команди можна виконувати пакетами (виконуємо пачку команд, потім отримуємо пачку результатів).
  • Підтримка механізму publish/subscribe. Програми, що використовують Redis, можуть створювати іменовані канали і додавати в них повідомлення, а інші програми, відповідно, можуть забирати дані з таких каналів. У моєму розумінні, це щось схоже на черги в RabbitMQ.
Зупинимось для початку на тому, як створювати та працювати з перерахованими вище типами даних. У якості ОС я обрав Ubuntu 16.04, в репозиторіях якої вже знаходиться Redis 3.0.6. Тож його установка проходить максимально просто:

# apt install redis-server -y

Команди Redis приймає як аргумент до команди redis-cli,

# redis-cli -p 6379 info

так і прямо в консолі Redis

# redis-cli -p 6379
127.0.0.1:6379> info

Strings. Рядки в Redis - це найпростіша структура, котру можна прив’язати до ключа

> set mykey somevalue
OK

Та отримаємо значення по записаному ключу:

> get mykey
"somevalue"

По-замовчуванню, перезапис значення ключа відбувається повторною інструкцією set. Це відбувається без попередження:

> set mykey somevalue2
OK

> get mykey
"somevalue2"

Є можливість однією командою призначити декілька ключів і значень до них:

> mset a 10 b 20 c 30
OK

> get a
"10"

> mget a b c
1) "10"
2) "20"
3) "30"

Як і багато інших баз даних, Redis має деякі вбудовані логічні функції для рядків. Наприклад, інкремент:

> set counter 100
OK

> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152

Аналогічно працює і операція декремент.

Перевірка на існування ключа, видалення запису по ключу, перевірка типу ключа:

> set mykey hello
OK

> exists mykey
(integer) 1

> type mykey
string

> del mykey
(integer) 1

> exists mykey
(integer) 0

Redis може видаляти записи після перебігу певного часу:

> set key some-value
OK

> expire key 5
(integer) 1

> ttl key
(integer) 4

> get key
"some-value"

> get key
(nil)

Ключ key було витерто після проходження 5-ти секунд.
Подібні можливості є і в інших NoSQL базах на кшталт Cassandra чи MongoDB.

Перевірка довжини ключа відбувається наступним чином:

> strlen key
(integer) 10

Виведення окремих символів по індексу:

> getrange key 2 8
"me-valu"

Додавання до рядка деякого значення:

> append key " test"
(integer) 15

> get key
"some-value test"

І т.п. тут їх купа http://redis.io/commands/#string

Lists. Списки дозволяють зберігати і маніпулювати масивами значень для заданого ключа. Розробники Redis стверджують, що додавання нових елементів до списку відбувається на протязі сталої одиниці часу, незалежно від того, наскільки багато елементів в списку. Можливих дій зі списками є також немало. Створимо новий список:

> rpush mylist A
(integer) 1

> rpush mylist B
(integer) 2

> lpush mylist first
(integer) 3

> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"

Із виводу повного списку можна помітити, що команда lpush додає кожний новий елемент в початок, а rpush - в кінець. lrange, окрім ключа, також потребує вказання номерів елементів, діапазон яких треба вивести. Відповідно "-1" - це перший елемент, якщо рахувати з кінця.

Можна вставляти в список одразу купу елементів:

> rpush mylist 1 2 3 4 5 "foo bar"
(integer) 9

> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "foo bar"

Елементи зі списку можна читати по-черзі і видаляти (аналог методу pop в мові програмування Python):

> rpush mylist a b c
(integer) 3

> rpop mylist
"c"

> rpop mylist
"b"

> rpop mylist
"a"

> rpop mylist
(nil)

Окрім цього, списки можна обрізати:

> rpush mylist 1 2 3 4 5
(integer) 5

> ltrim mylist 0 2
OK

> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"

Отже, ltrim зменшив кількість елементів в списку до трьох - з нульового по другий.

Розширенням можливостей rpop та lpop функцій над списками є brpop and blpop. Вони дозволяють віддавати NULL лише після деякої вказаної затримки:

> rpush tasks 1 2 3
(integer) 3

> brpop tasks 5
1) "tasks"
2) "3"
> brpop tasks 5
1) "tasks"
2) "2"
> brpop tasks 5
1) "tasks"
2) "1"
> brpop tasks 5
(nil)
(5.14s)

Це може бути корисним для програм, котрі роблять постійно опитування списку на предмет нових елементів. У такому разі при появи нових елементів - вони будуть віддаватись із певною затримкою, замість частого опитування і постійного отримання nil.

Звісно, списки також можна видаляти власноруч:

> rpush mylist 1 2 3 4 5 "foo bar"
(integer) 6

> del mylist
(integer) 1

> exists mylist
(integer) 0

Порожні списки, і не лише списки, Redis видаляє автоматично.

Кількість елементів в списку розраховується також не менш очевидно:

> lpush mylist 1 2 3 4 5
(integer) 5

> llen mylist
(integer) 5

Кому не вистачило базових функцій - продовжити їх вивчення можна за посиланням http://redis.io/commands#list

Hash tables. Хеш-таблиці надають додатковий рівень адресації даних - поля. Якщо провести аналогію, то хеш-таблиці - це словники в мові Python. На прикладі буде легше зрозуміти про що йде мова:

> hmset user:1000 username antirez birthyear 1977 verified 1

> hget user:1000 username
"antirez"

> hget user:1000 birthyear
"1977"

> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"

У даному випадку в одному хеші було представлені деякі персональні дані користувача user:1000 (ключ хеша). Виводити значення полів можна як поодинці, так і разом всі можливі. Отже, hmset описує поля та значення, а з hget проходить отримання значення будь-якого із полів.

Якщо запитати поле, котрого не існує - буде повернуто nil:

> hmget user:1000 username birthyear no-such-field
1) "antirez"
2) "1977"
3) (nil)

Числові поля можуть бути інкрементовані:

> hincrby user:1000 birthyear 10
(integer) 1987
> hget user:1000 birthyear
"1987"

Вивести всі назви полів можна так:

> hkeys user:1000
1) "username"
2) "birthyear"
3) "verified"

Видаляти окремі поля також ніхто не забороняє:

> hdel user:1000 verified
(integer) 1

> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1987"

А щоб видалити хеш повністю, необхідно виконати наступне:

> del user:1000
(integer) 1

І так видаляється будь-яка структура, не лише хеш-таблиця.

Як завжди, більше можна почитати за посиланням http://redis.io/commands#hash.

Sets. Множини Redis - це невпорядковані масиви рядків (strings). На відміну від звичайних списків Redis, кожному новому елементу може бути призначена будь-яка випадкова позиція.

> sadd myset 1 3 2 4 5
(integer) 5

> smembers myset
1) "1"
2) "2"
3) "3"
4) "5"
5) "4"

При додаванні вже існуючого значення, воно не буде додане:

> sadd myset 5 6
(integer) 1

> smembers myset
1) "1"
2) "2"
3) "3"
4) "6"
5) "5"
6) "4"

Є можливість перевірки входження елементів в множину:

> sismember myset 3
(integer) 1

> sismember myset 30
(integer) 0

3 - елемент множини, а 30 - ні.

Sorted sets. Тип даних Redis, що має спільні риси як із простими множинами (sets), так і з хеш-таблицями (Hashes). Як і множини, сортовані множини складаються з унікальних елементів, тобто рядків (strings), що не повторюються.

До кожного елемента в сортованій множині прив'язане деяке цифрове значення (score). Ось чому сортовані множини також схожі на хеш-таблиці, адже кожен елемент прив'язаний до свого значення.

Більше того, елементи в сортованій множині, вже при створенні самої множини, сортуються у правильний порядок. У випадку якщо цифрове значення (score) у обох елементів множини рівне - сортування відбудеться по буквам алфавіту самих значень. На практиці це більш очевидніше:

> zadd hackers 1940 "Alan Kay"
(integer) 1
> zadd hackers 1957 "Sophie Wilson"
(integer) 1
> zadd hackers 1953 "Richard Stallman"
(integer) 1
> zadd hackers 1949 "Anita Borg"
(integer) 1
> zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
> zadd hackers 1914 "Hedy Lamarr"
(integer) 1
> zadd hackers 1916 "Claude Shannon"
(integer) 1
> zadd hackers 1969 "Linus Torvalds"
(integer) 1
> zadd hackers 1912 "Alan Turing"
(integer) 1
> zadd hackers 1953 "Guido van Rossum"
(integer) 1

zadd дуже схожий на sadd, котрий використовується для додавання елементів до звичайних множин, проте також вимагає присутності додаткового числового значення - score (в даному випадку - це рік народження).

Звісно, що сортовану множину також можна створити однією командою:

> zadd hackers 1940 "Alan Kay" 1957 "Sophie Wilson" 1953 "Richard Stallman" 1949 "Anita Borg" 1965 "Yukihiro Matsumoto" 1914 "Hedy Lamarr" 1916 "Claude Shannon" 1969 "Linus Torvalds" 1912 "Alan Turing" 1953 "Guido van Rossum"

Переглянемо результат:

> zrange hackers 0 -1 withscores
 1) "Alan Turing"
 2) "1912"
 3) "Hedy Lamarr"
 4) "1914"
 5) "Claude Shannon"
 6) "1916"
 7) "Alan Kay"
 8) "1940"
 9) "Anita Borg"
10) "1949"
11) "Guido van Rossum"
12) "1953"
13) "Richard Stallman"
14) "1953"
15) "Sophie Wilson"
16) "1957"
17) "Yukihiro Matsumoto"
18) "1965"
19) "Linus Torvalds"
20) "1969"

"Guido van Rossum" знаходиться попереду "Richard Stallman", адже в алфавіті G йде попереду R.

Лише сортовані елементи можна вивести наступною командою:

> zrange hackers 0 -1
 1) "Alan Turing"
 2) "Hedy Lamarr"
 3) "Claude Shannon"
 4) "Alan Kay"
 5) "Anita Borg"
 6) "Guido van Rossum"
 7) "Richard Stallman"
 8) "Sophie Wilson"
 9) "Yukihiro Matsumoto"
10) "Linus Torvalds"

Або ж вивести все в зворотному порядку можна таким чином:

> zrevrange hackers 0 -1
 1) "Linus Torvalds"
 2) "Yukihiro Matsumoto"
 3) "Sophie Wilson"
 4) "Richard Stallman"
 5) "Guido van Rossum"
 6) "Anita Borg"
 7) "Alan Kay"
 8) "Claude Shannon"
 9) "Hedy Lamarr"
10) "Alan Turing"

Звісно і в сортованих множинах є певний набір логічних операцій. Почнемо з zrangebyscore:

> zrangebyscore hackers -inf 1950
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"

Так будуть виведені хакери, що народились до 1950 року включно. Є можливість видалити групу елементів за заданим діапазоном score:

> zremrangebyscore hackers 1940 1960
(integer) 5

> zrange hackers 0 -1
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Yukihiro Matsumoto"
5) "Linus Torvalds"

zrank може показувати на якій позиції знаходиться той чи інший елемент сортованої множини:

> zrank hackers "Claude Shannon"
(integer) 2

Сортовані множини мають оператор zrangebylex, що може фільтрувати по алфавіту. Створимо нову сортовану множину:

> zadd hackers 0 "Alan Kay" 0 "Sophie Wilson" 0 "Richard Stallman" 0 "Anita Borg" 0 "Yukihiro Matsumoto" 0 "Hedy Lamarr" 0 "Claude Shannon" 0 "Linus Torvalds" 0 "Alan Turing"
(integer) 4

Так як score у всіх елементів однаковий - елементи автоматично будуть відсортовані по алфавіту:

> zrange hackers 0 -1
1) "Alan Kay"
2) "Alan Turing"
3) "Anita Borg"
4) "Claude Shannon"
5) "Hedy Lamarr"
6) "Linus Torvalds"
7) "Richard Stallman"
8) "Sophie Wilson"
9) "Yukihiro Matsumoto"

Відфільтруємо елементи від літери B до P:

> zrangebylex hackers [B [P
1) "Claude Shannon"
2) "Hedy Lamarr"
3) "Linus Torvalds"

Bitmaps. Це насправді не так окремий тип даних Redis, як набір побітних операцій над типом string. Так як рядки - це великі бінарні об'єкти (BLOB) та їх довжина в Redis може сягати 512 MB, отже максимальна кількість біт, котрими можна маніпулювати рівна 2^32.

Біти встановлюються і читаються інструкціями setbit та getbit відповідно:

> setbit key 10 1
(integer) 1

> getbit key 10
(integer) 1
> getbit key 11
(integer) 0

setbit, окрім назви ключа, приймає номер біта і його значення. Значення невстановленого біта буде рівним нулю.

Є три можливі інструкції вбудовані в Redis для роботи з групами бітів:

1. bitop виконує побітні операції між різними рядками: AND, OR, XOR та NOT
2. bitcount підраховує кількість встановлених в одиницю бітів.

> setbit key 0 1
(integer) 0

> setbit key 100 1
(integer) 0

> bitcount key
(integer) 2

3. bitpos шукає перший біт, що встановлений в значення 0 чи 1.

HyperLogLogs. HyperLogLog - імовірнісна структура даних, яка використовується для підрахунку унікальних елементів. Зазвичай підрахунок унікальних елементів доволі затратна річ для пам'яті, адже потрібно постійно запам'ятовувати всі унікальні елементи які вже було прочитано. Тобто кількість пам'яті, що буде використана, прямо пропорційна кількості елементів, робота з якими буде проводитись. Структура HyperLogLog має вбудовані алгоритми для оптимізації цієї дії, тому кількість елементів, серед котрих необхідно знайти унікальні, не впливає на кількість затраченої пам'яті. https://habrahabr.ru/post/119852/

> pfadd hll a b c d a a f g
(integer) 1

> pfcount hll
(integer) 6

Також структури HyperLogLogs можна об'єднувати за допомогою pfmerge.

Як я вже згадав, Redis підтримує транзакції. Продемонструємо це на прикладі нижче:

> hmset user:1000 username antirez birthyear 1977 verified 1
OK

> multi
OK
> hincrby user:1000 birthyear 10
QUEUED
> hincrby user:1000 verified 4
QUEUED
> exec
1) (integer) 1987
2) (integer) 5

Як бачимо, операції інкременту виконались пакетом, одразу після виклику exec.

Дефолтна інсталяція Redis створює 16 баз Redis (їх назви - відповідні номери). Якщо не обирати базу при створенні нових записів - обиратись по-замовчуванню буде нульова:

> select 0
OK
> keys *
 1) "a"
 2) "b"
 3) "user:1000"
 4) "users:leto"
 5) "foo"
 6) "counter"
 7) "mykey"
 8) "key"
 9) "hackers"
10) "hll"
11) "hello"
12) "c"

Ось і всі наші раніше створені ключі.

Зупинимось наразі на опціях Redis, що забезпечують цілісність данних та описуються в redis.conf. Перш за все, можна обмежити кількість оперативної пам'яті, котру може займати Redis для збереження даних:

maxmemory 100mb

При досягнення цього максимуму, Redis може активувати одну із перерахованих нижче політик її очистки:
  • volatile-lru - видалення ключів з найменш використовуваними даними за допомогою LRU(Less Recently Used)-алгоритму, але лише ключів із встановленими expire-інструкціями
  • allkeys-lru - видалення даних, до яких на протязі останнього часу менше всього звертались, задля забезпечення місця в пам'яті новим даним
  • volatile-random - видалення випадкових ключів, але лише зі встановленими expire-інструкціями
  • allkeys-random - видалення випадкових ключів
  • volatile-ttl - те ж саме, що і volatile-lru, але до уваги також береться час TTL. Спочатку будуть видалятись дані ключів, що мають найменший TTL
  • noeviction - у цьому випадку старі ключі видалятись не будуть, проте, при подальших спробах запису, клієнту буде повертатись помилка. Якщо клієнт запитає виконання операцій, що додатково потребує пам'яті вище ліміту maxmemory - йому також буде відмовлено
Політика noeviction використовується по-замовчуванню (навіть якщо ця опція закоментована в конфігураційному файлі):

maxmemory-policy noeviction

Детальніше з політиками очистки оперативної пам'яті в Redis можна ознайомитись за посиланням http://redis.io/topics/lru-cache

Актуальні версії Redis вміють також зберігати дані на диск, що дуже корисно у разі перевантаження сервісу, його падіння у разі помилки чи відключення електричної мережі. Memcached не мав такої можливості: все що зникло - зникло з кінцями. Тому у разі його перевантаження, генерування нового кешу могло займало багато часу, що негативно впливано на швидкість роботи системи вцілому (наприклад, сесії створювались по-новому).

По-замовчуванню, Redis має таку політику збереження даних з оперативної пам'яті на диск:

save 900 1
save 300 10
save 60 10000

Що означає запуск збереження даних на диск через 900 секунд у разі, коли хоча б значення одного ключа змінилось. Збереження даних може відбутись і раніше, якщо на протязі 300 секунд було змінено значення 10 ключів чи значення 10000 ключів змінились на протязі 60 секунд.

appendonly no

Загалом, для гарантування цілісності бази даних Redis існує 2 механізму: RDB (про який я згадав вище) та AOF (Append Only File). По замовчуванню, ввімкнений лише RDB. Цей механізм асинхронно дампить дані на диск через певні проміжки часу. Його більше ніж досить у більшості випадків. Проте, якщо щось станеться з процесом чи відбудеться непланове вимкнення електроенергії, можлива втрата останніх змін на протязі декількох хвилин (в залежності від політики save описаної вище).

Append Only File - це альтернатива механізму забезпечення цілісності даних RDB. AOF логує кожну операцію запису в файл, тож у подібним непередбачуваних випадках дані будуть відновлюватись із логу AOF. Тож очевидною перевагою Append Only File режиму є можливість відновити дані без втрат, у разі падіння демону Redis. У разі переповнення історії операцій AOF, дані можуть очищуватись та перезаписуватись - тому лише AOF не може повністю гарантувати цілісність. Також AOF файли можуть бути значно більшими за RDB снепшоти і, очевидно, через операції запису такого логу,  можуть дещо уповільнювати роботу Redis. Тому вмикати цю опцію чи ні - особистий вибір кожного, компроміс між швидкістю та цілісністю даних. Дещо детальніше за посиланням http://redis.io/topics/persistence

appendfsync everysec

Опція дозволяє указувати як часто необхідно логувати операції запису (AOF механізм) на диск. Параметр appendfsync може також бути встановлений в значення always чи no.

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

Опції описують політику перезапису файлу операцій AOF. Redis запам'ятовує розмір AOF файлу лише після останнього запису логів операцій. Наступного разу, перед записом нових логів, Redis порівнює порівнює поточний розмір логу AOF з auto-aof-rewrite-percentage і, у разі його перевищення, перезаписує його.

Опцій, з якими може працювати Redis, дуже багато і в одній статті описати їх дуже складно. Тому раджу звернутись до офіційної документації, яка в Redis дуже якісна і зрозуміла.

То де ж використовувати Redis? Його можна успішно використовувати для наступних задач:
  • сховище web-сесій і профілів користувачів
  • сервер черг
  • сховище простих даних: кількість користувачів онлайн чи відвідувачів сторінки на протязі деякого часу, коди капч і т.п. У цьому звісно можуть допомогти вбудовані логічні операції Redis
  • як кеш для додатків (управляючи опцією maxmemory-policy)
  • сховище основних даних додатків. Наприклад, Sensu використовує Redis у якості основної бази для своїх даних
  • і т.п.
В наступній серії статей я опишу способи організації високої доступності, кластеризації, шардингу Redis: Master-Slave, Redis Sentinel та Redis Cluster.

Посилання:
http://redis.io/topics/data-types-intro
http://redis.io/topics/data-types
http://www.slideshare.net/arthurshvetsov/redis-basics
https://www.techandme.se/performance-tips-for-redis-cache-server/
http://www.slideshare.net/eleksdev/nosql-basics
http://redis.io/topics/persistence
http://eax.me/redis/
https://www.digitalocean.com/community/tutorials/how-to-install-and-use-redis

Немає коментарів:

Дописати коментар