六月第一场闷雷滚过北京时,星辰办公室停了电。
不是机房停电。三楼这栋旧楼的保险丝烧了,吊扇转得越来越慢,最后停在众人头顶。窗户全打开也没有风,打印机里刚吐出一半的纸卡在滚轴上,像一条没说完的话。
机房电话随即响起。
值班学生摸黑接起来,听了几句,捂住话筒:“有人说自己被我们封了,非要找负责人。”
“问工单号。”马飞说。
“他说没有。页面只写访问过于频繁,请稍后再试。”
马飞接过电话。对面是个年轻男人,语速很快,背景里还夹着儿童哭声。他说自己在一家小型机械厂做资料员,厂里准备修一台进口数控机床,他用星辰查伺服驱动器型号。拨号上网按时计费,连接一次不容易,他习惯把十几个词提前写好,连上以后快速逐个搜索。昨晚查到第七个,页面突然拒绝,重新拨号仍然不行。
“你们是不是把我当坏人了?”他问。
“现在还不能确定是哪条规则触发。”马飞说,“把大概时间和查询词告诉我,我们查记录。你的姓名和工厂名称可以不留。”
“不留你怎么回复?”
“留一个你愿意提供的联系方式,单独放在工单里,不和查询记录混在一起。”
对方迟疑片刻,报了传呼号。
停电恢复前,雷雨先落下来。豆大的雨点砸在铁皮窗台上,办公室里终于凉了一点。大家把桌椅往里挪,还是有半面故障表被溅湿,红蓝铅笔线在纸上慢慢晕开。
苏雨桐用报纸吸水,发现“明确拒绝比例”那一栏正好糊掉了。
“像是提醒我们重算。”她说。
正式上线首日记录的三千八百四十二次查询里,有一百一十七次被系统拒绝。当时团队正忙着处理容量故障,只把拒绝当成保护服务器的必要动作。规则很简单:同一来源在短时间内请求过多,就延迟;继续请求,就拒绝半小时。
在压力测试里,这条规则有效阻止了测试程序失控。进入真实网络以后,“同一来源”却不一定代表同一个人。学校机房几十台电脑可能共用出口,一家单位也可能只有一个拨号账户。相反,一个人重新拨号,又可能得到新的地址。
那名机械厂资料员的记录很快找到。他在两分四十秒内发出七次不同查询,每次都完整等到了结果,只是间隔短。第七次以后触发了限流。没有恶意程序,没有重复刷新,也没有试图下载整站。
“他只是手快。”值班学生说。
苏雨桐看着窗外的雨:“也可能是电话费贵。”
她自己去学校机房查资料时,也会先在纸上列好关键词。按分钟计算的上网费让每一次停顿都有价格。程序把快速查询解释成可疑行为,实际上惩罚的可能只是一个想早点断线的人。
一百一十七次拒绝中,多少属于这种情况?
电恢复后,马飞没有立刻改限流参数。他们先把记录按行为拆开:相同查询连续重复、不同查询快速提交、翻页请求、抓取大量结果、失败后重试、多人共用出口。原先被挤在同一个数字里的动作,分成六摞打印纸。
最厚的是失败后重试。
“这一百一十七不能全算防住了滥用。”马飞说,“有一部分是我们先失败,再拒绝他们。”
陈维明靠在窗边扇合同副本,衬衫后背湿了一片:“可你也不能什么都放。前天有人一次拉走上千条结果,明显是来抄数据。”
“所以分开。”
“怎么分?每个用户先打电话自我介绍?”
苏雨桐把机械厂资料员的七个查询写在黑板上,旁边写另一组记录。另一组在一分钟内重复请求同一个词一百二十次,翻页顺序从一到五十,间隔精确得几乎没有抖动。
“人会受页面速度、阅读和输入影响。”她说,“程序更规律。但规律只能增加风险,不能证明身份。”
她建议设置多层处理:普通快速查询先延迟并提示;同一会话的失败重试不重复计入;规律性批量翻页降低速度;明显持续抓取才短时拒绝。学校和单位的共享出口不能一封了之,要结合会话和请求模式。
“会话怎么认?”学生问。
“给浏览器一个随机编号,不要姓名。定期失效,不跨用途使用。”
“那我们就能知道他搜过什么。”
“所以查询日志和联系方式必须分开。”苏雨桐说,“能不留的字段不留,需要调试的只保留一段时间。不是因为硬盘能装下,就可以永远保存。”
陈维明抬头:“查询词很值钱。客户想知道大家在找什么。”
“分布统计可以做。”马飞说,“个人轨迹不能卖。”
“你们又给未来收入砍一刀。”
“先砍掉不该卖的。”
雷声渐远,屋里重新热起来。保险丝虽然换了,吊扇却只肯用最低的一档转,扇叶每转一圈便发出轻微的咔嗒声。众人讨论到晚饭时间,谁也不愿再吃方便面。马飞提议去楼下小馆,结果刚走到门口,又有电话进来。
这次是学校机房老师。二十多名学生同时上检索课,星辰把整个机房的出口限速了。老师没有发火,只问以后上课前是否需要提前报备。
“不需要。”马飞说,“公开搜索不能要求正常用户预约。”
挂掉电话,他把钱包递给值班学生,让他买四碗炸酱面带回来。学生问要不要加肉丁,马飞看了一眼钱包里的零钱,说都按普通的。
苏雨桐把钱包推回去:“今天我发了论文翻译费,我请。”
“公司加班饭,公司出。”
“那就记账。你别每次用自己钱包装公司的大方。”
她从抽屉里拿出一本薄账册,在“临时餐费”后写下日期和金额上限。陈维明看得直摇头,说四碗面也要立规矩。苏雨桐说,正因为是四碗面,才最容易没人记;等到四台服务器、四十个人,谁也说不清公司和创始人的钱从哪一刻混在一起。
面送回来时已经坨了。塑料袋底积着汤,四只一次性筷子只有三只能掰开。陈维明用两根圆珠笔夹着面吃,边吃边看新的拒绝页面文字。
原来的提示只有一句“访问过于频繁”。新的版本说明等待时间、可能原因和工单入口,还明确写着:本次限制不代表对用户身份或意图的判断。
“谁会读这么长?”陈维明问。
“被挡住的人。”机械厂资料员在回访电话里回答了同样的问题。
新的规则先在备用端口运行。马飞让人模拟机房二十台电脑共用出口,苏雨桐模拟快速输入不同查询,学生运行自动翻页脚本。正常用户的请求被适度排队但没有整段封禁,规律抓取则很快降到每分钟只能取少量页面。
测试结束后,他们给那名资料员发去传呼,请他再次拨号试用。
半小时后,对方回电。他顺利查完了十二个型号,其中两个没有结果。
“没结果没关系。”他说,“至少别在我电话费最贵的时候,说我是坏人。”
苏雨桐把这句话写进故障表旁的空白处。
随后是那所高校。机房老师组织同样的检索课,二十三台机器共用出口,只有两次请求因过快翻页被短暂延迟,没有整间机房一起被封。
真正的自动抓取也没有消失。新规则上线当晚,一个来源连续变换拨号地址抓取商业资料。它避开了单一地址限制,却在请求顺序和间隔上留下相似模式。团队没有追查个人身份,而是限制了批量翻页可取得的字段,并给商业资料页加上来源与使用说明。
“挡不死。”值班学生说。
“本来也没有一条规则能挡死。”马飞说,“我们只能提高成本,同时别把正常人先挡在外面。”
一周后,一百一十七次拒绝被重新分类。二十九次属于明显自动抓取,十六次无法确认,二十四次是共享出口连带影响,四十八次源于故障重试或正常快速查询。
这个结果比“成功拦截一百一十七次异常访问”难看得多。
陈维明却没有再要求改成宣传语言。他从女儿的作业本上撕下一张没写过的方格纸,抄走四个数字,说回家前要去见一位客户。
“我怎么解释?”他问。
“就说我们发现规则误伤,已经改了。”苏雨桐说。
“客户会问,怎么保证以后不误伤。”
“不能保证为零。保证记录、复查和申诉。”
陈维明把纸折好:“限定真多。”
他说这句话时已经没有生气,倒像渐渐学会了一种星辰特有的方言。
晚上十点,办公室终于空下来。雨后的空气仍闷,街边传来西瓜摊贩的吆喝。马飞关掉电脑,发现苏雨桐还在修那半张被雨水洇花的故障表。
“重抄一张吧。”他说。
“颜色的位置还看得出来。”
“纸都皱了。”
“皱了也是原件。”她把纸铺在两本厚书之间,“等压平再贴回去。”
两人下楼买了半个西瓜。摊主用旧报纸包住切面,又额外送了一只小木勺。西瓜不算甜,靠近瓜皮的地方还有点生。他们坐在楼门口的台阶上,一人挖一边,谁都不想再上楼拿碗。
马飞说起机械厂资料员那句“别说我是坏人”。
“程序不会骂人。”他说,“可规则给出的待遇,会让人知道自己被当成什么。”
苏雨桐用木勺刮下一小块瓜瓤:“所以拒绝也得留一扇门。”
“门后要有人。”
“有人就要工资。”
“先记在账上。”
她笑了:“你终于学会了。”
街灯下,来往自行车的铃声一阵一阵。谁也没有谈可回收火箭,也没有谈改变世界。两个人只把半个不太甜的西瓜吃干净,把瓜皮扔进垃圾桶,再回楼上确认夜间值班的人知道新申诉流程。
一百一十七次拒绝没有消失。它们被留在日志里,成为星辰第一批关于边界的证据。
系统需要有拒绝的能力,否则会被少数请求拖垮。
但拒绝不能成为一句没有来路的判决。
这是他们在那个闷热雨夜得到的另一条规则:保护服务,不等于把每个被挡住的人都写成敌人。