压测登录态模拟:用Redis批量生成1000个token的完整脚本
发布时间:2026/10/2 4:11:49来源:尧图网络
做黑马点评做到p70这个阶段很多朋友会被同一个问题卡住功能代码都写完了想拉一波压测或者造一批测试数据结果发现登录这关根本过不去。我当时的需求很直接就是自动生成1000个token让压测脚本能模拟1000个不同用户去访问接口。手动造不现实一条一条往Redis里塞更不现实折腾一晚上写了套批量脚本把token生成、用户造数、Redis写入一次搞定。这篇就把当时的完整思路、具体脚本和踩过的坑全整理出来。如果你正在做登录态模拟或者手头正好有这类用Redis做会话管理的项目这篇文章可以直接照抄改改key前缀和TTL就能跑。1. 需求场景复盘为什么做到p70时突然需要1000个token1.1 p70这个阶段的真实卡点在于登录态变成了测试瓶颈黑马点评做到p70前后基本是在集中处理登录后的功能点赞、关注、探店笔记、排行榜这类和用户强相关的模块。这些功能有个共同点——所有请求都要带登录态服务端才能知道“当前操作的是哪个用户”。刚开始手动测没什么感觉浏览器里登录一个账号点哪都能通。但一旦要验证“1000个用户看到的排行榜数据”、“不同用户点赞同一篇笔记之后的数据变化”手动登录就彻底废了。你不可能真的拿1000个手机号去收验证码也不可能在界面上反复退出登录再切换账号。所以本质上这一阶段缺的不是业务代码而是一批能代表不同用户的测试原料。token就是这个原料。1.2 1000这个数量不是随便拍的是根据场景反推出来的有人问为什么是1000不是100也不是10000我当时是用实际场景反推的。点赞排行榜功能的验证目标很简单至少500个不同用户对同一批笔记产生交互再叠加关注流测试每个用户平均发出2到3次请求1000个token刚好覆盖。如果你的目标更偏向纯接口压测也可以用公式粗算并发线程数乘以单用户请求次数就是token的下限。比如计划跑200个并发线程、每个线程循环5次那就至少需要1000个不同的身份。少了token不够分多了浪费Redis内存。1.3 同一个token反复用不行一人一token是硬要求一开始我也试过偷懒拿同一个token去压测结果所有请求都串到了同一个用户头上。排行榜全是一个人点赞关注列表全是一个人的数据压测结果完全失真。原因很直接token本质上是服务端识别身份的凭证同一串token永远只代表同一个用户。而且黑马点评这类项目用户维度数据很多——你赞过什么、关注了谁、浏览记录——全部挂在用户ID下面。想验证多用户场景就必须一人一个token没有任何捷径。2. 先把token的存储结构摸清楚UUID流水号还是JWT2.1 黑马点评默认的登录态方案是Redis缓存用户信息写脚本之前先得搞清楚项目里的token到底是什么。黑马点评的登录主流程是这样的用户用手机号验证码登录服务端校验通过后生成一串随机凭证token然后以token为key把用户信息序列化后存入Redis同时设置过期时间。前端后续请求在请求头里带上这串token后端拦截器拿着它去Redis里反查用户信息查得到就放行查不到就当作未登录。这里的token只是一把“钥匙”真正的用户数据躺在Redis里。用代码表示大概是String token UUID.randomUUID().toString(); String key login:token: token; redisTemplate.opsForValue().set( key, JSON.toJSONString(userDTO), 30, TimeUnit.MINUTES );所以批量生成token这件事本质上就是往Redis里批量塞“钥匙用户数据”的键值对。搞清楚这一点脚本怎么写就非常清晰了。2.2 UUID token和JWT的差别直接决定了脚本长什么样你如果翻过token相关的技术贴会发现JWTJSON Web Token非常流行因为它把用户信息直接编码进token字符串里服务端不用存储。那为什么黑马点评不用JWT根本原因是教学定位和实际需求不同。Redis加随机token的方案是标准的服务端会话管理过期时间可控、可以主动踢人、可以滑动续期、可以随时在服务端删掉某个会话。JWT虽然无状态但发出去的token没法主动收回必须配合黑名单或续签机制才能弥补。热搜词里经常出现的“token exchange failed”“token失效”这类问题在JWT方案里大多是签名密钥不一致或token过期在Redis方案里则通常是key不存在、TTL耗尽、Redis连接库选错。排查方向完全不一样先把项目用的是哪种方案确认清楚后面的问题才查得动。2.3 拦截器校验token的完整链路以及token失效的真正根源黑马点评的拦截器逻辑大致是从请求头里取出token取不到就放行但标记“需要登录”或者直接返回401。取到token后去Redis查login:token:{token}这个key。查不到判定token失效返回未登录。查到了顺手刷新TTL相当于滑动续期把用户信息放进ThreadLocal放行请求。你可以看到这里绝大多数“token失效”都不是token字符串本身坏了而是Redis里对应的key已经过期或者压根没写进去。排查的时候就一件事拿着报错信息里的token去Redis里GET一下看看到底有没有这个key。十次有九次问题一下就清楚了。3. 手把手写批量生成脚本一次造出1000个可用token3.1 动手前的准备把项目里的Redis配置和key前缀确认清楚写脚本前花两分钟看一眼项目源码里的Redis配置和登录逻辑把host、port、db、key前缀、TTL全部记下来。这一步做扎实后面能少踩一半的坑。我当时确认的核心参数就四样Redis地址localhost:6379数据库编号db0key前缀login:token:过期时间1800秒30分钟确认完毕再写脚本。如果这些值拿不准脚本写得再漂亮也是白搭。3.2 主脚本UUID版批量生成对应黑马点评默认方案这是最贴合项目现状的写法。直接生成UUID作为token模拟用户信息批量写入Redis最后再导出一份token清单给压测工具用。# -*- coding: utf-8 -*- import json import random import uuid import redis r redis.Redis( hostlocalhost, port6379, db0, decode_responsesTrue, passwordNone ) NICKNAME_POOL [ 星野, 阿澈, 小鱼, 丸子, 大白, 桃子, 木槿, 北岛, 南风, 拾柒 ] def build_user(index: int) - dict: return { id: 10000 index, nickName: f{random.choice(NICKNAME_POOL)}_{index}, icon: fhttps://example.com/avatar/{random.randint(1, 999)}.png } def generate_tokens(total: int 1000, ttl: int 1800) - list: tokens [] for i in range(total): token uuid.uuid4().hex user build_user(i) key flogin:token:{token} r.setex(key, ttl, json.dumps(user, ensure_asciiFalse)) tokens.append(token) if (i 1) % 200 0: print(f已生成 {i 1}/{total}) return tokens if __name__ __main__: token_list generate_tokens(1000, 1800) with open(tokens.txt, w, encodingutf-8) as f: f.write(\n.join(token_list)) print(f完成共 {len(token_list)} 个 token已写入 tokens.txt)几个关键点的说明uuid.uuid4().hex生成的是去掉横线的32位字符串和项目里保存的UUID格式保持了一致。setex是原子操作把set值和设置过期时间一步做完避免先set再expire中途崩溃导致Redis里残留永不过期的垃圾key。decode_responsesTrue让读取结果直接是字符串不然后面调试的时候会一直被bytes类型困扰。ensure_asciiFalse保证中文昵称正常写入不至于变成一长串\uXXXX转义字符。3.3 参数计算的核心就一个点TTL到底设多长TTL是这套脚本里最需要动脑的参数。项目默认的登录过期时间是30分钟但压测往往不是马上就能跑起来光准备测试数据、调整压测脚本、调试参数就可能花掉半小时。我当时的做法是把压测环境单独的TTL拉到7200秒先保证压测过程中token不会中途集体失效。压测结束之后再去Redis里把这批key统一清掉不影响正常开发。如果你要测试的接口规模很大或者压测脚本执行时间长甚至可以把TTL设成86400秒压测完成后再单独写一条命令清理。redis-cli --scan --pattern login:token:* | xargs redis-cli del这样既保证token在测试期间不失效也不至于在Redis里留下大量永不过期的脏数据。3.4 进阶玩法项目如果改成JWT方案脚本怎么调整在看热搜词的时候会发现很多面试题都围绕JWT展开比如“JWT实现token登录验证”“JWT实现token续签”。如果你想把项目从Redis会话方案升级成JWT或者只是想在本地对比演示批量生成JWT token的脚本也不复杂。# -*- coding: utf-8 -*- import time import jwt SECRET_KEY your-hmdp-secret ALGORITHM HS256 def make_jwt(user_id: int, nickname: str, expire_minutes: int 30) - str: payload { userId: user_id, nickName: nickname, iat: int(time.time()), exp: int(time.time()) expire_minutes * 60 } return jwt.encode(payload, SECRET_KEY, algorithmALGORITHM) tokens [] for i in range(1000): token make_jwt(10000 i, fuser_{i}, 30) tokens.append(token) with open(jwt_tokens.txt, w, encodingutf-8) as f: f.write(\n.join(tokens))JWT版脚本写入时要注意secret必须和项目里拦截器验签用的完全一致否则就会出现热搜词里那种“token exchange failed”或者签名验证失败的情况。JWT的好处是服务端无状态不需要Redis参与校验但缺点是token一旦发出去就管不住了。所以真实项目里往往会给JWT叠加Redis白名单或者设计双token机制做续签这在上线前是要想清楚的。3.5 验证生成的token到底能不能用脚本跑完不要直接上压测工具先抽查。我习惯用redis-cli做三连查redis-cli keys login:token:* | wc -l redis-cli get login:token:具体的token字符串 redis-cli ttl login:token:具体的token字符串第一行看数量是不是1000第二行看value结构是否和项目代码里的UserDTO字段对得上第三行看过期时间还剩多少。确认Redis侧没问题之后再拿其中一个token放到Postman或者curl里请求一个需要登录态的接口比如查询当前用户信息curl -H Authorization: 具体的token http://localhost:8080/api/user/me返回200且能拿到用户数据这批token才算真正可用。4. 批量造token时的常见坑与排查顺序4.1 所有token都提示未登录八成是Key前缀不一致这是最容易踩的坑。项目里登录代码写的key是login:token:{token}脚本里如果写成了token:{token}或者漏了login:前缀Redis当然能写入成功但项目拦截器拿着请求头里的token去查login:token:{token}查不到自然判断未登录。排查方法很直接用redis-cli keys看一眼实际生成的key长什么样和项目代码里的拼接规则比对一遍立刻就能定位。我见过有人在这里卡了一下午最后发现只是少了一个前缀。4.2 token明明刚生成却说失效看看TTL是不是已经耗尽黑马点评的登录是30分钟滑动过期。压测脚本在启动那一刻读取token列表如果压测跑到第40分钟最早一批token早就过了TTL被Redis自动清理了。这里面有个容易忽略的点滑动过期指的是用户每次访问都会刷新TTL但如果压测工具在某个阶段只发请求不携带token或者两个请求之间的间隔超过30分钟token照样会失效。处理办法就三条压测前把TTL调大到7200秒压测工具里保证每个token被持续复用压测完成后统一清理测试key。4.3 Redis库写错了写入成功等于白写本地开发的时候经常一台机器上跑好几个项目Redis数据库编号各不相同。黑马点评默认用db0但你自己起过别的服务可能把默认库改了如果项目实际用的db3脚本连的是db0批量写入全部进错库。项目端怎么查都查不到还以为是逻辑写错了。脚本里的db参数一定要和项目配置文件对齐。这个参数在代码里就一行但错了就是全线崩溃。另外还要确认password字段。如果Redis设置了密码而脚本没填写入直接报NOAUTH Authentication required这种错误好在报得明显反而不会让人困惑太久。4.4 中文昵称乱码或者JSON解析失败序列化姿势不对往Redis里存数据必须以字符串形式存。常见两种错误第一种直接存了Python字典对象进去项目端用JSON.parse解析的时候直接抛异常。解决办法是写入前做json.dumps。第二种写入时没加ensure_asciiFalse中文昵称全被转成了\uXXXX格式。虽然技术上说能存能读但调试的时候满屏转义字符根本看不出数据对不对。加一个参数就能解决的小问题不值得浪费十几分钟。4.5 用户ID撞车导致数据互相覆盖造数逻辑要加序号用随机数生成用户ID批量造1000个token撞车概率远比想象得高。一旦两个token指向同一个用户ID压测的时候点赞和关注数据会互相干扰排行榜数据也彻底没法看。解决办法最简单也最可靠ID用固定基数加序号比如10000 i保证全局唯一。昵称也一样加上序号后缀这样即使随机词库出现重复也能通过后面的序号快速区分用户。4.6 会续期才能不失效顺带把token续签机制说清楚黑马点评默认的滑动过期本身就是最简单的续签用户每次操作拦截器刷新TTLtoken就一直有效30分钟不操作才会自动过期。这在Redis方案里是天然的能力不需要额外开发。如果换成JWT就享受不到这种被动续期的便利了。面试经常问“JWT如何实现token续签”实操中常见的落地方式有两种第一种是双token机制短时效的access_token负责日常请求长时效的refresh_token负责在access_token过期后换取新token。缺点是客户端要多处理一次过期刷新逻辑。第二种是Redis白名单方案签发JWT的同时往Redis里记录一个有效token白名单每次请求先查白名单再验签。相当于把无状态的JWT变成半有状态牺牲了一部分JWT特性换来了可控性。两种方案我都跑过。双token更贴合无状态架构白名单方案在现有Redis环境里改造更少。具体选哪种要看你们项目对“主动踢人”和“服务端控制会话”的需求有多强。最后分享一点实际操作中的体会这套批量生成token的思路不止适用于黑马点评。任何用Redis做会话管理的项目遇到“需要模拟大量用户登录”的场景都可以用同样的脚本思路解决。核心就三层摸清token的存储结构写脚本批量造用户并写入Redis再拿真实接口验证token可用性。我个人最大的体会是造数据这种事一定要做成可重复执行的脚本而不是手工操作一次就完了。项目开发过程中Redis数据会被清掉测试环境会切换token列表随时需要重新生成。把脚本做成带参数的、可以反复跑的自动化工具每次用的时候改个数量、改个TTL就能直接出结果比临时抱佛脚手工造数据强太多。最后一个小建议脚本里涉及的所有配置项比如key前缀、TTL、Redis db编号都集中放在文件顶部并在注释里注明来源是哪段项目源码。过两个星期再回头看这个脚本不用翻代码就能知道每个参数的含义省下的时间远超写这段脚本本身的花费。
网站建设高端定制企业官网