РАЗРАБОТКА НА БИТРИКС

Кэширование данных с помощью Redis

Подписывайтесь на канал для bitrix-разработчиков в Telegram!

Перед интеграцией кеша по данному кейсу рекомендуется использовать стандартный функционал, если он по каким-то причинам не подходит, то можно приступать к собственной реализации.
Если некоторые данные необходимо забирать моментально, их количество велико, а объем незначителен, при этом доступ к данным реально получать по единственному параметру — ключу, то можно настроить кэширование с помощью Redis через стандартные механизмы «Битрикс».
Для этого необходимо выполнить несколько легких шагов
  • Устанавливаем Redis на сервере и настраиваем его
Обращайтесь за этим к админу, а если можете сами, то и без моей инструкции справитесь, т. к. систем много, а гуглить вы сами умеете.
  • Установка composer, настройка для Битрикса и добавление пакета **predis/predis
Описание установки в предыдущем спойлере, а настроить для «Битрикс» можно разными путями, например, используя официальную документацию.
  • Создание класса
Необходимо класс поместить в своё пространство имен и реализовать методы интерфейсов ICacheEngine и ICacheEngineStat.

<?php

namespace SITE\CacheEngine;

use Bitrix\Main\Config\Configuration;
use Bitrix\Main\Data\ICacheEngine;
use Bitrix\Main\Data\ICacheEngineStat;
use Predis\Client;

class Redis implements ICacheEngine, ICacheEngineStat
{
    /**
     * Redis client
     *
     * @var Client
     */
    private Client $client;

    /**
     * @var array
     */
    private $options = [
        'scheme' => 'tcp',
        'host' => '127.0.0.1',
        'port' => 6379,
    ];

    public function __construct()
    {
        $cacheConfig = Configuration::getValue("cache");
        if (is_null($this->options) && isset($cacheConfig["redis"])) {
            $this->options = $cacheConfig["redis"];
        }

        $this->client = new Client($this->options);
    }

    public function getClient(): Client
    {
        return $this->client;
    }

    public function isAvailable(): bool
    {
        try {
            return $this->getClient()->ping() == "PONG";
        } catch (\Exception $e) {
            return false;
        }
    }

    public function clean($baseDir, $initDir = false, $filename = false)
    {
        $key = $this->getKey($baseDir, $initDir, $filename);
        if ($filename && !empty($key)) {
            $this->getClient()->del($key);
            return;
        }

        $key .= '*';
        $keyList = $this->getClient()->keys($key);
        if (!empty($keyList)) {
            $this->getClient()->del($keyList);
        }
    }

    public function read(&$arAllVars, $baseDir, $initDir, $filename, $TTL): bool
    {
        $key = $this->getKey($baseDir, $initDir, $filename);
        if ($this->getClient()->exists($key) <= 0) {
            return false;
        }

        $rawData = $this->getClient()->get($key);
        $arAllVars = json_decode($rawData, true);
        return true;
    }

    public function write($arAllVars, $baseDir, $initDir, $filename, $TTL)
    {
        $key = $this->getKey($baseDir, $initDir, $filename);
        $data = json_encode($arAllVars);
        $this->getClient()->set($key, $data);
        $this->getClient()->expire($key, $TTL);
    }

    private function getKey($baseDir, $initDir, $filename): string
    {
        return $baseDir . ":" . $initDir . ":" . $filename;
    }

    public function isCacheExpired($path): bool
    {
        return false;
    }

    public function getReadBytes(): int
    {
        $info = $this->getClient()->info();
        return (int) $info['Stats']['total_net_output_bytes'];
    }

    public function getWrittenBytes(): int
    {
        $info = (int) $this->getClient()->info();
        return (int) $info['Stats']['total_net_input_bytes'];
    }

    public function getCachePath(): string
    {
        return "";
    }
}
  • Пример использования
Механизм хорошо подходит для кэширования редко изменяемых значений, которых много, они небольшие и доступ к ним должен быть очень быстрым.
В примере ниже происходит кэширование запросов к dadate за адресами в методе getAddress.
Синтаксис, как и при обычном кэшировании, только при создании экземпляра класса указываем, что механизм кэширования будет работать через наш cacheEngineRedis.

