微服务架构实战篇(五):Spring boot2.x + Guava 并使用RateLimiter实现秒杀限流demo

一壶清酒 2019-03-15 09:53:11 ⋅ 1117 阅读

简介

该项目主要利用Spring boot2.x + Guava 实现数据缓存,并使用RateLimiter做秒杀限流示例。

  • Guava是一种基于开源的Java库,其中包含谷歌正在由他们很多项目使用的很多核心库。这个库是为了方便编码,并减少编码错误。这个库提供用于集合,缓存,支持原语,并发性,常见注解,字符串处理,I/O和验证的实用方法。

  • Guava - RateLimiter使用的是一种叫令牌桶的流控算法,RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行。

  • Google guava工具类快速入门指南

  • 源码地址

    • GitHub:https://github.com/yundianzixun/spring-boot-starter-guava

    • 码云:https://gitee.com/itunion/spring-boot-starter-guava


  • 联盟公众号:IT实战联盟

  • 我们社区:https://100boot.cn



小工具一枚,欢迎使用和Star支持,如使用过程中碰到问题,可以提出Issue,我会尽力完善该Starter

版本基础

  • Spring Boot:2.0.4

  • Guava:19.0

操作步骤

第一步:添加maven依赖

        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>

第二步:增加GuavaCacheConfig 配置

GuavaCacheConfig.java

package com.itunion.guava.config;

import com.google.common.cache.CacheBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.guava.GuavaCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.concurrent.TimeUnit;

/**
* Created by lin on 19/3/14.
*/

@EnableConfigurationProperties(GuavaProperties.class)
@EnableCaching
@Configuration
public class GuavaCacheConfig {
@Autowired
private GuavaProperties guavaProperties;
@Bean
public CacheBuilder<Object, Object> cacheBuilder() {
long maximumSize = guavaProperties.getMaximumSize();
long duration = guavaProperties.getExpireAfterAccessDuration();
if (maximumSize <= 0) {
maximumSize = 1024;
}
if (duration <= 0) {
duration = 5;
}
return CacheBuilder.newBuilder()
.maximumSize(maximumSize)
.expireAfterWrite(duration, TimeUnit.SECONDS);
}

/**
* expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。
* expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收,如果我们认为缓存数据在一段时间后数据不再可用,那么可以使用该种策略。
* refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。
* @return
*/

@DependsOn({"cacheBuilder"})
@Bean
public CacheManager cacheManager(CacheBuilder<Object, Object> cacheBuilder) {
GuavaCacheManager cacheManager = new GuavaCacheManager();
cacheManager.setCacheBuilder(cacheBuilder);
return cacheManager;
}
}

备注:duration 缓存项在指定的5秒钟内有效,超过试卷进行回收操作,具体时间根据业务配置;

第三步:增加GuavaProperties配置

GuavaProperties.java

package com.itunion.guava.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* Created by lin on 19/3/14.
*/

@ConfigurationProperties(prefix = "guava.cache.config")
public class GuavaProperties {

private long maximumSize;

private long maximumWeight;

private long expireAfterWriteDuration;

private long expireAfterAccessDuration;

private long refreshDuration;

private int initialCapacity;

private int concurrencyLevel;

public long getMaximumSize() {
return maximumSize;
}

public void setMaximumSize(long maximumSize) {
this.maximumSize = maximumSize;
}

public long getMaximumWeight() {
return maximumWeight;
}

public void setMaximumWeight(long maximumWeight) {
this.maximumWeight = maximumWeight;
}

public long getExpireAfterWriteDuration() {
return expireAfterWriteDuration;
}

public void setExpireAfterWriteDuration(long expireAfterWriteDuration) {
this.expireAfterWriteDuration = expireAfterWriteDuration;
}

public long getExpireAfterAccessDuration() {
return expireAfterAccessDuration;
}

public void setExpireAfterAccessDuration(long expireAfterAccessDuration) {
this.expireAfterAccessDuration = expireAfterAccessDuration;
}

public long getRefreshDuration() {
return refreshDuration;
}

public void setRefreshDuration(long refreshDuration) {
this.refreshDuration = refreshDuration;
}

public int getInitialCapacity() {
return initialCapacity;
}

public void setInitialCapacity(int initialCapacity) {
this.initialCapacity = initialCapacity;
}

public int getConcurrencyLevel() {
return concurrencyLevel;
}

public void setConcurrencyLevel(int concurrencyLevel) {
this.concurrencyLevel = concurrencyLevel;
}
}

