微服务架构四大金刚利器

概述

互联网应用发展到今天,从单体应用架构到SOA以及今天的微服务,随着微服务化的不断升级进化,服务和服务之间的稳定性变得越来越重要,分布式系统之所以复杂,主要原因是分布式系统需要考虑到网络的延时和不可靠,微服务很重要的一个特质就是需要保证服务幂等,保证幂等性很重要的前提需要分布式锁控制并发,同时缓存、降级和限流是保护微服务系统运行稳定性的三大利器。

随着业务不断的发展,按业务域的划分子系统越来越多,每个业务系统都需要缓存、限流、分布式锁、幂等工具组件,distributed-tools组件(暂未开源)正式包含了上述分布式系统所需要的基础功能组件。

distributed-tools组件基于tair、redis分别提供了2个springboot starter,使用起来非常简单。
以使用缓存使用redis为例,application.properties添加如下配置

redis.extend.hostName=127.0.0.1
redis.extend.port=6379
redis.extend.password=pwdcode
redis.extend.timeout=10000redis.idempotent.enabled=true

接下来的篇幅,重点会介绍一下缓存、限流、分布式锁、幂等的使用方式。

缓存

缓存的使用可以说无处不在,从应用请求的访问路径来看,用户user -> 浏览器缓存 -> 反向代理缓存-> WEB服务器缓存 -> 应用程序缓存 -> 数据库缓存等,几乎每条链路都充斥着缓存的使用,缓存最直白的解释就是“用空间换时间”的算法。缓存就是把一些数据暂时存放于某些地方,可能是内存,也有可能硬盘。总之,目的就是为了避免某些耗时的操作。我们常见的耗时的操作,比如数据库的查询、一些数据的计算结果,或者是为了减轻服务器的压力。其实减轻压力也是因查询或计算,虽然短耗时,但操作很频繁,累加起来也很长,造成严重排队等情况,服务器抗不住。

distributed-tools组件提供了一个CacheEngine接口,基于Tair、Redis分别有不同的实现,具体CacheEngine定义如下:

    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> boolean put(String key, T value);/*** 存储缓存数据* * @param key* @param value* @param expiredTime* @param unit* @return*/public <T extends Serializable> boolean put(String key, T value, int expiredTime, TimeUnit unit);/*** 基于key删除缓存数据* * @param key* @return*/public boolean invalid(String key);

get方法针对key进行查询,put存储缓存数据,invalid删除缓存数据。

限流

在分布式系统中,尤其面对一些秒杀、瞬时高并发场景,都需要进行一些限流措施,保证系统的高可用。通常来说限流的目的是通过对并发访问/请求进行限速,或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以 拒绝服务(定向到错误页或告知资源没有了)、排队 或 等待(比如秒杀、评论、下单)、降级(返回托底数据或默认数据,如商品详情页库存默认有货)。

常见的一些限流算法包括固定窗口、滑动窗口、漏桶、令牌桶,distributed-tools组件目前基于计数器只实现了固定窗口算法,具体使用方式如下:

 /*** 指定过期时间自增计数器,默认每次+1,非滑动窗口* * @param key 计数器自增key* @param expireTime 过期时间* @param unit  时间单位* @return*/public long incrCount(String key, int expireTime, TimeUnit unit);/*** 指定过期时间自增计数器,单位时间内超过最大值rateThreshold返回true,否则返回false* * @param key 限流key* @param rateThreshold 限流阈值* @param expireTime 固定窗口时间* @param unit 时间单位* @return*/public boolean rateLimit(final String key, final int rateThreshold, int expireTime, TimeUnit unit);

基于CacheEngine的rateLimit方法可以实现限流,expireTime只能设定固定窗口时间,非滑动窗口时间。
另外distributed-tools组件提供了模板RateLimitTemplate可以简化限流的易用性,可以直接调用RateLimitTemplate的execute方法处理限流问题。

 /*** @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();}

另外distributed-tools组件还提供了注解@RateLimit的使用方式,具体注解RateLimit定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RateLimit {/*** 限流KEY*/String limitKey();/*** 允许访问的次数,默认值MAX_VALUE*/long limitCount() default Long.MAX_VALUE;/*** 时间段*/long timeRange();/*** 阻塞时间段*/long blockDuration();/*** 时间单位,默认为秒*/TimeUnit timeUnit() default TimeUnit.SECONDS;
}

