Вопрос по mod-wsgi, python, memcached, shelve, multiprocessing – Постоянный многопроцессорный общий кэш в Python с stdlib или минимальными зависимостями

5

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

Мне было интересно, как лучше всего подойти, если я хочу сделать этот многопроцессный процесс безопасным? Я в курсе Redis, Memcached и тому подобное "реальные решения ", но я'Я хотел бы использовать только части стандартной библиотеки Python или очень минимальные зависимости, чтобы сохранить мой код компактным и не вносить ненужную сложность при запуске кода в одном процессе - однопоточной модели.

Это'Легко придумать решение с одним процессом, но это не очень хорошо работает во время работы Python. В частности, проблема заключается в том, что в среде Apache + mod_wsgi

Только один процесс обновляет кэшированные данные один раз (блокировка файлов, как-нибудь?)

Другие процессы используют кэшированные данные во время обновления

Если процессу не удается обновить кэшированные данные, штрафуется в течение N минут, прежде чем другой процесс может повторить попытку (чтобы предотвратитьгромоподобное стадо и такой) - как сигнализировать это между процессами mod_wsgi

Вы не используете "тяжелые инструменты " для этого только стандартные библиотеки Python и UNIX

Также, если какой-то пакет PyPi делает это без внешних зависимостей, сообщите мне об этом, пожалуйста. Альтернативные подходы и рекомендации, вроде "просто используйте sqlite " Добро пожаловать

Пример:

import datetime
import os
import shelve
import logging


logger = logging.getLogger(__name__)


class Converter:

    def __init__(self, fpath):
        self.last_updated = None
        self.data = None

        self.data = shelve.open(fpath)

        if os.path.exists(fpath):
            self.last_updated = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))

    def convert(self, source, target, amount, update=True, determiner="24h_avg"):
        # Do something with cached data
        pass

    def is_up_to_date(self):
        if not self.last_updated:
            return False

        return datetime.datetime.now() < self.last_updated + self.refresh_delay

    def update(self):
        try:
            # Update data from the external server
            self.last_updated = datetime.datetime.now()
            self.data.sync()
        except Exception as e:
            logger.error("Could not refresh market data: %s %s", self.api_url, e)
            logger.exception(e)
Должны ли ваши кэшированные данные иметь возможность обмениваться и / или сохраняться на диске, или это то же самое, чтобы предполагать, что ваш кэш будет помещаться в доступную память? cnelson
Это'просто небольшое количество данных; но он должен быть там после холодного запуска сервера, чтобы избежать проблем, связанных с отсутствием данных при запуске. Mikko Ohtamaa

Ваш Ответ

3   ответа
2

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

dogpile.cache Документация говорит следующее:

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

Кроме того, dogpile.cache сам по себе без потоков, он просто используетthreading.Lock Antti Haapala
В настоящее время я исследую ту же задачу, и натолкнулся на собачью кучу. Хотя я до сих пор не до конца понимаю внутренности dogpile, существует множество случаев использования потоков в документах. По словам [тупик] (stackoverflow.com/questions/24509650/...) и другие, включая мой опыт, многопроцессорность + многопоточность + ведение журнала могут привести к тупику. Этого можно избежать, сначала порождая процессы, а затем - потоки. Zoltan K.
+1 за собачью кучуЭто довольно хорошо и может справиться с громовым ударом. это сторонний пакет, и, вероятно, он требует некоторых зависимостей в реальной жизни, поэтому немного не в ладу с OP. Dima Tisnek
@ZoltanK. Я отсылаю вас к моему другому ответуstackoverflow.com/a/46440564/918959 :) Antti Haapala
0

Я написал блокирующую (многопроцессную и многопроцессорную безопасную) оболочку вокруг стандартногоshelve модуль без внешних зависимостей:

https://github.com/cristoper/shelfcache

Он отвечает многим вашим требованиям, но у него нет какой-либо стратегии отсрочки для предотвращения гремящих стад, и если вы хотите блокировку Reader-Writer (так, чтобы несколько потоков могли читать, но только одну запись), вы должны предоставить свой собственный RW. замок.

Однако, если бы я сделал это снова, я бынаверноепросто используйте sqlite ",shelve модуль, который абстрагируется от нескольких различных реализаций dbm, которые сами абстрагируются от различных механизмов блокировки ОС, является проблемой (с использованием полкаflock опция с gdbm в Mac OS X (или busybox), например, приводит к взаимоблокировке).

Существует несколько проектов Python, которые пытаются предоставить стандартный интерфейс dict для sqlite или других постоянных хранилищ, например:https://github.com/RaRe-Technologies/sqlitedict

(Обратите внимание, чтоsqldict являетсянить безопасен даже для одного и того же соединения с базой данных, но небезопасно разделять одно и то же соединение с базой данных между процессами.)

2

Позволять's систематически учитывать ваши требования:

минимум или нет внешних зависимостей

Ваш вариант использования определит, можете ли вы использовать внутриполосную (дескриптор файла или область памяти, унаследованную через fork) или внеполосную синхронизацию (блокировки файлов posix, общая память sys V).

Тогда у вас могут быть другие требования, например, кроссплатформенная доступность инструментов и т. д.

Там действительно нетТак много в стандартной библиотеке, кроме простых инструментов. Один модуль, однако, выделяется,sqlite3, Sqlite использует блокировки fcntl / posix, однако существуют ограничения производительности, многие процессы подразумевают базу данных с файловой поддержкой, а sqlite требует fdatasync при фиксации.

Таким образом, естьs ограничение транзакций / с в sqlite, налагаемое оборотами вашего жесткого диска. Последнее не имеет большого значения, если у вас есть hw raid, но может быть серьезным препятствием на рынке аппаратного обеспечения, например ноутбук или USB флэш или SD-карты. Планируйте ~ 100tps, если вы используете обычный, вращающийся жесткий диск.

Ваши процессы также могут блокироваться на sqlite, если вы используете специальные режимы транзакций.

предотвращение гремящего стада

Есть два основных подхода к этому:

вероятностно обновить элемент кэша раньше, чем требуется, илиобновлять только при необходимости, но блокировать других абонентов

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

Wrt. лимит транзакций / с: если вы непользователю требуется постоянный кеш при перезагрузкеPRAGMA synchronous = OFF, тогда fdatasyncs не будут выполнены, и производительность снова будет низкой; не забудьте очистить кеш при запуске. в качестве альтернативы храните ваши базы данных на томе tmpfs / ramfs. Dima Tisnek
Круто, это качественный вход, который я искал. Mikko Ohtamaa

Похожие вопросы