Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 339

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 392

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 394
Joyber 的博客

Serena MCP 完整使用教程(适配你的 Cocos+Oops TS游戏项目,解决一次性读大量源码耗token问题)

Serena核心作用:符号级代码检索,AI不再一次性读取整个文件/整个仓库;需要哪个函数、哪个类,才单独拉取那一小段代码,大幅削减token消耗,刚好解决你之前Cursor索引Oops框架直接把额度耗尽的痛点。
原理:基于LSP语言服务器,识别TS的类、函数、接口,而不是把文件当纯文本全文读取。
项目地址:https://github.com/oraios/serena

一、前置依赖安装(Windows/Mac通用)

Serena推荐用uv包管理器(比pip简单很多)

  1. 安装 uv

    # Windows(PowerShell)
    powershell -ExecutionPolicy ByPass -c "irm [https://astral.sh/uv/install.ps1](https://astral.sh/uv/install.ps1) | iex"
    
    # Mac
    curl -LsSf [https://astral.sh/uv/install.sh](https://astral.sh/uv/install.sh) | sh

    安装完成重启终端,让环境变量生效。

  2. 安装 Serena-agent

    uv tool install -p 3.13 serena-agent@latest --prerelease=allow
  3. 初始化Serena(下载语言服务依赖,只需要跑一次)

    serena init

    执行完输入 serena --help,能正常输出帮助就代表安装成功。

二、接入到 Trae(推荐,你准备切换的免费AI编辑器)

Trae支持MCP,直接配置项目级MCP服务:

  1. 打开你的Cocos游戏项目(根目录)
  2. 在项目根新建文件夹 .trae,里面新建 mcp.json

    {
      "servers": [
     {
       "name": "serena",
       "transport": "stdio",
       "command": "serena",
       "args": [
         "start-mcp-server",
         "--context=ide",
         "--project",
         "${workspaceFolder}"
       ]
     }
      ]
    }
  3. 重启Trae,进入项目,打开AI聊天面板 → 设置 → MCP,看到serena状态绿色就是成功。

    ${workspaceFolder} 自动读取当前项目根目录,也就是你的Cocos项目根,Serena会自动索引。

接入Cursor的配置(如果你后续还要用Cursor)

在项目根新建 .cursor/mcp.json

{
  "mcpServers": {
    "serena": {
      "command": "serena",
      "args": [
        "start-mcp-server",
        "--context=ide",
        "--project-from-cwd"
      ]
    }
  }
}

保存,重启Cursor。

三、针对你的《弹力幻境》项目,Serena的配置优化(重点)

Serena会读取项目文件,你需要过滤不需要索引的目录,避免它扫描library、缓存、demo,和.traeignore配合:

  1. 在项目根新建 .serenaignore,内容:

    # Cocos编译缓存
    library/
    temp/
    build/
    dist/
    node_modules/
    
    # Oops示例代码,不需要让serena索引demo
    oops/demo/
    oops/example/
    
    # 只保留 code/业务代码 + oops核心源码
    *.log
    .DS_Store
    Serena会读取这个忽略清单,不会去索引缓存文件,只解析code/下面你的TS业务脚本 + oops框架核心代码,极大减少索引体积。

四、使用方式(对话指令,直接复制丢给AI)

激活成功之后,在AI聊天框直接发指令,Serena会自动调用符号检索工具

使用serena:查找code/下所有弹力相变相关函数,只读取函数签名和实现,不要读取整个文件。
使用serena:查找oops.message的on和off方法,查看定义。
使用serena:找到GameManager类,分析依赖,新增小球对象池逻辑。

✅ Serena能做到:

  • 查找函数、类、接口定义,按需读取片段,不加载完整文件,省大量token
  • 查找函数调用关系,找到哪里调用了某个方法
  • 重命名类/函数,跨文件批量修改符号(TS类型安全
  • 查看类型继承关系

❌ Serena不做:写完整大段业务代码、运行游戏、调试Cocos引擎物理。它是代码检索工具,用来减少AI读代码时的token开销。

五、使用工作流(适配你的游戏开发)

  1. 打开Trae,加载Cocos项目,serena自动后台索引(首次索引会慢几十秒,后续走缓存很快)
  2. 写代码时,直接让AI调用serena检索code/目录里的类/函数
  3. AI不再@整个项目,只通过serena符号检索按需拉取代码片段
  4. 配合 .traeignore + .serenaignore 双重过滤缓存目录,从根源解决一次性加载海量源码耗尽额度

六、坑点提醒(必看)

  1. Serena依赖TypeScript语言服务,本地需要安装Node+TS,否则TS解析会失败
  2. 第一次索引会扫描项目,一定要配置.serenaignore,否则还是会扫描library、node_modules,索引慢、耗资源
  3. Serena免费版使用LSP后端,足够TS项目使用;JetBrains后端是付费,你不需要
  4. 不能替代.traeignore,两者搭配:.traeignore控制Trae内置索引,.serenaignore控制Serena符号索引

备选简化方案:不想装Serena,用Repomix

如果你觉得Serena配置略复杂,Repomix是命令行一次性打包精简代码,适合一次性给AI看架构,不用常驻后台。

repomix --include "code/**/*,oops/**/*" --exclude "oops/demo/**/*,library/**/*,temp/**/*,build/**/*,node_modules/**/*" --compress

--compress AST精简,只保留函数签名,砍掉函数内部实现,token大幅压缩。

推荐你当前的组合方案

Trae + Serena MCP + .traeignore + .serenaignore

  • Trae免费SOLO Agent无硬限额
  • Serena按需读取代码片段,避免一次性读大量源码
  • 双重ignore过滤缓存、demo目录,杜绝爆token的问题
  • 适配 Cocos3.8.8 + Oops + TS微信小游戏

Yii2 joinWith left join 附加 on 额外 and 条件

joinWith 默认把 closure 里面的条件放到 WHERE,不是 ON。想要 LEFT JOIN xxx ON a=b AND c=d,必须用 onCondition,不能写 andFilterWhere / andWhere

关键点

  • joinWith('关联名', false):第二个参数 false不执行 eager loading 预加载,只做 JOIN SQL;true 会额外做一次 IN 查询。
  • onCondition 里面写的条件,会拼接在 ON 后面,生成 AND xxx=xxx
  • andWhere / andFilterWhere 会跑到 WHERE 子句,会把 left join 变成等价 inner join,这是高频踩坑。

模型关联定义(必须先定义 relation)

// Order 模型
public function getLog()
{
    /**
     * LEFT JOIN log ON log.order_id = order.id AND log.type = 1
     */
    return $this->hasMany(Log::class, ['order_id' => 'id'])
        ->onCondition(['log.type' => 1]); // 这里的条件直接拼 ON AND
}
如果条件需要动态变量,用数组格式防注入:
->onCondition([
    'log.type' => $typeVal,
    'log.status' => 2
]);

查询使用 joinWith

$query = Order::find()
    ->joinWith([
        'log' => function($q) {
            // ❌ 不要在这里写 andWhere,会进 WHERE
            // ✅ 如果需要再追加 ON 的 AND,继续 onCondition
            $q->onCondition(['log.is_del' => 0]);
        }
    ], false) // false:只JOIN,不贪婪加载
    ->all();

生成 SQL:

SELECT `order`.* FROM `order`
LEFT JOIN `log` ON `log`.`order_id` = `order`.`id` AND `log`.`type`=1 AND `log`.`is_del`=0

这里说一下 joinWith 第二个参数 $eagerLoading(bool)

方法签名:

public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')

$eagerLoading = false:只执行 SQL JOIN,不做贪婪(eager)预加载。

$eagerLoading = true(默认)

Order::find()->joinWith('log', true);

会执行2条SQL

  1. 主查询:SELECT order.* LEFT JOIN log ON ... 拿到主表+关联表连接后的数据集
  2. 额外再跑一条 IN 查询:SELECT * FROM log WHERE order_id IN (1,2,3...),把关联模型数据查出来,给模型的 $order->log 属性赋值。
目的:做贪婪加载,循环遍历 $orders 的时候,访问 $order->log 不会触发数据库查询。
代价:多一次数据库查询。即使你根本不需要读取 $order->log,这条SQL依旧会执行。

$eagerLoading = false

Order::find()->joinWith('log', false);

只执行1条SQL:带LEFT JOIN的主查询。

  • SQL里会做 LEFT JOIN,你可以在select里取log表字段、可以where过滤log表字段;
  • 不会额外执行IN查询,不会填充模型关联属性
  • 此时你访问 $order->log,会触发延迟加载(lazy load),单独再查一次数据库。

使用场景什么时候写 false

  1. 仅仅为了做表连接,用于过滤、排序、取关联表字段,业务代码不读取 $model->relation

    绝大多数报表、列表查询场景,都用 false,节省一次SQL。
    // 用于 where、orderBy、select取log.*,不需要读取$order->log
    Order::find()
     ->joinWith('log',false)
     ->select('order.*,log.content')
     ->andWhere(['log.type'=>1])
     ->orderBy('log.create_time desc');
  2. 你只是需要 LEFT JOIN xxx ON ... AND ...,不需要拿到关联模型对象。

什么时候用 true(默认)

需要循环遍历结果,并且要读取关联模型属性:

$list = Order::find()->joinWith('log',true)->all();
foreach($list as $item){
    echo $item->log[0]->content; // 不会N+1,全部已经预加载完毕
}

场景2:不想修改模型关联,查询时临时追加 ON AND

模型本身不带 onCondition,查询动态增加 ON 条件

$query = Order::find()
    ->joinWith([
        'log' => function ($q) {
            /** @var $q ActiveQuery */
            // 追加 ON AND 条件,不会污染模型定义
            $q->onCondition([
                'log.type' => 1,
                'log.is_del' => 0
            ]);
        }
    ], false)
    ->all();

场景3:onCondition 需要大于、不等于这类非等于条件

onCondition 支持 yii 的条件数组格式:

$q->onCondition([
    'and',
    ['log.type' => 1],
    ['>', 'log.create_time', '2026-01-01 00:00:00']
]);

输出:ON ... AND log.type=1 AND log.create_time > '2026‑01‑01 00:00:00'

场景4:原生 SQL 习惯,直接手写 join(备选)

如果逻辑复杂,也可以直接 leftJoin,完全手写on:

Order::find()
    ->leftJoin('log', 'log.order_id = `order`.id AND log.type = :t', [':t' => 1])
    ->all();

常见错误对比

❌ 错误写法(条件跑到 WHERE,left join 失效)

->joinWith(['log' => function($q){
    $q->andWhere(['log.type' =>1]); // 条件进 WHERE,不是 ON
}], false);

✅ 正确:onCondition,条件拼接在 ON ... AND xxx

小提示

  1. joinWith(relName, false):只做表连接,不查询关联数据;如果需要同时取出关联模型数据,第二个参数改为 true
  2. onCondition 是 ActiveQuery 的方法,只作用于 JOIN 的 ON,不会影响 where。
  3. 如果使用 andFilterWhere,同样会跑到 WHERE,绝对不要拿来写 ON 的 and 条件

Redis Lua原子操作 + 电商秒杀超卖超卖完整解决方案

说明:全文极简、重实战、无废话,包含 Redis Lua基础用法 + 原子特性原理 + 高并发秒杀防超卖完整落地方案,可直接用于生产环境。

一、Redis Lua 核心知识点(必懂)

1. 为什么Redis需要Lua脚本

作用:将多条Redis命令打包为单个原子指令执行

  • 普通多条命令:存在并发时差、线程安全问题
  • Lua脚本:执行不插针、不中断、原子串行
  • 解决:并发竞争、超卖、库存错乱、分布式锁竞争问题

2. Redis Lua 执行特性(硬核规则)

  • 单个Lua脚本:要么全部执行成功,要么全部失败
  • 脚本执行期间,其他客户端命令全部阻塞等待
  • 不支持事务回滚,但天然具备事务原子性
  • 性能极高:一次网络IO完成多条命令,减少网络开销

3. Redis Lua 标准语法模板

作用:固定传参、取值、返回格式,生产统一规范

-- KEYS[1]、KEYS[2] 传入key
-- ARGV[1]、ARGV[2] 传入参数值
return redis.call("命令名", KEYS[1], ARGV[1])

4. 常用Redis Lua命令

-- 取值
redis.call("GET", KEYS[1])
-- 设值
redis.call("SET", KEYS[1], ARGV[1])
-- 自增/自减
redis.call("INCR", KEYS[1])
redis.call("DECR", KEYS[1])
-- 判断存在
redis.call("EXISTS", KEYS[1])

5. Java/Python 调用Redis Lua通用格式

Python示例

import redis

r = redis.Redis(decode_responses=True)

# 加载Lua脚本
lua_script = """
return redis.call('GET', KEYS[1])
"""

# 执行脚本:keys列表、args列表
res = r.eval(lua_script, 1, "goods_stock")
print(res)

二、秒杀超卖问题根源

场景:库存10件,1000人同时抢购

普通代码bug流程

  1. 线程A查询库存 = 10
  2. 线程B查询库存 = 10(未扣减,并发同时读到充足库存)
  3. 线程A、B同时扣减 → 库存变为8,卖出12件,超卖

本质原因查库存、判库存、扣库存 非原子操作

三、Lua原子防超卖核心思路

一条Lua脚本完成三件原子操作(不可分割)

  1. 查询当前商品库存
  2. 判断库存是否大于0
  3. 满足则扣减库存,不满足直接返回失败

全程单线程执行,杜绝并发同时判定通过

四、完整可上线:秒杀防超卖Lua脚本

1. 脚本功能说明

  • 支持单商品限购1件(防止单人多抢)
  • 原子判定库存 + 原子扣减
  • 记录已抢购用户,防止重复下单
  • 返回状态码,前端/后端可直接判断结果

2. 最终生产级Lua脚本

-- KEYS[1]:商品库存key
-- KEYS[2]:用户抢购记录key
-- ARGV[1]:用户ID
-- ARGV[2]:本次抢购数量(固定1)

-- 1. 判断用户是否已抢购过(防重复下单)
local has_buy = redis.call("sismember", KEYS[2], ARGV[1])
if has_buy == 1 then
    return 0  -- 0:重复抢购,失败
end

-- 2. 获取当前库存
local stock = tonumber(redis.call("get", KEYS[1]) or 0)

-- 3. 库存不足直接返回
if stock < 1 then
    return -1 -- -1:库存不足,秒杀结束
end

-- 4. 原子扣减库存
redis.call("decr", KEYS[1])

-- 5. 记录用户抢购记录
redis.call("sadd", KEYS[2], ARGV[1])

-- 6. 秒杀成功
return 1

3. 返回码统一规范

  • 1:秒杀成功
  • 0:用户重复抢购
  • -1:库存不足、超卖拦截

五、高并发秒杀完整业务流程(无漏洞)

1. 秒杀预热(提前初始化库存)

# 初始化商品库存100件
SET seckill:goods:1001:stock 100
# 用户抢购记录集合
SADD seckill:goods:1001:user

2. 完整执行链路(层层拦截,抗百万并发)

前端拦截:按钮置灰、防抖、倒计时

网关拦截:限流、防刷、IP限制

Redis Lua原子拦截:防超卖、防重复抢(核心)

异步落库:秒杀成功后,MQ异步写入数据库(扛高并发)

六、Python 完整可运行秒杀Demo

功能:模拟100并发抢10库存,绝对无超卖

import redis
import threading

# 连接Redis
r = redis.Redis(decode_responses=True)

# 秒杀Lua脚本
SECKILL_LUA = """
local has_buy = redis.call("sismember", KEYS[2], ARGV[1])
if has_buy == 1 then
    return 0
end

local stock = tonumber(redis.call("get", KEYS[1]) or 0)
if stock < 1 then
    return -1
end

redis.call("decr", KEYS[1])
redis.call("sadd", KEYS[2], ARGV[1])
return 1
"""

# 初始化库存10
r.set("seckill:stock", 10)

def seckill(user_id):
    """单个用户抢购"""
    res = r.eval(SECKILL_LUA, 2, "seckill:stock", "seckill:user", user_id)
    if res == 1:
        print(f"用户{user_id} 秒杀成功")
    elif res == 0:
        print(f"用户{user_id} 重复抢购")
    else:
        print(f"用户{user_id} 秒杀失败,库存不足")

# 模拟100个并发线程
if __name__ == "__main__":
    thread_list = []
    for i in range(1,101):
        t = threading.Thread(target=seckill, args=(f"user{i}",))
        thread_list.append(t)
        t.start()

    for t in thread_list:
        t.join()

    # 最终库存校验
    final_stock = r.get("seckill:stock")
    print("最终剩余库存:", final_stock)
    print("成功抢购人数:", r.scard("seckill:user"))

七、PHP 完整可运行秒杀Demo(生产可用+高并发模拟)

功能:基于PHP Redis扩展 + Lua原子脚本,模拟高并发秒杀,彻底防超卖、防重复抢购,适配PHP框架(ThinkPHP/Laravel)原生使用

环境要求:安装PHP Redis扩展 pecl install redis

<?php
// 环境要求:安装PHP Redis扩展、开启pcntl多进程扩展(模拟高并发)
// 生产适配:兼容Laravel/ThinkPHP/Swoole所有PHP项目

// 1. 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 解决Redis超时、并发连接问题
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);

// 2. 生产级秒杀Lua脚本(原子防超卖、防重复抢购)
$luaScript = <<<LUA
local has_buy = redis.call("sismember", KEYS[2], ARGV[1])
if has_buy == 1 then
    return 0
end

local stock = tonumber(redis.call("get", KEYS[1]) or 0)
if stock < 1 then
    return -1
end

redis.call("decr", KEYS[1])
redis.call("sadd", KEYS[2], ARGV[1])
return 1
LUA;

// 3. 初始化秒杀库存(仅首次执行,可后台预热执行)
$redis->del('seckill:stock');
$redis->del('seckill:user');
$redis->set('seckill:stock', 10);

/**
 * 秒杀核心原子方法
 * @param string $userId 唯一用户ID
 * @return int 1=成功 0=重复抢购 -1=库存不足
 */
function seckill(string $userId): int
{
    global $redis, $luaScript;
    // PHP eval固定参数:脚本内容、[KEYS+ARGV数组]、KEYS参数数量
    return $redis->eval($luaScript, [
        'seckill:stock',
        'seckill:user',
        $userId
    ], 2);
}

/**
 * 多进程模拟高并发秒杀(100用户并发抢10库存)
 */
function batchSeckill()
{
    $processNum = 100;
    for ($i = 1; $i <= $processNum; $i++) {
        $pid = pcntl_fork();
        if ($pid == 0) {
            // 子进程执行秒杀
            $userId = 'user_' . $i;
            $res = seckill($userId);
            switch ($res) {
                case 1:
                    echo "【成功】{$userId} 秒杀下单完成\n";
                    break;
                case 0:
                    echo "【拦截】{$userId} 禁止重复抢购\n";
                    break;
                case -1:
                    echo "【售罄】{$userId} 库存不足,秒杀失败\n";
                    break;
            }
            // 必须退出子进程,避免循环派生
            exit(0);
        }
    }

    // 等待所有子进程执行完毕
    while (pcntl_waitpid(0, $status) != -1);
}

// 执行高并发秒杀模拟
batchSeckill();

// 4. 最终数据校验(杜绝超卖核心校验)
global $redis;
$finalStock = $redis->get('seckill:stock');
$successNum = $redis->sCard('seckill:user');

echo "\n========== 秒杀最终统计结果 ==========\n";
echo "剩余库存数量:{$finalStock}\n";
echo "成功下单人数:{$successNum}\n";
echo "校验结果:" . ($finalStock + $successNum == 10 ? "✅ 无超卖、无漏卖,数据正常" : "❌ 数据异常") . "\n";

$redis->close();
?>

一、PHP版本核心特性说明

  • 原子性互通:Lua脚本与Python版本完全一致,底层原子锁库存逻辑通用,跨语言无差异
  • PHP独有规则:Redis eval方法参数顺序固定,必须严格区分KEYS数量,否则脚本执行报错
  • 框架无缝适配:可直接移植 Laravel/ThinkPHP/Swoole 项目,仅需替换框架Redis实例
  • 高并发适配性:基于多进程模拟真实秒杀流量,兼容PHP-FPM、Swoole协程环境,分布式集群完全生效
  • 数据强一致性:最终库存+成功人数恒等于初始库存,彻底杜绝超卖、少卖、漏卖问题

二、PHP生产部署注意事项

  • 线上关闭pcntl多进程代码,仅用业务接口接收请求,多进程仅用于本地压测验证
  • Lua脚本建议使用script load预加载缓存,通过sha1值调用,减少网络传输开销
  • 秒杀Redis Key设置过期时间,自动清理过期秒杀数据,避免内存堆积
  • 接口层增加限流、防刷、IP黑白名单,拦截恶意黄牛请求
  • 脚本预加载优化:生产环境禁止每次请求传递完整Lua脚本,采用script\_load预加载+sha1哈希调用,大幅减少网络IO、提升并发吞吐量

三、PHP Lua脚本缓存(SHA1)生产优化代码

优化作用:首次加载脚本生成唯一SHA1哈希值,后续所有秒杀请求直接通过哈希值调用脚本,无需重复传输大段脚本内容,高并发场景性能提升30%+

<?php
// 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);

// 秒杀Lua脚本
$luaScript = <<<LUA
local has_buy = redis.call("sismember", KEYS[2], ARGV[1])
if has_buy == 1 then
    return 0
end
local stock = tonumber(redis.call("get", KEYS[1]) or 0)
if stock < 1 then
    return -1
end
redis.call("decr", KEYS[1])
redis.call("sadd", KEYS[2], ARGV[1])
return 1
LUA;

// 核心:预加载脚本,获取SHA1哈希值(项目初始化执行一次即可)
$sha1Hash = $redis->script('load', $luaScript);
echo "脚本缓存SHA1值:" . $sha1Hash . "\n";

/**
 * 缓存版秒杀方法(生产正式使用)
 * 无需传递完整脚本,仅通过SHA1调用,性能更高
 */
function fastSeckill(string $userId, string $sha1Hash): int
{
    global $redis;
    // evalsha 哈希调用脚本
    return $redis->evalsha($sha1Hash, [
        'seckill:stock',
        'seckill:user',
        $userId
    ], 2);
}

// 初始化库存
$redis->del('seckill:stock', 'seckill:user');
$redis->set('seckill:stock', 10);

// 测试缓存版秒杀
$res = fastSeckill('user_001', $sha1Hash);
var_dump($res);

// 脚本状态校验(判断脚本是否已缓存)
$isExists = $redis->script('exists', $sha1Hash);
echo "脚本是否缓存成功:" . ($isExists[0] ? "是" : "否") . "\n";

$redis->close();
?>

四、脚本缓存生产高可用方案(自动降级容错)

核心解决问题:Redis重启、缓存脚本丢失、evalsha执行失败等线上突发异常,无感知自动降级,保障秒杀服务不宕机、不报错

<?php
// 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);

// 原始秒杀Lua脚本
$luaScript = <<<LUA
local has_buy = redis.call("sismember", KEYS[2], ARGV[1])
if has_buy == 1 then
    return 0
end
local stock = tonumber(redis.call("get", KEYS[1]) or 0)
if stock < 1 then
    return -1
end
redis.call("decr", KEYS[1])
redis.call("sadd", KEYS[2], ARGV[1])
return 1
LUA;

// 项目初始化预加载脚本,全局缓存SHA1值
$sha1Hash = $redis->script('load', $luaScript);

/**
 * 高可用秒杀方法(缓存优先 + 自动降级)
 * 优先evalsha高性能调用,失败自动降级eval原生调用
 * @param string $userId 用户唯一ID
 * @return int 1成功 0重复 -1库存不足
 */
function safeSeckill(string $userId): int
{
    global $redis, $sha1Hash, $luaScript;

    $keys = ['seckill:stock', 'seckill:user', $userId];
    $keyNum = 2;

    try {
        // 优先使用缓存脚本(高性能)
        return $redis->evalsha($sha1Hash, $keys, $keyNum);
    } catch (RedisException $e) {
        // 捕获脚本不存在异常,自动降级原生执行
        if (str_contains($e->getMessage(), 'NOSCRIPT')) {
            // 重新加载脚本缓存,修复异常
            $GLOBALS['sha1Hash'] = $redis->script('load', $luaScript);
            // 降级执行完整脚本
            return $redis->eval($luaScript, $keys, $keyNum);
        }
        // 其他异常直接抛出
        throw $e;
    }
}

// 初始化秒杀库存
$redis->del('seckill:stock', 'seckill:user');
$redis->set('seckill:stock', 10);

// 模拟多次秒杀调用,验证容错性
for ($i = 1; $i <= 5; $i++) {
    $res = safeSeckill('user_' . $i);
    echo "用户user_{$i} 结果:" . ($res === 1 ? "秒杀成功" : "失败") . "\n";
}

// 最终数据校验
$finalStock = $redis->get('seckill:stock');
$successNum = $redis->sCard('seckill:user');
echo "\n========== 高可用秒杀结果 ==========\n";
echo "剩余库存:{$finalStock}\n";
echo "成功人数:{$successNum}\n";

$redis->close();
?>

五、高可用方案核心特性

  • 智能优先策略:正常场景使用 evalsha 缓存调用,性能拉满
  • 无感自动降级:捕捉 NOSCRIPT 脚本丢失异常,自动重新加载脚本并降级原生执行
  • 自愈修复能力:异常后自动更新全局SHA1缓存,后续请求恢复高性能模式
  • 业务零影响:整个容错过程无报错、无中断,用户完全感知不到

六、脚本缓存生产使用规范

  • 加载时机:项目启动、框架初始化时执行一次 script load,常驻Redis缓存,无需重复加载
  • 缓存有效期:Redis不主动清理缓存脚本,重启Redis后依赖自动降级逻辑自愈
  • 统一复用:全局统一SHA1常量,所有秒杀接口共用缓存脚本,避免重复加载
  • 异常监控:线上可捕获降级日志,用于监控Redis脚本缓存异常频次,及时排查问题
  • 加载时机:项目启动、框架初始化时执行一次 script load,常驻Redis缓存,无需重复加载
  • 缓存有效期:Redis不主动清理缓存脚本,重启Redis后需重新加载
  • 降级方案:若evalsha报错(脚本丢失),自动降级为eval完整脚本加载,保障服务不中断
  • 统一复用:全局统一SHA1常量,所有秒杀接口共用缓存脚本,避免重复加载

八、彻底解决超卖超买的核心原理总结

1. 为什么普通代码会超卖

查库存、判库存、扣库存 三步非原子,并发间隙导致多人同时抢到资格

2. 为什么Lua能彻底杜绝超卖

Redis单线程执行Lua脚本,整个秒杀逻辑串行原子执行,无间隙、无并发穿插,从底层锁死库存竞争

九、生产环境额外优化(高阶)

  • 脚本缓存:使用 script load + sha1 缓存脚本,不用每次传完整脚本
  • 过期时间:秒杀key设置过期时间,自动释放内存
  • 限流熔断:接口层增加QPS限流,避免打垮Redis
  • 异步落库:Redis只做预扣减,MQ异步更新MySQL,提升吞吐量
  • 防黄牛:绑定用户、IP、设备号,限制单用户限购次数

十、常见误区避坑

  • 误区:Java加锁(synchronized/lock)防超卖

问题:分布式集群部署,单机锁完全失效

  • 误区:Redis多条普通命令判库存

问题:命令之间存在并发空隙,必然超卖

  • 唯一稳妥方案Redis Lua原子脚本(行业通用秒杀最终方案)

Python 进阶极简教程(高阶核心+纯用法)

python基础版

说明:承接Python基础教程,全文无冗余描述,仅保留进阶核心知识点 + 作用 + 可运行代码,覆盖Python后端、脚本、工程化全部高阶刚需内容。

一、变量与深浅拷贝(进阶核心)

1. 引用机制原理

作用:弄懂赋值、浅拷贝、深拷贝区别,解决数据篡改问题

# 赋值:只是绑定同一内存地址(完全共享数据)
a = [1, 2, [3, 4]]
b = a
b[0] = 99
print(a) # a同步被修改

2. 浅拷贝 copy

作用:拷贝一层数据,内层嵌套对象依旧引用

import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
b[0] = 99       # 外层独立,不影响原数据
b[2][0] = 999   # 内层嵌套共享,原数据被修改
print(a)

3. 深拷贝 deepcopy

作用:递归拷贝所有层级,完全独立数据,互不影响

import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)
b[2][0] = 999
print(a) # 原数据完全不变

