ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java微服务短链接系统:架构拆解与Redis Stream异步统计实战

2026/10/8 13:48:46 拓冰建站 浏览量
Java微服务短链接系统:架构拆解与Redis Stream异步统计实战 简介一套基于Java核心技术构建的SaaS短链接管理系统设计源码面向需要批量生成和追踪短链接的企业及个人用户用于有效解决长链接冗长、分享不便、点击效果难以统计等运营痛点适配营销推广、活动投放与链接管理等场景。整套源码共219个文件其中188个Java源文件承载业务逻辑14个XML与11个YAML用于系统配置2个SQL文件负责数据库操作另有HTML页面、Lua脚本等辅助资源压缩包仅563KB目录结构清晰更便于学习与二次开发。系统内置PV、UV、UUId等多维访问统计指标兼顾链接安全监控与数据隐私保护项目还按admin、gateway、aggregation、project等模块划分可帮助理解SaaS平台的网关控制、数据聚合和后台管理流程。目前已有391人学习浏览适合具备一定Java基础、想掌握短链接系统设计及微服务模块拆分的开发者。1. 短链接系统到底值不值得自建从营销投放说到 PV/UV/UUId 统计做渠道投放的人应该都遇到过这个问题抖音、朋友圈广告里的跳转链接又长又丑平台还经常截断参数辛辛苦苦埋的 UTM 标识到后台一看全丢了。第三方的短链接服务确实省事但按条数收费、数据报表导不出明细、域名还被别人捏着怎么想都不踏实。这份基于 Java 的 SaaS 短链接管理系统源码解决的就是自建一套带数据回收能力的短链接平台这件事核心不是把长链接变短而是短了之后还能告诉你每一次点击来自哪里、是不是同一个用户、对转化有没有贡献。项目走的是微服务思路gateway 做入口网关aggregation 做数据聚合admin 管后台project 负责主业务配 Redis Stream 做消息异步处理PV浏览量、UV独立访客、UUId用户唯一标识都有对应的统计实现。适合的人群很明确正在做投放又不想被第三方短链服务绑架的运营团队以及想找一套能改成自己产品的 Java 微服务参考实现的开发者。2. 先拆架构从一次短链接点击看清 gateway、Redis Stream 和数据聚合模块的职责2.1 模块边界与一次完整点击的流转路径这套源码的模块划分非常贴近生产环境。打开根目录能看到典型的微服务结构gateway 模块承担统一入口负责路由转发和 Token 校验admin 模块处理用户、分组、短链接的后台管理操作aggregation 模块承担最累的活PV、UV、UUId 这些统计值都是在这一层聚合的project 模块是核心业务逻辑所在ShortLinkServiceImpl、RecycleBinServiceImpl 这些业务类都放在这里。另外 UserServiceImpl 管用户体系GroupServiceImpl 管分组短链接不是在用户维度下裸奔的每个链接必须归属到某个分组里这样营销活动才能按渠道做数据隔离。我在看代码的时候比较关注的是 RedisStreamConfiguration 和 ShortLinkStatsMsgListener 这两组类的联动关系。它们的存在说明这套系统没有走传统的请求进来同步查库同步写统计的路径而是把点击统计做成了异步事件流。理论上用户点击短链接后系统先 302 跳转到目标长网址同时把本次点击的原始信息扔进 Redis StreamShortLinkStatsMsgConsumer 负责从 Stream 取消息ShortLinkStatsMsgListener 再去做数据清洗和落库。这么设计的好处是高峰期点击量再大也不会把压力全打在数据库上网关只负责转发和快速响应。一次完整点击的链路可以这么理解用户在浏览器输入短链接DNS 解析后请求先到达 gateway 网关网关做跨域、限流、权限校验然后路由到 project 模块project 模块里的 ShortLinkServiceImpl 根据短码查出原始长链接执行 302 跳转跳转的同时点击事件被封装成一条消息推进 Redis Streamaggregation 模块的消费者监听 Stream拉取消息后计算 PV、UV、UUId写统计结果。从这套路径能看出一个很关键的设计判断跳转链路和统计链路是分离的跳转慢不会拖累统计统计挂了也不会影响用户访问。这种主链路轻、旁路重的做法在营销类系统里是合格解法。2.2 各模块文件职责速查与选型理由整个源码包有 219 个文件188 个 Java 源文件占了绝大部分14 个 XML 和 11 个 YAML 是配置层2 个 SQL 管数据库初始化。我整理了一张模块与核心类的对应关系表方便你按图索骥找代码模块/层级关键类职责gatewaygateway 相关路由配置请求入口、路由转发、统一鉴权adminUserServiceImpl、GroupServiceImpl用户管理、分组管理、后台配置projectShortLinkServiceImpl短链接生成、长链接查询、跳转逻辑projectRecycleBinServiceImpl回收站管理、软删除、恢复与清理aggregationShortLinkStatsMsgConsumerRedis Stream 消息拉取aggregationShortLinkStatsMsgListener、ShortLinkStatsServiceImpl统计消息消费、PV/UV/UUId 聚合落库公共RedisStreamConfigurationRedis Stream 连接、序列化、消费者组配置选 Redis Stream 而不是 RabbitMQ、Kafka这个取舍值得细说。对于短链接系统的量级绝大多数情况下单机 Redis 就能扛住点击消息的吞吐Kafka 在这里属于杀鸡用牛刀还要额外维护一整套 Kafka 集群。Redis Stream 的优势是和 Redis 共用一套基础设施消息保留策略由 XADD 的 MAXLEN 参数控制消费者组用 XGROUP CREATE 建好就行代码里 RedisStreamConfiguration 已经把 Stream 的 key、消费者组名、消息拉取线程数都集中配置好了这比每换一个环境就要去改消息队列参数要省心得多。关于数据侧2 个 SQL 文件的规模决定了这不会是一个玩出花样的多库架构更接近单体数据库 分表的设计。短链接表存短码和长链接的映射统计表按天存 PV、UV、UUId 的明细这和我看过的大多数自研短链接项目的表结构是一致的。这套结构放在 SaaS 多租户场景里靠 user_id 和 group_id 做数据隔离每个用户只能看到自己名下的链接和统计数据。2.3 动手跑起来之前的目录视角把项目导入 IDE 后先别急着点启动我建议按这个顺序读代码先看 RedisStreamConfiguration搞清楚 Stream 的 key 是什么、消费者组怎么建的这决定了你后面调试统计链路时去哪看消息积压然后看 ShortLinkServiceImpl找到短码生成和长链接查询的核心方法再看 ShortLinkStatsServiceImpl看统计数据的聚合维度是什么。这三个文件串起来整个系统的骨架就摸清了。提示项目里有一个 notfound.html 文件这是短链接不存在或者过期时展示的兜底页面。生产环境记得把它改成你自己的 404 风格否则用户访问一个失效短链会看到这个默认页面对品牌形象有影响。改法很简单找到模板渲染的配置项把静态资源路径指向你自定义的 HTML 就行。3. 核心链路代码走读短链接生成、跳转与 Redis Stream 异步统计的实现细节3.1 短码生成算法62 进制与碰撞处理短链接系统的地基是怎么把一个数字 ID 变成一串又短又不重复的字符。业界和这套系统用的方案基本都是 62 进制转换——用 0-9、a-z、A-Z 一共 62 个字符表示数字。假设系统内部每条长链接记录有自增 ID短码就是这个 ID 的 62 进制表示6 位短码最多可以表示 62^6 组链接约 568 亿个组合对绝大多数场景绝对够用。我在 ShortLinkServiceImpl 里找到了生成逻辑的核心片段用伪代码还原大概是这个意思public String encodeToBase62(long num) { // 字符表顺序固定不允许改动中间字符否则历史短码全部失效 String BASE62_CHARS 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ; StringBuilder sb new StringBuilder(); while (num 0) { // 取余数定位字符表下标 sb.append(BASE62_CHARS.charAt((int) (num % 62))); // 整除62继续处理高位 num num / 62; } // 反转拼接结果低位在右保证短码长度最短 return sb.reverse().toString(); }这段代码的核心逻辑只有两行第一行num % 62取余拿到当前位对应的字符下标第二行num / 62把数字缩小到下一轮。这里有一个实际开发经常踩的坑字符表的顺序一旦发布到生产环境就永远不能改否则同一个 ID 会映射出不同的短码线上查询会全部错乱。我一般会把这 62 个字符的排列用单元测试锁死任何改动直接构建失败。另外要提醒的是真正的生成链路不能只靠这一个方法。生产环境里并发创建短链接时ID 分发容易撞车。这套系统里如果 ID 来自数据库自增主键并发生成会有主键冲突风险我自己的习惯是配合号段模式批量取 ID每次拿一批号段在内存里慢慢用这样既减少数据库压力也避免了重复。3.2 跳转逻辑与统计消息的生产端埋点短链接跳转的实现是典型的缓存优先 数据库兜底。代码里 ShortLinkServiceImpl 的查询逻辑大概分三步先查 Redis 缓存key 是短码value 是长链接缓存没命中再查数据库查到后回填缓存并设置过期时间查不到则跳到 notfound.html 兜底页面。这里用了 302 而不是 301 跳转这个选择很多人看不懂我后面避坑章会专门展开说。统计消息的生产端埋点就在跳转命中的分支里关键事件是组装一个包含 shortUri、userId、groupId、访问设备 UA、IP 地址、当前时间戳的对象然后用 Redis Stream 的 XADD 命令把它推进消息队列。这套系统里 RedisStreamConfiguration 配置了 Stream key 的后缀策略通常按天分隔比如 shortlink:stats:20250607好处是后续清理历史消息直接删 key 就行不用做复杂的数据归档。3.3 消费端聚合PV、UV、UUId 的统计口径统计链路的后半段在 aggregation 模块。ShortLinkStatsMsgConsumer 负责从 Stream 里拉取消息ShortLinkStatsMsgListener 拿到消息后做维度拆分。PV 很好算消息每消费一条就加 1UV 的严谨做法是根据用户唯一标识去重。这里用户唯一标识不是登录用户的 ID而是匿名访客的标识常见做法是用 Cookie 里的随机 UUID或者对访问设备的 UA IP 做哈希。UUId 统计在代码里的口径是带用户身份的访问计数即登录用户的访问行为统计这个指标对营销分析的价值在于区分路过的人和真实注册用户。聚合落库我看到的做法是按天和短链接维度做批量写入统计表主键是日期 短码 分组 ID比明细表少好几个数量级查询报表时直接按天聚合出曲线响应速度能控制在几十毫秒内。整套异步设计的核心收益是用户跳转的响应时间完全不背统计的锅。4. 避坑实录短链接系统最常见的五个线上事故4.1 跳转状态码用了 301统计数字越看越假现象上线后发现短链接的 PV 统计远低于第三方工具的数据有时候同一条链接点击量一整天都不涨。原因浏览器对 301 跳转有缓存机制同一个短链接第一次访问后会被浏览器永久缓存后续用户再次点击请求根本不会到你的服务器直接命中本地缓存跳到长链接了统计埋点完全收不到事件。解决把跳转状态码从 301 改成 302。实践中 302 表示临时重定向浏览器每回都会重新请求短链接服务器统计才能完整记录每一次点击。这个错误刚做短链接系统的人大概率会踩代码里如果用 HttpServletResponse 做跳转记得response.setStatus(302)而不是默认的 301。4.2 Redis Stream 消费端重复消费导致 PV 虚高现象统计后台显示某条链接的 PV 在中午翻了一倍但投放渠道没加预算访问也不可能有这么大波动。原因消费者组在异常场景下没有正确 ACK消息被 Redis Stream 重新投递同一条点击事件被统计了两次。解决每次消费完业务逻辑必须调用XACK确认消息处理完成代码里 Consumer 的try-catch里要把 ACK 放在 catch 之外确保业务逻辑完整执行后才确认消费。我之前排查过一次类似事故发现是消费者线程抛了空指针异常ACK 语句没执行到消息无限重试PV 就像滚雪球一样涨。查这个问题的命令是XINFO GROUPS shortlink:stats:当前日期看 pending 数量是不是一直在涨。4.3 UV 统计直接用 Set 集合去重内存爆掉现象运营某天导出了全渠道的 UV 数据后台服务紧接着报了 OutOfMemory 错误服务重启后又重复出现。原因UV 统计要按独立访客去重我用简单方式实现时习惯往 Redis Set 里塞用户标识一天 50 万 UV 就是 50 万个字符串多链接多日期叠加内存根本扛不住。解决改用 Redis HyperLogLog 数据结构做 UV 统计PFADD 添加用户标识PFCOUNT 获取去重后的数量标准误差在 0.81% 左右对营销报表足够精准内存占用只有 Set 方式的零头。这套系统里如果没做这个优化建议拿到源码后改造的重点就放在这。4.4 分库分表后短码突然重复现象短链接系统拆分数据库表之后生成的短码出现重复后面创建的链接盖掉了前面的链接。原因短码是从数据库自增 ID 做的 62 进制转换分表之后每张表各自维护自增 ID不同表的 ID 从 1 开始转换出来的短码必然重复。解决不要用数据库自增 ID 作为短码的种子而是用雪花算法生成全局唯一 ID或者用号段模式从单独的表里批量取 ID 段确保全局不重复。这是做短链接系统最需要提前想清楚的设计决策等线上出了事故再改就晚了。4.5 网关超时时间设置太短大流量下跳转大量失败现象渠道推广流量一上来网关开始频繁报超时部分用户短链接打不开但数据库负载看起来并不高。原因gateway 模块的默认超时配置太保守短链接跳转链路涉及 Redis 查询、数据库 fallback偶尔一次缓存未命中就会超时流量峰值时问题被放大。解决把网关到 project 模块的路由超时时间从默认的 3 秒调到 10 秒同时开启重试机制但只允许重试 GET 请求重试次数控制在 1 次避免重复提交统计事件。配置在 gateway 的 YAML 文件的spring.cloud.gateway.httpclient段里。5. 把源码跑起来数据库初始化、YAML 配置和环境参数说明5.1 两个 SQL 文件的作用与初始化流程项目里的 2 个 SQL 文件从内容上看基本可以确定是建表和基础数据初始化分离的一个负责建库建表一个负责写入初始管理员账号和分组数据。拿到项目后第一步就是执行建表脚本先创建短链接主表、分组表、用户表、统计聚合表和回收站记录表。常见的表结构字段列表如下拿到 SQL 后你可以对照检查字段是否齐全表名关键字段用途t_userid、username、password_hash、salt用户表salt 和 hash 分开存t_groupid、user_id、group_name分组表链接归属维度t_linkid、short_uri、long_url、group_id、user_id、create_time、expire_time短链接主表short_uri 必须有唯一索引t_link_statsid、date、short_uri、pv、uv、uuid_count按日聚合的统计表t_recycle_binid、short_uri、user_id、delete_time回收站表软删除记录初始化命令很简单MySQL 客户端执行mysql -u root -p -h 127.0.0.1 short_link_db schema.sql mysql -u root -p -h 127.0.0.1 short_link_db init_data.sql执行完检查一下 t_link 表的 short_uri 字段是不是有唯一索引这是防止短码重复的最后一道防线没有的话一定要补上。提示init_data.sql 里默认账号的密码大概率是加密存储的不要以为可以直接用明文登录。先看 UserServiceImpl 里用的什么加密算法常见的是 BCrypt 或者 SHA-256 加盐。如果不想改代码可以先手动把一条已知加密值的用户数据更新进去绕开注册流程直接调试后台功能。5.2 YAML 配置的改动点集中在数据源和 Redis Stream11 个 YAML 文件分布在各个模块改动最频繁的集中在 application.yaml 和 RedisStreamConfiguration 相关的配置段。下面是数据源配置文件里最容易出错的部分注意每个服务的端口不能冲突spring: datasource: url: jdbc:mysql://127.0.0.1:3306/short_link_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password_here redis: host: 127.0.0.1 port: 6379 password: timeout: 3000ms # 短链接业务自定义配置 short-link: stats: stream-key-prefix: shortlink:stats consumer-group: stats-consumer-group consumer-name: consumer-1 batch-size: 100stream-key-prefix 表示 Stream key 的前缀实际使用时 key 会追加日期比如shortlink:stats:20250607consumer-group 必须保证所有集群节点用同一个组名这样消息才能在多个消费者之间做负载均衡batch-size 表示每次拉取多少条消息批量处理数值越大吞吐越高但内存占用也越大我一般设置在 50 到 100 之间太小的话频繁拉取浪费网络开销。Redis Stream 消费者组在项目启动时会自动创建但如果没有自动创建的逻辑你需要手动执行初始化命令redis-cli XGROUP CREATE shortlink:stats:20250607 stats-consumer-group 0这个命令的要点是最后一个参数0表示从 Stream 的第一条消息开始消费。如果填$就表示只消费新消息历史数据会被跳过启动时不是大量积压场景可以这样用。5.3 构建启动的顺序和验证命令多模块项目用 Maven 构建建议按 gateway → aggregatorion → project → admin 的顺序启动因为 gateway 启动时会检查下游服务的可用性全部就绪后再启动网关能避开无意义的报错。构建命令mvn clean package -DskipTests java -jar gateway/target/gateway.jar --server.port8080 java -jar aggregation/target/aggregation.jar --server.port8081 java -jar project/target/project.jar --server.port8082 java -jar admin/target/admin.jar --server.port8083启动验证先看日志有没有报 Redis 连接失败和数据库连不上的错误然后调用管理接口创建一条短链接再用 curl 测试跳转和统计链路curl -L -v http://127.0.0.1:8080/s/abc123-L参数会跟随 302 跳转最终到达长链接-v参数能打印出响应头你会看到 Location 字段指向原始长网址这一条命令验证了网关路由、短链查询和跳转三大核心链路。6. 数据验证与进阶用法把统计链路调到可信再上线拿到这套源码别急着一上来就部署。先做一次数据全链路验证确认统计数字不是摆设。我自己的习惯是造一批测试数据用脚本模拟 50 个不同用户标识访问同一链接 100 次然后对比 PV、UV、UUId 三个数字是否符合预期。PV 应该等于总访问次数UV 应该等于 50UUId 按定义取决于有没有带上用户身份。验证脚本用 Python 写比较快import requests import uuid # 模拟50个独立用户每个用户访问2次共100次请求 for i in range(50): # 每个用户分配固定cookie值模拟独立访客 cookies {visitor_id: str(uuid.uuid4())} for j in range(2): # 关闭自动跳转只看302状态码 resp requests.get( http://127.0.0.1:8080/s/testcode, cookiescookies, allow_redirectsFalse ) assert resp.status_code 302跑完脚本等 30 秒等 Redis Stream 消费完成然后查统计接口或直接查数据库的t_link_stats表看 PV 是不是 100UV 是不是 50。要注意的是消费端是异步的脚本跑完立刻查数据大概率还没落库这个延迟是正常的不是 bug。进阶用法里我最推荐研究的是 RecycleBinServiceImpl 这套软删机制。短链接被删除后会进回收站而不是物理删除这个设计非常实用投放渠道的短链误删了运营可以在回收站一键恢复短码不变已经印在物料上的二维码不会失效。你拿到源码后可以改的扩展点是设置回收站保留期比如 30 天自动清空避免历史数据无限膨胀。做法是写一个定时任务扫描 t_recycle_bin 表里超过保留期的记录先删 t_link_stats 的统计数据再删主记录最后从回收站表移除。这套系统的到期链接处理用的是类似的思路但如果你加了自动清理的定时任务务必先确认统计数据已经导出归档否则会丢掉历史趋势数据。排查统计链路问题时最常用的命令我列在下面# 查看Redis Stream消息总数和消费者组积压 redis-cli XLEN shortlink:stats:20250607 redis-cli XINFO GROUPS shortlink:stats:20250607 # 手动消费一条消息调试不ACK只查看 redis-cli XREADGROUP GROUP stats-consumer-group consumer-1 COUNT 1 STREAMS shortlink:stats:20250607 判断系统健康的标准是消费者组的 lag 长期接近于 0如果 lag 一直在涨说明消费者性能跟不上生产端的消息写入速度此时优先看消费者 batch-size 和线程数配置而不是急着加服务器。我从那以后每次排查短链接系统统计异常都会强制先走一遍看 lag — 查 pending — 手动消费一条消息确认数据格式的流程再去看代码和业务逻辑这种排查顺序能筛掉一半以上的假故障。希望帮到你。本文还有配套的精品资源点击获取