新闻详情

新闻详情

首页 / 资讯中心 / 详情

短链接系统从零实现:发号器、缓存与高并发全链路解析

发布时间:2026/9/26 18:19:55来源:尧图网络
短链接系统从零实现:发号器、缓存与高并发全链路解析
做短链接系统之前我原以为这不过是个“长网址转短码”的小工具顶多写个发号器加一张映射表就完事了。等真正从零开始搭完一套能扛住生产流量的短链接服务我才发现里面藏着不少值得掰开揉碎讲的东西。短链接的核心价值从来不只是“把URL变短”而是一个集编码算法、发号策略、存储选型、缓存设计、跳转追踪、安全防护于一体的链路系统。如果你正准备自己动手实现一套或者正在做技术选型想摸清底细这篇文章会把我在落地过程中验证过的方案、踩过的坑、以及不值得再走一遍的弯路都摆出来。1. 系统结构拆解一个短链接服务到底由哪些模块组成先别急着写代码。短链接服务从用户点击“生成”到访客最终落地到目标页面中间要经过至少四个核心环节。把这些环节一层层拆开整个系统的骨架就清晰了。1.1 核心链路从长链接进入到最后跳转用户把长链接粘贴进输入框点击生成前端请求到达后端接口。后端首先要做的不是直接存库而是校验这个长链接的合法性——是不是一个可访问的HTTP/HTTPS地址、有没有恶意特征、是不是已经存在于系统里存在就直接复用老短码避免数据膨胀。校验通过后系统分配一个短码把“短码与长链接的映射关系”写入存储返回完整的短链接给用户。当访客在浏览器里打开这个短链接时DNS解析落到你的服务上短码从URL路径中被提取出来例如https://your.domain/xk9f2p中的xk9f2p系统拿着这个短码去缓存里查映射。命中就直接返回302 Location跳转没命中就穿透到数据库查询查到之后再回填缓存。整个过程从请求进来到浏览器收到跳转指令理想状态下应该控制在10毫秒以内。1.2 三大核心子系统生成、跳转、统计把链路摊开看系统本质上由三个子系统组成。生成子系统负责短码的分配与持久化。这个模块的核心难点不在“怎么写接口”而在“如何保证短码唯一且高效”。后面我会专门讲发号器设计。跳转子系统负责短码到长链接的解析与重定向。这个模块最容易被低估因为看起来就是“查表-重定向”两件事。但真实场景里要考虑缓存击穿、缓存雪崩、单短码热点、恶意遍历短码、失效短码的快速判断每一样都需要单独设计。统计子系统负责记录每一次点击的来源、IP、设备、时间。短链接系统在企业场景里往往不是单纯“缩短URL”而是渠道追踪工具。投放人员要看某个渠道带来多少点击所以统计模块的可扩展性从一开始就要留好。我在实际项目中见过不少失败的方案最典型的是把统计埋点直接写在跳转接口里同步落库。一旦访问量上来数据库先被写满跳转链路跟着一起雪崩。正确的做法是把统计事件异步化也就是跳转接口只干一件事——查映射、回302然后把点击事件丢进消息队列由消费端异步落库。1.3 技术栈选型逻辑为什么要这样搭短链接系统不挑技术栈我用的是Go Redis MySQL的组合这个选型理由很直白Go的并发模型处理高并发HTTP跳转请求有天然优势Redis承担短码映射的读缓存和发号器的原子自增MySQL做最终的数据持久化。如果你更熟悉Java、Node.js或者Python同样能搭出来核心逻辑是通用的。关键不是语言而是每一层承担什么职责。Redis负责热数据路径MySQL负责冷数据存储和查询消息队列我用的是Kafka体量小的也可以用RabbitMQ或者Redis Stream负责削峰填谷。这套三层结构几乎成了短链接系统的标准解。2. 核心算法解析短码生成方案怎么选短码是短链接系统的门面用户看到的就是那一串字符。短码的生成方案直接决定了系统的并发上限、数据表结构、以及后续的维护复杂度。2.1 自增ID Base62编码最稳的方案我用的是“全局自增ID Base62编码”方案。系统每接收到一个生成短链接的请求就从Redis里执行INCR shortlink_id拿到一个全局自增的整数ID然后把这个十进制整数转成62进制字符串——用0-9、a-z、A-Z共62个字符表示。结果就是短码。举个例子假设当前自增ID是100000转成Base62之后是q0M具体值取决于你的字符表顺序。这个方案的好处极其明显——短码天然唯一因为源头就是唯一自增ID同时短码长度稳定可控在数据量没到千万级之前短码长度基本保持在6到7位完全够用。我在编码时用的是自定义字符表而不是直接用现成库。原因是自定义字符表可以去掉容易混淆的字符比如数字0和大写字母O、数字1和小写字母l避免用户手动输入短链接时出错。这个细节在生产环境里很实用。2.2 哈希截取方案简单但有冲突风险另一种常见方案是把长链接做哈希MD5或MurmurHash取结果的前几位作为短码。这个方案的好处是同一个长链接生成的短码是固定的不用做“查重后复用”的逻辑坏处是哈希截断必然存在碰撞风险。一旦两个不同的长链接哈希出同一个短码就要做冲突检测和二次哈希反而增加了系统的复杂度和响应延迟。哈希方案在并发量不高的内部工具场景下能用但作为对外开放的短链接服务我强烈建议用发号器方案。发号器方案把“冲突”问题从源头消解了而不是等撞上了再解决。2.3 短码查重绝对不能省略的一步不管是哪种方案短码落库之前必须做唯一性校验。我的实践是在数据库层给短码字段加唯一索引同时在写入前先查一次Redis缓存。有人觉得“有了唯一索引查重就交给数据库报错好了”理论上确实如此但生产环境里数据库唯一索引冲突抛异常会打断批量写入的节奏而且异常日志会刷得很猛。更好的做法是“先查Redis再写数据库数据库唯一索引兜底”——三层防线同时存在才能保证短码绝对唯一。这个环节还有一个容易踩坑的细节一旦短码查重发现已存在前端拿到的应该还是原来的短链接而不是报错。很多用户会重复提交同一个长链接这种场景下直接返回已有短码既省了存储空间也让用户觉得系统很“智能”。3. 存储与缓存设计数据层要解决的关键问题短链接系统的数据模型极度简单——一张表两个字段短码、长链接再加创建时间和过期时间。但就是这张简单的表在高并发场景下藏着不少讲究。3.1 数据库表结构设计与索引策略表结构我保留最精简的版本生产环境里加了一些企业级字段CREATE TABLE short_link ( id bigint unsigned NOT NULL AUTO_INCREMENT, short_code varchar(16) NOT NULL COMMENT 短码, long_url varchar(2048) NOT NULL COMMENT 原始长链接, expire_at datetime DEFAULT NULL COMMENT 过期时间, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_short_code (short_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时最需要留意的两个点第一short_code必须加唯一索引这是短码唯一性的数据库兜底第二long_url字段长度要留够。我见过有人用varchar(255)存长链接结果遇到带了一长串追踪参数的URL直接报错。2048是常见选择但如果你预判会有极端场景用TEXT类型也行只是会牺牲一点查询性能。过期短链接的清理我用的是定时任务 批量删除。每过一个小时扫描一次expire_at不为空且已经过期的记录按主键ID分批删除避免一次DELETE太多锁住大范围行。3.2 Redis缓存策略缓存什么、缓存多久、怎么更新Redis在短链接系统里承担两个职责短码到长链接的映射缓存以及发号器的原子自增。映射缓存我用的是String结构key为shortlink:{shortCode}value为长链接。缓存过期时间设置成24小时配合LRU淘汰策略。为什么要设过期时间而不是永久有效因为短链接系统天然存在“热点短码”和“冷门短码”之分老的短码热度下降后让缓存自然过期可以减少内存占用。即便缓存过期了请求会穿透到MySQL数据库查到后重新回填缓存对用户体验几乎没有影响。回填缓存时一定要设置过期时间而且要设置一个随机值比如24小时加上0到5分钟的随机偏移。如果不加随机偏移大量短码同时过期请求在同一个时间点全部穿透到数据库数据库瞬间被打满这就是经典的缓存雪崩。3.3 多实例部署下的发号器设计单机部署的时候Redis的INCR命令做发号器毫无压力。但如果你做了多实例部署就会遇到一个问题每次生成短链接都调一次RedisRedis挂了或者网络抖动了生成接口会跟着不可用。更稳的方案是“号段模式”。从Redis里一次取一批ID比如一次取10000个每个实例在内存里维护自己的号段用完了再向Redis取下一批。这样Redis的访问频率直接降低三个数量级即使Redis抖动一下实例内存里还有几千个号可用。我实践的参数是每次取10000个号段按当前业务量计算一次Redis交互可以支撑很久几乎可以忽略发号器的性能瓶颈。号段模式还有个附加好处可以支持批量生成短链接的场景。运营人员一次性导入一万条长链接如果用逐条INCR 逐条写库耗时非常难堪。号段模式下一次取号、批量写库效率完全不同。4. 统计模块与技术实现点击数据从哪里来统计模块是短链接系统里最容易被“先不做”的部分。但如果你做过渠道投放场景就会知道没有统计的短链接系统等于废了一半。投放人员不仅要知道短链接被点了几次还要知道是谁在什么时间点、从哪个平台点进来的。4.1 埋点设计不影响主链路的最小侵入方案我的做法是把统计逻辑从跳转链路中摘出去。跳转接口收到请求后只做三件事查缓存拿到长链接、记录一个访问日志Nginx访问日志或者独立的事件日志、返回302。访问日志里的关键字段包括短码、访问时间、访客IP、User-Agent、Referer、可选的自定义渠道参数。日志产生之后通过Filebeat之类的采集组件持续收集发送到Kafka。下游的消费服务从Kafka拉取数据解析User-Agent得到设备类型和浏览器类型解析IP得到地理位置信息最后批量写入ClickHouse或者MySQL里的统计宽表。这么做的核心逻辑是“先保证跳转成功率再谈统计”。把统计从同步链路里摘出来之后即使统计系统整体挂了短链接跳转功能依然不受影响。这是生产系统设计里非常重要的隔离思想。4.2 PV / UV / 独立访客的计算口径统计口径上踩过坑的人都知道“PV和UV看着简单定义起来全是细节”。PVPage View就是短链接被访问的总次数每一条点击日志都算一次PV。UVUnique Visitor需要按访客去重我用的口径是“IP User-Agent”组合去重24小时内视为同一个访客。用Cookie去重更准但短链接场景里很多点击来自微信、抖音这类App的内置浏览器Cookie不一定能正常写入所以IPUA组合是更现实的方案。还有一个容易被忽略的点302跳转本身由搜索引擎的爬虫、微信的链接防屏蔽检测服务触发时这些流量不是真实用户的点击却会记录成PV。为了过滤这部分噪音我会在统计逻辑里维护一个“已知爬虫UA清单”匹配到的记录打上爬虫标签不进入有效PV统计。4.3 异步落库消息队列扛住刷量场景接入消息队列还有一个被很多人忽视的好处——它有天然的抗流量峰值能力。比如某个短链接被放到了App首页瞬间涌入几千个点击请求。如果这些请求直接写数据库数据库写入线程池会瞬间被打满。但经过Kafka缓冲之后落库速度就完全由消费端决定数据库始终处于“可控的写入压力”下不会被打爆。如果你不想引入Kafka这种重量级组件体量在百万级以下时用Redis的List结构做简易队列完全够用。生产端LPUSH消费端RPOP起一个定时任务批量处理。我在早期版本里就是这么实现的跑了近一年没出过问题。技术选型永远不要一步到位够用就行。5. 安全防护与踩坑实录那些上线之后才暴露的问题短链接系统上线之前我心里最没底的不是功能跑不跑得通而是这个系统一旦对外开放会成为恶意流量的靶子。实际上线后遇到的问题也确实集中在这个方向。5.1 并发冲突短码被同时请求两次第一种高频问题不是安全攻击而是并发写入导致的数据重复。两个请求同时携带着同一个短码进来都去数据库插入其中一条会触发唯一索引冲突报错。排查方式很简单——看后端日志里有没有Duplicate entry关键字的报错。应对方案分两层数据库层已经有唯一索引兜底遇到冲突直接捕获异常回查已有记录返回即可应用层在插入前先查一次缓存能拦下大部分重复请求。实测下来加了应用层缓存查询后数据库层的唯一索引冲突日志直接少了九成以上。5.2 恶意遍历短码被脚本批量探测短码的字符集是有限集合62个字符的N次方这决定了它天然可以被枚举。黑客或者竞争对手可以写一个脚本遍历所有可能的短码组合请求你的跳转接口找出所有有效短链接再对这些链接做批量刷量或者收集信息。应对方案是我在实际运维中逐渐补全的目前有效手段有三个第一接口限流。针对同一个IP的短链接访问频率做限制比如一分钟内超过200次请求就弹出验证码或者直接拒绝。限流用的是Redis计数简单有效。第二短码长度提到8位以上。短码每多一位枚举空间就扩大一个量级。8位短码的枚举空间是62的8次方约两百万亿脚本遍历的代价大到不值得。第三监控异常模式。同一个IP在短时间内请求了大量不同短码触发告警人工确认后加入黑名单。这个手段最原始但也是最后一道防线。5.3 失效短码的高效判定短链接系统都会遇到“短码不存在”的请求。最糟糕的做法是每次都查数据库然后发现查不到。因为这种“无效请求”如果来自恶意脚本会直接击穿缓存、压垮数据库。我的做法是在Redis里维护一个 “短码黑名单缓存”查缓存没命中时再查数据库如果数据库也没查到就往Redis里写入一个空值或者特殊标记过期时间设置得短一些比如5分钟。这样同一个不存在的短码在短时间内再次被请求时直接命中“空缓存”无需穿透数据库。这个做法非常基础但能挡住大部分恶意探测的压力。5.4 跳转安全不允许开放重定向安全评审里被点名的一个问题就是“开放重定向漏洞”。具体场景是攻击者构造一个短链接系统生成的短码指向恶意外部网站诱导用户点击用来钓鱼或者传播恶意链接。这就是“短链接成为恶意帮凶”的情况。我的处理方式分两步生成短链接时用服务端请求库Golang的http.Client发起一个HEAD请求验证目标地址是否可访问并检查域名是否命中已知恶意域名库。跳转时在302 Location里强制要求目标地址必须是http/https协议禁止javascript:这类非标准协议。这两个措施叠加可以挡住绝大部分恶意场景。还有一个细节值得分享跳转时使用302临时重定向而不是301永久重定向。表面上看301对SEO更友好但301 Permanent Redirect会被浏览器缓存意味着用户第一次访问后再次点击短链接浏览器可能直接走本地缓存根本不再请求你的服务器。这样你就永远失去了对这个链接的后续访问数据的掌控力。除非你确定这个短链接永不变化否则一律用302。6. 压测数据与性能调优经验分享短链接系统上线后我在本地和生产环境各做了一轮压测。压测工具用的wrk一个轻量级HTTP压测工具模拟真实的并发跳转场景。6.1 压测结果核心链路究竟能扛多大流量压测环境是4核8G的云服务器单实例部署Redis和MySQL都跑在同一台机器的Docker里。用wrk发起2000个并发连接持续压测60秒测试接口是短链接跳转接口。实测数据是QPS稳定在12000左右P99延迟在12毫秒内没有出现超时或者错误响应。这个数据对于一个单实例短链接服务来说已经够用。如果把应用层和Redis分开部署加上连接池调优单实例的瓶颈还能再往上推一些。性能瓶颈的分析很有意思——最终卡住QPS的不是应用代码而是网络连接的文件描述符数量。Linux默认的ulimit是1024意味着单进程最多只能同时打开1024个文件描述符跑满后就无法接受新连接了。调优方式是执行ulimit -n 65535同时把/etc/security/limits.conf里的 nofile 也改到65535。这个坑如果不提前踩到上线前压测就会摸不着头脑。6.2 Redis连接池与超时配置通过压测发现应用和Redis之间的连接管理对性能影响非常大。早期版本里我每次请求都新建一个Redis连接压测直接出现大量cannot assign requested address错误——系统把所有可用的本地端口都占满了连接无法建立。换成Redis连接池后问题迎刃而解。同时我把Redis的超时时间设置为“连接超时2秒读取超时1秒”这样极端情况下Redis不响应请求会快速失败而不是卡住用户。更重要的是给Redis访问加了一层本地缓存用bigcache内存缓存库同一个短码在几毫秒内被大量请求时大部分请求命中本地缓存Redis压力直线下降。6.3 数据库连接池和批量写入调优数据库写入侧的优化也有讲究。用Golang的GORM连接MySQL时连接池参数直接影响写入吞吐sqlDB, _ : db.DB() sqlDB.SetMaxOpenConns(200) // 最大打开连接数 sqlDB.SetMaxIdleConns(50) // 最大空闲连接数 sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大复用时间SetMaxOpenConns 设置得太小会导致写入排队太大则会增加MySQL的连接管理开销。200对我这个场景刚合适。短链接生成的批量写入场景我用的是拼接多行VALUES插入一次写500条比一条一条写快了近10倍。压测的最终结论是在单实例部署的前提下短链接系统的性能瓶颈几乎永远不会落在应用代码上而是落在网络层、连接池、GC参数这些外围配置上。所以遇到性能问题先别急着优化业务逻辑把连接池参数、内核参数、Redis配置都过一遍往往能找到意想不到的收获。7. 实际运维中的几个额外体会写到这里再分享几个我在实际维护中沉淀下来的判断算是给后来者的一些“认知避坑”。第一个是对缓存一致性的看法。短链接映射一旦写入几乎永远不会变更除非你做自定义短链编辑功能所以不需要像普通缓存那样考虑复杂的双删策略。你只需要做到“数据库有记录缓存迟早会补上”就行最终一致性完全够用。别在这个简单系统里引入分布式事务或者复杂的一致性协议那是给自己找麻烦。第二个是对短链接系统的定位认知。在企业内部短链接系统往往被纳入“基础工具”范畴但它的核心价值其实在于“数据回收”。短链接是你在第三方渠道里唯一能埋下的“自己的眼睛”——用户在抖音、微博、短信里点了什么链接带来多少次访问都通过这些短链接回到你的系统里。所以统计能力、标签能力、渠道管理能力值得花至少一半的精力去做。第三个是安全防护的节奏感。安全建设不用第一天就追求“全副武装”而是随着流量形态变化逐步加码。刚上线时做好基础限流和URL协议校验就够了等到出现恶意遍历、刷量苗头时再补黑名单、空缓存这些手段。安全策略的演进应该是“按需加固”而不是“一步到位”后者往往会把前期的开发节奏拖垮。短链接系统论复杂程度远不如电商、社交这类大流量系统但它是一个绝佳的“全链路练手项目”——麻雀虽小、五脏俱全从算法到存储、从并发到安全、从业务到运维全部覆盖。把这个系统吃透你至少能在“中等复杂度系统设计”这个级别上形成一套完整的直觉判断这才是做这个项目最大的隐形收益。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

