如何实现一个短链接服务

短链接,通俗来说,就是将长的URL网址,通过程序计算等方式,转换为简短的网址字符串。

大家经常会收到一些莫名的营销短信,里面有一个非常短的链接让你跳转。新浪微博因为限制字数,所以也会经常见到这种看着不像网址的网址。短链的兴起应该就是微博限制字数激起了大家的创造力。

如果创建一个短链系统,我们应该做什么呢?

  1. 将长链接变为短链;
  2. 用户访问短链接,会跳转到正确的长链接上去。

查找到对应的长网址,并跳转到对应的页面。

短链生成方法

短码一般是由 [a - z, A - Z, 0 - 9] 这62 个字母或数字组成,短码的长度也可以自定义,但一般不超过8位。比较常用的都是6位,6位的短码已经能有568亿种的组合:(26+26+10)^6 = 56800235584,已满足绝大多数的使用场景。

目前比较流行的生成短码方法有:自增id摘要算法普通随机数

自增id

该方法是一种无碰撞的方法,原理是,每新增一个短码,就在上次添加的短码id基础上加1,然后将这个10进制的id值,转化成一个62进制的字符串。

一般利用数据表中的自增id来完成:每次先查询数据表中的自增id最大值max,那么需要插入的长网址对应自增id值就是 max+1,将max+1转成62进制即可得到短码。

但是短码 id 是从一位长度开始递增,短码的长度不固定,不过可以用 id 从指定的数字开始递增的方式来处理,确保所有的短码长度都一致。同时,生成的短码是有序的,可能会有安全的问题,可以将生成的短码id,结合长网址等其他关键字,进行md5运算生成最后的短码。

摘要算法

摘要算法又称哈希算法,它表示输入任意长度的数据,输出固定长度的数据。相同的输入数据始终得到相同的输出,不同的输入数据尽量得到不同的输出。

算法过程:

  1. 将长网址md5生成32位签名串,分为4段, 每段8个字节;
  2. 对这四段循环处理, 取8个字节, 将他看成16进制串与0x3fffffff(30位1)与操作, 即超过30位的忽略处理;
  3. 这30位分成6段, 每5位的数字作为字母表的索引取得特定字符, 依次进行获得6位字符串;
  4. 总的md5串可以获得4个6位串;取里面的任意一个就可作为这个长url的短url地址;

这种算法,虽然会生成4个,但是仍然存在重复几率。

虽然几率很小,但是该方法依然存在碰撞的可能性,解决冲突会比较麻烦。不过该方法生成的短码位数是固定的,也不存在连续生成的短码有序的情况。

普通随机数

该方法是从62个字符串中随机取出一个6位短码的组合,然后去数据库中查询该短码是否已存在。如果已存在,就继续循环该方法重新获取短码,否则就直接返回。

该方法是最简单的一种实现,不过由于Math.round()方法生成的随机数属于伪随机数,碰撞的可能性也不小。在数据比较多的情况下,可能会循环很多次,才能生成一个不冲突的短码。

算法分析

以上算法利弊我们一个一个来分析。

如果使用自增id算法,会有一个问题就是不法分子是可以穷举你的短链地址的。原理就是将10进制数字转为62进制,那么别人也可以使用相同的方式遍历你的短链获取对应的原始链接。打个比方说:http://tinyurl.com/a3300和 http://bit.ly/a3300,这两个短链网站,分别从a3300 - a3399,能够试出来多次返回正确的url。所以这种方式生成的短链对于使用者来说其实是不安全的。

摘要算法,其实就是hash算法吧,一说hash大家可能觉得很low,但是事实上hash可能是最优解。比如:http://www.sina.lt/ 和 http://mrw.so/ 连续生成的url发现并没有规律,很有可能就是使用hash算法来实现。

普通随机数算法,这种算法生成的东西和摘要算法一样,但是碰撞的概率会大一些。因为摘要算法毕竟是对url进行hash生成,随机数算法就是简单的随机生成,数量一旦上来必然会导致重复。

综合以上,我选择最low的算法:摘要算法。

实现
存储方案

数据库存储方案

短网址基础数据采用域名和后缀分开存储的形式。另外域名需要区分 HTTP 和 HTTPS,hash方案针对整个链接进行hash而不是除了域名外的链接。域名单独保存可以用于分析当前域名下链接的使用情况。

增加当前链接有效期字段,一般有短链需求的可能是相关活动或者热点事件,这种短链在一段时间内会很活跃,过了一定时间热潮会持续衰退。所以没有必要将这种链接永久保存增加每次查询的负担。