二、函数高阶特性

1. 闭包

作用:内层函数捕获外层局部变量,保存私有状态,数据常驻内存

def counter():
    num = 0
    def inner():
        nonlocal num # 声明使用外层变量
        num += 1
        return num
    return inner

# 多个实例状态独立
c1 = counter()
c2 = counter()
print(c1()) # 1
print(c1()) # 2
print(c2()) # 1

2. 装饰器(无参/有参)

作用:不修改原函数代码,动态扩展功能(日志、校验、计时)

# 无参装饰器
def wrapper(func):
    def inner(*args, **kwargs):
        print("执行前")
        res = func(*args, **kwargs)
        print("执行后")
        return res
    return inner

@wrapper
def test():
    print("核心逻辑")

test()

3. 递归优化与尾递归

作用:简化循环逻辑,处理层级嵌套数据

# 阶乘递归
def fact(n):
    if n == 1:
        return 1
    return n * fact(n-1)

print(fact(10))

4. 函数缓存 lru\_cache

作用:缓存函数返回值,解决递归重复计算,大幅提速

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 2:
        return 1
    return fib(n-1) + fib(n-2)

print(fib(30))

三、高阶语法与推导式

1. 四大推导式

作用:极简代码快速生成容器数据,开发高频用法

# 列表推导式
lst = [x for x in range(10) if x % 2 == 0]

