经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
redis + AOP + 自定义注解实现接口限流
来源:cnblogs  作者:古渡蓝按  时间:2024/1/5 9:12:12  对本文有异议

限流介绍

限流(rate limiting)

? 是指在一定时间内,对某些资源的访问次数进行限制,以避免资源被滥用或过度消耗。限流可以防止服务器崩溃、保证用户体验、提高系统可用性。

限流的方法有很多种,常见的有以下几种:

  • 漏桶算法:

    ? 漏桶算法通过一个固定大小的漏桶来模拟流量,当流量进入漏桶时,会以恒定的速率从漏桶中流出。如果流量超过漏桶的容量,则会被丢弃。

  • 令牌桶算法:

    ? 令牌桶算法通过一个固定大小的令牌桶来模拟流量,当流量进入令牌桶时,会从令牌桶中取出一个令牌。如果令牌桶中没有令牌,则会拒绝该流量。

  • 滑动窗口算法:

    ? 滑动窗口算法通过一个固定大小的滑动窗口来模拟流量,当流量进入滑动窗口时,会统计窗口内流量的数量。如果窗口内流量的数量超过了一定的阈值,则会拒绝该流量。

限流可以应用在很多场景,例如:

  • 防止服务器崩溃:当服务器的请求量过大时,可以通过限流来防止服务器崩溃。

  • 保证用户体验:当用户请求某个资源的频率过高时,可以通过限流来降低用户的等待时间。

  • 提高系统可用性:当系统的某些资源被滥用或过度消耗时,可以通过限流来提高系统的可用性。

?

? 限流是一个非常重要的技术,它可以帮助我们提高系统的稳定性和可用性。在实际开发中,我们可以根据不同的场景选择合适的限流算法。

我们定义的注解使用到技术:redis,redisson,AOP,自定义注解等

依赖

用到的部分依赖,这里没有指定版本,可根据市场上的版本进行配置

  1. <!--redisson-->
  2. <dependency>
  3. <groupId>org.redisson</groupId>
  4. <artifactId>redisson-spring-boot-starter</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.baomidou</groupId>
  8. <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
  9. </dependency>
  10. <!-- Spring框架基本的核心工具 -->
  11. <dependency>
  12. <groupId>org.springframework</groupId>
  13. <artifactId>spring-context-support</artifactId>
  14. </dependency>
  15. <!-- SpringWeb模块 -->
  16. <dependency>
  17. <groupId>org.springframework</groupId>
  18. <artifactId>spring-web</artifactId>
  19. </dependency>
  20. <!-- 自定义验证注解 -->
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-validation</artifactId>
  24. </dependency>
  25. <!-- aop -->
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-aop</artifactId>
  29. </dependency>
  30. <!--常用工具类 -->
  31. <dependency>
  32. <groupId>org.apache.commons</groupId>
  33. <artifactId>commons-lang3</artifactId>
  34. </dependency>
  35. <!-- servlet包 -->
  36. <dependency>
  37. <groupId>jakarta.servlet</groupId>
  38. <artifactId>jakarta.servlet-api</artifactId>
  39. </dependency>
  40. <!-- hutool 工具类 -->
  41. <dependency>
  42. <groupId>cn.hutool</groupId>
  43. <artifactId>hutool-core</artifactId>
  44. </dependency>
  45. <dependency>
  46. <groupId>cn.hutool</groupId>
  47. <artifactId>hutool-http</artifactId>
  48. </dependency>
  49. <dependency>
  50. <groupId>cn.hutool</groupId>
  51. <artifactId>hutool-extra</artifactId>
  52. </dependency>
  53. <!-- lombok 工具类 -->
  54. <dependency>
  55. <groupId>org.projectlombok</groupId>
  56. <artifactId>lombok</artifactId>
  57. </dependency>
  58. <!-- 自动生成YML配置关联JSON文件 -->
  59. <dependency>
  60. <groupId>org.springframework.boot</groupId>
  61. <artifactId>spring-boot-configuration-processor</artifactId>
  62. </dependency>
  63. <!-- 版本升级 -->
  64. <dependency>
  65. <groupId>org.springframework.boot</groupId>
  66. <artifactId>spring-boot-properties-migrator</artifactId>
  67. <scope>runtime</scope>
  68. </dependency>
  69. <!-- 代码生产工具 -->
  70. <dependency>
  71. <groupId>io.github.linpeilie</groupId>
  72. <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
  73. </dependency>
  74. <!-- 离线IP地址定位库 -->
  75. <dependency>
  76. <groupId>org.lionsoul</groupId>
  77. <artifactId>ip2region</artifactId>
  78. </dependency>

