Вы, наверное, знаете о 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: '[email protected]') #=> <Author>
Чтобы получить значение атрибута, используйте обозначение точки, как обычно:
irb> author.email #=> [email protected]
Точечная нотация получит значение атрибута из нашего локально кэшированного объекта Author. Чтобы получить значение непосредственно из Redis, используйте метод get
instance:
irb> author.get(:email) #=> [email protected]
индексирование
Мы можем индексировать атрибуты с помощью макроса класса 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: "[email protected]") irb> Author.find(l_name: "Bloggs").size #=> 1
Метод find
возвращает объект Ohm::Set
. Ohm::Set
— неупорядоченный список с внешним поведением, похожим на поведение массивов Ruby. Как и массивы, информация может быть извлечена с помощью знакомых методов, таких как first
:
irb> Author.find(l_name: "Bloggs").first.email #=> "[email protected]"
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: "[email protected]")
который возвращает:
#=> #<Author:0x000000023c8118 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"[email protected]"}, @_memo={}, @id="1">
Одна вещь, которая сразу становится очевидной, это то, что Ом добавил атрибут id
к нашему новому объекту. Это эквивалентно первичному ключу реляционной базы данных: уникальному идентификатору для этого типа объекта в текущей базе данных Redis. Теперь мы можем использовать значение атрибута id
с методом класса []
для создания экземпляра с теми же атрибутами, когда нам это нужно:
irb> a = Author[1] #=> #<Author:0x000000021db990 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"[email protected]"}, @_memo={}, @id="1">
Обратите внимание, что каждый раз, когда мы получаем объект Author на основе его идентификатора, создается новый объект Author с теми же атрибутами, что и у нашего целевого объекта. Таким образом, у нас может быть много объектов Ruby Ohm::Model
ссылающихся на одну и ту же сущность Redis.
Удаление и истечение срока действия ключей
Удалите объект, вызвав его метод delete
, т.е. object.delete
. При использовании #delete
помните о двух вещах:
- Он удалит ключи объекта из Redis, включая уникальные элементы, индексы и любые наборы, которые может содержать объект. Однако он не удалит объекты, на которые ссылаются наборы объектов. Они будут продолжать счастливо жить в памяти Redis до тех пор, пока не истечет срок их действия, не будет принудительно исключен политикой выселения Redis или не будет удален путем
#delete
их метода#delete
. -
Он удалит ключи объекта из Redis, но не удалит объект Ruby из пространства памяти нашего приложения. Кроме того, оставшийся объект Ruby можно использовать для «реанимации» удаленных ключей объекта Redis, создавая следующее явление:
irb> author = Author.create(f_name: "John", l_name: "Smith", age: 34, email: "[email protected]") => #<Author:0x00000001e48e68 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"[email protected]"}, @_memo={}, @id="13"> irb> Author[13] #check that new author is stored in Redis => #<Author:0x00000001e2f238 @attributes={:f_name=>"John", :l_name=>"Smith", :email=>"[email protected]", :age=>"34"}, @_memo={}, @id=13> irb> author.delete #delete author from Redis => #<Author:0x00000001e48e68 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"[email protected]"}, @_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=>"[email protected]"}, @_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=>"[email protected]", :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: "[email protected]") => #<Author:0x0000000279a480 @attributes={:f_name=>"John", :l_name=>"Smith", :age=>34, :email=>"[email protected]"}, @_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 с легкостью реляционного совершенства, идеальное сочетание для многих современных приложений.