面试场景题系列--(2)短 URL 生成器设计:百亿短 URL 怎样做到无冲突?--xunznux

文章目录

  • 面试场景题:短 URL 生成器设计:百亿短 URL 怎样做到无冲突?
    • 1. 需求分析
    • 2. 短链接生成算法
      • 2.1 自增法
      • 2.2 散列函数法
      • 2.3 预生成法
    • 3. 部署模型
      • 3.1 其他部署方案
    • 4. 设计
      • 4.1 重定向响应码
      • 4.2 短 URL 预生成文件及预加载
      • 4.3 用户自定义短 URL
      • 4.4 URL Base64 编码
      • 4.5 URL 访问统计

面试场景题:短 URL 生成器设计:百亿短 URL 怎样做到无冲突?

1. 需求分析

短 URL 生成器,也称作短链接生成器,就是将一个比较长的 URL 生成一个比较短的 URL,当浏览器通过短 URL 生成器访问这个短 URL 的时候,重定向访问到原始的长 URL 目标服务器。
对于需要展示短 URL 的应用程序,由该应用调用短 URL 生成器生成短 URL,并将该短URL 展示给用户,用户在浏览器中点击该短 URL 的时候,请求发送到短 URL 生成器(短URL 生成器以 HTTP 服务器的方式对外提供服务,短 URL 域名指向短 URL 生成器),短 URL 生成器返回 HTTP 重定向响应,将用户请求重定向到最初的原始长 URL,浏览器访问长 URL 服务器,完成请求服务。

  1. 用户 client 程序可以使用短 URL 生成器为每个长 URL 生成唯一的短 URL,并存储起来。
  2. 用户可以访问这个短 URL,平台将请求重定向到原始长 URL。
  3. 生成的短 URL 可以是自动生成的,也可以是用户自定义的。用户可以指定一个长 URL 对应的短 URL 内容,只要这个短 URL 还没有被使用。
  4. 管理员可以通过 web 后台检索、查看使用情况。
  5. 短 URL 有有效期(比如一个月),后台定时任务会清理超过有效期的 URL,以节省存储资源,同时回收短 URL 地址链接资源。
  6. 存储空间:每条短 URL 数据库记录大约 1KB。
  7. 网络带宽:短 URL 的重定向响应包含长 URL 地址内容,长 URL 地址大约 500B,HTTP 响应头其他内容大约 500B,所以每个响应 1KB。
  8. 短 URL 长度估算:短 URL 采用 Base64 编码,如果短 URL 长度是 7 个字符的话,大约可以编码 4 万亿个短 URL。如果短 URL 长度是 6 个字符的话,大约可以编码 680 亿个短 URL。
  9. 短 URL 应该是不可猜测的,即不能猜测某个短 URL 是否存在,也不能猜测短 URL 可能对应的长 URL 地址内容。

2. 短链接生成算法

短 URL 生成器的设计核心就是短 URL 的生成,即长 URL 通过某种函数,计算得到一个 6 个字符的短 URL。短 URL 有几种不同的生成算法。

2.1 自增法

一种免冲突的算法是用自增长自然数来实现,即维持一个自增长的二进制自然数,然后将该自然数进行 Base64 编码即可得到一系列的短 URL。这样生成的的短 URL 必然唯一,而且还可以生成小于6个字符的短URL,比如自然数0 的Base64编码是字符“A”, 就可以用 http://xun.com/A 作为短 URL。
但是这种算法将导致短 URL 是可猜测的,如果某个应用在某个时间段内生成了一批短URL,那么这批短 URL 就会集中在一个自然数区间内。只要知道了其中一个短 URL,就可以通过自增(以及自减)的方式请求访问其他 URL。

2.2 散列函数法

将长 URL 利用 MD5 或者 SHA256 等单项散列算法,进行 Hash 计算,得到 128bit 或者 256bit 的 Hash 值。然后对该 Hash 值进行 Base64 编码,得到 22 个或者 43 个 Base64 字符,再截取前面的 6 个字符,就得到短 URL 了。
但是这样得到的短 URL,可能会发生 Hash 冲突,即不同的长 URL,计算得到的短 URL 是相同的(MD5 或者 SHA256 计算得到的 Hash 值几乎不会冲突,但是 Base64 编码后再截断的 6 个字符有可能会冲突)。所以在生成的时候,需要先校验该短 URL 是否已经映射为其他的长 URL(可以使用布隆过滤器判断是否已使用),如果是,那么需要重新计算(换散列算法,或者换 Base64 编码截断位置)。重新计算得到的短 URL 依然可能冲突,需要再重新计算。但是这样的冲突处理需要多次到存储中查找 URL,无法保证性能要求。

