增加范例
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"app_name": "Redis Stream Demo",
|
||||
|
||||
"redis_desc": "Redis 数据库连接配置及相关描述",
|
||||
"redis": {
|
||||
"connection": {
|
||||
"url": "redis://:HaitenRedis@20250703@100.64.0.1:3379/2",
|
||||
"max_connections": 1000,
|
||||
"encoding": "utf-8",
|
||||
"decode_responses": true
|
||||
},
|
||||
"streams": {
|
||||
"demo": {
|
||||
"group": "DEMO_PROCESSORS",
|
||||
"consumer": "demo_worker"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"logger_desc": "用于日志输出的配置,各服务可以有自己的配置,但要使用独立配置时,必须编写额外代码",
|
||||
"logger": {
|
||||
"default": {
|
||||
"desc": "默认日志配置,该配置小节的名称已经配置在 PASTE 框架中",
|
||||
"basic": {
|
||||
"filename": "logs/root.log",
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"level": 20
|
||||
},
|
||||
"filename": "logs/default.log",
|
||||
"name": "Demo",
|
||||
"max_bytes": 20971520,
|
||||
"backup_count": 40
|
||||
}
|
||||
},
|
||||
|
||||
"tornado_desc": "用于 Tornado 服务的配置,每一项后面允许设置多个服务",
|
||||
"tornado": {
|
||||
"demo": {
|
||||
"autoreload": false,
|
||||
"handlers_pkg": "examples.03_redis_stream",
|
||||
"port": 9000,
|
||||
"static_path": "static",
|
||||
"template_path": "templates",
|
||||
"swagger_title": "DemoAPI",
|
||||
"swagger_description": "Demo API",
|
||||
"swagger_api_version": "1.0.1",
|
||||
"swagger_contact": "email@qq.com"
|
||||
}
|
||||
},
|
||||
|
||||
"version": "1.0.1"
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
from paste.core.logging import echo_log
|
||||
from paste.db.redis import StreamActor
|
||||
from paste.web.decorators import route
|
||||
from paste.web.handler import RequestHandler
|
||||
|
||||
@route("/stream")
|
||||
class MessageHandler(RequestHandler):
|
||||
"""
|
||||
演示请求发布 Redis Stream 消息。
|
||||
"""
|
||||
|
||||
# 从配置中加载 Stream 配置路径
|
||||
stream_config_path = "redis.streams.demo"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 初始化 StreamActor 实例(按配置创建)
|
||||
self.actor = StreamActor.new_actor(self.stream_config_path)
|
||||
|
||||
async def post(self):
|
||||
"""
|
||||
接收前端 POST 请求,发布消息到 Redis Stream,立即响应。
|
||||
请求体格式:
|
||||
{
|
||||
"user_id": "123",
|
||||
"event": "login",
|
||||
"data": {"ip": "192.168.1.1"}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# 1. 获取请求参数
|
||||
body = self.request_arguments()
|
||||
user_id = body.get("user_id")
|
||||
event = body.get("event")
|
||||
data = body.get("data", {})
|
||||
|
||||
if not user_id or not event:
|
||||
self.response_error(
|
||||
Exception("参数缺失:必须提供 user_id 和 event"),
|
||||
status_code=400,
|
||||
api_status_code=400
|
||||
)
|
||||
return
|
||||
|
||||
# 2. 构造消息数据
|
||||
message_data = {
|
||||
"user_id": user_id,
|
||||
"event": event,
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat() + 'Z',
|
||||
"data": json.dumps(data)
|
||||
}
|
||||
|
||||
# 3. 异步发布消息(立即返回,不等待消费)
|
||||
msg_id = await self.actor.publish(message_data)
|
||||
|
||||
# 4. 响应成功
|
||||
self.response_ok(
|
||||
message="消息已成功发布",
|
||||
message_id=msg_id,
|
||||
stream=self.stream_config_path
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
echo_log('异常', logging.ERROR, True)
|
||||
self.response_error(e, status_code=500, api_status_code=500)
|
||||
@@ -0,0 +1,20 @@
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from paste.core import config
|
||||
from paste.core.logging import set_logger_config
|
||||
from paste.web.application import Application
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 日志配置
|
||||
logger_config_name = 'logger.default'
|
||||
set_logger_config(logger_config_name)
|
||||
# 应用配置
|
||||
demo_config: dict = config.get_config('tornado.demo', {})
|
||||
port = config.get_config('tornado.demo.port', 9000)
|
||||
# 创建应用
|
||||
app = Application(**demo_config)
|
||||
app.listen(port)
|
||||
handlers_pkg = config.get_config('tornado.demo.handlers_pkg')
|
||||
print(f"App {handlers_pkg} is running at http://localhost:{port}")
|
||||
# 启动监听
|
||||
IOLoop.current().start()
|
||||
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
演示 Redis Stream 消息队列服务。
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import redis
|
||||
|
||||
from paste.core import aio_pool
|
||||
from paste.core.logging import set_logger_config, echo_log, get_logger
|
||||
from paste.db.redis import StreamActor
|
||||
from paste.service.daemonize import DaemonizeService
|
||||
|
||||
logger_config_name = 'logger.default'
|
||||
"""
|
||||
配置文件中日志配置字段名称。
|
||||
"""
|
||||
|
||||
current_event_loop = None
|
||||
"""
|
||||
事件循环对象。
|
||||
"""
|
||||
|
||||
pid_file = os.path.join(os.path.curdir, 'stream_service.pid')
|
||||
"""
|
||||
PID 文件路径。
|
||||
"""
|
||||
|
||||
service_name = 'Redis Stream 消息队列服务'
|
||||
"""
|
||||
服务名称。
|
||||
"""
|
||||
|
||||
# 配置路径:从 config.json 中读取
|
||||
stream_config_path = "redis.streams.demo"
|
||||
|
||||
# 创建 StreamActor 实例
|
||||
stream_actor: Optional[StreamActor] = None
|
||||
|
||||
|
||||
async def process_message(data: dict):
|
||||
"""
|
||||
业务回调:处理每条消息
|
||||
"""
|
||||
user_id = data.get("user_id", "unknown")
|
||||
event = data.get("event", "")
|
||||
stream_data = data.get("data", "")
|
||||
timestamp = data.get("timestamp", "")
|
||||
|
||||
echo_log(f"消费消息: user_id={user_id}, event='{event}', stream_data='{stream_data}', time={timestamp}")
|
||||
|
||||
# 模拟处理:写入数据库、发送邮件、更新缓存...
|
||||
# 示例:记录日志 + 模拟耗时
|
||||
for i in range(10):
|
||||
echo_log(f"后台任务开始执行-{i}...")
|
||||
await asyncio.sleep(0.8)
|
||||
|
||||
echo_log(f"消息处理完成: {user_id}")
|
||||
return True
|
||||
|
||||
|
||||
def current_loop() -> asyncio.AbstractEventLoop:
|
||||
"""
|
||||
这里必须采用方法,在适当的时间点创建事件循环对象,否则会导致服务无法启动。
|
||||
:return: 事件循环对象
|
||||
"""
|
||||
global current_event_loop
|
||||
if current_event_loop is None:
|
||||
current_event_loop = asyncio.new_event_loop()
|
||||
return current_event_loop
|
||||
|
||||
|
||||
def start_service():
|
||||
"""
|
||||
控制台服务方式启动。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
echo_log(f"正在启动{service_name}...")
|
||||
|
||||
try:
|
||||
# 检测 Redis 连接
|
||||
echo_log('检测 Redis 服务...')
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(StreamActor.ping())
|
||||
echo_log('Redis 服务正常.')
|
||||
|
||||
# 创建 StreamActor 监听服务
|
||||
global stream_actor
|
||||
stream_actor = StreamActor.new_actor(stream_config_path)
|
||||
echo_log(f"{service_name}已启动,正在监听{service_name}...")
|
||||
_runner(stream_actor.run_forever(process_message, is_delete=True))
|
||||
except (redis.exceptions.TimeoutError, socket.timeout):
|
||||
echo_log('Redis 服务异常.', level=logging.ERROR, is_log_exc=True)
|
||||
echo_log(f"{service_name}启动失败.")
|
||||
except KeyboardInterrupt:
|
||||
echo_log(msg='KeyboardInterrupt')
|
||||
stop_service()
|
||||
except Exception as e:
|
||||
echo_log(msg=e, level=logging.ERROR, is_log_exc=True)
|
||||
echo_log(f"{service_name}因未知异常启动失败.")
|
||||
|
||||
|
||||
def stop_service():
|
||||
"""
|
||||
停止服务。
|
||||
"""
|
||||
echo_log(f"正在停止{service_name}...")
|
||||
# 停止监听
|
||||
stream_actor.subscribe_stop()
|
||||
# 停止事件循环
|
||||
current_loop().stop()
|
||||
echo_log(f"{service_name}已停止.")
|
||||
|
||||
|
||||
def start():
|
||||
"""
|
||||
驻内存服务方式启动。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
get_logger()
|
||||
ds = DaemonizeService(pid_file=pid_file, name=service_name)
|
||||
ds.set_start_callback(start_service)
|
||||
ds.set_term_callback(stop_service)
|
||||
ds.start()
|
||||
|
||||
|
||||
def stop():
|
||||
"""
|
||||
驻内存服务方式停止。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
get_logger()
|
||||
ds = DaemonizeService(pid_file=pid_file, name=service_name)
|
||||
ds.set_start_callback(start_service)
|
||||
ds.set_term_callback(stop_service)
|
||||
ds.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "start":
|
||||
start_service()
|
||||
elif sys.argv[1] == "stop":
|
||||
stop_service()
|
||||
else:
|
||||
print("用法: python service/stream_service.py start")
|
||||
else:
|
||||
start_service()
|
||||
Reference in New Issue
Block a user