-
Notifications
You must be signed in to change notification settings - Fork 944
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #95 from linzhihan/linzhihan
编程式限流
- Loading branch information
Showing
12 changed files
with
506 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
...-common/src/main/java/com/abin/mallchat/common/common/domain/dto/FrequencyControlDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.abin.mallchat.common.common.domain.dto; | ||
|
||
import lombok.*; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
@Data | ||
@ToString | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
/** 限流策略定义 | ||
* @author linzhihan | ||
* @date 2023/07/03 | ||
* | ||
*/ | ||
public class FrequencyControlDTO { | ||
/** | ||
* 代表频控的Key 如果target为Key的话 这里要传值用于构建redis的Key target为Ip或者UID的话会从上下文取值 Key字段无需传值 | ||
*/ | ||
private String key; | ||
/** | ||
* 频控时间范围,默认单位秒 | ||
* | ||
* @return 时间范围 | ||
*/ | ||
private Integer time; | ||
|
||
/** | ||
* 频控时间单位,默认秒 | ||
* | ||
* @return 单位 | ||
*/ | ||
private TimeUnit unit; | ||
|
||
/** | ||
* 单位时间内最大访问次数 | ||
* | ||
* @return 次数 | ||
*/ | ||
private Integer count; | ||
} |
39 changes: 39 additions & 0 deletions
39
...on/src/main/java/com/abin/mallchat/common/common/exception/FrequencyControlException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.abin.mallchat.common.common.exception; | ||
|
||
import lombok.Data; | ||
|
||
/** | ||
* 自定义限流异常 | ||
* | ||
* @author linzhihan | ||
* @date 2023/07/034 | ||
*/ | ||
@Data | ||
public class FrequencyControlException extends RuntimeException { | ||
private static final long serialVersionUID = 1L; | ||
|
||
/** | ||
* 错误码 | ||
*/ | ||
protected Integer errorCode; | ||
|
||
/** | ||
* 错误信息 | ||
*/ | ||
protected String errorMsg; | ||
|
||
public FrequencyControlException() { | ||
super(); | ||
} | ||
|
||
public FrequencyControlException(String errorMsg) { | ||
super(errorMsg); | ||
this.errorMsg = errorMsg; | ||
} | ||
|
||
public FrequencyControlException(ErrorEnum error) { | ||
super(error.getErrorMsg()); | ||
this.errorCode = error.getErrorCode(); | ||
this.errorMsg = error.getErrorMsg(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...abin/mallchat/common/common/service/frequencycontrol/AbstractFrequencyControlService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package com.abin.mallchat.common.common.service.frequencycontrol; | ||
|
||
import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; | ||
import com.abin.mallchat.common.common.exception.CommonErrorEnum; | ||
import com.abin.mallchat.common.common.exception.FrequencyControlException; | ||
import com.abin.mallchat.common.common.utils.AssertUtil; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.commons.lang3.ObjectUtils; | ||
|
||
import javax.annotation.PostConstruct; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* 抽象类频控服务 其他类如果要实现限流服务 直接注入使用通用限流类 | ||
* 后期会通过继承此类实现令牌桶等算法 | ||
* | ||
* @author linzhihan | ||
* @date 2023/07/03 | ||
* @see TotalCountWithInFixTimeFrequencyController 通用限流类 | ||
*/ | ||
@Slf4j | ||
public abstract class AbstractFrequencyControlService<K extends FrequencyControlDTO> { | ||
|
||
@PostConstruct | ||
protected void registerMyselfToFactory() { | ||
FrequencyControlStrategyFactory.registerFrequencyController(getStrategyName(), this); | ||
} | ||
|
||
/** | ||
* @param frequencyControlMap 定义的注解频控 Map中的Key-对应redis的单个频控的Key Map中的Value-对应redis的单个频控的Key限制的Value | ||
* @param supplier 函数式入参-代表每个频控方法执行的不同的业务逻辑 | ||
* @return 业务方法执行的返回值 | ||
* @throws Throwable | ||
*/ | ||
private <T> T executeWithFrequencyControlMap(Map<String, K> frequencyControlMap, SupplierThrowWithoutParam<T> supplier) throws Throwable { | ||
if (reachRateLimit(frequencyControlMap)) { | ||
throw new FrequencyControlException(CommonErrorEnum.FREQUENCY_LIMIT); | ||
} | ||
try { | ||
return supplier.get(); | ||
} finally { | ||
//不管成功还是失败,都增加次数 | ||
addFrequencyControlStatisticsCount(frequencyControlMap); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* 多限流策略的编程式调用方法 无参的调用方法 | ||
* | ||
* @param frequencyControlList 频控列表 包含每一个频率控制的定义以及顺序 | ||
* @param supplier 函数式入参-代表每个频控方法执行的不同的业务逻辑 | ||
* @return 业务方法执行的返回值 | ||
* @throws Throwable 被限流或者限流策略定义错误 | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
public <T> T executeWithFrequencyControlList(List<K> frequencyControlList, SupplierThrowWithoutParam<T> supplier) throws Throwable { | ||
boolean existsFrequencyControlHasNullKey = frequencyControlList.stream().anyMatch(frequencyControl -> ObjectUtils.isEmpty(frequencyControl.getKey())); | ||
AssertUtil.isFalse(existsFrequencyControlHasNullKey, "限流策略的Key字段不允许出现空值"); | ||
Map<String, FrequencyControlDTO> frequencyControlDTOMap = frequencyControlList.stream().collect(Collectors.groupingBy(FrequencyControlDTO::getKey, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0)))); | ||
return executeWithFrequencyControlMap((Map<String, K>) frequencyControlDTOMap, supplier); | ||
} | ||
|
||
/** | ||
* 单限流策略的调用方法-编程式调用 | ||
* | ||
* @param frequencyControl 单个频控对象 | ||
* @param supplier 服务提供着 | ||
* @return 业务方法执行结果 | ||
* @throws Throwable | ||
*/ | ||
public <T> T executeWithFrequencyControl(K frequencyControl, SupplierThrowWithoutParam<T> supplier) throws Throwable { | ||
return executeWithFrequencyControlList(Collections.singletonList(frequencyControl), supplier); | ||
} | ||
|
||
|
||
@FunctionalInterface | ||
public interface SupplierThrowWithoutParam<T> { | ||
|
||
/** | ||
* Gets a result. | ||
* | ||
* @return a result | ||
*/ | ||
T get() throws Throwable; | ||
} | ||
|
||
/** | ||
* 是否达到限流阈值 子类实现 每个子类都可以自定义自己的限流逻辑判断 | ||
* | ||
* @param frequencyControlMap 定义的注解频控 Map中的Key-对应redis的单个频控的Key Map中的Value-对应redis的单个频控的Key限制的Value | ||
* @return true-方法被限流 false-方法没有被限流 | ||
*/ | ||
protected abstract boolean reachRateLimit(Map<String, K> frequencyControlMap); | ||
|
||
/** | ||
* 增加限流统计次数 子类实现 每个子类都可以自定义自己的限流统计信息增加的逻辑 | ||
* | ||
* @param frequencyControlMap 定义的注解频控 Map中的Key-对应redis的单个频控的Key Map中的Value-对应redis的单个频控的Key限制的Value | ||
*/ | ||
protected abstract void addFrequencyControlStatisticsCount(Map<String, K> frequencyControlMap); | ||
|
||
/** | ||
* 获取策略名称 | ||
* | ||
* @return 策略名称 | ||
*/ | ||
protected abstract String getStrategyName(); | ||
|
||
} |
51 changes: 51 additions & 0 deletions
51
...abin/mallchat/common/common/service/frequencycontrol/FrequencyControlStrategyFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.abin.mallchat.common.common.service.frequencycontrol; | ||
|
||
import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; | ||
|
||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
/** | ||
* 限流策略工厂 | ||
* | ||
* @author linzhihan | ||
* @date 2023/07/03 | ||
*/ | ||
public class FrequencyControlStrategyFactory { | ||
/** | ||
* 指定时间内总次数限流 | ||
*/ | ||
public static final String TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER = "TotalCountWithInFixTime"; | ||
/** | ||
* 限流策略集合 | ||
*/ | ||
static Map<String, AbstractFrequencyControlService<?>> frequencyControlServiceStrategyMap = new ConcurrentHashMap<>(8); | ||
|
||
/** | ||
* 将策略类放入工厂 | ||
* | ||
* @param strategyName 策略名称 | ||
* @param abstractFrequencyControlService 策略类 | ||
*/ | ||
public static <K extends FrequencyControlDTO> void registerFrequencyController(String strategyName, AbstractFrequencyControlService<K> abstractFrequencyControlService) { | ||
frequencyControlServiceStrategyMap.put(strategyName, abstractFrequencyControlService); | ||
} | ||
|
||
/** | ||
* 根据名称获取策略类 | ||
* | ||
* @param strategyName 策略名称 | ||
* @return 对应的限流策略类 | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
public static <K extends FrequencyControlDTO> AbstractFrequencyControlService<K> getFrequencyControllerByName(String strategyName) { | ||
return (AbstractFrequencyControlService<K>) frequencyControlServiceStrategyMap.get(strategyName); | ||
} | ||
|
||
/** | ||
* 构造器私有 | ||
*/ | ||
private FrequencyControlStrategyFactory() { | ||
|
||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
...n/java/com/abin/mallchat/common/common/service/frequencycontrol/FrequencyControlUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.abin.mallchat.common.common.service.frequencycontrol; | ||
|
||
import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; | ||
import com.abin.mallchat.common.common.utils.AssertUtil; | ||
import org.apache.commons.lang3.ObjectUtils; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* 限流工具类 提供编程式的限流调用方法 | ||
* | ||
* @author linzhihan | ||
* @date 2023/07/03 | ||
*/ | ||
public class FrequencyControlUtil { | ||
|
||
/** | ||
* 单限流策略的调用方法-编程式调用 | ||
* | ||
* @param strategyName 策略名称 | ||
* @param frequencyControl 单个频控对象 | ||
* @param supplier 服务提供着 | ||
* @return 业务方法执行结果 | ||
* @throws Throwable | ||
*/ | ||
public static <T, K extends FrequencyControlDTO> T executeWithFrequencyControl(String strategyName, K frequencyControl, AbstractFrequencyControlService.SupplierThrowWithoutParam<T> supplier) throws Throwable { | ||
AbstractFrequencyControlService<K> frequencyController = FrequencyControlStrategyFactory.getFrequencyControllerByName(strategyName); | ||
return frequencyController.executeWithFrequencyControl(frequencyControl, supplier); | ||
} | ||
|
||
|
||
/** | ||
* 多限流策略的编程式调用方法调用方法 | ||
* | ||
* @param strategyName 策略名称 | ||
* @param frequencyControlList 频控列表 包含每一个频率控制的定义以及顺序 | ||
* @param supplier 函数式入参-代表每个频控方法执行的不同的业务逻辑 | ||
* @return 业务方法执行的返回值 | ||
* @throws Throwable 被限流或者限流策略定义错误 | ||
*/ | ||
public static <T, K extends FrequencyControlDTO> T executeWithFrequencyControlList(String strategyName, List<K> frequencyControlList, AbstractFrequencyControlService.SupplierThrowWithoutParam<T> supplier) throws Throwable { | ||
boolean existsFrequencyControlHasNullKey = frequencyControlList.stream().anyMatch(frequencyControl -> ObjectUtils.isEmpty(frequencyControl.getKey())); | ||
AssertUtil.isFalse(existsFrequencyControlHasNullKey, "限流策略的Key字段不允许出现空值"); | ||
AbstractFrequencyControlService<K> frequencyController = FrequencyControlStrategyFactory.getFrequencyControllerByName(strategyName); | ||
return frequencyController.executeWithFrequencyControlList(frequencyControlList, supplier); | ||
} | ||
|
||
/** | ||
* 构造器私有 | ||
*/ | ||
private FrequencyControlUtil() { | ||
|
||
} | ||
|
||
} |
Oops, something went wrong.