2.3 预生成法

采用预生成短 URL 的方案(类似线程池的概念,提前创建好,需要时直接取用)。即预先生成一批没有冲突的短 URL 字符串,当外部请求输入长 URL 需要生成短 URL 的时候,直接从预先生成好的短 URL 字符串池中获取一个即可。
预生成短 URL 的算法可以采用随机数来实现,6 个字符,每个字符都用随机数产生(用0~63 的随机数产生一个 Base64 编码字符)。为了避免随机数产生的短 URL 冲突,需要在预生成的时候检查该 URL 是否已经存在(用布隆过滤器检查)。因为预生成短 URL 是离线的,所以这时不会有性能方面的问题。事实上,Fuxi 在上线之前就已经生成全部需要的 144 亿条短 URL 并存储在文件系统中(假设预估需要短 URL120 亿,预生成的时候进行了 20%的冗余,即 144 亿。)也可以根据业务增长量,分批次地离线创建短URL,不需要一次性创建完所有预估的数量。

3. 部署模型

有挑战的就是高并发的读请求如何处理、预生成的短 URL 如何存储以及访问。高并发访问主要通过负载均衡与分布式缓存解决,而海量数据存储则可以通过 HDFS 以及 HBase 来完成。
系统调用可以分成两种情况,一种是用户请求生成短 URL 的过程;另一种是用户访问短 URL,通过平台跳转到长 URL 的过程。

  • 对于用户请求生成短 URL 的过程,在短 URL 系统上线前,已经通过随机数算法预生成 144 亿条短 URL 并将其存储在 HDFS 文件系统中。系统上线运行后,应用程序请求生成短 URL 的时候(即输入长 URL,请求返回短 URL),请求通过负载均衡服务器被发送到短 URL 服务器集群,短 URL 服务器再通过负载均衡服务器调用短 URL 预加载服务器集群。短 URL 预加载服务器此前已经从短 URL 预生成文件服务器(HDFS)中加载了一批短 URL 存放在自己的内存中,这时,只需要从内存中返回一个短 URL 即可,同时将短 URL 与长 URL 的映射关系存储在HBase 数据库中。

    • 对于用户自定义的短URL,可以构建一个布隆过滤器,定期将数据库中存在的短URL存在其中,在访问数据库前可以先查一次布隆过滤器,是否有该短 URL 是被使用的这种情况(因为布隆过滤器本质上也是使用Hash,存在Hash冲突的问题,所以不能百分百确定)。如果判断存在则直接返回不可使用;如果判断不存在则可以尝试往数据库中写入,数据库中存在唯一约束(可以当做兜底策略),如果写入成功则返回成功给用户;如果写入失败则是可能布隆过滤器没有及时更新导致判断出错,返回用户该URL已被占用。当然,采用该方案需要考虑到布隆过滤器的重建代价是否可以接受,以及判断出错率是否可以接受。
  • 对于用户通过客户端请求访问短 URL 的过程(即输入短 URL,请求返回长 URL),请求通过负载均衡服务器发送到短 URL 服务器集群,短 URL 服务器首先到缓存服务器中查找是否有该短 URL,如果有,立即返回对应的长URL,短 URL 生成服务器构造重定向响应返回给客户端应用。

    • 如果缓存没有用户请求访问的短 URL,短 URL 服务器将访问 HBase 短 URL 数据库服务器集群。如果数据库中存在该短 URL,短 URL 服务器会将该短 URL 写入缓存服务器集群,并构造重定向响应返回给客户端应用。如果 HBase 中没有该短 URL,短 URL 服务器将构造 404 响应返回给客户端应用。
  • 过期短 URL 清理服务器会每个月启动一次,将已经超过有效期的 URL 数据删除,并将这些短 URL 追加写入到短 URL 预生成文件中(重生到池子中)。

  • 为了保证系统高可用,应用服务器、文件服务器、数据库服务器最好可以采用集群部署方案,单个服务器故障不会影响短 URL 的可用性。

  • 对于高性能要求,80%以上的访问请求将被设计为通过缓存返回。Redis 的缓存响应时间 1ms 左右,服务器端请求响应时间小于 3ms,满足 80%请求小于 5ms 的性能目标。对于缓存没有命中的数据,通过 HBase 获取,期望HBase 平均响应时间为 10ms,满足设计目标中的性能指标。

  • 对于 Redis 缓存内存空间估算,可以根据以往数据评估“热点时期”,假设超过 80%请求集中在最近一周生成的短 URL 上,那么平台主要缓存最近一周投入使用的短 URL 即可。

