Разделите dict с несколькими скриптами Python
Я хотел бы, чтобы уникальная база данных dict
(key/value) была доступна из нескольких сценариев Python, запущенных одновременно.
Если script1.py
обновляет d[2839]
, тогда script2.py
должно увидеть измененное значение при запросе d[2839]
через несколько секунд после.
-
Я думал об использовании SQLite, но кажется, что одновременная запись/чтение из нескольких процессов не является силой SQLite (скажем, script1.py
только что изменил d[2839]
, как script2.py
соединение SQLite знает он должен перезагрузить эту конкретную часть базы данных?)
-
Я также думал о блокировке файла, когда хочу сбросить изменения (но это довольно сложно сделать) и использовать json.dump
для сериализации, затем, пытаясь обнаружить изменения, используйте json.load
для перезагрузки, если какая-либо модификация и т.д.... о нет, я изобретаю колесо и заново изобретаю особенно неэффективную базу данных ключ/значение!
-
redis выглядел как решение, но официально не поддерживает Windows, то же самое относится к leveldb.
-
несколько сценариев, возможно, захотят записать точно в одно и то же время (даже если это очень редкое событие), есть ли способ, которым система БД справляется с этим (благодаря параметру блокировки? Кажется, что SQLite по умолчанию не может этого сделать, потому что SQLite поддерживает неограниченное количество одновременных считывателей, но это позволит только одному писателю в любой момент времени.)
Что для этого было бы Pythonic?
Примечание. Я нахожусь в Windows, а dict должен иметь максимум 1M элементов (ключ и значение обоих целых чисел).
Ответы
Ответ 1
В MOS встроенного хранилища данных, отличного от SQLite, нет оптимизации для одновременного доступа, мне также было интересно, как работает одновременная производительность SQLite, поэтому я сделал контрольный показатель:
import time
import sqlite3
import os
import random
import sys
import multiprocessing
class Store():
def __init__(self, filename='kv.db'):
self.conn = sqlite3.connect(filename, timeout=60)
self.conn.execute('pragma journal_mode=wal')
self.conn.execute('create table if not exists "kv" (key integer primary key, value integer) without rowid')
self.conn.commit()
def get(self, key):
item = self.conn.execute('select value from "kv" where key=?', (key,))
if item:
return next(item)[0]
def set(self, key, value):
self.conn.execute('replace into "kv" (key, value) values (?,?)', (key, value))
self.conn.commit()
def worker(n):
d = [random.randint(0, 1<<31) for _ in range(n)]
s = Store()
for i in d:
s.set(i, i)
random.shuffle(d)
for i in d:
s.get(i)
def test(c):
n = 5000
start = time.time()
ps = []
for _ in range(c):
p = multiprocessing.Process(target=worker, args=(n,))
p.start()
ps.append(p)
while any(p.is_alive() for p in ps):
time.sleep(0.01)
cost = time.time() - start
print(f'{c:<10d}\t{cost:<7.2f}\t{n/cost:<20.2f}\t{n*c/cost:<14.2f}')
def main():
print(f'concurrency\ttime(s)\tpre process TPS(r/s)\ttotal TPS(r/s)')
for c in range(1, 9):
test(c)
if __name__ == '__main__':
main()
результат на моем 4-ядерном поле macOS, том SSD:
concurrency time(s) pre process TPS(r/s) total TPS(r/s)
1 0.65 7638.43 7638.43
2 1.30 3854.69 7709.38
3 1.83 2729.32 8187.97
4 2.43 2055.25 8221.01
5 3.07 1629.35 8146.74
6 3.87 1290.63 7743.78
7 4.80 1041.73 7292.13
8 5.37 931.27 7450.15
результат на сервере с ядрами с 8 ядрами 2012 облачный сервер, объем SSD:
concurrency time(s) pre process TPS(r/s) total TPS(r/s)
1 4.12 1212.14 1212.14
2 7.87 634.93 1269.87
3 14.06 355.56 1066.69
4 15.84 315.59 1262.35
5 20.19 247.68 1238.41
6 24.52 203.96 1223.73
7 29.94 167.02 1169.12
8 34.98 142.92 1143.39
получается общая пропускная способность, независимо от concurrency, а SQLite медленнее в окнах, чем macOS, надеюсь, что это будет полезно.
Поскольку блокировка записи SQLite - это мудрая база данных, чтобы получить больше TPS, вы можете разделить данные на файлы с несколькими базами данных:
class MultiDBStore():
def __init__(self, buckets=5):
self.buckets = buckets
self.conns = []
for n in range(buckets):
conn = sqlite3.connect(f'kv_{n}.db', timeout=60)
conn.execute('pragma journal_mode=wal')
conn.execute('create table if not exists "kv" (key integer primary key, value integer) without rowid')
conn.commit()
self.conns.append(conn)
def _get_conn(self, key):
assert isinstance(key, int)
return self.conns[key % self.buckets]
def get(self, key):
item = self._get_conn(key).execute('select value from "kv" where key=?', (key,))
if item:
return next(item)[0]
def set(self, key, value):
conn = self._get_conn(key)
conn.execute('replace into "kv" (key, value) values (?,?)', (key, value))
conn.commit()
результат на моем mac с 20 разделами:
concurrency time(s) pre process TPS(r/s) total TPS(r/s)
1 2.07 4837.17 4837.17
2 2.51 3980.58 7961.17
3 3.28 3047.68 9143.03
4 4.02 2486.76 9947.04
5 4.44 2249.94 11249.71
6 4.76 2101.26 12607.58
7 5.25 1903.69 13325.82
8 5.71 1752.46 14019.70
общий TPS выше, чем отдельный файл базы данных.
Ответ 2
До того, как появился redis, был Memcached (который работает в Windows).
Вот учебник. https://realpython.com/blog/python/python-memcache-efficient-caching/
Ответ 3
Я бы рассмотрел 2 варианта, обе встроенные базы данных
SQlite
Как ответил здесь и здесь, это должно быть хорошо
BerkeleyDB
ссылка
Berkeley DB (BDB) - это программная библиотека, предназначенная для обеспечения высокопроизводительной встроенной базы данных для данных ключа/значения
Он был разработан специально для вашей цели
BDB может поддерживать тысячи одновременных потоков управления или одновременные процессы, обрабатывающие базы данных размером до 256 терабайты, 3 в самых разных операционных системах, включая большинство Unix-подобных и Windows-систем, а также операционных систем реального времени.
Он прочен и существует уже много лет, если не десятилетия.
Включение redis
/memcached
/любой другой полноценный сервер на основе сокетов, требующий участия sysops IMO, является накладной задачей для обмена данными между двумя скриптами, расположенными в том же поле
Ответ 4
Вы можете использовать словарь python для этой цели.
Создайте общий класс или script с именем G, который инициализирует словарь в нем. G будет запускать script1.py и script2.py и передает словарь в оба файла сценариев, в словаре python по умолчанию передается по ссылке. Таким образом, для хранения данных используется один словарь, и оба сценария могут изменять значения словаря, изменения можно увидеть в обоих сценариях. Я надеюсь, что script1.py и script2.py основаны на классах. Это не гарантирует сохранение данных. Для настойчивости вы можете хранить данные в базе данных через x интервалов.
Пример
script1.py
class SCRIPT1:
def __init__(self, dictionary):
self.dictionary = dictionary
self.dictionary.update({"a":"a"})
print("SCRIPT1 : ", self.dictionary)
def update(self):
self.dictionary.update({"c":"c"})
script2.py
class SCRIPT2:
def __init__(self, dictionary):
self.dictionary = dictionary
self.dictionary.update({"b":"b"})
print("SCRIPT 2 : " , self.dictionary)
main_script.py
import script1
import script2
x = {}
obj1 = script1.SCRIPT1(x) # output: SCRIPT1 : {'a': 'a'}
obj2 = script2.SCRIPT2(x) # output: SCRIPT 2 : {'a': 'a', 'b': 'b'}
obj1.update()
print("SCRIPT 1 dict: ", obj1.dictionary) # output: SCRIPT 1 dict: {'c': 'c', 'a': 'a', 'b': 'b'}
print("SCRIPT 2 dict: ", obj2.dictionary) # output: SCRIPT 2 dict: {'c': 'c', 'a': 'a', 'b': 'b'}
Также создайте пустой файл _ init _.py в каталоге, в котором вы будете запускать скрипты.
Другой вариант:
Redis
Ответ 5
Вы можете использовать диспетчер базы данных на основе документов. Возможно, для вашей системы слишком тяжело, но одновременный доступ обычно является одной из причин, по которым используются системы управления БД и API для подключения к ним.
Я использовал MongoDB с Python, и он отлично работает. Документация API Python достаточно хороша, и каждый документ (элемент базы данных) является словарем, который может быть загружен на питон как таковой.
Ответ 6
Я бы использовал паб /sub websocket-framework, например Autobahn/Python с одним script как "сервер", и он обрабатывает все файлы, но это зависит от масштаба, возможно, это может быть Overkill.
Ответ 7
Вероятно, вам стоит взглянуть на Apache Arrow
(pyarrow пакет для python).
Apache Arrow - это платформа разработки на разных языках для данных в памяти. Он определяет стандартизованный независимый от языка формат столбчатой памяти для плоских и иерархических данных, организованный для эффективных аналитических операций на современном оборудовании. Он также предоставляет вычислительные библиотеки и потоковые передачи с нулевым копированием и межпроцессные коммуникации.
Связи Python со стрелками имеют первоклассную интеграцию с NumPy, pandas и встроенными объектами Python.
Формат памяти Arrow поддерживает чтение с нулевой копией для быстрого доступа к данным без накладных расходов сериализации.
Идея заключалась бы в том, чтобы сохранить в памяти представление вашей БД (Arrow), разделяемое всеми процессами и просто пишите на паркет или перо в качестве соответствующих столбчатых форматов данных на диске для сохранения.
Ответ 8
CodernintyDB можно было бы изучить, используя версию сервера.
http://labs.codernity.com/codernitydb/
Версия сервера:
http://labs.codernity.com/codernitydb/server.html
Ответ 9
Похоже, вам действительно нужна база данных.
Если redis не будет работать для окон, я бы посмотрел на MongoDB.
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/
MongoDB отлично работает с python и может функционировать подобно redis. Вот установочные документы для PyMongo:
http://api.mongodb.com/python/current/installation.html?_ga=2.78008212.1422709185.1517530606-587126476.1517530605
Кроме того, многие люди подняли SQlite. Я думаю, вы были обеспокоены тем, что он позволяет только одному писателю за раз, но для вас это не проблема. Я думаю, что это говорит о том, что если есть два автора, второй будет заблокирован до тех пор, пока первый не будет завершен. Вероятно, это хорошо для вашей ситуации.