# 字典推导式
dic = {k:v for k,v in zip(["a","b"], [1,2])}

# 集合推导式
s = {x**2 for x in [1,2,2,3]}

# 生成器推导式(省内存)
gen = (x for x in range(10))
print(next(gen))

2. 高阶函数 map/filter/reduce

作用:批量处理、过滤、聚合数据

from functools import reduce
lst = [1,2,3,4]

# map 批量处理
res1 = list(map(lambda x:x*2, lst))

# filter 过滤数据
res2 = list(filter(lambda x:x>2, lst))

# reduce 累计聚合
res3 = reduce(lambda a,b:a+b, lst)

print(res1, res2, res3)

四、面向对象进阶

1. 类属性与实例属性

作用:区分全局类属性、私有实例属性,优化内存

class Demo:
    cls_val = 100 # 类属性(全局共享)
    def __init__(self, val):
        self.val = val # 实例属性(独立私有)

d1 = Demo(200)
d2 = Demo(300)
print(Demo.cls_val)
print(d1.val, d2.val)

2. 三大方法(实例/类/静态)

作用:适配不同场景的类调用逻辑

class Test:
    # 实例方法:必须实例调用,自带self
    def func1(self):
        pass

    # 类方法:类/实例均可调用,自带cls
    @classmethod
    def func2(cls):
        pass

    # 静态方法:无默认参数,纯工具方法
    @staticmethod
    def func3():
        pass