3.1 其他部署方案

当然,如果想要使用 Mysql + Redis 作为存储方案,那么可以考虑使用MySQL 双中心集群部署。把主库进行分片,例如平分到每个分片大概百万的量级。MySQL 集群采用 1 主 3 从的架构,主库放在机房 A,从库放在机房 B,两个机房之间通过专线同步数据,保证延迟在几毫秒内。系统通过 DBRoute 读写数据,写数据都路由到 master 节点所在的机房 A,读数据都路由到本地机房,就近访问,减少网络延迟。这样,采用双中心的 MySQL 集群架构,极大提高了可用性,即使机房 A 整体都崩了,还可以将机房 B 的 Slave 升级为 Master,继续提供服务。
并且还可以采用 Redis 双中心多集群架构,机房 A 和机房 B 各部署一套 Redis 集群。更新缓存数据时,双写,只有两个机房的 Redis 集群都写成功了,才返回成功。查询缓存数据时,机房内就近查询,降低延时。这样,即使机房 A 整体故障,机房 B 还能提供完整的服务。
当然,这只是我的一个粗略的想法,具体实施起来可能还有许多困难点需要克服。

4. 设计

4.1 重定向响应码

满足短 URL 重定向要求的 HTTP 重定向响应码有 301 和 302 两种,其中 301 表示永久重定向,即浏览器一旦访问过该短 URL,就将重定向的原始长 URL 缓存在本地,此后不再请求短 URL 生成器,直接根据缓存在浏览器(HTTP 客户端)的长 URL 路径进行访问。
302 表示临时重定向,每次访问短 URL 都需要访问短 URL 生成器。一般说来,使用 301 状态码可以降低服务器的负载压力,但无法统计短 URL 的使用情况,如果架构设计完全可以承受这些负载压力,最好还是使用 302 状态码构造重定向响应。

4.2 短 URL 预生成文件及预加载

短 URL 可以在系统上线前全部预生成,也可以上线前生成一部分,后期根据使用情况逐批追加,将它们存储在 HDFS 文件中。假设全部预先生成共 144 亿个短 URL,每个短 URL 6 个字符,文件大小 144 亿* 6B=86.4GB。
文件格式就是直接将 144 亿个短 URL 的 ASC 码无分割地存储在文件中。
所以如果短 URL 预加载服务器第一次启动的时候加载 1 万个短 URL,那么就从文件头读取 60K 数据,并标记当前文件偏移量 60K。下次再加载 1 万个短 URL 的时候,再从 文件 60K 偏移位置继续读取 60K 数据即可。
因此,除了需要一个在 HDFS 记录预生成短 URL 的文件外,还需要一个记录偏移量的文件,记录偏移量的文件也存储在 HDFS 中。同时,由于预加载短 URL 服务器集群部署多台服务器,会出现多台服务器同时加载相同短 URL 的情况,所以还需要利用偏移量文件对多个服务器进行互斥操作,即利用文件系统写操作锁的互斥性实现多服务器访问互斥。
应用程序的文件访问流程应该是:写打开偏移量文件 -> 读偏移量 -> 读打开短 URL 文件 -> 从偏移量开始读取 60K 数据 -> 关闭短 URL 文件 -> 修改偏移量文件 -> 关闭偏移量文件。
由于写打开偏移量文件是一个互斥操作,所以第一个预加载短 URL 服务器写打开偏移量文件以后,其他预加载短 URL 服务器无法再写打开该文件,也就无法完成读 60K 短 URL 数据及修改偏移量的操作,这样就能保证这两个操作是并发安全的。
加载到预加载短 URL 服务器的 1 万个短 URL 会以链表的方式存储,每使用一个短 URL,链表头指针就向后移动一位,并设置前一个链表元素的 next 对象为 null。这样用过的短 URL 对象可以被垃圾回收。
当剩余链表长度不足 2000 的时候,触发一个异步线程,从文件中加载 1 万个新的短 URL,并链接到链表的尾部。

4.3 用户自定义短 URL

