Skip to content

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;
    }
}
Clone this wiki locally