3. 继承、重写、多继承

作用:代码复用,实现多态特性

# 父类
class Father:
    def show(self):
        print("父类方法")

# 子类继承 + 方法重写
class Son(Father):
    def show(self):
        print("子类重写方法")

s = Son()
s.show()

4. 私有属性与属性装饰器

作用:数据封装,控制属性读写权限

class User:
    def __init__(self, age):
        self.__age = age # 私有属性(外部无法直接访问)

    # 属性装饰器:将方法转为属性调用
    @property
    def age(self):
        return self.__age

u = User(18)
print(u.age)

五、迭代器与生成器(性能优化)

1. 迭代器原理

作用:统一遍历规则,节省内存

lst = [1,2,3]
it = iter(lst) # 获取迭代器
print(next(it))
print(next(it))

2. 生成器 yield

作用:边执行边返回,不一次性加载所有数据,处理超大数据集

def gen():
    yield 1
    yield 2
    yield 3

g = gen()
print(next(g))
print(next(g))

# 遍历生成器
for i in gen():
    print(i)

六、异常处理进阶

1. 完整异常捕获结构

作用:精准捕获异常,程序不崩溃

try:
    num = 1 / 0
except ZeroDivisionError as e:
    print("除零错误:", e)
except Exception as e:
    print("通用错误:", e)