手写SVG鹈鹕骑自行车:从viewBox到SMIL动画实战 2026/9/26 21:52:22

手写SVG鹈鹕骑自行车:从viewBox到SMIL动画实战

上一篇我写了SVG的基础形状和颜色,接下来肯定不能只停在会画圆和长方形,早晚要碰路径、坐标系统和动画。这一篇我打算用一个能跑出画面的小项目来把这些东西串起来:在HTML里用SVG绘制一只鹈鹕骑自行车的2D动画。别看这个题材听起来有点无厘头…

阅读更多 →
WorkBuddy Enterprise 企业级 Agent 平台:SkillHub 技能沉淀与 Agent 任务编排实践 2026/9/26 21:52:22

WorkBuddy Enterprise 企业级 Agent 平台:SkillHub 技能沉淀与 Agent 任务编排实践

1. 从单兵作战到团队协同:WorkBuddy Enterprise 要解决的真实问题很多团队在用 AI 编程助手时都经历过这样一个阶段:某位同事用 CodeBuddy 把效率拉满,一个人一天能提交过去三天的代码量,成了名副其实的「超级个体」。但问题随之而…

阅读更多 →
银河麒麟桌面系统程序崩溃数据采集与分析实战指南 2026/9/26 21:52:22

银河麒麟桌面系统程序崩溃数据采集与分析实战指南

