日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

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

需求

線上出現的問題是,一些非核心的查詢數據業務,在請求超時或者錯誤的時候,用戶會越查詢,導致數據庫cup飆升,拖垮核心的業務。

領導讓我做三件事,一是把這些接口做一個限流,這些限流參數是可配的,第二是這些接口可以設置開關,當發現問題時,可以手動關閉這些接口,不至于數據庫壓力過大影響核心業務的服務。第三是做接口的熔斷,熔斷設置可以配置。

經過確定,前兩個實現用redis來實現,第三個因為熔斷討論覺得比較復雜,決定采用我提出的用Hystrix,目前項目不能熱加載生效配置中心的最新的配置,所以后期推薦使用Archaius,這些網上查到的,具體為啥不選其他的,原因就是其他的比較復雜,上手感覺這個最快。

這篇文章說實現,其他問題不涉及,請多多指教。

思路

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

代碼

 

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

 

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

package com.hcfc.auto.util.limit;
import JAVA.util.concurrent.TimeUnit;
/**
 * @創建人 peng.wang
 * @描述 訪問限制器
 */
public interface AccessLimiter {
    /**
     * 檢查指定的key是否收到訪問限制
     * @param key   限制接口的標識
     * @param times 訪問次數
     * @param per   一段時間
     * @param unit  時間單位
     * @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);
 
    /**
     * 接口限流時間段
     * @param redisKeyPer
     * @return
     */
    public long getPer(String redisKeyPer);
 
    /**
     * 接口的限流時間單位
     * @param redisKeyUnit
     * @return
     */
    public TimeUnit getUnit(String redisKeyUnit);
 
    /**
     * 是否刪除接口限流
     * @param redisKeyIsRefresh
     * @return
     */
    public boolean getIsRefresh(String redisKeyIsRefresh);
}

RedisAccessLimiter是AccessLimiter接口的實現類

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;
 
/**
 * @創建人 peng.wang
 * @描述 基于Redis的實現
 */
@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標簽接口,實現注解方式

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

LimitAspect 切面的實現,實現接口屏蔽和限流的邏輯

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;
 
/**
 * @創建人 peng.wang
 * @創建時間 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;
        }
    }
}

還有一個不重要的RedisKey實現類GenerateRedisKey和一個錯誤封裝類,目前沒有使用到,使用項目中其他的錯誤封裝類了。

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;
 
/**
 * @創建人 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();
    }
}

總結

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

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

作者:Ingram--MSN

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

分享到:
標簽:Redis
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

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

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定