Глава 4. Разработка с помощью Redis

Содержание

Глава 4. Разработка с помощью Redis
Введение
Когда применять Reddis в вашем приложении
Сохранение сеанса
Аналитика
Рейтинги
Очереди
Последние N записей
Кэширование
Также ознакомьтесь...
Дополнительно
Применение правильных типов данных
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Применение правильного API Redis
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Подключение к Redis в Java
Подготовка...
Как это сделать...
Подключение к серверу Redis
Применение конвейеров в Jedis
Применение транзакций в Jedis
Запуск сценариев Lua в Jedis
Применение пула подключений в Jedis
Как это работает...
Также ознакомьтесь...
Подключение к Redis в Python
Подготовка...
Как это сделать...
Подключение к серверу Redis
Применение конвейеров
Запуск сценариев Lua
Как это работает...
Также ознакомьтесь...
Дополнительно
Подключение к Redis при помощи Spring Data Redis
Подготовка...
Как это сделать...
Как это работает...
Дополнительно
Создание задания MapReduce для Redis
Подготовка...
Как это сделать...
Как это работает...
Дополнительно
Создание задания Spark для Redis
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно

В данной главе мы рассмотрим такие рецепты:

  • Когда применять Redis в вашем приложении.

  • Использование верных типов данных.

  • Применение правильного API Redis.

  • Соединение Redis с Java.

  • Подключение Redis в Python.

  • Сочленение Redis с Spring Data Redis.

  • Написание задания MapReduce для Redis.

  • Создание задания Spark для Redis.

Введение

В Главе 2, Типы данных и Главе 3, Свойства данных мы изучили имеющиеся типы данных и многие полезные свойства Redis. В этой главе мы сосредоточимся на теме разработки приложения при помощи Redis.

Вначале мы начнём с введения в некоторые распространённые варианты использования Redis, чтобы дать вам краткие мысли по поводу того как Redis работает в реальном мире.

После этого будут предоставлены некоторые руководящие правила того как ыбирать правильные типы значений и API. При разработке приложений с помощью Redis крайне важно следовать этим руководящим правилам, так как в отличии от большинства RDBMS вы почти ничего не можете делать для оптимизации вычислительной мощности Redis выполняя только настройку конфигурации на стороне своего сервера. Применение надлежащих типов данных и API с самого начала проектирования приложения является необходимым ключевым фактором для полного применения высокой производительности Redis и в то же самое время для обхода её ловушек.

После приведения руководящих правил мы собираемся показать как разрабатывать какое- то приложение с помощью Redis применяя Java и Python. Для разработки веб приложений также мы рассматриваем Spring Data Redis.

Наконец, самые последние два рецепта в данной главе рассмотрят как применять Redis в мире больших данных.

Когда применять Reddis в вашем приложении

Поскольку вы уже видели мощные свойства Redis в предыдущих главах, мы можем быть удивлены тем, что redis может делать в некотором приложении и когда его следует применять. В данном рецепте мы покажем несколько сценариев приложений реального мира в которых Redis является наилучшим выбором по сравнению с прочими решениями хранения. Надеемся, что вы получите и дополнительные идеи применения Redis в ваших приложениях.

Сохранение сеанса

При современной архитектуре веб площадок позади одного или нескольких балансировщиков нагрузки присутствует множество веб серверов. Обычно требуется сохранять сеансы во внешних системах хранения. Если какой- либо из имеющихся серверов отключается, другой сервер может получить данный сеанс из такого внешнего хранилища и продолжить обслуживание обмена. Redis является исключительным хранилищем сеансов, так как он имеет очень низкую латентность доступа в сравнении с RDBMS. Кроме того, поддержка срока давности в Redis может естественным образом адаптирована для управления таймаутом сеанса.

Аналитика

Redis также может применяться для целей аналитики и статистики. Например, если вы желаете подсчитывать сколько пользователей просмотрели некий ресторан в вашем приложении, мы можем просто воспользоваться командой INCR для инкрементального приращения своего счётчика при просмотре данного ресторана. Нам не следует беспокоиться об условиях скорости, так как команды Redis являются атомарными. Более изощрённые счётчики или статистические данные, захватываемые в системе могут быть построены при помощи таких типов данных как хэш, Сортированный набор и HyperLoglog.

Рейтинги

При помощи Сортированного набора в Redis мы легко можем реализовать некий рейтинг. Как мы уже видели в рецепте Применение типа данных сортированного множества из Главы 2, Типы данных, мы можем создать некое Сортированное множество ресторанов; значение суммы баллов в этом Множестве является числом голосов пользователей. Таким образом, наша команда ZREVRANGE возвращает рестораны в соответствии с их популярностью. Ту же самую функциональность мы также можем реализовать при помощи некоей RDBMS, такой как MySQL, однако запрос SQL будет намного медленнее чем Redis.

Очереди

Вспомните команды с блокировкой PUSH/POP, которые мы представили в рецепте Применение типа данных списков в Главе 2, Типы данных. Как мы показали в этом рецепте, список данных Redis можно применить для реализации некой простой очереди заданий. Широко применяемый проект Resque, который является некоторой библиотекой Ruby на основе Redis для очередей заданий, базируется на этой идее. При помощи команды RPOPLPUSH мы также можем реализовывать надёжные очереди с применением списков Redis.

Последние N записей

Допустим, что нам бы хотелось получить десятку ресторанов, которые были добавлены в наше приложение самыми последними. Если бы мы применяли RDBMS, мы бы должны были запустить некий запрос SQL, подобный такому:


SELECT * FROM restaurants ORDER BY created_at DESC LIMIT 10
 	   

Для решения этой задачи мы можем воспользоваться преимуществами списка Redis:

  1. Введём в строй некий список Redis, latest_restaurants

  2. При всяком новом добавлении ресторана выполняйте LPUSH latest_restaurants и LTRIM recent_restaurants 0 10. Таким образом наш список Redis всегда будет содержать самые последние десять ресторанов.

Кэширование

Так как Redis является системой хранения данных в оперативной памяти, использование Redis в качестве некоторого кэша спереди какой- то RDBMS обычно ускоряет обработку запроса такой базы данных. Вот некий пример варианта использования: прежде чем запрашивать свою RDBMS, мы вначале просматриваем записи в Redis. Если такой записи нет в Redis, мы выполняем запрос в своей RDBMS и помещаем соответствующую запись в Redis. При записи в нашу RDBMS мы также записываем эту запись в Redis. Записи в таком кэше связываются с неким таймаутом или с какой- то политикой отселения, например LRU (Least Recently Used, наиболее долго неиспользованных) для ограничения предела использования данного кэша.

Также ознакомьтесь...

Как показывают некоторые приведённые выше варианты применения, определённые задачи, которые являются медленными или трудно выполнимыми в RDBMS могут быть проще и быстрее делаться в Redis. Однако, redis не является вариантом для всех потребностей хранения. Во- первых, поскольку по умолчанию распределённый Redis хранит все данные в оперативной памяти (некоторые основанные на облачных решениях службы Redis предоставляют вариант применения SSD в качестве основы для хранения таких данных), если же размер данных превосходит размер имеющейся памяти, Redis не может удерживать все данные. Во- вторых, транзакции Redis не являются полностью совместимыми с Атомарностью, Согласованностью, Изолированностью и Устойчивостью (Atomicity, Consistency, Isolation и Durability, ACID). Если необходимы транзакции ACID, Redis не может применяться. Для таких вариантов применения следует использовать RDBMS или иную систему базы данных.

Дополнительно

Для понимания смысла ACID обратитесь к справочным материалам https://en.wikipedia.org/wiki/ACID.

Относительно проекта Resque обратитесь к https://github.com/resque/resque.

Применение правильных типов данных

В Главе 2, Типы данных мы изучили что Redis предоставляет богатые типы данных для наполнения деловых потребностей. Поэтому, когда при помощи Redis мы разрабатываем некоторое приложение, для нас крайне важно для начала быть знакомыми с этими типами данных. Такое знакомство включает в себя не только осведомлённость об имеющихся семантических различиях между ними, но также и выявление их сильных и слабых сторон когда мы собираемся реализовывать с их помощью определённые бизнес сценарии. Хотя не составляет труда при проектировании выбрать один или более типов данных для соответствия нашим требованиям, всегда имеется место для оптимизации когда мы рассматриваем значение производительности и потребления памяти.

В данном рецепте мы собираемся выполнить пример проектирования хранения пользовательских данных чтобы показать как применять верные типы данных для меньшего потребления памяти.

