Лекция 9

Ахтаров Данил

Tinkoff Python

Python

Микросервисы

Про что говорим

  1. Как работает интерпретатор
  2. Логирование
  3. Микросервисы, сервисы, макросервисы
  4. Архитектура web приложений
  5. Мониторинг
  6. CI/CD
  7. ...

 

Зачем?

Проще диагностировать проблемы

Меньше магии

Всегда знаем куда пойти посмотреть

Что такое интерпретатор?

Достоинство?

Исходный текст

(test.py)

Байт код

(test.pyc)

Выполнение

(PVM)

Зачем?

Компактное представление
Ограниченный набор команд
Можно сразу интерпретировать без дополнительных шагов

Tokenizer

def say_hello():
    print("Hello, World!")

say_hello()
$ python -m tokenize hello.py
0,0-0,0:            ENCODING       'utf-8'
1,0-1,3:            NAME           'def'
1,4-1,13:           NAME           'say_hello'
1,13-1,14:          OP             '('
1,14-1,15:          OP             ')'
1,15-1,16:          OP             ':'
1,16-1,17:          NEWLINE        '\n'
2,0-2,4:            INDENT         '    '
2,4-2,9:            NAME           'print'
2,9-2,10:           OP             '('
2,10-2,25:          STRING         '"Hello, World!"'
2,25-2,26:          OP             ')'
2,26-2,27:          NEWLINE        '\n'
3,0-3,1:            NL             '\n'
4,0-4,0:            DEDENT         ''
4,0-4,9:            NAME           'say_hello'
4,9-4,10:           OP             '('
4,10-4,11:          OP             ')'
4,11-4,12:          NEWLINE        '\n'
5,0-5,0:            ENDMARKER      ''

Можно получить ошибки несоответствия грамматике

Грамматика языка

...
comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
star_expr: '*' expr
expr: xor_expr ('|' xor_expr)*
xor_expr: and_expr ('^' and_expr)*
and_expr: shift_expr ('&' shift_expr)*
shift_expr: arith_expr (('<<'|'>>') arith_expr)*
arith_expr: term (('+'|'-') term)*
...

Abstract Syntax Tree

Можно получить синтаксические ошибки

AST

import ast

tree = ast.parse('print("Hello world")')

tree
# <_ast.Module at 0x10a3ba0b8>

AST

import ast

node = ast.UnaryOp()
node.op = ast.USub()
node.operand = ast.Constant()
node.operand.value = 5
node.operand.lineno = 0
node.operand.col_offset = 0
node.lineno = 0
node.col_offset = 0

Дизассемблирование байткода

import dis

def foo(y):
  x = 1
  return x + y

dis.dis(foo)
  4           0 LOAD_CONST               1 (1)
              2 STORE_FAST               1 (x)

  5           4 LOAD_FAST                1 (x)
              6 LOAD_FAST                0 (y)
              8 BINARY_ADD
             10 RETURN_VALUE

Дизассемблирование байткода

def f(num):
    if num == 42:
        return True
    return False
(1)|(2)|(3)|(4)|          (5)         |(6)|  (7)
---|---|---|---|----------------------|---|-------
  2|   |   |  0|LOAD_FAST             |  0|(num)
   |-->|   |  2|LOAD_CONST            |  1|(42)
   |   |   |  4|COMPARE_OP            |  2|(==)
   |   |   |  6|POP_JUMP_IF_FALSE     | 12|
   |   |   |   |                      |   |
  3|   |   |  8|LOAD_CONST            |  2|(True)
   |   |   | 10|RETURN_VALUE          |   |
   |   |   |   |                      |   |
  4|   |>> | 12|LOAD_CONST            |  3|(False)
   |   |   | 14|RETURN_VALUE          |   |