“这系统又崩了”——在安可项目推进的这几年里,这句话是我在国产化终端现场听得最多的一句。尤其银河麒麟桌面系统(Kylin Desktop)部署到办公和业务一线之后,程序闪退、窗口消失、界面假死这些问题几乎每天都在发生。大多数同事的…

阅读更多 →
open-code-review:基于Git Diff的开源代码审查协议 2026/9/26 21:52:22

open-code-review:基于Git Diff的开源代码审查协议

1. 项目概述:这不是一个“代码审查工具”,而是一套可嵌入开发流程的开源协作协议“open-code-review”这个名称乍看像某个具体软件,但实际它代表的是一种正在快速演化的工程实践范式——把传统封闭、人工驱动、高门槛的代码审查(C…

阅读更多 →
通信型CRM如何重塑客户信息管理与坐席工作效率 2026/9/26 21:52:22

通信型CRM如何重塑客户信息管理与坐席工作效率

干这一行久了,我越来越发现一个规律:很多客服团队和销售团队的痛点,根本不是“没人干活”,而是“工具太多太碎”。你看前台业务员,手机上挂着微信,桌面上开着聊天窗口,座机旁边还有一部话务耳机…

阅读更多 →
CRM选型不纠结:从客户数据库到永久在线的销售管理工具 2026/9/26 21:52:16

CRM选型不纠结:从客户数据库到永久在线的销售管理工具

不用再纠结要不要上 CRM 了,真正值得花时间想清楚的是:你团队现在缺的到底是一套「客户数据库」,还是一个「能让销售动作不变形」的日常工具。我做销售管理这几年,见过太多团队花几万块上系统,最后用成了 Excel 加强版…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