else:
    print("无异常执行")
finally:
    print("必定执行")

2. 主动抛异常

作用:参数校验、逻辑拦截主动终止程序

def check_age(age):
    if age < 0:
        raise ValueError("年龄不能为负数")

check_age(-1)

七、文件与上下文管理器

1. with 上下文管理器

作用:自动关闭文件、释放资源,无需手动close

# 读写文件自动释放
with open("test.txt", "w", encoding="utf-8") as f:
    f.write("进阶测试")

with open("test.txt", "r", encoding="utf-8") as f:
    print(f.read())

2. 自定义上下文管理器

作用:自定义资源开启、关闭逻辑

class MyCtx:
    def __enter__(self):
        print("开启资源")
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("关闭资源")

with MyCtx():
    print("执行业务逻辑")

八、模块、包与导包进阶

1. \_\_init\_\_.py 作用

作用:标识文件夹为Python包,统一导出接口

# 在包目录 __init__.py 中定义
__all__ = ["func1", "Demo"] # 控制from xxx import * 导入内容

# 批量导出
from .test import func1, Demo

2. 动态导包

作用:程序运行中动态导入模块,灵活解耦

import importlib

# 动态导入指定模块
module = importlib.import_module("math")
print(module.pi)

九、多线程与多进程

1. 多线程(IO密集型)