Подготовка...

Вам требуется завершить установку своего Cервера Redis, которое мы описывали в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.

Вам понадобится выполнить команду FLUSHALL для сброса всех имеющихся данных в вашем экземпляре Redis прежде чем вы перейдёте к следующему разделу.

Для установки инструментов dos2unix в Ubuntu выполните следующую команду:


sudo apt-get install dos2unix
		

В macOS вы можете установить инструментарий dos2unix при помощи следующей команды:


brew install dos2unix
		

Как это сделать...

Чтобы показать как выполнять правильный выбор типа данных, мы пожелаем сохранять имеющуюся информацию пользователя Relp в Redis. В качестве прототипа подтверждения решения мы собираемся наполнить Redis некими образцами данных для 10 000 пользователей чтобы проверить что мы применяем наилучшую стратегию для хранения данных пользователей в Redis. Это означает, что при соблюдении потребностей дела данного приложения, мы попытаемся применять настолько мало памяти, насколько это возможно. Для иллюстрации целей для одного пользователя будет сохраняться следующая информация:

  • id: идентификатор пользователя.

  • name: имя пользователя.

  • sex: пол.

  • register_time: временной штамп подписи.

  • nation: национальная принадлежность пользователя.

  1. Мы проверяем потребление памяти неким пустым экземпляром Redis:

    
    127.0.0.1:6379> INFO MEMORY
     # Memory
     used_memory:827512
     used_memory_human:808.12K
     	   
  2. Первое что приходит в голову это сохранять каждыё элемент информации в виде строковой пары ключ- значение:

    
    $ cat populatedata0.sh
    #!/bin/bash
    DATAFILE="string.data"
    rm $DATAFILE >/dev/null 2>∓1
    NAMEOPTION[0]="Jack"
    NAMEOPTION[1]="MIKE"
    NAMEOPTION[2]="Mary"
    SEXOPTION[0]="m"
    SEXOPTION[1]="f"
    NATIONOPTION[0]="us"
    NATIONOPTION[1]="cn"
    NATIONOPTION[2]="uk"
    for i in `seq -f "%010g" 1 10000`
    do
      namerand=$[ $RANDOM % 3 ]
      sexrand=$[ $RANDOM % 2 ]
      timerand=$[ $RANDOM % 30 ]
      nationrand=$[ $RANDOM % 3 ]
      echo "set \"user:${i}:name\" \"${NAMEOPTION[$namerand]}\"" >> $DATAFILE
      echo "set \"user:${i}:sex\" \"${SEXOPTION[$sexrand]}\"" >> $DATAFILE
      echo "set \"user:${i}:resigter_time\" \"`date +%s%N`\"" >> $DATAFILE
      echo "set \"user:${i}:nation\" \"${NATIONOPTION[$nationrand]}\"" >> $DATAFILE
      sleep 0.00000${timerand}
    done
    unix2dos $DATAFILE
    $ bin/redis-cli FLUSHALL
    OK
    $ bash populatedata0.sh
    unix2dos: converting file string.data to DOS format ...
    $ cat string.data | redis-cli --pipe
    All data transferred. Waiting for the last reply...
    Last reply received from server.
    errors: 0, replies: 40000
     	   
  3. Внутри redis-cli проверим потребление памяти, воспользовавшись INFO MEMORY:

    
    127.0.0.1:6379> INFO MEMORY
    # Memory
    used_memory:4231960
    used_memory_human:4.04M
     	   
  4. Самой первой оптимизацией, которую мы собираемся сделать состоит в сохранении информации в виде строки JSON:

    
    $ cat populatedata1.sh  
    #!/bin/bash 
    DATAFILE="json.data" 
    ... (omit all the variables declared above) 
     
    for i in `seq -f "%010g" 1 10000` 
    do 
        namerand=$[ $RANDOM % 3 ] 
        sexrand=$[ $RANDOM % 2 ] 
        timerand=$[ $RANDOM % 30 ] 
        nationrand=$[ $RANDOM % 3 ]
        echo "set \"user:${i}\" '{\"name\":\"${NAMEOPTION[$namerand]}\",sex:\"${SEXOPTION[$sexrand]}\",resigter_time:`date +%s%N`,nation:\"${NATIONOPTION[$nationrand]}\"}'" >> $DATAFILE      
        sleep 0.00000${timerand} 
    done 
     
    unix2dos $DATAFILE  
     
    $ bin/redis-cli FLUSHALL 
    OK 
     
    $ bash populatedata1.sh 
    unix2dos: converting file string.data to DOS format ... 
     
    $ cat json.data | redis-cli --pipe 
    All data transferred. Waiting for the last reply... 
    Last reply received from server. 
    errors: 0, replies: 10000
     	   
  5. Проверяя потребление нами памяти мы обнаруживаем, что было сохранено около 43% ((4.04-2.29)/4.04) пространства памяти:

    
    127.0.0.1:6379> INFO MEMORY
    # Memory
    used_memory:2398896
    used_memory_human:2.29M
     	   
  6. После этого мы применим некий сценарий Lua, который был представлен нами в Главе 3, Свойства данных, для упорядочения наших сырых строк JSON при помощи библиотеки msgpack:

    
    $ cat setjsonasmsgpack.lua 
    --EVAL 'this script' 1 some-key '{"some": "json"}' 
    local key = KEYS[1]; 
    local value = ARGV[1]; 
    local mvalue = cmsgpack.pack(cjson.decode(value)); 
    return redis.call('SET', key, mvalue); 
     
    $ cat populatedata2.sh 
    #!/bin/bash 
    DATAFILE="msgpack.data" 
     
    rm $DATAFILE >/dev/null  2>&1 
    ... (omit all the variables declared above) 
     
    for i in `seq -f "%010g" 1 10000` 
    do 
        namerand=$[ $RANDOM % 3 ] 
        sexrand=$[ $RANDOM % 2 ] 
        timerand=$[ $RANDOM % 30 ] 
        nationrand=$[ $RANDOM % 3 ] 
            echo "user:00000${i}{\"name\":\"${NAMEOPTION[$namerand]}\",\"sex\":\"${SEXOPTION[$sexrand]}\",\"resigter_time\":`date +%s`,\"nation\":\"${NATIONOPTION[$nationrand]}\"}" >> $DATAFILE      
           sleep 0.00000${timerand} 
    done 
     
    unix2dos $DATAFILE 
     
    $ bin/redis-cli FLUSHALL 
    OK 
     
    $ bash populatedata2.sh 
    unix2dos: converting file msgpack.data to DOS format ... 
     
    $ cat msgpack.data | while read CMD; do  var1=$(cut -d' ' -f1 <<< $CMD); var2=$(cut -d' ' -f2 <<< $CMD) ; /bin/redis-cli --eval setjsonasmsgpack.lua  $var1 , $var2; done
     	   
  7. С помощью упорядочения msgpack мы сохранили около 49% ((4.04-2.06)/4.04) памяти:

    
    127.0.0.1:6379> INFO MEMORY
    # Memory
    used_memory:2159048
    used_memory_human:2.06M
     	   
  8. Далее мы продолжим попытки оптимизации своего решения при помощи типа данных хэша:

    
    $ cat populatedata3.sh 
    #!/bin/bash 
    DATAFILE="hash.data" 
     
    ... (omit all the variables declared above) 
     
    for i in `seq -f "%010g" 1 10000` 
    do 
        namerand=$[ $RANDOM % 3 ] 
        sexrand=$[ $RANDOM % 2 ] 
        timerand=$[ $RANDOM % 30 ] 
        nationrand=$[ $RANDOM % 3 ]
        echo "hset \"user:${i}\" \"name\" \"${NAMEOPTION[$namerand]}\" \"sex\" \"${SEXOPTION[$sexrand]}\" \"resigter_time\" `date +%s%N` \"nation\" \"${NATIONOPTION[$nationrand]}\"" >> $DATAFILE
        sleep 0.00000${timerand}
    done 
     
    unix2dos $DATAFILE 
         
    $ bin/redis-cli FLUSHALL 
    OK 
     
    $ bash populatedata3.sh  
    unix2dos: converting file hash.data to DOS format ... 
     
    $ cat hash.data | redis-cli --pipe 
    All data transferred. Waiting for the last reply... 
    Last reply received from server. 
    errors: 0, replies: 10000
     	   
  9. Снова проверим потребление памяти. Опаньки! кажется, что в сравнении с нашим предыдущим механизмом оптимизации при посощи msgpack на этот раз используется больше памяти и примерно то же количество памяти, которое требовалось для решения со строками JSON:

    
    127.0.0.1:6379> INFO MEMORY
    # Memory
    used_memory:2399352
    used_memory_human:2.29M
     	   
  10. Мы обнаружили это, хотя для сохранения данных пользователей мы применяли тип данных хэша, причём в сравнении с методом строк JSON общее количество ключей не устранено. Поэтому мы попробуем снизить общее число ключей путём разделения имеющегося идентификатора пользователя. Для начала мы изменим свои настройки значения максимального числа элементов и значения хеширования для применения ziplist:

    
    $ vim conf/redis.conf
    hash-max-ziplist-entries 1000
    hash-max-ziplist-value 64
     	   
  11. Перезапустим свой Сервер Redis чтобы заставить работать свои сетевые настройки. После этого начнём проверку нашего прототипа:

    
    $ cat populatedata4.sh 
    #!/bin/bash 
    DATAFILE="hashpartition.data" 
     
    PLENGTH=3 
     
    ... (omit all the variables declared above) 
     
     
    for i in `seq -f "%010g" 1 10000` 
    do 
        namerand=$[ $RANDOM % 3 ] 
        sexrand=$[ $RANDOM % 2 ] 
        timerand=$[ $RANDOM % 30 ] 
        nationrand=$[ $RANDOM % 3 ] 
        LENGTH=`echo ${#i}` 
        LENGTHCUT=`echo $((LENGTH-PLENGTH))` 
        LENGTHEND=`echo $((LENGTHCUT+1))` 
        VALUE1=`echo $i | cut -c1-${LENGTHCUT}` 
        VALUE2=`echo $i | cut -c${LENGTHEND}-${LENGTH}`
        echo "hset \"user:${VALUE1}\" $VALUE2 '{\"name\":\"${NAMEOPTION[$namerand]}\",\"sex\":\"${SEXOPTION[$sexrand]}\",\"resigter_time\":`date +%s%N`,\"nation\":\"${NATIONOPTION[$nationrand]}\"}'" >> $DATAFILE   
      sleep 0.00000${timerand} 
    done 
     
    unix2dos $DATAFILE  
     
    $ bin/redis-cli FLUSHALL 
    OK 
     
    $ bash populatedata4.sh  
    unix2dos: converting file hashpartition.data to DOS format ... 
     
    $ cat hashpartition.data | redis-cli --pipe 
    All data transferred. Waiting for the last reply... 
    Last reply received from server. 
    errors: 0, replies: 10000
     	   
  12. Проверим объём памяти для раздела хэша. Совершенно ясно видно, что было сохранено около 52% ((4.04-1.94)/4.04) памяти за счёт такой оптимизации:

    
    127.0.0.1:6379> INFO MEMORY
    # Memory
    used_memory:2032160
    used_memory_human:1.94M
     	   
  13. Наконец, мы попытаемся объединить свой раздел хэша и упорядочение msgpack чтобы посмотреть чего мы достигнем:

    
    $ cat populatedata5.sh 
    #!/bin/bash 
    DATAFILE="hashpartitionmsgpack.data" 
    PLENGTH=3 
     
    rm $DATAFILE >/dev/null  2>&1 
     
    ... (omit all the variables declared above) 
     
    for i in `seq -f "%010g" 1 10000` 
    do 
        namerand=$[ $RANDOM % 3 ] 
        sexrand=$[ $RANDOM % 2 ] 
        timerand=$[ $RANDOM % 30 ] 
        nationrand=$[ $RANDOM % 3 ] 
        LENGTH=`echo ${#i}` 
        LENGTHCUT=`echo $((LENGTH-PLENGTH))` 
        LENGTHEND=`echo $((LENGTHCUT+1))` 
        VALUE1=`echo $i | awk '{print substr($1,1,"'$LENGTHCUT'")}'` 
        VALUE2=`echo $i | awk '{print substr($1,"'$LENGTHEND'","'$PLENGTH'")}'`
        echo "user:${VALUE1} $VALUE2 {\"name\":\"${NAMEOPTION[$namerand]}\",\"sex\":\"${SEXOPTION[$sexrand]}\",\"resigter_time\":`date +%s%N`,\"nation\":\"${NATIONOPTION[$nationrand]}\"}" >> $DATAFILE sleep 0.00000${timerand} 
    done 
     
    unix2dos $DATAFILE  
     
    $  bin/redis-cli FLUSHALL 
    OK 
     
    $ bash populatedata5.sh 
    unix2dos: converting file hashpartitionmsgpack.data to DOS format ... 
     
    $ cat hashpartitionmsgpack.data | while read CMD; do  var1=$(cut -d' ' -f1 <<< $CMD); var2=$(cut -d' ' -f2 <<< $CMD) ; var3=$(cut -d' ' -f3 <<< $CMD); /redis/bin/redis-cli --eval setjsonashashmsgpack.lua  $var1 $var2 , $var3; done
     	   
  14. Ошеломлюяще обнаружить, что после всех оптимизаций наши образцы данных занимают только 1.34M, что составляет всего 33% (1.34/4.04) от объёма памяти, потребляемого в первоначальном решении:

    
    127.0.0.1:6379> INFO MEMORY
    # Memory
    used_memory:1403272
    used_memory_human:1.34M
     	   