对于过期数据的处理,可以在新增短链的时候判断当前短链的失效日期,将每天到达失效日期的数据在HBase单独建一张表,有新增的时候判断失效日期放到对应的HBase表中即可,每天只用处理当天HBase表中的失效数据。

数据库基础表如下:

base_url suffix_url shot_code total_click_count full_url expiration_date
http://www.aichacha.com /search/12345 edfg3s http://www.aichacha.com//search/12345
http://www.aichacha.com /aiCheck/getResult/123 Fe9dq http://www.aichacha.com//aiCheck/getResult/123
http://www.baidu.com /wenku/12354 lcfr53 http://www.baidu.com/wenku/12354

字段释义:

base_url:域名

suffix_url:链接除了域名外的后缀

full_url:完整链接

shot_code:当前 suffix_url 链接的短码

expiration_date:失效日期

total_click_count:当前链接总点击次数

expiration_date:当前链接失效时间

缓存方案

  • 查询需求

个人认为对于几百个G的数据量都放在缓存肯定是不合适的,所以有个折中的方案:将最近3个月内有查询或者有新增的url放入缓存,使用LRU算法进行热更新。这样最近有使用的发概率会命中缓存,就不用走库。查不到的时候再走库更新缓存。

  • 新增需求

对于新增的链接就先查缓存是否存在,缓存不存在再查库,数据库已经分表了,查询的效率也不会很低。

  • 缓存的设计

查询的需求是用户拿着短链查询对应的真实地址,那么缓存的key只能是短链,可以使用 KV的形式存储。



番外

其实也可以考虑别的存储方案,比如HBase,HBase作为NOSQL数据库,性能上仅次于redis但是存储成本比redis低很多个数量级,存储基于HDFS,写数据的时候会先先写入内存中,只有内存满了会将数据刷入到HFile。读数据也会快,原因是因为它使用了LSM树型结构,而不是B或B+树。HBase会将最近读取的数据使用LRU算法放入缓存中,如果想增强读能力,可以调大blockCache。

其次,也可以使用ElasticSearch,合适的索引规则效果不输缓存方案。



是否有分库分表的需要?

对于单条数据10b以内,一亿条数据总容量大约为 953G,单表肯定无法撑住这么大的量,所以有分表的需要,如果你对服务很有信心2年内能达到这个规模,那么你可以从一开始设计就考虑分表的方案。

那么如何定义分表的规则呢?

如果按照单表500万条记录来算,总计可以分为20张表,那么单表容量就是47G,还是挺大,所以考虑分表的 key 和单表容量,如果分为100张表那么单表容量就是10G,并且通过数字后缀路由到表中也比较容易。可以对short_code 做encoding编码生成数字类型然后做路由。

如何转跳

当我们在浏览器里输入 http://bit.ly/a3300 时

  1. DNS首先解析获得 http://bit.ly的 IP 地址
  2. DNS 获得 IP 地址以后(比如:12.34.5.32),会向这个地址发送 HTTP GET 请求,查询短码 a3300
  3. [http://bit.ly 服务器会通过短码 a3300 获取对应的长 URL
  4. 请求通过 HTTP 301 转到对应的长 URL http://www.theaustralian.news.com.au/story/0,25197,26089617-5013871,00.html。

这里有个小的知识点,为什么要用 301 跳转而不是 302 呐?

知识点:为什么要使用302跳转,而不是301跳转呢?

301是永久重定向,302是临时重定向。短地址一经生成就不会变化,所以用301是符合http语义的。但是如果用了301, Google,百度等搜索引擎,搜索的时候会直接展示真实地址,那我们就无法统计到短地址被点击的次数了,也无法收集用户的Cookie, User Agent 等信息,这些信息可以用来做很多有意思的大数据分析,也是短网址服务商的主要盈利来源。

引自知乎-武林的回答,原文链接

附上两个算法:

摘要算法:


import org.apache.commons.lang3.StringUtils;

import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicLong;

import static com.alibaba.fastjson.util.IOUtils.DIGITS;

/**
 * @author rickiyang
 * @date 2020-01-07
 * @Desc TODO
 */
public class ShortUrlGenerator {

    public static void main(String[] args) {
        String sLongUrl = "http://www.baidu.com/121244/ddd";
        for (String shortUrl : shortUrl(sLongUrl)) {
            System.out.println(shortUrl);
        }
    }

    public static String[] shortUrl(String url) {
        // 可以自定义生成 MD5 加密字符传前的混合 KEY
        String key = "dwz";
        // 要使用生成 URL 的字符
        String[] chars = new String[]{"a", "b", "c", "d", "e", "f", "g", "h",
                "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
                "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
                "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
                "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
                "U", "V", "W", "X", "Y", "Z"
        };
        // 对传入网址进行 MD5 加密
        String sMD5EncryptResult = "";
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update((key + url).getBytes());
            byte[] digest = md.digest();
            sMD5EncryptResult = DatatypeConverter.printHexBinary(digest).toUpperCase();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        String[] resUrl = new String[4];
        //得到 4组短链接字符串
        for (int i = 0; i < 4; i++) {
            // 把加密字符按照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算
            String sTempSubString = sMD5EncryptResult.substring(i * 8, i * 8 + 8);
            // 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用 long ,则会越界
            long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);
            String outChars = "";
            //循环获得每组6位的字符串
            for (int j = 0; j < 6; j++) {
                // 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引(具体需要看chars数组的长度   以防下标溢出,注意起点为0)
                long index = 0x0000003D & lHexLong;
                // 把取得的字符相加
                outChars += chars[(int) index];
                // 每次循环按位右移 5 位
                lHexLong = lHexLong >> 5;
            }
            // 把字符串存入对应索引的输出数组
            resUrl[i] = outChars;
        }
        return resUrl;
    }

}

