亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

需求

線上出現(xiàn)的問題是,一些非核心的查詢數(shù)據(jù)業(yè)務(wù),在請求超時(shí)或者錯(cuò)誤的時(shí)候,用戶會越查詢,導(dǎo)致數(shù)據(jù)庫cup飆升,拖垮核心的業(yè)務(wù)。

領(lǐng)導(dǎo)讓我做三件事,一是把這些接口做一個(gè)限流,這些限流參數(shù)是可配的,第二是這些接口可以設(shè)置開關(guān),當(dāng)發(fā)現(xiàn)問題時(shí),可以手動關(guān)閉這些接口,不至于數(shù)據(jù)庫壓力過大影響核心業(yè)務(wù)的服務(wù)。第三是做接口的熔斷,熔斷設(shè)置可以配置。

經(jīng)過確定,前兩個(gè)實(shí)現(xiàn)用redis來實(shí)現(xiàn),第三個(gè)因?yàn)槿蹟嘤懻撚X得比較復(fù)雜,決定采用我提出的用Hystrix,目前項(xiàng)目不能熱加載生效配置中心的最新的配置,所以后期推薦使用Archaius,這些網(wǎng)上查到的,具體為啥不選其他的,原因就是其他的比較復(fù)雜,上手感覺這個(gè)最快。

這篇文章說實(shí)現(xiàn),其他問題不涉及,請多多指教。

思路

接口的屏蔽:通過AOP實(shí)現(xiàn),每次訪問接口的時(shí)候,通過接口的Key值,在Redis取到接口設(shè)置開關(guān)值,如果打開繼續(xù),否在拒絕。接口限流也是基于AOP,根據(jù)接口的Key值,取到這個(gè)接口的限流值,表示多長時(shí)間,限流幾次,每次訪問都會請求加一,通過比較,如果超過限制再返回,否在繼續(xù)。

代碼

 

接口的屏蔽和限流很難么?Redis全搞定

 

AccessLimiter接口,主要有兩類方法,是否開啟限流,取Redis中的限流值。

package com.hcfc.auto.util.limit;
import JAVA.util.concurrent.TimeUnit;
/**
 * @創(chuàng)建人 peng.wang
 * @描述 訪問限制器
 */
public interface AccessLimiter {
    /**
     * 檢查指定的key是否收到訪問限制
     * @param key   限制接口的標(biāo)識
     * @param times 訪問次數(shù)
     * @param per   一段時(shí)間
     * @param unit  時(shí)間單位
     * @return
     */
    public boolean isLimited(String key, long times, long per, TimeUnit unit);
 
    /**
     * 移除訪問限制
     * @param key
     */
    public void refreshLimited(String key);
 
    /**
     * 接口是否打開
     * @return
     */
    public boolean isStatus(String redisKey);
 
    /**
     * 接口的限流大小
     * @param redisKeyTimes
     * @return
     */
    public long getTimes(String redisKeyTimes);
 
    /**
     * 接口限流時(shí)間段
     * @param redisKeyPer
     * @return
     */
    public long getPer(String redisKeyPer);
 
    /**
     * 接口的限流時(shí)間單位
     * @param redisKeyUnit
     * @return
     */
    public TimeUnit getUnit(String redisKeyUnit);
 
    /**
     * 是否刪除接口限流
     * @param redisKeyIsRefresh
     * @return
     */
    public boolean getIsRefresh(String redisKeyIsRefresh);
}

RedisAccessLimiter是AccessLimiter接口的實(shí)現(xiàn)類

package com.hcfc.auto.util.limit;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
import java.util.concurrent.TimeUnit;
 
/**
 * @創(chuàng)建人 peng.wang
 * @描述 基于Redis的實(shí)現(xiàn)
 */
@Component
public class RedisAccessLimiter implements AccessLimiter {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisAccessLimiter.class);
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    @Override
    public boolean isLimited(String key, long times, long per, TimeUnit unit) {
        Long curTimes = redisTemplate.boundValueOps(key).increment(1);
        LOGGER.info("curTimes {}",curTimes);
        if(curTimes > times) {
            LOGGER.debug("超頻訪問:[{}]",key);
            return true;
        } else {
            if(curTimes == 1) {
                LOGGER.info(" set expire ");
                redisTemplate.boundValueOps(key).expire(per, unit);
                return false;
            } else {
                return false;
            }
        }
    }
 
    @Override
    public void refreshLimited(String key) {
        redisTemplate.delete(key);
    }
 
    @Override
    public boolean isStatus(String redisKey) {
        try {
            return (boolean)redisTemplate.opsForValue().get(redisKey+"IsOn");
        }catch (Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKey);
            return false;
        }
    }
 
    @Override
    public long getTimes(String redisKeyTimes) {
        try {
            return (long)redisTemplate.opsForValue().get(redisKeyTimes+"Times");
        }catch (Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyTimes);
            return 0;
        }
    }
 
    @Override
    public long getPer(String redisKeyPer) {
        try {
            return (long)redisTemplate.opsForValue().get(redisKeyPer+"Per");
        }catch (Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyPer);
            return 0;
        }
    }
 
    @Override
    public TimeUnit getUnit(String redisKeyUnit) {
        try {
            return (TimeUnit) redisTemplate.opsForValue().get(redisKeyUnit+"Unit");
        }catch (Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyUnit);
            return TimeUnit.SECONDS;
        }
    }
 
    @Override
    public boolean getIsRefresh(String redisKeyIsRefresh) {
        try {
            return (boolean)redisTemplate.opsForValue().get(redisKeyIsRefresh+"IsRefresh");
        }catch (Exception e){
            LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyIsRefresh);
            return false;
        }
    }
}