平台允许用户自己定义短 URL,即在生成短 URL 的时候,由用户指定短 URL 的内容。但为了避免预生成的短 URL 和用户指定的短 URL 冲突,可以限制用户自定义短 URL 的字符个数,不允许用户使用 6 个字符的自定义短 URL,且 URL 长度不得超过 20 个字符。
但是用户自定义短 URL 依然可能和其他用户自定义短 URL 冲突,所以生成自定义短 URL 的时候需要到数据库中检查冲突,是否指定的 URL 已经被使用,如果发生冲突,要求用户重新指定。或者和前面提到的建议一样,在允许一定的失误率的前提下,使用布隆过滤器来判断是否可以使用该自定义URL。通过布隆过滤器而不是使用缓存保存整个URL,可以减少很多缓存空间。

4.4 URL Base64 编码

Base64编码对照表:
在这里插入图片描述

其中“+”和“/”在 URL 中会被编码为“%2B”以及“%2F”,而“%”在写入数据库的时候又和 SQL 编码规则冲突,需要进行再编码,因此直接使用标准 Base64 编码进行短 URL 编码并不合适。URL 保留字符编码表如下。
在这里插入图片描述

所以,需要针对 URL 场景对 Base64 编码进行改造,使用 URL 保留字符表以外的字符对 Base64 编码表中的 62,63 进行编码:将“+”改为“-”,将“/”改为“_”。

4.5 URL 访问统计

在后端处理用户点击的短 URL 请求时,可以获取到请求头中的很多数据,包括请求时间,地理位置,发出请求的设备,请求频率,请求的目标地址等。而这些数据可以根据需求进行不同维度的统计,包括时间维度,地理位置维度,目标地址维度等等。通过这些统计数据可以进行数据分析,获取到所需要的信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/383618.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EtherNet/IP转Profinet协议网关(经典配置案例)

怎么样才能把EtherNet/IP和Profinet网络连接起来呢?这几天有几个朋友问到了这个问题,作者在这里统一为大家详细说明一下。其实有一个设备可以很轻松地解决这个问题,名为JM-PN-EIP,下面是详细介绍。 一,设备主要功能 1、捷米特J…

AnyMP4 Data Recovery for Mac v1.5.8免激活版:高效数据恢复新选择

AnyMP4 Data Recovery for Mac是一款专为Mac用户设计的高效数据恢复软件,凭借其强大的功能和简洁的操作界面,为用户提供了快速、安全的数据恢复体验。 该软件支持恢复多种文件类型,包括照片、视频、音频、文档等,无论是常见的图片…

前端学习7——自学习梳理

​​​​​​jQuery 教程 | 菜鸟教程jQuery 教程 jQuery 是一个 JavaScript 库。 jQuery 极大地简化了 JavaScript 编程。 jQuery 很容易学习。 本章节的每一篇都包含了在线实例 通过本站的在线编辑器,你可以在线运行修改后的代码,并查看运行结果。 实例…

【Python正则表达式】:文本解析与模式匹配

文章目录 1.正则表达式2. re模块3.修饰符3.元字符3-1 字符匹配元字符3-2 重复次数限定元字符3-3 字符集合匹配元字符3-4 分组元字符3-5 边界匹配元字符3-6 字符类别匹配元字符 4.技巧4-1 贪婪与非贪婪 5.案例 1.正则表达式 正则表达式面向什么样的问题? 1、判断一个…

uniapp引入自定义图标

目录 一、选择图标,加入购物车 二、下载到本地 三、导入项目 四、修改字体引用路径 五、开始使用 这里以扩展iconfont图标为例 官网:iconfont-阿里巴巴矢量图标库 一、选择图标,加入购物车 二、下载到本地 直接点击下载素材&#xff0…

2019数字经济公测大赛-VMware逃逸

文章目录 环境搭建漏洞点exp 环境搭建 ubuntu :18.04.01vmware: VMware-Workstation-Full-15.5.0-14665864.x86_64.bundle 这里环境搭不成功。。patch过后就报错,不知道咋搞 发现可能是IDA加载后的patch似乎不行对原来的patch可能有影响,重新下了patch&…

【Kettle实现神通(数据库)MPP增量、全量数据ETL,同步任务Linux运行(通用)】

1、背景介绍 具体Kettle操作步骤不做过多介绍,主要技术方案说明,Kettle8.2版本放在底部链接提取,本次采用Kettle实现源端:神通数据通用库、目标端:神通MPP增量数据同步,并在服务器端运行Job。 2、windows…

