Версия Python: Redis на одном компьютере достигает пиков для предотвращения переполнения.

Redis задняя часть Python Gevent
Версия Python: Redis на одном компьютере достигает пиков для предотвращения переполнения.

тестовая среда

  • ubuntu 16.04
  • python 3.6.6
  • redis 3.0.6

Краткое описание

Срочные покупки и всплески являются очень распространенным сценарием применения.Есть две основные проблемы, которые необходимо решить:

  • 1 высокий параллелизм
  • 2 Как правильно решить проблему сокращения запасов (проблема перепроданности)

описание команды redis

exists

Возвращает, существует ли ключ

# 原型
EXISTS key [key ...]
# 示例
EXISTS key1

incrby

Добавьте декремент к числу, соответствующему ключу. Если ключ не существует, ключ будет установлен в 0 перед операцией

# 原型
INCRBY key increment
# 示例
INCRBY mykey 5

setnx

Установите ключ в значение, если ключ не существует, этот случай эквивалентен команде SET. Когда ключ существует, ничего не делайте. SETNX — это сокращение от «SET if Not eXists».

# 原型
SETNX key value
# 示例
SETNX mykey "Hello"

блок-схема

流程.png

Ключевые моменты процесса:

  1. Здесь нам нужно использовать setnx для инициализации данных, чтобы избежать установки значения ключа несколько раз, когда он сталкивается с параллелизмом.
  2. Только после incrby можно судить, превышает ли он лимит.Обратите внимание, что если максимальное количество равно 100, оно может быть больше 100 после incrby, но это не влияет на решение.

реализация кода на питоне

import logging
from logging import handlers

import redis
from flask import Flask

# 为了方便查看添加日志
rf_handler = handlers.TimedRotatingFileHandler(
    'redis.log', when='midnight', interval=1, backupCount=7)
rf_handler.setFormatter(
    logging.Formatter(
        "%(asctime)s %(filename)s line:%(lineno)d [%(levelname)s] %(message)s")
)
logging.getLogger().setLevel(logging.INFO)
logging.getLogger().addHandler(rf_handler)

app = Flask(__name__)

# connect redis
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)


def limit_handler():
    """
    return True: 允许; False: 拒绝
    """
    amount_limit = 100  # 限制数量
    keyname = 'limit'  # redis key name
    incr_amount = 1  # 每次增加数量

    # 判断key是否存在
    if not r.exists(keyname):
        # 为了方便测试,这里设置默认初始值为95
        # setnx可以防止并发时多次设置key
        r.setnx(keyname, 95)

    # 数据插入后再判断是否大于限制数
    if r.incrby(keyname, incr_amount) <= amount_limit:
        return True

    return False


@app.route("/limit")
def v2():
    if limit_handler():
        logging.info("successful")
    else:
        logging.info("failed")
    return 'limit'


if __name__ == '__main__':
    app.run(debug=True)

простой тест

тестовый инструмент ab

  • Установить
sudo apt install apache2-utils
  • тестовая команда
ab -c 100 -n 200 http://127.0.0.1:5000/limit
# -c表示并发数, -n表示请求数

Результаты теста

  • По логу видно, что только максимум 5 успешных
    result.png

Тест развертывания

Решение: супервизор + пушка + гевент

  1. Установить зависимости
apt-get install supervisor
pip install gevent
pip install gunicorn

2. Создайте конфигурацию

echo_supervisord_conf > supervisord.conf

3. Измените конфигурацию и добавьте ее в конец supervisord.conf.

[program:redis-limit]
directory = /home/dong/projects/py ; 程序的启动目录
command = gunicorn -k gevent -w 4 -b 0.0.0.0:5000 app:app  ; 启动命令, 使用gevent, 开启4个进程
autostart = true     ; 在 supervisord 启动的时候也自动启动
startsecs = 5        ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true   ; 程序异常退出后自动重启
startretries = 3     ; 启动失败自动重试次数,默认是 3
redirect_stderr = true  ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 20MB  ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20     ; stdout 日志文件备份数
stdout_logfile = /home/dong/projects/py/limit_stdout.log

4. Запустите службу супервизора

supervisord -c ./supervisord.conf

5. Проверьте приложение супервизора

# 如果没有启动可以手动start redis-limit
supervisorctl -c ./supervisord.conf

6. Тест

ab -c 100 -n 200 http://127.0.0.1:5000/limit

Ссылаться на