基于注解的方式限流使用代码如下:

@RateLimit(limitKey = "#key", limitCount = 5, timeRange = 2, blockDuration = 3, timeUnit = TimeUnit.MINUTES)
public String testLimit2(String key) {..........return key;
}

任何方法添加上述注解具备了一定的限流能力(具体方法需要在spring aop指定拦截范围内),如上代码表示以参数key作为限流key,每2分钟请求次数不超过5次,超过限制后阻塞3分钟。

分布式锁

在Java单一进程中通过synchronized关键字和ReentrantLock可重入锁可以实现在多线程环境中控制对资源的并发访问,通常本地的加锁往往不能满足我们的需要,我们更多的面对场景是分布式系统跨进程的锁,简称为分布式锁。分布式锁实现手段通常是将锁标记存在内存中,只是该内存不是某个进程分配的内存而是公共内存如Redis、Tair,至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。分布式锁相对单机进程的锁之所以复杂,主要原因是分布式系统需要考虑到网络的延时和不可靠。

distributed-tools组件提供的分布式锁要具备如下特性:
互斥性:同本地锁一样具有互斥性,但是分布式锁需要保证在不同节点进程的不同线程的互斥。
可重入性:同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
锁超时:和本地锁一样支持锁超时,防止死锁,通过异步心跳demon线程刷新过期时间,防止特殊场景(如FGC死锁超时)下死锁。
高性能、高可用:加锁和解锁需要高性能,同时也需要保证高可用防止分布式锁失效,可以增加降级。
支持阻塞和非阻塞:同ReentrantLock一样支持lock和trylock以及tryLock(long timeOut)。
公平锁和非公平锁(不支持):公平锁是按照请求加锁的顺序获得锁,非公平锁就相反是无序的,目前distributed-tools组件提供的分布式锁不支持该特性。

