тестовая среда
- 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"
блок-схема
Ключевые моменты процесса:
- Здесь нам нужно использовать setnx для инициализации данных, чтобы избежать установки значения ключа несколько раз, когда он сталкивается с параллелизмом.
- Только после 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 успешных
Тест развертывания
Решение: супервизор + пушка + гевент
- Установить зависимости
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
Ссылаться на
- Командная строка Redis:Woohoo.Redis.Компетенции/команды.Контракты...
- www.imooc.com/learn/1067