redis限制請求頻率及資源隔離

qixiaobo發表於2018-01-05

title: redis限制請求頻率及資源隔離 tags:

  • redis
  • dataSource
  • aop
  • limiter
  • Spring categories: redis date: 2017-08-04 18:18:53

背景

由於匯入及匯出服務的使用,可能過多佔用業務系統的請求。

為此在db層次做了切分(資源隔離)使用不同的db連線池。

同時針對匯入匯出服務增加請求頻率限制,避免佔用過多資源

解決方案

db連線資源分離

比較簡單的利用spring做dataSource路由

    /**
     * Created by qixiaobo on 2016/12/2.
     */
    public class DataSourceRouter extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return WxbStatic.getDataSourceRouting();
        }
    }

    import com.air.tqb.annoate.DataSource;
    import com.air.tqb.annoate.DataSourceType;
    import com.air.tqb.utils.WxbStatic;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
     
    /**
     * Created by qixiaobo on 2016/12/2.
     */
    @Aspect
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE + 2)
    public class DataSourceRoutingAspect {
     
        private static final Log logger = LogFactory.getLog(DataSourceRoutingAspect.class);
     
     
        @Around("execution(public * com.air.tqb.service..*.*(..)) && @annotation(com.air.tqb.annoate.DataSource) && @annotation(dataSource)")
        public Object setDataSource(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {
            DataSourceType dataSourceType = dataSource.value();
            try {
                WxbStatic.setDataSourceRouting(dataSourceType.getValue());
                if (logger.isDebugEnabled()) {
                    logger.debug("DataSourceType[" + dataSourceType.getValue() + "] set.");
                }
                return joinPoint.proceed();
            } finally {
                WxbStatic.clearDataSourceRouting();
                if (logger.isDebugEnabled()) {
                    logger.debug("DataSourceType[" + dataSourceType.getValue() + "] remove.");
                }
            }
     
        }
     
     
        @Around("execution(public * com.air.tqb.service.report..*.*(..))")
        public Object setDataSource(ProceedingJoinPoint joinPoint) throws Throwable {
            DataSourceType dataSourceType = DataSourceType.SLOW;
            try {
                WxbStatic.setDataSourceRouting(dataSourceType.getValue());
                if (logger.isDebugEnabled()) {
                    logger.debug("report DataSourceType[" + dataSourceType.getValue() + "] set.");
                }
                return joinPoint.proceed();
            } finally {
                WxbStatic.clearDataSourceRouting();
                if (logger.isDebugEnabled()) {
                    logger.debug("report DataSourceType[" + dataSourceType.getValue() + "] remove.");
                }
            }
     
        }
    }
複製程式碼

這樣我們可以吧report和正常請求區分開來。使用不同的db連線

    /**
     * Created by qixiaobo on 2016/12/2.
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface DataSource {
        DataSourceType value() default DataSourceType.NORMAL;
    }

    package com.air.tqb.annoate;
     
    public enum DataSourceType {
     
        NORMAL("normal"), SLOW("slow"), REPORT("report");
     
        DataSourceType(String value) {
            this.value = value;
        }
     
        private String value;
     
        public String getValue() {
            return value;
        }
    }
複製程式碼

在不同的方法上加上指定的db標籤可以完成db的選擇。從而完成資源隔離。

方法呼叫限制

對於指定的佔用資源方法採用開發按照key做配置,對於指定次數的限制

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface OperationLimit {
        int  value() default 5;
        String key() default "import";
    }
複製程式碼
    package com.air.tqb.aop;
    
    import com.air.tqb.Exception.LimitOperationException;
    import com.air.tqb.annoate.OperationLimit;
    import com.google.common.base.Throwables;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.stereotype.Component;
    
    import java.util.concurrent.TimeUnit;
    
    @Aspect
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE + 4)
    public class OperationLimitAspect {
        @Autowired
        @Qualifier(value = "stringRedisTemplate")
        private StringRedisTemplate template;
    
        private final static String LIMIT_KEY_PREFIX = "limit:";
    
        @Around("@annotation(com.air.tqb.annoate.OperationLimit) && @annotation(operationLimit)")
        public Object operationLimit(ProceedingJoinPoint joinPoint, OperationLimit operationLimit) {
            ValueOperations<String, String> stringStringValueOperations = template.opsForValue();
            final String key = getLimitKey(operationLimit);
            Long incremented = stringStringValueOperations.increment(key, 1);
            //暫時不考慮事務了,簡單策略過期即可
            stringStringValueOperations.getOperations().expire(key, 180, TimeUnit.SECONDS);
            boolean limitReached = checkLimit(incremented, operationLimit);
            if (limitReached) {
                stringStringValueOperations.increment(key, -1);
                throw new LimitOperationException("當前操作" + operationLimit.key() + "已經超過最大運算元" + operationLimit.value() + "限制,請稍後再試!");
            } else {
                try {
                    return joinPoint.proceed();
                } catch (Throwable throwable) {
                    return Throwables.propagate(throwable);
                } finally {
                    stringStringValueOperations.increment(key, -1);
                }
            }
    
        }
    
        private boolean checkLimit(Long incremented, OperationLimit operationLimit) {
            if (operationLimit.value() < 0 || incremented <= operationLimit.value()) {
                return false;
            }
            return true;
        }
    
        private String getLimitKey(OperationLimit operationLimit) {
            return LIMIT_KEY_PREFIX + operationLimit.key();
        }
    
    }
複製程式碼

215603_QtH7_871390.png

這個資料是更新了的

對於返回為空可以參考

    public Long incrBy(byte[] key, long value) {
    		try {
    			if (isPipelined()) {
    				pipeline(new JedisResult(pipeline.incrBy(key, value)));
    				return null;
    			}
    			if (isQueueing()) {
    				transaction(new JedisResult(transaction.incrBy(key, value)));
    				return null;
    			}
    			return jedis.incrBy(key, value);
    		} catch (Exception ex) {
    			throw convertJedisAccessException(ex);
    		}
    	}
複製程式碼

相關文章