数字转为base62算法:


/**
 * @author rickiyang
 * @date 2020-01-07
 * @Desc TODO
 * <p>
 * 进制转换工具,最大支持十进制和62进制的转换
 * 1、将十进制的数字转换为指定进制的字符串;
 * 2、将其它进制的数字(字符串形式)转换为十进制的数字
 */
public class NumericConvertUtils {

    public static void main(String[] args) {
        String str = toOtherNumberSystem(22, 62);
        System.out.println(str);
    }

    /**
     * 在进制表示中的字符集合,0-Z分别用于表示最大为62进制的符号表示
     */
    private static final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};

    /**
     * 将十进制的数字转换为指定进制的字符串
     *
     * @param number 十进制的数字
     * @param seed   指定的进制
     * @return 指定进制的字符串
     */
    public static String toOtherNumberSystem(long number, int seed) {
        if (number < 0) {
            number = ((long) 2 * 0x7fffffff) + number + 2;
        }
        char[] buf = new char[32];
        int charPos = 32;
        while ((number / seed) > 0) {
            buf[--charPos] = digits[(int) (number % seed)];
            number /= seed;
        }
        buf[--charPos] = digits[(int) (number % seed)];
        return new String(buf, charPos, (32 - charPos));
    }

    /**
     * 将其它进制的数字(字符串形式)转换为十进制的数字
     *
     * @param number 其它进制的数字(字符串形式)
     * @param seed   指定的进制,也就是参数str的原始进制
     * @return 十进制的数字
     */
    public static long toDecimalNumber(String number, int seed) {
        char[] charBuf = number.toCharArray();
        if (seed == 10) {
            return Long.parseLong(number);
        }

        long result = 0, base = 1;

        for (int i = charBuf.length - 1; i >= 0; i--) {
            int index = 0;
            for (int j = 0, length = digits.length; j < length; j++) {
                //找到对应字符的下标,对应的下标才是具体的数值
                if (digits[j] == charBuf[i]) {
                    index = j;
                }
            }
            result += index * base;
            base *= seed;
        }
        return result;
    }
}

原文地址:https://www.cnblogs.com/rickiyang/p/12178644.html

时间: 2024-10-05 02:56:48

如何实现一个短链接服务的相关文章

百度 谷歌 Twitter,这么多短链接服务(Short Url)到底哪家强?

一.短链接是什么 短链接,通俗来说,就是将长的URL网址,通过程序计算等方式,转换为简短的网址字符串. 它的原理也非常简单,就是采用 Domain Redirect(域名重定向) ,将一个域名自动跳转到另一个域名. 根据wikipedia描述,短链接的方案最早在2000年诞生.在2009年11月,著名短链接服务提供商Bitly的访问量是21亿,可见短链接的使用需求是非常大的. 维基地址 而在国内,短链接的普及与微博息息相关. 最早是由新浪仿照Twitter的140短篇博文功能,上线了新浪微博,之

【文章阅读】短链接服务系统开发

一.短链接服务系统开发 https://www.javadoop.com/post/url-shortener?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io 二.思路说明 文章很棒,不过最重要的是思路,在可实现的思路下,你会发现每一步都不难. 本文目的就是整理思路,然后让自己实现. 1.功能: 长链转短链,短链跳转到长链. 2.如何保证短链唯一: 短链是一个字符串,只需由一个唯一的ID,可以根据某种规则,转换出唯一的