// ceval.c
for (;;) {
  switch (opcode) {
    case TARGET(NOP): {
      FAST_DISPATCH();
    }
    case TARGET(LOAD_FAST): {
      ...
    }
    case TARGET(LOAD_CONST): {
      PREDICTED(LOAD_CONST);
      PyObject *value = GETITEM(consts, oparg);
      Py_INCREF(value);
      PUSH(value);
      FAST_DISPATCH();
    }
    case TARGET(STORE_FAST): {
      PREDICTED(STORE_FAST);
      PyObject *value = POP();
      SETLOCAL(oparg, value);
      FAST_DISPATCH();
    }
    case TARGET(POP_TOP): {
      ...
    }
    case TARGET(ROT_TWO): {
      ...
    }
    case TARGET(ROT_THREE): {
      ...
    }
    case TARGET(ROT_FOUR): {
      ...
    }
    case TARGET(DUP_TOP): {
      ...
    }
    case TARGET(DUP_TOP_TWO): {
      ...
    }
    case TARGET(UNARY_POSITIVE): {
      ...
    }
    case TARGET(UNARY_NEGATIVE): {
      ...
    }
    case TARGET(UNARY_NOT): {
      ...
    }
    case TARGET(UNARY_INVERT): {
      ...
    }
    case TARGET(BINARY_POWER): {
      ...
    }
    case TARGET(BINARY_MULTIPLY): {
      ...
    }
    case TARGET(BINARY_MATRIX_MULTIPLY): {
      ...
    }
    case TARGET(BINARY_TRUE_DIVIDE): {
      ...
    }
    case TARGET(BINARY_FLOOR_DIVIDE): {
      PyObject *divisor = POP();
      PyObject *dividend = TOP();
      PyObject *quotient = PyNumber_FloorDivide(dividend, divisor);
      Py_DECREF(dividend);
      Py_DECREF(divisor);
      SET_TOP(quotient);
      if (quotient == NULL)
      goto error;
      DISPATCH();
    }
    case TARGET(BINARY_MODULO): {
      ...
    }
    ...
  }
}

CPython

Что есть что в репо?

  • Grammar - граматика питона
  • Include - .h файлы для C кода (здесь же объявляются почти все основные структуры)
  • Lib - стандартная библиотека на питоне
  • Modules - стандартная библиотека на C
  • Objects - объектная система питона, встроенные структуры данных
  • Parser - парсинг исходного кода (до ast)
  • Python - интерпретатор, компилятор байт кода
/* Minimal main program -- everything is loaded from the library */

#include "Python.h"
#include "pycore_pylifecycle.h"

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif

Parser/

  • token.c
  • tokenizer.c
  • parser.c

Python/

  • ast.c
  • ceval.c
// ceval.c
for (;;) {
  switch (opcode) {
    case TARGET(NOP): {
      FAST_DISPATCH();
    }
    case TARGET(LOAD_FAST): {
      ...
    }
    case TARGET(LOAD_CONST): {
      PREDICTED(LOAD_CONST);
      PyObject *value = GETITEM(consts, oparg);
      Py_INCREF(value);
      PUSH(value);
      FAST_DISPATCH();
    }
    case TARGET(STORE_FAST): {
      PREDICTED(STORE_FAST);
      PyObject *value = POP();
      SETLOCAL(oparg, value);
      FAST_DISPATCH();
    }
    case TARGET(POP_TOP): {
      ...
    }
    case TARGET(ROT_TWO): {
      ...
    }
    case TARGET(ROT_THREE): {
      ...
    }
    case TARGET(ROT_FOUR): {
      ...
    }
    case TARGET(DUP_TOP): {
      ...
    }
    case TARGET(DUP_TOP_TWO): {
      ...
    }
    case TARGET(UNARY_POSITIVE): {
      ...
    }
    case TARGET(UNARY_NEGATIVE): {
      ...
    }
    case TARGET(UNARY_NOT): {
      ...
    }
    case TARGET(UNARY_INVERT): {
      ...
    }
    case TARGET(BINARY_POWER): {
      ...
    }
    case TARGET(BINARY_MULTIPLY): {
      ...
    }
    case TARGET(BINARY_MATRIX_MULTIPLY): {
      ...
    }
    case TARGET(BINARY_TRUE_DIVIDE): {
      ...
    }
    case TARGET(BINARY_FLOOR_DIVIDE): {
      PyObject *divisor = POP();
      PyObject *dividend = TOP();
      PyObject *quotient = PyNumber_FloorDivide(dividend, divisor);
      Py_DECREF(dividend);
      Py_DECREF(divisor);
      SET_TOP(quotient);
      if (quotient == NULL)
      goto error;
      DISPATCH();
    }
    case TARGET(BINARY_MODULO): {
      ...
    }
    ...
  }
}

