Python - делаем распределенное хранилище или простой peer-to-peer на Python!

Эта статья будет длинной, так что прежде чем писать тонны кода - подумайте, надо ли оно вам =)

Не большая предистория: я как сисадмин на аутсорсе обслуживаю сервера под Windows, Linux, плюс у меня есть VPS для хостинга сайтов. Возникает вопрос надежного хранения данных, я настроил везде копирование баз 1с в Яндекс.Диск простым бат и bash ну и плюс сжатие в rar или gzip в зависимости от того, на какой операционной системе крутятся базы. Яндекс.Диск у меня бесплатный и емкость ограничена и эти резервные копии я сливаю на свой VPS, учитывая его бюджетность и емкость - 40 гб (из свободных 10-13 гб) приходится ручками удалять прилетевшие копии, смотреть чтобы все было в нужных количествах в нужных местах и чтобы всегда все можно было легко реанимировать в случае шифровальщиков, вирусов, человеческого фактора, аппаратного сбоя.

Не то чтобы оно мне очень прямо не обходимо, просто очень интересно - создать систему распределенного хранилища данных, опять же если считаете что я пишу велосипед и такие системы уже точно есть, можете не вникать. Но все сервера с различными ОС - где то Windows, где то Linux, плюс пользовательские машины с Windows 7-10, которые так же можно использовать, в перспективе хранилище можно расширить и на другие платформы. По этому задачка не простая - создать кроссплатформенное peer-to-peer хранилище для некоторого объема данных, в общей сложности в районе 100-200 гигабайт, в основном это базы данных 1с.

Сеть должна быть безопасной, стабильной и всегда доступной. Все обслуживаемые мной компьютеры я объеденил в VPN-сеть, кроме серверов - это несколько десятков компьютеров.

Сейчас не много математики, статистики и вероятности... Сразу скажу - фраза "вероятность никогда не равна нулю" будет здесь повторяться очень часто.

И так, у нас есть тестовая база данных 1с объемом 3 гигабайта, ее можно тупо копировать на два соседних сервера и получить бэкап в удобное время. Какова вероятность ее восстановить - 50/50 либо эти два соседних сервера будут работать одновременно либо одновременно будут не доступны, могут быть в том районе города перебои с питанием или интернетом, Другое дело статистика - на которую влияет масса факторов - одинаковый ли интернет провайдер у двух соседних серверов, от одной ли линии электро сети они питаются, есть ли ИБП и резервный канал связи, по статистике что два соседних компьютера будут недоступны одновременны - вероятность минимальна, но НЕ РАВНА НУЛЮ. Чем больше "соседних компьютеров" - тем по статистике вероятность меньше одновременных отказов всех сразу - но НЕ РАВНА НУЛЮ никогда. По этому надо хранить дубли информации на различных серверах, уж точно не на одном или двух, пяти - вполне хватит, но сливать 3 гигабайта сразу на пять серверов - это долго и не безопасно хранить чужие данные на чужих серверах, врядли кто-то из вас согласится отдать свои базы 1с кому-то на ответственное хранение. По этому мы как вариант можем сжать базу в многотомный архив и раскидать по разным компютерам - пока не соберете все фрагменты - не распаковать и не прочитать данные, плюс при приеме сервером нагрузка на сеть будет ниже если загружать части архива по очереди чем сосать сразу весь объем.

Исходя из абзаца выше нам надо различные тома архива раскидать в пяти экземплярах на пять разных серверов - при чем на как можно больше разных серверов, то есть не делать один многотомный архив из 5-и частей, перемешивать их и заливать на эти же пять серверов в различном порядке - это бессмыслено. Чем больше узлов используется - тем лучше, тогда можно архив разбивать не на 5 частей для пяти серверов, а на гораздо меньшие части - хоть до килобайта, но тогда нужен огромный парк компьютеров. Каждый компьютер который отдает свои данные по частям другим компьютерам - должен знать где они лежат, чтобы взять при необходимости обратно. Если вдруг какой-то узел в сети недоступен условно говоря "навсегда" - то есть с него пропали данные, для большей вероятности сохранения - другие узлы должны выбрать где еще хранить пятую копию.

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