Как это работает...

Когда мы приступаем к проектированию некоторого приложения с применением Redis, один из моментов, который следует держать на уме, состоит в том, что не всегда есть только один тип данных, который соответствует вашему бизнес сценарию. Это означает, что различные требования для хранения и производительности запроса приводят к различным решениям. Вашим заданием при проектировании является в вытаскивании на поверхность понимания того, какой именно тип данных является наилучшим в вашем случае применяя проверки различных прототипов, как в предыдущем примере.

В данном примере вначале вы делаем некий базовый проект, который является простым соответствием некоторого ключа атрибуту пользователя. Мы делаем это на самом первом этапе в качестве базового уровня потребления памяти для ссылок на него в дальнейшем.

После этого мы пытаемся уменьшить своё потребление памяти двумя различными способами. Один состоит в попытке уменьшения общего числа ключей. Причина по которой мы делаем это состоит в том, что Redis применяет для сопровождения какого- то ключа некие внутренние структуры данных. Именно уменьшение общего числа ключей приводит к меньшей стоимости внутренних структур данных, поэтому мы можем сберечь много памяти. Для данной цели, в своём предыдущем примере мы пытаемся применять строку JSON в качестве значения для одного пользователя и использовать тип данных хэша.

Особое внимание следует уделить разбиению на разделы ключа хэша. Мы собираемся хранить по 1000 записей в некотором ключе хэша. Поэтому, самая первая вещь которую мы выполняем состоит в изменении значения настройки hash-max-ziplist-entries на 1000 чтобы убедиться, что внутренним кодированием для значения нашего ключа хэша является ziplist. В своём рецепте Применение типа данных хэшей из Главы 2, Типы данных, мы знаем, что если длина некоторого хэша в Redis меньше значения hash-max-ziplist-entries, а размер каждого элемента в этом списке меньше чем hash-max-ziplist-value, тогда ziplist является наиболее действенной структурой данных, когда он применяется для внутреннего кодирования объектов хэша. Необходим повторный запуск Redis. Мы делим на разделы свои ключи по самым первым семи цифрам идентификатора пользователя, а вся прочая информация пользователя хранится как некая строка JSON. Таким образом, один раздел организован следующим образом и все разделы (кроме "user:0000000" и "user:0000010") содержат 1000 записей:


$ bin/redis-cli hgetall "user:0000007"
088
{"name":"Mary","sex":"f","resigter_time":1506942816344887079,"nation":"uk"}
...
331
{"name":"Mary","sex":"f","resigter_time":1506942817274215585,"nation":"us"}

$ bin/redis-cli hlen "user:0000007"
(integer) 1000
		

Окончательное число ключей для данного примера составляет 11, что намного меньше чем первоначальное количество (4000):


$ bin/redis-cli dbsize
(integer) 11
		

Другой способ уменьшения объёма используемой памяти состоит в сжатии самих значений данного ключа. В достижении этой цели играет важную роль имеющаяся библиотека cmsgpack в нашем сценарии lua.

Окончательное решение сочетает оба этих метода.

Другой простой способ снижения потребления памяти состоит в уменьшении длины самого ключа. Например, мы можем применять regt вместо "resigter_time" для краткости. Вы можете опробовать эту стратегию чтобы увидеть насколько больше памяти вы можете сохранять.

