YYeTsBot后端架构深度剖析
本文深入剖析了YYeTsBot后端系统的多数据库架构设计、Tornado Web框架应用、数据同步与采集机制以及缓存策略与性能优化方案。系统采用MongoDB作为主数据存储、Redis作为缓存层、MySQL处理结构化数据的混合架构,结合Tornado异步框架构建高性能API服务,并通过多层次采集系统实现多源数据获取,最后通过Redis缓存和MeiliSearch搜索引擎优化确保系统的高并发处理能力和响应速度。
多数据库架构设计:MongoDB、Redis、MySQL
在YYeTsBot的后端架构中,多数据库协同工作是其核心设计理念之一。系统采用了MongoDB作为主数据存储、Redis作为缓存层、以及MySQL(通过SQLAlchemy)处理结构化数据的混合架构模式,每种数据库都承担着特定的职责,共同构建了一个高性能、可扩展的影视资源管理系统。
数据库架构概览
MongoDB:主数据存储引擎
MongoDB作为系统的核心数据存储,主要负责存储影视资源、评论、豆瓣数据等非结构化数据。其灵活的文档模型完美适应了影视资源的复杂数据结构。
核心集合设计:
| 集合名称 | 数据类型 | 主要字段 |
|---|---|---|
yyets | 影视资源 | data.info.cnname, data.info.enname, data.info.id |
comment | 用户评论 | username, content, resource_id, date |
douban | 豆瓣数据 | resourceId, actors, directors, genres |
subtitle | 字幕信息 | filename, content, resource_id |
MongoDB聚合查询示例:
def __get_yyets(self):
return self.db["yyets"].aggregate([
{"$project": self.yyets_projection},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{"origin": "yyets"},
"$data.info",
{"_id": "$_id"},
]
}
}
},
])
Redis:高性能缓存层
Redis在架构中承担着缓存和会话管理的重任,通过内存存储提供毫秒级的响应速度。
缓存策略实现:
class Redis:
def __init__(self):
self.r = redis_client
super().__init__()
@classmethod
def cache(cls, timeout: int):
def func(fun):
def inner(*args, **kwargs):
func_name = fun.__name__
cache_value = cls().r.get(func_name)
if cache_value:
logging.info("Retrieving %s data from redis", func_name)
return json.loads(cache_value)
else:
logging.info("Cache expired. Executing %s", func_name)
res = fun(*args, **kwargs)
cls().r.set(func_name, json.dumps(res), ex=timeout)
return res
return inner
return func
Redis主要用途:
- 函数结果缓存(自动过期机制)
- 用户会话状态管理
- 频繁访问数据的快速读取
- 分布式锁实现
数据同步与监控机制
系统实现了实时数据变更监控,确保多个数据源之间的数据一致性:
多数据库协同工作流程
class SearchEngine(Mongo):
def __init__(self):
self.search_client = meilisearch.Client(os.getenv("MEILISEARCH"), "masterKey")
self.yyets_index = self.search_client.index("yyets")
self.comment_index = self.search_client.index("comment")
super().__init__()
def run_import(self):
t0 = time.time()
self.add_yyets() # 从MongoDB导入
self.add_comment() # 从MongoDB导入
self.add_douban() # 从MongoDB导入
logging.info(f"Import data to search engine in {time.time() - t0:.2f}s")
性能优化策略
读写分离设计:
- 读操作:优先访问Redis缓存,缓存未命中时查询MongoDB
- 写操作:直接写入MongoDB,同时更新Redis缓存
- 批量处理:使用MongoDB的聚合管道进行复杂查询
索引优化:
yyets_projection = {
"data.info.cnname": 1,
"data.info.enname": 1,
"data.info.aliasname": 1,
"data.info.area": 1,
"data.info.id": 1,
"_id": {"$toString": "$_id"},
"origin": "yyets",
}
容错与恢复机制
系统实现了完善的错误处理和恢复策略:
- 连接池管理:每个数据库连接都包含重试机制
- 故障转移:当主数据库不可用时自动切换到备用方案
- 数据备份:定期备份关键数据到多个存储介质
- 监控告警:实时监控数据库性能指标和健康状态
这种多数据库架构设计不仅提供了高性能的数据访问能力,还确保了系统的可扩展性和可靠性,为YYeTsBot的海量影视资源管理和用户服务提供了坚实的技术基础。
Tornado Web框架应用与API设计
YYeTsBot后端采用Tornado作为核心Web框架,构建了一个高性能、异步非阻塞的影视资源API服务。Tornado的选择充分考虑了项目对高并发请求处理、实时数据同步和高效IO操作的需求。
路由架构设计
项目采用清晰的路由分层架构,将所有API端点组织在统一的handlers模块中:
handlers = [
(r"/", IndexHandler),
(r"/api/resource", ResourceHandler),
(r"/api/download", SubtitleDownloadHandler),
(r"/api/resource/latest", ResourceLatestHandler),
(r"/api/top", TopHandler),
(r"/api/like", LikeHandler),
(r"/api/user", UserHandler),
# ... 更多路由配置
]
这种设计使得API结构清晰可维护,每个端点对应一个专门的Handler类,遵循单一职责原则。
请求处理流程
Tornado的异步处理机制在YYeTsBot中得到了充分应用,整个请求处理流程如下:
核心Handler实现
BaseHandler - 请求预处理基类
所有Handler都继承自BaseHandler,实现了统一的请求预处理机制:
class BaseHandler(tornado.web.RequestHandler):
def prepare(self):
"""请求预处理,包括安全检查、用户认证等"""
self.check_request()
self.get_real_ip()
def check_request(self):
"""检查请求合法性,防止恶意访问"""
if self.request.uri.startswith('/api/'):
self.__ip_check()
self.__user_check()
def get_current_user(self) -> str:
"""获取当前登录用户信息"""
return self.get_secure_cookie("user")
ResourceHandler - 资源API核心
资源处理Handler实现了完整的CRUD操作:
class ResourceHandler(BaseHandler):
async def get(self):
"""获取资源信息"""
resource_id = self.get_argument("id", None)
keyword = self.get_argument("keyword", None)
if resource_id:
data = await self.get_resource_data(resource_id)
elif keyword:
data = await self.search_resource(keyword)
else:
self.set_status(400)
return
self.write(json.dumps(data))
async def post(self):
"""创建新资源"""
if not self.current_user:
self.set_status(401)
return
data = tornado.escape.json_decode(self.request.body)
result = await self.add_resource(data)
self.write(json.dumps(result))
async def patch(self):
"""更新资源信息"""
data = tornado.escape.json_decode(self.request.body)
result = await self.patch_resource(data)
self.write(json.dumps(result))
异步数据库操作
项目充分利用Tornado的异步特性,实现了高效的数据库访问:
async def search_resource(self, keyword: str) -> dict:
"""异步搜索资源"""
try:
# 本地数据库搜索
local_result = await self.mongodb_search(keyword)
if not local_result.get("data"):
# 外部资源站搜索
extra_result = await self.fansub_search(keyword)
local_result["extra"] = extra_result
return local_result
except Exception as e:
logging.error(f"Search error: {e}")
return {"data": [], "extra": []}
中间件与安全机制
频率限制与黑名单
def __ip_check(self):
"""IP检查与黑名单机制"""
ip = self.get_real_ip()
if self.application.blacklist_manager.is_banned(ip):
self.set_status(403)
self.finish("IP banned")
return False
return True
def __user_check(self):
"""用户权限检查"""
user = self.get_current_user()
if user and self.application.user_manager.is_blocked(user):
self.set_status(403)
self.finish("User blocked")
return False
return True
验证码系统
class CaptchaHandler(BaseHandler):
async def get(self):
"""生成验证码"""
captcha_id, image_data = generate_captcha()
self.set_header('Content-Type', 'image/png')
self.write(image_data)
self.set_secure_cookie('captcha_id', captcha_id)
async def post(self):
"""验证验证码"""
user_input = self.get_argument('captcha')
captcha_id = self.get_secure_cookie('captcha_id')
result = verify_captcha(user_input, captcha_id)
self.write(json.dumps(result))
定时任务集成
Tornado与APScheduler结合,实现后台定时任务:
# 定时任务配置
scheduler = BackgroundScheduler(timezone=timez)
scheduler.add_job(Other().reset_top, trigger=CronTrigger.from_crontab("0 0 1 * *"))
scheduler.add_job(sync_douban, trigger=CronTrigger.from_crontab("1 1 1 * *"))
scheduler.add_job(entry_dump, trigger=CronTrigger.from_crontab("2 2 1 * *"))
scheduler.add_job(Other().import_ban_user, "interval", seconds=300)
scheduler.start()
性能优化策略
缓存机制
def cache(timeout: int):
"""方法缓存装饰器"""
def decorator(func):
@functools.wraps(func)
async def wrapper(self, *args, **kwargs):
cache_key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
cached = self.redis.get(cache_key)
if cached:
return json.loads(cached)
result = await func(self, *args, **kwargs)
self.redis.setex(cache_key, timeout, json.dumps(result))
return result
return wrapper
return decorator
数据库连接池
class DatabasePool:
def __init__(self):
self.pool = []
self.max_connections = 10
async def get_connection(self):
"""获取数据库连接"""
if not self.pool:
conn = await self.create_connection()
return conn
return self.pool.pop()
async def release_connection(self, conn):
"""释放连接回池"""
if len(self.pool) < self.max_connections:
self.pool.append(conn)
else:
await conn.close()
API响应格式标准化
所有API响应都遵循统一的格式标准:
{
"status": true,
"message": "success",
"data": {
// 具体业务数据
},
"metadata": {
"timestamp": "2024-01-01T00:00:00Z",
"version": "1.0.0"
}
}
错误响应也保持统一格式:
{
"status": false,
"message": "Resource not found",
"error_code": "RESOURCE_NOT_FOUND",
"details": "The requested resource ID does not exist"
}
监控与日志系统
集成完整的监控和日志记录:
def setup_logger():
"""配置结构化日志"""
logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
这种基于Tornado的架构设计使得YYeTsBot能够处理高并发请求,同时保持良好的代码组织和可维护性。异步非阻塞的特性特别适合IO密集型的影视资源查询场景,为用户提供快速响应和流畅体验。
数据同步与采集机制实现
YYeTsBot 的数据同步与采集系统采用了多层次、分布式的架构设计,实现了对多个字幕组资源的自动化采集、处理和存储。整个系统由离线数据同步、在线采集、缓存机制和数据转换四个核心模块组成。
采集架构设计
系统采用基于类的模块化设计,通过 BaseFansub 抽象基类定义了统一的采集接口规范:
class BaseFansub:
def search_preview(self, search_text: str) -> dict:
"""搜索预览接口,返回简略结果"""
pass
def search_result(self, url_or_hash: str) -> dict:
"""获取详细结果接口"""
pass
def get_html(self, link: str, encoding=None) -> str:
"""通用HTML获取方法"""
pass
这种设计使得每个字幕组都可以通过继承基类并实现特定方法来集成到系统中,实现了良好的扩展性和维护性。
多源数据采集机制
1. 人人影视离线数据采集
人人影视的数据采集采用批量处理模式,通过 BagAndDrag 模块实现:
具体的采集流程通过 drag.py 实现并发获取:
def get_api_json(resource_id):
"""获取单个资源的API数据"""
try:
time.sleep(args.i)
if not is_cookie_valid():
login()
res = s.post(SHARE_URL, data={"rid": resource_id}, cookies=load_cookies()).json()
share_code = res['data'].split('/')[-1]
data = s.get(API_DATA.format(code=share_code)).json()
insert_db(data)
except Exception:
insert_error(resource_id, traceback.format_exc())
2. 字幕侠在线数据采集
字幕侠的采集采用网页解析方式,通过 BeautifulSoup 解析HTML结构:
class ZimuxiaOnline(BaseFansub):
@Redis.preview_cache()
def search_preview(self, search_text: str) -> dict:
search_url = FIX_SEARCH.format(kw=search_text)
html_text = self.get_html(search_url)
soup = BeautifulSoup(html_text, "html.parser")
link_list = soup.find_all("h2", class_="post-title")
dict_result = {}
for link in link_list:
url = link.a["href"]
url_hash = hashlib.sha1(url.encode("u8")).hexdigest()
name = link.a.text
dict_result[url_hash] = {"url": url, "name": name, "class": self.__class__.__name__}
return dict_result
3. 追新番数据同步
追新番采用API接口方式进行数据同步,支持增量更新:
class YYSub(BaseSync):
def run(self):
end = self.get_lastest_id() + 1
start = (self.sync.find_one({"name": "yysub"}) or {}).get("resource_id", 41557)
api = "https://m.yysub.net/api/resource/{}"
for i in range(start, end):
resp = self.session.get(api.format(i))
if resp.status_code != 200:
continue
data = resp.json()["data"]
if data.get("cnname"):
self.insert_data(structure.copy())
缓存机制设计
系统采用Redis作为缓存层,实现了两级缓存策略:
classDiagram
class RedisCache {
+preview_cache(timeout)
+result_cache(timeout)
-r: Redis连接
+__init__()
+__del__()
}
class BaseFansub {
+redis: RedisCache
+search_preview()
+search
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