1,定义限流类型

这里定义限流枚举类:LimitType

  1. public enum LimitType {
  2. /**
  3. * 默认策略全局限流
  4. */
  5. DEFAULT,
  6. /**
  7. * 根据请求者IP进行限流
  8. */
  9. IP,
  10. /**
  11. * 实例限流(集群多后端实例)
  12. */
  13. CLUSTER
  14. }

2,定义注解 RateLimiter

定义注解,在后续的代码中使用进行限流

  1. import java.lang.annotation.*;
  2. /**
  3. * 限流注解
  4. *
  5. * @author Lion Li
  6. */
  7. @Target(ElementType.METHOD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Documented
  10. public @interface RateLimiter {
  11. /**
  12. * 限流key,支持使用Spring el表达式来动态获取方法上的参数值
  13. * 格式类似于 #code.id #{#code}
  14. */
  15. String key() default "";
  16. /**
  17. * 限流时间,单位秒
  18. */
  19. int time() default 60;
  20. /**
  21. * 限流次数
  22. */
  23. int count() default 100;
  24. /**
  25. * 限流类型
  26. */
  27. LimitType limitType() default LimitType.DEFAULT;
  28. /**
  29. * 提示消息 支持国际化 格式为 {code}
  30. */
  31. String message() default "{rate.limiter.message}";
  32. }

redis 工具类

这里提供一下第 3 步需要的redis 工具类,可以根据自己的需求进行部分方法进行复制。

  1. @NoArgsConstructor(access = AccessLevel.PRIVATE)
  2. @SuppressWarnings(value = {"unchecked", "rawtypes"})
  3. public class RedisUtils {
  4. private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
  5. /**
  6. * 限流
  7. *
  8. * @param key 限流key
  9. * @param rateType 限流类型
  10. * @param rate 速率
  11. * @param rateInterval 速率间隔
  12. * @return -1 表示失败
  13. */
  14. public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
  15. RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
  16. rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
  17. if (rateLimiter.tryAcquire()) {
  18. return rateLimiter.availablePermits();
  19. } else {
  20. return -1L;
  21. }
  22. }
  23. /**
  24. * 获取客户端实例
  25. */
  26. public static RedissonClient getClient() {
  27. return CLIENT;
  28. }
  29. /**
  30. * 发布通道消息
  31. *
  32. * @param channelKey 通道key
  33. * @param msg 发送数据
  34. * @param consumer 自定义处理
  35. */
  36. public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
  37. RTopic topic = CLIENT.getTopic(channelKey);
  38. topic.publish(msg);
  39. consumer.accept(msg);
  40. }
  41. public static <T> void publish(String channelKey, T msg) {
  42. RTopic topic = CLIENT.getTopic(channelKey);
  43. topic.publish(msg);
  44. }
  45. /**
  46. * 订阅通道接收消息
  47. *
  48. * @param channelKey 通道key
  49. * @param clazz 消息类型
  50. * @param consumer 自定义处理
  51. */
  52. public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
  53. RTopic topic = CLIENT.getTopic(channelKey);
  54. topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
  55. }
  56. /**
  57. * 缓存基本的对象,Integer、String、实体类等
  58. *
  59. * @param key 缓存的键值
  60. * @param value 缓存的值
  61. */
  62. public static <T> void setCacheObject(final String key, final T value) {
  63. setCacheObject(key, value, false);
  64. }
  65. /**
  66. * 缓存基本的对象,保留当前对象 TTL 有效期
  67. *
  68. * @param key 缓存的键值
  69. * @param value 缓存的值
  70. * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
  71. * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
  72. */
  73. public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
  74. RBucket<T> bucket = CLIENT.getBucket(key);
  75. if (isSaveTtl) {
  76. try {
  77. bucket.setAndKeepTTL(value);
  78. } catch (Exception e) {
  79. long timeToLive = bucket.remainTimeToLive();
  80. setCacheObject(key, value, Duration.ofMillis(timeToLive));
  81. }
  82. } else {
  83. bucket.set(value);
  84. }
  85. }
  86. /**
  87. * 缓存基本的对象,Integer、String、实体类等
  88. *
  89. * @param key 缓存的键值
  90. * @param value 缓存的值
  91. * @param duration 时间
  92. */
  93. public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
  94. RBatch batch = CLIENT.createBatch();
  95. RBucketAsync<T> bucket = batch.getBucket(key);
  96. bucket.setAsync(value);
  97. bucket.expireAsync(duration);
  98. batch.execute();
  99. }
  100. /**
  101. * 如果不存在则设置 并返回 true 如果存在则返回 false
  102. *
  103. * @param key 缓存的键值
  104. * @param value 缓存的值
  105. * @return set成功或失败
  106. */
  107. public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
  108. RBucket<T> bucket = CLIENT.getBucket(key);
  109. return bucket.setIfAbsent(value, duration);
  110. }
  111. /**
  112. * 如果存在则设置 并返回 true 如果存在则返回 false
  113. *
  114. * @param key 缓存的键值
  115. * @param value 缓存的值
  116. * @return set成功或失败
  117. */
  118. public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
  119. RBucket<T> bucket = CLIENT.getBucket(key);
  120. return bucket.setIfExists(value, duration);
  121. }
  122. /**
  123. * 注册对象监听器
  124. * <p>
  125. * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
  126. *
  127. * @param key 缓存的键值
  128. * @param listener 监听器配置
  129. */
  130. public static <T> void addObjectListener(final String key, final ObjectListener listener) {
  131. RBucket<T> result = CLIENT.getBucket(key);
  132. result.addListener(listener);
  133. }
  134. /**
  135. * 设置有效时间
  136. *
  137. * @param key Redis键
  138. * @param timeout 超时时间
  139. * @return true=设置成功;false=设置失败
  140. */
  141. public static boolean expire(final String key, final long timeout) {
  142. return expire(key, Duration.ofSeconds(timeout));
  143. }
  144. /**
  145. * 设置有效时间
  146. *
  147. * @param key Redis键
  148. * @param duration 超时时间
  149. * @return true=设置成功;false=设置失败
  150. */
  151. public static boolean expire(final String key, final Duration duration) {
  152. RBucket rBucket = CLIENT.getBucket(key);
  153. return rBucket.expire(duration);
  154. }
  155. /**
  156. * 获得缓存的基本对象。
  157. *
  158. * @param key 缓存键值
  159. * @return 缓存键值对应的数据
  160. */
  161. public static <T> T getCacheObject(final String key) {
  162. RBucket<T> rBucket = CLIENT.getBucket(key);
  163. return rBucket.get();
  164. }
  165. /**
  166. * 获得key剩余存活时间
  167. *
  168. * @param key 缓存键值
  169. * @return 剩余存活时间
  170. */
  171. public static <T> long getTimeToLive(final String key) {
  172. RBucket<T> rBucket = CLIENT.getBucket(key);
  173. return rBucket.remainTimeToLive();
  174. }
  175. /**
  176. * 删除单个对象
  177. *
  178. * @param key 缓存的键值
  179. */
  180. public static boolean deleteObject(final String key) {
  181. return CLIENT.getBucket(key).delete();
  182. }
  183. /**
  184. * 删除集合对象
  185. *
  186. * @param collection 多个对象
  187. */
  188. public static void deleteObject(final Collection collection) {
  189. RBatch batch = CLIENT.createBatch();
  190. collection.forEach(t -> {
  191. batch.getBucket(t.toString()).deleteAsync();
  192. });
  193. batch.execute();
  194. }
  195. /**
  196. * 检查缓存对象是否存在
  197. *
  198. * @param key 缓存的键值
  199. */
  200. public static boolean isExistsObject(final String key) {
  201. return CLIENT.getBucket(key).isExists();
  202. }
  203. /**
  204. * 缓存List数据
  205. *
  206. * @param key 缓存的键值
  207. * @param dataList 待缓存的List数据
  208. * @return 缓存的对象
  209. */
  210. public static <T> boolean setCacheList(final String key, final List<T> dataList) {
  211. RList<T> rList = CLIENT.getList(key);
  212. return rList.addAll(dataList);
  213. }
  214. /**
  215. * 追加缓存List数据
  216. *
  217. * @param key 缓存的键值
  218. * @param data 待缓存的数据
  219. * @return 缓存的对象
  220. */
  221. public static <T> boolean addCacheList(final String key, final T data) {
  222. RList<T> rList = CLIENT.getList(key);
  223. return rList.add(data);
  224. }
  225. /**
  226. * 注册List监听器
  227. * <p>
  228. * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
  229. *
  230. * @param key 缓存的键值
  231. * @param listener 监听器配置
  232. */
  233. public static <T> void addListListener(final String key, final ObjectListener listener) {
  234. RList<T> rList = CLIENT.getList(key);
  235. rList.addListener(listener);
  236. }
  237. /**
  238. * 获得缓存的list对象
  239. *
  240. * @param key 缓存的键值
  241. * @return 缓存键值对应的数据
  242. */
  243. public static <T> List<T> getCacheList(final String key) {
  244. RList<T> rList = CLIENT.getList(key);
  245. return rList.readAll();
  246. }
  247. /**
  248. * 获得缓存的list对象(范围)
  249. *
  250. * @param key 缓存的键值
  251. * @param form 起始下标
  252. * @param to 截止下标
  253. * @return 缓存键值对应的数据
  254. */
  255. public static <T> List<T> getCacheListRange(final String key, int form, int to) {
  256. RList<T> rList = CLIENT.getList(key);
  257. return rList.range(form, to);
  258. }
  259. /**
  260. * 缓存Set
  261. *
  262. * @param key 缓存键值
  263. * @param dataSet 缓存的数据
  264. * @return 缓存数据的对象
  265. */
  266. public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
  267. RSet<T> rSet = CLIENT.getSet(key);
  268. return rSet.addAll(dataSet);
  269. }
  270. /**
  271. * 追加缓存Set数据
  272. *
  273. * @param key 缓存的键值
  274. * @param data 待缓存的数据
  275. * @return 缓存的对象
  276. */
  277. public static <T> boolean addCacheSet(final String key, final T data) {
  278. RSet<T> rSet = CLIENT.getSet(key);
  279. return rSet.add(data);
  280. }
  281. /**
  282. * 注册Set监听器
  283. * <p>
  284. * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
  285. *
  286. * @param key 缓存的键值
  287. * @param listener 监听器配置
  288. */
  289. public static <T> void addSetListener(final String key, final ObjectListener listener) {
  290. RSet<T> rSet = CLIENT.getSet(key);
  291. rSet.addListener(listener);
  292. }
  293. /**
  294. * 获得缓存的set
  295. *
  296. * @param key 缓存的key
  297. * @return set对象
  298. */
  299. public static <T> Set<T> getCacheSet(final String key) {
  300. RSet<T> rSet = CLIENT.getSet(key);
  301. return rSet.readAll();
  302. }
  303. /**
  304. * 缓存Map
  305. *
  306. * @param key 缓存的键值
  307. * @param dataMap 缓存的数据
  308. */
  309. public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
  310. if (dataMap != null) {
  311. RMap<String, T> rMap = CLIENT.getMap(key);
  312. rMap.putAll(dataMap);
  313. }
  314. }
  315. /**
  316. * 注册Map监听器
  317. * <p>
  318. * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
  319. *
  320. * @param key 缓存的键值
  321. * @param listener 监听器配置
  322. */
  323. public static <T> void addMapListener(final String key, final ObjectListener listener) {
  324. RMap<String, T> rMap = CLIENT.getMap(key);
  325. rMap.addListener(listener);
  326. }
  327. /**
  328. * 获得缓存的Map
  329. *
  330. * @param key 缓存的键值
  331. * @return map对象
  332. */
  333. public static <T> Map<String, T> getCacheMap(final String key) {
  334. RMap<String, T> rMap = CLIENT.getMap(key);
  335. return rMap.getAll(rMap.keySet());
  336. }
  337. /**
  338. * 获得缓存Map的key列表
  339. *
  340. * @param key 缓存的键值
  341. * @return key列表
  342. */
  343. public static <T> Set<String> getCacheMapKeySet(final String key) {
  344. RMap<String, T> rMap = CLIENT.getMap(key);
  345. return rMap.keySet();
  346. }
  347. /**
  348. * 往Hash中存入数据
  349. *
  350. * @param key Redis键
  351. * @param hKey Hash键
  352. * @param value 值
  353. */
  354. public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
  355. RMap<String, T> rMap = CLIENT.getMap(key);
  356. rMap.put(hKey, value);
  357. }
  358. /**
  359. * 获取Hash中的数据
  360. *
  361. * @param key Redis键
  362. * @param hKey Hash键
  363. * @return Hash中的对象
  364. */
  365. public static <T> T getCacheMapValue(final String key, final String hKey) {
  366. RMap<String, T> rMap = CLIENT.getMap(key);
  367. return rMap.get(hKey);
  368. }
  369. /**
  370. * 删除Hash中的数据
  371. *
  372. * @param key Redis键
  373. * @param hKey Hash键
  374. * @return Hash中的对象
  375. */
  376. public static <T> T delCacheMapValue(final String key, final String hKey) {
  377. RMap<String, T> rMap = CLIENT.getMap(key);
  378. return rMap.remove(hKey);
  379. }
  380. /**
  381. * 删除Hash中的数据
  382. *
  383. * @param key Redis键
  384. * @param hKeys Hash键
  385. */
  386. public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
  387. RBatch batch = CLIENT.createBatch();
  388. RMapAsync<String, T> rMap = batch.getMap(key);
  389. for (String hKey : hKeys) {
  390. rMap.removeAsync(hKey);
  391. }
  392. batch.execute();
  393. }
  394. /**
  395. * 获取多个Hash中的数据
  396. *
  397. * @param key Redis键
  398. * @param hKeys Hash键集合
  399. * @return Hash对象集合
  400. */
  401. public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
  402. RMap<K, V> rMap = CLIENT.getMap(key);
  403. return rMap.getAll(hKeys);
  404. }
  405. /**
  406. * 设置原子值
  407. *
  408. * @param key Redis键
  409. * @param value 值
  410. */
  411. public static void setAtomicValue(String key, long value) {
  412. RAtomicLong atomic = CLIENT.getAtomicLong(key);
  413. atomic.set(value);
  414. }
  415. /**
  416. * 获取原子值
  417. *
  418. * @param key Redis键
  419. * @return 当前值
  420. */
  421. public static long getAtomicValue(String key) {
  422. RAtomicLong atomic = CLIENT.getAtomicLong(key);
  423. return atomic.get();
  424. }
  425. /**
  426. * 递增原子值
  427. *
  428. * @param key Redis键
  429. * @return 当前值
  430. */
  431. public static long incrAtomicValue(String key) {
  432. RAtomicLong atomic = CLIENT.getAtomicLong(key);
  433. return atomic.incrementAndGet();
  434. }
  435. /**
  436. * 递减原子值
  437. *
  438. * @param key Redis键
  439. * @return 当前值
  440. */
  441. public static long decrAtomicValue(String key) {
  442. RAtomicLong atomic = CLIENT.getAtomicLong(key);
  443. return atomic.decrementAndGet();
  444. }
  445. /**
  446. * 获得缓存的基本对象列表
  447. *
  448. * @param pattern 字符串前缀
  449. * @return 对象列表
  450. */
  451. public static Collection<String> keys(final String pattern) {
  452. Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
  453. return stream.collect(Collectors.toList());
  454. }
  455. /**
  456. * 删除缓存的基本对象列表
  457. *
  458. * @param pattern 字符串前缀
  459. */
  460. public static void deleteKeys(final String pattern) {
  461. CLIENT.getKeys().deleteByPattern(pattern);
  462. }
  463. /**
  464. * 检查redis中是否存在key
  465. *
  466. * @param key 键
  467. */
  468. public static Boolean hasKey(String key) {
  469. RKeys rKeys = CLIENT.getKeys();
  470. return rKeys.countExists(key) > 0;
  471. }
  472. }

