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();
}
}
複製程式碼
這個資料是更新了的
對於返回為空可以參考
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);
}
}
複製程式碼