这篇文章主要介绍了微服务架构中四大利器的用法示例,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。
概述
互联网应用发展到今天,从单体应用架构到 SOA 以及今天的微服务,随着微服务化的不断升级进化,服务和服务之间的稳定性变得越来越重要,分布式系统之所以复杂,主要原因是分布式系统需要考虑到网络的延时和不可靠,微服务很重要的一个特质就是需要保证服务幂等,保证幂等性很重要的前提需要分布式锁控制并发,同时缓存、降级和限流是保护微服务系统运行稳定性的三大利器。
redis.extend.hostName=127.0.0.1
redis.extend.port=6379
redis.extend.password=pwdcode
redis.extend.timeout=10000
redis.idempotent.enabled=true
缓存
缓存的使用可以说无处不在,从应用请求的访问路径来看,用户 user -> 浏览器缓存 -> 反向代理缓存-> WEB服务器缓存 -> 应用程序缓存 -> 数据库缓存等,几乎每条链路都充斥着缓存的使用,缓存最直白的解释就是“用空间换时间”的算法。缓存就是把一些数据暂时存放于某些地方,可能是内存,也有可能硬盘。总之,目的就是为了避免某些耗时的操作。我们常见的耗时的操作,比如数据库的查询、一些数据的计算结果,或者是为了减轻服务器的压力。其实减轻压力也是因查询或计算,虽然短耗时,但操作很频繁,累加起来也很长,造成严重排队等情况,服务器抗不住。
public String get(String key);
/**
* 获取指定的key对应的对象,异常也会返回null
*
* @param key
* @param clazz
* @return
*/
public <T> T get(String key, Class<T> clz);
/**
* 存储缓存数据,忽略过期时间
*
* @param key
* @param value
* @return
*/
public <T extends Serializable> booleanput(String key, T value);
/**
* 存储缓存数据
*
* @param key
* @param value
* @param expiredTime
* @param unit
* @return
*/
public <T extends Serializable> booleanput(String key, T value, int expiredTime, TimeUnit unit);
/**
* 基于key删除缓存数据
*
* @param key
* @return
*/
publicbooleaninvalid(String key);
get 方法针对 key 进行查询, put 存储缓存数据, invalid 删除缓存数据。
限流
在分布式系统中,尤其面对一些秒杀、瞬时高并发场景,都需要进行一些限流措施,保证系统的高可用。通常来说限流的目的是通过对并发访问/请求进行限速,或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以 拒绝服务(定向到错误页或告知资源没有了)、排队 或 等待(比如秒杀、评论、下单)、降级(返回托底数据或默认数据,如商品详情页库存默认有货)。
/**
* 指定过期时间自增计数器,默认每次+1,非滑动窗口
*
* @param key 计数器自增key
* @param expireTime 过期时间
* @param unit 时间单位
* @return
*/
publiclongincrCount(String key, int expireTime, TimeUnit unit);
/**
* 指定过期时间自增计数器,单位时间内超过最大值rateThreshold返回true,否则返回false
*
* @param key 限流key
* @param rateThreshold 限流阈值
* @param expireTime 固定窗口时间
* @param unit 时间单位
* @return
*/
publicbooleanrateLimit(final String key, finalint rateThreshold, int expireTime, TimeUnit unit);
/**
* @param limitKey 限流KEY
* @param resultSupplier 回调方法
* @param rateThreshold 限流阈值
* @param limitTime 限制时间段
* @param blockDuration 阻塞时间段
* @param unit 时间单位
* @param errCodeEnum 指定限流错误码
* @return
*/
public <T> T execute(String limitKey, Supplier<T> resultSupplier, long rateThreshold, long limitTime,
long blockDuration, TimeUnit unit, ErrCodeEnum errCodeEnum){
boolean blocked = tryAcquire(limitKey, rateThreshold, limitTime, blockDuration, unit);
if (errCodeEnum != null) {
AssertUtils.assertTrue(blocked, errCodeEnum);
} else {
AssertUtils.assertTrue(blocked, ExceptionEnumType.ACQUIRE_LOCK_FAIL);
}
return resultSupplier.get();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RateLimit {
/**
* 限流KEY
*/
String limitKey();
/**
* 允许访问的次数,默认值MAX_VALUE
*/
longlimitCount()default Long.MAX_VALUE;
/**
* 时间段
*/
longtimeRange();
/**
* 阻塞时间段
*/
longblockDuration();
/**
* 时间单位,默认为秒
*/
TimeUnit timeUnit()default TimeUnit.SECONDS;
}
@RateLimit(limitKey = "#key", limitCount = 5, timeRange = 2, blockDuration = 3, timeUnit = TimeUnit.MINUTES)
public String testLimit2(String key){
..........
return key;
}
在 Java 单一进程中通过 synchronized 关键字和 ReentrantLock 可重入锁可以实现在多线程环境中控制对资源的并发访问,通常本地的加锁往往不能满足我们的需要,我们更多的面对场景是分布式系统跨进程的锁,简称为分布式锁。分布式锁实现手段通常是将锁标记存在内存中,只是该内存不是某个进程分配的内存而是公共内存如 Redis、Tair ,至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。分布式锁相对单机进程的锁之所以复杂,主要原因是分布式系统需要考虑到网络的延时和不可靠。
互斥性:同本地锁一样具有互斥性,但是分布式锁需要保证在不同节点进程的不同线程的互斥。
可重入性:同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
锁超时:和本地锁一样支持锁超时,防止死锁,通过异步心跳 demon 线程刷新过期时间,防止特殊场景(如 FGC 死锁超时)下死锁。
高性能、高可用:加锁和解锁需要高性能,同时也需要保证高可用防止分布式锁失效,可以增加降级。
支持阻塞和非阻塞:同 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock ( long timeOut )。
公平锁和非公平锁(不支持):公平锁是按照请求加锁的顺序获得锁,非公平锁就相反是无序的,目前 distributed-tools 组件提供的分布式锁不支持该特性。
/**
* 分布式锁处理模板执行器
*
* @param lockKey 分布式锁key
* @param resultSupplier 分布式锁处理回调
* @param waitTime 锁等待时间
* @param unit 时间单位
* @param errCodeEnum 指定特殊错误码返回
* @return
*/
public static <T> T execute(String lockKey, Supplier<T> resultSupplier, long waitTime, TimeUnit unit,
ErrCodeEnum errCodeEnum){
AssertUtils.assertTrue(StringUtils.isNotBlank(lockKey), ExceptionEnumType.PARAMETER_ILLEGALL);
boolean locked = false;
Lock lock = DistributedReentrantLock.newLock(lockKey);
try {
locked = waitTime > 0 ? lock.tryLock(waitTime, unit) : lock.tryLock();
} catch (InterruptedException e) {
throw new RuntimeException(String.format("lock error,lockResource:%s", lockKey), e);
}
if (errCodeEnum != null) {
AssertUtils.assertTrue(locked, errCodeEnum);
} else {
AssertUtils.assertTrue(locked, ExceptionEnumType.ACQUIRE_LOCK_FAIL);
}
try {
return resultSupplier.get();
} finally {
lock.unlock();
}
}
幂等
在分布式系统设计中幂等性设计中十分重要的,尤其在复杂的微服务中一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用 RPC 通信或者 restful ,分布式系统中的网络延时或中断是避免不了的,通常会导致服务的调用层触发重试。具有这一性质的接口在设计时总是秉持这样的一种理念:调用接口发生异常并且重复尝试时,总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。
幂等 key 提取能力:获取唯一幂等 key
分布式锁服务能力:提供全局加锁、解锁的能力
distributed-tools 幂等组件需要使用自身提供的分布式锁功能,保证其并发唯一性, distributed-tools 提供的分布式锁能够提供其可靠、稳定的加锁、解锁能力。
高性能的写入、查询能力:针对幂等结果查询与存储
distributed-tools 幂等组件提供了基于 tair 、 redis 的存储实现,同时支持自定义一级、二级存储通过 spring 依赖注入到 IdempotentService ,建议 distributed-tools 幂等存储结果一级存储 tair mdb ,二级存储ldb或者 tablestore ,一级存储保证其高性能,二级存储保证其可靠性。
@Idempotent(spelKey = "#request.requestId", firstLevelExpireDate = 7,secondLevelExpireDate = 30)
publicvoidexecute(BizFlowRequest request){
..................
}
/**
* 幂等模板处理器
*
* @param request 幂等Request信息
* @param executeSupplier 幂等处理回调function
* @param resultPreprocessConsumer 幂等结果回调function 可以对结果做些预处理
* @param ifResultNeedIdempotence 除了根据异常还需要根据结果判定是否需要幂等性的场景可以提供此参数
* @return
*/
public R execute(IdempotentRequest<P> request, Supplier<R> executeSupplier,
Consumer<IdempotentResult<P, R>> resultPreprocessConsumer, Predicate<R> ifResultNeedIdempotence){
........
}
感谢你能够认真阅读完这篇文章,希望小编分享的“微服务架构中四大利器的用法示例”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。