获取i18n资源文件

提供一下第 3 步需要 获取i18n资源文件 类,可以做国际化进行处理,如果项目没有国际化,这个可以省略

  1. @NoArgsConstructor(access = AccessLevel.PRIVATE)
  2. public class MessageUtils {
  3. private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
  4. /**
  5. * 根据消息键和参数 获取消息 委托给spring messageSource
  6. *
  7. * @param code 消息键
  8. * @param args 参数
  9. * @return 获取国际化翻译值
  10. */
  11. public static String message(String code, Object... args) {
  12. try {
  13. return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
  14. } catch (NoSuchMessageException e) {
  15. return code;
  16. }
  17. }
  18. }

自定义异常

这个我们再自定义一个业务异常类,用于抛出异常 ,如果自己项目之前有定义,也可以使用自己的异常类

ServiceException

  1. @Data
  2. @EqualsAndHashCode(callSuper = true)
  3. @NoArgsConstructor
  4. @AllArgsConstructor
  5. public final class ServiceException extends RuntimeException {
  6. @Serial
  7. private static final long serialVersionUID = 1L;
  8. /**
  9. * 错误码
  10. */
  11. private Integer code;
  12. /**
  13. * 错误提示
  14. */
  15. private String message;
  16. /**
  17. * 错误明细,内部调试错误
  18. */
  19. private String detailMessage;
  20. public ServiceException(String message) {
  21. this.message = message;
  22. }
  23. public ServiceException(String message, Integer code) {
  24. this.message = message;
  25. this.code = code;
  26. }
  27. public String getDetailMessage() {
  28. return detailMessage;
  29. }
  30. @Override
  31. public String getMessage() {
  32. return message;
  33. }
  34. public Integer getCode() {
  35. return code;
  36. }
  37. public ServiceException setMessage(String message) {
  38. this.message = message;
  39. return this;
  40. }
  41. public ServiceException setDetailMessage(String detailMessage) {
  42. this.detailMessage = detailMessage;
  43. return this;
  44. }
  45. }