$cacheEngineRedis = new Redis();
$obCache = new Cache($cacheEngineRedis);

<?php

namespace SITE\Helper;

use \SITE\CacheEngine\Redis;
use \Bitrix\Main\Data\Cache;

class Dadata
{
    private static $dadata = false;
    private static array $blocked = [];
    private static int $count = 10;
    private static float $checkBalance = 1000.00;

    private static $token = "#####";
    private static $secret = "#####";

    public function __construct()
    {
        if (!self::$dadata) {
            self::$dadata = new \Dadata\DadataClient(self::$token, self::$secret);
        }
        if (!self::$blocked) {
            self::$blocked = (new BlockedCities())->getBlocked();
        }
    }

    public static function checkBalanceAgent(): string
    {
        self::$dadata = new \Dadata\DadataClient(self::$token, self::$secret);

        (new Dadata())->checkBalance();

        return '\SITE\Helper\Dadata::checkBalanceAgent();';
    }

    public function getCity($address): array
    {
        $data = $this->getAddress($address);
        $output = [];

        foreach ($data as $item) {
            if (!$item['city']) continue;

            if (in_array($item['region'], self::$blocked['region'])) continue;
            if (in_array($item['city'], self::$blocked['city'])) continue;

            $output[$item['city']] = $item;
            $output[$item['city']]['show'] = $item['city'];
        }

        unset($data);

        return array_values($output);
    }

    public function getStreet($address): array
    {
        $data = $this->getAddress($address);
        $output = [];

        foreach ($data as $item) {
            if (!$item['street']) continue;
            $output[$item['street']] = $item;
            $output[$item['street']]['show'] = $item['street'];
        }

        unset($data);

        return array_values($output);
    }

    public function getHouse($address): array
    {
        $data = $this->getAddress($address);
        $output = [];

        foreach ($data as $item) {
            if (!$item['house']) continue;
            $output[$item['house']] = $item;
            $output[$item['house']]['show'] = $item['house'];
        }

        unset($data);

        return array_values($output);
    }

    private function getAddress($address): array
    {
        $data = [];
        $cacheEngineRedis = new Redis();
        $obCache = new Cache($cacheEngineRedis);
        if ($obCache->initCache(604800, $address, "dadata")) {
            $data = $obCache->GetVars();
            // $obCache->output(); // Кешированный ввод
        } elseif ($obCache->startDataCache()) {
            $arAddress = self::$dadata->suggest("address", $address, self::$count);

            foreach ($arAddress as $value) {
                if (!$value['data']['city'] && !$value['data']['settlement']) {
                    continue;
                }

                $house = '';
                if ($value['data']['house']) {
                    $house = $value['data']['house'];
                    if ($value['data']['block_type']) {
                        $house .= " " . $value['data']['block_type'];
                    }
                    if ($value['data']['block']) {
                        $house .= " " . $value['data']['block'];
                    }
                }

                $data[] = [
                    'address' => $value['value'],
                    'region' => $value['data']['region'],
                    'city' => $value['data']['city'] ?? $value['data']['settlement'],
                    'street' => $value['data']['street_with_type'] ?? $value['data']['street'] ?? '',
                    'house' => $house,
                    'postalCode' => $value['data']['postal_code'] ?? $value['data']['geoname_id'] ?? '100000',
                    'area' => $value['data']['area'] ?? '',
                    'allData' => $value
                ];
            }

            $obCache->endDataCache($data);
        }

        return $data;
    }

    private function checkBalance()
    {
        $balance = self::$dadata->getBalance();

        if ($balance < self::$checkBalance) {
            \CAdminNotify::Add(
                [
                    'MESSAGE' => 'На счету dadata менее 1000 рублей',
                    'TAG' => 'DADATA_BALANCE'
                ]
            );
        }
    }
}
Подписывайтесь на канал для bitrix-разработчиков в Telegram!

Рекомендованные статьи