作用:处理等待、文件、网络IO任务

import threading
import time

def task(name):
    print(f"线程{name}执行")
    time.sleep(1)

t = threading.Thread(target=task, args=("A",))
t.start()
t.join() # 等待线程结束

2. 多进程(CPU密集型)

作用:绕过GIL全局锁,多核并行计算

from multiprocessing import Process

def task():
    print("子进程执行")

if __name__ == "__main__":
    p = Process(target=task)
    p.start()
    p.join()

十、协程异步编程

作用:超高并发IO处理,性能远超多线程

import asyncio

# 异步函数
async def async_task(num):
    print(f"任务{num}开始")
    await asyncio.sleep(1)
    print(f"任务{num}结束")

# 并发执行
async def main():
    tasks = [async_task(1), async_task(2)]
    await asyncio.gather(*tasks)

asyncio.run(main())

十一、正则表达式进阶

作用:复杂文本匹配、提取、替换、校验

import re

s = "Python123Java456"

# 匹配所有数字
res1 = re.findall(r"\d+", s)
# 匹配所有字母
res2 = re.findall(r"\a+", s)
# 替换指定内容
res3 = re.sub(r"\d", "", s)

print(res1, res2, res3)

十二、GIL锁与性能优化

作用:理解Python单线程特性,针对性优化代码

  • GIL全局解释器锁:同一时刻一个进程只有一个线程执行Python代码
  • CPU密集:用多进程规避GIL
  • IO密集:用多线程/协程提升效率