客户端工具类

如果对 ip 进行限流,在注解处理中会用到参数,ip ,url 等信息

ServletUtils

  1. @NoArgsConstructor(access = AccessLevel.PRIVATE)
  2. public class ServletUtils extends JakartaServletUtil {
  3. /**
  4. * 获取request
  5. */
  6. public static HttpServletRequest getRequest() {
  7. try {
  8. return getRequestAttributes().getRequest();
  9. } catch (Exception e) {
  10. return null;
  11. }
  12. }
  13. public static String getClientIP() {
  14. return getClientIP(getRequest());
  15. }
  16. }

3,处理限流注解

处理限流注解:RateLimiterAspect

对注解处理的核心代码就在这里,

  1. @Slf4j
  2. @Aspect
  3. public class RateLimiterAspect {
  4. /**
  5. * 定义spel表达式解析器
  6. */
  7. private final ExpressionParser parser = new SpelExpressionParser();
  8. /**
  9. * 定义spel解析模版
  10. */
  11. private final ParserContext parserContext = new TemplateParserContext();
  12. /**
  13. * 定义spel上下文对象进行解析
  14. */
  15. private final EvaluationContext context = new StandardEvaluationContext();
  16. /**
  17. * 方法参数解析器
  18. */
  19. private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
  20. /**
  21. * GLOBAL_REDIS_KEY 和 RATE_LIMIT_KEY 最好还是定义在项目的一个统一的常量文件中,这里为了解剖出来的文件少一点
  22. *
  23. * */
  24. /**
  25. * 全局 redis key (业务无关的key)
  26. */
  27. private final String GLOBAL_REDIS_KEY = "global:";
  28. /**
  29. * 限流 redis key
  30. */
  31. private final String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
  32. @Before("@annotation(rateLimiter)")
  33. public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
  34. // 获取注解传的 时间 次数
  35. int time = rateLimiter.time();
  36. int count = rateLimiter.count();
  37. // 处理 key
  38. String combineKey = getCombineKey(rateLimiter, point);
  39. try {
  40. RateType rateType = RateType.OVERALL;
  41. if (rateLimiter.limitType() == LimitType.CLUSTER) {
  42. rateType = RateType.PER_CLIENT;
  43. }
  44. long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
  45. if (number == -1) {
  46. String message = rateLimiter.message();
  47. if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
  48. message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
  49. }
  50. throw new ServiceException(message);
  51. }
  52. log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
  53. } catch (Exception e) {
  54. if (e instanceof ServiceException) {
  55. throw e;
  56. } else {
  57. throw new RuntimeException("服务器限流异常,请稍候再试");
  58. }
  59. }
  60. }
  61. /**
  62. * 返回带有特定前缀的 key
  63. * @param rateLimiter 限流注解
  64. * @param point 切入点
  65. * @return key
  66. */
  67. public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
  68. String key = rateLimiter.key();
  69. // 获取方法(通过方法签名来获取)
  70. MethodSignature signature = (MethodSignature) point.getSignature();
  71. Method method = signature.getMethod();
  72. Class<?> targetClass = method.getDeclaringClass();
  73. // 判断是否是spel格式
  74. if (StringUtils.containsAny(key, "#")) {
  75. // 获取参数值
  76. Object[] args = point.getArgs();
  77. // 获取方法上参数的名称
  78. String[] parameterNames = pnd.getParameterNames(method);
  79. if (ArrayUtil.isEmpty(parameterNames)) {
  80. throw new ServiceException("限流key解析异常!请联系管理员!");
  81. }
  82. for (int i = 0; i < parameterNames.length; i++) {
  83. context.setVariable(parameterNames[i], args[i]);
  84. }
  85. // 解析返回给key
  86. try {
  87. Expression expression;
  88. if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
  89. && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
  90. expression = parser.parseExpression(key, parserContext);
  91. } else {
  92. expression = parser.parseExpression(key);
  93. }
  94. key = expression.getValue(context, String.class) + ":";
  95. } catch (Exception e) {
  96. throw new ServiceException("限流key解析异常!请联系管理员!");
  97. }
  98. }
  99. // 限流前缀key
  100. StringBuilder stringBuffer = new StringBuilder(RATE_LIMIT_KEY);
  101. stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
  102. // 判断限流类型
  103. if (rateLimiter.limitType() == LimitType.IP) {
  104. // 获取请求ip
  105. stringBuffer.append(ServletUtils.getClientIP()).append(":");
  106. } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
  107. // 获取客户端实例id
  108. stringBuffer.append(RedisUtils.getClient().getId()).append(":");
  109. }
  110. return stringBuffer.append(key).toString();
  111. }
  112. }