Также ознакомьтесь...

Можно перечислить более сложные примеры, такие как подсчёт числа уникальных посетителей на ваш вебсайт; вы можете применять для своей цели множество, побитовое изображение или HyperLogLog. Вам придётся выбирать правильные типы данных. Чем лучше вы знакомы с имеющимися в Redis типами данных, тем больше способов вы можете отыскать для реализации своего приложения.

Помимо рассмотрения потребления памяти вам также следует уделять внимание производительности манипуляции выбранным вами типом данных. Постоянно следует рассматривать компромисс пространство- время. Наш следующий рецепт даст вам полезные советы относительно вопросов производительности некоторого проекта приложения с применением Redis.

Дополнительно

За дополнительными подробностями оптимизации памяти отсылаем вас к https://redis.io/topics/memory-optimization.

Данный блог инженера Instagram описывает вариант успешного сбережения памяти.

Блог разработки Deliveroo, которая является Британской компанией доставки еды в реальном масштабе времени, описывает другую успешную практику сохранения ключей сеансов в Redis.

Применение правильного API Redis

В Главе 2, Типы данных и в Главе 3, Свойства данных мы показали, что Redis предоставляет множество мощных API для манипуляции различными имеющимися видами типов данных. Когда вы собираетесь разрабатывать своё приложение с Redis, аналогично выбору типов данных, рассмотренного в нашем предыдущем рецепте Применение правильных типов данных, всегда имеется более одного API Redis для вас чтобы реализовать определённые потребности бизнеса. Чтобы гарантировать необходимую производительность некоторого экземпляра Redis, должны быть предприняты чрезвычайные предосторожности когда вы принимаете решение какой именно API вы будете использовать.

В данном рецепте мы рассмотрим различные примеры изучения того как применять правильные API для достижения какой- то хорошей производительности в Redis.

Подготовка...

Вам требуется завершить установку своего Cервера Redis, которое мы описывали в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.

Вам понадобится выполнить команду FLUSHALL для сброса всех имеющихся данных в вашем экземпляре Redis прежде чем вы перейдёте к следующему разделу.

Для установки инструментов dos2unix в Ubuntu выполните следующую команду:


sudo apt-get install dos2unix
		

В macOS вы можете установить инструментарий dos2unix при помощи следующей команды:


brew install dos2unix
		

Как это сделать...

Чтобы показать как применять верные API Redis, необходимые этапы таковы:

  1. В самом начале данного примера мы импортируем имеющиеся данные 1 миллиона пользователей в некие ключи хэша в Redis с помощью двух API: HSET и HMSET. Для наилучшей иллюстрации мы исполним приводимый ниже сценарий, который соединится имеющимся Сервером Redis и заполнит необходимые данные пользователей в другом хосте, который рассматривается в качестве некоего сервера приложения. Возможно вам придётся подождать достаточно долго с тем чтобы исполнить этот сценарий и затем воспользоваться прерыванием и вернуться обратно для перехвата полученных результатов:

    
    $ cat hmset-vs-hset.sh  
    #!/bin/bash 
    HSETFILE="hset.cmd" 
    HMSETFILE="hmset.cmd" 
     
    rm $HSETFILE $HMSETFILE 
     
    ... (omit all the variables declared above)
    printf "hmset \"user\" " >> $HMSETFILE
    for i in `seq -f "%010g" 1 1000000` 
    do 
        namerand=$[ $RANDOM % 3 ] 
        sexrand=$[ $RANDOM % 2 ] 
        timerand=$[ $RANDOM % 30 ] 
        nationrand=$[ $RANDOM % 3 ]
        echo "hset \"user\" ${i} '{\"name\":\"${NAMEOPTION[$namerand]}\",\"sex\":\"${SEXOPTION[$sexrand]}\",\"resigter_time\":`date +%s%N`,\"nation\":\"${NATIONOPTION[$nationrand]}\"}'" >> $HSETFILE
        printf " ${i} '{"name":"${NAMEOPTION[$namerand]}","sex":"${SEXOPTION[$sexrand]}","resigter_time":`date +%s%N`,"nation":"${NATIONOPTION[$nationrand]}"}' " >> $HMSETFILE 
        sleep 0.00000${timerand} 
    done 
     
    unix2dos $HSETFILE 
    unix2dos $HMSETFILE 
     
    time cat $HSETFILE |/redis/bin/redis-cli -h $SERVER 
    sleep 10 
    time cat $HMSETFILE |/redis/bin/redis-cli -h $SERVER 
     
    $  bin/redis-cli FLUSHALL 
    OK 
     
    $ bash hmset-vs-hset.sh 
    ... 
     
    real    0m4.557s 
    user    0m0.696s 
    sys     0m1.284s 
    OK 
    (0.51s) 
     
    real    0m0.750s 
    user    0m0.224s 
    sys     0m0.496s
     	   
  2. Как только мы завершим импорт своих данных, мы попробуем получить все имеющиеся данные данного ключа "user" хэша при помощи HGETALL и измерим время данной операции. Перед вызовом HGETALL мы откроем другой Терминал чтобы запустить некую проверку задержки при помощи параметра --latency в redis-cli:

    
    $ bin/redis-cli --latency
    min: 0, max: 1, avg: 0.11 (136 samples)
     	   
  3. Мы выполняем выборку всех данных данного пользователя вызывая HGETALL:

    
    $ bin/redis-cli HGETALL user
     	   
  4. В течении обработки HGETALL в Redis выявляется высокая латентность:

    
    $ bin/redis-cli --latency
    min: 0, max: 57, avg: 0.13 (2474 samples)
     	   
  5. Остановим проверку латентности нажав Ctrl + C и перезапустив её:

    
    $ bin/redis-cli --latency
    min: 0, max: 1, avg: 0.08 (186 samples)
     	   
  6. Вместо HGETALL мы попробуем на этот раз итерировать все имеющиеся данные пользователя с помощью HSCAN:

    
    $ cat hscan.sh  
    #!/bin/bash 
    cr=0 
    key=$1 
     
    rm ${1}.dumpfile 
      
    while true; do 
        cr=`/redis/bin/redis-cli HSCAN user $cr MATCH '*' | { 
            read a 
            echo $a 
            while read x; read y; do 
                echo $x:$y >> ${1}.dumpfile 
            done 
        }` 
     
        echo $cr 
      
        if [ $cr == "0" ]; then 
            break 
        fi 
    done 
     
    $ bash hscan.sh user
     	   
  7. На протяжении итерации не выявляется никаких сюрпризов с задержками:

    
    $ bin/redis-cli --latency
    min: 0, max: 1, avg: 0.09 (13423 samples)
     	   

Как это работает...

Когда вы собираетесь проверять некое приложение с Redis, во внимание необходимо принять два принципа. Первый принцип состоит в том, что вам следует предпринять все усилия по сочетанию манипуляций с данными во избежание получения RTT (round-trip time, промежутка времени на передачу и подтверждение приёма). Данное понятие RTT было введено нами при обсуждении имеющейся функциональности конвейера в рецепте Применение конвейеров из Главы 3, Свойства данных. Redis осведомлён о своих наивысших скоростях при обработке запросов. Поэтому, если вы можете снизить значение RTT, будет достигнуто впечатляющее улучшение производительности. Применение преимуществ конвейера это хорошая мысль, в то время как некие API данных Redis могут снижать значение RTT естественным образом. Наша первая часть в предыдущем примере является хорошим примером этого. По сравнению с установкой имеющихся значений хэша одного за другим при помощи HSET, использование HMSET для установки в один приём очевидно лучший способ делать то же самое задание если все данные могут быть подготовлены до их установки. Было сохранено 83.5% (1- 0.750/4.557) времени!

Другой принцип, который вам следует иметь в виду состоит в том, что Redis в основном служба хранения данных с единственным потоком, что означает что вам следует выбирать команды Redis чрезвычайно тщательно и иметь в виду их временную сложность. Имеющаяся документация Redis показывает значение временной сложности для каждого API. Например, вы можете отыскать в имеющейся документации такое значение времени сложности HGETALL:


Time complexity: O(N)
 	   

где N является значением размера данного хэша.

Здесь временная сложность обозначается при помощи нотации Большого О. Мы не будем много обсуждать нотацию Большого О кроме того, что это распространённый термин для описания значения временной сложности некоторого алгоритма. Для снабжения вас быстрой справкой следующая схема показывает скорость роста нотации Большого О:


