短链接的原理以及其实现

短链接的场景:短链接现在屡见不鲜,用于做促销、引流和数据统计等等,在一些限制字数的场景也很方便,看起来也是简洁,便于输入。

原理

短链接的原理

下面以短链接:wsc.fit/FZ(B站短链接)为例

  • 首先,在浏览器输入短链接,DNS先解析获得其IP地址;
  • 通过短链接,发送HttpGet请求,通过短链接,获取到原始的长连接;、
  • 请求通过重定向,跳转到长链接。302重定向,对服务器压力更大,但是优势在于,可以通过重定向进行流量统计。

短链接生成算法

知道了基本的原理,那短链接是通过什么生成的。短链接的组成分为:域名(区分Http和Https)+当前链接的短码。以bit.ly/2XyAbC为例,域名是bit.ly,短码是2XyAbC。一般来说,短码通过010,az,A~Z这六十二个数字和字母组成,四到八位短码,六位短码就有500多亿的组成方式,已经足够使用。常见的生成短码的方式有三种:

  • 通过自增ID。

在查询上一次的ID,在ID的基础上的进行MAX+1,然后然后将这十进制的ID转变为六十二为进制的转化成一个62进制的字符串。这样生成短码的优势在于,不会出现冲突,劣势在于,生成的短码是有序且安全性差,并且初始值不设置好,短码长度不一。可以结合其他数据进行MD5加密。

  • 通过随机数生成

将62位的数字和字母,随机排列组合作为短码,然后在数据库中查询是否已经存在。如果已经存在,则再次生成。优势就是逻辑简单,但是计算机生成的随机数是伪随机,如果需要生成大量短链接,可能出现重复需要再次生成。

  • 摘要算法(推荐)

摘要算法就是,通过一个函数,把任意的数据长度的数据转换成长度固定的数据。比如说,MD5,SHA1,SHA512等等。摘要算法,不是用于加密算法,因为不能通过摘要反推明文,而是用于防篡改。

通过加盐操作,很可以有效的防止MD5值被碰撞出来。

实现方案

数据库存储方案

sql 复制代码
 # 短链表
create table `t_short_link`
(
    `id`             bigint primary key auto_increment comment '主键ID',
    `short_link`     varchar(32)  not null default '' comment '短链接',
    `long_link_hash` bigint       not null default 0  comment  'hash值',
    `long_link`      varchar(128) not null default '' comment '长链接',
    `status`         tinyint      not null default 1  comment '状态:1-可用,0-不可用',
    `expiry_time`    datetime     null comment '过期时间',
    `create_time`    datetime     not null default current_timestamp comment '创建时间'
) comment '短链表';
# 创建对应的索引
create index idx_sl_hash_long_link on t_short_link (long_link_hash, long_link);
create index idx_sl_short_link on t_short_link (short_link);

缓存方案

如果所有的请求直接打在数据库上,无疑会影响到数据库的性能。但是如果直接将所有的短链信息直接存储在缓存中,也不太现实,当然数据量小也是可以的。

如果数据量大,可以考虑根据LRU(最近最少使用页面置换算法),在缓存中存放热点数据,并且设置合适的过期时间,将缓存进行热更新。

长链接转短链接的工具类:

案例一:
arduino 复制代码
import org.apache.commons.lang.StringUtils;
import com.google.common.hash.Hashing;

/**
 * 短网址工具类
 * 
 * @author Roc-xb
 *
 */
public class urlUtil {
	/**
	 * 10个数字+26个小写字母+26个大写字母
	 */
	private static String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	/**
	 * 62进制
	 */
	private static int scale = 62;
	/**
	 * 最小长度
	 */
	private static int minLength = 5;
	/**
	 * 种子
	 */
	private static int seed = 0;

	/**
	 * 长链接转短链接
	 * 
	 * @param longUrl 长链接
	 * @return 短链接
	 */
	public static String shortUrl(String longUrl) {
		// 用MurmurHash算法得出长链接的hash值
		int hash = Hashing.murmur3_32(seed).hashBytes(longUrl.getBytes()).asInt();
		return hex10To62(hash);
	}