到这里注解就定义好了,接下来就可以进行测试和使用!!!

测试限流

定义一个 Controller 来测试限流,这里返回的 R ,可以根据自己项目统一定义的返回,或者使用 void

RedisRateLimiterController

  1. @Slf4j
  2. @RestController
  3. @RequestMapping("/demo/rateLimiter")
  4. public class RedisRateLimiterController {
  5. /**
  6. * 测试全局限流
  7. * 全局影响
  8. */
  9. @RateLimiter(count = 2, time = 10)
  10. @GetMapping("/test")
  11. public R<String> test(String value) {
  12. return R.ok("操作成功", value);
  13. }
  14. /**
  15. * 测试请求IP限流
  16. * 同一IP请求受影响
  17. */
  18. @RateLimiter(count = 2, time = 10, limitType = LimitType.IP)
  19. @GetMapping("/testip")
  20. public R<String> testip(String value) {
  21. return R.ok("操作成功", value);
  22. }
  23. /**
  24. * 测试集群实例限流
  25. * 启动两个后端服务互不影响
  26. */
  27. @RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER)
  28. @GetMapping("/testcluster")
  29. public R<String> testcluster(String value) {
  30. return R.ok("操作成功", value);
  31. }
  32. /**
  33. * 测试请求IP限流(key基于参数获取)
  34. * 同一IP请求受影响
  35. *
  36. * 简单变量获取 #变量 复杂表达式 #{#变量 != 1 ? 1 : 0}
  37. */
  38. @RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value")
  39. @GetMapping("/testObj")
  40. public R<String> testObj(String value) {
  41. return R.ok("操作成功", value);
  42. }
  43. }

如果代码写的有问题,欢迎大家评论交流,进行指点!!!

也希望大家点个关注哦~~~~~~~~

原文链接:https://www.cnblogs.com/blbl-blog/p/17944006

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号