Каждый экземпляр приложения хотя бы раз должен подключиться к мастер-серверу - получить с него список IP-адресов других серверов и цепляться к ним и раскидывать данные, если все находится в одной локальной сети - в принципе легко реализуемо (в том числе VPN), что же делать если компьютер не в этой подсети, находится за натом например - тогда он будет чисто клиентским - который раз в какой-нибудь период времени будет опрашивать доступные серверы, получать от них команды - что делать - например так же - хранить информацию, получать или отдавать.

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

Теперь более практично - как должно действовать приложение, которое раздает свои данные:

Подключиться к мастер-серверу, взять список IP-адресов доступных других серверов, запомнить их.

Взять большой файл, посчитать его контрольную сумму.

Посмотреть сколько есть доступных серверов-приемников, разбить файл на необходимое количество фрагментов.

Преобразовать каждый фрагмент в base64 и сверить контрольную сумму этой кодировки.

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

Сервер же получает эти данные - строку base64 и контрольную сумму, сохраняет в базу.

Клиент же запоминает - каким серверам кинул файлы, то есть пишет уже в своей базе - контрольную сумму всего файла, его размер, IP-сервера куда ушло, контрольную сумму фрагмента, имя файла, в идеале же еще сделать авторизацию - логин и пароль.

Все - оно разлетелось.

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

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

С теорией вроде как разобрались, началась практика... И так - нам надо многопоточный сервер на Python! Это первая трудность с которой пришлось столкнуться, в интернете нашел примеры клиент-серверных приложений, однако после отключения клиента - сервер вырубался, соответственно следующий клиент уже не мог к нему подключиться. Так же - пытался к нему подключится второй клиент, пока первый не отключен - но отправить ему не мог, так как сервер в одном потоке не мог обработать несколько входящих сообщений. Раз Python в основе своей язык объектно-ориентированный, то и будем писать в ООП!

 

#!/usr/bin/python
# -*- coding: utf-8 -*-

import socket, sqlite3, threading                                                   # Подключаем необходимые модули
from threading import Thread                                                        # Эта штука нам нужна для многопоточности

class Server:                                                                       # Класс сервера
    port = 5006                                                                     # порт на котором сервер будет слушать клиентов
    def __init__(self):								    # Конструктор
	connsql = sqlite3.connect("mydatabase.db")                                  # В этой базе будем хранить все наши данные
	cursor = connsql.cursor()                                                   # Создаем курсор для работы с SQL
	cursor.execute("CREATE TABLE IF NOT EXISTS clients (ip text)")              # Создаем первую таблицу, в нее записываем "входящих"
	server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)	    # Создаем сокет
	server_socket.bind(('', Server.port))                                       # слушаем все интерфейсы по нужному порту
	server_socket.listen(20)                                                    # количество одновременных подключений
	while True:                                                                 # в бесконечном цикле
	    conn, address = server_socket.accept()                                  # принимаем подключения
	    print("Connection from: " + str(address))                               # пишем в консоли - кто подключился
	    cursor.execute('INSERT INTO clients VALUES ("' + str(address) + '")')   # Пишем данные в таблицу - кто подключился
	    connsql.commit()	                                                    # Сохраняем изменения
	    th = Thread(target=Server.threaded, args=(self,conn ))                  # создаем поток с функцией обработки запросов
	    th.start()                                                              # запускаем поток
	conn.close()

    def threaded(self, c): 							    # функция котораая что-нибудь отвечает клиенту
	while True: 
    	    data = c.recv(1024) 
    	    if not data: 
        	print('Bye') 
        	break
    	    c.send(data)
	c.close()

server=Server()                                                                     # Запускаем класс сервер

Комментарии

Дорогие посетители моего сайта, подпишитесь на мой канал в Дзене! Это стимулирует меня писать новые и новые познавательные статьи!