昨日应部门要求,去国家会展中心参观医疗行业的博览会。主要就是浏览下行业内其他公司的最新展品,以此来比照自家产品,方便查漏补缺,对照不足。

彼时馆内人流不息,走马观花溜了一遍,发现大多数产品[中大型硬件]都是面向经销商,医院之流。很多高大上的产品却非是只一家所有,除了都很贵外,彼此只细微差别。而我们公司展会上,自觉引人最多围观的还是5分钟做测评。毕竟快速体验[是我从事的]嘛。
看着一窝人排队等着上炕做测评,我心里还是有点小高兴的,像是养了好些年的白菜终于有猪来拱了一样。这种方式受欢迎,是因其流程简单速度快。但此微服务为展会版本,场景是在展会处一个一个排队来做。若以后用此服务部署在全国多家医院呢?那可能就要有并发考虑了。
于是使用JMeter针对此服务做了一些压力测试。发现并发量为50/s时,后端就会内存溢出。虽是此测试服务器因性能不佳而承受不了太大压力,但以后若报错甩锅,肯定还是我这后端来抗伤害啊。并且,若有人恶意访问,那岂不是要导致我整个服务GG?遂此篇为限流配置记录。
测试所使框架及版本介绍:
springCloudVersion = 'Dalston.SR4'springBootVersion = '1.5.8.RELEASE'JDK = 1.8
在分布式系统中,网关是每个请求的必经入口。遂限流措施置于Zuul网关处
RateLimiter算法介绍:
RateLimiter用于限制对一些物理资源或者逻辑资源的访问速率RateLimiter使用的是一种叫令牌桶的流控算法,RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌
作为码畜,你可以不关注RateLimiter本身,只用关注Spring Cloud Zuul RateLimiter即可:其结合Zuul对RateLimiter进行了封装,通过实现ZuulFilter提供了服务限流功能
Step 1
导包:
compile group: 'com.marcosbarbero.cloud', name: 'spring-cloud-zuul-ratelimit', version: '1.5.0.RELEASE'
此处选用了Maven上使用次数最多的1.5.0.RELEASE版本,实则我是先试用的2.2.3最新版,但其配置信息与之前配置有所迥异,且我压力测试下来,其配置毫无效果,所以新的玩不转还是玩稳的吧。沃斯渣渣辉,是兄弟就来砍我
RateLimiter的限流数据默认以Concurrent HashMap方式存储在内存中的,考虑到要部署Zuul的集群,肯定不会都放在一台机器上了。所以,我们可以将限流数据存储在Redis中,这样就可以集中记录各个Zuul节点的限流数据,来保证限流的准确性
为此,导入Redis包:
compile group: 'org.springframework.data', name: 'spring-data-redis', version: '1.8.8.RELEASE'compile group: 'redis.clients', name: 'jedis', version: '2.9.0'
使用的是Jedis客户端,但你要知道,SpringBoot1.x默认是Jedis客户端,2.x以后都是Lettuce了。所以,看一个Demo不关注其Version那都是白搭啊
Step 2
yml配置信息:
zuul:ratelimit:enabled: true #开启限流repository: REDIS #启用redis存储 默认为内存存储,还有:redis Consul Bucket4j Spring Data ConcurrentHashMap(内存)behind-proxy: true #代理之后default-policy: #默认全局配置refresh-interval: 10 #刷新时间窗口的时间 (秒)limit: 20 #每个刷新时间窗口对应的 请求数量限制quota: 4 #每个刷新时间窗口对应的 请求时间限制(秒)type: URL #根据什么限流 ORIGIN:根据ip地址区分 USER:是通过登录用户名进行区分,也包括匿名用户 URL:根据请求路径区分#本机的Redis配置spring:redis:host: 127.0.0.1port: 6379
此为全局配置。其中,repository不能为null。我们使用Redis,记得导包。参数说明都在以上注释中了,就不多啰嗦了
若要针对各个微服务单独进行配置,则:
zuul:ratelimit:policies: #开始针对各个微服务。最新版本与此配置不同[Service-Id]: #此处填你的服务IDrefresh-interval: 10 #刷新时间窗口的时间 (秒)limit: 10 #每个刷新时间窗口对应的 请求数量限制quota: 4 #每个刷新时间窗口对应的 请求时间限制(秒)type: URL
Step 3
使用JMeter开始测试:

此处JMeter配置为:模拟11个用户在10秒中发送我配置的请求。你要清楚,上面配置Demo中,我全局配置为:限定10秒可处理20个请求。而针对某单个服务配置,设置10秒中只处理10个请求。那么此时访问该指定服务,并查看结果树:

此处11个请求,最后一个失败:Too Many Requests. 单独的服务配置生效,即其优先级高与全局配置。
Step 4
优化:限流虽然成功,但是返回前端的提示为系统自带的错误信息,并非我们系统统一返回的JSON对象,遂写个ErrorFilter,为使其优先级比默认的SendErrorFilter高,则其顺序应小于-1.贴出其run方法如下:
@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();Object obj = ctx.getThrowable();if (obj != null && obj instanceof ZuulException) {ctx.remove("throwable");}//我自定义的Json对象:ResponseResponse responseDto = new Response<>(ResultCode.ERROR);try {ctx.setResponseBody(objectMapper.writeValueAsString(responseDto));} catch (JsonProcessingException e) {log.error(e.getMessage(), e);}ctx.setResponseStatusCode(429);ctx.setSendZuulResponse(false);return null;}}
再次测压:

返回了我指定的ResultCode.ERROR状态码及信息。此处乱码是因为JMeter未设置编码导致,无伤大雅,完事儿吃饭~
The End




