给定一个长的URL,你怎么设计一个服务,能够提供短且独一无二的短URL(短链接-短网址)

作者: admin 分类: 应用技巧 发布时间: 2020-04-01 09:57  阅读: 338 views

短网址

如原地址:https://www.baidu.com/abcdefghigklmnopqrstuvwxyz.html

短网址为:https://dwz.cn/wcplVJvy

短网址:顾名思义就是一个长度比较短的url地址。把原来的长的URL通过程序设计等方式转换为短的链接。

百度搜索“短网址”,会发现有很多相关的在线工具,或者公司平台。说明这个短链接还是有很多用武之地的。

  • 在限制文本长度的社交平台里,可以输入更多信息(朋友圈、微博、短信等)
  • 生成的二维码更容易被识别(识别难度低)
  • 可以将带有中文等特殊字符的链接转化为可识别的链接(如:https://www.baidu.com/s?ie=UTF-8&wd= 百度)
  • 推广时容易复制、不易被拦截等。
  • 针对短链接地址做UV/PV/地域访问统计等处理(针对淘宝客单品短链接分析等)。

问题?

如果要你做一个短网址生成工具的话,应该怎么做呢?又要考虑哪些点呢?

  1. 如何给每个长URL生成一个唯一的标识ID?
  2. 怎么样在短时间内产生大量的唯一标识ID?
  3. 在服务器中如何设置URL重定向?
  4. 如何设计可定制化的短链接生成规则?(针对不同的用户/客户去生成)
  5. 删除短链接的策略?
  6. 如何有效的记录点击等统计数据?

解决方案

一、先处理标识ID的唯一性,以及保证快速大量生成。

  • Hash算法

一般情况下,我们把一个不定长度的字符串生成固定长度的值,会用到Hash算法(如MD5)。

但是它会产生hash碰撞啊,当需要转换的链接越多时,产生碰撞的情况越容易发生,这样的话,可能三个不同的页面都用一个短链接来表示了,肯定会产生问题。

  • 用户数据库处理
| 主键id |类型   |初始值     |当前值      |最大值         |步长 | 
|   1    |短网址 |1000000001 |1000000011  |9999999999    |1    |

## 也就是在数据库中设定一个初始值,每次增加步长。保证每次取出的数字不一样

但是这样做可能会有一个问题,就是当高并发的时候对数据库的压力会比较大。生成1w个短链接就会连接数据库1W次。

  • Redis、Zookeeper

redis官网测试读写到10万左右,zookeeper相比较差一些。都可以生成唯一值。
redis利用 incr 命令自增。 zookeeper利用节点的顺序特性也可以实现唯一性。

但是这种方式就显得短网址地址太具有规律性。就是一个部分可自增的地址,容易被人发现猫腻,不安全。

  • 用不重复算法

也就是将10进制转换为62进制。六十二进制是由[a – z, A – Z, 0 – 9] 总共 62 个字母组成的。1位可以表示62个数字,如果短网址唯一识别码是6位的话,也有62^6种显示(568亿种),这种数量一般是足够了。如果不够的话,可以增加唯一识别码的长度。

如原长链接
http://www.xx.com/aasdfadfadfadfasdf/xsdf/asf?agent=sdfwkefjaskdnfkand
先转自增
http://www.xx.com/10000000001
在转62进制
http://www.xx.com/aUKYOB


算法参考:https://segmentfault.com/a/1190000012088345

但是,这种短网址对于非技术人员来说好像不具有特别的辨识度。但是有点计算机知识的话,还是会发现它比较有规律的,仍然不安全。

  • 不重复算法 + 规则插入

就是在上面的基础上,固定位置插入随机数。

(?)a(?)U(?)K(?)Y(?)O(?)B(?)
在转为62进制数后长度固定的基础上,选取一个或多个?号的位置插入随机字符,扰乱视听。只要自己在解析的时候对应处理就好了。 当然这样处理涉及到对字符串的拼接处理。拆得越碎可能效率上会有影响,自己去评估吧。

综上所述,可以结合redis + 转62进制的方式保证唯一和批量生产。

二、客户想要自定义短链接/或者按照业务区分短链接

## 我们可以针对域名做处理,如下域名:
https://dwz.cn/wcplVJvy  

可以分为几部分
https://?.dwz.cn/?/wcplVJvy

第一个问号表示二级域名的名称,虽然可以设置无限个,但是空间商不会让你这么干的,我们可以将有限的二级域名内部使用。好像为了短网址做二级域名的区分意义不是太大。

第二个问号表示访问地址的某一段。可以任意长度,由用户自己定义,作为个性化的一种服务也不错。在访问时,在nginx的域名转发配置中,可以根据正则进行过滤或者其他处理。

注:如果用户想自定义第二个?后的内容,就要做一些和已存在数据的验证处理。或者做一些额外标记,区分用户自定义的或者系统自动生成的。

三、URL重定向

以文章开头的地址为例,过程如下:

  1. DNS解析 https://dwz.cn/wcplVJvy 的IP地址
  2. 服务端获取短网址的唯一标识符 wcplVJvy
  3. 根据唯一标识符去数据库中获取对应的长连接(数据量大可能导致这里查询慢,可以优化下,前置索引或其他?)
  4. 返回302重定向。(也就是在http协议的响应信息中返回新的location 及 Status)
  5. 浏览器跳转到新的url地址

==注==:如果是将 dwz.cn/wcplVJvy的访问地址重写为 https://dwz.cn/wcplVJvy可以参考这个 【nginx重定向】

四、删除短链接的策略

应该有两种方式去处理。
1. 设定短链接的有效时间,比如一周/一月/一年,定时定期检查过期数据进行删除。
2. 参考redis的过期删除策略。 LRU或者LFU算法都可以(最近最少使用,最不频繁使用)

五、统计数据的处理

这里统计的数据就可以很丰富了。

1. 短网址的访问来源(就是从哪个网址点击的短网址)
通过短网址服务端 request.getHeader("Referer");获取。
也可以做个中转页,通过js document.referrer来获取。(这种不太好)

2. 页面访问频次等统计

3.IP,PV等
可以通过请求做一些简单的数据统计,
也可以通过短网址服务端的访问日志来获取,如下:

以前写的可供参考的几种针对日志的处理。

利用logstash6.4.2监控access访问日志并切割,使用geoip插件分析ip地址在kibana控制台形成用户热点地图

通过python处理解析accesslog日志文件,kettle抽取数据并做PV/UV的统计实现

利用linux命令行grep|awk在mac本上分析wordpress.log访问日志


最后附上一段项目中的shortUrls的方法实现部分代码:

package com.chl.tools;

import java.util.concurrent.TimeUnit;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

@Service("shortUrlService")
public class ShortUrls implements shortUrlService {

    private static Cache<String, String> cache = CacheBuilder.newBuilder()
            // 设置cache的初始大小为100,要合理设置该值
            .initialCapacity(100)
            // 最大缓存数量1万
            .maximumSize(10000)
            // 设置并发数为5,即同一时间最多只能有5个线程往cache执行写入操作
            .concurrencyLevel(5)
            // 设置cache中的数据在写入之后的存活时间为60分钟
            .expireAfterWrite(3600, TimeUnit.SECONDS)
            // 构建cache实例
            .build();

    @Override
    public String getShortUrl(String longUrl) {
        // 如果已经存在直接返回
        ShortUrlDO target = shortUrlDao.getByLongUrl(longUrl);
        if (target != null) {
            return config.getShortDomainPath() + target.getShortId();
        }

        // 这里是通过数据库步长的方式生成唯一标识
        long id = sequenceService.next(SequenceService.SHORT_URL);

        // 转换成32进制
        String shortId = Utils.to32hex(id);

        // 保存db,然后返回
        target = new ShortUrlDO();
        target.setLongUrl(longUrl);
        target.setShortId(shortId);
        target.setStatus(1);

        shortUrlDao.insert(target);

        return config.getShortDomainPath() + shortId;
    }

    @Override
    public String getLongUrl(String shortId) {
        // 加入了本地缓存处理
        String longUrl = cache.getIfPresent(shortId);
        if (longUrl != null) {
            return longUrl;
        }

        // 查询db
        ShortUrlDO target = shortUrlDao.getByShortId(shortId);
        if (target == null) {
            return null;
        } else {
            cache.put(shortId, target.getLongUrl());
        }
        return target.getLongUrl();
    }

}



   原创文章,转载请标明本文链接: 给定一个长的URL,你怎么设计一个服务,能够提供短且独一无二的短URL(短链接-短网址)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

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

更多阅读