В нашем примере после установки всех данных в некий ключ хэша Redis, мы пытаемся выбрать все эти данные при помощи HGETALL. Уловив ужасающую задержку, которая зачастую является неким ночным кошмаром для некоторой службы данных Redis в реальном времени. Основная причина того, почему это происходит, состоит в том, что HGETALL имеет временную сложность O(n), где n является значением размера данного хэша. В нашем примере n равен 100000, что является достаточно большим числом для Redis в отношении его скорости обработки. При обработке этой команды наш Сервер Redis не имеет возможности отвечать на любые иные запросы. Наилучшим способом достижения той же самой цели является применение HSCAN для итеративной обработки данного ключа хэша. Данный API будет поступательно итерировать данный ключ хэша, эффективно исключая нежелательный всплеск латентности.

Также ознакомьтесь...

Помимо прочих, наш Сервер Redis могут блокировать команды KEYS *, FLUSHDB, DEL и HDEL. Говоря в целом, вам следует уделять особое внимание API Redis, которое имеет временную сложность хуже чем, либо сопоставимую с O(n).

Дополнительно

Если вы желаете отыскать какие именно операции замедляют ваш Сервер Redis, вы можете журнал замедления для записи команд slow, обрабатываемых в вашем Сервере Redis. Для получения дополнительных подробностей по Выявлению медленных операций/ запросов при помощи рецепта Выявление медленных запросов при помощи SLOWLOG из Главы 10, Поиск неисправностей Redis.

Дополнительные механизмы поиска сложностей с задержками будут представлены в разделе Выявление неисправностей латентности Главы 10, Поиск неисправностей Redis.

Для получения дополнительных седений относительно нотации Большого О пользуйтесь https://en.wikipedia.org/wiki/Big_O_notation.

Подключение к Redis в Java

Чтобы применять Redis в приложениях Java, нам понадобится некий клиент Java Redis. Существует пара вариантов, которые вы можете отыскать в разделе Clients домашней страницы Redis. В данном рецепте вы представим Jedis, который является неким открытым исходным кодом и его достаточно просто применять в качестве клиента Java Redis.

Подготовка...

Вам требуется завершить установку своего Cервера Redis, которое мы описывали в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.

Вам потребуется установить JDK (Java Development Kit) версии 1.8.

Рекомендуется для применения, но не является обязательным Java IDE, такой как IntelliJ IDEA или NetBeans.

Как это сделать...

Чтобы показать как подключаться к Redis при помощи Java, самым первым требованием для применения Jedis в нашем приложении Java состоит во включении его библиотеки в наш проект. Мы можем либо выгрузить необходимый файл JAR библиотеки Jedis и добавить его в путь к CLASSPATH, либо применять инструменты, подобные Maven, Gradle или Bazel для управления имеющимися зависимостями библиотек. В примерах данного рецепта мы применяем Gradle для включения Jedis в качестве зависимости своего проекта.

Добавьте приведённую ниже строку в раздел dependencies build.gradle:


compile group: 'redis.clients', name: 'jedis', version: '2.9.0'
 	   

Подключение к серверу Redis

Создайте некий класс Java, dependencies, воспользовавшись следующим кодом:

 

Рисунок 4-2



Наше демонстрационное приложение выведет следующие строки:


Address for Extreme Pizza is 300 Broadway, New York, NY 10011
Favorite Restaurants: [Olive Garden, PF Chang's, Olive Garden, PF Chang's, Indian Tandoor, Longhorn Steakhouse, Outback Steakhouse, Red Lobster, Outback Steakhouse, Red Lobster]
 	   

Применение конвейеров в Jedis

Давайте создадим другой класс Java, JedisPipelineDemo:

 

Рисунок 4-3



Вывод этой программы:


mykey: myvalue
Number of Elements in set: 2
 	   

Применение транзакций в Jedis

Давайте создадим некий класс Java, JedisTransactionDemo:

 

Рисунок 4-4



Вывод этой программы такой:


Number of orders: 401
Users: [user:1000, user:401, user:302]
 	   

Запуск сценариев Lua в Jedis

Напишите сценарий Lua, updateJson.lua, и поместите его в свою папку Java resources. Содержимое этого сценария Lua можно отыскать в рецепте Применение Lua из Главы 3, Свойства данных

Создайте некий класс Java, JedisLuaDemo:

 

Рисунок 4-5



Вывод этой программы следующий:


users:id:992452: {"grade":"C","name":"Tina","sex":"female"}
 	   

Применение пула подключений в Jedis

Создайте некий класс Java, JedisPoolDemo:

 

Рисунок 4-6



Вывод этой программы:


Kyoto Ramen rating: 5.0
rating: 5.0
phone: 555-123-6543
address: 801 Mission St, San Jose, CA
 	   

Как это работает...

В нашем первом приложении, JedisSingleDemo, мы подключаемся к своему Серверу Redis создавая некий экземпляр Jedis с именем хоста данного сервера. Если это не описано особенным образом, значением порта по молчанию является 6379. Данный класс Jedis также имеет пару других конструкторов, в которых мы можем определять значение порта сервера, тайимаут соединения и тому подобного.

Раз мы получили необходимый экземпляр Jedis, мы можем вызывать его функции для отправки команд в свой Сервер Redis. Соответствующие названия функции в точности те же самые, что и имена команд Redis, которые мы определяли в предыдущих главах.

В своём рецепте Применение конвейеров из Главы 3, Свойства данных мы изучали как применять функциональность конвейеров Redis путём построения сырых строк RESP. Использование конвейера Redis при помощи Jedis намного проще, поскольку Jedis позаботится о необходимом построении для нас сырых строк RESP.

В JedisPipelineDemo мы применяем метод pipelined() для создания экземпляра конвейера Jedis, затем мы можем добавлять команды в этот конвейер точно так же как мы исполняли команды напрямую. Основное отличие здесь состоит в том, что отклики от команд являются объектами Response<T>, которые не доступны пока данный конвейер выполняет отправку в свой сервер и исполняется. Наш sync() является собственно методом для отправки этого конвейера в сервер, после чего могут быть возвращены отклики.

Аналогично созданию некоторого конвейера, в JedisTransactionDemo соответствующий метод multi() применяется для создания некоторого экземпляра транзакции Redis, а exec() исполняет эту транзакцию. Отклики задерживаются вплоть до завершения исполнения этой транзакции.

В JedisLuaDemo мы вначале считываем updateJson.lua из ресурсов в виде некоторой строки, а затем регистрируем этот сценарий Lua при помощи scriptLoad() для получения необходимого SHA. Мы используем evalsha() для исполнения данного сценария Lua; KEYS и ARGS являются необходимыми типами списков строк Java и передаются в evalsha() в качестве аргументов.

Тот экземпляр Jedis, который мы создали в предыдущих примерах не является сберегающим потоки, что означает, что один и тот же экземпляр Jedis не может совместно использоваться в различных потоках. Вместо создания некоего экземпляра Jedis для каждого потока, мы также можем воспользоваться JedisPool, который является сберегающим потоки пулом соединений Jedis. Всякий раз когда нам требуется некое подключение, мы запрашиваем одно из этого пула; после завершения необходимой работы мы возвращаем это подключение в данный пул. Таким образом мы можем сберегать накладные расходы создания и закрытия подключений, поскольку соединения в данном пуле всегда установлены.

Соединения Jedis, запрашиваемые из данного JedisPool обязаны возвращаться в этот пул посредством вызова close(). Так как класс Jedis реализует интерфейс AutoClosable, мы также можем применять имеющийся блок try-resource для закрытия и возврата его в свой пул.

Также ознакомьтесь...

Данный рецепт представляет собой всего лишь царапину на общей поверхности применения Jedis в приложениях Java. Поскольку Jedis является проектом с открытым исходным кодом, для изучения тех моментов, которые мы не охватили в данном рецепте (публикацию/ подписку, репликации и так далее), вы можете пользоваться справочными материалами на странице GitHub Jedis.

Подключение к Redis в Python

Существует пара клиентов Python для соединения с Redis. В данном рецепте мы кратко ознакомимся с тем как применять клиента Redis Python, redis-py.

Подготовка...

Вам требуется завершить установку сервера Redis как это было описано в рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.

Вам также потребуется иметь установленным Python 2.6+ или 3.4+.

Как это сделать...

Чтобы показать как подключить Redis к Python, прежде всего нам потребуется установить необходимую библиотеку redis-py. При помощи PyPI можно запросто установить redis-py исполнив:


pip install redis
		

Подключение к серверу Redis

Теперь давайте создадим RedisDemo.py со следующим кодом:

 

Рисунок 4-7



Данное демонстрационное приложение выведет следующие строки:


Address for Extreme Pizza is 300 Broadway, New York, NY 10011
Favorite Restaurants: [Olive Garden, PF Chang's, Olive Garden, PF Chang's, Indian Tandoor, Longhorn Steakhouse, Outback Steakhouse, Red Lobster, Outback Steakhouse, Red Lobster]
		

Применение конвейеров

Конвейеры Redis могут очень просто реализовываться при помощи redis-py.

Давайте создадим другой файл RedisPipelineDemo.py:

 

Рисунок 4-8



В данном примере мы получим некий список, который содержит соответствующие отклики команд SET, SADD, GET и SCARD:


[True, 0, 'myvalue', 2]
		

Запуск сценариев Lua

redis-py предоставляет очень удобную функцию register_script() дле регистрации сценариев Lua. register_script() возвратит некий экземпляр Script, который может быть позднее применён как некая функция для вызова сценариев Lua. В рецепте Применение Lua из главы Главе 3, Свойства данных мы применяли SCRIPT LOAD и EVALSHA для кэширования какого- то сценария Lua и его повторного применения. Давайте посмотрим как мы можем делать это в redis-py:

Собственно содержимое updateJson.lua можно отыскать в рецепте Применение Lua из главы Главе 3, Свойства данных.

Создайте RedisLuaDemo.py со следующим кодом:

 

Рисунок 4-9



Выводом данной программы будет обновлённое значение JSON:


{"grade":"C","name":"Tina","sex":"female"}
 	   

Как это работает...

Чтобы применять библиотеку redis-py, для начала нам необходимо импортировать соответствующий модуль при помощи "import redis". "redis.StrictRedis()" в строке 6 из RedisDemo.py создаст некий экземпляр StrictRedis, который подключится к имеющемуся локальному хосту Сервера Redis. Методы в StrictRedis могут применяться для отправки команд в ваш Сервер Redis. Большинство названий методов и их синтаксис являются теми же самыми, что и в самих командах Redis, которые мы ввели в Главе 2, Типы данных и в Главе 3, Свойства данных. Несколько имеющихся исключений мы поясним позднее в данном рецепте.

В RedisPipelineDemo.py соответствующий метод pipeline() создаст некий экземпляр конвейера Redis, затем мы можем добавить команды в этот конвейер точно так же как когда мы непосредстенно исполняли команды. Вызов execute() отправит эти команды в наш сервер и вернёт получаемые отклики команд в последовательности.

По умолчанию, redis-py обернёт все команды в некий конвейер с командами MULTI и EXEC, делая эти команды какой- то транзакцией и исполняя их атомарно. Это можно запретить установив соответствующий аргумент транзакции в значение False в методе execute():


pipeline.execute(transaction=False)
 	   

В нашем примере RedisLuaDemo.py мы вначале считываем содержимое своего сценария Lua из некоторого внешнего файла, а затем регистрируем этот сценарий вызывая register_script(). Возвращаемый в результате экземпляр может применяться как некая функция; KEYS[] и ARGV[] в сценариях Lua могут передаваться в необходимую функцию в виде ключей и аргументов для вызова данного сценария.

Также ознакомьтесь...

Методы из класса StrictRedis почти повторяют все официальные команды Redis в отношении названий команд и синтаксиса за несколькими исключениями:

  1. Команда DEL переименовывает удаление, так как del зарезервировано как ключевое слово в Python

  2. Команды MULTI/EXEC находятся не здесь, так как они реализованы в другом классе pipeline.

  3. Команда SELECT не реализована по причине проблем сбережения числа потоков, что будет пояснено позднее.

  4. SUBSCRIBE/LISTEN реализуются как определённый класс PubSub.

В redis-py имеется также некий класс с тем же самым названием, Redis. Он является неким субкласом StrictRedis для предоставления обратной совместимости с более ранними версиями redis-py. Их очень небольшое отличие можно отыскать на странице redis-py GitHub.

Наш экземпляр StrictRedis в redis-py является сберегающим потоки, так как внутри имеется некий пул соединений, который управляет подключениями к нашему Серверу Redis. По умолчанию каждый экземпляр имеет свой собственный пул соединений. Такое поведение можно переписать передавая некий экземпляр пула подключений в виде конкретного аргумента connection_pool при создании нового экземпляра StrictRedis или Redis. Такой экземпляр пула подключений может создавться вызовом redis.ConnectionPool() и он должен создаваться до самого экземпляра StrictRedis или Redis:


>>> connectionPool = redis.ConnectionPool(host="localhost", port=6379)
>>> client = redis.StrictRedis(connection_pool=connectionPool)
		

Дополнительно

У нас нет возможности охватить в этом рецепте всё для redis-py. Так как он является проектом с открытым исходным кодом, дополнительные подробности можно отыскать на его странице GitHub.

Подключение к Redis при помощи Spring Data Redis

В двух своих предыдущих рецептах мы показывали как подключаться к Redis при помощи Java и Python. Настало время обсудить веб приложения. Когда доходит дело до веб разработки в мире Java, наиболее заметной инфраструктурой является Spring от Pivotal и она получает преимущества шаблонов MVC для простой и быстрой разработки надёжных веб приложений Java. Что касается Redis, при наличии соответствующей библиотеки Spring Data Redis мы можем обращаться с Redis в Spring сразу после установки.

В данном рецепте мы создадим некий демонстрационный проект, который реализует операции CRUD (create/ read/ update/ delete) некоторой модели пользователя для демонстрации того как подключать Redis при помощи библиотеки Spring Data Redis.

Подготовка...

Вам требуется завершить установку сервера Redis как это было описано в рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis. Вам следует применить команду FLUSHALL для сброса всех данных в вашем экземпляре Redis перед тем как перейти к нашему следующему разделу.

Для данного рецепта рекомендуется некий IDE. Здесь мы применяем Intellij IDEA (Community Edition, для красткости, Intellij). Вы можете скачать Intellij по ссылке.

Также понадобится JDK (8 или выше) для компиляции и запуска кода.

Из- за ограничений в пространстве данной книги мы не можем обсудить все подробности данного демонстрационного проекта. В последующих двух разделах будут обсуждены только сам код и установки, относящиеся к Redis. Для полного проекта вам следует обратиться к образцам данного кода, поставляемого с самой книгой.

Как это сделать...

Для показа того как соединять Redis с Spring Data Redis шаги таковы:

  1. В IDEA создайте некий новый Проект и примените Spring Initializer:

     

    Рисунок 4-10



  2. Нажмите Next и и переместитесь к метаданным данного проекта и заполните их

  3. Выберите зависимости для данной демонстрации и затем завершите создание этого проекта:

     

    Рисунок 4-11



  4. Вначале мы создадим некий класс User модели пользователя как представление данных

  5. После этого конструируется некий класс настроек приложения в котором загружаются необходимые адрес и порт Сервера Redis, а далее создаётся экземпляр RedisTemplate:

     

    Рисунок 4-12



  6. Для инкапсуляции операций с данными в Redis мы делаем некую службу взаимодействия с названием UserService, а затем реализуем её для CRUD с данными пользователя:

     

    Рисунок 4-13



  7. Для создания необходимого с операциями нашего пользователя мы применяем Spring RestController:

     

    Рисунок 4-14



  8. По окончанию кодирования мы запускаем свою демонстрацию и проверяем полученный API при помощи Swagger-ui по адресу http://127.0.0.1:8080/swagger-ui.html:

     

    Рисунок 4-15



  9. Для образца проверки мы создадим через вызов /rest/user/{id} некоего пользователя при помощи метода POST:

     

    Рисунок 4-16



  10. После создания пользователя с именем Mike мы просмотрим данные этого пользователя применив redis-cli:

    
    127.0.0.1:6379> hgetall user
    1) "0000088211" 
    2) "{\"id\":\"0000088211\",\"name\":\"Mike\",\"sex\":\"Male\",\"nation\":\"US\",\"register_time\":1507448493}"
    		

Как это работает...

Самым первым экземпляром, который вам требуется создать для Spring Data Redis является RedisTemplate, который будет применяться для манипуляции данными Redis. Прежде чем создан экземпляр RedisTemplate, необходимо для Redis настроить адрес хоста и его порт через JedisConnectionFactory. Для более надёжного управления соединением мы также включаем необходимый пул подключений своих клиентов Redis.