第三步:增加测试Guava缓存服务

CacheGuavaServiceImpl.java

@Cacheable(value = "guavacache")
@Override
public Map<String, String> getUserCache() {
new Thread().start();
while(true){
try {
Map<String,String> userMap = new HashMap<>();
userMap.put("name","IT实战联盟");
userMap.put("url","https://100boot.cn");
userMap.put("address","上海");
Thread.sleep(5000);
return userMap;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

备注:在请求数据的时候 为了达到大数据的效果,这里设置了睡眠5秒的线程,5秒后返回结果

第四步:访问服务并命中guava缓存

CacheController.java

/**
* 查询缓存
*/

@ApiOperation(value = "getByCache", notes = "查询缓存")
@RequestMapping(value = "getByCache", method = RequestMethod.GET)
@ResponseBody
public String getByCache()
{
Long startTime = System.currentTimeMillis();
Map<String,String> map = cacheGuavaService.getUserCache();
Long endTime = System.currentTimeMillis();
System.out.println("耗时: " + (endTime - startTime));
System.out.println(map.toString());
return map.toString();
}

第五步:测试效果


如上图所示:第一次请求耗费了5秒,接下来5秒钟内是直接命中guava从缓存里面获取的数据。5秒钟后进行了回收,再一次请求了服务并存储到guava中。

第六步:创建RateLimiter令牌限流服务

GuavaRateLimiterServiceImpl.java

 /**
* 每秒钟只发出2个令牌,拿到令牌的请求才可以进入下一个业务
*/

private RateLimiter seckillRateLimiter = RateLimiter.create(2);

@Override
public boolean tryAcquireSeckill() {
return seckillRateLimiter.tryAcquire();
}

这里为了能够方便测试只设置了2个令牌。

CacheGuavaServiceImpl.java

@Override
@Transactional
public String executeSeckill() {
// 验证是否被限流器限制,如果没有,则继续往下执行业务
if(guavaRateLimiterService.tryAcquireSeckill()){
return "哈哈哈,你没有限制住我!啦啦啦啦!";
}else {
//被限流器限制
return "呜呜呜,竟然限制我!!!";
}
}

在使用RateLimiter令牌时,进行验证是否被限流器限制,如果没有则继续执行下面业务,如果被限制则直接返回不继续执行。

第六步:调用RateLimiter令牌限流服务

CacheController.java

  /**
* 测试限流器
*/

@ApiOperation(value = "getRateLimiter", notes = "测试限流器")
@RequestMapping(value = "getRateLimiter", method = RequestMethod.GET)
@ResponseBody
public String getRateLimiter() {
String str = cacheGuavaService.executeSeckill();
System.out.println(str+","+new Date());
return str;
}

第七步:RateLimiter令牌限流服务测试


备注:由于手速原因可以看到每秒超过令牌个数的直接返回“呜呜呜,竟然限制我!!!”,说明有效果。

贡献者

  • IT实战联盟-Line

  • IT实战联盟-咖啡

往期回顾

微服务架构实战篇(四):Spring boot2.x + Mybatis +Druid监控数据库访问性能
微服务架构实战篇(三):Spring boot2.x + Mybatis + PageHelper实现增删改查和分页查询功能
微服务架构实战篇(二):Spring boot2.x + Swagger2 让你的API可视化
微服务架构实战篇(一):使用start.spring.io 构建SpringBoot2.x项目
Google guava工具类快速入门指南

---------------END----------------

后续的内容同样精彩

长按关注“IT实战联盟”哦




全部评论: 0

    我有话说:

    服务架构实战(六):Spring boot2.x 集成阿里大鱼短信接口详解与Demo

    Spring boot2.x 集成阿里大鱼短信接口,发送短信验证码及短信接口详解。

    码云推荐:一个优秀的分布式spring boot/Spring Cloud API框架,特别适合服务架构

    一个优秀的分布式spring boot/Spring Cloud API框架,特别适合服务架构.

    服务架构学习笔记:gRPC Spring Boot Starter 2.2.0 发布,及使用步骤

    gRPC Spring Boot Starter 项目是一个 gRPC 的 Spring Boot 模块。内嵌一个 gRPC Server 对外提供服务支持 Spring Cloud 的服务发现

    架构实战(六):Spring Boot RestTemplate的使用

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

    架构实战(十):Spring Boot 解耦之事件驱动

    通过使用spring 事件来解决业务代码的耦合

    架构实战):Spring Boot 表单验证和异常处理

    为了让API 能够更好的提供服务,表单数据验证和异常的处理是必不可少的,让我们来看看怎么处理......

    架构实战(十六):Spring Boot Assembly服务化打包

    使用assembly来打包springboot服务项目,让发布更简单

    架构实战(十七):Spring Boot Assembly 整合 thymeleaf

    如何让服务器上的 sprig boot 项目升级变的方便快捷

    架构实战(三)-Spring Boot架构搭建RESTful API案例

    之前分享了Spring Boot 整合Swagger 让API可视化和前后端分离架构 受到了大家一致好评 ,本节就接着上节的代码做了详细的查询代码的补充和完善搭建RESTful API架构案例。

    Java并发解决方案:分布式应用实践

    任何都不是漫无目的的,也不是一个开关就可以解决的问题,常用的算法有:令牌桶,漏桶。在之前的文章中,也讲到过,但是那是基于单机场景来写。 之前文章:接口算法:漏桶算法&令牌桶算法

    架构实战(七):Spring Boot Data JPA 快速入门

    Spring Data JPA 是Spring Data 的一个子项目,它通过提供基于JPA的Repository极大了减少了操作JPA的代码。

    架构实战(十):Spring Boot 集成 Dubbo

    Dubbo是阿里巴巴SOA服务化治理方案的核心框架,一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案。

    架构实战(十三):Spring Boot Logback 邮件通知

    日志对于应用程序来说是非常重要的,当你的程序报错了,而你又不知道是多么可怕的一件事情,本文使用logback把程序报错信息邮件到开发者

    服务架构实战:快速入手SpringBoot 2.0,欢迎入坑哦~~~

    SpringBoot 2.0 基本要求Java最低要求8以上,不再支持Java 6 和 7等低版本。

    架构实战(十一):Spring Boot 集成企业级搜索引擎 SolrCloud

    Solr是以Lucene为基础实现的文本检索应用服务。Solr部署方式有单机方式、多机Master-Slaver方式、Cloud方式。

    「推荐」通过API网关实现服务管控-,熔断和降级

      今天准备谈下基于API网关来实现服务治理管控中的服务,熔断和降级方面的内容。在前面谈服务架构的时候也谈到过类似通过Hystrix,Sentinel来是服务熔断。包括也不断

    架构实战(一)-Spring Boot+MyBatis基础架构搭建

    Spring的追求一定是简单点简单点,让java的开发变得更加简单、容易。瞧瞧的告诉你们直接copy就能用哦~~~

    架构实战:一个可供中小团队参考的服务架构技术栈

    作者近年一直在一线互联网公司(携程,拍拍贷等)开展服务架构实践,根据我个人的一线实践经验和我平时对Spring Cloud的调研,我认为Spring Cloud技术栈中的有些组件离生产级开发尚有