distributed-tools组件提供的分布式锁,使用起来非常简单,提供了一个分布式锁模板:DistributedLockTemplate,可以直接调用模板提供的静态方法(如下):

 /*** 分布式锁处理模板执行器* * @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,分布式系统中的网络延时或中断是避免不了的,通常会导致服务的调用层触发重试。具有这一性质的接口在设计时总是秉持这样的一种理念:调用接口发生异常并且重复尝试时,总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。

幂等通常会有两个维度:
1. 空间维度上的幂等,即幂等对象的范围,是个人还是机构,是某一次交易还是某种类型的交易。
2. 时间维度上的幂等,即幂等的保证时间,是几个小时、几天还是永久性的。

在实际系统中有很多操作,不管操作多少次,都应该产生一样的效果或返回相同的结果。以下这些应用场景也是通常比较常见的应用场景:
1. 前端重复提交请求,且请求数据相同时,后台需要返回对应这个请求的相同结果。
2. 发起一次支付请求,支付中心应该只扣用户账户一次钱,当遇到网络中断或系统异常时,也应该只扣一次钱。
3. 发送消息,同样内容的短信发给用户只发一次。
4. 创建业务订单,一次业务请求只能创建一个,重试请求创建多个就会出大问题。
5. 基于msgId的消息幂等处理

在正式使用distributed-tools组件提供的幂等之前,我们先看下distributed-tools幂等组件的设计。

  • 幂等key提取能力:获取唯一幂等key

    幂等key的提取支持2中注解:IdempotentTxId、IdempotentTxIdGetter,任意方法添加以上2注解,即可提取到相关幂等key,前提条件是需要将Idempotent注解添加相关需要幂等的方法上。

如果单纯使用幂等模板进行业务处理,需要自己设置相关幂等key,且要保证其唯一性。

  • 分布式锁服务能力:提供全局加锁、解锁的能力

    distributed-tools幂等组件需要使用自身提供的分布式锁功能,保证其并发唯一性,distributed-tools提供的分布式锁能够提供其可靠、稳定的加锁、解锁能力。
  • 高性能的写入、查询能力:针对幂等结果查询与存储

    distributed-tools幂等组件提供了基于tair、redis的存储实现,同时支持自定义一级、二级存储通过spring依赖注入到IdempotentService,建议distributed-tools幂等存储结果一级存储tair mdb,二级存储ldb或者tablestore,一级存储保证其高性能,二级存储保证其可靠性。

二级存储并行查询会返回查询最快的幂等结果。

二级存储并行异步写入,进一步提高性能。

  • 高可用的幂等写入、查询能力:幂等存储出现异常,不影响业务正常流程,增加容错

    distributed-tools幂等组件支持二级存储,为了保证其高可用,毕竟二级存储出现故障的概率太低,不会导致业务上不可用,如果二级存储同时出现故障,业务上做了一定的容错,针对不确定性的异常采取重试策略,会执行具体幂等方法。

一级存储与二级存储的写入与查询处理进行隔离,任何一级存储的异常不会影响整体业务执行。

在了解了distributed-tools组件幂等之后,接下来我们来看下如何去使用幂等组件,首先了解下common-api提供的幂等注解,具体幂等注解使用方式如下:

注解定义使用范围使用描述
Idempotent方法Idempotent需要定义到具体Method上。Idempotent有个属性定义:
expireDate表示幂等有效期,默认30天。
spelKey表示可以使用spring表达式生成幂等唯一ID,比如直接获取到对象属性或者方法或者其他表达式。
IdempotentTxId参数、对象属性IdempotentTxId可以直接定义到方法参数或者参数对象属性上,直接获取幂等ID
IdempotentTxIdGetter方法IdempotentTxIdGetter可以直接定义参数对象的方法上,调用该方法获取幂等ID

幂等拦截器获取幂等ID的优先级:

  1. 首先判断Idempotent的spelKey的属性是否为空,如果不为空会根据spelKey定义的spring表达式生成幂等ID。
  2. 其次判断参数是否包含IdempotentTxId注解,如果有IdempotentTxId,会直接获取参数值生成幂等ID。
  3. 再次通过反射获取参数对象属性是否包含IdempotentTxId注解,如果对象属性包含IdempotentTxId注解会获取该参数对象属性生成幂等ID。
  4. 最后以上三种情况仍未获取到幂等ID,会进一步通过反射获取参数对象的Method是否定义IdempotentTxIdGetter注解,如果包含该注解则通过反射生成幂等ID。

代码使用示例:

    @Idempotent(spelKey = "#request.requestId", firstLevelExpireDate = 7,secondLevelExpireDate = 30)public void execute(BizFlowRequest request) {..................}

如上述代码表示从request获取requestId作为幂等key,一级存储有效期7天,二级存储有效期30天。

distributed-tools除了可以使用幂等注解外,幂等组件还提供了一个通用幂等模板IdempotentTemplate,使用幂等模板的前提必须设置tair.idempotent.enabled=true或者redis.idempotent.enabled=true,默认为false,同时需要指定幂等结果一级存储,幂等结果存储为可选项配置。
具体使用幂等模板IdempotentTemplate的方法如下:

/*** 幂等模板处理器** @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) {........}

request:
幂等参数IdempotentRequest组装,可以设置幂等参数和幂等唯一ID

executeSupplier:
具体幂等的方法逻辑,比如针对支付、下单接口,可以通过JDK8函数式接口Supplier Callback进行处理。

resultBiConsumer:
幂等返回结果的处理,该参数可以为空,如果为空采取默认的处理,根据幂等结果,如果成功、不可重试的异常错误码,直接返回结果,如果失败可重试异常错误码,会进行重试处理。
如果该参数值不为空,可以针对返回幂等结果进行特殊逻辑处理设置ResultStatus(ResultStatus包含三种状态包括成功、失败可重试、失败不可重试)。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/517434.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RuoYi-Cloud 部署篇_01(linux环境 Oracle +nginx版本)

文章目录一、基础准备1. 技术选型2. 源码克隆3. 安装依赖4. 安装oracle5. 安装启动Mysql6. 安装启动Redis7. 创建数据库&#xff0c;执行 SQL脚本文件二、安装与配置 nacos2.1. 下载nacos2.2. 安装 nacos2.3. nacos持久化配置2.4. 执行脚本文件2.5. nacos连接 mysql 配置信息2.…

当60亿次攻击来袭,人机联合打了一场漂亮的防御战

云是大规模体量下各种小概率事件常态化的一个复杂场&#xff0c;云上的攻防对抗是攻击者和防御者在这张复杂场上的博弈与演化。大规模的环境之中充斥着各种各样转瞬即逝的信息&#xff0c;对于威胁&#xff0c;没有什么是比「大规模」和「转瞬即逝」还更好的隐匿与庇护。任何一…

RuoYi-Cloud 部署篇_02(linux环境 Oracle +nginx版本)

文章目录一、模块配置修改1. ruoyi-gateway-dev.yml2. ruoyi-auth-dev.yml3. ruoyi-system-dev.yml4. ruoyi-gen-dev.yml5. ruoyi-job-dev.yml6. ruoyi-file-dev.yml二、后端配置预启动2.1. 部署资料整合2.2. 模块端口划分2.3. 组件端口划分2.4. 服务脚本编写2.5. 前端编译生产…

一个多业务、多状态、多操作的交易链路?闲鱼架构这样演进

前言 双十一刚刚结束&#xff0c;成交额2684亿震惊全世界&#xff0c;每秒订单峰值达54.4W笔。在闲鱼2000万DAU&#xff0c;交易数额同样增长迅速的今天&#xff0c;我们如何保障交易链路的稳定与快速支撑业务&#xff1f;这篇文章从客户端开发的角度&#xff0c;介绍闲鱼交易…

RuoYi-Cloud 部署篇_03(linux环境 Oracle +nginx版本)

请参考RuoYi-Cloud 分布式部署_03&#xff08;linux环境 Mysqlnginxredis版本&#xff09;

没想到 Google 排名第一的编程语言,为什么会这么火?

没想到吧&#xff0c;Python 又拿第一了&#xff01; 在 Google 公布的编程语言流行指数中&#xff0c;Python 依旧是全球范围内最受欢迎的技术语言&#xff01;01为什么 Python 会这么火&#xff1f;核心还是因为企业需要用它&#xff01;因为其易用、逻辑简单并拥有海量扩展包…

写1行代码影响1000000000人,这是个什么项目?

不带钱不带卡&#xff0c;只带手机出门就能畅行无阻&#xff0c;这已是生活的常态。益普索发布的《2019第一季度第三方移动支付用户研究》报告显示&#xff0c;移动支付在手机网民中的渗透率高达95.1%&#xff0c;截至今年1月&#xff0c;支付宝全球用户数已经突破10亿。你或许…

高德客户端及引擎技术架构演进与思考

2019杭州云栖大会上&#xff0c;高德地图技术团队向与会者分享了包括视觉与机器智能、路线规划、场景化/精细化定位、时空数据应用、亿级流量架构演进等多个出行技术领域的热门话题。现场火爆&#xff0c;听众反响强烈。我们把其中的优秀演讲内容整理成文并陆续发布出来&#x…

免费直播:主流深度框架对比:总有一款适合你~

常常有小伙伴在后台反馈&#xff1a;想了解深度学习该怎么学&#xff1f;自学难度大又没有效果&#xff0c;该怎么办&#xff1f;CSDN为了解决这个难题&#xff0c;联合唐宇迪老师为大家带来了一场精彩的直播【一节课掌握深度学习必备框架】。本次直播将带大家了解在开始深度学…

Swift 在 GAIA 平台云端一体化的探索

作者|姜沂&#xff08;倾寒&#xff09; 出品|阿里巴巴新零售淘系技术部 S1 阶段在使用 SwiftUI 编写集团内部使用的 SOT APP 时&#xff0c;有幸参与到 GAIA &#xff08;FaaS&#xff09;平台云端一体化的探索&#xff0c;从头到尾实现了一套基于 Swift 语言实现的遵守 GAIA…

微信小程序实现刷脸登录

&#x1f3a8;领域&#xff1a;Java后端开发&#x1f525;收录专栏&#xff1a; 系统设计与实战 &#x1f412;个人主页&#xff1a;BreezAm &#x1f496;Gitee&#xff1a;https://gitee.com/BreezAm ✨个人标签&#xff1a;【后端】【大数据】【前端】【运维】 文章目录&am…

SOFAStack的前世今生

十二年前&#xff0c;为了解决支付宝第一代架构在迅猛发展的业务面前捉襟见肘的困境&#xff0c;蚂蚁金服技术团队开启了一次前所未有的尝试。创新都是被逼出来的&#xff0c;今天高速发展的SOFAStack同样如此。 十二年时间&#xff0c;几代蚂蚁技术人参与攻坚&#xff0c;SOFA…

从浪漫走向坚韧:开源数据库的演变

图&#xff1a;Peter Zaitsev作者 | Adrian Bridgwater译者 | 火火酱&#xff0c;责编| Carol“最初&#xff0c;所有的软件都是开源的。”——这是Percona首席执行官彼得扎伊采夫&#xff08;Peter Zaitsev&#xff09;在其公司今年虚拟年度用户/客户峰会上的开场白。如果我们…

中国数据库OceanBase登顶之路

今年天猫双11当天&#xff0c;OceanBase数据库再次刷新数据库处理峰值&#xff0c;达6100万次/秒&#xff0c;创造了新的世界纪录。 在此之前一个月&#xff0c;据权威机构国际事务处理性能委员会TPC披露&#xff1a;蚂蚁金服的分布式关系数据库OceanBase&#xff0c;打破美国…

github 访问慢,一键加速,完美访问

文章目录1. 下载dev-sidecar2. 安装3. 开机启动4. 安装证书5. 开启增强模式6. 重新启动dev-sidecar7. 测试验证1. 下载dev-sidecar https://gitee.com/docmirror/dev-sidecar/releases/v1.6.1 2. 安装 这里windows环境演示 双击安装 3. 开机启动 4. 安装证书 5. 开启增强…

给 AI 讲故事,如何教它脑补画面?

阿里妹导读&#xff1a;视觉想象力是人与生俱来的&#xff0c; AI 能否拥有类似的能力呢&#xff1f;比如&#xff1a;给出一段故事情节&#xff0c;如何让机器展开它的想象力&#xff0c;“脑补”出画面呢&#xff1f;看看阿里AI Labs 感知实验室的同学们如何解决这个问题。 …

产业数字化升级进入深化期,腾讯智慧出行释放“数字底座”核心能力

6月24日&#xff0c;腾讯智慧出行举办线上年度新品发布会。本次发布会以“创造出行新价值”为主题&#xff0c;发布了生态车联网产品TAI 3.0、全新一代自动驾驶虚拟仿真平台TAD Sim 2.0&#xff0c;以及汽车云数字营销解决方案、智慧交通解决方案&#xff0c;从深度和广度上展示…

MySQL与Redis的双写一致性解决方案

文章目录一、解决方案1.1 先更新缓存&#xff0c;再更新数据库1.2 先删除缓存&#xff0c;再更新数据库1.3 先更新数据库&#xff0c;再更新缓存二、总结一、解决方案 1.1 先更新缓存&#xff0c;再更新数据库 &#x1f4a3; 问题: 数据库更新成功了&#xff0c;但是由于异常…

解密双十一小程序云背后毫秒级伸缩的Serverless计算平台:函数计算

自2017年第一批小程序上线以来&#xff0c;越来越多的移动端应用以小程序的形式呈现。小程序拥有触手可及、用完即走的优点&#xff0c;这大大减少了用户的使用负担&#xff0c;使小程序得到了广泛的传播。在阿里小程序也被广泛地应用在淘宝/支付宝/钉钉/高德等平台上&#xff…

RuoYi-Cloud 进阶篇_01( Seata 单机环境搭建)

文章目录一、部署单机 TC Server1. 下载Seata2. 解压缩3. 启动4. 监听日志5. 启动命令讲解一、部署单机 TC Server 1. 下载Seata 在https://github.com/seata/seata/releases 这里找到对应的版本软件包进行下载 注&#xff1a;单机模式只适用于学习或者本地测试阶段&#xf…