Статьи

Простая система KVP с Amazon S3

Несколько недель назад я писал о начале работы с Amazon SDK для PHP . В этой статье предполагается, что вы знакомы с основными понятиями использования SDK. Теперь мы собираемся использовать эти знания для создания чего-то классного: легкой, но мощной системы хранения данных, которая может масштабироваться вечно.

Когда вы думаете об облачном хранилище, большую часть времени вы думаете о медиа, таких как фотографии, видео и другие файлы содержимого. Но облачное хранилище также является отличным местом для хранения других данных.

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

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

Но некоторые данные, которые вы, возможно, захотите добавить, не обязательно должны быть доступны для поиска, или они могут быть слишком большими для эффективного хранения в базе данных. Например, возможно, вы захотите разрешить пользователю настраивать внешний вид вашего веб-приложения. Они смогут выбрать несколько цветов, загрузить логотип и настроить другие параметры отображения. Эти параметры не обязательно должны быть доступны для поиска в базе данных, но вам нужен способ простого и надежного доступа к этой информации, когда каждый пользователь входит в систему.

Решение Amazon S3 для хранения данных обеспечивает чрезвычайно простое решение. Вот как это сделать:

Требования

Служба S3. Вам потребуется учетная запись Amazon AWS с включенной функцией S3.

S3 Bucket — Вам нужно будет создать S3 Bucket (обсуждалось в моей предыдущей статье ). В нашем коде мы будем называть этот сегмент «my-unique-bucket-name». Вместо этого вы будете использовать свое собственное имя.

Веб-хостинг — Вам понадобится обычный веб-хостинг. Однако для этого конкретного случая целесообразно разместить ваше приложение в облаке на Amazon на виртуальном сервере Amazon EC2. Причина в том, что когда вы размещаете свой сайт на экземпляре Amazon EC2, весь трафик между вашим сервером и Amazon S3 полностью бесплатный. Хостинг в любом другом месте будет нести расходы. При тестировании и настройке эти расходы незначительны (и, скорее всего, они подпадают под уровни бесплатного использования Amazon), но в долгосрочной перспективе это хорошая идея для установки на EC2.

Погружение в Кодекс

Время намочить ноги …

Базовый класс PHP

Чтобы сделать эту систему максимально простой, мы собираемся создать простой класс PHP. Это позволит нам легко использовать концепцию KVP (пара ключ-значение) практически для всего, что вы можете себе представить. Итак, для начала создайте минимальный класс, который 1) принимает аргумент с именем «itemName» и 2) автоматически создает объект Amazon S3 с AWS SDK. Опять же, если вы не уверены, где мы находимся, начните с чтения этой статьи .

class s3Kvp {

    private $itemName = Null;    // Property to store item name throughout the class.
    private $awsS3 = Null;       // Property to store an instance of the S3 Object

    // Construct runs when we create the class.
    public function __construct($itemName) {
        $this->itemName = $itemName;    // Store item name as class property.
        $this->awsS3 = new AmazonS3();  // Create s3 object as class property.
    }

}

// Create a new instance of the class
// Submitting 'user101' as the item name
// will allow us to store KVP data specifically
// for User with ID 101.
$myKvp = new s3Kvp('user101');

// We can create a separate instance of this class
// submitting a different itemName. In this case,
// data will be stored specifcially for User with ID 335.
$anotherKvp = new s3Kvp('user335');

// Of course, none of this does anything quite yet...

Загрузка данных