鸿蒙OpenHarmony Native API【支持的标准库+Node_API】

Native API中支持的标准库 简介 表1 OpenHarmony支持的标准库 名称简介标准C库[libc、libm、libdl]组合实现C11标准C库。标准C库[libc]是C标准库的一种实现。OpenSL ES[OpenSL ES]是一个嵌入式跨平台的音频处理库。zlib[Zlib]是基于C/C语言实现的一个通用的数据压缩库。EGL[…

VMare centos 7 设置固定ip

第一步获取网关 查看虚拟机的网关-》编辑-》虚拟网络编辑器 NAT模式-》NAT设置 获取网关IP 192.168.70.2 第二步获取主机dns1 在本地主机获取dns1,本地主机调出cmd输入ipconfig dns1为192.168.31.1 用管理员权限的账号进入需要设置固定ip的虚拟机,在t…

零基础学习Python(四)

1. __getitem__、__setitem__、__iter__、__next__魔法方法 __index__方法是对象被作为索引访问时调用的魔法方法,那么当对象要进行索引访问时,调用什么魔法方法呢?答案是__getitem__魔法方法。 class C:def __getitem__(self, index):prin…

vscode回退不显示了,不方便操作

一、后退前进按钮 顶部显示&#xff0c;方便调试 <—— ——> 文件-> 首选项 -> 设置->commandcenter->勾选 Window: Title Bar Style->custom 将native —>custom

MongoDB教程(二十二):MongoDB固定集合

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、固定集…

单片机学习历程

学习单片机的过程可以分为几个主要阶段&#xff0c;每个阶段都涉及不同的学习内容和技能提升。下面我将以一个典型的学习历程为例进行介绍&#xff1a; 初学阶段 1.入门理论学习&#xff1a; 开始接触单片机的基础知识&#xff0c;学习其工作原理、体系结构和常见的芯片类型…

昇思25天学习打卡营第20天|CV-ResNet50图像分类

打卡 目录 打卡 图像分类 ResNet网络介绍 数据集准备与加载 可视化部分数据集 残差网络构建 Building Block 结构 代码实现 Bottleneck结构 代码实现 构建ResNet50网络 代码定义 模型训练与评估 可视化模型预测 重点&#xff1a;通过网络层数加深&#xff0c;感知…

vue3前端开发-小兔鲜项目-路由拦截器增加token的携带

vue3前端开发-小兔鲜项目-路由拦截器增加token的携带&#xff01;实际开发中&#xff0c;很多业务接口的请求&#xff0c;都要求必须是登录状态&#xff01;为此&#xff0c;这个token信息就会频繁的被加入到了请求头部信息中。request请求头内既然需要频繁的携带这个token.我们…

STM32烧录的时候报错:Error :Flash Download failed -“Cortex-M3“

点击图中标号1&#xff0c;按顺序点击进入设置 按图中标序&#xff0c;进入添加页面 添加图中所选&#xff0c;然后一直确定退出即可&#xff0c;若没有图中所示选项&#xff0c;可能软件没下载对&#xff0c;文章已附带 添加后&#xff0c;即可烧录成功。

《JavaEE篇》--多线程(2)

《JavaEE篇》--多线程(1) 线程安全 线程不安全 我们先来观察一个线程不安全的案例&#xff1a; public class Demo {private static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() -> {//让count自增5W次…

【Android】碎片—动态添加、创建Fragment生命周期、通信

简单用法 在一个活动中添加两个碎片&#xff0c;并让这两个碎片平分活动空间 先新建一个左侧碎片布局和一个右侧碎片布局 左侧碎片 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/…

场外期权如何报价?名义本金是什么?

今天带你了解场外期权如何报价&#xff1f;名义本金是什么&#xff1f;投资者首先需要挑选自己想要进行期权交易的沪深上市公司股票。选出股票后&#xff0c;需要将股票信息、预期的操作时间&#xff08;如期限&#xff09;、看涨或看跌的选择以及预计的交易金额等信息报给场外…

java-selenium 截取界面验证码图片并对图片文本进行识别

参考链接 1、需要下载Tesseract工具并配置环境变量&#xff0c;步骤如下 Tesseract-OCR 下载安装和使用_tesseract-ocr下载-CSDN博客 2、需要在IDEA中导入tess4j 包&#xff1b;在pom.xml文件中输入如下内容 <!--导入Tesseract 用于识别验证码--><dependency>&l…