短URL服务设计
引言
在营销系统里,为了增加系统的活跃用户数,经常会有各种各样的营销活动。这类活动几乎都是为了充分利用存量用户的价值,促使他们分享产品或App以达到触达到更多用户的目的。又或者是出于营销目的,群发优惠券触达短信这种场景。
分享App活动页(或其他各种页面)时URL一般会带有各种参数,比如分享者fromUserId,涉及活动campaignId,分享源头类型sourceType等,还有其他各种和具体业务场景挂钩的参数,一个URL动辄300~400个字符。显然,这么长的URL看得头大,而且除了开发者或黑客可能没有人关心URL里具体有什么参数。期望分享出去的URL是一些更短、更易于阅读的短URL。
假设使用阿里云短信发送平台。在群发短信的场景下,假设需要发送10w条短信。短信付费有按数量付费,和套餐包两种形式,一般而言,在待发送的短信数量较大时,套餐包比按量付费来得优惠。大致看一下套餐包价格表:
还是一笔不小的开支。
因此,如果要把活动链接URL几百个字符通过短信来发送,明显会超过短信发送长度限制。阿里云的短信内容有长度限制:
就算不考虑短信付费问题。在短信内容超长后需拆分发送的规则下,长链接URL被拆分为多个短信发送,URL会被截断,就不是正确的URL。
上面洋洋洒洒说了这么多,甚至有愚蠢的嫌疑,无非是想引出本文的主题。
我们需要一个端URL服务。当短信触达用户(被分享用户)点击这个短URL时,可以重定向访问到原始的长URL地址。比如https://t.cn/A12afD就是我随便写的一个短链,其中https://t.cn/
是新浪微博申请的域名。后面的6位随机字符就是精简后的短URL,区分大小写,使用a-z,A-Z,0-9共62个字符。严谨来说,长URL对应的短URL实际上指的是A12afD
;域名(https://t.cn/
)+精简后端URL字符(A12afD
)共同组成一个可点击的链接地址。
简介
短URL服务,也有叫做短链服务,短链接生成器。当客户端(服务请求者,发起方,调用者)请求端URL服务时,短URL服务返回一个或多个短URL。用户浏览器(PC、App、H5等)点击URL时,可以自动访问原始长URL对应的目标服务。
一个简单的涉及到4个模块(子系统)的时序图如下:
流程分析:对于需要展示短URL的应用程序,由该应用调用短URL生成器生成短URL,并将该短URL展示给用户,用户在浏览器中点击该短URL时,请求发送到短URL生成器(短URL生成器以HTTP服务器的方式对外提供服务,短URL域名指向短URL生成器),短URL生成器返回HTTP重定向响应,将用户请求重定向到最初的原始长URL,浏览器访问长URL服务器,完成请求服务。
又是一个显而易见,短URL和长URL之间必须存在一一匹配的映射关系,只有在满足这种关系后,点击短URL才能跳转到想要的长URL。存储这种映射关系的数据结构就是键值对HashMap。
总结一下,短链的优势:
- 减少文本长度,常用于发布微博或Twitter(有字数限制),短信群发(减少短信发送成本)等场景
- 可阅读性:相比于长链接里一大堆不知所以的参数,短链接更加简洁友好
- 安全:不暴露访问参数。当然短链接转换为长链接后,小部分用户(开发者)还是能分析出参数的
- 长链接在有些平台上无法自动识别为超链接
- URL与二维码。长链接在生成二维码时更密集一些;短链接在生成二维码时更稀疏。一般而言,越稀疏的二维码,识别扫描效率更高,尤其是在光线不好、二维码部分遮挡或图像质量较差的情况下。
Hash算法
将一个原始的长URL经过某种计算生成对应的,唯一的短URL的过程就是Hash算法。所谓唯一,指的是长URL1和长URL2经过Hash计算后不会生成相同的短URL。因此短URL服务的核心之一是一款效率高(计算速度快)和冲突概率低(不冲突最好,但不太实际)的算法。
MurmurHash就是这样一种算法,Redis,Google Guava,Apache commons-codec内部都有这种算法的实现。
public class HashUtil {private static final int c1 = 0xcc9e2d51;private static final int c2 = 0x1b873593;private static final int r1 = 15;private static final int r2 = 13;private static final int m = 5;private static final int n = 0xe6546b64;private static final int DEFAULT_SEED = 0;/*** TODO:直接搬自Google Guava的MurmurHash 32bits生成算法(还有64位,128位)*/public static int hash32(String str) {return hash32(str.getBytes());}private static int hash32(byte[] key) {int hash = HashUtil.DEFAULT_SEED;final int len = key.length;int i = 0;int k = 0;for (; i + 4 <= len; i += 4) {k = ((key[i + 3] & 0xff) << 24)| ((key[i + 2] & 0xff) << 16)| ((key[i + 1] & 0xff) << 8)| (key[i] & 0xff);k *= c1;k = Integer.rotateLeft(k, r1);k *= c2;hash ^= k;hash = Integer.rotateLeft(hash, r2);hash = hash * m + n;}int k1 = 0;switch (len - i) {case 3:k1 = (key[i + 2] & 0xff) << 16;case 2:k1 |= (key[i + 1] & 0xff) << 8;case 1:k1 |= key[i] & 0xff;k1 *= c1;k1 = Integer.rotateLeft(k1, r1);k1 *= c2;hash ^= k1;}hash ^= len;hash ^= hash >>> 16;hash *= 0x85ebca6b;hash ^= hash >>> 13;hash *= 0xc2b2ae35;hash ^= hash >>> 16;return hash;}/*** 转换为62进制字符串, 即包含a-z, A-Z, 0-9共62个字符*/private static String to62Hex(int num) {num = Math.abs(num);String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";StringBuilder sb = new StringBuilder();int remainder;while (num > 62 - 1) {remainder = Long.valueOf(num % 62).intValue();sb.append(chars.charAt(remainder));num = num / 62;}sb.append(chars.charAt(Long.valueOf(num).intValue()));return sb.reverse().toString();}public static void main(String[] args) {int i = hash32("https://google.com/");String s = to62Hex(i);// 输出:1027772095 17yqthSystem.out.println(i + " " + s);}
}
将https://google.com/
经过hash32
方法计算后的结果为1027772095,这是一串十进制的整型值,再经过to62Hex
方法转化为62进制字符串得到17yqth。
Hash冲突
Hash冲突是无法避免的,但在业务上一定不能有多个长URL映射为同一个短URL的情况存在。
为了防止数据库中出现相同短链,用刚生成的短链作为查询条件去数据库中查询看是否有相同的短链,如果有则需要比对这个短链对应的长链与正在处理的长链地址是否一样。如果一样,说明传入的长链是重复的,则此短链可直接复用,不用再存储一次。
如果不一样,则说明发生冲突,此时可以给长链加一串特殊的字符,然后再进行Hash运算,如果此次不冲突则可以将长链和这个特殊字符拼接起来和短链一并存储到数据库中下次接受到短链请求后,把特殊字符截取掉,然后再进行重定向。
判断优化
长URL和短URL的映射关系是存储于数据库中,判断是否发生Hash冲突时需要查询数据库,这样数据库或许会成为性能评价。
很自然想到引入分布式缓存集群。此外还有布隆过滤器。
服务设计
一道频繁出现的面试题:如何设计一个十亿级、高性能、高可用的短URL服务?
需求分析:
- 使用几位随机字符
- 是否需要考虑URL的过期时间,即经过多长时间后,URL不能转换为对应的长URL
- 是否需要考虑URL(及对应的营销文案)的点击率
- 是否需要考虑支持用户自定义短URL?即是否支持用户自定义(调用方或客户端)短URL。用户可以指定一个长URL对应的短URL内容,只要这个短URL还没有被使用
字符个数
使用62进制字符串,1个字符可以表示62种结果,2个字符可以表示62*62=3844种结果。以此类推,5个字符可以表示916132832种结果,约9亿,即可以代表9亿种不同的长URL,理论上并且在实际上足够使用。目前应该没有哪家公司有这么大的实际需求量,并且短URL是可以回收的(技术可行性上)。
在做架构设计时,架构师们总是习惯于冗余设计;5个字符和6个字符,几乎没有什么技术上的区别。鉴于面试官的题目是十亿级别的短URL服务,因此5个字符不够用。6个字符可以表示56800235584种结果,约568亿个不同的URL。
所以,我们看到的短URL一般都是6个62位二进制字符。
分库分表
如果使用MySQL这类关系型数据库存储,则需要考虑分库分表。如果使用非关系型数据库,如HBase,也需要做集群化部署保证数据库系统高可用。
短URL有效期
数据库存储长、短URL映射关系时,除了这两个核心字段,还可以考虑增加最后访问时间字段,短URL被访问时,则更新此字段。然后可考虑以一个月为执行频率,在凌晨短URL服务集群负载低时,发起定时调度任务,分批查询数据库节点,扫描获取最近1年(或2年)没有被访问(最后访问时间是1/2年前)的短URL,做逻辑删除,回收利用。
短链跳转
如果需要对营销活动进行数据分析,则需要做页面埋点,页面埋点肯定只能对长链接进行埋点,因为长链接里URL携带有各种参数。
浏览器请求短URL,短URL到达短链服务器,短链服务器返回长URL后,浏览器访问长URL的动作叫重定向,重定向有301和302两类。
如果要做页面埋点,做数据分析,做营销活动效果可视化等一系列任务,则推荐使用302这种方式。
301和302的区别:
- 301:代表永久重定向,也就是说第一次请求拿到长链接后,下次浏览器再去请求短链的话,不会向短网址服务器请求,而是直接从浏览器的缓存里拿,这样在server层面就无法获取到短网址的点击数,如果这个链接刚好是某个活动的链接,也就无法分析此活动的效果。
- 302:代表临时重定向,也就是说每次去请求短链都会去请求短网址服务器(除非响应头中有Cache-Control或Expired指示使用浏览器缓存),这样就便于server统计点击数,用302会给server增加一点压力,但在数据异常重要的今天,这点统计代码和性能损失是值得的。
短URL生成方式
长URL通过某种函数,计算得到一个6个字符的短URL。
Hash
将长URL利用MD5或SHA256等单项散列算法,进行Hash计算,得到128bit或256bit的Hash值。然后对该Hash值进行Base64编码,得到22个或43个Base64字符,再截取前面的6个字符,就得到短URL。
但是这样得到的短URL,可能会发生Hash冲突(MD5或SHA256计算得到的Hash值几乎不会冲突,但Base64编码后再截断的6个字符有可能会冲突)。所以在生成时,需要先校验该短URL是否已经映射为其他的长URL,如果是,那么需要重新计算(换单向散列算法,或者换Base64编码截断位置)。重新计算得到的短URL依然可能冲突,需要再重新计算。但是这样的冲突处理需要多次到存储中查找 URL,性能损耗比较严重。
自增长
一种免冲突的算法是用自增长自然数来实现,即维持一个自增长的二进制自然数,然后将该自然数进行Base64编码即可得到一系列的短URL。这样生成的的短URL必然唯一,而且还可以生成小于6个字符的短URL,如自然数0的Base64编码是字符A,则用https://1.cn/A
作为短URL。
但这种算法将导致短URL是可猜测的,如果某个应用在某个时间段内生成一批短URL,则这批短URL就会集中在一个自然数区间内。只要知道了其中一个短URL,就可以通过自增(以及自减)的方式请求访问其他URL。不允许短URL可预测。
预生成
预先生成一批没有冲突的短URL字符串,当外部请求输入长URL需要生成短URL时,直接从预先生成好的短URL字符串池中获取一个即可。
采用随机数来实现,6个字符每个字符都用随机数产生(用0~63的随机数产生一个Base64编码字符)。为了避免随机数产生的短URL冲突,需要在预生成的时候检查该URL是否已经存在(采用布隆过滤器检查)。因为预生成短URL是离线的,所以这时不会有性能方面的问题。
架构
一个可供参考的架构如下图所示:
短URL预加载服务器此前已经从短URL预生成文件服务器(HDFS)中加载一批短URL存放在自己的内存中,这时只需要从内存中返回一个短URL即可,同时将短URL与长URL的映射关系存储在HBase数据库中,时序图如下图所示:
对于用户通过客户端请求访问短URL的过程(即输入短URL,请求返回长URL),请求通过负载均衡服务器发送到短URL服务器集群,短URL服务器首先到缓存服务器中查找是否有该短URL,如果有,立即返回对应的长URL,短URL生成服务器构造重定向响应返回给客户端应用。
如果缓存没有用户请求访问的短URL,短URL服务器将访问HBase短URL数据库服务器集群。如果数据库中存在该短URL,短URL服务器会将该短URL写入缓存服务器集群,并构造重定向响应返回给客户端应用。若HBase中没有该短URL,短URL服务器将构造404响应返回给客户端应用,时序图如下图所示:
回到需求分析部分:
- 为了保证系统高可用,上面的架构图里的应用服务器、文件服务器、数据库服务器都采用集群部署方案
- 为了满足高性能要求,引入Redis缓存集群。80%以上的访问请求将被设计为通过缓存返回
标准Base64编码表
其中+
和/
在URL中会被编码为%2B
以及%2F
,而%
在写入数据库时又和SQL编码规则冲突,需要进行再编码,因此直接使用标准Base64编码进行短URL编码并不合适。URL 保留字符编码表如下
所以需要针对URL场景对Base64编码进行改造,使用URL保留字符表以外的字符对Base64编码表中的62,63进行改造:将+
改为-
,将/
改为_
。
参考
- 高性能短链设计
相关文章:

短URL服务设计
引言 在营销系统里,为了增加系统的活跃用户数,经常会有各种各样的营销活动。这类活动几乎都是为了充分利用存量用户的价值,促使他们分享产品或App以达到触达到更多用户的目的。又或者是出于营销目的,群发优惠券触达短信这种场景。…...

Kafka集成flume
1.flume作为生产者集成Kafka kafka作为flume的sink,扮演消费者角色 1.1 flume配置文件 vim $kafka/jobs/flume-kafka.conf # agent a1.sources r1 a1.sinks k1 a1.channels c1 c2# Describe/configure the source a1.sources.r1.type TAILDIR #记录最后监控文件…...

如何让视频有高级感 高级感视频制作方法 高级感视频怎么剪 会声会影视频剪辑制作教程 会声会影中文免费下载
高质量视频通常具有清晰的画面、优质的音频和令人印象深刻的视觉效果。这篇文章来了解如何让视频有高级感,高级感视频制作方法。 一、如何让视频有高级感 要让视频有高级感,要注意以下几个要点: 1、剧本和故事性:一个好的剧本和…...

详解|访问学者申请被拒原因有哪些?
访问学者项目为全球科研人员提供了一个难得的机会,让他们能够跨越国界,深入不同的学术环境,进行学术交流和合作。然而,并非所有申请者都能如愿以偿地获得这一机会。本文将对访问学者申请中常见的被拒原因进行详细解析,…...
[鹤城杯 2021]BabyRSA
题目: from Crypto.Util.number import getPrime, bytes_to_long from secret import flagp getPrime(1024) q getPrime(1024) n p * q e 65537 hint1 p >> 724 hint2 q % (2 ** 265) ct pow(bytes_to_long(flag), e, n) print(hint1) print(hint2) p…...
西安市工业倍增引导基金子基金申报条件流程和材料程序指南(2024年)
一、基本情况 产业投资基金是以产业发展为首要目标,围绕经济社会发展规划和产业发展政策,发挥“有效市场”作用,支持重点领域、重点产业、重点区域(如:全市六大支柱产业、五大新兴产业领域成熟期重点规模以上企业以及“…...

微型丝杆的耐用性和延长使用寿命的关键因素!
无论是机械设备,还是精密传动元件,高精度微型丝杆是各种机械设备中不可或缺的重要组件。它的精度和耐用性直接影响着工作效率和产品品质,在工业技术不断进步的情况下,对微型丝杆的性能要求也越来越高,如何提升微型丝杆…...

音频文件下载后,如何轻松转换格式?
在我们日常的数字生活中,下载各种音频文件是司空见惯的事情。然而,有时候我们可能需要将这些音频文件转换为不同的格式,以适应不同的设备或编辑需求。无论您是希望将下载的音频文件转换为通用的MP3格式,还是需要将其转换为高保真的…...

Intel平台,13600KF+3060Ti,虚拟机安装macOS 14(2024年6月)
距离上次装macOS虚拟机已经有一段时间了,macOS系统现在大版本升级的速度也是越来越快了,由于Office只支持最新三个版本的macOS,所以现在保底也得安装macOS 12了,我这次是用macOS 14做实验,13和12的安装方式和macOS 14一…...

Cookie、Session、Token的关系和区别
关系 Session与Cookie:Session通常依赖于Cookie来工作。当服务器为客户端创建一个Session时,它会在服务器上存储与客户端相关的信息,并将一个唯一的SessionID通过Cookie发送给客户端。客户端在后续的请求中会携带这个Cookie(包含…...

Windows 11 中安装 Docker Desktop 并安装镜像
本该主要介绍在 Windows 11 中安装 Docker Desktop 时的一些准备工作,以及该如何下载和安装,然后分别使用管理界面和 Docker 命令安装两个镜像。 一、准备工作 在 Windows 11 中安装 Docker Desktop 前,需要做一些准备。打开 【Windows 功能…...
深入剖析Java线程池之“newWorkStealingPool“
1. 概述 newWorkStealingPool 是Java 8中引入的一个新型线程池,它基于ForkJoinPool实现,并采用了“工作窃取”(Work-Stealing)算法。这种线程池特别适用于可并行化且计算密集型的任务,能够充分利用多核CPU资源,提高任务执行效率。 2. 工作窃取算法(Work-Stealing Algor…...

《跟我一起学“网络安全”》——安全设备
安全设备 一、安全设备–IDS IDS入侵检测 (1)什么是入侵检测: 入侵检测系统(intrusion detection system,简称“IDS”)是一种对网络传输进行即时监视,在发现可疑传输时发出警报或者采取主动反应措施的网络安全设备。…...
猜测Tomcat如何实现WebSocket协议
一、WebSocket协议的实现 (一)WebSocket是官方的协议接口标准。 (二)如果一门编程语言可以网络连接和并发,就能创建一种WebSocket实现。 (三)同一种编程语言,有不同的协议实现版本和框架。 二、Tomcat实现 在Tomcat容器中实现了对应的WebSocket版本&am…...

uniApp @input事件更改输入框值,值改变了但是页面没更新新的值
<uni-easyinputtype"text"trim"all":inputBorder"false"v-model"customFormData.completePercent"input"(val) > completeOnInput(val)"placeholder"请输入" /> function completeOnInput(val) {let num…...

两行css 实现瀑布流
html <ul ><li><a href"" ><img src"05094532gc6w.jpg" alt"111" /><p>传奇</p></a></li><li><a href"" ><img src"05094532gc6w.jpg" alt"111"…...
Centos7.9部署单节点K8S环境
Centos7.9部署单节点K8S环境 通过Centos extras镜像源安装K8S环境,优点是方便快捷,缺点是版本较低,安装后的版本为1.5.2。 1. 准备工作 关闭selinux [rootlocalhost ~]# cat /etc/selinux/config# This file controls the state of SELin…...
【CV】stable diffusion初步理解
来自gpt-4o Stable diffusion 和DALLE的关系 Stable Diffusion 和 DALL-E 都是生成图像的人工智能模型,但它们有不同的开发背景和技术实现。 Stable Diffusion: 开发者: 由Stability AI开发,并与CompVis和LAION等组织合作。技术: 基于扩散模型…...

足底筋膜炎最好的恢复办法
足底筋膜炎是一种由足底筋膜受到炎症刺激而引起的疼痛和不适的疾病。其典型症状主要包括: 1、足底疼痛:这是足底筋膜炎最常见的症状。疼痛通常位于足跟部位,患者可能感到刺痛或灼热感。尤其在早晨起床或长时间站立后,这种疼痛感会…...

Fiddler抓包工具介绍
下载 下载:Web Debugging Proxy and Troubleshooting Tools|Fiddler 进去要填一个表 汉化版 百度网盘 请输入提取码 提取码:xq9t 下载过附件之后分别把两个文件 点开fiddler就ok了 配置https fiddler要想抓到https包(解密的),点击tools->options勾选三个对…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...