	/**
	 * 10进制转62进制
	 * 
	 * @param hash
	 * @return 短链接
	 */
	public static String hex10To62(long hash) {
		// 解决hash值小于0时,会触发异常。
		hash = Math.abs(hash);
		StringBuilder sb = new StringBuilder();
		int remainder;
		while (hash > scale - 1) {
			// 对 scale 进行求余,然后将余数追加至 sb 中,由于是从末位开始追加的,因此最后需要反转字符串
			remainder = Long.valueOf(hash % scale).intValue();
			sb.append(chars.charAt(remainder));
			// 除以进制数,获取下一个末尾数
			hash /= scale;
		}
		sb.append(chars.charAt(Long.valueOf(hash).intValue()));
		String value = sb.reverse().toString();
		// value长度小于minLength时,在左侧填充字符'0'
		return StringUtils.leftPad(value, minLength, '0');

	}

}

测试类:

typescript 复制代码
@Test
void test(){
    // 长链接
	String longUrl = "https://www.zhangsan.com/community/test_service";
    // 短链接
    String shortUrl = urlUtil.shortUrl(longUrl);
    System.out.println(String.format("长链接:%s", longUrl));
    System.out.println(String.format("短链接:https://www.zhangsan.com/%s", shortUrl));
}

测试结果:

案例二

工具类:

arduino 复制代码
import org.apache.commons.codec.digest.DigestUtils;
import java.util.HashMap;

public class UrlUtil {
    public static HashMap<String, String> urls = new HashMap<>();
    static String[] chars = new String[]{
            "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"};

    public static String getChar(int index) {
        return chars[index];
    }

    public static String[] toShortAndSave(String str, String key) {
        // 拼接后的 url
        String realUrl = key + str;
        // md5 长度为 32 位
        String md5 = DigestUtils.md5Hex(realUrl);
        // md5 会被拆分为 4 部分 , 每部分均被转译成一种处理结果
        String[] shortUrl = new String[4];
        // 对 md5 的4个部分分别处理
        for (int beginIndex = 0; beginIndex < 4; beginIndex++) {
            // 预定义该部分的处理结果
            String out = "";
            // 字符串截取的两个坐标
            int endIndex = beginIndex + 1;
            // 裁剪 8 位
            String strPart = md5.substring(beginIndex * 8, endIndex * 8);
            // 与0x3FFFFFFF 进行位运算 获得30位长度的数字编码
            long index = Long.valueOf("3FFFFFFF", 16) & Long.valueOf(strPart, 16);
            // 将30位的数字编码拆分成6个部分 每次处理5位
            for (int i = 0; i < 6; i++) {
                // 与 0x0000003D 与运算,相当于与运算后5位,获得对应的字符下标
                int idx = (int) (Long.valueOf("0000003D", 16) & index);
                // 获得下标对应字符
                out += getChar(idx);
                // 数字右移5位 继续处理
                index = index >> 5;
            }
            // out 即为处理后的一种short方案,可以存储到数据库等存储介质中,这里为了方便展示直接存储到 hashMap 里
            urls.put(out, str);
            // 一共返回4种方案 分别赋值
            shortUrl[beginIndex] = out;
        }
        return shortUrl;
    }

    public static String[] head(String[] shortUrls) {
        String[] res = new String[4];
        int idx = 0;
        for (String e : shortUrls) {
            res[idx++] = "http://t.cn/" + e;
        }
        return res;
    }

}

测试类:

ini 复制代码
    @Test
    void test1(){
        String url = "https://blog/zhangsan";
        String key = "test";
        System.out.println("目标url:" + url);
        System.out.println("临时key:" + key);
        String[] res = UrlUtil.toShortAndSave(url, key);
        System.out.println("短网址处理结果:");
        for (String e : res) {
            System.out.println(e);
        }
        String[] head = head(res);
        System.out.println("短URL展示:");
        for (String e : head) {
            System.out.println(e);
        }
        System.out.println("网址映射:");
        for (String e : res) {
            System.out.println("短网址 : " + "https://blog/"+e + "   ->   实际网址 : " + urls.get(e));
        }
    }

结果:

相关推荐
小突突突1 天前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
iso少年1 天前
Go 语言并发编程核心与用法
开发语言·后端·golang
掘金码甲哥1 天前
云原生算力平台的架构解读
后端
码事漫谈1 天前
智谱AI从清华实验室到“全球大模型第一股”的六年征程
后端
码事漫谈1 天前
现代软件开发中常用架构的系统梳理与实践指南
后端
Mr.Entropy1 天前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate
YDS8291 天前
SpringCloud —— MQ的可靠性保障和延迟消息
后端·spring·spring cloud·rabbitmq
无限大61 天前
为什么"区块链"不只是比特币?——从加密货币到分布式应用
后端
洛神么么哒1 天前
freeswitch-初级-01-日志分割
后端
蝎子莱莱爱打怪1 天前
我的2025年年终总结
java·后端·面试