Статьи

Полуореляционное моделирование данных с помощью Redis и Ohm

ом

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

  • Структуры данных, такие как списки, наборы и хэши
  • Постоянство в режимах моментальных снимков и журналов
  • Механизм Pub / Sub и -by extension- реализация очереди сообщений
  • Кластеризация, разбиение и высокая доступность (через [Sentinel] (http://redis.io/topics/sentinel))

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

Кроме того, мы увидим, как мы можем использовать эту объектную модель для эмуляции некоторых функций, подобных ActiveRecord. Прежде чем мы начнем, давайте разберемся одно: Redis не является реляционной базой данных, а Ohm не является ORM. Если вы ожидаете использовать Redis / Ohm для таких вещей, как транзакции и каскадная ссылочная целостность, вы будете сильно разочарованы.

Однако, если вы хотите хранить и извлекать слабо связанные объекты (например, агрегаты, а не композиты) ослепительно быстро с дополнительным бонусом к разделению данных, высокой доступности и возможностям публикации / подписки, тогда Redis и Ohm были созданы для вас. ,

Ключевые правила

Это руководство не предназначено для охвата всего спектра функций Ohm или Redis. Он охватит небольшое подмножество, необходимое для понимания и реализации наиболее распространенных случаев использования моделирования данных. Ом работает по двум аспектам:

  • Управление объектами в пространстве памяти Ruby
  • Управление хэши, индексы и другие структуры данных Redis, которые соответствуют этим объектам Ruby

Синхронизация между ними не всегда очевидна, но Ом предлагает множество методов для управления синхронностью, неявно или явно. В этом руководстве мы предпочитаем неявные методы, давая нам возможность предполагать, что любая операция с нашими объектами Ohm будет иметь эквивалентное влияние на соответствующие структуры данных Redis (если не указано иное). Когда, например, мы обновляем атрибут объекта, мы ожидаем, что эквивалентные записи Redis будут соответственно обновлены.

Настройка

Очевидно, что нам нужно установить Redis и убедиться, что он работает. Следуйте инструкциям здесь и получите Redis работать на вашем компьютере.

Кроме того, как уже упоминалось, мы будем использовать драгоценный камень Ohm , поэтому убедитесь, что он установлен:

$ gem install ohm 

Определение классов

Для целей данного руководства мы смоделируем систему авторинга. Определите два класса: класс Author и класс Book, оба производные от Ohm::Model . Автор может создавать от 0 до многих книг. Предположим, что книга может принадлежать только одному автору.

Самая базовая реализация системы выглядит следующим образом:

 require 'ohm' class Author < Ohm::Model attribute :f_name attribute :l_name attribute :email attribute :age end class Book < Ohm::Model attribute :title end 

Атрибуты

Мы определили наши основные атрибуты класса, используя макрос класса attribute . Значения атрибута определены как строки, но мы также можем передать лямбду в качестве второго аргумента макросу, который может служить способом простого приведения типов:

 attribute :age, lambda { |x| x.to_i } 

Вышеуказанное гарантирует, что атрибут age всегда будет возвращаться как Integer , даже если мы установим его как String .

Чтобы установить значение атрибута, используйте метод экземпляра update :

 irb> author.update(email: 'fred@gmail.com') #=> <Author> 

Чтобы получить значение атрибута, используйте обозначение точки, как обычно:

 irb> author.email #=> fred@gmail.com 

Точечная нотация получит значение атрибута из нашего локально кэшированного объекта Author. Чтобы получить значение непосредственно из Redis, используйте метод get instance:

 irb> author.get(:email) #=> fred@gmail.com 

индексирование

Мы можем индексировать атрибуты с помощью макроса класса index . Это делает атрибуты доступными для поиска с помощью метода find class. Давайте удостоверимся, что мы можем искать авторов по их last_name :

 class Author < Ohm::Model index :l_name end 

Теперь это возможно:

 irb> author = Author.create(f_name: "Joe", l_name: "Bloggs", age: 34, email: "jbloggs@gmail.com") irb> Author.find(l_name: "Bloggs").size #=> 1 

Метод find возвращает объект Ohm::Set . Ohm::Set — неупорядоченный список с внешним поведением, похожим на поведение массивов Ruby. Как и массивы, информация может быть извлечена с помощью знакомых методов, таких как first :

 irb> Author.find(l_name: "Bloggs").first.email #=> "jbloggs@gmail.com" 

Uniques

Если мы хотим, чтобы атрибут имел уникальное значение, установите его как unique . В нашем примере Author атрибут email вероятно, является хорошим кандидатом для пометки в качестве уникального:

 class Author < Ohm::Model unique :email end 

Теперь создание автора с тем же значением электронной почты, что и у существующего автора, приведет к ошибке Ohm::UniqueIndexViolation .

Создание модели

Как и в ActiveRecord, мы используем метод create class для создания нового объекта модели:

 irb> author = Author.create(f_name: "John", l_name: "Smith", age: 34, email: "jsmith@gmail.com") 

который возвращает:

 #=> #<Author:0x000000023c8118 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"jsmith@gmail.com"}, @_memo={}, @id="1"> 

Одна вещь, которая сразу становится очевидной, это то, что Ом добавил атрибут id к нашему новому объекту. Это эквивалентно первичному ключу реляционной базы данных: уникальному идентификатору для этого типа объекта в текущей базе данных Redis. Теперь мы можем использовать значение атрибута id с методом класса [] для создания экземпляра с теми же атрибутами, когда нам это нужно:

 irb> a = Author[1] #=> #<Author:0x000000021db990 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"jsmith@gmail.com"}, @_memo={}, @id="1"> 

Обратите внимание, что каждый раз, когда мы получаем объект Author на основе его идентификатора, создается новый объект Author с теми же атрибутами, что и у нашего целевого объекта. Таким образом, у нас может быть много объектов Ruby Ohm::Model ссылающихся на одну и ту же сущность Redis.

Удаление и истечение срока действия ключей

Удалите объект, вызвав его метод delete , т.е. object.delete . При использовании #delete помните о двух вещах:

  1. Он удалит ключи объекта из Redis, включая уникальные элементы, индексы и любые наборы, которые может содержать объект. Однако он не удалит объекты, на которые ссылаются наборы объектов. Они будут продолжать счастливо жить в памяти Redis до тех пор, пока не истечет срок их действия, не будет принудительно исключен политикой выселения Redis или не будет удален путем #delete их метода #delete .
  2. Он удалит ключи объекта из Redis, но не удалит объект Ruby из пространства памяти нашего приложения. Кроме того, оставшийся объект Ruby можно использовать для «реанимации» удаленных ключей объекта Redis, создавая следующее явление:

     irb> author = Author.create(f_name: "John", l_name: "Smith", age: 34, email: "abc@gmail.com") => #<Author:0x00000001e48e68 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"abc@gmail.com"}, @_memo={}, @id="13"> irb> Author[13] #check that new author is stored in Redis => #<Author:0x00000001e2f238 @attributes={:f_name=>"John", :l_name=>"Smith", :email=>"abc@gmail.com", :age=>"34"}, @_memo={}, @id=13> irb> author.delete #delete author from Redis => #<Author:0x00000001e48e68 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"abc@gmail.com"}, @_memo={}, @id="13"> irb> Author[13] #author not present in Redis anymore => nil irb> author.update(f_name: 'Jack') #we use the author object to update an attribute => #<Author:0x00000001e48e68 @attributes={:f_name=>"Jack", :l_name=>"Smith", :age=>34, :email=>"abc@gmail.com"}, @_memo={}, @id="13"> irb> Author[13] #surprise! the author has been re-created in Redis => #<Author:0x00000001cffe58 @attributes={:f_name=>"Jack", :l_name=>"Smith", :email=>"abc@gmail.com", :age=>"34"}, @_memo={}, @id=13> 

Пока вы знаете об этом, вы можете использовать delete мере необходимости. Тем не менее, безопасная практика — иметь хороший параметр политики выселения в файле redis.conf . Хорошая ставка будет:

 maxmemory 512mb maxmemory-policy volatile-lru 

Это приводит к тому, что Redis начинает удалять ключи, как только будет достигнут предел в 512 мегабайт памяти. Ключи будут удаляться по принципу наименьшего количества использовавшихся только в том случае, если у них установлен срок действия , что полезно, если ваша база данных Redis содержит смешанные данные (т. Е. Не только данные Ohm, но также некоторые виды данных кэширования из других приложений). Если единственной целью базы данных Redis является сохранение реляционных данных, а время истечения срока действия имеет решающее значение, тогда noeviction настройка политики noeviction .

ассоциации

Ohm::Model позволяет нам определять отношения между объектами, используя макросы #reference и #collection . Макрос collection эквивалентен методу has_many ActiveRecord, в то время как reference примерно имитирует методы has_a или belongs_to ActiveRecord. Добавьте эти макросы в существующие классы:

 require 'ohm' class Author < Ohm::Model attribute :f_name attribute :l_name attribute :email attribute :age, lambda { |x| x.to_i } index :l_name unique :email collection :books, :Book end class Book < Ohm::Model attribute :title reference :author, :Author end 

Создайте автора и пару книг:

 irb> author = Author.create(f_name: "John", l_name: "Smith", age: 34, email: "jsmith@gmail.com") => #<Author:0x0000000279a480 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"jsmith@gmail.com"}, @_memo={}, @id="1"> irb> book1 = Book.create(title: 'Moby Dick') => #<Book:0x00000002776fa8 @attributes={:title=>"Moby Dick"}, @_memo={}, @id="1"> irb> book2 = Book.create(title: 'The Hobbit') => #<Book:0x000000026348c0 @attributes={:title=>"The Hobbit"}, @_memo={}, @id="2"> 

reference макрос дал нашим объектам author атрибут author , который мы можем использовать для установки автора наших книг:

 irb> book1.update(author: author) => #<Book:0x00000002776fa8 @attributes={:title=>"Moby Dick", :author_id=>"1"}, @_memo={}, @id="1"> irb> book2.update(author: author) => #<Book:0x000000026348c0 @attributes={:title=>"The Hobbit", :author_id=>"1"}, @_memo={}, @id="2"> 

Ом добавил новый атрибут author_id к объектам книги. Это атрибут внешнего ключа, ссылающийся на автора, связанного с этой книгой. Макрос collection используемый в классе Author, гарантирует, что автор может отслеживать все свои книги:

 irb> author.books.each {|b| puts b.title} => Moby Dick => The Hobbit 

Кроме того, мы можем использовать некоторые из методов сортировки и поиска Ohm::Set , например #include? , #find , #sort и #sort_by для фильтрации и сортировки коллекции книг.

Разговор напрямую с Redis

Если вам когда-нибудь понадобится запустить команду Redis, которая не абстрагируется методом Ohm, используйте метод redis.call :

 irb> Ohm.redis.call "FLUSHDB" => "OK" 

Здесь мы говорим Redis очистить текущую базу данных. Таким способом мы можем запустить любую из команд Redis-cli из нашего кода на Ruby.

Резюме

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