一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情。
往期回顾
前言
目前为止,技术派还是一个单体架构的网站,虽然并发量还很小,完全没达到单台MySQL数据库实例(瓶颈一般都在数据库上)几千QPS的极限。但是凡是都讲究一个未雨绸缪,况且提供更好的用户体验也是我们技术人不懈的追求。
缓存
用缓存,主要有两个用途:高性能、高并发。
高性能
一个操作去DB查,可能要几百毫秒甚至几秒,直接去内存中通过key-value结构去查,只要几毫秒到几十毫秒,性能极大的提升。
对于很少变更的数据、读多写少的数据就很适合放到缓存中,提升用户响应速度。
高并发
查询的速度变快,是不是单位时间内,能查询的次数就变多了呢?
Redis
以Springboot+maven的项目为例,配置和使用都很容易入手:
- 添加Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 添加项目配置(yml)
spring:
redis:
# springboot 2.x 默认使用lettuce做redis 客户端
lettuce:
# 这里是连接池配置,可以不配置,使用默认的参数
pool:
max-active: 100
max-idle: 10
min-idle: 5
max-wait: -1
host: xxx.xxx.xxx.xxx
port: 6379
password: xxxxx
- 使用RedisTemplate操作
/**
* 先自动装配上RedisTemplate对象
*/
@Autowired
RedisTemplate redisTemplate;
// redis string 类型的插入数据,带有过期时间
redisTemplate.opsForValue().set("name","l拉不拉米",10, TimeUnit.MINUTES);
// redis string 类型的查询数据
redisTemplate.opsForValue().get("name");
本地缓存
使用Guava Cache工具库,同类型的还有Spring Cache。仅限于单机部署。
// 实例化一个cache对象,并设置过期时间
private Cache<String, List<ArticleVo>> articleCache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build();
/**
* 文章点赞排名
*
* @return
*/
public List<ArticleVo> getTopLikeArticle() {
// 先从本地缓存获取数据
List<ArticleVo> topLikeCache = topArticleCache.getIfPresent(ApiConstant.TOP_LIKE_CACHE);
if (CollUtil.isNotEmpty(topLikeCache)) {
return topLikeCache;
}
// 如果缓存没有就从数据库查
List<ArticleVo> topLikeArticles = getTopLikeArticles();
// 重新放到缓存中
topArticleCache.put(ApiConstant.TOP_LIKE_CACHE, topLikeArticles);
return topLikeArticles;
}
限流
使用Guava RateLimiter工具库。仅限于单机部署。
技术派使用自定义注解+切面编程的方式实现。
- 先定义一个注解
/**
* 限流注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/**
* 每秒向桶放入令牌的数量
*/
double capacity() default 1.0;
/**
* 限流对象的名称
*/
String name() default "rateLimiter";
}
- 定义限流切面
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
/**
* 限流容器
*/
private ConcurrentHashMap<String, RateLimiter> RATE_LIMITER_CONTAINER = new ConcurrentHashMap<>();
@Pointcut("@annotation(com.techpai.web.annotation.RateLimit)")
private void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
//获取拦截的方法名
Signature sig = point.getSignature();
//获取拦截的方法名
MethodSignature msig = (MethodSignature) sig;
//返回被织入增加处理目标对象
Object target = point.getTarget();
//为了获取注解信息
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
//获取注解信息
RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);
// 获取注解每秒加入桶中的token
double capacity = annotation.capacity();
// 注解所在方法名区分不同的限流策略
String name = msig.getName();
RateLimiter rateLimiter;
if (RATE_LIMITER_CONTAINER.containsKey(name)) {
rateLimiter = RATE_LIMITER_CONTAINER.get(name);
} else {
RATE_LIMITER_CONTAINER.put(name, RateLimiter.create(capacity));
rateLimiter = RATE_LIMITER_CONTAINER.get(name);
}
if (rateLimiter.tryAcquire()) {
log.info("{}-{}:限流处理完成", name, capacity);
return point.proceed();
} else {
return "访问超过限流限制,请稍后再试";
}
}
}
- 使用限流
// 在方法上加上自定义注解
// guava rateLimiter使用的令牌桶,capacity表示每秒向桶内放入的令牌数,即每秒能处理的请求数,多的请求直接拒绝或等待(在切面中根据自己的业务场景确定)
@RateLimit(capacity = 10,name = "rss")
在之前的文章中,笔者也写过两篇文章讲解四种主流的限流算法,有兴趣的同学可以看看。
肯定会有同学要问,在分布式的场景下如何做限流呢?
笔者提供两个主流的方案:
- 阿里巴巴的开源分布式高可用流量组件:
Sentinel - Redis实现:用
Lua脚本写限流的实现逻辑,通过redis客户端调用Lua脚本
下期预告
在下一期中,详细讲解技术派-Github趋势功能的实现。
喜欢的同学请多多点赞,多多收藏我的网站 www.jspai.cc 哟😇😇