Java 域名短链接服务原理及解决方案

一.背景 现在在各种圈的产品各种推广地址,由于URL地址过长,不美观.不方便收藏.发布.传播以及各种发文字数限制等问题,微信.微博都在使用短链接技术.最近由于使用的三方的生成.解析短链接服务开始限制使用以及准备收费.不方便统计分析.不方便流控等问题,决定自建一个短地址服务. 二.原理 比如,http://a.b.com/15uOVS 这个短地址 第1步,浏览器请求这个地址 第2步,通过DNS后到短地址服务端,还原这个短地址对应的原始长地址. 第3步,请求http 301 或302到原始的长地址上

最近做了一个短网址服务 di81.com

最近做了一个短网址服务:   di81.com 项目中有一处需求,需要把长网址缩为短网址,把结果通过短信.微信等渠道推送给客户.刚开始直接使用网上现成的开放服务,然后在某个周末突然手痒想自己动手实现一个别具特色的长网址(文本)缩短服务. 由于以前做过socket服务,对数据包的封装排列还有些印象,因此,短网址服务我第一反应是先设计数据的存储格式,我这里没有采用数据库,而是使用2个文件来实现: Url.db存储用户提交的长网址文本,Url.idx  存储数据索引,记录每次提交数据的位置(Begin

PHP伪静态与短链接

如今,Web服务高速发展的时代,各式各类的门户网站,如新浪http://www.sina.com.腾讯http://www.qq.com,这些网站大家都很容易记住,因为这种名称都是有规则和含义的.如果给你一个http://14.215.177.38,你肯定记不住这个网站是什么,可是我告诉你它就是百度首页. 所以,我们在开发一个网站系统的时候,还是要对链接进行优化的,而我理解的优化类型有伪静态与短连接. 其实,如果你数据结构学的不错,这个对你来说很容易理解,毕竟它就是一种地址映射的关系. 常识告诉

长链接转短链接(短网址)

场景 ? 短链接服务就是将一段长的URL转换为短的URL,比如利用新浪微博的短链接生成器,可将一段长的URL(http://blog.csdn.net/poem_qianmo/article/details/52344732)转换为一段短的URL(http://t.cn/RtFFvic),用户通过访问短链接即可重定向到原始的URL. 整个交互流程如下: 用户访问短链接:http://t.cn/RtFFvic 短链接服务器t.cn收到请求,根据URL路径RtFFvic获取到原始的长链接:http:

新浪短链接

https://www.aliyun.com/jiaocheng/327498.html 阿里云 >  教程中心   >  java教程 >  短链(短地址.Short URL) 短链(短地址.Short URL) 发布时间:2018-02-07 来源:网络 上传者:用户 关键字: 新浪微博 接口 流行 统计 网络 发表文章 摘要:   短链接产生和流行得益于社交网络的发展.短链接可以更方便的在网络中传播,避免超出字符限制,使得分享地址更加容易并且能够统计此地址的访问信息.   下面是新

生成短链接的URL

假设你想做一个像微博短链接那样的短链接服务,短链接服务生成的URL都非常短例如: http://t.cn/E70Piib, 我们应该都能想到链接中的E70Piib对应的就是存储长链接地址的数据记录的ID,可是这个有大小写字母和数字构成的唯一ID是怎么生成的呢,刚学编程的时候我们用的方法都试拼接一个足够唯一的字符串(比如时间戳加用户ID等等)然后再用MD5或者SHA1散列算法算出一个散列值,用这种方法得到的唯一ID有可能比原始的链接的长度还要长,所以如何来优雅的生成足够短的字符串唯一ID呢? 我们

15款最好用的新浪短链接(t.cn接口)在线生成工具

短链接已经广泛应用于各种场景,电商,微信,短信等等.那么今天给大家推荐15款t.cn.url.cn的短链接短网址 在线生成工具,都是我从市面数十款短链接短网址生成工具里面挑选的最为简便,最为稳定的短链接短网址生成 工具.废话不多! 1,青梅短网址 网站链接:http://qingmeidwz.cn 青梅短网址是发展的比较成熟的一款在线短网址短链接生成工具,一直是本着“简单,高效,自由,稳定”的理 念.不管是同行业还是用户的口中都有着非常好的口碑. 青梅短网址最大的优点就和他们所倡导的理念一样:“