Add week 8 materials
This commit is contained in:
1 parent
a987b82834
commit
0bf71ed374
38 files changed
+2908
No files matched your search
Binary file not shown.
@@ -0,0 +1,10 @@
|
|||||||
|
## 8. Масштабирование, балансировка нагрузки и шардинг.
|
||||||
|
|
||||||
|
### Лекция
|
||||||
|
|
||||||
|
- [Презентация](08-scaling.pdf)
|
||||||
|
- См. также материалы в конце
|
||||||
|
|
||||||
|
### Семинар
|
||||||
|
|
||||||
|
- [Материалы семинара](seminar/readme.md)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 362 KiB |
@@ -0,0 +1,30 @@
|
|||||||
|
services:
|
||||||
|
kv1:
|
||||||
|
build:
|
||||||
|
context: ./storage
|
||||||
|
ports:
|
||||||
|
- 5001:5000
|
||||||
|
command: -n kv1
|
||||||
|
|
||||||
|
kv2:
|
||||||
|
build:
|
||||||
|
context: ./storage
|
||||||
|
ports:
|
||||||
|
- 5002:5000
|
||||||
|
command: -n kv2
|
||||||
|
|
||||||
|
kv3:
|
||||||
|
build:
|
||||||
|
context: ./storage
|
||||||
|
ports:
|
||||||
|
- 5003:5000
|
||||||
|
command: -n kv3
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
build:
|
||||||
|
context: ./proxy
|
||||||
|
ports:
|
||||||
|
- 5005:5000
|
||||||
|
command: -c compose_config.yml
|
||||||
|
volumes:
|
||||||
|
- ./proxy/compose_config.yml:/app/compose_config.yml:ro
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENTRYPOINT [ "python", "main.py"]
|
||||||
Whitespace-only changes.
@@ -0,0 +1,8 @@
|
|||||||
|
mode: sharding
|
||||||
|
nodes:
|
||||||
|
- name: node1
|
||||||
|
url: http://kv1:5000
|
||||||
|
- name: node2
|
||||||
|
url: http://kv2:5000
|
||||||
|
- name: node3
|
||||||
|
url: http://kv3:5000
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import argparse
|
||||||
|
import requests
|
||||||
|
from strategies import (
|
||||||
|
Proxy, ShardingProxy,
|
||||||
|
ReplicatingProxy, ReplicatingQuorumProxy,
|
||||||
|
RAID3Proxy
|
||||||
|
)
|
||||||
|
import yaml
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
proxy: Proxy = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/put', methods=['POST'])
|
||||||
|
def put_handler():
|
||||||
|
key = request.args.get('key')
|
||||||
|
value = request.get_json().get('value')
|
||||||
|
|
||||||
|
if key is None or value is None:
|
||||||
|
return jsonify({'error': 'Please provide key and value.'}), 400
|
||||||
|
|
||||||
|
return proxy.put(key, value)
|
||||||
|
|
||||||
|
@app.route('/get', methods=['GET'])
|
||||||
|
def get_handler():
|
||||||
|
key = request.args.get('key')
|
||||||
|
|
||||||
|
if key is None:
|
||||||
|
return jsonify({'error': 'Please provide key.'}), 400
|
||||||
|
|
||||||
|
return proxy.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/state', methods=['GET'])
|
||||||
|
def state_handler():
|
||||||
|
results = ""
|
||||||
|
for _, node in proxy.nodes.items():
|
||||||
|
url = f'{node["url"]}/state'
|
||||||
|
response = requests.get(url)
|
||||||
|
results += f'<h1 style="font-family: monospace;">Response from {url}</h1>{response.content.decode("utf-8")}<hr>'
|
||||||
|
|
||||||
|
return f"<html><body>{results}</body></html>", 200
|
||||||
|
|
||||||
|
|
||||||
|
def load_proxy_from_config(config_path):
|
||||||
|
with open(config_path, 'r') as file:
|
||||||
|
config = yaml.safe_load(file)
|
||||||
|
|
||||||
|
mode = config.get('mode', '')
|
||||||
|
nodes = {}
|
||||||
|
for node in config['nodes']:
|
||||||
|
name = node['name']
|
||||||
|
url = node['url']
|
||||||
|
nodes[name] = {'url': url}
|
||||||
|
if mode == 'sharding':
|
||||||
|
return ShardingProxy(nodes)
|
||||||
|
elif mode == 'replication':
|
||||||
|
return ReplicatingProxy(nodes)
|
||||||
|
elif mode == 'replication-quorum':
|
||||||
|
return ReplicatingQuorumProxy(nodes)
|
||||||
|
elif mode == 'replication-raid3':
|
||||||
|
return RAID3Proxy(nodes)
|
||||||
|
raise ValueError('Invalid mode, allowed values: sharding, replication, replication-quorum')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Flask app acting as a proxy for key-value nodes')
|
||||||
|
parser.add_argument('-c', '--config', type=str, required=True, help='Path to the YAML config file')
|
||||||
|
parser.add_argument('-p', '--port', type=int, help='Port of the server')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
proxy = load_proxy_from_config(args.config)
|
||||||
|
|
||||||
|
app.run('0.0.0.0', args.port)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Flask==2.3.3
|
||||||
|
requests==2.31.0
|
||||||
|
pyyaml==6.0.1
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
import json
|
||||||
|
from collections import defaultdict
|
||||||
|
import math
|
||||||
|
from typing import Union
|
||||||
|
from flask import jsonify
|
||||||
|
import requests
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
class Proxy(ABC):
|
||||||
|
def __init__(self, nodes):
|
||||||
|
self.nodes = nodes
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def put(self, key, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get(self, key):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ShardingProxy(Proxy):
|
||||||
|
def __init__(self, nodes):
|
||||||
|
super().__init__(nodes)
|
||||||
|
|
||||||
|
def put(self, key, value):
|
||||||
|
node = self.get_node(key)
|
||||||
|
if node is None:
|
||||||
|
return {'error': 'No nodes available.'}, 500
|
||||||
|
|
||||||
|
url = f'{node["url"]}/put?key={key}'
|
||||||
|
response = requests.post(url, json={'value': value})
|
||||||
|
|
||||||
|
return response.content, response.status_code
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
node = self.get_node(key)
|
||||||
|
if node is None:
|
||||||
|
return {'error': 'No nodes available.'}, 500
|
||||||
|
|
||||||
|
url = f'{node["url"]}/get?key={key}'
|
||||||
|
response = requests.get(url)
|
||||||
|
|
||||||
|
return response.content, response.status_code
|
||||||
|
|
||||||
|
def get_node(self, key):
|
||||||
|
if len(self.nodes) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
hash_val = hashlib.sha256(key.encode()).hexdigest()
|
||||||
|
node_index = int(hash_val, 16) % len(self.nodes)
|
||||||
|
return self.nodes[list(self.nodes.keys())[node_index]]
|
||||||
|
|
||||||
|
|
||||||
|
class ReplicatingProxy(Proxy):
|
||||||
|
def __init__(self, nodes):
|
||||||
|
super().__init__(nodes)
|
||||||
|
|
||||||
|
def put(self, key, value):
|
||||||
|
responses = []
|
||||||
|
for _, node in self.nodes.items():
|
||||||
|
url = f'{node["url"]}/put?key={key}'
|
||||||
|
response = requests.post(url, json={'value': value})
|
||||||
|
if response.status_code == 200:
|
||||||
|
responses.append(response.content)
|
||||||
|
|
||||||
|
if len(responses) == 0:
|
||||||
|
return {'error': 'No nodes available.'}, 500
|
||||||
|
|
||||||
|
return responses[0], 200
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
non_ok_responses = dict()
|
||||||
|
for node_name, node in self.nodes.items():
|
||||||
|
url = f'{node["url"]}/get?key={key}'
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.content, 200
|
||||||
|
else:
|
||||||
|
non_ok_responses[node_name] = response.json()
|
||||||
|
|
||||||
|
return jsonify({'error': 'None of the nodes responded with 200.', 'responses': non_ok_responses}), 500
|
||||||
|
|
||||||
|
|
||||||
|
class ReplicatingQuorumProxy(Proxy):
|
||||||
|
def __init__(self, nodes):
|
||||||
|
super().__init__(nodes)
|
||||||
|
|
||||||
|
def put(self, key, value):
|
||||||
|
responses = []
|
||||||
|
for _, node in self.nodes.items():
|
||||||
|
url = f'{node["url"]}/put?key={key}'
|
||||||
|
response = requests.post(url, json={'value': value})
|
||||||
|
if response.status_code == 200:
|
||||||
|
responses.append(response.content)
|
||||||
|
|
||||||
|
if len(responses) == 0:
|
||||||
|
return {'error': 'No nodes available.'}, 500
|
||||||
|
|
||||||
|
if len(responses) > len(self.nodes) / 2:
|
||||||
|
return responses[0], 200
|
||||||
|
|
||||||
|
return jsonify({'error': 'Not enough responses for quorum write.', 'responses': responses}), 300
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
non_ok_responses = dict()
|
||||||
|
values = defaultdict(lambda: 0)
|
||||||
|
for node_name, node in self.nodes.items():
|
||||||
|
url = f'{node["url"]}/get?key={key}'
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
values[response.content] += 1
|
||||||
|
else:
|
||||||
|
non_ok_responses[node_name] = response.json()
|
||||||
|
|
||||||
|
for value, cnt in values.items():
|
||||||
|
if cnt > len(self.nodes) / 2:
|
||||||
|
return value, 200
|
||||||
|
|
||||||
|
if len(values) > 0:
|
||||||
|
values_for_response = []
|
||||||
|
for value, cnt in values.items():
|
||||||
|
values_for_response.append({'value': json.loads(value)['value'], 'count': cnt})
|
||||||
|
return jsonify({'error': 'Got multiple values, none of them has enough responses for quorum', 'values': values_for_response}), 300
|
||||||
|
|
||||||
|
return jsonify({'error': 'None of the nodes responded with 200.', 'responses': non_ok_responses}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DataBlock:
|
||||||
|
index: Union[int, None]
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
|
||||||
|
def raid3_split(data_blocks: int, value: str) -> list[DataBlock]:
|
||||||
|
bytes_value = value.encode()
|
||||||
|
L = int(math.ceil(len(bytes_value) / data_blocks))
|
||||||
|
split: list[bytes] = [
|
||||||
|
bytes_value[i:i + L]
|
||||||
|
for i in range(0, len(bytes_value), L)
|
||||||
|
]
|
||||||
|
res = []
|
||||||
|
for i, block in enumerate(split):
|
||||||
|
data_bytes: bytes = block + (L - len(block)) * b'\x00'
|
||||||
|
res.append(DataBlock(i, data_bytes))
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def raid3_parity(blocks: list[DataBlock]) -> DataBlock:
|
||||||
|
parity = len(blocks[0].data) * b'\x00'
|
||||||
|
for block in blocks:
|
||||||
|
if len(block.data) != len(parity):
|
||||||
|
raise ValueError('All blocks must have the same length.')
|
||||||
|
parity = bytes(a ^ b for a, b in zip(parity, block.data))
|
||||||
|
return DataBlock(None, parity)
|
||||||
|
|
||||||
|
|
||||||
|
def raid3_join(blocks: list[DataBlock]) -> str:
|
||||||
|
return (b''.join(block.data for block in blocks[:-1])).rstrip(b'\x00').decode()
|
||||||
|
|
||||||
|
|
||||||
|
def raid3_recover(blocks: list[Union[DataBlock, None]], error_index: int) -> str:
|
||||||
|
if error_index == len(blocks) - 1:
|
||||||
|
return raid3_join(blocks)
|
||||||
|
|
||||||
|
recovered = None
|
||||||
|
for block in blocks:
|
||||||
|
if recovered is None and block is not None:
|
||||||
|
recovered = len(block.data) * b'\x00'
|
||||||
|
if block is None:
|
||||||
|
continue
|
||||||
|
recovered = bytes(a ^ b for a, b in zip(recovered, block.data))
|
||||||
|
|
||||||
|
blocks[error_index] = DataBlock(error_index, recovered)
|
||||||
|
return raid3_join(blocks)
|
||||||
|
|
||||||
|
|
||||||
|
class RAID3Proxy(Proxy):
|
||||||
|
def __init__(self, nodes):
|
||||||
|
if len(nodes) < 3:
|
||||||
|
raise ValueError('RAID3 requires at least 3 nodes.')
|
||||||
|
if len(nodes) > 255:
|
||||||
|
raise ValueError('This implementation does not support more than 255 nodes.')
|
||||||
|
super().__init__(nodes)
|
||||||
|
self._divisor = len(self.nodes) - 1
|
||||||
|
|
||||||
|
def put(self, key, value):
|
||||||
|
if len(value) < self._divisor:
|
||||||
|
return {'error': f'Too small body for RAID3 algorithm with {self._divisor} blocks.'}, 500
|
||||||
|
|
||||||
|
blocks = raid3_split(self._divisor, value)
|
||||||
|
parity = raid3_parity(blocks)
|
||||||
|
|
||||||
|
for node, block in zip(self.nodes.values(), blocks + [parity]):
|
||||||
|
value_dict = {
|
||||||
|
'index': block.index,
|
||||||
|
'value': base64.b64encode(block.data).decode('utf-8'),
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f'{node["url"]}/put?key={key}'
|
||||||
|
response = requests.post(url, json={'value': json.dumps(value_dict)})
|
||||||
|
if response.status_code != 200:
|
||||||
|
return {'error': f'Error while writing to node {node["url"]}.'}, 500
|
||||||
|
|
||||||
|
return jsonify({'message': 'Key-value pair stored successfully on all nodes.'}), 200
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
blocks = []
|
||||||
|
errors = []
|
||||||
|
error_index = None
|
||||||
|
for node in self.nodes.values():
|
||||||
|
url = f'{node["url"]}/get?key={key}'
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
value_dict = json.loads(response.json()['value'])
|
||||||
|
blocks.append(DataBlock(value_dict['index'], base64.b64decode(value_dict['value'])))
|
||||||
|
else:
|
||||||
|
error_index = len(blocks)
|
||||||
|
blocks.append(None)
|
||||||
|
errors.append(response)
|
||||||
|
if len(errors) > 1:
|
||||||
|
return {'error': f'Got too many errors while reading key \'{key}\''}, 500
|
||||||
|
except ConnectionError as e:
|
||||||
|
error_index = len(blocks)
|
||||||
|
blocks.append(None)
|
||||||
|
errors.append(e)
|
||||||
|
if len(errors) > 1:
|
||||||
|
return {'error': f'Got too many errors while reading key \'{key}\''}, 500
|
||||||
|
|
||||||
|
if error_index is None:
|
||||||
|
value = raid3_join(blocks)
|
||||||
|
else:
|
||||||
|
value = raid3_recover(blocks, error_index)
|
||||||
|
|
||||||
|
return jsonify({'value': value}), 200
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from .strategies import raid3_join, raid3_split
|
||||||
|
|
||||||
|
|
||||||
|
def test_raid3_split():
|
||||||
|
value = 'Hello, world!' # len = 13
|
||||||
|
blocks = raid3_split(2, value) # 2 data blocks of 7 bytes, 1 parity block
|
||||||
|
assert len(blocks) == 2
|
||||||
|
|
||||||
|
assert blocks[0].index == 0
|
||||||
|
assert blocks[0].data == b'Hello, '
|
||||||
|
assert blocks[1].index == 1
|
||||||
|
assert blocks[1].data == b'world!\x00'
|
||||||
|
|
||||||
|
|
||||||
|
def test_raid3_join_utf():
|
||||||
|
value = 'съешь ещё этих мягких французских булок'
|
||||||
|
blocks = raid3_split(4, value)
|
||||||
|
assert len(blocks) == 4
|
||||||
|
joined = raid3_join(blocks + [None])
|
||||||
|
assert joined == value
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# Шардирование на примере простого key-value-хранилища
|
||||||
|
|
||||||
|
Этот пример показывает, как разделить данные между несколькими узлами. В стенде работают три простых
|
||||||
|
key-value-хранилища: каждое принимает ключ и значение и сохраняет их в памяти. Перед ними находится прокси
|
||||||
|
с единым API. Клиент обращается только к прокси, а тот определяет владельца ключа и пересылает запрос
|
||||||
|
нужному хранилищу.
|
||||||
|
|
||||||
|
В основном режиме прокси вычисляет `SHA-256(key) mod N` и направляет запись и чтение владельцу ключа.
|
||||||
|
В конце также кратко рассмотрен учебный режим репликации.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- `POST /put?key=<key>` записывает значение из JSON-поля `value`.
|
||||||
|
- `GET /get?key=<key>` возвращает значение или `404`, если ключа нет на выбранном узле.
|
||||||
|
- `GET /state` показывает содержимое всех хранилищ.
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
Из корня репозитория:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd materials/08-scaling/seminar/kv
|
||||||
|
docker compose up -d --build
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
Прокси будет доступен на порту 5005. Хранилища будут доступны на портах 5001, 5002 и 5003.
|
||||||
|
|
||||||
|
## Запись и чтение
|
||||||
|
|
||||||
|
Записать одно значение через прокси:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -X POST 'http://localhost:5005/put?key=key-0' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"value":"value-0"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Прочитать его:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS 'http://localhost:5005/get?key=key-0'
|
||||||
|
```
|
||||||
|
|
||||||
|
Записать `N` значений с ключами от `key-0` до `key-(N-1)`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
N=20
|
||||||
|
|
||||||
|
for ((i = 0; i < N; i++)); do
|
||||||
|
curl -sS -X POST "http://localhost:5005/put?key=key-$i" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "{\"value\":\"value-$i\"}"
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка распределения
|
||||||
|
|
||||||
|
Получить состояние всех узлов через прокси:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
open 'http://localhost:5005/state'
|
||||||
|
```
|
||||||
|
|
||||||
|
Состояние отдельных хранилищ:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS 'http://localhost:5001/state'
|
||||||
|
curl -sS 'http://localhost:5002/state'
|
||||||
|
```
|
||||||
|
|
||||||
|
Для текущей конфигурации `key-0` находится на третьем узле:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -i 'http://localhost:5003/get?key=key-0' # 200
|
||||||
|
curl -i 'http://localhost:5002/get?key=key-0' # 404
|
||||||
|
```
|
||||||
|
|
||||||
|
## Изменение конфигурации
|
||||||
|
|
||||||
|
Состав узлов задаётся в `proxy/compose_config.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
mode: sharding
|
||||||
|
nodes:
|
||||||
|
- name: node1
|
||||||
|
url: http://kv1:5000
|
||||||
|
- name: node2
|
||||||
|
url: http://kv2:5000
|
||||||
|
- name: node3
|
||||||
|
url: http://kv3:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
Конфигурация подключена к контейнеру как файл только для чтения. После её изменения достаточно
|
||||||
|
перезапустить прокси:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose restart proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
Хранилища при этом не перезапускаются, и данные в их памяти сохраняются.
|
||||||
|
|
||||||
|
## Режим репликации
|
||||||
|
|
||||||
|
В режиме `replication` прокси записывает каждое значение во все хранилища. При чтении он опрашивает
|
||||||
|
узлы по очереди и возвращает первый успешный ответ. В отличие от шардирования, данные не разделяются,
|
||||||
|
а копируются.
|
||||||
|
|
||||||
|
Чтобы включить этот режим, изменить первую строку в `proxy/compose_config.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
mode: replication
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем перезапустить только прокси и повторить команды записи и чтения:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose restart proxy
|
||||||
|
open 'http://localhost:5005/state'
|
||||||
|
```
|
||||||
|
|
||||||
|
После записи новый ключ должен появиться на всех трёх узлах. Это упрощённая учебная реализация:
|
||||||
|
она не гарантирует согласованность при частичных записях и не обрабатывает отказы как промышленная система.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENTRYPOINT [ "python", "main.py"]
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import argparse
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/put', methods=['POST'])
|
||||||
|
def put_handler():
|
||||||
|
key = request.args.get('key')
|
||||||
|
value = request.get_json().get('value')
|
||||||
|
|
||||||
|
if key is None or value is None:
|
||||||
|
return jsonify({'error': 'Please provide key and value.'}), 400
|
||||||
|
|
||||||
|
data[key] = value
|
||||||
|
return jsonify({'message': 'Key-value pair stored successfully.'}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/get', methods=['GET'])
|
||||||
|
def get_handler():
|
||||||
|
key = request.args.get('key')
|
||||||
|
|
||||||
|
if key is None:
|
||||||
|
return jsonify({'error': 'Please provide key.'}), 400
|
||||||
|
|
||||||
|
value = data.get(key)
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
return jsonify({'error': 'Key not found.', 'key': key}), 404
|
||||||
|
|
||||||
|
return jsonify({'value': value}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/state', methods=['GET'])
|
||||||
|
def state_handler():
|
||||||
|
table = '<table>'
|
||||||
|
table += f'<tr><th colspan="2" style="font-size: 24px; font-weight: bold; font-family: monospace; padding-bottom: 10px;">Data on {app.name}</th></tr>'
|
||||||
|
table += '<tr><th style="font-weight: bold; text-align: left; font-family: monospace; padding-bottom: 15px;">Key</th><th style="font-family: monospace; text-align: left; padding-bottom: 15px; padding-left: 20px;">Value</th></tr>'
|
||||||
|
|
||||||
|
for key, value in data.items():
|
||||||
|
value = value[:64] + f'...[total of {len(value)} bytes]' if len(value) > 100 else value
|
||||||
|
table += f'<tr><td style="font-weight: bold; font-family: monospace;">{key}</td><td style="font-family: monospace; text-align: left; padding-left: 20px;">{value}</td></tr>'
|
||||||
|
|
||||||
|
table += '</table>'
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/health', methods=['GET'])
|
||||||
|
def health_handler():
|
||||||
|
return f'Hi from {app.name}'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Flask app with hostname as app name')
|
||||||
|
parser.add_argument('-n', '--hostname', type=str, help='Hostname of the server')
|
||||||
|
parser.add_argument('-p', '--port', type=int, help='Port of the server')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.hostname:
|
||||||
|
app.name = args.hostname
|
||||||
|
|
||||||
|
|
||||||
|
app.run('0.0.0.0', args.port)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Flask==2.3.3
|
||||||
|
requests==2.31.0
|
||||||
|
pyyaml==6.0.1
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
target
|
||||||
|
**/target
|
||||||
|
**/__pycache__
|
||||||
|
**/.pytest_cache
|
||||||
|
*.pyc
|
||||||
|
Lab.ipynb
|
||||||
|
readme.md
|
||||||
|
media/
|
||||||
@@ -0,0 +1,725 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.2.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"clap_derive",
|
||||||
|
"clap_lex",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"once_cell",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "3.2.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||||
|
dependencies = [
|
||||||
|
"os_str_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "distsys-lb-lab"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"rand",
|
||||||
|
"rand_distr",
|
||||||
|
"rand_pcg",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simcore",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast-rs"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "erased-serde"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"typeid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown 0.12.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.175"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.101"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_distr"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_pcg"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.143"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap 2.11.1",
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_type_name"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92acc4cd6ae20767c54d6cf1a075624e7f4d9e99d7ebc685398ff243144d8714"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simcore"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a20dfec85e94e569fa5b810b2d992c68b361f31bfba471447427536488c9c292"
|
||||||
|
dependencies = [
|
||||||
|
"colored",
|
||||||
|
"downcast-rs",
|
||||||
|
"dyn-clone",
|
||||||
|
"erased-serde",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
"rand_pcg",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_type_name",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.16.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typeid"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "distsys-lb-lab"
|
||||||
|
version = "0.1.0"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
resolver = "3"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "3.1.17", features = ["cargo", "derive"] }
|
||||||
|
rand = "0.8.5"
|
||||||
|
rand_distr = "0.4.3"
|
||||||
|
rand_pcg = "0.3.1"
|
||||||
|
serde = { version = "1.0.142", features = ["derive"] }
|
||||||
|
serde_json = "1.0.107"
|
||||||
|
simcore = "0.1.0"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM rust:1.97.1-bookworm@sha256:0e2bcaef56d041a486784e54104a81aebe0da44bd03019bd70bc0401e42e4a97 AS builder
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y build-essential python3-dev
|
||||||
|
|
||||||
|
WORKDIR /lab
|
||||||
|
COPY . .
|
||||||
|
RUN --mount=type=cache,id=distsys-course-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
|
||||||
|
--mount=type=cache,id=distsys-course-cargo-target-rust-1-97-1,target=/lab/target,sharing=locked \
|
||||||
|
cargo install --locked --path . --root /opt/course
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y python3-dev wget && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder /opt/course/bin/distsys-lb-lab /usr/local/bin/distsys-lb-lab
|
||||||
|
|
||||||
|
WORKDIR /lab
|
||||||
|
ENTRYPOINT ["distsys-lb-lab"]
|
||||||
@@ -0,0 +1,569 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "9ba1cf58",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Балансировщики нагрузки: лабораторная работа"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "0d8f9518",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"В этой лабораторной работе сравним алгоритмы балансировки по задержкам запросов. Будем менять нагрузку, число балансировщиков и актуальность сведений о серверах. Перед запуском выполните [инструкции подготовки](readme.md): Docker-образ нужно собрать из текущих исходников."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"id": "7c48777d",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAqIAAAIGCAYAAACPjO0LAAA1dnRFWHRteGZpbGUAJTNDbXhmaWxlJTIwaG9zdCUzRCUyMmFwcC5kaWFncmFtcy5uZXQlMjIlMjBtb2RpZmllZCUzRCUyMjIwMjMtMTAtMzFUMTQlM0E1NSUzQTIzLjcxMFolMjIlMjBhZ2VudCUzRCUyMk1vemlsbGElMkY1LjAlMjAoWDExJTNCJTIwTGludXglMjB4ODZfNjQlM0IlMjBydiUzQTEwOS4wKSUyMEdlY2tvJTJGMjAxMDAxMDElMjBGaXJlZm94JTJGMTA5LjAlMjIlMjBldGFnJTNEJTIyNGVnTjE0dlhvbmpYamZvd0hBaW0lMjIlMjB2ZXJzaW9uJTNEJTIyMjEuNi44JTIyJTIwdHlwZSUzRCUyMmRldmljZSUyMiUzRSUwQSUyMCUyMCUzQ2RpYWdyYW0lMjBuYW1lJTNEJTIyUGFnZS0xJTIyJTIwaWQlM0QlMjJ3dW0wNGlsSUJDZ19XaXFjdmtjZiUyMiUzRSUwQSUyMCUyMCUyMCUyMCUzQ214R3JhcGhNb2RlbCUyMGR4JTNEJTIyMTQzNCUyMiUyMGR5JTNEJTIyNzA2JTIyJTIwZ3JpZCUzRCUyMjAlMjIlMjBncmlkU2l6ZSUzRCUyMjEwJTIyJTIwZ3VpZGVzJTNEJTIyMSUyMiUyMHRvb2x0aXBzJTNEJTIyMSUyMiUyMGNvbm5lY3QlM0QlMjIxJTIyJTIwYXJyb3dzJTNEJTIyMSUyMiUyMGZvbGQlM0QlMjIxJTIyJTIwcGFnZSUzRCUyMjElMjIlMjBwYWdlU2NhbGUlM0QlMjIxJTIyJTIwcGFnZVdpZHRoJTNEJTIyODUwJTIyJTIwcGFnZUhlaWdodCUzRCUyMjExMDAlMjIlMjBtYXRoJTNEJTIyMCUyMiUyMHNoYWRvdyUzRCUyMjAlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlM0Nyb290JTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjIwJTIyJTIwJTJGJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbXhDZWxsJTIwaWQlM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyMCUyMiUyMCUyRiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214Q2VsbCUyMGlkJTNEJTIyYVVXOVFVaW5zME5Cek82b3dMM1MtMTYlMjIlMjB2YWx1ZSUzRCUyMiUyMiUyMHN0eWxlJTNEJTIycm91bmRlZCUzRDElM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQmh0bWwlM0QxJTNCZ2xhc3MlM0QwJTNCJTIyJTIwdmVydGV4JTNEJTIyMSUyMiUyMHBhcmVudCUzRCUyMjElMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteEdlb21ldHJ5JTIweCUzRCUyMjEyMCUyMiUyMHklM0QlMjIyNjAlMjIlMjB3aWR0aCUzRCUyMjU2MCUyMiUyMGhlaWdodCUzRCUyMjEyMCUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMmFVVzlRVWluczBOQnpPNm93TDNTLTElMjIlMjB2YWx1ZSUzRCUyMnNlcnZlciUyMiUyMHN0eWxlJTNEJTIyc2hhcGUlM0RjeWxpbmRlcjMlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQmh0bWwlM0QxJTNCYm91bmRlZExibCUzRDElM0JiYWNrZ3JvdW5kT3V0bGluZSUzRDElM0JzaXplJTNEMTUlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyMSUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyMTMwJTIyJTIweSUzRCUyMjQwMCUyMiUyMHdpZHRoJTNEJTIyNjAlMjIlMjBoZWlnaHQlM0QlMjI4MCUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMmFVVzlRVWluczBOQnpPNm93TDNTLTIlMjIlMjB2YWx1ZSUzRCUyMnNlcnZlciUyMiUyMHN0eWxlJTNEJTIyc2hhcGUlM0RjeWxpbmRlcjMlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQmh0bWwlM0QxJTNCYm91bmRlZExibCUzRDElM0JiYWNrZ3JvdW5kT3V0bGluZSUzRDElM0JzaXplJTNEMTUlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyMSUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyMjEwJTIyJTIweSUzRCUyMjQwMCUyMiUyMHdpZHRoJTNEJTIyNjAlMjIlMjBoZWlnaHQlM0QlMjI4MCUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMmFVVzlRVWluczBOQnpPNm93TDNTLTMlMjIlMjB2YWx1ZSUzRCUyMnNlcnZlciUyMiUyMHN0eWxlJTNEJTIyc2hhcGUlM0RjeWxpbmRlcjMlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQmh0bWwlM0QxJTNCYm91bmRlZExibCUzRDElM0JiYWNrZ3JvdW5kT3V0bGluZSUzRDElM0JzaXplJTNEMTUlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyMSUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyMjkwJTIyJTIweSUzRCUyMjQwMCUyMiUyMHdpZHRoJTNEJTIyNjAlMjIlMjBoZWlnaHQlM0QlMjI4MCUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMmFVVzlRVWluczBOQnpPNm93TDNTLTQlMjIlMjB2YWx1ZSUzRCUyMnNlcnZlciUyMiUyMHN0eWxlJTNEJTIyc2hhcGUlM0RjeWxpbmRlcjMlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQmh0bWwlM0QxJTNCYm91bmRlZExibCUzRDElM0JiYWNrZ3JvdW5kT3V0bGluZSUzRDElM0JzaXplJTNEMTUlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyMSUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyMzcwJTIyJTIweSUzRCUyMjQwMCUyMiUyMHdpZHRoJTNEJTIyNjAlMjIlMjBoZWlnaHQlM0QlMjI4MCUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMmFVVzlRVWluczBOQnpPNm93TDNTLTUlMjIlMjB2YWx1ZSUzRCUyMnNlcnZlciUyMiUyMHN0eWxlJTNEJTIyc2hhcGUlM0RjeWxpbmRlcjMlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQmh0bWwlM0QxJTNCYm91bmRlZExibCUzRDElM0JiYWNrZ3JvdW5kT3V0bGluZSUzRDElM0JzaXplJTNEMTUlM0IlMjIlMjB2ZXJ0ZXglM0QlMjIxJTIyJTIwcGFyZW50JTNEJTIyMSUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ214R2VvbWV0cnklMjB4JTNEJTIyNDUwJTIyJTIweSUzRCUyMjQwMCUyMiUyMHdpZHRoJTNEJTIyNjAlMjIlMjBoZWlnaHQlM0QlMjI4MCUyMiUyMGFzJTNEJTIyZ2VvbWV0cnklMjIlMjAlMkYlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0MlMkZteENlbGwlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NteENlbGwlMjBpZCUzRCUyMmFVVzlRVWluczBOQnpPNm93TDNTLTYlMjIlMjB2YWx1ZSUzRCUyMnNlcnZlciUyMiUyMHN0eWxlJTNEJTIyc2hhcGUlM0RjeWxpbmRlcjMlM0J3aGl0ZVNwYWNlJTNEd3JhcCUzQmh0bWwlM0QxJTNCYm91bmRlZExibCUzRDElM0JiYWNrZ3JvdW5kT3V0bGlLine truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<IPython.core.display.Image object>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 1,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"from IPython.display import Image\n",
|
||||||
|
"Image(filename='media/balancers.png') "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "9cfcf69f",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Все балансировщики распределяют запросы по **одному общему набору серверов**. Это дискретно-событийная симуляция на Rust с библиотекой simcore: серверы и балансировщики моделируются обработчиками событий внутри одного процесса.\n",
|
||||||
|
"\n",
|
||||||
|
"- Запрос имеет числовой ID отправителя и длительность обслуживания. На медленном сервере длительность **умножается** на коэффициент замедления.\n",
|
||||||
|
"- Каждый сервер обслуживает запросы последовательно, остальные ждут в очереди.\n",
|
||||||
|
"- Входящий запрос попадает на случайный балансировщик. Состав серверов в течение эксперимента не меняется.\n",
|
||||||
|
"- Раз в `sync_interval` секунд балансировщик обновляет снимок числа незавершённых запросов на каждом сервере, включая обслуживаемый запрос. Между обновлениями снимок может устареть.\n",
|
||||||
|
"\n",
|
||||||
|
"`sync_interval` — интервал обновления сведений, а не время обработки запроса балансировщиком. Затраты на выбор сервера, сетевые задержки и стоимость проверок в этой модели не учитываются.\n",
|
||||||
|
"\n",
|
||||||
|
"На всех графиках по оси X отложено начало окна `t`, по оси Y — **p99 времени ответа** для запросов, пришедших в интервал `[t, t + 60)` секунд. Время ответа включает ожидание и обслуживание. Чтобы получить p99, длительности сортируют и берут элемент с номером `ceil(0.99 × n)` при нумерации с единицы. Например, для 100 запросов это 99-е значение, а не максимум. Ось Y логарифмическая.\n",
|
||||||
|
"\n",
|
||||||
|
"Симуляция создаёт запросы до конца последнего окна, затем дожидается завершения всех очередей. Поэтому даже долгие ответы учитываются полностью. Параметр `--time-limit` задаёт последнюю отметку на оси X, а не момент принудительного завершения запросов."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "79e49154-fa8e-4bad-9377-b72b5350deb4",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Вспомогательный код"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"id": "ebd17f6c",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import json\n",
|
||||||
|
"import matplotlib.pyplot as plt\n",
|
||||||
|
"import numpy as np\n",
|
||||||
|
"import subprocess\n",
|
||||||
|
"\n",
|
||||||
|
"from pathlib import Path\n",
|
||||||
|
"\n",
|
||||||
|
"# Сначала соберите образ по инструкции в readme.md.\n",
|
||||||
|
"# Для локальной сборки и запуска через Cargo задайте False.\n",
|
||||||
|
"USE_DOCKER = True\n",
|
||||||
|
"DOCKER_IMAGE = 'distsys-load-balancing-lab:local'\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def run_experiment(name, n_balancers=1, settings=None, balancers=None,\n",
|
||||||
|
" sync_interval=0.01, seed=123):\n",
|
||||||
|
" settings = list(settings or [])\n",
|
||||||
|
" if balancers is None:\n",
|
||||||
|
" balancers = ['Hash', 'Random', 'RoundRobin', 'LeastLoaded']\n",
|
||||||
|
" config_path = Path('.tmp-config.json')\n",
|
||||||
|
" result_path = Path('.tmp-result.txt')\n",
|
||||||
|
" configs = [{'balancer': balancer, 'n_balancers': n_balancers,\n",
|
||||||
|
" 'sync_interval': sync_interval} for balancer in balancers]\n",
|
||||||
|
" config_path.write_text(json.dumps(configs), encoding='utf-8')\n",
|
||||||
|
" args = ['--servers', '100', '--seed', str(seed), '--dump', result_path.name,\n",
|
||||||
|
" '--config', config_path.name, *settings]\n",
|
||||||
|
" try:\n",
|
||||||
|
" if USE_DOCKER:\n",
|
||||||
|
" command = ['docker', 'run', '--rm', '--mount',\n",
|
||||||
|
" f'type=bind,source={Path.cwd()},target=/lab',\n",
|
||||||
|
" DOCKER_IMAGE, *args]\n",
|
||||||
|
" else:\n",
|
||||||
|
" command = ['cargo', 'run', '--locked', '--release', '--', *args]\n",
|
||||||
|
" subprocess.run(command, check=True)\n",
|
||||||
|
" results = result_path.read_text(encoding='utf-8').splitlines()\n",
|
||||||
|
" finally:\n",
|
||||||
|
" config_path.unlink(missing_ok=True)\n",
|
||||||
|
" result_path.unlink(missing_ok=True)\n",
|
||||||
|
"\n",
|
||||||
|
" plt.figure(figsize=(12, 5))\n",
|
||||||
|
" plt.title(name)\n",
|
||||||
|
" plt.yscale('log')\n",
|
||||||
|
" plt.ylabel('p99 времени ответа, с (окно 60 с)')\n",
|
||||||
|
" plt.xlabel('Начало окна t, с')\n",
|
||||||
|
" for i in range(0, len(results), 3):\n",
|
||||||
|
" times = np.fromstring(results[i + 1], sep=',')\n",
|
||||||
|
" response_times = np.fromstring(results[i + 2], sep=',')\n",
|
||||||
|
" plt.plot(times, response_times, label=results[i])\n",
|
||||||
|
" plt.legend(loc='upper left')\n",
|
||||||
|
" plt.tight_layout()\n",
|
||||||
|
" plt.show()\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "7bbba437",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Эксперимент 1: централизованный балансировщик"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "fcb15af2",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Начнём с четырёх алгоритмов:\n",
|
||||||
|
"\n",
|
||||||
|
"- **Hash** закрепляет отправителя за сервером. В симуляции это `sender_id % num_servers` — упрощённая модель детерминированного выбора по клиенту.\n",
|
||||||
|
"- **Random** каждый раз выбирает случайный сервер.\n",
|
||||||
|
"- **RoundRobin** перебирает серверы по кругу.\n",
|
||||||
|
"- **LeastLoaded** выбирает сервер с наименьшим числом незавершённых запросов по последнему снимку. Это не Least Connections: TCP-соединения здесь не моделируются.\n",
|
||||||
|
"\n",
|
||||||
|
"Сначала работает один балансировщик с обновлением снимков раз в **10 мс**. Сравниваем p99 времени ответа, определённый выше. Меньшее значение означает меньшую задержку на границе самых медленных 1% запросов."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "bb9119ac",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Запустим эксперимент на 100 одинаковых серверах и 200 отправителях с сопоставимой интенсивностью запросов. При одинаковых параметрах нагрузки и `seed` все запуски используют одинаковый входной поток; генераторы выбора балансировщика и бэкенда отделены от генератора нагрузки."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"id": "5cc16a68",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABaAAAAJYCAYAAACUxkEyAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAASdAAAEnQB3mYfeAABAABJREFUeJzs3QWUG1UXB/D/et3d3d0NKtQpUErxFil8aIHiTlsoUtyhWAvFpdSgTil1d3d33dp27Tv3JZN5k0yySTZZ/f/O2bPxTGzkvvvujUhNTU0FEREREREREREREVGIRYb6AYmIiIiIiIiIiIiIGIAmIiIiIiIiIiIiorBhBjQRERERERERERERhQUD0EREREREREREREQUFgxAExEREREREREREVFYMABNRERERERERERERGHBADQRERERERERERERhQUD0EREREREREREREQUFgxAExEREREREREREVFYMABNRERERERERERERGHBADQRERERERERERERhQUD0EREREREREREREQUFgxAExEREREREREREVFYMABNRERERERERERERGHBADQRERERERERERERhQUD0EREREREREREREQUFgxAExEREREREREREVFYMABNRERERERERERERGHBADQRERERERERERERhQUD0ERERESU4Xbt2oWIiAjccccdYX0eeY5OnTohN5HXK687O6tSpYr688e///6rXu+wYcOQXV8DERERUU7GADQRERERERERERERhQUD0EREREREREREREQUFgxAExEREREREREREVFYMABNRERERJlq06ZN6Nu3L4oVK4b8+fOjQ4cOmD59usftTp8+jbfeegtdunRBhQoVEBsbi5IlS+Lqq6/GwoUL/X6+AwcO4OWXX0b79u1RpkwZ9TjlypXDLbfcgg0bNvisVy2nb7rpJpQoUQJ58uRBixYtMHnyZK/P9csvv+CKK65Qr01uLzWBb775Zixbtszjtj/99BM6d+6MIkWKqNvWrVsXI0aMQEJCgu1j//zzz2jevDny5s2LUqVKYeDAgeq1BaJt27bq9Z87d85yeceOHdVrvuuuuyyXb9y4UV1+2223eXw2zz77LGrXrq2WvWjRoujRowdmzpzps2bzkiVLcOWVV6r3Ry6T99eXH3/8EXFxceq9Seu2YuvWrWpZy5cv7/qc5bxcnt7vhUhNTcXHH3+M+vXrq9ctzzN48GD1fhARERGRAwPQRERERJRpdu7cqYKgJ06cwL333ovrr78ey5cvR69evVTw1j34+fzzzyMyMlIFLR977DF069YN//zzDy6//HJMnTrVr+f877//8MYbb6hA73XXXYdHH30Ubdq0we+//45WrVph9erVtvfbvXu3ul4CnxLsvfHGG7Fu3Tpcc801mD17tkdgUgLWEqxes2YN+vXrp57nsssuw9y5cz2C1oMGDVKBzm3btqllevDBB1VQ9sUXX0TPnj2RlJRkuf17772nAtk7duxQAdU777wTa9euRbt27XDy5Ek/332o4HhiYqJaJsP58+exaNEidXrWrFmW2xvn5X6GU6dOqeeV97Rw4cIYMmSIeg0yKNC9e3eMGjXK9rnlenk/Ll68qF7/7bffroK+3rz55psYMGAAWrZsifnz56fZ4G/p0qVqgOD7779X93niiSfU5yzn5XK5Pr3fC3mtDz30kHrP77nnHvV5y/ewa9euuHTpks/lIyIiIso1UomIiIiIMtjOnTtTZVdU/p544gnLdUuXLk2Njo5OLVKkSOrp06ddl586dSr16NGjHo+1d+/e1LJly6bWqVPH4zp5/I4dO1ouO3z4cOqZM2c8brtq1arU/Pnzp/bs2dPrsg4bNsxy3dSpU9XlvXr1slw+atQodXnLli3VcuuSkpJSDxw44Do/evRoddtrr7029fz585bbDh06VF33/vvvW5YnJiYmtWjRouq0ITk5ObVfv36uZfXHrFmzPD4D4zV169ZN/d+2bZvrur59+6rL9uzZ47rsnnvuUZfJ/5SUFNflW7ZsSS1UqFBqbGysZTlnz57tWsbPP//cdrkqV66s/ozXNXjwYHV7eX0XLlyw3NZ4PHmvDLIc8n2Qy7///nvL7X/++Wd1ee3atdVjB/u9mD9/vnqc6tWrpx4/ftx1uSxfmzZt1HXGayAiIiLKzZgBTURERESZRjJmX3rpJctlkp166623qszaP//803JbKX3hTspx9O/fX5Xy2LNnT5rPKeUqChYs6HF548aNVXkPyWaWrGB3lStXxgsvvGC5TMpMVKpUSZWS0H300Ufqv2T/ynLroqKiULZsWdf5Dz74ANHR0fjmm29UOQ2dZEAXL14cP/zwg+syOS3LJ5m3ehawZIZLiRL57y/JXJbSEXqms5yW5Rk+fLjr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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('balanced workload')"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "475a053a",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Уменьшим число отправителей до 10, сохранив 100 серверов. Сколько серверов сможет использовать Hash при таком выборе? Нужны ли коллизии, чтобы нагрузка оказалась неравномерной?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"id": "738b357c",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABaAAAAJYCAYAAACUxkEyAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAASdAAAEnQB3mYfeAABAABJREFUeJzsnQeYFMXWhs/mBEvOGSQjIBlFQFQUUVEM1yyGK9ecMWfF34wJFQOY9eoVxYAgCiqiBBWVnHNc0u6yeXf+59Ruz1TPzsxOqI7zvc8z7DDT07G6uuqrU99J8Hg8HgIAAAAAAAAAAAAAAAAAFJOoeoUAAAAAAAAAAAAAAAAAAARoAAAAAAAAAAAAAAAAAIaBCGgAAAAAAAAAAAAAAAAAhgABGgAAAAAAAAAAAAAAAIAhQIAGAAAAAAAAAAAAAAAAYAgQoAEAAAAAAAAAAAAAAAAYAgRoAAAAAAAAAAAAAAAAAIYAARoAAAAAAAAAAAAAAACAIUCABgAAAAAAAAAAAAAAAGAIEKABAAAAAAAAAAAAAAAAGAIEaAAAAAAAAAAAAAAAAACGAAEaAAAAAAAAAAAAAAAAgCFAgAYAAAAAAAAAAAAAAABgCBCgAQAAAAAAAAAAAAAAABgCBGgAAAAAAAAAAAAAAAAAhgABGgAAAAAAAAAAAAAAAIAhQIAGAAAAAAAAAAAAAAAAYAgQoAEAAAAAAAAAAAAAAAAYAgRoAAAAAAAAAAAAAAAAAIYAARoAAAAAwCQ2bdpECQkJNG7cOEO3w9sYPnw4xRN8vHzcTqZt27biFQ7z5s0Tx/vggw+SU49B5oEHHqD09HTaunWrIfvldqZNmybKA/+1Ax6Ph3r16kXHHnus1bsCAAAAABsAARoAAAAAAABgGSw6P/XUU3TVVVdRq1atdN99+umndP311wshMzs7W4isF110UY3rXLBgAZ1yyilUv359ysjIoJ49e9KkSZOovLzcwCMBGnydHn74YZo/f764hgAAAACIbyBAAwAAAAAAACzjkUceoeLiYpowYUK17x599FF66aWXaOnSpdSiRYuw1vfFF1/Q0KFD6aeffqIzzzyTrrvuOiopKaGbb76ZzjvvPAOOAARizJgx1LVrV7rnnntERDQAAAAA4hcI0AAAAAAAAABLOHToEL3//vt0/PHHU8uWLat9/9xzz9GaNWsoNzeXXnnllRrXx8v9+9//pqSkJGFT8uabb4roahawBw8eLKJxP/roI4OOBvhz6aWXiuv3/fff4+QAAAAAcQwEaAAAAAAAC1i1ahWdccYZwiIgKyuLhgwZQrNnzw4o0LGANmLECCHQpaamUqNGjej000+nX3/9Nezt7dixQ0yJP+aYY6hp06ZiPc2bN6cLLriAVqxYEdKvmt9z5GjDhg2FT2+/fv3oq6++Crqtjz/+WAiKfGy8PHsCn3/++bRkyZJqy3744Yd03HHHUd26dcWyHDHJUa8cERsIFg/79u0rbBUaN25MF198sTi2SGAhko//8OHDus+HDRsmjvmKK67Qfb5y5Urx+SWXXFLt2tx1113UuXNnse/16tWjk046iebMmRPSs3nRokU0evRocX74Mz6/ofjggw8oLS1NnJualmXWrl0r9pUjhrXrzP/nz2MtFwxHs3JUcvfu3cVx83Y4ypjPR6Tw9S8oKKB//etfAb/nstGxY8ew/b1ZYN67d68or1xONXg/uVwx4QjZGjNmzBBluVmzZuIa8LnhcjJ58uRqy+7fv1+UB75OXD7r1KkjfhvovpY9m+fOnSs8zGvXri1sRrhscJkLxLp16+icc84RZY3rjaOPPpq+/vrrkMewbds2cX3at28vjqFBgwai/li8eHG1Zbl88n5xeeVyN3DgQKpVq5bO1zuSc6JFnPNAAAAAAADiFwjQAAAAAAAms3HjRiGCsmA1fvx4ISj9/vvvNGrUKCHeyrAQxVPYExMThTB1yy230Iknnkg//PCDsBn49ttvw9om2xH83//9nxB6zzrrLGFHMGjQICHYDRgwgP7666+Av9u8ebP4noVPFntZKFy2bJmYXs/Cmb8wyYI1i05///03jR07VmyH/Xt//vnnaqL15ZdfLoROFtV4n6699lohyt5333108sknU1lZWbVoWBayN2zYIATVyy67jP755x8hwh04cCDMs09CPCstLRX7pMEi6G+//Sbe+0drav/n32kcPHhQbJfPKQuNN910kzgGHhQYOXIkvfbaawG3zd/z+SgqKhLHzxGiLPoG48knnxSex/3796dffvmlxgR/LCqy8Pree++J39x2223iOvP/+XN/0TGacsHHyr7MfM7Zt5mvN5fDE044QVhdRIIm1vMAjAr4vmC4/PjD90tmZqbwhw42wCEzZcoUUc5ZiD/ttNPo1ltv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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('balanced workload (10 senders)', settings=['--senders', '10'])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "d1b44c0b",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Теперь вернём 200 отправителей, но один из них будет посылать запросы в 50 раз чаще остальных. Проследите, куда попадёт его нагрузка при разных алгоритмах."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"id": "ab3d360c",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABaAAAAJYCAYAAACUxkEyAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAASdAAAEnQB3mYfeAAA+7pJREFUeJzs3Qd0FOXXx/GbnpBCb6H3For0DqIoIIqiWFGx8hcVsaEoSlDsioqKiihgL7yIigVFQUEpYsMEpPfeEgiQnvfcZzKb3RTqbMrm+zlnz+7OzM7M7iabzW/u3McvKysrSwAAAAAAAAAAcJi/0ysEAAAAAAAAAIAAGgAAAAAAAADgNVRAAwAAAAAAAAC8ggAaAAAAAAAAAOAVBNAAAAAAAAAAAK8ggAYAAAAAAAAAeAUBNAAAAAAAAADAKwigAQAAAAAAAABeQQANAAAAAAAAAPAKAmgAAAAAAAAAgFcQQAMAAAAAAAAAvIIAGgAAAAAAAADgFQTQAAAAAAAAAACvIIAGAAAAAAAAAHgFATQAAAAAAAAAwCsIoAEAAAAAAAAAXkEADQAAAAAAAADwCgJoAAAAAAAAAIBXEEADAAAAAAAAALyCABoAAADFQu/evcXPz09Kg9jYWPNcFyxYICXVsGHDzHPYtGnTSS2vy+p7XJKfAwAAAE4dATQAAAAAAAAAwCsIoAEAAAAAAAAAXkEADQAAAAAAAADwCgJoAAAAeNWXX34p55xzjlSvXl1CQkIkOjpaevXqJZMnTz6px//0009StmxZ87i///7bNX3btm1yxx13SP369c16K1asKBdddJH8/vvvHo9/8803TZ/ft956y2P6tGnTzPQyZcpISkqKx7xOnTpJaGioHDt2zGP60qVL5bLLLpNq1apJcHCw1KpVS4YPHy47duzId9//+OMP6devn0RGRkpUVJSce+65snjxYjkVY8aMMfv5ww8/eEwfN26cmd6gQYM8j9H9q127tse0zMxMeeONN6RDhw4SEREh4eHh5vbrr79u5hXUs3nXrl1y8803S40aNSQgIECmT59+3P39559/zLL6fHPvc34SExPNc2zSpIl5zcuXLy/nn3++zJs3L8+yqamp8uqrr8qAAQOkTp065n2vUKGCeV2//fbbAreh6+rRo4d5zrr8xRdfLP/9998J9w0AAABnjgAaAAAAXjNlyhQZNGiQrFy5Ui688EK59957TXiowa4GwCfywQcfSP/+/U34rMFtmzZtzPQ///zT3NYQW4PLO++806z/l19+ke7du8s333zjWoeG3+rHH3/0WLd9X/fFPRTWQFSD4y5dukhYWJhr+jvvvCPdunUzQefZZ58to0aNkvbt28vUqVPN9ZYtWzzW/9tvv5nQU8NPfQ4almtoraGuBtkn60T7v2HDBo9B9OLi4mT37t2ux9muvfZaue2228w8DZRvvfVW2bt3r4wYMcLMy8+BAwekc+fOsmTJEhk8eLB5DlWrVi1wX3WfevbsKVlZWea96Nu373GfW0JCgnTt2lWefvppc5BBX9NLL73UvB/nnXeeOXiQe3/uuusuOXz4sFn3PffcYw46/PXXX+bnSt+L3GbOnGkC7eXLl8uQIUPMAYP9+/eb93fjxo3H3T8AAAA4IAsAAADwkrZt22YFBwdn7d69O8+8vXv3etzv1atXlvvX06effjrLz88vq3v37ln79+93TU9LS8tq0KBBVkhISNaCBQs81rF9+/as6OjorGrVqmUlJye7pteuXTurcuXKWZmZma5p1atXz+rTp0+Wv79/1tixY13TZ8+ebfbjsccec01bvXp1VlBQkNnutm3bPLY5b948s46LL77YNU2306RJE7MeXZ+7l156yUzXy/z580/4Gh49etQ81/bt27umHT582OxP3759zXqmTp2aZ/3vvvuua9qHH35opp111lnmsbakpKSsdu3amXkffPCBx3btfbz22mvNa57b9ddfb+Zv3LjR3H/vvffMPjVr1ixr06ZNeZbXZfU9dnfrrbea6Xrt/t6sWbMmKyoqyvzs2OtX+p5u3bo1z7oTEhKyWrRokVW+fHnzerm/ThUqVMgKDAzM+v333z0eM2rUKNdzdN8GAAAAnEUFNAAAALwqMDBQgoKC8kyvVKlSvstrOwittH3wwQflkksuMW0ctG2C7euvv5b169ebqmdt5eFOK6VHjx5t2ka4Vwz36dPHVPv++++/5r5WZO/cudO002jbtq3HsvZt9wpibVORlpYmL7/8smkv4U6X0yrcr776ylTm2tXPq1evNtXAWgHuTp9bfm0zCqJV2Fqtq1XfBw8eNNO0ulj3RyuGK1eufML91+ptpZXG2n7Dpi0pnnnmGXM7v+phrdh+/vnnzXt4PLre6667zrQu+fXXX017jBPR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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('skewed workload', settings=['--preset', 'skewed'])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "34925ed0",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"- Почему закрепление отправителя за сервером ухудшает задержки в этих двух случаях?\n",
|
||||||
|
"- Когда такая привязка полезна, например для локального кеша или состояния сессии?\n",
|
||||||
|
"- Чем равномерное распределение числа отправителей отличается от равномерного распределения нагрузки?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "1bccea52",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Пока все серверы были одинаковыми. Вернём равномерный поток от 200 отправителей и замедлим 5% серверов в пять раз."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 6,
|
||||||
|
"id": "0372fc52",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('balanced workload (different servers)', settings=['--different-servers'])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "9c8afb20",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"- Почему алгоритмы, не учитывающие очереди, накапливают запросы на медленных серверах?\n",
|
||||||
|
"- Насколько LeastLoaded помогает в этой ситуации и как на результат влияет период обновления снимка?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "ce26e221",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Эксперимент 2: независимые балансировщики"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "ed6ad1a3",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Теперь несколько балансировщиков используют общий набор серверов. У каждого свой снимок состояния, который обновляется раз в **5 секунд**. В этой модели обновления происходят одновременно, поэтому все балансировщики видят одинаковые снимки. Запросы по-прежнему распределяются между балансировщиками случайно.\n",
|
||||||
|
"\n",
|
||||||
|
"По умолчанию у экземпляров Random и PowerOfK разные воспроизводимые последовательности случайных чисел, а у RoundRobin — случайные начальные позиции. Параметр `seed` позволяет воспроизвести эксперимент.\n",
|
||||||
|
"\n",
|
||||||
|
"По сравнению с первым экспериментом меняются и число балансировщиков, и период обновления. Сначала посмотрим на один балансировщик с периодом 5 секунд, чтобы отделить эти эффекты."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 7,
|
||||||
|
"id": "623b10ba",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('balanced workload (1 balancer, updates every 5 s)', sync_interval=5)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "b2578704",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"- Зачем распределять входящий поток между несколькими балансировщиками?\n",
|
||||||
|
"- Что изменилось при переходе от обновлений раз в 10 мс к обновлениям раз в 5 секунд при одном балансировщике?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 8,
|
||||||
|
"id": "a5f41eba",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('balanced workload (5 balancers)', n_balancers=5, sync_interval=5)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "59fb2833",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"- Почему LeastLoaded направляет много запросов на один сервер между обновлениями? Почему при одинаковых снимках и входном потоке его кривая не меняется с числом балансировщиков?\n",
|
||||||
|
"- Как изменяется p99 у Random и RoundRobin? Не ожидайте одинакового порядка кривых при любых параметрах нагрузки.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "17865058",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Эксперимент 3: Power of Two Choices и Power of k Choices"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "158f1f53",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"С устаревшими снимками глобальный минимум может привлекать слишком много запросов от разных балансировщиков. Попробуем выбирать минимум только среди **двух случайных серверов** — Power of Two Choices. Более общий вариант с k кандидатами называется Power of k Choices.\n",
|
||||||
|
"\n",
|
||||||
|
"Это не гарантия улучшения для любой нагрузки: результат зависит от актуальности снимков, длительности запросов и различий между серверами. Проверим алгоритм на том же наборе из пяти балансировщиков. Подробнее о методе: [The Power of Two Choices in Randomized Load Balancing](http://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf)."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 12,
|
||||||
|
"id": "98374a5a",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"exp3_balancers = ['Hash', 'Random', 'RoundRobin', 'LeastLoaded', 'PowerOfK[k=2]']"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 13,
|
||||||
|
"id": "64609ded",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('power of two choices', n_balancers=5, sync_interval=5, balancers=exp3_balancers)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "44c62e12",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Увеличим число кандидатов k. Больший выбор повышает шанс найти свободный сервер, но при устаревших снимках может усилить концентрацию запросов на одних и тех же кандидатах. Какой эффект виден на этих графиках? Повторите эксперимент с другим периодом обновления или seed."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 14,
|
||||||
|
"id": "5f1c5da8",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABaAAAAJYCAYAAACUxkEyAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAASdAAAEnQB3mYfeAABAABJREFUeJzsnQW4VFX3xtfcDrq7VVBCRRETxERFEQML61NRPxS7OzD/2PFhYSAKYiJiIYqgYoPS3X253M75P2vfOWf2OXNy8szc98dzmZnTffZ+99rv8vn9fj8BAAAAAAAAAAAAAAAAAFEmLdoLBAAAAAAAAAAAAAAAAAAgQAMAAAAAAAAAAAAAAACIGYiABgAAAAAAAAAAAAAAABATIEADAAAAAAAAAAAAAAAAiAkQoAEAAAAAAAAAAAAAAADEBAjQAAAAAAAAAAAAAAAAAGICBGgAAAAAAAAAAAAAAAAAMQECNAAAAAAAAAAAAAAAAICYAAEaAAAAAAAAAAAAAAAAQEyAAA0AAAAAAAAAAAAAAAAgJkCABgAAAAAAAAAAAAAAABATIEADAAAAAAAAAAAAAAAAiAkQoAEAAAAAAAAAAAAAAADEBAjQAAAAAAAAAAAAAAAAAGICBGgAAAAAAAAAAAAAAAAAMQECNAAAAAAAAAAAAAAAAICYAAEaAAAAAAAAAAAAAAAAQEyAAA0AAAAAAAAAAAAAAAAgJkCABgAAAAAAAAAAAAAAABATIEADAAAAAACQpOzZs4euvfZa6tKlC2VkZJDP56O//vqLvMjEiRPF9vFnMq8DAAAAAAC4AwI0AAAAAAAAScott9xCzz33HPXp04duv/12uvfee6lNmzaW8wwePFiItLNnzw4ZV1JSQieddJIYf8IJJ1BxcXEMtx4AAAAAANQHMhK9AQAAAAAAAIDwmD59Ou2999702WefRXwId+zYQSeffDLNnz+fzj//fHrjjTcoMzMzqU7N6aefTgMHDqS2bdsmelMAAAAAAEAAREADAAAAAACQpGzatCkqYuvatWvpiCOOEOLzDTfcQG+//XbSic9M48aNqWfPnuITAAAAAAB4AwjQAAAAAAAg7qxZs0bYPFx88cW0ZMkSGj58ODVr1ozy8/OFEPrVV18ZzldRUUGPPvqosJzIy8ujRo0a0ZFHHklTpkzRTMfWEVlZWXT44YdrhpeVlVFOTo5YN4usMi+99JIY/vrrr2uG79q1S9hb9OrVi3Jzc4W4ecwxxxhuo+xBPHPmTGF3wdPzMCds3ryZ/vvf/wpPZ97+li1b0ogRI+j33383tNHw+/30/fffi+/8x8PdsnDhQjrssMNo2bJl9MQTT9D//d//Od5ehffff18cEz6HfHx5+88991z67bffDKf/7rvvxLY2bNhQnEOOvF68eHFEx8TOA3rDhg3CL3uvvfYS55G3dcCAAfTggw8aTjtmzBjq1q0bZWdnU/PmzenUU0+lX3/9NWTaoqIisYzevXuLfeF96t69O40cOdJwGwEAAAAA6huw4AAAAAAAAAlj9erVdOihhwpBefTo0UJsZDFz6NCh9O677woRT6GyslL4ErPgylGuLEqWlpbSBx98IKbj5Hvjxo0T0zZo0ECIi7/88osQCFkUZObOnStEbObbb7+lUaNGqcvn3wwLqXJkMAulLJiz0H3iiScKn2S2vuDv//vf/+jyyy8P2S/eJhageT+uvPJKsRwnx4LFd45qHjJkiBBw169fT1OnTqXPP/+cpk2bRqeccoqYloV73q7777+fOnfuLH4zLNK64YcffhDCKh/HN998U3M8nMAC+CWXXCLmbdGihRCGWSBmAZdF5n322YcOOuggzTx87D755BP12CxatIhmzJghxF3+zssJ55hYwUI4XzvcmHDUUUeJ7eR95vXdd999dPfdd6vT/vHHH3T88ceLaXkenpbtST7++GOxLR999JHwyVb2n6+DefPmiev4sssuE8kglf3na6Z///6ujikAAAAAQMrhBwAAAAAAIM6sXr3az0VR/rvppps043799Vd/RkaGv0mTJv7CwkJ1+Lhx48T0Q4cO9VdVVanDt27d6u/cubMYN3fuXHX43XffLYZNnz5dHXbbbbf509PT/UOGDPF36NBBHV5TU+Nv1qyZv1u3bpptGTRokN/n8/knT56sGV5QUODv16+fPycnx79lyxZ1+BtvvCHWyfN88cUXro7J8ccfL+Z96KGHNMN5n3ibefuKioo043h63kY38PQ839ixY8X25+fnu95Whf/9739iWQcffLB/9+7dmnHV1dX+TZs2hRwb3pdvvvlGMy2fFx732GOPRXRMlHXwp0JFRYW/S5cuYvikSZNC9mH9+vXqd76uunfv7s/OzvbPnj1bM93GjRv97dq187dp08ZfXl4u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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('power of K choices', n_balancers=5, sync_interval=5, balancers=['PowerOfK[k=2]', 'PowerOfK[k=3]', 'PowerOfK[k=5]', 'PowerOfK[k=10]'])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "198d4745",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"PowerOfK выбирает по периодически обновляемому снимку. Вариант **PowerOfKWithUpdates** сравнивает актуальные очереди только у выбранных k серверов перед каждым решением. В симуляции это прямое чтение состояния: стоимость и задержка таких проверок не моделируются. Сравните варианты и обсудите, какой дополнительный обмен потребовался бы в реальной системе."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 15,
|
||||||
|
"id": "ecb9a473",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('do updates matter?', n_balancers=5, sync_interval=5, balancers=['PowerOfK[k=2]', 'PowerOfKWithUpdates[k=2]'])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "cffbb495",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Повторим сравнение при наличии медленных серверов. Сохраняется ли преимущество алгоритмов, учитывающих очередь? Как на результаты влияют выбранный seed и частота обновления состояния?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 16,
|
||||||
|
"id": "ddcbc891",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABaAAAAJYCAYAAACUxkEyAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAASdAAAEnQB3mYfeAABAABJREFUeJzs3Qd4U+XbBvD7nOzuXfbeICDIcgCKIuBAUf9uRdx77wWK+rn3wgHuvXEPcIEMEZE9y+reM2nWdz1vSUk6oNCEtun9u66Y5JyTsxpLep8nz6t5vV4viIiIiIiIiIiIiIiCTA/2ComIiIiIiIiIiIiIGEATERERERERERERUciwApqIiIiIiIiIiIiIQoIBNBERERERERERERGFBANoIiIiIiIiIiIiIgoJBtBEREREREREREREFBIMoImIiIiIiIiIiIgoJBhAExEREREREREREVFIMIAmIiIiIiIiIiIiopBgAE1EREREREREREREIcEAmoiIiIiIiIiIiIhCggE0EREREREREREREYUEA2giIiIiIiIiIiIiCgkG0EREREREREREREQUEgygiYiIiIiIiIiIiCgkGEATERERERERERERUUgwgCYiIiIiIiIiIiKikGAATUREREREREREREQhwQCaiIiIiIiIiIiIiEKCATQRERERUQgUFxfjmmuuQZcuXWA0GqFpGpYvX94qz7Uc+9ixY1v8NoiIiIho3xn34zVERERERLQXt9xyC15++WUcf/zxOPfcc2EwGNCmTZs9vkbCapGWlsbzS0RERERhgQE0EREREVEIzJ07F7169cJXX33F83sArFmzBhERETzXRERERM0MW3AQEREREYVAeno62rZty3N7gPTp0wedOnXi+SYiIiJqZhhAExEREVEAaf8g/XSnTp2KtWvX4qSTTkJCQgIiIyNx+OGH44cffqjzjDkcDvzf//0fDjroIFWJGhMTgyOOOAIffvhhwHKlpaUwm8047LDDAqZXVFTAarWqbb/11lsB81588UU1/fXXXw+Ynp+fj9tvvx19+/aFzWZDbGwsxo0bV+c+zpkzR61D7r/77jvVL1iWl2kNkZGRgSuvvFK1yZD9T05OxpQpU/D3338HLCfrlXV6vV78+uuv6vHe+hPPnz9fLbN161Z1873G93MQ7dq1Q/v27Wu9tnPnzmq5+++/P2D6t99+q6bfc889+3UcDSHvj2nTpql1WSwWpKSkqJ+5/Lzqkpubi0suuUQF87J8//79MXv27DqX9Xg8eOmllzBs2DBERUWp9588lnXLvJrqO8dut1utR95v8vOW90mPHj1w0UUXYcOGDQHLulwuvPDCCxg5cqR6/8r7+OCDD8Zzzz1X5za//PJL9X7zHY/8jMaMGaPWQURERERV2IKDiIiIiOq0ZcsWjBo1SgXKl156qQouP/jgA0ycOBHvvvsuTj/99OplKysrceyxx6rAVSpRJeAsLy/Hxx9/rJaTwfcefPBBtayEicOHD8eiRYtQUlKC6OhoNf3PP/9UIbb4+eefVd9kH3kuJOzzkaBWAkcJzCX0nDBhAsrKylTrC3ks/ZcvvvjiWscl+yQBtBzHZZddptbTkHMh4btUNR911FE488wzsX37dnz00Uf4+uuv8cknn6hez0ICY9mvGTNmqHDYFyD7+jvXRebde++9eOqpp9Tz6667rnre4MGD1b1s95133lGhr5xjsXHjRmzbtq36HN199917PGf7chx7I8ufdtpp6mcm51vWVVhYiH///RePPPIILr/88oDlZZ6EwBJ6n3rqqep1sl0JsHVdx/nnnx+wvPz85X3WsWNHFRZLwPzZZ5/hiiuuwB9//KHOxd7I+1KO58cff1TrOeuss1SwLO8ZWZeci549e6plnU4nTjjhBHz//ffo3bu3WlYuiMybNw9XX321er/6XxiZNWuW+v9C+nrL65KSkpCdnY0VK1aoUF32k4iIiIgAqcwgIiIiIqq2ZcsWr3xMlNtNN90UcGaWLFniNRqN3ri4OG9RUVH19AcffFAtP3HiRK/T6ayenpWV5e3cubOa9+eff1ZPv/vuu9W0uXPnVk+77bbbvAaDwXvUUUd5O3ToUD3d7XZ7ExISvN26dQvYlzFjxng1TfO+9957AdMLCgq8gwYN8lqtVm9mZmb19NmzZ6ttymu+/fbbffqJjx8/Xr125syZAdPlmGSfZf9KSkoC5snyso/7Qs6V3Ory2muvqXU+99xz1dNeeuklNe2YY47xms1mb1lZWfW8wYMHe202m9fhcDTqOOqSk5PjjYmJ8ZpMJu/8+fNrzd++fXvAc9/76cILL/S6XK7q6atWrVLb7du3b8Dy7777rlr+4IMPDtif0tJS79ChQ9W8d955Z6/n+/bbb1fTTzjhBK/dbg+YJ8+zs7Orn997771q2auuuipgH+XxtGnT1LzPP/+8evqQIUPUOZf3eF3nh4iIiIiqsAUHEREREdVJ2hXUbN9wyCGH4Oyzz1bVrFJB6iOtMaRC9YknnoDRuPtLdtKSwVeV++qrr1ZP91Xl+qp0fY+HDh2q2kHs2LED69evV9OlelpabfhX8kqVrVRbn3LKKTjjjDMC9jEuLk5VH9vtdlXRW9PkyZNVxW5Dyb5ISw/pL3zLLbcEzDv00ENV5a/s36effopQqu+cpaam4pprrlHVvlIZLPLy8tQ5kgpfqTgO9nG88cYbKC4uVlXO0nKipg4dOtSaJu0s5P1hMBiqp/Xr109VRcsAgtKaxcfXakVaukjFvI+04Xj44YdrvZ/qIq03pBWGtNyQFhzSIsOfPJf2I0Laazz77LOqmvnJJ58M2Ed5/Pjjj6v3d82qa3mvm0ymWtuWamgiIiIiqsIWHERERERUpyFDhlS3x/An7SUkgPznn39U2wRpoyGtIKQ/sa81hD9p9SBkeR9p7SHBoC9MLSoqwrJly1Qw6lte5vXq1Qu//PJLwHrEwoULq183ffr0WtvMyclR9xJs1iTtP/aFb7+lzUddYaPs19tvv62WO++88xAq0s6jW7duql+0BKYSiMrjo48+WoXAEobKORs/frxqGyFFwf7nLJjH8ddff6l7aWPSUNLqQtpf1CStMURBQUF12CzvBWnLUVdPZzlWCYX93091kVYl8v4YMWKE6s28J3KxQ8J32ceZM2fWuYy8X/3fT3Ih5sYbb1QhulwEkf2SMN0XahMRERFRFQbQRERERFQnqayti1SJCgn3/O9lILa6+KZL1bSPVOVKde5PP/2kwuIFCxaoilWp8pUBBeU1EqZKha3cS9jqH6ZKha+Q3r5yq49/VW3N/W+o/Tm+UJHz88orr6iAVkJkOXcyTS4UyAB9vkC/rv7PwTwO3zJ1DYpYH6lMr4uvYl5+/v77KgNf+qq3ay7v67ccrH30vZ9kUEKpnm/I++mGG25Q+yFV1s8884zq3y3vUwmiH330UfVtASIiIiIC2IKDiIiIiOqUlZVV5/TMzMzqFh3+977pNcnghf7L+UigLFW6EpbKTQZ8kwpS3zyp4pWB6n7//Xf0799ftfPw8a3r6aefVuuo7yaDwdUkIeG+2N/jCwVfCC/Bfc2QWeZJVbBU8so82R+pYg/FcfjC5J07dyIUZB/kOGRgwJpcLhdyc3PrrKbe3330HfPJJ5+8x/eTDOLoTyrFpRpcAmwZlPHCCy/Eb7/9pgbk9FXhExEREbV2DKCJiIiIqE5SZSvtNWqStg/i4IMPVvdSfdu9e3cV9EkFaU0SJAv/MLRmT2NpsyF9iCWE9s2TAPLFF19EWVlZQCWvGDlypLqXcDrUfMcp/ZUl/Gzo8e0PaS3hXwlck4TMEqD7zpm05OjSpYuaJ+dIWnO8+eab6ucg7Sv8exkH8zh85//bb79FKMi+yrFImFuTTJNztLf9lHYwEkKvWLEC6enpDVpWwuS6Qu+9kddOmjRJVadPnTpVvXfr2nciIiKi1ogBNBERERHVSdog3HfffQHTli5dqgZik4pRqRb1mTZtmqoQvfnmmwMCVKlUvf/++6uX8ScBoqzniy++wKpVqwJCZl+l70MPPRTw3EfaG0gvYxkwzzdgXU3//fffXts0NIQMqHfMMccgLS1NtVnwt2jRIrz77ruIj48POB/7KzExUVXOVlRU1DlfqsClGvzPP/9UAaf/OfMF+PWds2Aeh/T+lgpkuUBQV9AqAx42hu+9cvvtt6O8vLx6ujy+7bbb1GOpNt4TCd+vuOIKdS4vu+wyVU3vTwZt9FUpS1uPq6++WlWBy4COdZ1/mbd69erq574+2zX53nMy6CIRERERsQc0EREREdVj9OjRePXVV1U4Ka0xJID74IMPVGXqyy+/HNAC4aabblLVsBImDxo0SFWDSlj40UcfqUBOBheUns81A0Kp0pXXCP8wVQbck6rqTZs2qeWkr25NEphKyCpBpPTglcHmpBJVwk+pel25cqUarNC/dcf+eumll9Q5kID9hx9+UAH49u3b1fHJYHnS6qOuARv3lZyDJUuWYMKECer8WywWdT5POOGEgGXk2HyPfWRZ2ce6+j8H+zik97Gc/1NPPRVHHnmkGoxw4MCBKC4uVude1lmzXcW+OOuss9T74sMPP1SB+0knnaQqvz///HO13tNPP10NArg39957r3r/fvXVV2pAy+OPP14dn+yfHL/0apaKZXH33Xfj33//VedIlpf3lvSPlvevVJRL6P/AAw+oQQeFBPUyaKJUg0sVuoTRUpEvP7+hQ4eqwSGJiIiICJAPSkRERERE1bZs2SJlnd7zzz/fu3r1au+JJ57ojYuL89psNu+hhx7q/e677+o8WxUVFd4HHnjA279/f6/VavVGRUV5DzvsMO+7775b79l95pln1LZiYmK8LpcrYN4ll1yi5g0fPrze1xcXF6ttDhkyxBsZGam226VLF++kSZO8L7/8sre0tLR62dmzZ6v1yf3+2LFjh/eyyy7zdurUyWsymbyJiYneyZMnexcLine truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('power of two choices', n_balancers=5, sync_interval=5, balancers=exp3_balancers, settings=['--different-servers'])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "correlated-selectors-intro",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Дополнительно: коррелированные выборы балансировщиков\n",
|
||||||
|
"\n",
|
||||||
|
"Этот эксперимент можно выполнить после основной части. Сравните его с запуском `balanced workload (5 balancers)` из эксперимента 2: число балансировщиков, поток запросов и период обновления снимков остаются прежними.\n",
|
||||||
|
"\n",
|
||||||
|
"Флаг `--correlated-selectors` задаёт всем балансировщикам одинаковый seed выбора сервера. Поэтому у экземпляров Random совпадают последовательности случайных чисел, а у RoundRobin — начальные позиции. Это отличается от общего параметра `seed`: без флага он воспроизводит эксперимент, но экземпляры получают разные последовательности выбора.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 11,
|
||||||
|
"id": "5e3a734f",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABaAAAAJYCAYAAACUxkEyAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAASdAAAEnQB3mYfeAABAABJREFUeJzs3Qd4U2UXwPFT2lL23ntvZAmIbESZIoJ7gVtRERcOVMCBgrj9cAsqbmWJCIiigCLgQPbee28otM33nPf2Njdp2qZt0qbp/+cTyWpyc3OT3Hve854T4XK5XAIAAAAAAAAAQIDlCfQDAgAAAAAAAABAABoAAAAAAAAAEDRkQAMAAAAAAAAAgoIANAAAAAAAAAAgKAhAAwAAAAAAAACCggA0AAAAAAAAACAoCEADAAAAAAAAAIKCADQAAAAAAAAAICgIQAMAAAAAAAAAgoIANAAAAAAAAAAgKAhAAwAAAAAAAACCggA0AAAAAAAAACAoCEADAAAAAAAAAIKCADQAAAAAAAAAICgIQAMAAAAAAAAAgoIANAAAAAAAAAAgKAhAAwAAAAAAAACCggA0AAAAAAAAACAoCEADAAAAAAAAAIKCADQAAECYGzhwoERERMiWLVskp/j111/NMo8YMSK7FyVHrr/sdPDgQSlRooQMGjQoaM+h24W+J7qdBMuECRPMc+i/8K1Pnz5Ss2ZNOXv2LKsIAACkiAA0AAAA/NKpUycTkEPw5eR1PXz4cDl9+rQ8+eSTPgO6KZ3eeeedbFtmZMwzzzwjmzdvljfeeINVCAAAUhSV8k0AAAAA4L9t27bJu+++KzfffLNUqFDB530uu+wyadq0abLrzz//fFZ1DqPvY/fu3eX55583Ge8FChTI7kUCAAAhiAA0AAAAgIDQ4HNcXJwpW5KSvn37pno7cpYBAwbIjz/+KJ9//rncdttt2b04AAAgBFGCAwAAIAWLFy+Wq6++WipWrCgxMTFSvnx5ueSSS+Trr79Odl+9rkOHDlK0aFHJnz+/NG7cWF544QWJjY1Ndt9q1aqZ07Fjx+TBBx8056Ojo5PqHad1u1qzZo0J4lWuXFny5s0rZcuWleuuu07Wrl3r9/upJRH69+8vNWrUMMtcpEgRadu2rUycONHjflr7WEsk/Pbbb+ays2yClopw2rFjh9x7773mMXWdlSxZ0tSJXbJkic9l2Lt3r9x6661m+XUZNKPy448/lvTSGrRaBqB58+ZSvHhxk4mp602zbefMmZPs/oFYf2rRokVyxRVXSLly5czj6OPdeeedsmvXLp/3P3TokAwbNkwaNWpkllG3lyZNmshjjz0mJ0+eTNe6/vvvv837V6ZMGbOuq1atarJQd+/enWId602bNsmbb74p5513nlnf9mO6XC6z3i+88EIpXbq05MuXz7yWbt26yVdffeXXutDHGD9+vPk7fZysosvdrFkz83p0Xdxyyy2yZ8+eZPfT9XX//feb9a01qvU11q5dWx566CE5fPiw3883d+5cueOOO6RBgwbmM6PPq+/nyJEj5cyZM6nWq/7222+lVatW5r3XZbjmmmtk586dGdpWvO/7+OOPS/369c3y6H0vuugimT17dqq1rWfOnGm2Ab2/s+TL/Pnz5dJLL5VKlSqZbUu37wsuuMC8Rm/6GdN1+eGHH/q9DgEAQO5CBjQAAIAP77//vtx9990SGRlpAqgaqNq3b5/89ddfMm7cOLnqqquS7vvEE0+YYHOpUqVMELNQoUImI1CvnzVrlgkCaXDSO2DapUsXEzjSoLYGsqpXr+7X7Ro06tevn5w7d84EiWrVqmUCv5MmTZIffvjBBMg0EJsWfX0NGzY0gXMNrmvzuBkzZsiNN95oArHPPvusuV+xYsVMXV8NWG3dutWct2mQ1/bPP/+YZdVl1sClLuOBAwdkypQp0q5dO5k8ebL07Nkz6f56mwYqNSiqt+tJg6d33XWXeZz00ADrF198YYJ1N910kwnCaRB4wYIFZn117do16b6BWn8fffSRCURqgE63EQ28rl+/Xj744AP5/vvv5c8//5QqVaok3V9r5Xbu3NmswxYtWpj1n5CQIOvWrZNXX33VvG5/1/X06dNN8FmDvhoA1+CzBljffvttmTp1qnndzu3JpgFYDS726tXLvBe6fSsNdOo2rH+j27YGJPW90IGDb775xgzEpGXlypXmbzSompqlS5fKa6+9ZoK1Orij60QDnRmh600/X7p8WgpCX7cGwTXYq4MDGkx3fqZ1G+zYsaPZHnTd6zp75ZVXzOdV71+4cOE0n3P06NFmAEO3XV2P+jp+//13E2jW59UBD3u9Oun3xrRp08y2osugz6fB/f/++8+sE92O0rOtFCxY0NxX76NBZB28aN++vVkPGqDWbUTPa1b67bff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 truncated
|
||||||
|
"text/plain": [
|
||||||
|
"<Figure size 1200x500 with 1 Axes>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"run_experiment('correlated selectors (5 balancers)', n_balancers=5, sync_interval=5,\n",
|
||||||
|
" settings=['--correlated-selectors'])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "correlated-selectors-questions",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"- Как изменились p99 у Random и RoundRobin по сравнению с независимой инициализацией? Почему одинаковые последовательности могут создавать перекосы нагрузки?\n",
|
||||||
|
"- Почему результаты Hash и LeastLoaded не изменились? Используют ли они генератор случайных чисел при выборе сервера?\n",
|
||||||
|
"\n",
|
||||||
|
"В этом прогоне особенно заметен рост p99 у Random. Запросы поступают на балансировщики случайно, поэтому их последовательности продвигаются с разной скоростью: одинаковый seed не означает, что все балансировщики всегда выбирают один сервер одновременно.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.13.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
# Балансировщики нагрузки: лабораторная работа
|
||||||
|
|
||||||
|
Откройте `Lab.ipynb` в JupyterLab или редакторе с поддержкой Jupyter и выполните ячейки по порядку. Нужны Python, JupyterLab, NumPy и Matplotlib:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install jupyterlab numpy matplotlib
|
||||||
|
```
|
||||||
|
|
||||||
|
В основной части три раздела и 10 прогонов. В конце есть дополнительный эксперимент с одинаковыми seed у балансировщиков: его можно оставить для самостоятельного разбора.
|
||||||
|
|
||||||
|
Симулятор написан на Rust с библиотекой simcore. По умолчанию ноутбук запускает его в Docker. Перед первым запуском и после изменения Rust-кода соберите образ из этой папки:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t distsys-load-balancing-lab:local .
|
||||||
|
```
|
||||||
|
|
||||||
|
Для запуска без Docker установите Rust версии, указанной в `rust-toolchain.toml` в корне репозитория, и задайте `USE_DOCKER = False` в ноутбуке. Тогда `cargo run --locked --release` соберёт текущие исходники автоматически.
|
||||||
|
|
||||||
|
На графиках показан p99 времени ответа для запросов, пришедших в окно `[t, t + 60)` секунд. Время ответа включает ожидание в очереди и обслуживание. Смысл метрики и параметры экспериментов разобраны в ноутбуке. После изменения симулятора или параметров пересчитайте графики.
|
||||||
|
|
||||||
|
Проверка симулятора при установленном Rust:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test --locked
|
||||||
|
```
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
use std::boxed::Box;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
|
use rand_pcg::Pcg64;
|
||||||
|
use simcore::cast;
|
||||||
|
use simcore::context::SimulationContext;
|
||||||
|
use simcore::event::Event;
|
||||||
|
use simcore::handler::EventHandler;
|
||||||
|
|
||||||
|
use crate::events::{RequestArrivalEvent, SyncEvent};
|
||||||
|
use crate::host::Host;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct HostSnapshot {
|
||||||
|
pub id: usize,
|
||||||
|
pub requests: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LoadBalancer {
|
||||||
|
state: Vec<HostSnapshot>,
|
||||||
|
hosts: Vec<Rc<RefCell<Host>>>,
|
||||||
|
selector: Box<dyn HostSelector>,
|
||||||
|
sync_interval: f64,
|
||||||
|
sync_until: f64,
|
||||||
|
ctx: Rc<RefCell<SimulationContext>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoadBalancer {
|
||||||
|
pub fn new(
|
||||||
|
hosts: Vec<Rc<RefCell<Host>>>,
|
||||||
|
selector: Box<dyn HostSelector>,
|
||||||
|
sync_interval: f64,
|
||||||
|
sync_until: f64,
|
||||||
|
ctx: Rc<RefCell<SimulationContext>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
state: (0..hosts.len())
|
||||||
|
.map(|i| HostSnapshot { id: i, requests: 0 })
|
||||||
|
.collect(),
|
||||||
|
hosts,
|
||||||
|
selector,
|
||||||
|
sync_interval,
|
||||||
|
sync_until,
|
||||||
|
ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventHandler for LoadBalancer {
|
||||||
|
fn on(&mut self, event: Event) {
|
||||||
|
cast!(match event.data {
|
||||||
|
RequestArrivalEvent {
|
||||||
|
processing_time,
|
||||||
|
sender,
|
||||||
|
} => {
|
||||||
|
let host = self.selector.select(&self.state, &self.hosts, sender);
|
||||||
|
self.hosts[host.id]
|
||||||
|
.borrow_mut()
|
||||||
|
.on_new_request(processing_time, event.time);
|
||||||
|
}
|
||||||
|
SyncEvent {} => {
|
||||||
|
for (i, host) in self.hosts.iter().enumerate() {
|
||||||
|
self.state[i].requests = host.borrow().requests.len();
|
||||||
|
}
|
||||||
|
if event.time + self.sync_interval < self.sync_until {
|
||||||
|
self.ctx
|
||||||
|
.borrow_mut()
|
||||||
|
.emit_self(SyncEvent {}, self.sync_interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HostSelector {
|
||||||
|
fn select(
|
||||||
|
&mut self,
|
||||||
|
states: &[HostSnapshot],
|
||||||
|
hosts: &[Rc<RefCell<Host>>],
|
||||||
|
sender: usize,
|
||||||
|
) -> HostSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RoundRobinSelector {
|
||||||
|
pub ptr: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostSelector for RoundRobinSelector {
|
||||||
|
fn select(
|
||||||
|
&mut self,
|
||||||
|
states: &[HostSnapshot],
|
||||||
|
hosts: &[Rc<RefCell<Host>>],
|
||||||
|
_sender: usize,
|
||||||
|
) -> HostSnapshot {
|
||||||
|
let id = self.ptr;
|
||||||
|
self.ptr += 1;
|
||||||
|
if self.ptr == hosts.len() {
|
||||||
|
self.ptr = 0;
|
||||||
|
}
|
||||||
|
states[id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RandomSelector {
|
||||||
|
rng: Pcg64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RandomSelector {
|
||||||
|
fn new(seed: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
rng: Pcg64::seed_from_u64(seed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostSelector for RandomSelector {
|
||||||
|
fn select(
|
||||||
|
&mut self,
|
||||||
|
states: &[HostSnapshot],
|
||||||
|
_hosts: &[Rc<RefCell<Host>>],
|
||||||
|
_sender: usize,
|
||||||
|
) -> HostSnapshot {
|
||||||
|
*states.choose(&mut self.rng).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LeastLoadedSelector {}
|
||||||
|
|
||||||
|
impl HostSelector for LeastLoadedSelector {
|
||||||
|
fn select(
|
||||||
|
&mut self,
|
||||||
|
states: &[HostSnapshot],
|
||||||
|
_hosts: &[Rc<RefCell<Host>>],
|
||||||
|
_sender: usize,
|
||||||
|
) -> HostSnapshot {
|
||||||
|
*states.iter().min_by_key(|s| s.requests).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PowerOfKSelector {
|
||||||
|
k: usize,
|
||||||
|
rng: Pcg64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerOfKSelector {
|
||||||
|
pub fn new(k: usize, seed: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
k,
|
||||||
|
rng: Pcg64::seed_from_u64(seed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostSelector for PowerOfKSelector {
|
||||||
|
fn select(
|
||||||
|
&mut self,
|
||||||
|
states: &[HostSnapshot],
|
||||||
|
_hosts: &[Rc<RefCell<Host>>],
|
||||||
|
_sender: usize,
|
||||||
|
) -> HostSnapshot {
|
||||||
|
**states
|
||||||
|
.iter()
|
||||||
|
.choose_multiple(&mut self.rng, self.k)
|
||||||
|
.iter()
|
||||||
|
.min_by_key(|s| s.requests)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PowerOfKSelectorWithUpdates {
|
||||||
|
k: usize,
|
||||||
|
rng: Pcg64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerOfKSelectorWithUpdates {
|
||||||
|
pub fn new(k: usize, seed: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
k,
|
||||||
|
rng: Pcg64::seed_from_u64(seed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostSelector for PowerOfKSelectorWithUpdates {
|
||||||
|
fn select(
|
||||||
|
&mut self,
|
||||||
|
states: &[HostSnapshot],
|
||||||
|
hosts: &[Rc<RefCell<Host>>],
|
||||||
|
_sender: usize,
|
||||||
|
) -> HostSnapshot {
|
||||||
|
**states
|
||||||
|
.iter()
|
||||||
|
.choose_multiple(&mut self.rng, self.k)
|
||||||
|
.iter()
|
||||||
|
.min_by_key(|s| hosts[s.id].borrow().requests.len())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HashSelector {}
|
||||||
|
|
||||||
|
impl HostSelector for HashSelector {
|
||||||
|
fn select(
|
||||||
|
&mut self,
|
||||||
|
states: &[HostSnapshot],
|
||||||
|
_hosts: &[Rc<RefCell<Host>>],
|
||||||
|
sender: usize,
|
||||||
|
) -> HostSnapshot {
|
||||||
|
states[sender % states.len()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_selector_by_name(name: &str, seed: u64, num_hosts: usize) -> Box<dyn HostSelector> {
|
||||||
|
match name {
|
||||||
|
"RoundRobin" => Box::new(RoundRobinSelector {
|
||||||
|
ptr: Pcg64::seed_from_u64(seed).gen_range(0..num_hosts),
|
||||||
|
}),
|
||||||
|
"Random" => Box::new(RandomSelector::new(seed)),
|
||||||
|
"LeastLoaded" => Box::new(LeastLoadedSelector {}),
|
||||||
|
"Hash" => Box::new(HashSelector {}),
|
||||||
|
_ => {
|
||||||
|
if name.starts_with("PowerOfK[k=") && name.ends_with("]") {
|
||||||
|
let k = name[11..name.len() - 1].parse::<usize>().unwrap();
|
||||||
|
assert!(
|
||||||
|
(1..=num_hosts).contains(&k),
|
||||||
|
"k must be between 1 and the number of servers"
|
||||||
|
);
|
||||||
|
Box::new(PowerOfKSelector::new(k, seed))
|
||||||
|
} else if name.starts_with("PowerOfKWithUpdates[k=") && name.ends_with("]") {
|
||||||
|
let k = name[22..name.len() - 1].parse::<usize>().unwrap();
|
||||||
|
assert!(
|
||||||
|
(1..=num_hosts).contains(&k),
|
||||||
|
"k must be between 1 and the number of servers"
|
||||||
|
);
|
||||||
|
Box::new(PowerOfKSelectorWithUpdates::new(k, seed))
|
||||||
|
} else {
|
||||||
|
panic!("Unknown host selector: {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use simcore::simulation::Simulation;
|
||||||
|
|
||||||
|
fn choices(name: &str, seed: u64) -> Vec<usize> {
|
||||||
|
let mut sim = Simulation::new(0);
|
||||||
|
let log = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
let hosts: Vec<_> = (0..100)
|
||||||
|
.map(|id| {
|
||||||
|
Rc::new(RefCell::new(Host::new(
|
||||||
|
id,
|
||||||
|
1.0,
|
||||||
|
log.clone(),
|
||||||
|
Rc::new(RefCell::new(sim.create_context(format!("host_{id}")))),
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let states: Vec<_> = (0..hosts.len())
|
||||||
|
.map(|id| HostSnapshot { id, requests: 0 })
|
||||||
|
.collect();
|
||||||
|
let mut selector = create_selector_by_name(name, seed, hosts.len());
|
||||||
|
(0..200)
|
||||||
|
.map(|_| selector.select(&states, &hosts, 0).id)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn selector_streams_are_reproducible_and_depend_on_seed() {
|
||||||
|
for name in [
|
||||||
|
"Random",
|
||||||
|
"RoundRobin",
|
||||||
|
"PowerOfK[k=2]",
|
||||||
|
"PowerOfKWithUpdates[k=2]",
|
||||||
|
] {
|
||||||
|
assert_eq!(choices(name, 111), choices(name, 111), "{name}");
|
||||||
|
assert_ne!(choices(name, 111), choices(name, 222), "{name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub balancer: String,
|
||||||
|
pub n_balancers: u32,
|
||||||
|
pub sync_interval: f64,
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
pub struct SyncEvent {}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
pub struct RequestArrivalEvent {
|
||||||
|
pub processing_time: f64,
|
||||||
|
pub sender: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
pub struct RequestEndEvent {}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use simcore::cast;
|
||||||
|
use simcore::context::SimulationContext;
|
||||||
|
use simcore::event::Event;
|
||||||
|
use simcore::handler::EventHandler;
|
||||||
|
|
||||||
|
use crate::events::RequestEndEvent;
|
||||||
|
use crate::log::EventLog;
|
||||||
|
|
||||||
|
pub struct Host {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub id: usize,
|
||||||
|
pub requests: VecDeque<(f64, f64)>,
|
||||||
|
slowdown: f64,
|
||||||
|
log: Rc<RefCell<EventLog>>,
|
||||||
|
ctx: Rc<RefCell<SimulationContext>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Host {
|
||||||
|
pub fn new(
|
||||||
|
id: usize,
|
||||||
|
slowdown: f64,
|
||||||
|
log: Rc<RefCell<EventLog>>,
|
||||||
|
ctx: Rc<RefCell<SimulationContext>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
requests: Default::default(),
|
||||||
|
slowdown,
|
||||||
|
log,
|
||||||
|
ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_new_request(&mut self, duration: f64, time: f64) {
|
||||||
|
self.requests.push_back((duration * self.slowdown, time));
|
||||||
|
if self.requests.len() == 1 {
|
||||||
|
self.ctx
|
||||||
|
.borrow_mut()
|
||||||
|
.emit_self(RequestEndEvent {}, duration * self.slowdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventHandler for Host {
|
||||||
|
fn on(&mut self, event: Event) {
|
||||||
|
cast!(match event.data {
|
||||||
|
RequestEndEvent {} => {
|
||||||
|
let (_, old) = self.requests.pop_front().unwrap();
|
||||||
|
self.log.borrow_mut().push((event.time, old));
|
||||||
|
if let Some((delay, _)) = self.requests.front() {
|
||||||
|
self.ctx.borrow_mut().emit_self(RequestEndEvent {}, *delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub type EventLog = Vec<(f64, f64)>;
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::io::BufWriter;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use rand_distr::Exp;
|
||||||
|
use rand_pcg::Pcg64;
|
||||||
|
use simcore::simulation::Simulation;
|
||||||
|
|
||||||
|
mod balancer;
|
||||||
|
mod config;
|
||||||
|
mod events;
|
||||||
|
mod host;
|
||||||
|
mod log;
|
||||||
|
mod metrics;
|
||||||
|
|
||||||
|
use crate::balancer::{create_selector_by_name, LoadBalancer};
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::events::{RequestArrivalEvent, SyncEvent};
|
||||||
|
use crate::host::Host;
|
||||||
|
use crate::log::EventLog;
|
||||||
|
use crate::metrics::{windowed_p99, WINDOW};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Results dump path.
|
||||||
|
#[clap(long)]
|
||||||
|
dump: String,
|
||||||
|
|
||||||
|
/// Configuration JSON file.
|
||||||
|
#[clap(long)]
|
||||||
|
config: String,
|
||||||
|
|
||||||
|
/// Number of servers.
|
||||||
|
#[clap(long, default_value = "100")]
|
||||||
|
servers: u32,
|
||||||
|
|
||||||
|
/// Last arrival time shown on the graph (seconds).
|
||||||
|
#[clap(long, default_value = "3600")]
|
||||||
|
time_limit: f64,
|
||||||
|
|
||||||
|
/// Number of senders generating requests.
|
||||||
|
#[clap(long, default_value = "200")]
|
||||||
|
senders: u32,
|
||||||
|
|
||||||
|
/// Load distribution preset (balanced/skewed).
|
||||||
|
#[clap(long, default_value = "balanced")]
|
||||||
|
preset: String,
|
||||||
|
|
||||||
|
/// Adds slower servers to simulate heterogenous cluster.
|
||||||
|
#[clap(long)]
|
||||||
|
different_servers: bool,
|
||||||
|
|
||||||
|
/// Random seed.
|
||||||
|
#[clap(long, default_value = "123")]
|
||||||
|
seed: u64,
|
||||||
|
|
||||||
|
/// Reproduce correlated selector streams for the teaching experiment.
|
||||||
|
#[clap(long)]
|
||||||
|
correlated_selectors: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_simulation(args: &Args, config: &Config) -> Vec<(f64, f64)> {
|
||||||
|
assert!(
|
||||||
|
args.servers > 0 && args.senders > 0,
|
||||||
|
"servers and senders must be positive"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
args.time_limit.is_finite() && args.time_limit > 0.0,
|
||||||
|
"time-limit must be finite and positive"
|
||||||
|
);
|
||||||
|
assert!(config.n_balancers > 0, "n_balancers must be positive");
|
||||||
|
assert!(
|
||||||
|
config.sync_interval.is_finite() && config.sync_interval > 0.0,
|
||||||
|
"sync_interval must be finite and positive"
|
||||||
|
);
|
||||||
|
// Generate all arrivals needed by the last complete measurement window.
|
||||||
|
let arrivals_end = args.time_limit + WINDOW;
|
||||||
|
let mut sim = Simulation::new(args.seed);
|
||||||
|
let ctx = sim.create_context("entry point");
|
||||||
|
let log = Rc::new(RefCell::new(EventLog::new()));
|
||||||
|
let mut hosts = Vec::with_capacity(args.servers as usize);
|
||||||
|
for i in 0..args.servers {
|
||||||
|
let slowdown = if args.different_servers && i < args.servers / 20 {
|
||||||
|
5.0
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
hosts.push(Rc::new(RefCell::new(Host::new(
|
||||||
|
i as usize,
|
||||||
|
slowdown,
|
||||||
|
log.clone(),
|
||||||
|
Rc::new(RefCell::new(sim.create_context(format!("host_{i}")))),
|
||||||
|
))));
|
||||||
|
sim.add_handler(format!("host_{i}"), hosts[i as usize].clone());
|
||||||
|
}
|
||||||
|
let mut balancers = Vec::with_capacity(config.n_balancers as usize);
|
||||||
|
// Keep selector randomness separate from the workload generator.
|
||||||
|
let mut selector_seeds = Pcg64::seed_from_u64(args.seed);
|
||||||
|
for i in 0..config.n_balancers {
|
||||||
|
let seed = if args.correlated_selectors {
|
||||||
|
111
|
||||||
|
} else {
|
||||||
|
selector_seeds.gen()
|
||||||
|
};
|
||||||
|
balancers.push(Rc::new(RefCell::new(LoadBalancer::new(
|
||||||
|
hosts.clone(),
|
||||||
|
create_selector_by_name(&config.balancer, seed, hosts.len()),
|
||||||
|
config.sync_interval,
|
||||||
|
arrivals_end,
|
||||||
|
Rc::new(RefCell::new(sim.create_context(format!("balancer_{i}")))),
|
||||||
|
))));
|
||||||
|
sim.add_handler(format!("balancer_{i}"), balancers[i as usize].clone());
|
||||||
|
ctx.emit(SyncEvent {}, sim.lookup_id(&format!("balancer_{i}")), 0.0);
|
||||||
|
}
|
||||||
|
let mut rng = Pcg64::seed_from_u64(args.seed);
|
||||||
|
let mut routing_rng = Pcg64::seed_from_u64(args.seed.wrapping_add(1));
|
||||||
|
for sender in 0..(args.senders as usize) {
|
||||||
|
let distr = Exp::new(if args.preset == "skewed" && sender == 0 {
|
||||||
|
50.0
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let mut t = 0f64;
|
||||||
|
loop {
|
||||||
|
t += distr.sample(&mut rng) * 5.;
|
||||||
|
if t >= arrivals_end {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let processing_time = rng.gen_range(0.5..2.5);
|
||||||
|
ctx.emit(
|
||||||
|
RequestArrivalEvent {
|
||||||
|
processing_time,
|
||||||
|
sender,
|
||||||
|
},
|
||||||
|
sim.lookup_id(&format!(
|
||||||
|
"balancer_{}",
|
||||||
|
routing_rng.gen_range(0..balancers.len())
|
||||||
|
)),
|
||||||
|
t,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Periodic updates stop at arrivals_end. Drain every queued request so that
|
||||||
|
// slow responses are measured in full, not truncated at a time cutoff.
|
||||||
|
sim.step_until_no_events();
|
||||||
|
let requests = log
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.map(|&(end, start)| (start, end - start))
|
||||||
|
.collect();
|
||||||
|
windowed_p99(requests, args.time_limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
let configs: Vec<Config> =
|
||||||
|
serde_json::from_reader(File::open(Path::new(&args.config)).unwrap()).unwrap();
|
||||||
|
let mut out = BufWriter::new(File::create(&args.dump).unwrap());
|
||||||
|
for config in configs.iter() {
|
||||||
|
let result = run_simulation(&args, config);
|
||||||
|
out.write_all(config.balancer.as_bytes()).unwrap();
|
||||||
|
out.write_all(b"\n").unwrap();
|
||||||
|
out.write_all(
|
||||||
|
result
|
||||||
|
.iter()
|
||||||
|
.map(|x| format!("{:.3}", x.0))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",")
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
out.write_all(b"\n").unwrap();
|
||||||
|
out.write_all(
|
||||||
|
result
|
||||||
|
.iter()
|
||||||
|
.map(|x| format!("{}", x.1))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",")
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
out.write_all(b"\n").unwrap();
|
||||||
|
}
|
||||||
|
out.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overloaded_server_finishes_requests_after_arrival_horizon() {
|
||||||
|
let args = Args::parse_from([
|
||||||
|
"lab",
|
||||||
|
"--dump",
|
||||||
|
"unused",
|
||||||
|
"--config",
|
||||||
|
"unused",
|
||||||
|
"--servers",
|
||||||
|
"1",
|
||||||
|
"--senders",
|
||||||
|
"100",
|
||||||
|
"--time-limit",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
let config = Config {
|
||||||
|
balancer: "Hash".into(),
|
||||||
|
n_balancers: 1,
|
||||||
|
sync_interval: 0.01,
|
||||||
|
};
|
||||||
|
let result = run_simulation(&args, &config);
|
||||||
|
assert!(!result.is_empty());
|
||||||
|
assert!(result.iter().all(|(t, _)| *t <= args.time_limit));
|
||||||
|
assert!(result[0].1 > args.time_limit + WINDOW);
|
||||||
|
assert_eq!(result, run_simulation(&args, &config));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
pub const WINDOW: f64 = 60.0;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
struct RequestTime {
|
||||||
|
time: f64,
|
||||||
|
id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for RequestTime {}
|
||||||
|
|
||||||
|
impl Ord for RequestTime {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.time
|
||||||
|
.total_cmp(&other.time)
|
||||||
|
.then(self.id.cmp(&other.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for RequestTime {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Nearest-rank p99 of response times for requests arriving in [t, t + WINDOW).
|
||||||
|
/// Emit one point per distinct arrival time t <= time_limit.
|
||||||
|
pub fn windowed_p99(mut requests: Vec<(f64, f64)>, time_limit: f64) -> Vec<(f64, f64)> {
|
||||||
|
requests.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut items = BTreeSet::new();
|
||||||
|
let mut end = 0;
|
||||||
|
let mut start = 0;
|
||||||
|
while start < requests.len() && requests[start].0 <= time_limit {
|
||||||
|
let time = requests[start].0;
|
||||||
|
while end < requests.len() && requests[end].0 < time + WINDOW {
|
||||||
|
items.insert(RequestTime {
|
||||||
|
time: requests[end].1,
|
||||||
|
id: end,
|
||||||
|
});
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
// ceil(0.99 * n), computed exactly, without floating-point rounding.
|
||||||
|
let rank = items.len() - items.len() / 100;
|
||||||
|
let p99 = items.iter().rev().nth(items.len() - rank).unwrap().time;
|
||||||
|
result.push((time, p99));
|
||||||
|
// Equal response times remain distinct observations thanks to id.
|
||||||
|
// Remove the entire arrival-time group before advancing the window.
|
||||||
|
while start < requests.len() && requests[start].0 == time {
|
||||||
|
items.remove(&RequestTime {
|
||||||
|
time: requests[start].1,
|
||||||
|
id: start,
|
||||||
|
});
|
||||||
|
start += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expires_old_requests_and_excludes_right_boundary() {
|
||||||
|
let requests = vec![
|
||||||
|
(0.0, 100.0),
|
||||||
|
(1.0, 10.0),
|
||||||
|
(61.0, 1.0),
|
||||||
|
(62.0, 1.0),
|
||||||
|
(123.0, 3.0),
|
||||||
|
];
|
||||||
|
assert_eq!(
|
||||||
|
windowed_p99(requests, 123.0),
|
||||||
|
vec![
|
||||||
|
(0.0, 100.0),
|
||||||
|
(1.0, 10.0),
|
||||||
|
(61.0, 1.0),
|
||||||
|
(62.0, 1.0),
|
||||||
|
(123.0, 3.0)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uses_nearest_rank_and_counts_equal_response_times() {
|
||||||
|
let mut requests = vec![(0.0, 1.0); 99];
|
||||||
|
requests.push((0.0, 100.0));
|
||||||
|
assert_eq!(windowed_p99(requests, 0.0), vec![(0.0, 1.0)]);
|
||||||
|
let requests = (1..=100).map(|n| (0.0, n as f64)).collect();
|
||||||
|
assert_eq!(windowed_p99(requests, 0.0), vec![(0.0, 99.0)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sorts_arrivals_and_limits_reported_times() {
|
||||||
|
assert_eq!(
|
||||||
|
windowed_p99(vec![(120.0, 8.0), (60.0, 2.0), (0.0, 4.0)], 60.0),
|
||||||
|
vec![(0.0, 4.0), (60.0, 2.0)]
|
||||||
|
);
|
||||||
|
assert!(windowed_p99(Vec::new(), 60.0).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn matches_independent_sort_per_window_reference() {
|
||||||
|
let requests: Vec<_> = (0..400)
|
||||||
|
.map(|i| (((i * 37) % 241) as f64, ((i * 17) % 31 + 1) as f64))
|
||||||
|
.collect();
|
||||||
|
let mut times: Vec<_> = requests
|
||||||
|
.iter()
|
||||||
|
.map(|r| r.0)
|
||||||
|
.filter(|t| *t <= 180.0)
|
||||||
|
.collect();
|
||||||
|
times.sort_by(f64::total_cmp);
|
||||||
|
times.dedup();
|
||||||
|
let expected: Vec<_> = times
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| {
|
||||||
|
let mut values: Vec<_> = requests
|
||||||
|
.iter()
|
||||||
|
.filter(|r| r.0 >= t && r.0 < t + WINDOW)
|
||||||
|
.map(|r| r.1)
|
||||||
|
.collect();
|
||||||
|
values.sort_by(f64::total_cmp);
|
||||||
|
let rank = (0.99 * values.len() as f64).ceil() as usize;
|
||||||
|
(t, values[rank - 1])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert_eq!(windowed_p99(requests, 180.0), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
image: nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
||||||
|
|
||||||
|
server1:
|
||||||
|
image: hello
|
||||||
|
build:
|
||||||
|
context: hello-world
|
||||||
|
command: 8000 "Alice"
|
||||||
|
ports:
|
||||||
|
- 8001:8000
|
||||||
|
|
||||||
|
server2:
|
||||||
|
image: hello
|
||||||
|
build:
|
||||||
|
context: hello-world
|
||||||
|
command: 8000 "Bob"
|
||||||
|
ports:
|
||||||
|
- 8002:8000
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
RUN pip install Flask==2.3.3
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENTRYPOINT ["python3", "main.py"]
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from flask import Flask
|
||||||
|
import sys
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print('please call with arguments <port> <hostname>')
|
||||||
|
exit()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return f'Hello from {app.name}\n'
|
||||||
|
|
||||||
|
app.name = sys.argv[2]
|
||||||
|
app.run(host='0.0.0.0', port=int(sys.argv[1]))
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
upstream rr_backend {
|
||||||
|
server server2:8000;
|
||||||
|
server server1:8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8000;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://rr_backend;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Пример распределения нагрузки с помощью Nginx
|
||||||
|
|
||||||
|
В *hello-world/main.py* написан простейший hello-world сервер, который отвечает хостом, указанном при запуске сервера в формате `python3 main.py <HOST>`.
|
||||||
|
|
||||||
|
В *nginx.conf* приведен пример конфигурации Nginx, который распределяет нагрузку между двумя инстансами hello-world сервера. Nginx работает как HTTP-прокси: принимает запрос клиента, отправляет его бэкенду и возвращает ответ клиенту. Stateless-логика приложения при этом совместима с TCP-соединениями на обоих участках.
|
||||||
|
|
||||||
|
Другие стратегии балансировки: https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/
|
||||||
|
|
||||||
|
Запуск примера:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build
|
||||||
|
docker compose up
|
||||||
|
|
||||||
|
for i in {0..20}; do curl localhost:8000; done
|
||||||
|
```
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Балансировка трафика и шардирование
|
||||||
|
|
||||||
|
На семинаре разбираем, как распределять запросы между серверами и данные между узлами хранения, чтобы масштабировать сервисы при росте нагрузки и объёма данных. Сравнить алгоритмы балансировки можно в [лабораторной работе](./load-balancing-lab/), а запустить небольшие сервисы — в примерах [Nginx](./nginx-balancing/) и [key-value](./kv/).
|
||||||
|
|
||||||
|
## Балансировка трафика
|
||||||
|
|
||||||
|
При росте "читающей" нагрузки на сервис одно из простых решений — запустить несколько копий
|
||||||
|
сервиса и научиться распределять нагрузку между ними. Балансировщик принимает запрос или пакет и выбирает сервер для его обработки. На выбор могут влиять адрес клиента, загрузка и производительность
|
||||||
|
серверов, а также состояние уже установленных соединений.
|
||||||
|
|
||||||
|
При переходе к модели с несколькими копиями сервиса необходимо продумать,
|
||||||
|
нужно ли обмениваться информацией между данными копиями. Как правило, проще всего балансировать
|
||||||
|
нагрузку в случае реплицированного и/или stateless-сервиса.
|
||||||
|
|
||||||
|
Одним из вопросов при выборе балансировщика нагрузки является вопрос: на каком "уровне" вы хотите
|
||||||
|
балансировать нагрузку? L3/L4-балансировщики работают на третьем или четвёртом уровне модели OSI и распределяют
|
||||||
|
входящие TCP- и UDP-пакеты. На данном уровне мы не знаем о клиенте ничего, кроме его IP-адреса и порта,
|
||||||
|
и не смотрим на содержимое запросов, поэтому балансировка менее точная, но более легковесная.
|
||||||
|
L7-балансировщики оперируют на уровне протоколов приложений, например HTTP. Данные балансировщики более
|
||||||
|
гибкие и имеют больше информации о пользователе. Также достаточно много полезной информации можно
|
||||||
|
получить из заголовков HTTP-запроса (например, идентификатор пользователя). Это позволяет сделать
|
||||||
|
балансировку более точной, но балансировщик начинает выполнять больше задач, из-за чего требуется
|
||||||
|
больше ресурсов.
|
||||||
|
|
||||||
|
### Запросы и соединения
|
||||||
|
|
||||||
|
Stateless-приложение не требует хранить состояние клиентской сессии в конкретном экземпляре между запросами. Оно может работать поверх TCP: состояние транспортного соединения и состояние приложения — разные вещи. Например, HTTP-прокси может распределять независимые запросы между серверами, поддерживая собственные TCP-соединения с ними.
|
||||||
|
|
||||||
|
UDP также не означает отсутствие состояния: HTTP/3 работает поверх QUIC, который поддерживает соединения, используя UDP. См. [HTTP/3](https://www.rfc-editor.org/rfc/rfc9114.html) и [QUIC](https://www.rfc-editor.org/rfc/rfc9000.html).
|
||||||
|
|
||||||
|
При балансировке TCP-пакетов одного соединения они должны попадать на сервер, который знает состояние этого соединения. Для этого можно хранить таблицу соединений или детерминированно выбирать сервер по адресам и портам. Изменение набора серверов требует осторожности: оно может изменить выбор для уже открытых соединений. Короткий таймаут таблицы сам по себе эту задачу не решает.
|
||||||
|
|
||||||
|
DNS помогает выбрать адрес перед установлением соединения. Новый DNS-ответ не меняет удалённый адрес существующего TCP-соединения. При anycast один адрес объявляют из нескольких мест, а путь к нему выбирает сеть; изменение этого пути — отдельная причина попадания пакетов на другой балансировщик.
|
||||||
|
|
||||||
|
Ответ может возвращаться через прокси или идти напрямую от бэкенда клиенту — схема Direct Server Return/Response (DSR). Подход зависит от архитектуры. Например, в нашем [демо Nginx](./nginx-balancing/) ответы проходят через HTTP-прокси. Подробнее: [Google SRE: балансировка на входе](https://sre.google/sre-book/load-balancing-frontend/).
|
||||||
|
|
||||||
|
### Алгоритмы выбора сервера
|
||||||
|
|
||||||
|
| Алгоритм | Как выбирается сервер |
|
||||||
|
| --- | --- |
|
||||||
|
| **Round robin** | Серверы перебираются по кругу. |
|
||||||
|
| **Random** | Для каждого выбора берётся случайный сервер. Даже равномерный случайный выбор даёт колебания нагрузки. |
|
||||||
|
| **Least connections** | Выбирается сервер с наименьшим числом активных соединений. **LeastLoaded** в лабораторной работе использует другую метрику — число незавершённых запросов. |
|
||||||
|
| **Power of Two Choices** | Выбираются два случайных сервера, затем менее загруженный из них по выбранной метрике. Обобщение на k кандидатов называется **Power of k Choices**. |
|
||||||
|
| **IP hashing** | Сервер выбирается детерминированно по адресу клиента, чтобы его запросы попадали на один бэкенд при неизменном составе серверов. |
|
||||||
|
| **Consistent hashing** | Устойчивое отображение ключа на сервер уменьшает число переназначений при изменении состава серверов. Применяется и в балансировке, и в шардировании. |
|
||||||
|
|
||||||
|
Серверы могут иметь разные мощности; тогда в выборе учитывают веса. Число соединений или запросов не всегда точно отражает нагрузку: запросы различаются по стоимости, а сведения о состоянии успевают устареть. Несколько балансировщиков, видя один и тот же наименее загруженный сервер, могут одновременно направить на него много запросов.
|
||||||
|
|
||||||
|
Балансировщику также нужны сведения о доступности бэкендов. Активные проверки здоровья и наблюдение за ошибками обычных запросов позволяют временно исключать проблемные экземпляры. Например, [Keepalived](https://github.com/acassen/keepalived#readme) поддерживает проверки здоровья и VRRP для переключения виртуального адреса; его название не следует путать с TCP keepalive.
|
||||||
|
|
||||||
|
В Nginx, Envoy и HAProxy многие из этих алгоритмов уже реализованы. В [лабораторной работе](./load-balancing-lab/) мы сравним их на общем наборе серверов при разной нагрузке и частоте обновления сведений.
|
||||||
|
|
||||||
|
## Шардирование и репликация
|
||||||
|
|
||||||
|
Шардирование делит данные на части — **шарды**. Оно нужно, когда данные или нагрузка на их обработку уже не помещаются на одном сервере. **Репликация** создаёт несколько копий данных для доступности и надёжности. Эти приёмы можно сочетать: каждый логический шард хранить в нескольких репликах.
|
||||||
|
|
||||||
|
Шард, реплика и физический сервер не обязательно соответствуют друг другу один к одному. Один сервер может хранить несколько шардов и реплики разных частей данных. Есть и схемы, где набор узлов хранения выбирается отдельно для каждого ключа; тогда эти наборы могут пересекаться.
|
||||||
|
|
||||||
|
<p align="center"><img src="./content/sharding.png" width="100%" /></p>
|
||||||
|
|
||||||
|
Ключ шардирования позволяет детерминированно определить, где находятся нужные данные. В [демо kv](./kv/) можно отдельно сравнить шардирование и репликацию; сочетание обоих подходов в нём не реализовано.
|
||||||
|
|
||||||
|
### Остаток от деления
|
||||||
|
|
||||||
|
Простой вариант: `shard_id = hash(key) % num_shards`. При равномерном хеше ключи распределяются по шардам примерно равномерно, но при изменении числа шардов приходится перемещать большую часть данных. Популярность отдельных ключей при этом может быть очень разной: равное число ключей не означает равную нагрузку.
|
||||||
|
|
||||||
|
Пример реализации: [simple-sharding](./simple-sharding/).
|
||||||
|
|
||||||
|
### Consistent hash ring
|
||||||
|
|
||||||
|
Представим пространство хешей как кольцо. Хеш идентификатора узла задаёт его позицию на кольце, а хеш ключа — позицию данных. Для ключа выбираем ближайший узел по часовой стрелке. Можно договориться и об обратном направлении; все участники должны применять одно правило.
|
||||||
|
|
||||||
|
<p align="center"><img src="./content/ring_composition.png" width="100%" /></p>
|
||||||
|
|
||||||
|
*[Источник иллюстрации](https://medium.com/@kewei.train/deterministic-aperture-algorithm-%E7%AD%86%E8%A8%98-3a3bbb5e30e2)*
|
||||||
|
|
||||||
|
При добавлении узла к нему переходит часть ключей соседнего участка; при удалении его участок переходит следующему узлу. При равномерном хешировании и фиксированном коэффициенте репликации ожидаемый объём перемещений имеет порядок N / n, где N — объём данных, n — число узлов.
|
||||||
|
|
||||||
|
Одна случайная точка на физический сервер может давать заметный перекос размеров участков. Поэтому одному серверу назначают несколько **виртуальных узлов** — позиций на кольце. Сумма нескольких участков обычно распределена ровнее; число виртуальных узлов можно выбирать с учётом мощности сервера.
|
||||||
|
|
||||||
|
Если нужны r копий ключа, обходят кольцо и выбирают r разных физических узлов. Виртуальные позиции, принадлежащие уже выбранному серверу, пропускают: несколько копий на одной машине не защищают от её отказа. Пример такой схемы описан в [Dynamo, разделы 4.2–4.3](https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf).
|
||||||
|
|
||||||
|
## Полезные материалы
|
||||||
|
|
||||||
|
- [Оригинальная статья про балансер Твиттера](https://blog.twitter.com/engineering/en_us/topics/infrastructure/2019/daperture-load-balancer.html)
|
||||||
|
- [Описание алгоритмов балансировки в envoy proxy](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/load_balancers)
|
||||||
|
- [Load Balancing in the Datacenter](https://sre.google/sre-book/load-balancing-datacenter/)
|
||||||
|
- [Power of 2 choices](https://www.eecs.harvard.edu/~michaelm/postscripts/mythesis.pdf)
|
||||||
|
- [Test Driving “Power of Two Random Choices” Load Balancing](https://www.haproxy.com/blog/power-of-two-load-balancing/)
|
||||||
|
- [A Guide to Consistent Hashing](https://www.toptal.com/big-data/consistent-hashing)
|
||||||
|
- [Consistent hashing](http://michaelnielsen.org/blog/consistent-hashing/)
|
||||||
|
- [Consistent Hashing: Algorithmic Tradeoffs](https://dgryski.medium.com/consistent-hashing-algorithmic-tradeoffs-ef6b8e2fcae8)
|
||||||
|
- [Sharding with consistent hashing](https://blog.yugabyte.com/four-data-sharding-strategies-we-analyzed-in-building-a-distributed-sql-database/) и [cassandra partitioning (секция 5)](https://www.cs.cornell.edu/projects/ladis2009/papers/lakshman-ladis2009.pdf)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
# put your shards for experiments here
|
||||||
|
shards = {
|
||||||
|
'server1': 'http://localhost:8001/',
|
||||||
|
'server2': 'http://localhost:8002/'
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = [
|
||||||
|
'aasdjkahsdl',
|
||||||
|
'clkjhlkajhsd',
|
||||||
|
'basdjahlkj',
|
||||||
|
'dakjshd',
|
||||||
|
]
|
||||||
|
|
||||||
|
MAX_HASH = 1000000
|
||||||
|
|
||||||
|
def hashf(x: str):
|
||||||
|
# sum of ascii codes
|
||||||
|
return sum([ord(c) for c in x]) % MAX_HASH
|
||||||
|
|
||||||
|
def get_shard(k, shards):
|
||||||
|
i = hashf(k) % len(shards)
|
||||||
|
return list(shards)[i]
|
||||||
|
|
||||||
|
|
||||||
|
def call_by_key(key):
|
||||||
|
shard = get_shard(key, shards.keys())
|
||||||
|
resp = requests.get(shards[shard])
|
||||||
|
return resp.text
|
||||||
|
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
for k in keys:
|
||||||
|
print(call_by_key(k))
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Пример шардирования
|
||||||
|
|
||||||
|
Обычно под шардированием понимают "распределение чего-либо по множеству узлов (*шардов*) детерминированно и постоянно для указанного ключа". Например, шардирование данных в key-value хранилище обычно происходит на основе ключа. Шардирование запросов между http серверами может происходить на основе имени пользователя или его id. Такое свойство обычно называют *ключом шардирования*.
|
||||||
|
|
||||||
|
В этом примере написан самый простой пример шардирования запросов: в словаре указываются url куда слать запрос и имена серверов. Ниже указан список ключей для проверки алгоритма шардирования. Для каждого ключа скрипт выбирает сервер и отправляет на него get запрос, после чего печатает результат в *stdout*.
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
# 1. Запустить какие-то сервера, которые будут принимать запрос. Например, [hello-world](../nginx-balancing/README.md) из примера с nginx или [key-value](../kv/README.md) из примера с шардированием ключей.
|
||||||
|
cd ../nginx-balancing
|
||||||
|
docker compose up
|
||||||
|
|
||||||
|
# 2. Поправить словарь shards, добавить туда свои url (если они отличны от уже указанных)
|
||||||
|
|
||||||
|
# 3. Запустить скрипт, выполняющий запросы
|
||||||
|
python3 client.py
|
||||||
|
|
||||||
|
# 4. Посмотреть на результаты, что для указанного списка ключей запросы были сделаны на разные сервера
|
||||||
|
```
|
||||||
Reference in new issue
Block a user