问题:什么是短链接?有什么作用?

问题:短链接是如何实现页面跳转的?

问题:如何生成短链接?短链接生成算法是什么?

问题:为什么使用这种算法?

问题:为什么短链接表要以gid作为分片件?

​ –解答:这里主要是方便进行分页查询

问题:哈希冲突怎么解决?

​ –解答:重试,随机一个UUID继续生成。

问题:为什么使用UUID作为生成短链接?

​ –解答:每次重试的时候,由于UUID是随机生成的,冲突的概率更小

问题:为什么建议Spring使用构造注入?

问题:为什么一个表中往往有两个id字段?

问题:为什么使用布隆过滤器?

​ –解答:这里的目的是我们在生成一个短链接后需要查看数据库中是否已经存在该短链接。一种最直接的实现方案是直接查询数据库,但面对海量用户时,这种方案不仅性能低,而且由于数据库存在最大连接数,无法支持海量用户。布隆过滤器可以快速的判断出数据库是否存在此短链接,性能高。

问题:为什么不使用set来判断是否存在短链接呢?

​ –解答:布隆过滤器本身是由一个二进制位数组和多个哈希函数组成,工作原理是将key通过哈希函数映射到二进制数组的对应位置,因此所需要的内存是非常小的。set还存在大key问题?

问题:针对布隆过滤器的误判如何解决?

​ –虽然布隆过滤器的性能非常好,但是存在误判的可能,由于其本质上是一种哈希算法,那么就必然会出现哈希冲突,即两个不同的key映射成相同的值。布隆过滤器可以判断数据库中一定不存在某个值,但当布隆过滤器判断数据库中存在某个值时,可能是误判导致,针对误判问题,我的解决方案是先查询数据库判断是否的确存在该短链接,如果不存在,直接返回生成的短链接,否则进入重试。

问题:当布隆过滤器的内存快满时,此时误判率极高,如何优化?

问题:同一时间,有多个创建短链接的请求,这些请求生成的短链接完全一致并且布隆过滤器不存在该链接,这些请求是不是都会插入到数据库中?

​ –解答:这是一个很好的问题,在线程并发情况下,很容易出现这种情况。我的解决方案也很简单,就是将数据库的full_short_url字段标识为unique约束,这样就能够避免以上问题了,因为数据库中不允许相同的full_short_url内容出现,因此这些请求中只有一个能够成功,其余请求都会报错。

问题:也就是说所有请求都会打到数据库,这种方案遇到海量请求是否有问题?如何解决?


实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* shortLink project 服务实现层
*/
@RequiredArgsConstructor
@Service
@Slf4j
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLinkDO> implements ShortLinkService {
/**
* 布隆过滤器
*/
private final RBloomFilter<String> shortLinkExistOrNotBloomFilter;
private final ShortLinkMapper shortLinkMapper;

/**
* 创建短链接
*/
public Result createShortLink(ShortLinkCreateReqDTO requestParam){
Integer validDateType = requestParam.getValidDateType();

String shortLinkSuffix = generateSuffix(requestParam.getDomain());
StringBuilder fullShortUrl = new StringBuilder();
fullShortUrl.append(requestParam.getDomain()).append("/").append(shortLinkSuffix);

ShortLinkDO shortLinkDO = ShortLinkDO.builder().domain(requestParam.getDomain())
.originUrl(requestParam.getOriginUrl())
.gid(requestParam.getGid())
.fullShortUrl(fullShortUrl.toString())
.enableStatus(requestParam.getEnableStatus())
.shortUri(shortLinkSuffix)
.createType(requestParam.getCreateType())
.describe(requestParam.getDescribe()).build();
if(validDateType == 0){
shortLinkDO.setValidDateType(0);
}else{
shortLinkDO.setValidDateType(1);
Date validDate = requestParam.getValidDate();
shortLinkDO.setValidDate(validDate);
}

try {
shortLinkMapper.insert(shortLinkDO);
shortLinkExistOrNotBloomFilter.add(fullShortUrl.toString());
} catch (Exception e) {
throw new ServerException(String.format("短链接:%s生成重复",fullShortUrl.toString()));
}

return Results.success(BeanUtil.copyProperties(shortLinkDO, ShortLinkCreateResDTO.class));
}

/**
* 生成短链接后缀
*/
public String generateSuffix(String domain){
int count = 0;
String res;
while(true){
if(count == 10){
throw new ClientException("生成短链接重试次数达到10次");
}
//1. 使用UUID生成短链接后缀
String shortLinkSuffix = HashUtil.hashToBase62(UUID.randomUUID().toString(true));
StringBuilder url = new StringBuilder();
url.append(domain).append("/").append(shortLinkSuffix);
//2. 通过布隆过滤器判断是否已经存在该短链接
if(!shortLinkExistOrNotBloomFilter.contains(url.toString())){
//如果不存在,说明数据库中一定不存在该短链接
res = shortLinkSuffix;
break;
}else{
//布隆过滤器判断数据库中存在该短链接,但有误判的可能,需要查询数据库中是否存在该短链接
LambdaQueryWrapper<ShortLinkDO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShortLinkDO::getFullShortUrl,url);
ShortLinkDO shortLinkDO = shortLinkMapper.selectOne(queryWrapper);
if(shortLinkDO == null){
//布隆过滤器误判,数据库中其实没有该短链接
res = shortLinkSuffix;
break;
}else{
//重试
count++;
}
}
}
return res;
}
}