如何设计一个短网址服务「终于解决」

如何设计一个短网址服务「终于解决」一、简介 不知道大家有没有了解过短网址,所谓短网址就是将一个长的url转换成一个短的url,这样用户访问短url的时候也能够正常访问到正常的url。应用广泛,比如短信、分享链接等等。。。 下面我会从几

一、简介

不知道大家有没有了解过短网址,所谓短网址就是将一个长的url转换成一个短的url,这样用户访问短url的时候也能够正常访问到正常的url。应用广泛,比如短信、分享链接等等。。。

下面我会从几个方面来讲述如何设计一个高性能的短网址服务

干货满满,你准备好了吗🐒

二、正文

你为什么能通过一个短的链接访问的正常的数据呢

这里假设短网址为mtw.so/6kK03S 我们称之为A
对应的长链接为tech.meituan.com/2021/10/20/… 我们称之为B
(这里是通过一个在线网址转换工具生成的,图省事😂)

当我们访问A时,会经过mtw.so的域名解析最终会请求该域名下的某一个http接口(😎后段程序员都懂,就不做多解释了
这个接口会拿到url后的参数 6kK03S
通过这个参数后端会定位到一个长链接,也就是原始链接,最后通过重定向到原始链接(301/302按需使用),至此就结束了

如何将一个长链接变成一个短链接呢🤨

错误的做法 ❌

你首先肯定会想到加密啊,把长的url通过一种算法加密,压缩长度,用户访问加密后的url,通过后段再解密就好了呀。ohhhh!! 我是个天才,这也太简单了吧!!(如果真的有这样子的算法,那么你就创造奇迹了。。。原因自行百度,不做多解释了)

那么应该怎么做呢? 😱
如果我们能吧长链接和短链接的对应关系存储起来,用户访问短链接时,去数据库中把长链接读取出来,不就可以完美解决了吗?

如何通过短链接定位到长链接

那么问题来了,这个存储要怎么设计?如何通过短链接定位到长链接
使用id,如果我们有一个全局id,这个id永远不会重复,是不是就解决问题了?
当一个长链接来申请变成短链接时候,我们为他生成一个id,然后存到数据库中,假设id=996,那么996这个id就对应我们这个长链接,最后我们直接把这个996提供给用户使用就好了,假设生成的短链接baidu.com/996 ,那么接下来的流程就简单了

  1. 用户访问baidu.com/996
  2. 服务器接受到请求,获得996这个参数
  3. 后端服务那996这个id,去数据库中查询长链接
  4. 重定向到这个长链接
  5. 用户看到长链接的页面效果 完美解决问题!!!

你认为这样解决了吗?既然这么简单,那这篇文的意义何在❓

image.png
问题一:全局id怎么生成?
问题二:如果id还是太长了怎么办?
问题三:如果我这个短网址服务一天要生成十万、百万、千万级别的短网址,要如何存储?
问题四:请求量大了怎办?服务扛得住吗?
(面试官的致命组合拳)
莫慌莫慌,下面我们一一道来😴(以下问题解决方案基于分布式环境探讨)

全局id怎么生成

常见的全局id生成有几种,对于高并发情况,我们一般会涉及一个id生成器,后文会详解

redis incr ⭐️⭐️⭐️⭐️

完全依赖于redis,
优点:简单无脑
缺点:数据会丢失,即便是redis有RDB和AOF,也会丢失不能保证

数据库自增id ⭐️⭐️⭐️⭐️

基于数据库的自增id
优点:简单无脑
缺点:要做好并发控制,比如sql层面可以通过select for update,代码层面可以使ReentrantLock来保证线程安全,还要注意id是有上限的(具体上限跟随数据类型),如果达到上限,讲不会再生成id

uuid (不推荐)⭐️

优点:简单无脑
缺点:生成的id不连续切中英文混合,落地到mysql会造成页分裂,影响mysql存储性能

雪花算法(SnowFlake) ⭐️⭐️⭐️⭐️⭐️

优点:通过单例模式保证id唯一性
缺点:由于是基于时间的,所以时间回退会造成id重复 (谁没事会去改服务器的时间???)

如果自增id很长了怎么办

当我们的自增id长度很长,生成了8573834749584939,也不满足我们短链接的需求怎么办?

进制转化
我们可以使用62进制(数字 + 小写字母 + 大写字母)来处理我们的id

10进制 62进制
996 g4
1024102410241024 4GNTCX7B6
996996996996996996996 j9TiP3ZLxcIA

是不是变短了很多(你那么短,却那么能干🤔)

数据量大怎么存储

数据量大?多大算大呢?个、十、百、千、万、十万、爸、爷、祖宗!!!!

根据《阿里巴巴Java开发手册》,单表超过500W行,或者单表数据大小超过2GB,才推荐做分表操作
如何选择分表策略?
这里给出两张常见的分表策略方案:

  1. 根据日期按月/季度/年分表
  2. 固定表分表

按时间份表

适用场景:日增数据量很大,比如日增十万、百万级别的数据
具体实现:根据时间,把当前时间生成的数据存储到具体的时间表中,比如今天是2021-10-28,选择按月份表,我们就把10月份产生的数据都存到t_id_test_202110,这张表中
优点:可以利用mysql的事件自动创建表,且单表容量可控
缺点:id生成需要带上时间,不方便做统计操作

固定表分表

固定表分表指的就是自己评估未来的数据增长,把数据表分为若干个表,比如分为8、16、32、64、128张表(这里建议是2的x次方

适用场景:能预估数据量的大小,并且不希望创建很多表
具体实现:创建若干数据表8、16、32、64、128张表,以序号命名,比如:t_id_test_1、t_id_test_43等,当生成一个id(996996),我们将这个id取模分表总数
这里也是为什么建议分表数一定要是2的x次方的原因,因为当n=2的x次方,任意数%n 结果和 任意数&n-1 的结果是一致的,但是位运算的效率要高得多得多得多
比如分32张表,996996%32、996996&31=4,那么我们就把数据写到t_id_test_4 这张表中,读取同理
优点:id不用额外处理,分表数量可控,统计方便
缺点:表扩容比较复杂,需要暂停服务,重新做一次hash计算

如何扛住高并发,1W+的QPS

对于高并发情况,我们要如何应对?我们先总结一下短链接的过程

  1. 生成短链接
    a. 获取一个全局id
    b. 把长链接和id存到数据库
    c. 将id做62进制转换
    d. 拼接成短链接返回
  2. 访问短链接
    a. 后段接收请求
    b. 拿到请求参数
    c. 将参数取10进制
    d. 从数据库中讲这个id对应的长链接取出来
    e. 重定向
    我们来思考一下,对于高并发场景,我们要考虑以上哪一过程呢?🤔🤔我们一步一步来
    先看 1 生成端链接的过程
    1.b、1.c 和1.d 是不需要考虑的,因为你并发量再大,他们也不会影响,就是一个计算+返回+写db的过程
    1.a有没有并发问题?当然有,什么问题?I/O瓶颈呀,如果选择redis、mysql自增作为全局id,那么I/O是重大损耗,那么如何解决呢?可以利用id生成器

    再来看 2 访问短链接的过程
    2.a、2.b、2.c、2.e 是不会有高并发的问题的,我们重点看2.d
    我们知道,高并发环境下影响性能最严重的就是I/O,而且大批量的请求直接打到mysql,如果mysql的配置不高,也容易搞快mysql,那么如何解决呢?可以利用缓存

全局id生成器

所谓id生成器,就是我负责生(I/O),你负责用(从内存拿),职责单一,减小I/O!
具体实现:一条线程不停的生成id,保证唯一性和顺序性,然后将生成的id放入队列中(先进先出、保证顺序),工作线程要获取id时直接从队列头拿出来即可,这样是不就除去了工作线程I/O的过程,直接读内存,速度大大的提高。当然我们也可以设置一个规则,比如当队列长度小于某一个界限的时候再生成id。

缓存

我们要从两个地方坐缓存,第一数据过滤,第二数据缓存
数据过滤:
为什么要有数据过滤?因为我们要先行过滤无效的数据,如果这个数据是有效的我们才会往后面走流程,常用的数据过滤器可以使用布隆过滤器(Bloom Filter),生成短链接时,讲当时的id放入BloomFilter,后面有请求来先通过一层Filter,如果为true,往下执行,如果是false,直接拒绝 (BloomFilter会有误差,如果存在则不一定存在,如果不存在则一定不存在)

数据缓存:
当每生成一个短链接,我们可以先将其缓存至redis,读取时先读redis,redis没有再去mysql读,然后再放入redis,防止大批量的请求直接打到mysql,将其击垮。这里要考虑一个问题,因为短链接的量非常大,我们要选择性长期缓存,对于一些热点数据,我们要缓存时间长一些,直到他成为冷门数据,对于冷门数据,我们甚至可以不用缓存。
如果还是并发大相应慢,我们可以利用本地缓存,也就是jvm层面的缓存,可以讲链接信息暂时放入本地map,使用lru算法保证不会OOM,先读本地缓存、再读redis、最后mysql兜底。

三、总结

本文主要探讨了一个高性能的短链接服务的设计思路,如有错误的地方,希望各位大佬帮助小弟改正。
新人小白第一文,可以关注我,后续会有很多干货哦~~~

一个努力让自己变强的深漂Java程序员

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13356.html

(0)

相关推荐

  • 使用brew安装Python3指南

    使用brew安装Python3指南Python是一门非常受欢迎的编程语言,可以用于开发Web应用程序、科学计算、数据分析以及人工智能等多个领域。如果你还没有安装Python3,本文将介绍如何使用Homebrew在macOS上安装Python3。

    2024-05-21
    69
  • Python文件路径的正确书写方式

    Python文件路径的正确书写方式在实际开发过程中,文件路径的正确书写方式是极其重要的。无论是在开发和部署代码中,还是在处理数据和配置文件中,正确的文件路径都将直接影响程序的运行效果。Python作为广泛使用的编程语言,对于文件路径的处理也有着自己的一套规范和标准,本文将为读者详细介绍Python文件路径的正确书写方式。

    2024-09-22
    12
  • Python集合运算简介

    Python集合运算简介在Python中,集合是一种无序、可变的数据类型,可以进行各种集合运算。常用的集合运算包括交集、并集、差集和对称差集。

    2024-06-16
    53
  • 用Python来创建交互式画布

    用Python来创建交互式画布Python是一种简单易学的编程语言,拥有丰富的绘图和可视化库,可以帮助开发者生成高质量的可视化图表。交互式可视化是数据分析和数据科学的重要组成部分。在这篇文章中,我们将介绍Python如何使用交互式图形库来创建交互式画布。通过本文的学习,你将会了解到Python中的交互式绘图,可以将其用于数据分析和领域特定的可视化应用中。

    2023-12-25
    113
  • db2数据库创建索引,删除索引,查看表索引,SQL语句执行计划以及优化建议[亲测有效]

    db2数据库创建索引,删除索引,查看表索引,SQL语句执行计划以及优化建议[亲测有效]db2数据库创建索引,删除索引,查看表索引,SQL语句执行计划以及优化建议 1、建立表索引 create index 索引名 on 表名(列名,列名); 2、删除表索引 drop index 索引名…

    2023-02-26
    140
  • linux安装oracle弹窗不显示_oracle安装乱码弹框

    linux安装oracle弹窗不显示_oracle安装乱码弹框Linux安装Oracle,弹出的oracle安装界面为乱码(方块)处理方法原因分析:oracle安装默认没有中文语言包,只有用英文。解决方法:英文临时解决:$exportLANG=en_US英文永…

    2023-03-25
    147
  • Mysql死锁如何排查:insert on duplicate死锁一次排查分析过程[通俗易懂]

    Mysql死锁如何排查:insert on duplicate死锁一次排查分析过程[通俗易懂]遇到Mysql死锁问题,我们应该怎么排查分析呢?之前线上出现一个insert on duplicate死锁问题,本文将基于这个死锁问题,分享排查分析过程,希望对大家有帮助。 考虑到有些读者可能对上面insert intention锁等不太熟悉,所以这里这里补一小节锁相关概念。 …

    2023-04-02
    145
  • MySQL——事务(Transaction)详解

    MySQL——事务(Transaction)详解该博客详解MySQL中的事务一、事务定义Transaction事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同…

    2023-04-02
    180

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注