系统设计题有哪些?短链系统设计

系统设计第一期。。。。。。系统设计第一期。。。。。。系统设计第一期。。。。。。

大家好,我是南哥。

一个Java学习与进阶的领路人,相信对你通关面试进入心心念念的公司有所帮助。

文章目录

  1. 短链系统设计
    1. 基础功能
    2. 稳定性设计
    3. 安全性设计

1. 短链系统设计

1.1 基础功能

如何设计短链服务最基础、最核心的功能?短链系统把一条长链转换为一条短链,它们之间是一个一对一的关系。这样我们使用Map结构来存储这个对应关系,这里我们用到高性能内存服务器Redis。

这个对应关系同样需要进行物理存储,暂定使用MySQL,先做出一个表设计。

# 短链数据库表
CREATE TABLE `short_link` (
    `distributed_id` BIGINT UNSIGNED NOT NULL COMMENT '分布式ID',
    `long_url` VARCHAR(2048) NOT NULL,
    `short_code` VARCHAR(10) NOT NULL UNIQUE,
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `expires_at` TIMESTAMP NULL,
    `user_id` BIGINT UNSIGNED,
    `effective_days` INT UNSIGNED COMMENT '有效天数',
    PRIMARY KEY (`distributed_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

上面数据库表的分布式ID有什么用?别急是这样的,每条长链的长短不同、格式不同、组成不同,我们很难保证所有短链生成出来的短链长度、格式一致!

可以这样取巧,每条唯一的长链在进行数据库物理存储时,都会被分配一个唯一的分布式ID。咱们不是根据长链来生成短链,而是根据这个分布式ID来生成短链。

分布式ID由数字组成,把长数字转换为短链,这个技术我们使用MurmurHash非加密型哈希函数算法,当然可选的技术有Base62编码。

南哥给出它们之间的区别。

特性 MurmurHash Base62
用途 数据映射到固定长度的哈希值 数据编码为短字符串
是否可逆 不可逆 可逆
输出格式 固定长度的整数或字节 可变长度的字符串
字符集 无特定字符集限制 0-9, a-z, A-Z
分布性 分布均匀,减少哈希冲突 无分布均匀性,字符分布取决于输入
安全性 无安全性,非加密 无安全性,非加密
典型应用 哈希表、数据分片、数据校验 URL 短链接、UUID 编码、序列号生成

我们需要保证生成的短链具有唯一性,基于这样的需求,咱们采用第一种MurmurHash来确保生成的hash分布均匀且哈希冲突更少,同时会把该结果进行编码转换为链接字符串。

// 使用MurmurHash转换长链
public class ShortLinkService {

    private static final String BASE_URL = "http://short.url/";

    public String generateShortLink(String longUrl) {
        // 生成分布式ID
        long id = saveLongUrl(longUrl);
        // 使用MurmurHash生成短链
        int hash = MurmurHash3.hash32(id);
        String shortCode = Integer.toString(hash, 36); // Base36编码以生成短链

        return BASE_URL + shortCode;
    }
}

但是!!如果你只回答了上面这一步,那我认为这道题你只能得35分,一个系统不仅仅只要保证基础功能。。。

1.2 稳定性设计

(1)缓存加速读取

稳定性在这套系统设计题的体现是流量高峰期间的一个并发能力

相对通用且常见的缓存方案,实际上来来去去就那么一套,但各个公司使用的基础架构不同、服务器架构不同,最稳妥是要基于公司实际的架构情况做出最合适当前需求的方案。

南哥在上文有提到把短链、长链的一对一关系存储到Redis,通过Redis缓存技术可以提高访问短链的速度,另外只有热门的短链才有这个待遇,其他非热门短链走下面的流程。

短链系统有 N 个微服务节点,我们在每隔微服务节点都设置了本地缓存,那本地缓存的目标对象是哪些?

我们无需像热门短链一样配置在Redis,这里使用 LRU 算法,下面介绍下。

LRU 算法即最近最少使用算法,为本地缓存设置最大容量,当缓存容量达到阈值时,该算法会把最久没有访问的内容作为替换对象从缓存中删除。保证本地缓存的对象是相对访问量高的短链。

如果一个短链、长链的对应关系需要100字节存储,那 10 MB 本地缓存可以存储 10 w 个长短链对应关系。

// LRU算法使用
public class LRU {
    public static void main(String[] args) {
        LRUMap map = new LRUMap(3);
        map.put(0, 0);
        map.put(1, 1);
        map.put(2, 2);
        System.out.println(map);

        map.get(0);
        System.out.println(map);
        map.put(3, 3);
        System.out.println(map);
    }

    private static class LRUMap extends LinkedHashMap {
        private int max;

        public LRUMap(int max) {
            // accessOrder设置true时,把该get的元素放到队尾
            super((int) (max * 1.4f), 1.75f, true);
            this.max = max;
        }

        // 容量满时,删除队首元素
        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
            return size() > max;
        }
    }
}
# 执行结果
{0=0, 1=1, 2=2}
{1=1, 2=2, 0=0}
{2=2, 0=0, 3=3} // 删除了最近最少使用的队首元素1

(2)异步消息实现削峰

短链系统对于用户来说,主要就两个请求。一个是把长链生成短链,另一个是访问短链重定向到长链地址。上文的缓存方案服务的是第二种用户请求,下面我们针对第一种用户请求加上并发支持

南哥先画出这部分的抽象图。

alt

用户把长链转换请求到短链系统,我们把每一次请求看成一个个消息队列消息,上传到消息队列Kafka中。这里消息队列起到一个流量削峰的作用。

大家可以思考下,如果没有消息队列把一个个任务先缓存起来,而是任由每秒几千 / 上万个请求直接冲击服务器,这很有可能造成服务器崩溃。而队列任务一个个执行则减少了一瞬间需要处理的 N 个任务个数,减少服务器瞬时压力。

但是!!如果你只回答了上面两步,那我认为这道题你只能得70分,一个系统要完善不仅仅需要完成基础功能、保证系统稳定性,还需要保证系统的安全性

1.3 安全性设计

我们从两方面来考虑安全性的问题:生成短链如何保证安全?访问短链如何保证安全?

一、生成短链

(1)防止用户上传带有敏感性、不合法的链接,我们需要对长链的原始URL进行合法性的检测,如果是非法的链接直接拒绝服务。

// 原始URL合法性检测
public class UrlValidator {
    public boolean isValidUrl(String url) {
        ...
    }
}

(2)防止用户滥用短链生成服务,我们需要对用户每天的可用次数进行限制。

可以根据用户ID查询用户每天生成短链的次数,限制用户生成短链次数;当然用户很聪明,会制造多个小号,我们对同一IP生成短链的频率也加上限制。

使用Nginx对同一IP生成短链的频率进行限制。

# Nginx对同一IP生成短链的频率限制
http {
    limit_req_zone $binary_remote_addr zone=shortlink_limit:10m rate=1r/s;

    server {
        listen 80;

        location /generate {
            limit_req zone=shortlink_limit burst=5 nodelay;
            proxy_pass http://your_backend_service;
        }
    }
}

二、访问短链

为了防止短链访问被滥用,我们可以为每条短链设置每小时可访问的次数;同时每一条短链与长链的对应关系具有时效性,过期了需要重新生成短链。

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️❤️

#牛客激励计划##秋招##春招##大厂#
全部评论

相关推荐

一、开启投递:四处碰壁,陷入迷茫​3 月 12 日,我满怀期待地开启了简历投递之旅。起初,我将目光投向实习群和校招官网的实习岗位,觉得这些信息更及时精准,但一周过去,投递的十几份简历石沉大海。3 月 20 日起,我开始在大厂广泛投递,腾讯、阿里、字节跳动、京东、拼多多、华为等 Java 开发岗都成了我的目标。然而现实残酷,大部分投递仅显示 “简历筛选中” 便再无进展。​看着身边同学陆续收到面试邀约,我陷入了自我怀疑。反复检查简历,发现可能存在项目描述不够精炼、技术亮点未突出的问题;也担心自己对底层原理的掌握不够深入。那段时间,焦虑和迷茫时常笼罩着我,甚至开始自我怀疑。​二、柳暗花明,初现曙光​3月底4 月初,我开始陆续接到了面试。面试准备时,我从小红书、牛客等渠道查询面经,润色自我介绍,并根据每个岗位的 JD进行定制化面试题目准备。4 月 10 日,我迎来腾讯一面。面试官先让我介绍最有成就感的项目,我详细阐述了用 Seata 解决分布式事务的全过程。随后,针对 Spring 循环依赖问题,从三级缓存原理到实际应用场景展开追问,虽然部分细节回答得不够完美,但整体表现还算稳定。4 月 18 -25日,进行了二三面,注意聚焦系统设计,要求设计一个高并发秒杀系统,我从限流(Guava RateLimiter、Sentinel)、缓存预热、消息队列削峰填谷等方面进行分析,得到了面试官认可。4 月 27 日,我收到了录用评估。​但我并未停下脚步,继续参与其他公司面试。阿里的面试让我备受打击,4 月 18 日的一面中,面试官对 JVM 垃圾回收算法刨根问底,涉及 G1 收集器的分区设计和并发标记细节,很多问题我答得磕磕绊绊,最终止步一面。字节跳动的面试更侧重算法,4 月 22 日的面试中,现场两道算法题被爆杀。京东和拼多多的面试贴近业务,常问实际场景的技术解决方案。​华为的面试流程漫长,从 4 月 25 日的技术一面,到 5 月 5 日的综合主管面,每一轮都很压力。目前在泡池子。​在这一系列面试中,我积累了宝贵经验:面试前要对简历项目倒背如流,预想所有可能追问点;回答问题先理清逻辑,再分点阐述;遇到不会的问题,大方承认并尝试说出解题思路,展现学习能力。​三、收获成果(AI润色过)手握腾讯和华为的 offer,我陷入了幸福的纠结。腾讯的岗位在微信事业群,作为国民级应用,能接触到亿级流量的系统设计,团队氛围年轻活跃,技术大牛云集;华为的岗位聚焦云计算领域,公司在通信和云技术实力强劲,项目技术挑战性高,职业发展路径清晰。​为了做出抉择,我查阅大量行业报告,对比两家公司业务发展趋势和技术生态;联系在腾讯、华为工作的学长学姐,了解工作强度、成长机会和团队文化。经过反复权衡,我最终选择了腾讯。我认为,微信海量用户带来的复杂业务场景,能让我在高并发、分布式系统设计上快速成长,这与我未来从事互联网后端开发的职业规划高度契合。​回顾这段找实习的心路历程,有过焦虑、迷茫和自我怀疑,但更多的是成长与收获。它让我明白,求职之路从不会一帆风顺,唯有坚持不懈、不断调整、持续提升,才能抵达理想彼岸。希望我的经历能给正在求职的 26 届同学一些启发,祝愿大家都能斩获心仪的 offer!       
投递腾讯等公司8个岗位
点赞 评论 收藏
分享
评论
3
7
分享

创作者周榜

更多
牛客网
牛客企业服务