Фреймы

code, args, kwargs, const, etc.

code, args, kwargs, const, etc.

code, args, kwargs, const, etc.

current frame

Модуль

main

sort

sort_file

Получение текущего фрэйма

import inspect

current_frame = inspect.currentframe()

current_frame
# <frame at 0x1068357a8, file '<...>', line 1, code <module>>

current_frame.f_back
# <frame at 0x7fa91b874df8, file './.py', line 2981, code run_code>
>>> import requests
>>> requests.get(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/requests/api.py", line 55, in get
    return request('get', url, **kwargs)
  File "/usr/lib/python3/dist-packages/requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/lib/python3/dist-packages/requests/sessions.py", line 421, in request
    prep = self.prepare_request(req)
  File "/usr/lib/python3/dist-packages/requests/sessions.py", line 359, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "/usr/lib/python3/dist-packages/requests/models.py", line 287, in prepare
    self.prepare_url(url, params)
  File "/usr/lib/python3/dist-packages/requests/models.py", line 338, in prepare_url
    "Perhaps you meant http://{0}?".format(url))
requests.exceptions.MissingSchema: Invalid URL '42': No schema supplied. Perhaps you meant http://42?
// ceval.c
for (;;) {
  switch (opcode) {
    case TARGET(NOP): {
      FAST_DISPATCH();
    }
    case TARGET(LOAD_FAST): {
      ...
    }
    case TARGET(LOAD_CONST): {
      PREDICTED(LOAD_CONST);
      PyObject *value = GETITEM(consts, oparg);
      Py_INCREF(value);
      PUSH(value);
      FAST_DISPATCH();
    }
    case TARGET(STORE_FAST): {
      PREDICTED(STORE_FAST);
      PyObject *value = POP();
      SETLOCAL(oparg, value);
      FAST_DISPATCH();
    }
    case TARGET(POP_TOP): {
      ...
    }
    case TARGET(ROT_TWO): {
      ...
    }
    case TARGET(ROT_THREE): {
      ...
    }
    case TARGET(ROT_FOUR): {
      ...
    }
    case TARGET(DUP_TOP): {
      ...
    }
    case TARGET(DUP_TOP_TWO): {
      ...
    }
    case TARGET(UNARY_POSITIVE): {
      ...
    }
    case TARGET(UNARY_NEGATIVE): {
      ...
    }
    case TARGET(UNARY_NOT): {
      ...
    }
    case TARGET(UNARY_INVERT): {
      ...
    }
    case TARGET(BINARY_POWER): {
      ...
    }
    case TARGET(BINARY_MULTIPLY): {
      ...
    }
    case TARGET(BINARY_MATRIX_MULTIPLY): {
      ...
    }
    case TARGET(BINARY_TRUE_DIVIDE): {
      ...
    }
    case TARGET(BINARY_FLOOR_DIVIDE): {
      PyObject *divisor = POP();
      PyObject *dividend = TOP();
      PyObject *quotient = PyNumber_FloorDivide(dividend, divisor);
      Py_DECREF(dividend);
      Py_DECREF(divisor);
      SET_TOP(quotient);
      if (quotient == NULL)
      goto error;
      DISPATCH();
    }
    case TARGET(BINARY_MODULO): {
      ...
    }
    ...
  }
}

Выводы

  • Python - компилируемый и интерпретируемый язык
  • Интерпретатор - стековый
  • Байт код кэшируется
  • Фреймы создаются всегда при вызове функции
  • Оптимизаций мало

Logging

Зачем?

А что с print-ом не так?

Использование

import logging


logger = logging.getLogger(__name__)


def foo():
  logger.info('Some log text')

Конфигурирование

import logging