После получения необходимого экземпляра RedisTemplate мы устанавливаем соответственно преобразователь последовательности его ключа, ключ хэша и значение хэша. Для значения основного ключа и ключа хэша данных в этой демонстрации мы просто применяем обычный текст. Для значения хэша используется формат JSON с целью лучшей организации данных.

Так как мы подготовили сам экземпляр RedisTemplate и относящиеся к нему настройки, мы можем применять его для реализации необходимого CRUD данных пользователя в Redis для нашей службы Spring.

На следующем этапе мы создаём качестве интерфейса RESTful API для манипуляции имеющимися данными пользователя.

На стадии проверки мы применяем Swagger для отправки данный некоего пользователя через настроенный REST API и подтверждения полученных данных в Redis с помощью redis-cli.

Дополнительно

Для ознакомления с дополнительными сведениями относительно Spring Redis Data обращайтесь к его главной странице.

В строке проверки мы применяем Swagger, популярный инструмент веб разработки. Если он интересует вас, вы можете отыскать дополнительные подробности на https://swagger.io/.

Создание задания MapReduce для Redis

Redis может играть важную роль в проектировании и разработке вашего приложения, если вы являетесь инженером отрасли Больших данных. В некотором пакете сценария задания вы можете выбирать данные из Redis для выполнения неких сложных алгоритмов вычислений неким распределённым образом. Для какого- то запроса в реальном масштабе времени вы можете сохранять получаемый результат набора данных в Сервере Redis для достижения наилучшей производительности.

В оставшихся последних рецептах этой главы мы покажем вам как манипулировать данными в Redis при помощи MapReduce и Spark, которые оба чрезвычайно востребованы в инфраструктурах распределённых вычислений из мира Больших данных.

Подготовка...

Вам требуется завершить установку сервера Redis как это было описано в рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis. Вам следует применить команду FLUSHALL для сброса всех данных в вашем экземпляре Redis перед тем как перейти к нашему следующему разделу.

Основные требования к IDE и JDK в точности те же самые, что и для нашего предыдущего рецепта, Подключение к Redis при помощи Spring Data Redis.

Некий кластер Hadoop необходим, но не обязателен. Для целей демонстрации вы можете отлаживать и запускать вместо этого в локальном окружении соответствующее задание MapReduce.

Для данного рецепта понадобятся базовые знания MapReduce.

Как это сделать...

Чтобы показать как создать некое задание MapReduce для Redis, допустим что каждый пользователь в Relp имеет некий кредит, который может применяться для оплаты служб в данном приложении. В качестве некоего предложения мы собираемся добавить по 10$ в баланс кредита каждого пользователя Relp чтобы побудить такого пользователя использовать Relp. Мы бы хотели сделать это неким распределённым образом при помощи инфраструктуры MapReduce.

  1. Для начала мы подготовим данные образца при помощи своего сценария оболочки, preparedata_mr.sh:

    
    $ bash preparedata_mr.sh
    OK
    unix2dos: converting file mr.data to DOS format ...
    All data transferred. Waiting for the last reply...
    Last reply received from server.
    errors: 0, replies: 10000
     	   
  2. Затем мы изучим полученные данные посредством redis-cli:

    
    127.0.0.1:6379> SCAN 0 
    1) "7" 
    2)  1) "user:0000006" 
        2) "user:0000000" 
        3) "user:0000008" 
        4) "user:0000004" 
        5) "user:0000003" 
        6) "user:0000002" 
        7) "user:0000007" 
        8) "user:0000001" 
        9) "user:0000009" 
       10) "user:0000005" 
    127.0.0.1:6379> SCAN 7 
    1) "0"
     	   
  3. Откроем IDEA и создадим некий новй проект воспользовавшись в качестве инфраструктуры поддержки Maven.

  4. Чтобы продолжить и заполнить необходимые метаданные своего проекта нажмите Next.

  5. В pom.xml добавьте MapReduce и другие относящиеся к нему зависимости:

    
    <dependency>
         <groupId>org.apache.hadoop</groupId>
         <artifactId>hadoop-client</artifactId>
         <version>2.6.5</version>
     </dependency>
     	   
  6. После этого, в качестве бина (bean) своих данных JSON мы создаём некий класс модели пользователя, User.

  7. Для выборки необходимых данных из Redis мы персонализируем свой InputFormat для задания MapReduce:

     

    Рисунок 4-17



  8. Для каждого раздела имеющихся ключей хэша мы применяем некий индивидуальный InputSplit чтобы создать некое расщепление получения требуемых данных:

     

    Рисунок 4-18



  9. Для каждого ответвления мы выделяем некий RecordReader для выполнения итераций по его данным в Redis при помощи библиотеки Jedis:

     

    Рисунок 4-19



  10. После выборки необходимых данных мы настраиваем соответствие для добавления 10$ кредита в соответствующий баланс каждого пользователя:

     

    Рисунок 4-20



  11. Для записи необходимых данных обратно в Redis мы расширяем OutputFormat и RecordWriter:

     

    Рисунок 4-21



  12. В самом конце мы создаём некий класс функций main():

     

    Рисунок 4-22



  13. Подставляем полученное задание:

    
    ...
    2017-10-10 11:54:42,154 INFO [main] mapreduce.Job
    (Job.java:monitorAndPrintJob(1374)) - map 100% reduce 0%
    2017-10-10 11:54:42,154 INFO [main] mapreduce.Job
    (Job.java:monitorAndPrintJob(1385)) - Job job_local736372500_0001
    completed successfully
    2017-10-10 11:54:42,184 INFO [main] mapreduce.Job
    (Job.java:monitorAndPrintJob(1392)) - Counters: 18
    ...
     	   
  14. Для того чтобы убедиться что наше задание выполнено, мы получим соответствующее значение хэша воспользовавшись hget из redis-cli:

    
    127.0.0.1:6379> hget "user:0000001" 123 
    "{\"name\":\"Jack\",\"sex\":\"m\",\"rtime\":1507607748117668688,\"nation\":\"uk\",\"balance\":103}"
     	   

Как это работает...

Для выработки каких- то данных с целью демонстрации через свой сценарий мы помещаем в Redis образец данных пользователя. Мы делим на разделы этот образец данных применяя имеющиеся семь символов самого идентификатора пользователя и сохраняем его как некую структуру хэша.

Из- за имеющихся свойтсв своего образца данных в нашем приложении MapReduce, мы вначале персонализируем имеющийся класс InputFormat расширяя этот класс InputFormat чтобы создать определённый InputSplit для каждого раздела своих данных пользователя. Один InputSplit предназначен для одного ключа хэша.

В каждом ответвлении, инициируется некий RedisHashRecordReader, который является неким подклассом RecordReader, , для соответствующей выборки необходимых данных ключа хэша. В процессе инициации такого RecordReader для создания соединения с Сервером Redis применяется имеющаяся библиотека Jedis, причём получается значение длины соответствующего ключа хэша через вызов соответствующей команды HLEN и осуществляется выборка всех данных этого ключа хэша при помощи команды HGETALL. После этого предоставляется некий итератор.

Для добавления значения баланса каждому пользователю применяется некое средство соответствия. В сомом конце мы применяем персональные OutputFormat и RecordWriter для записи получаемых в результате данных пользователя в Redis в соответствии с первоначальным правилом деления на разделы.

По завершению кодирования мы подставляем полученное задание MapReduce и проверяем полученный результат. Все полученные балансы наших данных пользователя были увеличены на десятку, как и ожидалось.

Дополнительно

То, как получить запущенным некое локальное задание MapReduce выходит за рамки данной книги; вы можете обратиться к следующему руководству.

Создание задания Spark для Redis

В своём предыдущем рецепте мы обсудили как создать некое задание MapReduce для чтения и записи данных в Redis. При разработке крупномасштабных вычислений с годами Apache Spark получил большую популярность нежели .span class="term">MapReduce. Apache Spark является вычислительным механизмом с открытым исходным кодом распределённых Больших данных. При сопоставлении с MapReduce он предоставляет лучшую производительность, а также более мощный и более дружественный к пользователю API.

Когда дело доходит до примения Redis в задании Spark, Лабораторией Redis предоставляется некое подключение для манипуляции данными в Redis посредством Spark. В данном рецепте мы покажем вам как применять библиотеку подключения Spark-Redis для чтения и записи данных в Redis.

Подготовка...

