短网址
如原地址:https://www.baidu.com/abcdefghigklmnopqrstuvwxyz.html
短网址为:https://dwz.cn/wcplVJvy
短网址:顾名思义就是一个长度比较短的url地址。把原来的长的URL通过程序设计等方式转换为短的链接。
百度搜索“短网址”,会发现有很多相关的在线工具,或者公司平台。说明这个短链接还是有很多用武之地的。
- 在限制文本长度的社交平台里,可以输入更多信息(朋友圈、微博、短信等)
- 生成的二维码更容易被识别(识别难度低)
- 可以将带有中文等特殊字符的链接转化为可识别的链接(如:https://www.baidu.com/s?ie=UTF-8&wd= 百度)
- 推广时容易复制、不易被拦截等。
- 针对短链接地址做UV/PV/地域访问统计等处理(针对淘宝客单品短链接分析等)。
问题?
如果要你做一个短网址生成工具的话,应该怎么做呢?又要考虑哪些点呢?
- 如何给每个长URL生成一个唯一的标识ID?
- 怎么样在短时间内产生大量的唯一标识ID?
- 在服务器中如何设置URL重定向?
- 如何设计可定制化的短链接生成规则?(针对不同的用户/客户去生成)
- 删除短链接的策略?
- 如何有效的记录点击等统计数据?
解决方案
一、先处理标识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重定向
以文章开头的地址为例,过程如下:
- DNS解析 https://dwz.cn/wcplVJvy 的IP地址
- 服务端获取短网址的唯一标识符 wcplVJvy
- 根据唯一标识符去数据库中获取对应的长连接(数据量大可能导致这里查询慢,可以优化下,前置索引或其他?)
- 返回302重定向。(也就是在http协议的响应信息中返回新的location 及 Status)
- 浏览器跳转到新的url地址
注:如果是将 dwz.cn/wcplVJvy的访问地址重写为 https://dwz.cn/wcplVJvy可以参考这个 【nginx重定向】
四、删除短链接的策略
应该有两种方式去处理。
- 设定短链接的有效时间,比如一周/一月/一年,定时定期检查过期数据进行删除。
- 参考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();
}
}
来自个博:deathearth