Теперь мы собираемся добавить в наш класс несколько новых базовых элементов, в том числе два новых свойства: одно для хранения фактических данных, когда класс используется, и другое, чтобы сообщить нам, являются ли данные грязными (если они изменились с тех пор, как мы загрузил его). Мы также собираемся загрузить наши данные с S3. Это начинает казаться немного сложным, но вы можете следить за комментариями для всех новых функций, которые мы добавляем:

 class s3Kvp {

    private $itemName = Null;
    private $awsS3 = Null;

    private $data = Null;                        // We'll store our item data here.
    private $isDirty = false;                    // We'll track whether or not anything changed here.
    private $bucket = 'my-unique-bucket-name';   // This is the "bucket", or domain where your data is stored.

    public function __construct($itemName) {
        $this->itemName = $itemName;
        $this->awsS3 = new AmazonS3();

        // Here we're trying to get an object matching our item name
        // located in the folder 'kvpdata'.
        // Start by Creating a Secure URL to Access Your Data
        $url = $this->awsS3->get_object_url('my-unique-bucket-name', 'kvpdata/'.$this->itemName, '10 Days');

        // Then grab the contents of the URL
        // The @ will allow the script to continue
        // if the URL cannot be reached.
        $responseData = false;
        $responseData = @file_get_contents($url);

        // If we got something back in our response data,
        // load it into the "data" property of this class.
        if ($responseData) {

            // We're going to store our data as an array encoded JSON,
            // so if we get anything we know we can
            // decode it back to an array.
            $responseAsArray = json_decode($responseData, true);

            // Check if the data is valid.
            if (is_array($responseAsArray)) {

                // It's valid! Load it up.
                $this->data = $responseAsArray;

            } else {

                // Not valid! Load an empty array.
                // A good app would do some error handling here.
                $this->data = array();

            }

        } else {

            // If we don't get anything back,
            // it means this item is new so we're just going to
            // load 'data' as an empty array.
            $this->data = array();

        }

    }

}

Перегрузка объекта

Перегрузка в PHP предоставляет средства для динамического «создания» свойств и методов. Это действительно весело, и мы собираемся использовать его для динамического хранения данных элементов.

Мы собираемся создать два новых метода в нашем классе. Один, называемый «__get», позволит нам легко извлекать информацию, хранящуюся в данных нашего предмета. Другой «__set» позволит нам обновлять существующие данные и сохранять новые данные.

Вот как это работает:

Если вы создали новый экземпляр нашего класса s3Kvp с именем $ myKvp и хотите получить доступ к свойству customBackgroundColor (т. Е. $ MyKvp-> customBackgroundColor), вы получите ошибку, потому что это свойство не существует.

Но если мы создадим метод с именем «__get» в нашем классе, PHP запустит эту функцию до получения ошибки. Это позволяет нам динамически получать доступ к данным. То же самое работает с «__set». Если свойство не существует, но вы пытаетесь установить его, будет вызван метод «__set», если он существует.