十三、常用工程化技巧

1. 枚举类

作用:固定常量状态,避免魔法值

from enum import Enum

class Status(Enum):
    SUCCESS = 200
    FAIL = 500

print(Status.SUCCESS.value)

2. 时间处理

作用:时间戳、格式化、时间差计算

from datetime import datetime

# 当前时间
now = datetime.now()
# 格式化时间
time_str = now.strftime("%Y-%m-%d %H:%M:%S")
print(time_str)

Lua 进阶极简教程(高阶核心+纯用法)

lua基础版

说明:承接Lua基础教程,全文无冗余描述,仅保留进阶核心知识点 + 作用 + 可运行代码,覆盖Lua高阶开发全部刚需内容。

一、变量深层特性(进阶必懂)

1. 全局环境 \_G

作用:存储所有全局变量、函数的全局表,可动态遍历、修改全局内容

-- 查看所有全局变量
print(_G)

-- 定义全局变量等价写法
_G.testVal = 100
print(testVal)

-- 遍历所有全局函数/变量
for k,v in pairs(_G) do
    print(k,v)
end

2. 局部变量优化

作用:局部变量访问速度远快于全局变量,高阶开发优先局部缓存

-- 缓存全局函数,提升执行效率
local print = print
local pairs = pairs

print("优化后的局部调用")

3. 变量nil清空与垃圾回收

作用:主动置空变量,触发GC释放内存,避免内存泄漏

local data = {1,2,3,4}
data = nil -- 置空引用,等待GC回收
collectgarbage() -- 手动触发垃圾回收

二、函数进阶(Lua核心高阶)

1. 闭包(Closure)

作用:内层函数捕获外层局部变量,变量常驻内存,实现数据私有、状态保存

local function counter()
    local num = 0 -- 被捕获的局部变量
    return function()
        num = num + 1
        return num
    end
end

-- 独立状态,互不干扰
local c1 = counter()
local c2 = counter()
print(c1()) -- 1
print(c1()) -- 2
print(c2()) -- 1

2. 函数尾调用(Tail Call)

作用:尾部调用函数,不堆叠栈帧,避免递归栈溢出,优化递归性能

-- 尾递归写法(无栈溢出)
local function factorial(n, res)
    res = res or 1
    if n <= 1 then return res end
    return factorial(n-1, n*res) -- 纯粹尾部返回
end

print(factorial(10))

3. 函数重载模拟

作用:Lua无原生重载,通过判断参数数量/类型实现多参数适配

local function add(...)
    local args = {...}
    if #args == 1 then
        return args[1] * 2
    elseif #args == 2 then
        return args[1] + args[2]
    end
end

print(add(10))  -- 20
print(add(10,20)) -- 30

4. 函数缓存(记忆化)

作用:缓存函数执行结果,避免重复计算,大幅提升高频函数效率

local cache = {}
local function fib(n)
    if cache[n] then return cache[n] end
    if n <= 2 then return 1 end
    local res = fib(n-1) + fib(n-2)
    cache[n] = res
    return res
end

print(fib(30))

三、Table 高阶操作(Lua灵魂)

1. 数组与哈希表分离原理

作用:Lua table 内部分数组段(连续数字key)、哈希段(自定义key),优化存储

-- 纯数组(高效)
local arr = {1,2,3,4}
-- 纯哈希(高效)
local hash = {a=1,b=2}
-- 混合表(性能略低,不推荐高频使用)
local mix = {1,2,a=3,b=4}

2. 表深拷贝/浅拷贝

作用:解决table默认引用赋值问题,独立复制数据表

-- 浅拷贝(仅复制一层)
local function shallowCopy(t)
    local res = {}
    for k,v in pairs(t) do res[k] = v end
    return res
end

-- 深拷贝(递归复制所有层级)
local function deepCopy(t)
    local res = {}
    if type(t) ~= "table" then return t end
    for k,v in pairs(t) do
        res[k] = deepCopy(v)
    end
    return res
end

local t1 = {1,2,{3,4}}
local t2 = deepCopy(t1)
t2[3][1] = 99
print(t1[3][1]) -- 原表不变

3. 表去重/排序/过滤

作用:高频数组处理工具方法

