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

短网址

如原地址: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();
	}

}

来自个博:deathearth

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在前端 Vue 中,可以使用以下方法将给定的图片 URL 保存到本地: 1. 使用 `fetch` API 或 `axios` 等库进行图片的网络请求,获取图片的数据。 ```javascript import axios from 'axios'; axios.get('https://example.com/image.jpg', { responseType: 'blob' }) .then(response => { const imageURL = window.URL.createObjectURL(new Blob([response.data])); // 执行保存本地的操作 }) .catch(error => { // 处理错误 }); ``` 2. 创建一个链接元素 `<a>`,将图片 URL 赋值给它的 `href` 属性,并设置 `download` 属性为所需的文件名。 ```javascript const link = document.createElement('a'); link.href = imageURL; link.download = 'image.jpg'; ``` 3. 使用 `link.click()` 方法触发下载操作,并在下载完成后释放 URL 对象。 ```javascript link.click(); window.URL.revokeObjectURL(imageURL); ``` 完整的代码示例: ```javascript import axios from 'axios'; axios.get('https://example.com/image.jpg', { responseType: 'blob' }) .then(response => { const imageURL = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = imageURL; link.download = 'image.jpg'; link.click(); window.URL.revokeObjectURL(imageURL); }) .catch(error => { // 处理错误 }); ``` 这样,当用户点击保存按钮或触发某个事件时,Vue 将下载并保存给定的图片到本地。请确保网络请求成功并正常获取到图片数据,以及在适当的时机释放 URL 对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值