Merge branch 'main' into dozorova_alena_lab_5

This commit is contained in:
Zara28 2024-10-16 14:31:12 +04:00
commit 13b5dfc707
132 changed files with 4845 additions and 0 deletions

2
bogdanov_dmitry_lab_2/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
data/
result/

View File

@ -0,0 +1,41 @@
# Лабораторная работа №2
## Богданов Дмитрий ПИбд-42
### Для выполнения была проделана следующая работа:
Были написаны и развернуты 3 сервиса: генератор файлов, 2 приложения для работы с этими файлами по вариантам 2 и 1 соответственно:
Вариант 2 (для первого приложения):
```Формирует файл /var/result/data.txt из первых строк всех файлов каталога /var/data.```
Вариант 1 (для второго приложения):
```Ищет набольшее число из файла /var/data/data.txt и сохраняет его вторую степень в /var/result/result.txt.```
Приложения работают совместно, используя общий монтированный том для записи и получения информации:
```
volumes:
- ./data:/var/data
```
```
volumes:
- ./data:/var/data
- ./result:/var/result
```
```
volumes:
- ./result:/var/result
```
### Запуск лабораторной:
Необходимо перейти в папку с файлом docker-compose.yaml и ввести следующую команду:
```
docker compose up --build
```
Сервис генератора сгенерирует папки data и result, где будут сгенерированы входные файлы и файл-результат их обработки соответственно.
## Видео с результатом запуска:
Видео-демонстрацию работы можно посмотреть по данной [ссылке](https://drive.google.com/file/d/1CmVZjJuMStqNFFKbsMLjw4ihTiMnR7it/view).

View File

@ -0,0 +1,7 @@
FROM python:latest
WORKDIR /app
COPY app.py /app/
CMD ["python", "app.py"]

View File

@ -0,0 +1,30 @@
import os
# Вариант 2 - Формирует файл /var/result/data.txt из первых строк всех файлов каталога /var/data.
def solve(dir_files, dir_result, filename_result):
# Получаем список файлов в директории
filenames = os.listdir(dir_files)
result = ''
# Проходим через каждый файл
for filename in filenames:
filepath = os.path.join(dir_files, filename)
file = open(filepath, "r")
# Читаем первую строку, добавляем к результату
result += f"{file.readline()}"
file.close()
# Если директории для сохранения результата нет - создаём
if not os.path.exists(dir_result):
os.makedirs(dir_result)
# Если директория с результатом не пустая - завершаем работу
if os.listdir(dir_result):
return
# Пишем результат в файл
filepath_result = os.path.join(dir_result, filename_result)
result_file = open(filepath_result, "w")
result_file.write(result)
print(f"Результат записан в файл {filepath_result}")
result_file.close()
if __name__ == "__main__":
solve('/var/data', '/var/result', 'data.txt')

View File

@ -0,0 +1,7 @@
FROM python:latest
WORKDIR /app
COPY app.py /app/
CMD ["python", "app.py"]

View File

@ -0,0 +1,21 @@
import os
# Вариант 1 - Ищет набольшее число из файла /var/data/data.txt и сохраняет его вторую степень в /var/result/result.txt.
def solve(dir_input, dir_result, filename_result):
file_input = open(os.path.join(dir_input, 'data.txt'))
# Считываем все числа из файла
inputs = [int(line) for line in file_input.readlines()]
if inputs:
# Максимальное число
max_num = max(inputs)
print(f"Наибольшее число: {max_num}")
# Возводим во 2 степень
result = max(inputs) ** 2
file_result = open(os.path.join(dir_result, filename_result), "w")
# Пишем результат в файл
file_result.write(str(result))
print(f"Получен результат {result}")
file_result.close()
if __name__ == "__main__":
solve("/var/result", '/var/result', 'result.txt')

View File

@ -0,0 +1,7 @@
FROM python:latest
WORKDIR /app
COPY generator.py /app/
CMD ["python", "generate_files.py"]

View File

@ -0,0 +1,30 @@
import os
import random as rnd
import string
# Генератор названий файлов
def generate_filename(l):
return ''.join(rnd.choices(string.ascii_lowercase + string.digits, k=l)) + '.txt'
def generate_files(dir, num_files, num_lines):
# Если директории для сохранения файлов нет - создаём
if not os.path.exists(dir):
os.makedirs(dir)
# Если директория для сохранения файлов не пустая - завершаем работу
if os.listdir(dir):
return
# Создание файлов
for i in range(num_files):
filename = generate_filename(20)
filepath = os.path.join(dir, filename)
file = open(filepath, "w")
# Запись строк в файл
for j in range(num_lines):
file.write(f"{rnd.randint(-1000, 1000)}\n")
file.close()
if __name__ == "__main__":
generate_files('/var/data', 50, 50)

View File

@ -0,0 +1,27 @@
services:
# Генератор файлов
generator:
build:
context: ./app-generator # Путь к контексту (докер файл + скрипт)
volumes:
- ./data:/var/data # Папка контейнера : папка локальная
entrypoint: python generator.py # Точка входа
# Первое приложение
app1:
build:
context: ./app-1 # Путь к контексту
volumes:
- ./data:/var/data # Монтирование папок
- ./result:/var/result
depends_on:
- generator # Указываем, что запускается только после успешной работы сервиса generator
# Второе приложение, настройка аналогична сервисам выше
app2:
build:
context: ./app-2
volumes:
- ./result:/var/result
depends_on:
- app1

View File

@ -0,0 +1,50 @@
# Отчет. Лабораторная работа 4
## Описание
В ходе лабораторной работы были изучены главы туториала о работе с RabbitMQ. Результат выполнения заданий каждой главы
отражен на скриншотах в папке /images:
- Tutorial-Task1.png
![Tutorial-Task1](images/Tutorial-Task1.png)
- Tutorial-Task2.png
![Tutorial-Task2](images/Tutorial-Task2.png)
- Tutorial-Task3.png
![Tutorial-Task3](images/Tutorial-Task3.png)
Задание из 3-ей главы туториала было расширено условиями, которые были поставлены в задании к данной лабораторной работе.
Для демонстрации работы сервисов посредством ассинхронного общения через брокер сообщений RabbitMQ была выбрана
предметная область "Обработка заказов".
Сервис-издатель "Publisher" публикует в очередь сообщений событие поступления заказа с некоторым номером.
Сервисы-подписчики обрабатывают сообщения о заказах, при этом подписчики обрабатывают сообщение по-разному. Один вид
подписчика обрабатывает с задержкой в несколько секунд, другой - "мгновенно", они получают одни и те жа сообщения,
но соединены с разными очередями.
В качестве эксперимента изначально были запущены по одному экземпляру каждого вида.
На изображении Consumer2.png представлена работа мгновенно обрабатывающего подписчика. Он справляется с нагрузкой,
так как размер очереди не растет.
![Consumer 2](images/Consumer2.png)
На изображении Consumer1.png представлена работа подписчика, обрабатывающего сообщения с задержкой. Как мы видим,
в очереди накапливаются сообщения в состоянии 'Ready' - эти сообщения готовы для того, чтобы быть доставленными подписчикам.
Сервис не справляется с нагрузкой, так как отправляются сообщения быстрее, чем обрабатываются.
![Consumer 1](images/Consumer1.png)
Для того, чтобы обеспечить равную скорость отправки и обработки, увеличиваем количество экземпляров-подписчиков данного типа до трех.
На изображении видно, что теперь длина очереди не растет и система справляется с поступающими сообщениями. Также скорость "publish" и
"consumer ack" стали равны.
![Consumer 1](images/Consumer1-scaling.png)
## Как запустить
Для того, чтобы запустить сервисы, необходимо выполнить следующие действия:
1. Установить и запустить Docker Engine или Docker Desktop
2. Через консоль перейти в папку, в которой расположен файл docker-compose.yml
3. Выполнить команду для запуска брокера сообщений rabbitmq:
```
docker compose up rabbit -d
```
4. Выполнить команду для запуска остальных контейнеров:
```
docker compose up -d
```
Такой порядок запуска важен для того, чтобы брокер сообщений успел полностью запуститься
и произвести действия для того, чтобы быть готовым принимать соединения от сервисов. Потому что указания depends_on не хватает
для отслеживания завершения всех необходимых подготовительных процессов брокера.
## Видео-отчет
Работоспособность лабораторной работы можно оценить в следующем [видео](https://disk.yandex.ru/i/G0vsfp7vwazYHw).

View File

@ -0,0 +1,23 @@
# Используем образ Maven для сборки
FROM maven:3.8-eclipse-temurin-21-alpine AS build
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем остальные исходные файлы
COPY pom.xml .
COPY src src
# Собираем весь проект
RUN mvn clean package -DskipTests
RUN mvn dependency:copy-dependencies
# Используем официальный образ JDK для запуска собранного jar-файла
FROM eclipse-temurin:21-jdk-alpine
# Копируем jar-файл из предыдущего этапа
COPY --from=build /app/target/*.jar /app.jar
COPY --from=build /app/target/dependency /
# Указываем команду для запуска приложения
CMD ["java", "-jar", "app.jar"]

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.somecompany</groupId>
<artifactId>consumer-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.22.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,15 @@
package ru.somecompany;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import ru.somecompany.config.property.RabbitProperties;
@SpringBootApplication
@ConfigurationPropertiesScan(basePackageClasses = RabbitProperties.class)
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

View File

@ -0,0 +1,45 @@
package ru.somecompany.config;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.somecompany.config.property.RabbitProperties;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@Configuration
@RequiredArgsConstructor
public class ConnectionFactoryConfig {
private final RabbitProperties rabbitProperties;
@Bean
public ConnectionFactory connectionFactory() {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(rabbitProperties.getHost());
factory.setPort(rabbitProperties.getPort());
return factory;
}
@Bean
public Connection connection(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
return connectionFactory.newConnection();
}
@Bean
public Channel channel(Connection connection) throws IOException {
var exchange = rabbitProperties.getExchange();
var queue = rabbitProperties.getQueue();
var channel = connection.createChannel();
channel.exchangeDeclare(exchange, BuiltinExchangeType.FANOUT);
channel.queueDeclare(queue, true, false, true, null);
channel.queueBind(queue, exchange, "");
return channel;
}
}

View File

@ -0,0 +1,19 @@
package ru.somecompany.config.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "app.rabbit-properties")
public class RabbitProperties {
private String host;
private Integer port;
private Integer delay;
private String queue;
private String exchange;
}

View File

@ -0,0 +1,76 @@
package ru.somecompany.consumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.somecompany.config.property.RabbitProperties;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Component
@RequiredArgsConstructor
public class Consumer {
private final RabbitProperties rabbitProperties;
private final Connection connection;
private final Channel channel;
@PostConstruct
public void consume() {
try {
channel.basicQos(1);
channel.basicConsume(rabbitProperties.getQueue(), false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
long deliveryTag = envelope.getDeliveryTag();
String message = new String(body, StandardCharsets.UTF_8);
System.out.println(" [x] Received '" + message + "'");
var delay = rabbitProperties.getDelay();
try {
doWork(delay);
} finally {
System.out.println(" [x] Processed '" + message + "'");
channel.basicAck(deliveryTag, false);
}
}
});
} catch (Exception exception) {
log.error("Error while set up connection with rabbit", exception);
}
}
private static void doWork(Integer delay) {
if (delay > 0) {
try {
Thread.sleep(delay);
} catch (InterruptedException _ignored) {
Thread.currentThread().interrupt();
}
}
}
@PreDestroy
public void cleanUp() throws Exception {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
}
}

View File

@ -0,0 +1,10 @@
server:
port: ${SERVER_PORT:8081}
app:
rabbit-properties:
host: ${RABBIT_HOST:localhost}
port: ${RABBIT_PORT:5672}
delay: ${PROCESS_DELAY:0}
queue: ${QUEUE_NAME:queue-1}
exchange: ${EXCHANGE_NAME:order-events}

View File

@ -0,0 +1,79 @@
services:
rabbit:
container_name: rabbit
image: rabbitmq:3-management
ports:
- "15672:15672"
- "5672:5672"
- "5671:5671"
networks:
- local
publisher:
build: ./publisher-app
container_name: publisher
depends_on:
- rabbit
environment:
RABBIT_HOST: rabbit
RABBIT_PORT: 5672
networks:
- local
consumer-1:
build: ./consumer-app
container_name: consumer-1
depends_on:
- rabbit
- publisher
environment:
RABBIT_HOST: rabbit
RABBIT_PORT: 5672
PROCESS_DELAY: 3000
QUEUE_NAME: queue1
EXCHANGE_NAME: order-events
networks:
- local
consumer-2:
build: ./consumer-app
container_name: consumer-2
depends_on:
- rabbit
- publisher
environment:
RABBIT_HOST: rabbit
RABBIT_PORT: 5672
PROCESS_DELAY: 0
QUEUE_NAME: queue2
EXCHANGE_NAME: order-events
networks:
- local
consumer-12:
build: ./consumer-app
container_name: consumer-12
depends_on:
- rabbit
- publisher
environment:
RABBIT_HOST: rabbit
RABBIT_PORT: 5672
PROCESS_DELAY: 3000
QUEUE_NAME: queue1
EXCHANGE_NAME: order-events
networks:
- local
consumer-13:
build: ./consumer-app
container_name: consumer-13
depends_on:
- rabbit
- publisher
environment:
RABBIT_HOST: rabbit
RABBIT_PORT: 5672
PROCESS_DELAY: 3000
QUEUE_NAME: queue1
EXCHANGE_NAME: order-events
networks:
- local
networks:
local:

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.somecompany</groupId>
<artifactId>helloworld-tutorial</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.22.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,28 @@
package ru.somecompany;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
public class Main {
private static final String QUEUE_NAME = "hello-world";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try(Connection connection = factory.newConnection();
Channel channel = connection.createChannel();) {
var sender = new Sender(channel);
var receiver = new Receiver(channel);
} catch (Exception e) {
System.out.println(" [*] Error in Hello-World");
}
}
}

View File

@ -0,0 +1,20 @@
package ru.somecompany;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
public class Receiver {
private static final String QUEUE_NAME = "hello-world";
public Receiver(Channel channel) throws IOException {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}

View File

@ -0,0 +1,18 @@
package ru.somecompany;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class Sender {
private static final String QUEUE_NAME = "hello-world";
public Sender(Channel channel) throws IOException {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}

View File

@ -0,0 +1,8 @@
# Root logger option
log4j.rootLogger=INFO, stdout
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 KiB

View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -0,0 +1,21 @@
# Используем образ Maven для сборки
FROM maven:3.8-eclipse-temurin-21-alpine AS build
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем остальные исходные файлы
COPY pom.xml .
COPY src src
# Собираем весь проект
RUN mvn clean package -DskipTests
# Используем официальный образ JDK для запуска собранного jar-файла
FROM eclipse-temurin:21-jdk-alpine
# Копируем jar-файл из предыдущего этапа
COPY --from=build /app/target/*.jar /app.jar
# Указываем команду для запуска приложения
CMD ["java", "-jar", "app.jar"]

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.somecompany</groupId>
<artifactId>publisher-app</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.22.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,17 @@
package ru.somecompany;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import ru.somecompany.config.property.RabbitProperties;
@EnableScheduling
@SpringBootApplication
@ConfigurationPropertiesScan(basePackageClasses = RabbitProperties.class)
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

View File

@ -0,0 +1,40 @@
package ru.somecompany.config;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.somecompany.config.property.RabbitProperties;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@Configuration
@RequiredArgsConstructor
public class ConnectionFactoryConfig {
private final RabbitProperties rabbitProperties;
@Bean
public ConnectionFactory connectionFactory() {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(rabbitProperties.getHost());
factory.setPort(rabbitProperties.getPort());
return factory;
}
@Bean
public Connection connection(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
return connectionFactory.newConnection();
}
@Bean
public Channel channel(Connection connection) throws IOException {
var channel = connection.createChannel();
channel.exchangeDeclare(rabbitProperties.getExchange(), BuiltinExchangeType.FANOUT);
return channel;
}
}

View File

@ -0,0 +1,15 @@
package ru.somecompany.config.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "app.rabbit-properties")
public class RabbitProperties {
private String host;
private Integer port;
private String exchange;
}

View File

@ -0,0 +1,41 @@
package ru.somecompany.scheduler;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
@Service
@RequiredArgsConstructor
public class SenderScheduler {
private static final String EXCHANGE_NAME = "order-events";
private static final String MESSAGE = "Поступил заказ №%d";
private Integer index = 0;
private final ConnectionFactory connectionFactory;
private final Connection connection;
private final Channel channel;
@Scheduled(cron = "*/1 * * * * *")
public void sendMessage() {
try {
var message = String.format(MESSAGE, index);
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
index++;
System.out.println(" [x] Sent '" + message + "'");
} catch (IOException e) {
System.out.println(" [x] Error while send message");
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,8 @@
server:
port: ${SERVER_PORT:8080}
app:
rabbit-properties:
host: ${RABBIT_HOST:localhost}
port: ${RABBIT_PORT:5672}
exchange: ${EXCHANGE_NAME:order-events}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.somecompany</groupId>
<artifactId>workqueue-tutorial</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.22.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,23 @@
package ru.somecompany;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Main {
public static final String QUEUE_NAME = "task_queue";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try(Connection connection = factory.newConnection();
Channel channel = connection.createChannel();) {
var sender = new Sender(channel);
sender.send("Work Queue message");
var receiver = new Receiver(channel);
} catch (Exception e) {
System.out.println(" [*] Error in Work-Queue: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,44 @@
package ru.somecompany;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static ru.somecompany.Main.QUEUE_NAME;
public class Receiver {
public Receiver(Channel channel) throws IOException {
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.basicQos(1);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println(" [x] Received '" + message + "'");
try {
doWork(message);
} finally {
System.out.println(" [x] Done");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
}
private static void doWork(String task) {
for (char ch : task.toCharArray()) {
if (ch == '.') {
try {
Thread.sleep(1000);
} catch (InterruptedException _ignored) {
Thread.currentThread().interrupt();
}
}
}
}
}

View File

@ -0,0 +1,29 @@
package ru.somecompany;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import static ru.somecompany.Main.QUEUE_NAME;
public class Sender {
private Channel channel;
public Sender(Channel channel) throws IOException {
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
this.channel = channel;
}
public void send(String msg) throws IOException {
String message = String.join(" ", msg);
channel.basicPublish("", QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
}
}

View File

@ -0,0 +1,8 @@
# Root logger option
log4j.rootLogger=INFO, stdout
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35004.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumerDelay", "ConsumerDelay.csproj", "{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E7AED20-0868-42FE-9C39-581BC9D2BB22}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,22 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ConsumerDelay.csproj", "."]
RUN dotnet restore "./ConsumerDelay.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "./ConsumerDelay.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./ConsumerDelay.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConsumerDelay.dll"]

View File

@ -0,0 +1,24 @@
using ConsumerDelay;
var rabbitHost = Environment.GetEnvironmentVariable("RABBIT_HOST") ?? "localhost";
var rabbitUsername = Environment.GetEnvironmentVariable("RABBIT_USERNAME") ?? "user";
var rabbitPassword = Environment.GetEnvironmentVariable("RABBIT_PASSWORD") ?? "password";
var rabbitExchange = Environment.GetEnvironmentVariable("RABBIT_EXCHANGE") ?? "ReportIn";
var rabbitQueue = Environment.GetEnvironmentVariable("RABBIT_QUEUE") ?? "Second";
Thread.Sleep(2000);
var receiver = new Receiver(rabbitHost, rabbitUsername, rabbitPassword);
receiver.SubscribeTo(rabbitExchange, (message) =>
{
var rnd = new Random();
Console.WriteLine($"Пришло сообщение: {message}");
Thread.Sleep(rnd.Next(2000, 3000));
Console.WriteLine($"Обработка сообщения завершена");
},
rabbitQueue);
while (true) ;

View File

@ -0,0 +1,82 @@
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
namespace ConsumerDelay
{
public class Receiver : IDisposable
{
private readonly ConnectionFactory _connectionFactory;
private readonly IConnection _connection;
private readonly IModel _channel;
public Dictionary<string, HashSet<string>> Queues { get; private set; } = new();
public Receiver(string brockerHost, string brockerUsername, string brockerPassword)
{
_connectionFactory = new ConnectionFactory() { HostName = brockerHost, UserName = brockerUsername, Password = brockerPassword };
_connection = _connectionFactory.CreateConnection();
_channel = _connection.CreateModel();
}
public bool SubscribeTo(string exchange, Action<string> handler, string? queueName = null)
{
try
{
if (!Queues.ContainsKey(exchange))
{
_channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
Queues.Add(exchange, new HashSet<string>());
}
if (queueName != null)
_channel.QueueDeclare(queue: queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
queueName = queueName ?? _channel.QueueDeclare().QueueName;
_channel.QueueBind(queue: queueName,
exchange: exchange,
routingKey: string.Empty);
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += (model, ea) =>
{
try
{
var message = Encoding.UTF8.GetString(ea.Body.ToArray());
handler(message);
_channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
};
_channel.BasicConsume(queue: queueName,
autoAck: false,
consumer: consumer);
Queues[exchange].Add(queueName);
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return false;
}
~Receiver() => Dispose();
public void Dispose()
{
_connection.Dispose();
_channel.Dispose();
}
}
}

View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35004.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumerSimple", "ConsumerSimple.csproj", "{ACA8DE52-E29E-41BA-B3DA-213AF316685E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ACA8DE52-E29E-41BA-B3DA-213AF316685E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACA8DE52-E29E-41BA-B3DA-213AF316685E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACA8DE52-E29E-41BA-B3DA-213AF316685E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACA8DE52-E29E-41BA-B3DA-213AF316685E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {73265D6C-436C-470E-AE8A-17047E6C2ECC}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,22 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ConsumerSimple.csproj", "."]
RUN dotnet restore "./ConsumerSimple.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "./ConsumerSimple.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./ConsumerSimple.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConsumerSimple.dll"]

View File

@ -0,0 +1,23 @@
using ConsumerSimple;
var rabbitHost = Environment.GetEnvironmentVariable("RABBIT_HOST") ?? "localhost";
var rabbitUsername = Environment.GetEnvironmentVariable("RABBIT_USERNAME") ?? "user";
var rabbitPassword = Environment.GetEnvironmentVariable("RABBIT_PASSWORD") ?? "password";
var rabbitExchange = Environment.GetEnvironmentVariable("RABBIT_EXCHANGE") ?? "ReportIn";
var rabbitQueue = Environment.GetEnvironmentVariable("RABBIT_QUEUE") ?? "First";
Thread.Sleep(2000);
var receiver = new Receiver(rabbitHost, rabbitUsername, rabbitPassword);
receiver.SubscribeTo(rabbitExchange, (message) =>
{
var rnd = new Random();
Console.WriteLine($"Пришло сообщение: {message}");
Console.WriteLine($"Сообщение обрабатывается мгновенно");
},
rabbitQueue);
while (true) ;

View File

@ -0,0 +1,10 @@
{
"profiles": {
"ConsumerSimple": {
"commandName": "Project"
},
"Container (Dockerfile)": {
"commandName": "Docker"
}
}
}

View File

@ -0,0 +1,82 @@
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
namespace ConsumerSimple
{
public class Receiver : IDisposable
{
private readonly ConnectionFactory _connectionFactory;
private readonly IConnection _connection;
private readonly IModel _channel;
public Dictionary<string, HashSet<string>> Queues { get; private set; } = new();
public Receiver(string brockerHost, string brockerUsername, string brockerPassword)
{
_connectionFactory = new ConnectionFactory() { HostName = brockerHost, UserName = brockerUsername, Password = brockerPassword };
_connection = _connectionFactory.CreateConnection();
_channel = _connection.CreateModel();
}
public bool SubscribeTo(string exchange, Action<string> handler, string? queueName = null)
{
try
{
if (!Queues.ContainsKey(exchange))
{
_channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
Queues.Add(exchange, new HashSet<string>());
}
if (queueName != null)
_channel.QueueDeclare(queue: queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
queueName = queueName ?? _channel.QueueDeclare().QueueName;
_channel.QueueBind(queue: queueName,
exchange: exchange,
routingKey: string.Empty);
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += (model, ea) =>
{
try
{
var message = Encoding.UTF8.GetString(ea.Body.ToArray());
handler(message);
_channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
};
_channel.BasicConsume(queue: queueName,
autoAck: false,
consumer: consumer);
Queues[exchange].Add(queueName);
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return false;
}
~Receiver() => Dispose();
public void Dispose()
{
_connection.Dispose();
_channel.Dispose();
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>EmitLog_</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
using System.Text;
using RabbitMQ.Client;
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
var message = GetMessage(args);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "logs",
routingKey: string.Empty,
basicProperties: null,
body: body);
Console.WriteLine($" [x] Sent {message}");
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
static string GetMessage(string[] args)
{
return ((args.Length > 0) ? string.Join(" ", args) : "info: Hello World!");
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,32 @@
using System.Text;
using RabbitMQ.Client;
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
channel.QueueDeclare(queue: "task_queue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
var message = GetMessage(args);
var body = Encoding.UTF8.GetBytes(message);
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
channel.BasicPublish(exchange: string.Empty,
routingKey: "task_queue",
basicProperties: properties,
body: body);
Console.WriteLine($" [x] Sent {message}");
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
static string GetMessage(string[] args)
{
return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!");
}

View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Publisher.csproj", "."]
RUN dotnet restore "./Publisher.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "./Publisher.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Publisher.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Publisher.dll"]

View File

@ -0,0 +1,40 @@
using Publisher;
using System.Text;
var rabbitHost = Environment.GetEnvironmentVariable("RABBIT_HOST") ?? "localhost";
var rabbitUsername = Environment.GetEnvironmentVariable("RABBIT_USERNAME") ?? "user";
var rabbitPassword = Environment.GetEnvironmentVariable("RABBIT_PASSWORD") ?? "password";
var rabbitExchange = Environment.GetEnvironmentVariable("RABBIT_EXCHANGE") ?? "ReportIn";
var sender = new Sender(rabbitHost, rabbitUsername, rabbitPassword);
sender.AddExcange(rabbitExchange);
var rnd = new Random();
while (true)
{
StringBuilder sb = new();
var type = rnd.Next();
switch (type%2)
{
case 0:
{
sb.Append($"Был запрошен отчет о данных под номером {rnd.Next(1000)}");
break;
}
case 1:
{
sb.Append($"Был запрошен отчет об ошибках под номером {rnd.Next(1000)}");
break;
}
}
var text = sb.ToString();
Console.WriteLine($"Было опубликовано сообщение: {text}");
sender.PublishToExchange(rabbitExchange, text);
await Task.Delay(1000);
}

View File

@ -0,0 +1,10 @@
{
"profiles": {
"Publisher": {
"commandName": "Project"
},
"Container (Dockerfile)": {
"commandName": "Docker"
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35004.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Publisher", "Publisher.csproj", "{C23890FA-A4DD-4E5B-897F-37210C2F60CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C23890FA-A4DD-4E5B-897F-37210C2F60CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C23890FA-A4DD-4E5B-897F-37210C2F60CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C23890FA-A4DD-4E5B-897F-37210C2F60CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C23890FA-A4DD-4E5B-897F-37210C2F60CE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3BB20EB3-DE49-46CE-8C7A-D956E3DE90BC}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,69 @@
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Publisher
{
public class Sender : IDisposable
{
private readonly ConnectionFactory _connectionFactory;
private readonly IConnection _connection;
private readonly IModel _channel;
public HashSet<string> Exchanges { get; private set; } = new HashSet<string>();
public Sender(string brockerHost, string brockerUsername, string brockerPassword)
{
_connectionFactory = new ConnectionFactory() { HostName = brockerHost, UserName = brockerUsername, Password = brockerPassword };
_connection = _connectionFactory.CreateConnection();
_channel = _connection.CreateModel();
}
public bool AddExcange(string exchange, string exchangeType = ExchangeType.Fanout)
{
try
{
_channel.ExchangeDeclare(exchange, exchangeType);
Exchanges.Add(exchange);
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return false;
}
public bool PublishToExchange(string exchange, string message)
{
try
{
if (!Exchanges.Contains(exchange))
return false;
var messageBody = Encoding.UTF8.GetBytes(message);
_channel.BasicPublish(exchange: exchange,
routingKey: string.Empty,
basicProperties: null,
body: messageBody);
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return false;
}
~Sender() => Dispose();
public void Dispose()
{
_connection.Dispose();
_channel.Dispose();
}
}
}

View File

@ -0,0 +1,39 @@
# Лабораторная работа 4
В рамках данной работы были реализованы несколько проектов, работающих с RabbitMQ.
## tutorial
Для каждого урока были созданы консольные проекты
### "Hello World!"
![Task 1](image-4.png)
### Work Queues
![Task 2](image-5.png)
### Publish/Subscribe
![Task 3](image-6.png)
## Описание
В качестве предметной области была выбрана система запросов отчета двух видов: отчета и ошибок.
## Запуск
Для запуска лабораторной работы необходимо иметь запущенный Docker на устройстве.
Необходимо перейти в папку, где располагается данный файл. Далее открыть терминал и ввести команду:
```
docker compose up -d --build
```
Порты для RabbitMQ были 8081 (для UI) и 5672.
## Анализ
Первоначальный вариант запуска предполагает, что имеется всего 2 потребителя:
1. Тратит на обработку сообщения 2-3 секунды
2. Тратит на обработку сообщения крайне малое время
Задержка при обработке понижает пропускную способность обработчика, что вызывает переполнение очереди. Это подтверждается скринами.
<br/>
![alt text](image.png)
<br/>
![alt text](image-1.png)
<br/>
Теперь запустим несколько обычных обработчиков. Очередь не переполнена постоянно, а периодически, соответственно обработчики вполне справляются с потоком сообщений и увеличение их количества позволит в принципе избавиться от переполнения
<br/>
![alt text](image-2.png)
<br/>
![alt text](image-3.png)
## Видеодемонстрация
Демонстрация: (https://drive.google.com/file/d/16gJMGbMKSFZ_I5gCzuDekpAqUrhbpFRA/view?usp=sharing) Стоит обратить внимание на то, что настройки docker compose файла не гарантируют верный порядок подъема контейнеров, из-за чего некоторые контейнеры пришлось перезапустить.

View File

@ -0,0 +1,29 @@
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
channel.QueueDeclare(queue: "hello",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
Console.WriteLine(" [*] Waiting for messages.");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] Received {message}");
};
channel.BasicConsume(queue: "hello",
autoAck: true,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,31 @@
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
// declare a server-named queue
var queueName = channel.QueueDeclare().QueueName;
channel.QueueBind(queue: queueName,
exchange: "logs",
routingKey: string.Empty);
Console.WriteLine(" [*] Waiting for logs.");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
byte[] body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] {message}");
};
channel.BasicConsume(queue: queueName,
autoAck: true,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
using System.Text;
using RabbitMQ.Client;
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
channel.QueueDeclare(queue: "hello",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
const string message = "Hello World!";
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: string.Empty,
routingKey: "hello",
basicProperties: null,
body: body);
Console.WriteLine($" [x] Sent {message}");
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,39 @@
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
channel.QueueDeclare(queue: "task_queue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
Console.WriteLine(" [*] Waiting for messages.");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
byte[] body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] Received {message}");
int dots = message.Split('.').Length - 1;
Thread.Sleep(dots * 1000);
Console.WriteLine(" [x] Done");
// here channel could also be accessed as ((EventingBasicConsumer)sender).Model
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};
channel.BasicConsume(queue: "task_queue",
autoAck: false,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,59 @@
services:
rabbit:
image: rabbitmq:3.10.7-management
restart: always
ports:
- 5672:5672
- 8081:15672
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: admin
publisher:
build: ./Publisher/
restart: always
depends_on:
- rabbit
environment:
RABBIT_HOST: rabbit
RABBIT_USERNAME: admin
RABBIT_PASSWORD: admin
RABBIT_EXCHANGE: 'ReportIn'
concumer1:
build: ./ConsumerSimple/
restart: always
depends_on:
- rabbit
- publisher
environment:
RABBIT_HOST: rabbit
RABBIT_USERNAME: admin
RABBIT_PASSWORD: admin
RABBIT_EXCHANGE: 'ReportIn'
RABBIT_QUEUE: 'First'
concumer2:
build: ./ConsumerSimple/
restart: always
depends_on:
- rabbit
- publisher
environment:
RABBIT_HOST: rabbit
RABBIT_USERNAME: admin
RABBIT_PASSWORD: admin
RABBIT_EXCHANGE: 'ReportIn'
RABBIT_QUEUE: 'Second'
# concumer2:
# build: ./ConsumerDelay/
# restart: always
# depends_on:
# - rabbit
# - publisher
# environment:
# RABBIT_HOST: rabbit
# RABBIT_USERNAME: admin
# RABBIT_PASSWORD: admin
# RABBIT_EXCHANGE: 'ReportIn'
# RABBIT_QUEUE: 'Second'

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -10,3 +10,29 @@
/dozorova_alena_lab_5/ConsoleApp1/.vs
/dozorova_alena_lab_5/ConsoleApp1/bin
/dozorova_alena_lab_5/ConsoleApp1/obj
/dozorova_alena_lab_3/PostService/.vs
/dozorova_alena_lab_3/WorkerService/.vs
/dozorova_alena_lab_4/Receive/bin
/dozorova_alena_lab_4/Receive/obj
/dozorova_alena_lab_4/Send/bin
/dozorova_alena_lab_4/Send/obj
/dozorova_alena_lab_4/EmitLog/.vs
/dozorova_alena_lab_4/EmitLog/obj
/dozorova_alena_lab_4/NewTask/.vs
/dozorova_alena_lab_4/NewTask/bin
/dozorova_alena_lab_4/NewTask/obj
/dozorova_alena_lab_4/ReceiveLogs/obj
/dozorova_alena_lab_4/Worker/.vs
/dozorova_alena_lab_4/Worker/bin
/dozorova_alena_lab_4/Worker/obj
/dozorova_alena_lab_4/EmitLog/bin
/dozorova_alena_lab_4/ReceiveLogs/.vs
/dozorova_alena_lab_4/ReceiveLogs/bin
/dozorova_alena_lab_4/ConsumerDelay/.vs
/dozorova_alena_lab_4/ConsumerDelay/obj
/dozorova_alena_lab_4/ConsumerDelay/Properties
/dozorova_alena_lab_4/ConsumerSimple/.vs
/dozorova_alena_lab_4/ConsumerSimple/obj
/dozorova_alena_lab_4/Publisher/.vs
/dozorova_alena_lab_4/Publisher/bin
/dozorova_alena_lab_4/Publisher/objs

5
kalyshev_yan_lab_2/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
data/
result/
result_first/
result_second/
datagen.py

View File

@ -0,0 +1,16 @@
#Берем базовый образ python
FROM python:3.12
#Устанавливаем рабочую директорию
WORKDIR /app
#Копирум код в рабочую директорию
COPY main.py .
#Задаем /var/data как монтируемый
VOLUME ["/var/data"]
#Задаем /var/result как монтируемый
VOLUME ["/var/result"]
#Задаем команду для выполнения программы
CMD ["python", "main.py"]

View File

@ -0,0 +1,79 @@
import os
import random
# Установите полный путь к каталогу, в котором нужно искать самый большой файл.
CATALOG_PATH = "/var/data"
# Полный путь до файла результата.
RESULT_FILE = "/var/result/data.txt"
def find_largest_file(directory):
"""Ищет самый большой по объему файл в заданном каталоге и его подкаталогах."""
largest_file = None
max_size = 0
for root, _, files in os.walk(directory):
for file in files:
# Полный путь к текущему файлу.
filepath = os.path.join(root, file)
try:
file_size = os.stat(filepath).st_size
if file_size > max_size:
max_size = file_size
largest_file = (filepath, file_size)
except OSError as e:
print(f"Ошибка при открытии файла '{filepath}': {e}")
return largest_file
def copy_file(src, dst):
"""Копирует содержимое файла src в файл dst."""
try:
with open(dst, "wb") as f_dst, open(src, "rb") as f_src:
while chunk := f_src.read(4096):
f_dst.write(chunk)
print(f"Файл '{src}' успешно скопирован в '{dst}'.")
except Exception as e:
print(f"Ошибка при копировании файла '{src}': {e}")
def main():
largest_file_path = find_largest_file(CATALOG_PATH)
if largest_file_path:
src_path, _ = largest_file_path
dst_path = RESULT_FILE
copy_file(src_path, dst_path)
else:
print("Нет файлов в каталоге.")
def generate_random_numbers(filename, count):
"""Функция генерирует случайные числа и записывает их в файл."""
with open(filename, "w") as f:
for _ in range(count):
num = random.randint(0, 1000)
f.write(str(num) + "\n")
print(f"Случайные числа успешно записаны в '{filename}'.")
if __name__ == "__main__":
generate_random_numbers("/var/data/data1.txt", 50)
generate_random_numbers("/var/data/data2.txt", 75)
generate_random_numbers("/var/data/data3.txt", 25)
print("Генерация файлов завершена.")
main()

View File

@ -0,0 +1,74 @@
# Калышев Ян ПИбд-42
## Описание
Этот проект предоставляет 2 контейнера с простыми python-скриптами такими, что результат первого является исходными данными для второго.
## 1. Варианты
Для первой программы был взят вариант:
**0**. Ищет в каталоге /var/data самый большой по объёму файл и перекладывает его в /var/result/data.txt.
Для второй программы был взят вариант:
**2**. Ищет наименьшее число из файла /var/data/data.txt и сохраняет его третью степень в /var/result/result.txt.
## 2. Структура Dockerfile
Оба Dockerfile почти одинаковые:
```Dockerfile
#Берем базовый образ python
FROM python:3.12
#Устанавливаем рабочую директорию
WORKDIR /app
#Копирум код в рабочую директорию
COPY main.py .
#Задаем /var/data как монтируемый
VOLUME ["/var/data"]
#Задаем /var/result как монтируемый
VOLUME ["/var/result"]
#Задаем команду для выполнения программы
CMD ["python", "main.py"]
```
## 3. Docker Compose
`docker-compose.yml`:
```yaml
services:
#Первый скрипт
first:
#Указание директории для сборки первого скрипта
build: ./FirstProgram/
#Монтирование 2 каталогов из хост системы
volumes:
- ./data:/var/data
- ./result_first:/var/result
#Второй скрипт
second:
#Указание директории для сборки второго скрипта
build: ./SecondProgram/
#Задание очередности запуска с помощью depends_on
depends_on:
- first
#Монтирование 2 каталогов из хост системы
volumes:
- ./result_first:/var/data
- ./result_second:/var/result
```
## 4. Запуск
Для запуска использовать команду `docker compose up --build`
Предварительно нужно создать папку `data`, в которой создать файлы, где каждая строка - это число.
Результат первого скрипта будет в папке `result_first`, результат второго скрипта будет в папке `result_second`.
## 7. Ссылка на видео
[Видео-отчёт Калышев Ян ПИбд-42](https://zyzf.space/s/65HRyTTXAzxpwBF)

View File

@ -0,0 +1,16 @@
#Берем базовый образ python
FROM python:3.12
#Устанавливаем рабочую директорию
WORKDIR /app
#Копирум код в рабочую директорию
COPY main.py .
#Задаем /var/data как монтируемый
VOLUME ["/var/data"]
#Задаем /var/result как монтируемый
VOLUME ["/var/result"]
#Задаем команду для выполнения программы
CMD ["python", "main.py"]

View File

@ -0,0 +1,60 @@
# Установите полный путь к файлу, из которого нужно читать числа.
DATA_FILE = "/var/data/data.txt"
# Полный путь до файла результата.
RESULT_FILE = "/var/result/result.txt"
def find_smallest_number(data_file):
"""Ищет наименьшее число в заданном файле."""
try:
with open(data_file, "r") as f:
numbers = [int(line.strip()) for line in f.readlines()]
smallest_num = min(numbers)
return smallest_num
except ValueError as e:
print(f"Ошибка при преобразовании строк в числа: {e}")
except Exception as e:
print(f"Ошибка чтения файла '{data_file}': {e}")
return None
def cube_number(num):
"""Возвращает куб заданного числа."""
return num**3
def write_result(result_file, result):
"""Записывает результат в файл."""
try:
with open(result_file, "w") as f:
f.write(str(result))
print(f"Результат '{result}' успешно записан в '{result_file}'.")
except Exception as e:
print(f"Ошибка записи результата в файл '{result_file}': {e}")
def main():
smallest_num = find_smallest_number(DATA_FILE)
if smallest_num is not None:
result = cube_number(smallest_num)
write_result(RESULT_FILE, result)
print(result)
else:
print("Нет чисел в файле.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,21 @@
services:
#Первый скрипт
first:
#Указание директории для сборки первого скрипта
build: ./FirstProgram/
#Монтирование 2 каталогов из хост системы
volumes:
- ./data:/var/data
- ./result_first:/var/result
#Второй скрипт
second:
#Указание директории для сборки второго скрипта
build: ./SecondProgram/
#Задание очередности запуска с помощью depends_on
depends_on:
- first
#Монтирование 2 каталогов из хост системы
volumes:
- ./result_first:/var/data
- ./result_second:/var/result

484
kuzarin_maxim_lab_5/.gitignore vendored Normal file
View File

@ -0,0 +1,484 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from `dotnet new gitignore`
# dotenv files
.env
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
.idea
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# Vim temporary swap files
*.swp

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34916.146
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixMultiplication", "MatrixMultiplication\MatrixMultiplication.csproj", "{EF2DB216-9BCD-4FEE-B9C8-4262A5B7CC30}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EF2DB216-9BCD-4FEE-B9C8-4262A5B7CC30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF2DB216-9BCD-4FEE-B9C8-4262A5B7CC30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF2DB216-9BCD-4FEE-B9C8-4262A5B7CC30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF2DB216-9BCD-4FEE-B9C8-4262A5B7CC30}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {619485C2-FE35-496B-9E25-4F5465D0C1A1}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,117 @@

using System.Diagnostics;
internal class Program
{
private static string[] _resTableColumnsNames = new string[] { "m*m", "1 t", "2 t", "3 t", "4 t", "5 t", "6 t", "7 t", "8 t" };
private static void Main(string[] args)
{
Console.WriteLine(string.Join("\t", _resTableColumnsNames));
for (int i = 10; i < 10000; i *= 2)
{
var a = CreateMatrix(i, i);
var b = CreateMatrix(i, i);
List<long> times = new() { i };
for (int j = 1; j <= 8; j++)
{
var sw = new Stopwatch();
sw.Start();
MultiplyMatrix(a, b, j);
sw.Stop();
times.Add(sw.ElapsedMilliseconds);
}
Console.WriteLine(string.Join("\t", times));
}
}
/// <summary>
/// Создаём матрицу случайных элементов
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
private static int[,] CreateMatrix(int x, int y)
{
var rnd = new Random();
var res = new int[y, x];
for (int i = 0; i < y; i++)
{
for (int j = 0; j < x; j++)
{
res[i, j] = rnd.Next(0, 100);
}
}
return res;
}
/// <summary>
/// Вывести матрицу. Использовался при отладке
/// </summary>
/// <param name="mx"></param>
private static void PrintMatrix(int[,] mx)
{
for (int i = 0; i < mx.GetLength(0); i++)
{
for (int j = 0; j < mx.GetLength(1); j++)
{
Console.Write($"{mx[i, j].ToString("000")}\t");
}
Console.WriteLine();
}
}
/// <summary>
/// Непосредственно умножение матриц
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="maxTask"></param>
/// <returns></returns>
private static int[,] MultiplyMatrix(int[,] a, int[,] b, int maxTask)
{
int[,] res = new int[a.GetLength(0), b.GetLength(1)];
var semaphore = new SemaphoreSlim(maxTask, maxTask);
for (int i = 0; i < a.GetLength(0); i++)
{
for (int j = 0; j < b.GetLength(1); j++)
{
semaphore.Wait();
int ci = i;
int cj = j;
_ = Task.Run(() =>
{
try
{
res[ci, cj] = CalculateElement(a, b, ci, cj);
}
finally
{
semaphore.Release();
}
});
}
}
semaphore.Wait(maxTask);
return res;
}
/// <summary>
/// Вычисление значение одного элемента
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="i"></param>
/// <param name="j"></param>
/// <returns></returns>
private static int CalculateElement(int[,] a, int[,] b, int i, int j)
=> Enumerable.Range(0, a.GetLength(1)).Sum(k => a[i, k] * b[k, j]);
}

View File

@ -0,0 +1,20 @@
# Лабораторная работа 5
Данная работа посвящена параллельному умножению больших матриц (чтобы это был побыстрее)
## Описание
Основной принцип умножения матриц: строки умножаются на столбцы, таким образом получаются элементы. И так как значения в строках/столбцах не меняется по ходу алгоритма, его достаточно легко можно распаралелить.
## Запуск
Приложение представлет собой консольную программу на C#, которую можно скомпилировать и затем запустит exe файл. В результате в окне консоли появится таблица сравнения времени умножения 2-х квадратных матриц (размер указан в первой колонке) с использованием разного числа потоков(от 1 до 8). Чем больше размер, тем дольше будет идти расчёт.
## Результаты
Результаты одного из запусков программы представлены ниже.
<br/>
![Resout](Images/Resouts.png)
<br/>
Как можно заметить, на небольших размерах распаралеливание не приносит никакого выигрыша, а иногда даже удлиняет время работы. Это связана с тем, что работы с потоками (запуск, получение результата и т. п.) - требует дополнительного времени, а из-за малого числа операций в целом, получаем не прирост производительности, а её падение.
Противоположно обстоят дела в случае с большими матрицами. Так как там расчётов значительно больше, распаралеливание начинает значительно ускорять вычисление результата. Причём, часто можно видеть, что, например, увеличения числа потоков с 1 до 2 (в 2 раза) сокращает время в 2 раза. Но чем больше потоков, тем прирост меньше (так как вновь вступают в силу факторы, описанные ранее). И прирост производительности пусть и есть, но он очень небольшой.
## Видеодемонстрация
Был записан видеоролик, демонстрирующий процесс запуска и работы системы. Он расположен по [адресу](https://drive.google.com/file/d/1dhA3JUAhhpDUh32xVW7jMuW1BKDX02eV/view?usp=sharing)

Some files were not shown because too many files have changed in this diff Show More