Вот код:

 class s3Kvp {

    // This function will be called anytime
    // we try to RETRIEVE properties of our class
    // that do not exist.
    public function __get($propertName) {

        // Check to see if we have that property in our
        // data array.
        if (isset($this->data[$propertName])) {

            // If we have it, return it.
            // Simple, right!?
            return $this->data[$propertName];

        } else {

            // If not, return false;
            return false;

        }
    }

    // This function will be called anytime
    // we try to SET properties of our class
    // that do not exist.
    public function __set($propertName, $propertyValue) {

        // Simply set the propertyName submitted
        // in our data array to the value submitted.
        $this->data[$propertName] = $propertyValue;

        // One more thing...
        // We want to know if something is ever "set"
        // so we can mark the data as "dirty".
        // We'll use this later...
        $this->isDirty = true;

    }

    private $itemName = Null;
    private $awsS3 = Null;
    private $data = Null;
    private $isDirty = false;
    private $bucket = 'my-unique-bucket-name';

    public function __construct($itemName) {
        $this->itemName = $itemName;
        $this->awsS3 = new AmazonS3();
        $url = $this->awsS3->get_object_url('my-unique-bucket-name', 'kvpdata/'.$this->itemName, '10 Days');
        $responseData = false;
        $responseData = @file_get_contents($url);
        if ($responseData) {
            $responseAsArray = json_decode($responseData, true);
            if (is_array($responseAsArray)) {
                $this->data = $responseAsArray;

            } else {
                $this->data = array();
            }
        } else {
            $this->data = array();
        }
    }

Спаси этого ребенка

Теперь наш класс начинает обретать форму. Мы можем загрузить данные из S3, и мы можем установить и получить значения данных. Но чего-то все еще не хватает — сохранение данных. Нам нужно отправить его обратно на S3.

Для этого мы будем использовать метод __destruct, встроенный в классы PHP. Этот метод, если он существует, вызывается непосредственно перед тем, как PHP завершает класс. Поэтому, если мы создадим метод, он будет вызываться каждый раз, когда мы используем экземпляр класса, как только PHP узнает, что мы закончили с ним.

В методе __destruct мы собираемся проверить, является ли наш класс isDirty, и если это так, мы сохраним наши изменения до S3. Однако перед сохранением мы собираемся преобразовать наши данные в JSON. Это облегчает хранение и, при необходимости, облегчает доступ к другим системам.

 class s3Kvp {

    // The __destruct is called by PHP as
    // each instance of this class is destroyed.
    public function __destruct() {

        // Check if "isDirty" is true,
        // which tells us that at some point
        // during the script execution
        // data was changed.
        if ($this->isDirty) {

            // Encode our data as JSON.
            $dataToSave = json_encode($this->data);

            // Use "create_object" to upload the data
            // back to S3. If the object already exists,
            // this will simply replace it.
            $options = array('body' => $dataToSave);
            $this->awsS3->create_object('my-unique-bucket-name', 'kvpdata/'.$this->itemName, $options);

        }
    }

    public function __get($propertName) {
        if (isset($this->data[$propertName])) {
            return $this->data[$propertName];
        } else {
            return false;
        }
    }

    public function __set($propertName, $propertyValue) {
        $this->data[$propertName] = $propertyValue;
        $this->isDirty = true;
    }

    private $itemName = Null;
    private $awsS3 = Null;
    private $data = Null;
    private $isDirty = false;
    private $bucket = 'my-unique-bucket-name';

    public function __construct($itemName) {
        $this->itemName = $itemName;
        $this->awsS3 = new AmazonS3();
        $url = $this->awsS3->get_object_url('my-unique-bucket-name', 'kvpdata/'.$this->itemName, '10 Days');
        $responseData = false;
        $responseData = @file_get_contents($url);
        if ($responseData) {
            $responseAsArray = json_decode($responseData, true);
            if (is_array($responseAsArray)) {
                $this->data = $responseAsArray;

            } else {
                $this->data = array();
            }
        } else {
            $this->data = array();
        }
    }

}

Используя класс

Вот последний класс:

 class s3Kvp {

    private $itemName = Null;
    private $awsS3 = Null;
    private $data = Null;
    private $isDirty = false;
    private $bucket = 'my-unique-bucket-name';

    public function __construct($itemName) {
        $this->itemName = $itemName;
        $this->awsS3 = new AmazonS3();
        $url = $this->awsS3->get_object_url('my-unique-bucket-name', 'kvpdata/'.$this->itemName, '10 Days');
        $responseData = false;
        $responseData = @file_get_contents($url);
        if ($responseData) {
            $responseAsArray = json_decode($responseData, true);
            if (is_array($responseAsArray)) {
                $this->data = $responseAsArray;

            } else {
                $this->data = array();
            }
        } else {
            $this->data = array();
        }
    }

    public function __get($propertName) {
        if (isset($this->data[$propertName])) {
            return $this->data[$propertName];
        } else {
            return false;
        }
    }

    public function __set($propertName, $propertyValue) {
        $this->data[$propertName] = $propertyValue;
        $this->isDirty = true;
    }

    public function __destruct() {
        if ($this->isDirty) {
            $dataToSave = json_encode($this->data);
            $options = array('body' => $dataToSave);
            $this->awsS3->create_object('my-unique-bucket-name', 'kvpdata/'.$this->itemName, $options);

        }
    }

}

Конечно, теперь, когда мы сделали всю работу, мы хотим использовать эту вещь! Вот пара примеров:

Система учетных записей пользователей

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

 // Include the class we just created.
require_once 's3kvp.php';

// Create an instance of s4Kvp
// using the current User's ID;
$userId = 12301; //This might be stored in cookie or session variable.
$userKvp = new s3Kvp('user'.$userId);

// In this example,
// We're setting the "prefsItemsPerPage"
// equal to whatever we received in the
// "prefsItemsPerPage" querystring variable;
$submittedPreference = $_GET['prefsItemsPerPage'];
$userKvp->prefsItemsPerPage = $submittedPreference;
echo 'test';
 // Include the class we just created.
require_once 's3kvp.php';

$userId = 12301;
$userKvp = new s3Kvp('user'.$userId);

echo $userKvp->prefsItemsPerPage;

Система корзины покупок

Я создал целые системы корзины покупок, которые используют только класс KVP, как это. С появлением Google Analytics мне больше не нужно отслеживать «частичные» или «неполные» заказы. Использование системы, подобной этой, позволяет мне хранить тонны подробной информации о покупках, включая достоверную информацию об элементах корзины, рекламных акциях, данных оформления заказа и многое другое. Все это хранится в облачном хранилище до момента оформления заказа, после чего я копирую конкретные данные, необходимые для поиска в базе данных. Мне никогда не придется беспокоиться об увеличении размера моей базы данных, кроме как завершенных заказов.

Конечно, для этого типа примера мне нужно создать более надежный класс KVP — класс, который может обрабатывать списки элементов и другие функции. Но идея та же: хранить данные о предметах в облачном хранилище.

Итак, где мы находимся?

Менее чем в 50 строках кода мы создали надежную систему для хранения данных, специфичных для элементов; эта система является быстрой и масштабируемой.

Поскольку я использую подобные системы, я могу очень легко сохранять таблицы в своей базе данных, храня только ту информацию, которая, как мне известно, должна использоваться для поиска, фильтрации или сводных отчетов. Мне не нужно беспокоиться о том, что мои таблицы базы данных увеличиваются до огромных размеров, а с S3 мне никогда не нужно беспокоиться о нехватке памяти или резервном копировании моих данных.

Несколько вещей для запоминания

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

Кроме того, вы должны хранить свои данные в корзине S3, которая не имеет публичного доступа; это важно для безопасности.

Что дальше?

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

Шифрование — для дополнительного уровня безопасности я добавляю шифрование к этому классу. Я вставляю его в __construct и __destruct, чтобы каждый раз, когда я загружаю или сохраняю данные, я расшифровываю и шифрую.

Amazon SimpleDB — этот тип системы прекрасно работает в сочетании с SimpleDB. Amazon SimpleDB — это высокодоступное, гибкое и масштабируемое нереляционное хранилище данных, которое облегчает работу администрирования баз данных. Использование SimpleDB в сочетании с этой концепцией хранения S3 KVP позволяет создавать масштабируемые, гибкие системы, которые устраняют проблемы с производительностью или резервным копированием данных.

Данные по умолчанию — во многих случаях вы не хотите просто возвращать «ложь», когда вы не находите определенные пользовательские данные. Вы можете легко указать значения по умолчанию для различных свойств и вернуть их в функцию __get. Поскольку они являются значениями по умолчанию, вам даже не нужно хранить их в данных, относящихся к элементу, в S3, что делает ваши данные легче.

Кеширование — я все время использую кеширование. Такие системы, как memcached, позволяют хранить данные, к которым необходимо часто обращаться на вашем веб-сервере. Добавление уровня кэширования очень похоже на добавление уровня шифрования. Во время загрузки вы просто проверяете, находятся ли искомые данные в кеше, и, если это так, загружают их из кеша; в противном случае загрузите его из S3. При сохранении вы обновляете как кешированную версию, так и S3.

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

Управление версиями — S3 предоставляет мощный способ отслеживать версии ваших данных. Это отлично подходит для создания функциональности отката. Если вы включите управление версиями в своем хранилище, Amazon будет сохранять резервную копию при каждом сохранении. Затем вы можете вернуться к любой предыдущей версии в любое время. Я часто использую это просто как резервную копию — если я случайно напишу код, который уничтожит данные, я смогу вернуть его обратно, если у меня будет управление версиями. Есть много других применений. Однако управление версиями повышает требования к хранилищу, поэтому используйте его только в том случае, если оно действительно вам нужно.

Вопросов?

Если у вас есть какие-либо вопросы или идеи по поводу этой концепции, пожалуйста, начните разговор ниже. Я был бы рад ответить!