logging.basicConfig(filename="file.log", level=logging.INFO)
dictLogConfig = {
  "version":1,
  "handlers":{
    "fileHandler":{
      "class":"logging.FileHandler",
      "formatter":"myFormatter",
      "filename":"config2.log"
    }
  },
  "loggers":{
    "exampleApp":{
      "handlers":["fileHandler"],
      "level":"INFO",
    }
  },
  "formatters":{
    "myFormatter":{
      "format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    }
  }
}

logging.config.dictConfig(dictLogConfig)
[loggers]
keys=root,exampleApp
 
[handlers]
keys=fileHandler, consoleHandler
 
[formatters]
keys=myFormatter
 
[logger_root]
level=CRITICAL
handlers=consoleHandler
 
[logger_exampleApp]
level=INFO
handlers=fileHandler
qualname=exampleApp
 
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=myFormatter
args=(sys.stdout,)
 
[handler_fileHandler]
class=FileHandler
formatter=myFormatter
args=("config.log",)
 
[formatter_myFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
import logging


logging.config.fileConfig('logging.conf')

Уровни логирования

  1. DEBUG
  2. INFO
  3. WARNING
  4. ERROR
  5. CRITICAL

Exception

import logging

logging.basicConfig(filename="filname.log", level=logging.INFO)
logger = logging.getLogger(__name__)

try:
    raise RuntimeError
except RuntimeError:
    logger.exception("Error!")

Форматирование

FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
logging.basicConfig(format=FORMAT)

d = {'clientip': '192.168.0.1', 'user': 'fbloggs'}
logging.warning('Protocol problem: %s', 'connection reset', extra=d)
2006-02-08 22:20:02,165 192.168.0.1 fbloggs  Protocol problem: connection reset

Handlers

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# create the logging file handler
fh = logging.FileHandler("filename.log")

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)

# add handler to logger object
logger.addHandler(fh)

logger.info("Program started")

Всегда используйте logging

Микросервисы

Зачем?

Сервисы

Микросервисы

Worker, Consumer, REST API Server, Connector, ...

Макросервисы

Stateless

Host 1

Host 2

Host 3

External Storages

Message brokers

Databases

Network drive

Messenger

Монолит

Можно обмениваться сообщениями в комнатах (PtP)

Messenger

Messenger

Messenger

DB

Messenger

Authorization

Consumer 1

Consumer 2

Producer 1

Producer 2

Settings

Messages

Producer 3

Авторизация

Зачем?

Использую Basic Auth и не парюсь!

Token

  • 12231244124
  • 8178e09f-4c9b-46c4-b98c-dcd9d3c1e5ca
  • RRFHFGW183H9o5yb5jPH0fLFDEjDF2R2

Пара токенов

Access token - 15 минут (много раз)

Refresh token - 1 неделя (один раз)

Требования

  • Хочу подписывать токен ключом
  • Хочу хранить информацию внутри токена

JWT

json web token

Структура

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
iss, sub, aud, exp, nbf, jti, iat
>>> import jwt

>>> encoded_jwt = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
>>> encoded_jwt
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'

>>> jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
{'some': 'payload'}

OAuth2

Архитектура

Серебряная пуля

Основные компоненты

API Gateway

Auth Service

Storage

Your Service

Вопросы?

Мониторинг

Зачем?

Logging

Метрики

GET /metrics

python_threads_total 10
...
app_version_info 1.0
app_connections_total{role="admin"} 10
app_connections_total{role="user"} 110
...

Metric types

  • Counter
  • Gauge
  • Summary
  • Histogram
  • Info
  • Enum (Current state "stopped")

Визуализации

Alerts

CI/CD

Gitflow

  • master
  • develop
  • feature/*
  • bugfix/*
  • release/*
  • hotfix/*
  • master
  • develop
  • feature/STORY-1
  • bugfix/TASK-1
  • release/v1.0
  • hotfix/v1.0.1
  • TASK-6

Gitflow

Configs

Переменные окружения

Pydantic

from pydantic import BaseSettings


class Config(BaseSettings):
  MAX_SIZE: int = 1024
  KEY: str
  
  class Config:
    env_prefix = 'MY_APP_'
    

config = Config()

The Twelve-Factor App

Двенадцать факторов

  1. Кодовая база
  2. Зависимости
  3. Конфигурация
  4. Сторонние службы (Backing Services)
  5. Сборка, релиз, выполнение
  6. Процессы
  7. Привязка портов (Port binding)
  8. Параллелизм
  9. Утилизируемость (Disposability)
  10. Паритет разработки/работы приложения
  11. Журналирование (Logs)
  12. Задачи администрирования

Tinkoff Python 2020 - 9

By Afonasev Evgeniy

Tinkoff Python 2020 - 9

Как устроен Python интерпретатор. Микросервисы, архитектура построения web приложений.

  • 590