Limit標(biāo)簽接口,實(shí)現(xiàn)注解方式

package com.hcfc.auto.util.limit;
import java.lang.annotation.*;
/**
 * @創(chuàng)建人 peng.wang
 * @描述
 */
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limit {}

LimitAspect 切面的實(shí)現(xiàn),實(shí)現(xiàn)接口屏蔽和限流的邏輯

package com.hcfc.auto.util.limit;
import com.hcfc.auto.vo.response.ResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMApping;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
 
/**
 * @創(chuàng)建人 peng.wang
 * @創(chuàng)建時(shí)間 2019/10/11
 * @描述
 */
@Slf4j
@Aspect
@Component
public class LimitAspect {
    private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
 
    @Autowired
    private AccessLimiter limiter;
 
    @Autowired
    GenerateRedisKey generateRedisKey;
 
    @Pointcut("@annotation(com.hcfc.auto.util.limit.Limit)")
    public void limitPointcut() {}
 
    @Around("limitPointcut()")
    public Object doArround(ProceedingJoinPoint joinPoint) throws Throwable {
        String redisKey = generateRedisKey.getMethodUrlConvertRedisKey(joinPoint);
        long per = limiter.getPer(redisKey);
        long times = limiter.getTimes(redisKey);
        TimeUnit unit = limiter.getUnit(redisKey);
        boolean isRefresh =limiter.getIsRefresh(redisKey);
        boolean methodLimitStatus = limiter.isStatus(redisKey);
        String bindingKey = genBindingKey(joinPoint);
        if (methodLimitStatus) {
            logger.info("method is closed, key: ", bindingKey);
            return ResponseDto.fail("40007", "method is closed, key:"+bindingKey);
            //throw new OverLimitException("method is closed, key: "+bindingKey);
        }
        if(bindingKey !=null){
            boolean isLimited = limiter.isLimited(bindingKey, times, per, unit);
            if(isLimited){
                logger.info("limit takes effect: {}", bindingKey);
                return ResponseDto.fail("40006", "access over limit, key: "+bindingKey);
                //throw new OverLimitException("access over limit, key: "+bindingKey);
            }
        }
        Object result = null;
        result = joinPoint.proceed();
        if(bindingKey!=null && isRefresh) {
            limiter.refreshLimited(bindingKey);
            logger.info("limit refreshed: {}", bindingKey);
        }
        return result;
    }
 
    private String genBindingKey(ProceedingJoinPoint joinPoint){
        try{
            Method m = ((MethodSignature) joinPoint.getSignature()).getMethod();
            return joinPoint.getTarget().getClass().getName() + "." + m.getName();
        }catch (Throwable e){
            return null;
        }
    }
}

還有一個(gè)不重要的RedisKey實(shí)現(xiàn)類GenerateRedisKey和一個(gè)錯(cuò)誤封裝類,目前沒有使用到,使用項(xiàng)目中其他的錯(cuò)誤封裝類了。

GenerateRedisKey

package com.hcfc.auto.util.limit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method;
 
/**
 * @創(chuàng)建人 peng.wang
 * @描述
 */
@Component
public class GenerateRedisKey {
    public String getMethodUrlConvertRedisKey(ProceedingJoinPoint joinPoint){
        StringBuilder redisKey =new StringBuilder("");
        Method m = ((MethodSignature)joinPoint.getSignature()).getMethod();
        RequestMapping methodAnnotation = m.getAnnotation(RequestMapping.class);
        if (methodAnnotation != null) {
            String[] methodValue = methodAnnotation.value();
            String dscUrl = diagonalLineToCamel(methodValue[0]);
            return redisKey.append("RSK:").append("interfaceIsOpen:").append(dscUrl).toString();
        }
        return redisKey.toString();
    }
    private String diagonalLineToCamel(String param){
        char UNDERLINE='/';
        if (param==null||"".equals(param.trim())){
            return "";
        }
        int len=param.length();
        StringBuilder sb=new StringBuilder(len);
        for (int i = 1; i < len; i++) {
            char c=param.charAt(i);
            if (c==UNDERLINE){
                if (++i<len){
                    sb.append(Character.toUpperCase(param.charAt(i)));
                }
            }else{
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

總結(jié)

關(guān)鍵的代碼也就這幾行,訪問之前,對這個(gè)key值加一的操作,判斷是否超過限制,如果等于一這個(gè)key加一之后的值為一,說明之前不存在,則設(shè)置這個(gè)key,放在Redis數(shù)據(jù)庫中。

其實(shí)有更成熟的方案就是谷歌的Guava,領(lǐng)導(dǎo)說現(xiàn)在是咱們是分布式,不支持,還是用Redis實(shí)現(xiàn)吧,目前就這樣實(shí)現(xiàn)了。其實(shí)我是新來的,好多東西還不太明白,很多決定都是上面決定的,我只是盡力實(shí)現(xiàn)罷了。不足之處,請多多指教!

作者:Ingram--MSN

來源:
blog.csdn.net/u010843114/article/details/102695570

分享到:
標(biāo)簽:Redis
用戶無頭像

網(wǎng)友整理

注冊時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定