-- 数组去重
local function unique(arr)
    local temp = {}
    local res = {}
    for _,v in ipairs(arr) do
        if not temp[v] then
            temp[v] = true
            table.insert(res, v)
        end
    end
    return res
end

-- 自定义排序
local arr = {3,1,4,2}
table.sort(arr, function(a,b)
    return a > b -- 降序,默认升序
end)
print(table.concat(arr, ","))

四、元表与元方法(Lua进阶核心)

1. 基础元表读写

作用:为table绑定元表,拦截、自定义table操作行为

local t = {}
local mt = {}

setmetatable(t, mt) -- 绑定元表
local getMt = getmetatable(t) -- 获取元表
print(getMt == mt)

2. 常用元方法全覆盖

作用:拦截加减乘除、取值、赋值、长度、打印等操作

local t = {10,20}
local mt = {
    -- 取值拦截:t[key]
    __index = function(tab, k)
        return "默认值"
    end,
    -- 赋值拦截:t[key] = v
    __newindex = function(tab, k, v)
        print("禁止赋值")
    end,
    -- 加法重载 t1 + t2
    __add = function(a,b)
        return {a[1]+b[1], a[2]+b[2]}
    end,
    -- 打印重载 print(t)
    __tostring = function(tab)
        return "自定义表字符串"
    end,
    -- 长度重载 #t
    __len = function()
        return 99
    end
}

setmetatable(t, mt)
print(t[3])
t[1] = 999
print(t)
print(#t)

3. \_\_index 索引优化(继承核心)

作用:实现表的默认值、数据继承、查找降级

-- 默认值配置
local default = {name="默认", age=18}
local t = {}
setmetatable(t, {__index = default})

print(t.name) -- 找不到key,去元表查找

五、面向对象高阶(纯Lua实现)

1. 标准类、继承、重写

作用:完整OOP体系,支持封装、单继承、方法重写

-- 父类
local Person = {}
Person.__index = Person

function Person:new(name, age)
    local self = setmetatable({}, Person)
    self.name = name
    self.age = age
    return self
end

function Person:show()
    print(self.name, self.age)
end

-- 子类继承
local Student = {}
Student.__index = Student
setmetatable(Student, Person) -- 继承父类

function Student:new(name, age, id)
    local self = Person:new(name, age)
    self.id = id
    return setmetatable(self, Student)
end

-- 方法重写
function Student:show()
    print("学生:", self.name, self.id)
end

local s = Student:new("张三", 16, "001")
s:show()

2. 私有变量模拟

作用:通过闭包实现类私有属性,外部无法修改

local function User(name)
    -- 私有变量
    local _name = name

    -- 公开方法
    return {
        getName = function()
            return _name
        end,
        setName = function(n)
            _name = n
        end
    }
end

local u = User("李四")
print(u.getName())
-- print(u._name) 无法访问私有变量

六、协程高阶(Lua特色)

1. 协程完整生命周期

作用:掌握协程创建、启动、暂停、恢复、状态判断

local co = coroutine.create(function()
    print("协程第一步")
    coroutine.yield("暂停返回值")
    print("协程第二步")
    return "结束返回值"
end)

print(coroutine.status(co)) -- suspended 挂起
local ok, res1 = coroutine.resume(co)
print(res1)
local ok2, res2 = coroutine.resume(co)
print(res2)
print(coroutine.status(co)) -- dead 结束

2. 协程传参技巧

作用:resume与yield双向传参,实现数据交互

local co = coroutine.create(function(a)
    print("初始参数:", a)
    local b = coroutine.yield("第一次返回")
    print("恢复参数:", b)
end)

coroutine.resume(co, 100)
coroutine.resume(co, 200)

七、模块化与代码解耦

1. 标准模块封装写法

作用:规范模块导出,隔离全局环境,工程化开发必备

-- 新建 tool.lua 模块文件
local Tool = {}
Tool.__index = Tool

-- 私有函数(外部无法访问)
local function log(str)
    print("[日志]", str)
end

-- 公开函数
function Tool:add(a,b)
    log("执行加法")
    return a + b
end

return Tool

-- 主文件调用
-- local Tool = require("tool")
-- print(Tool:add(1,2))

2. 模块卸载与重载

作用:解决require缓存问题,支持热更新

-- 卸载模块
package.loaded["tool"] = nil
-- 重新加载
local Tool = require("tool")

八、错误处理与调试高阶

1. assert 断言

作用:条件不成立直接报错,用于参数校验

local num = nil
assert(num, "变量不能为空!")

2. xpcall 带栈追踪捕获错误

作用:比pcall多堆栈信息,精准定位报错位置

local function errHandler(err)
    return err .. "\n" .. debug.traceback()
end

local ok, res = xpcall(function()
    print(1/0)
end, errHandler)

if not ok then
    print("错误详情:", res)
end

3. debug 调试库

作用:查看函数栈、局部变量,高阶调试排错

local function test()
    local a = 100
    print(debug.localvars(1)) -- 打印当前函数局部变量
end
test()

九、字符串与正则进阶

作用:Lua原生正则(模式匹配),无需第三方库

local s = "Lua123Python456"

-- 匹配数字
print(string.match(s, "%d+"))
-- 匹配字母
print(string.match(s, "%a+"))
-- 全局替换
print(string.gsub(s, "%d", ""))
-- 查找所有匹配
for v in string.gmatch(s, "%d+") do
    print(v)
end

十、GC内存优化(工程必备)

作用:控制垃圾回收,优化游戏/长驻程序内存

collectgarbage("stop")  -- 停止GC
collectgarbage("restart")-- 重启GC
collectgarbage("count") -- 获取当前内存占用(KB)
collectgarbage()        -- 手动全量回收

print("当前内存:", collectgarbage("count"))