-
Notifications
You must be signed in to change notification settings - Fork 6
short link coderdream 20220428
CoderDream edited this page Apr 28, 2022
·
1 revision
package com.coderdream.helper;
import com.coderdream.utils.Constants;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.nio.charset.Charset;
import javax.annotation.PostConstruct;
@Service
@Slf4j
public class BloomFilterHelper {
/**
* 布隆过滤器
*/
private BloomFilter<String> bloomFilter;
@PostConstruct
public void init() {
bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), Constants.BLOOM_FILTER_INSERTION,
0.001);
}
/**
* 添加元素到过滤器
*/
public boolean put(String key) {
return bloomFilter.put(key);
}
/**
* 判断元素是否存在
*/
public boolean mightContain(String data) {
return bloomFilter.mightContain(data);
}
}
package com.coderdream.utils;
/**
* 功能描述
*
* @since 2022-04-28
*/
public class Constants {
public static final Integer BLOOM_FILTER_INSERTION = 100000000;
}
package com.coderdream.utils;
/**
* 功能描述
*
* @since 2022-04-28
*/
public enum DuplicatedEnum {
/**
* Item
*/
DUPLICATED("DUPLICATED"),
/**
* Lot
*/
OHMYGOD("OHMYGOD");
private String key;
DuplicatedEnum(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
package com.coderdream.helper;
import com.coderdream.utils.Config;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
@Service
@Slf4j
public class GuavaCacheHelper {
@Resource
private Config config;
private Cache<Object, Object> cache;
@PostConstruct
public void init() {
cache = CacheBuilder.newBuilder()
//设置并发级别为8,并发级别是指可以同时写缓存的线程数,此处最好和cpu数一致(Runtime.getRuntime().availableProcessors())
.concurrencyLevel(8)
//设置缓存容器的初始容量为10
.initialCapacity(10)
//设置缓存最大容量为100,超过100之后就会按照LRU最近最少使用算法来移除缓存项,生产环境可设置为 100000000
.maximumSize(100000000)
//是否需要统计缓存情况,该操作消耗一定的性能,生产环境应该去除
.recordStats()
//设置写缓存后n秒钟过期
.expireAfterWrite(config.EXPIRE_SEC, TimeUnit.SECONDS)
//设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
//.expireAfterAccess(17, TimeUnit.SECONDS)
//只阻塞当前数据加载线程,其他线程返回旧值
//.refreshAfterWrite(13, TimeUnit.SECONDS)
//设置缓存的移除通知
.removalListener(notification -> {
log.info(notification.getKey() + " " + notification.getValue() + " 被移除,原因:" + notification.getCause());
})
//build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
.build();
}
/**
* 获取缓存
*/
public Object get(String key) {
return StringUtils.isEmpty(key) ? null : cache.getIfPresent(key);
}
/**
* 放入缓存
*/
public void put(String key, Object value) {
if (StringUtils.isNotEmpty(key) && value != null) {
cache.put(key, value);
}
}
}
package com.coderdream.service.impl;
import com.coderdream.helper.BloomFilterHelper;
import com.coderdream.helper.GuavaCacheHelper;
import com.coderdream.model.ShortLinkBean;
import com.coderdream.service.LinkService;
import com.coderdream.utils.Config;
import com.coderdream.utils.DuplicatedEnum;
import com.coderdream.utils.ShortLinkComponent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class LinkServiceImpl implements LinkService {
@Resource
private Config config;
@Resource
private ShortLinkComponent shortLinkComponent;
@Resource
private GuavaCacheHelper guavaCacheHelper;
@Resource
private BloomFilterHelper bloomFilterHelper;
@Override
public String getShortLink(String longLink) {
String code = shortLinkComponent.createShortLinkCode(longLink);
ShortLinkBean shortLinkBean = new ShortLinkBean();
// 通过布隆过滤器判断:如果不存在(100%正确),则直接放入缓存中
if (!bloomFilterHelper.mightContain(code)) {
shortLinkBean.setShortLink(code);
shortLinkBean.setLongLink(longLink);
shortLinkBean.setExpireTime(System.currentTimeMillis() + config.EXPIRE_SEC * 1000);
guavaCacheHelper.put(code, shortLinkBean);
// 把短链接放入布隆过滤器
bloomFilterHelper.put(code);
}
// 如果存在(可能误判)
else {
// 从缓存中取对象
ShortLinkBean oldShortLinkBean = (ShortLinkBean) guavaCacheHelper.get(code);
// 如果不存在误判为存在,则直接将新的数据写入缓存中
if (oldShortLinkBean == null) {
shortLinkBean.setShortLink(code);
shortLinkBean.setLongLink(longLink);
shortLinkBean.setExpireTime(System.currentTimeMillis() + config.EXPIRE_SEC * 1000);
guavaCacheHelper.put(code, shortLinkBean);
// 把短链接放入布隆过滤器
bloomFilterHelper.put(code);
}
// 如果确实存在
else {
String oldLongLink = oldShortLinkBean.getLongLink();
// 判断是否Hash冲突了(code相同,长链接url不同)
if (code.equals(oldShortLinkBean.getShortLink()) && !longLink.equals(oldLongLink)) {
// 记录日志
log.error("Hash冲突, old and new code: " + code + "; old link: " + oldLongLink + " ; new link: "
+ longLink);
String newLongLink = "";
// 第一轮新code、新link
if (!oldLongLink.startsWith(DuplicatedEnum.DUPLICATED.getKey()) && !oldLongLink.startsWith(
DuplicatedEnum.OHMYGOD.getKey())) {
if (!oldLongLink.startsWith(DuplicatedEnum.DUPLICATED.getKey())) {
// code加上枚举前缀后Hash
code = shortLinkComponent.createShortLinkCode(DuplicatedEnum.DUPLICATED.getKey() + "_" + code);
newLongLink = DuplicatedEnum.DUPLICATED.getKey() + "_" + longLink;
} else {
code = shortLinkComponent.createShortLinkCode(
DuplicatedEnum.OHMYGOD.getKey() + "_" + code);
newLongLink = DuplicatedEnum.OHMYGOD.getKey() + "_" + longLink;
}
}
log.error("Hash冲突解决: new code: " + code + "; old link: " + oldShortLinkBean.getLongLink()
+ " ; new link: " + newLongLink);
shortLinkBean.setShortLink(code);
shortLinkBean.setLongLink(newLongLink);
shortLinkBean.setExpireTime(System.currentTimeMillis() + config.EXPIRE_SEC * 1000);
guavaCacheHelper.put(code, shortLinkBean);
// 把短链接放入布隆过滤器
bloomFilterHelper.put(code);
}
// 未冲突,已存在数据,不做处理,既不放到缓存中,也不放到过滤器中
else {
// 记录日志
log.info("已存在: code " + code + " ; longLink: " + longLink);
}
}
}
return code;
}
@Override
public String getLongLink(String shortLink) {
// 从缓存中获取对象
ShortLinkBean shortLinkBean = (ShortLinkBean) guavaCacheHelper.get(shortLink);
String longLink = shortLinkBean.getLongLink();
// 如果不存在Hash冲突标记,则直接返回长链接
if (!longLink.startsWith(DuplicatedEnum.DUPLICATED.getKey()) && !longLink.startsWith(
DuplicatedEnum.OHMYGOD.getKey())) {
return longLink;
}
// 否则去掉冲突标记后再返回
else {
longLink = longLink.replace(DuplicatedEnum.DUPLICATED + "_", "");
longLink = longLink.replace(DuplicatedEnum.OHMYGOD + "_", "");
}
return longLink;
}
}