Вам требуется завершить установку сервера Redis как это было описано в рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis. Вам следует применить команду FLUSHALL для сброса всех данных в вашем экземпляре Redis перед тем как перейти к нашему следующему разделу.

Основные требования к IDE и JDK в точности те же самые, что и для нашего предыдущего рецепта, Подключение к Redis при помощи Spring Data Redis.

Некий кластер Hadoop необходим, но не обязателен. На момент написания книги для Spark-Redis доступен только API Scala, поэтому в IDEA обязательны Scala v2.11 и соответствующий подключаемый модуль Scala. Кроме того для данного рецепта вам понадобятся базовые знания Spark и Scala.

Вы можете подставить своё задание в некий отдельно расположенный кластер Spark или какой- то кластер Yarn. Для целей демонстрации вместо этого мы можем отладить и запустить соответствующее задание Spark в локальном режиме.

Как это сделать...

В своём предыдущем рецепте мы предполагали, что у всякого пользователя имеется некий баланс. Чтобы показать как создавать некое задание Spark для Redis, в этом задании мы намерены вычислять сумму таких балансов всех пользователей в Relp:

  1. Своим первым шагом мы собираемся подготовить данные образца при помощи своего сценария preparedata_mr.sh:

    
    $ bash preparedata_mr.sh
    OK
    unix2dos: converting file mr.data to DOS format ...
    All data transferred. Waiting for the last reply...
    Last reply received from server.
    errors: 0, replies: 10000
     	   
  2. При помощи redis-cli мы можем изучить эти данные:

    
    127.0.0.1:6379> SCAN 0 
    1) "7" 
    2)  1) "user:0000006" 
        2) "user:0000000" 
        3) "user:0000008" 
        4) "user:0000004" 
        5) "user:0000003" 
        6) "user:0000002" 
        7) "user:0000007" 
        8) "user:0000001" 
        9) "user:0000009" 
       10) "user:0000005" 
    127.0.0.1:6379> SCAN 7 
    1) "0" 
    2) 1) "user:0000010"
     	   
  3. Открыв IDEA создаём некий новый проект Scala и заполняем метаданные этого проекта

  4. В качсетве инфраструктуры поддержки этого проекта добавляем Maven

  5. В pom.xml добавляем относящиеся к Spark-Redis зависимости и соответствующий репозиторий:

    
    <properties>
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
     </properties>
    
     <dependencies>
         <!-- https://mvnrepository.com/artifact/RedisLabs/spark-redis -->
         <dependency>
             <groupId>RedisLabs</groupId>
             <artifactId>spark-redis</artifactId>
             <version>0.3.2</version>
         </dependency>
         <dependency>
             <groupId>org.apache.spark</groupId>
             <artifactId>spark-core_2.11</artifactId>
             <version>2.1.0</version>
         </dependency>
    
         <dependency>
             <groupId>org.apache.spark</groupId>
             <artifactId>spark-streaming_2.11</artifactId>
             <version>2.1.0</version>
         </dependency>
    
         <dependency>
             <groupId>org.apache.spark</groupId>
             <artifactId>spark-sql_2.11</artifactId>
             <version>2.1.0</version>
         </dependency>
    
         <dependency>
             <groupId>redis.clients</groupId>
             <artifactId>jedis</artifactId>
             <version>2.9.0</version>
         </dependency>
     </dependencies>
     	   
  6. После этого мы создаём некий объект SumBalance, который расширит имеющиеся свойства нашей прикладной программы.

  7. В самом теле SumBalance мы вначале создаём некую переменную SparkConf с соответствующим режимом исполнения и установками настроек своего хоста Redis:

    
    val conf = new SparkConf()
       .setMaster("local")
       .setAppName("Spark Redis Demo")
       .set("redis.host", "192.168.1.7")
     	   
  8. После обретения такой переменной SparkConf мы инициализируем соответствующую SparkSession и затем получаем необходимый для применения Spark-Redis SparkContext:

    
    //Initialize the sparksession
     val sparkSession = SparkSession.builder.
       master("local")
       .config(conf)
       .appName("spark session example")
       .getOrCreate()
    
     //Fetch the sparkcontext for spark-redis library
     val sc = sparkSession.sparkContext
     	   
  9. Для чтения данных из Redis мы применяем имеющийся API fromRedisKeyPattern:

    
    // Read Hash data from Redis
     val userHashRDD= sc.fromRedisKeyPattern("user*").getHash()
     	   
  10. Прежде чем вычислить общую сумму всех балансов, мы вначале выполним синтаксический разбор соответствующих данных JSON:

    
    import sparkSession.implicits._
     val ds = sparkSession.createDataset(userHashRDD)
    
     //Preparing the schema of the JSON data
     val schema = StructType(Seq(
       StructField("name", StringType, true),
       StructField("sex", StringType, true),
       StructField("rtime", LongType, true),
       StructField("nation", StringType, true),
       StructField("balance", DoubleType,true)
     ))
    
     // Parse the json data and rename the columns
     val namedDS = ds
       .withColumnRenamed("_1","id")
       .withColumnRenamed("_2","jsondata")
       .withColumn("jsondata",from_json($"jsondata", schema))
     	   
  11. Как только мы подготовим необходимые данные, наступит самое время для их вычисления. После определения значения суммы мы готовим эти данные для записи полученной суммы в виде некоей пары строковых ключ значение обратно в Redis в виде запроса в реальном времени:

    
    //Generating the result [String, String] RDD
     val totalBalanceRDD = namedDS.agg(sum($"jsondata.balance")).rdd.map(total =>("totalBalance",total.get(0).toString()))
     	   
  12. Наконец, мы записываем полученный результат в Redis при помощи соответствующего API toRedisKV, предоставляемого Spark-Redis:

    
    /Write the result back to Redis`
     sc.toRedisKV(totalBalanceRDD)
     	   
  13. Чтобы проверить то, что наше задание отработало как ожидалось, мы подставим это задание:

    
    ... 
    17/10/10 21:02:28 INFO TaskSchedulerImpl: Removed TaskSet 1.0, whose tasks have all completed, from pool  
    17/10/10 21:02:28 INFO DAGScheduler: ResultStage 1 (foreachPartition at redisFunctions.scala:226) finished in 0.130 s 
    17/10/10 21:02:28 INFO DAGScheduler: Job 0 finished: foreachPartition at redisFunctions.scala:226, took 2.675819 s 
    17/10/10 21:02:28 INFO SparkContext: Invoking stop() from shutdown hook 
    17/10/10 21:02:28 INFO SparkUI: Stopped Spark web UI at http://192.168.56.1:4040 
    17/10/10 21:02:28 INFO MapOutputTrackerMasterEndpoint: MapOutputTrackerMasterEndpoint stopped! 
    17/10/10 21:02:28 INFO MemoryStore: MemoryStore cleared 
    17/10/10 21:02:28 INFO BlockManager: BlockManager stopped 
    17/10/10 21:02:28 INFO BlockManagerMaster: BlockManagerMaster stopped 
    17/10/10 21:02:28 INFO OutputCommitCoordinator$OutputCommitCoordinatorEndpoint: OutputCommitCoordinator stopped! 
    17/10/10 21:02:28 INFO SparkContext: Successfully stopped SparkContext 
    17/10/10 21:02:28 INFO ShutdownHookManager: Shutdown hook called
     	   
  14. Из redis-cli мы получим значение суммы имеющихся балансов всех пользователей:

    
    127.0.0.1:6379> GET totalBalance
    "492569.0"
     	   

Как это работает...

Приводимый код достаточно хорошо сам себя объясняет. Единственный момент, на который стоит обратить ваше внимание, так это то, что по причине требований самой библиотеки Spark-Redis, когда вы желаете записать полученные данные в Redis как некую простую пару ключ значение, требуется соответствующий RDD[(String, String)]. Самой первой частью StringRDD является значение того ключа, который вы бы хотели установить в Redis. Поэтому в данном примере прежде чем мы установим соответствующие данные в Redis, мы выполняем преобразование получнного результата в StringRDD в виде некоего ключа с названием totalBalance. Различные типы данных Redis требуют различных типов RDD в Spark-Redis.

Также ознакомьтесь...

Помимо манипуляций с неким отдельным Сервером Redis, Spark-Redis поддерживает в ваших приложениях подключения ко множеству Кластеров или экземпляров Redis.

Дополнительно

За дополнительными сведениями относительно предоставляемых Spark-Redis API вы можете обратиться к https://github.com/RedisLabs/spark-redis.