当前位置:   article > 正文

实时解答SpringCloud+Nginx秒杀实战,Zuul内部网关实现秒杀限流_springcloud zuul+nginx

springcloud zuul+nginx

Zuul内部网关实现秒杀限流

秒杀限流操作既可以在内部网关Zuul中完成,又可以在外部网关Nginx中完成。内部网关Zuul可以通过ZuulFilter过滤器的形式对获取秒杀令牌的请求进行拦截,然后通过Redis令牌桶限流服务实现分布式限流。

从前面的内容可知,Redis中存储限流令牌桶信息的是一个哈希表结构,其内部的键值对包括max_permits、curr_permits、rate、last_mill_second四个hash key,而整个令牌桶哈希表结构的缓存key的格式为rate_limiter:seckill:1(1为商品ID),其中重要的部分是秒杀商品ID,该ID表示限流统计的范围是针对一个秒杀商品的,而不是针对整个秒杀接口。

秒杀商品(假设ID为1)的限流令牌桶的Redis哈希表结构如图10-12所示。

实时解答SpringCloud+Nginx秒杀实战,Zuul内部网关实现秒杀限流

图10-12 存储令牌桶限流信息的Redis哈希表结构

在秒杀没有开始之前需要初始化限流令牌桶的Redis哈希表结构,虽然真正的初始化工作是在rate_limit.lua脚本中完成的,但是需要通过Java程序进行调用,并传入相关的初始化参数。什么时候进行限流令牌桶的初始化呢?生产环境上的秒杀开始之前应该有一个秒杀商品暴露(或者启动)的动作,该动作可以手动或者自动完成,限流的初始化工作可以在秒杀暴露时完成。

下面是一个限流的初始化的简单示例:

  1. package com.crazymaker.springcloud.seckill.controller;
  2. //省略import
  3. @RestController
  4. @RequestMapping("/api/seckill/good/")
  5. @Api(tags = "秒杀练习 商品管理")
  6. public class SeckillGoodController
  7. {
  8. /**
  9. *开启商品秒杀
  10. *
  11. *@param dto商品id
  12. *@return商品goodDTO
  13. */
  14. @PostMapping("/expose/v1")
  15. @ApiOperation(value = "开启商品秒杀")
  16. RestOut<SeckillGoodDTO> expose(@RequestBody SeckillDTO dto)
  17. {
  18. Long goodId = dto.getSeckillGoodId();
  19. SeckillGoodDTO goodDTO = seckillService.findGoodByID(goodId);
  20. if (null != goodDTO)
  21. {
  22. //初始化秒杀的限流器
  23. rateLimitService.initLimitKey(
  24. "seckill",
  25. String.valueOf(goodId),
  26. SeckillConstants.MAX_ENTER,
  27. SeckillConstants.PER_SECKOND_ENTER
  28. );
  29. /**
  30. *缓存限流lua脚本的sha1编码,方便在其他地方获取
  31. */
  32. rateLimitService.cacheSha1();
  33. /**
  34. *缓存秒杀lua脚本的sha1编码,方便在其他地方获取
  35. */
  36. redisSeckillServiceImpl.cacheSha1();
  37. return RestOut.success(goodDTO).setRespMsg("秒杀开启成功");
  38. }
  39. return RestOut.error("秒杀开启失败");
  40. }
  41. ...
  42. }

限流器初始化之后,就可以在Zuul内部网关或者Nginx外部网关进行请求拦截时使用分布式限流器进行限流。Zuul内部网关的限流拦截过程如图10-13所示。

实时解答SpringCloud+Nginx秒杀实战,Zuul内部网关实现秒杀限流

图10-13 Zuul内部网关限流拦截示意图

Zuul网关限流过滤器类SeckillRateLimitFilter的代码如下:

  1. package com.crazymaker.springcloud.cloud.center.zuul.filter;
  2. //省略import
  3. @Slf4j
  4. @ConditionalOnBean(RedisRateLimitImpl.class)
  5. @Component
  6. public class SeckillRateLimitFilter extends ZuulFilter
  7. {
  8. /**
  9. *Redis限流服务实例
  10. */
  11. @Resource(name = "redisRateLimitImpl")
  12. RateLimitService redisRateLimitImpl;
  13. @Override
  14. public String filterType()
  15. {
  16. return "pre"; //路由之前
  17. }
  18. /**
  19. *过滤的顺序
  20. */
  21. @Override
  22. public int filterOrder()
  23. {
  24. return 0;
  25. }
  26. /**
  27. *这里可以编写逻辑判断是否要过滤,true为永远过滤
  28. */
  29. @Override public boolean shouldFilter()
  30. {
  31. RequestContext ctx = RequestContext.getCurrentContext();
  32. HttpServletRequest request = ctx.getRequest();
  33. /**
  34. *如果请求已经被其他的过滤器终止,本过滤器就不做处理
  35. **/
  36. if (!ctx.sendZuulResponse())
  37. {
  38. return false;
  39. }
  40. /**
  41. *对秒杀令牌进行限流
  42. */
  43. if (request.getRequestURI().startsWith
  44. ("/seckill-provider/api/seckill/redis/token/v1"))
  45. {
  46. return true;
  47. }
  48. return false;
  49. }
  50. /**
  51. *过滤器的具体逻辑
  52. */
  53. @Override
  54. public Object run()
  55. {
  56. RequestContext ctx = RequestContext.getCurrentContext();
  57. HttpServletRequest request = ctx.getRequest();
  58. String goodId = request.getParameter("goodId");
  59. if (goodId != null)
  60. {
  61. String cacheKey = "seckill:" + goodId;
  62. Boolean limited = redisRateLimitImpl.tryAcquire(cacheKey);
  63. if (limited)
  64. {
  65. /**
  66. *被限流后的降级
  67. */
  68. String msg = "参与抢购的人太多,请稍后再试一试";
  69. fallback(ctx, msg);
  70. return null;
  71. }
  72. return null;
  73. } else
  74. {
  75. /**
  76. *参数输入错误时的降级处理
  77. */
  78. String msg = "必须输入抢购的商品";
  79. fallback(ctx, msg);
  80. return null;
  81. }
  82. }
  83. /**
  84. *被限流后的降级处理
  85. *
  86. *@param ctx
  87. *@param msg
  88. */
  89. private void fallback(RequestContext ctx, String msg)
  90. {
  91. ctx.setSendZuulResponse(false);
  92. try
  93. {
  94. ctx.getResponse().setContentType("text/html;charset=utf-8");
  95. ctx.getResponse().getWriter().write(msg);
  96. } catch (Exception e)
  97. { e.printStackTrace();
  98. }
  99. }
  100. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/224951
推荐阅读
相关标签
  

闽ICP备14008679号