diff --git a/README.md b/README.md index 64d45b9..1001774 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ + [Что такое async/await, для чего они нужны и как их использовать](questions.md/#Что-такое-asyncawait-для-чего-они-нужны-и-как-их-использовать) + [Как в питоне реализуется многопоточность. Какими модулями](questions.md/#Как-в-питоне-реализуется-многопоточность-Какими-модулями) + [В чем отличие тредов от мультипроцессинга](questions.md/#В-чем-отличие-тредов-от-мультипроцессинга) + + [Чем отличаются многопоточность, мультипроцессность и асинхронность](questions.md/#Чем-отличаются-многопоточность,-мультипроцессность-и-асинхронность) + [Какие задачи хорошо параллелятся, какие плохо](questions.md/#Какие-задачи-хорошо-параллелятся-какие-плохо) + [Нужно посчитать 100 уравнений. Делать это в тредах или нет](questions.md/#Нужно-посчитать-100-уравнений-Делать-это-в-тредах-или-нет) + [Треды в Питоне — это нативные треды или нет](questions.md/#Треды-в-Питоне--это-нативные-треды-или-нет) @@ -275,6 +276,7 @@ * [Что такое lru cache](questions.md/#Что-такое-lru-cache) * [Что такое MQ](questions.md/#Что-такое-MQ) * [Какие готовые реализации MQ вы знаете](questions.md/#Какие-готовые-реализации-MQ-вы-знаете) + * [Брокеры сообщений: подробное руководство](questions.md/#Брокеры-сообщений:-подробное-руководство) * [Что такое RPC](questions.md/#Что-такое-RPC) * [Что такое gPRC](questions.md/#Что-такое-gPRC) - [Алгоритмы, структуры](questions.md/#Алгоритмы-структуры) @@ -337,3 +339,6 @@ * [Вопросы для технического собеседования](questions.md/#Вопросы-для-технического-собеседования) - [Интересные ссылки](questions.md/#Интересные-ссылки) - [Источники вопросов](questions.md/#Источники-вопросов) +- [100 вопросов на python](https://uproger.com/bolee-100-voprosov-s-sobesedovaniya-python-razbor-realnyh-voprosov/) +- [99 вопросов для python собеседования](https://tproger.ru/articles/chto-dolzhen-znat-razrabotchik-na-python-bez-opyta) +- [Базовые вопросы с технического собеседования Python (Видео)](https://rutube.ru/video/67aa0e16dcea4027c3e1c9ea9d82be93/) diff --git a/questions.md b/questions.md index 4cecbad..3f14626 100644 --- a/questions.md +++ b/questions.md @@ -1500,6 +1500,108 @@ loop.run_until_complete(asyncio.gather(*futures)) Главное отличие в разделении памяти. Процессы независимы друг от друга, имеют раздельные адресные пространства, идентификаторы, ресурсы. Треды исполняются в совместном адресном порстранстве, имеют общий доступ к памяти, переменным, загруженным модулям. +### Чем отличаются многопоточность, мультипроцессность и асинхронность + +Многопоточность: Несколько потоков в рамках одного процесса делят общую память. +``` +import threading +import time + +def task(name): + print(f"{name} начал работу") + time.sleep(2) # Имитация I/O операции + print(f"{name} завершил работу") + +# Создание и запуск потоков +threads = [] +for i in range(3): + t = threading.Thread(target=task, args=(f"Поток-{i}",)) + threads.append(t) + t.start() + +for t in threads: + t.join() +``` + +Ключевые особенности: +* Общая память между потоками +* GIL ограничивает выполнение только одного потока Python одновременно +* Подходит для I/O-bound задач (сетевые запросы, работа с файлами) +* Не подходит для CPU-bound задач из-за GIL + +Мультипроцессность: Несколько независимых процессов с собственной памятью. +``` +import multiprocessing +import time + +def cpu_intensive_task(n): + result = 0 + for i in range(n): + result += i * i + return result + +if __name__ == "__main__": + with multiprocessing.Pool(processes=4) as pool: + results = pool.map(cpu_intensive_task, [10000000] * 4) + print(f"Результаты: {results}") +``` + +Ключевые особенности: +* Каждый процесс имеет собственную память +* Обход GIL — процессы выполняются действительно параллельно +* Подходит для CPU-bound задач (вычисления, обработка данных) +* Большие накладные расходы на создание процессов +* Сложнее обмен данными между процессами + +Асинхронность: Кооперативная многозадачность в одном потоке. +``` +import asyncio +import aiohttp + +async def fetch_url(https://github.com/session,%20url): + async with session.get(url) as response: + return await response.text() + +async def main(): + urls = [ + "https://httpbin.org/delay/1", + "https://httpbin.org/delay/2", + "https://httpbin.org/delay/1" + ] + + async with aiohttp.ClientSession() as session: + tasks = [fetch_url(https://github.com/session,%20url) for url in urls] + results = await asyncio.gather(*tasks) + print(f"Получено {len(results)} ответов") + +asyncio.run(main()) +``` + +Ключевые особенности: +* Один поток, кооперативная многозадачность +* Нет проблем с GIL — выполняется в одном потоке +* Идеально для I/O-bound задач с большим количеством соединений +* Низкие накладные расходы по сравнению с потоками/процессами +* Требует специального синтаксиса (async/await) + +image + +Когда что использовать: +Многопоточность — когда: +* Много I/O операций (запросы к API, БД, файловая система) +* Нужно обновлять GUI без блокировки интерфейса +* Простая синхронизация через общую память + +Мультипроцессность — когда: +* Тяжелые вычисления (машинное обучение, обработка изображений) +* Нужен истинный параллелизм для CPU-bound задач +* Задачи можно легко разделить на независимые части + +Асинхронность — когда: +* Высоконагруженные сетевые приложения (веб-серверы, API) +* Тысячи одновременных I/O операций +* Нужна максимальная производительность для I/O-bound задач + ### Какие задачи хорошо параллелятся, какие плохо Хорошо параллелятся задачи, которые порождают долгий IO. Когда тред упирается в ожидание сокета или диска, интерпретатор бросает этот тред и стартует следующий. Это значит, не будет простоя из-за ожидания. Наоборот, если ходить в сеть в одном треде (в цикле), то каждый раз придется ждать ответа. @@ -4764,6 +4866,280 @@ LRU (least recently used) — это алгоритм, при котором в - rocketMQ - zeroMQ + +## Брокеры сообщений: подробное руководство + +Брокер сообщений — это промежуточное программное обеспечение, которое обеспечивает асинхронный обмен сообщениями между различными компонентами распределенной системы через модель публикации-подписки или очереди задач. Он действует как посредник, принимая сообщения от отправителей (producers), храня их в очередях и доставляя получателям (consumers), обеспечивая тем самым развязку компонентов системы, повышение надежности и масштабируемости. +Основные концепции: Producer (отправитель) — компонент, который отправляет сообщения в брокер. Consumer (получатель) — компонент, который получает и обрабатывает сообщения из брокера. Queue (очередь) — буфер, где сообщения хранятся до обработки. Exchange (точка обмена) — компонент, который определяет правила маршрутизации сообщений по очередям. + +### Для чего используются брокеры сообщений + +Брокеры сообщений используются для развязки компонентов системы, когда различные модули приложения не общаются напрямую, а взаимодействуют через промежуточное программное обеспечение. Они обеспечивают асинхронную обработку задач, позволяя основному приложению быстро отвечать на запросы, в то время как тяжелые операции выполняются в фоновом режиме. Брокеры обеспечивают балансировку нагрузки, распределяя задачи между несколькими обработчиками, и повышают отказоустойчивость системы, сохраняя сообщения даже при временной недоступности обработчиков. Они также обеспечивают масштабируемость системы, позволяя легко добавлять новые обработчики для увеличения пропускной способности, и буферизацию сообщений, сглаживая пиковые нагрузки и предотвращая перегрузку системы. + +### Популярные брокеры сообщений + +RabbitMQ — наиболее популярный брокер, написанный на Erlang, поддерживающий AMQP протокол и предоставляющий богатые возможности по маршрутизации сообщений. Redis — in-memory хранилище данных, которое может использоваться как простой брокер сообщений с хорошей производительностью. Apache Kafka — высокопроизводительный брокер, предназначенный для обработки потоков данных и работы с большими объемами информации. Celery — распределенная очередь задач specifically designed для Python приложений. + +### Примеры использования на Python + +``` +pip install pika redis celery kafka-python +``` + +### Пример 1: RabbitMQ с pika + +### Producer + +``` +import pika +import json + +class RabbitMQProducer: + def __init__(self, host='localhost'): + self.connection = pika.BlockingConnection( + pika.ConnectionParameters(host=host) + ) + self.channel = self.connection.channel() + self.channel.queue_declare(queue='task_queue', durable=True) + + def send_message(self, message): + self.channel.basic_publish( + exchange='', + routing_key='task_queue', + body=json.dumps(message), + properties=pika.BasicProperties(delivery_mode=2) + ) + print(f"Sent: {message}") + + def close(self): + self.connection.close() + +producer = RabbitMQProducer() +producer.send_message({'task': 'process_image', 'image_id': 123}) +producer.send_message({'task': 'send_email', 'email': 'user@example.com'}) +producer.close() +``` + +### Consumer + +``` +import pika +import json +import time + +class RabbitMQConsumer: + def __init__(self, host='localhost'): + self.connection = pika.BlockingConnection( + pika.ConnectionParameters(host=host) + ) + self.channel = self.connection.channel() + self.channel.queue_declare(queue='task_queue', durable=True) + self.channel.basic_qos(prefetch_count=1) + + def process_message(self, ch, method, properties, body): + message = json.loads(body) + print(f"Received: {message}") + + try: + if message['task'] == 'process_image': + time.sleep(2) + elif message['task'] == 'send_email': + time.sleep(1) + + ch.basic_ack(delivery_tag=method.delivery_tag) + print(f"Processed: {message}") + + except Exception as e: + print(f"Error processing message: {e}") + ch.basic_nack(delivery_tag=method.delivery_tag) + + def start_consuming(self): + self.channel.basic_consume( + queue='task_queue', + on_message_callback=self.process_message + ) + print("Waiting for messages. To exit press CTRL+C") + self.channel.start_consuming() + +consumer = RabbitMQConsumer() +consumer.start_consuming() +``` + +### Пример 2: Redis как брокер + +### Producer + +``` +import redis +import json + +class RedisProducer: + def __init__(self, host='localhost', port=6379): + self.redis_client = redis.Redis(host=host, port=port, db=0) + + def send_message(self, queue_name, message): + self.redis_client.rpush(queue_name, json.dumps(message)) + print(f"Sent to {queue_name}: {message}") + +producer = RedisProducer() +producer.send_message('task_queue', {'task': 'data_processing', 'data': [1, 2, 3]}) +producer.send_message('email_queue', {'task': 'notification', 'user_id': 456}) +``` + +### Consumer + +``` +import redis +import json +import time + +class RedisConsumer: + def __init__(self, host='localhost', port=6379): + self.redis_client = redis.Redis(host=host, port=port, db=0) + + def process_message(self, message): + task_data = json.loads(message) + print(f"Processing: {task_data}") + + if task_data['task'] == 'data_processing': + result = sum(task_data['data']) + print(f"Processing result: {result}") + time.sleep(1) + + def start_consuming(self, queue_name): + print(f"Waiting for messages from {queue_name}") + while True: + message = self.redis_client.blpop(queue_name, timeout=0) + if message: + queue, message_data = message + self.process_message(message_data) + +consumer = RedisConsumer() +consumer.start_consuming('task_queue') +``` + +### Пример 3: Celery для распределенных задач + +### Конфигурация Celery + +``` +from celery import Celery + +app = Celery( + 'tasks', + broker='redis://localhost:6379/0', + backend='redis://localhost:6379/0', + include=['tasks'] +) + +app.conf.update( + task_serializer='json', + accept_content=['json'], + result_serializer='json', + timezone='Europe/Moscow', + enable_utc=True, +) +``` + +### Определение задач + +``` +from celery_config import app +import time + +@app.task +def process_image(image_id): + print(f"Processing image {image_id}") + time.sleep(5) + return f"Processed image {image_id}" + +@app.task +def send_email(email_address, subject, body): + print(f"Sending email to {email_address}") + time.sleep(2) + return f"Email sent to {email_address}" +``` + +### Producer для Celery + +``` +from tasks import process_image, send_email + +result1 = process_image.delay(123) +result2 = send_email.delay('user@example.com', 'Welcome', 'Hello!') + +print(f"Task IDs: {result1.id}, {result2.id}") +``` + +### Пример 4: Apache Kafka + +### Producer + +``` +from kafka import KafkaProducer +import json + +class KafkaMessageProducer: + def __init__(self, bootstrap_servers='localhost:9092'): + self.producer = KafkaProducer( + bootstrap_servers=bootstrap_servers, + value_serializer=lambda v: json.dumps(v).encode('utf-8') + ) + + def send_message(self, topic, message): + future = self.producer.send(topic, message) + future.get(timeout=10) + print(f"Sent to {topic}: {message}") + + def close(self): + self.producer.close() + +producer = KafkaMessageProducer() +producer.send_message('user_actions', {'user_id': 123, 'action': 'login'}) +producer.close() +``` + +### Consumer + +``` +from kafka import KafkaConsumer +import json + +class KafkaMessageConsumer: + def __init__(self, bootstrap_servers='localhost:9092'): + self.consumer = KafkaConsumer( + bootstrap_servers=bootstrap_servers, + auto_offset_reset='earliest', + enable_auto_commit=True, + group_id='my-group', + value_deserializer=lambda x: json.loads(x.decode('utf-8')) + ) + + def consume_messages(self, topics): + self.consumer.subscribe(topics) + try: + for message in self.consumer: + print(f"Received from {message.topic}: {message.value}") + except KeyboardInterrupt: + print("Stopping consumer") + finally: + self.consumer.close() + +consumer = KafkaMessageConsumer() +consumer.consume_messages(['user_actions', 'logs']) +``` + +### Паттерны использования + +Work Queues — распределение задач между несколькими workers. Pub/Sub (Publish/Subscribe) — отправка сообщений нескольким получателям. Request/Reply — взаимодействие по схеме запрос-ответ. Message Filtering — фильтрация сообщений на основе правил. + +### Best Practices + +Всегда обрабатывайте исключения и предусматривайте механизмы повтора для надежной обработки сообщений. Настраивайте сохранение сообщений на диск для важных данных чтобы предотвратить потерю информации. Используйте инструменты мониторинга для отслеживания состояния очередей и производительности системы. Настраивайте аутентификацию и шифрование для production-окружения чтобы обеспечить безопасность данных. Тестируйте обработчики сообщений в изоляции чтобы гарантировать корректную работу в различных сценариях. + + + + ## Что такое RPC Удалённый вызов процедур, реже Вызов удалённых процедур (от англ. Remote Procedure Call, RPC) — класс технологий, позволяющих компьютерным программам вызывать функции или процедуры в другом адресном пространстве (на удалённых компьютерах, либо в независимой сторонней системе на том же устройстве). Обычно реализация RPC-технологии включает в себя два компонента: сетевой протокол для обмена в режиме клиент-сервер и язык сериализации объектов (или структур, для необъектных RPC). На транспортном уровне RPC используют в основном протоколы TCP и UDP, однако, некоторые построены на основе HTTP (что нарушает архитектуру ISO/OSI, так как HTTP — изначально не транспортный протокол).