【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

微服务架构的四大杀手锏

  • 2020-04-23
  • 本文字数:6017 字

    阅读完需:约 20 分钟

微服务架构的四大杀手锏

概述

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


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


distributed-tools 组件基于 tair、redis 分别提供了 2 个 springboot starter ,使用起来非常简单。


以使用缓存使用 redis 为例, application.properties 添加如下配置:


redis.extend.hostName=127.0.0.1redis.extend.port=6379redis.extend.password=pwdcoderedis.extend.timeout=10000
redis.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> 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 删除缓存数据。

复制代码

限流

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


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


 /**     * 指定过期时间自增计数器,默认每次+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);
复制代码


基于 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)@Documentedpublic @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;}
复制代码


任何方法添加上述注解具备了一定的限流能力(具体方法需要在 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 提供的幂等注解,具体幂等注解使用方式如下:



幂等拦截器获取幂等 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)    publicvoidexecute(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 。


本文转载自技术琐话公众号。


原文链接:https://mp.weixin.qq.com/s/74MbVTiUhWMJJx_-uktFNg


2020-04-23 17:361203

评论

发布
暂无评论
发现更多内容

如何使用CORS来允许设置Cookie

百度搜索:蓝易云

Linux 运维 CORS Cookie 云服务器

ON1 NoNoise AI 2024 for Mac(AI图片降噪工具)v18.2.0.15224激活版

影影绰绰一往直前

DBeaverEE for Mac(数据库管理)v24.0.1企业激活版

影影绰绰一往直前

FxFactory 8 Pro for Mac(视觉特效处理包)v8.0.14激活版

影影绰绰一往直前

央视财经两名 AI 主播“上岗”:能 24 小时解答经济产业、旅游等领域问题!

青否数字人

数字人

华为联合伙伴发布公共数据授权运营方案,助力云上点数成金

华为云开发者联盟

云计算 华为云 华为云开发者联盟

Navicat Charts Viewer for Mac(Navicat图表查看器) v1.2.16激活版

影影绰绰一往直前

数字人堪比真人这项交互数字人技术太牛了!

青否数字人

数字人

零基础入门数据挖掘系列之「建模调参」

阿里云天池

阿里云

如何实现对iframe的自动化测试,一篇文章告诉你

霍格沃兹测试开发学社

梧桐树上凤凰来:“伙伴+华为”体系的建成与强化

脑极体

数智化转型

MediaHuman YouTube to MP3 Converter mac v3.9.9.89中文注册版

影影绰绰一往直前

注入js编程应该知道的一些基础知识,以防混淆造成的错误

Geek_ee6d52

企业有官网了还有必要做小程序吗?

天津汇柏科技有限公司

软件开发定制

一文带你详解天池电商数据集

阿里云天池

阿里云

一文带你详解天池工业数据集

阿里云天池

阿里云

鸿蒙x昇腾云:华为打造智能时代最佳AI基础设施

华为云开发者联盟

鸿蒙 华为云 华为云开发者联盟 AI基础设施 昇腾云

DBeaverUE for Mac(数据库管理软件)v24.0.1旗舰激活版

影影绰绰一往直前

QCN9074, QCN6274 ,QCN6224- High performance - low power - for wide range of application

wifi6-yiyi

QCN9074 QCN6224

JProfiler for Mac(Java开发分析软件)v14.0.1永久激活版

影影绰绰一往直前

Datawhale 零基础入门数据挖掘-Task1 赛题理解

阿里云天池

阿里云

文心一言赋能问卷生成,打造高效问卷调研工具

飞桨PaddlePaddle

json 百度 百度飞桨 开发者说 文心一言

2023 亚马逊云科技 re:Invent 大会探秘:Aurora 无限数据库的突破性应用

亚马逊云科技 (Amazon Web Services)

云计算

MediaHuman YouTube Downloader mac v3.9.9.89中文注册版

影影绰绰一往直前

CheckBook Pro for mac(优秀的个人理财管理软件) v2.7.29激活版

影影绰绰一往直前

「数据分析」之零基础入门数据挖掘

阿里云天池

阿里云

Syncovery for mac(文件备份和同步工具)v10.12.4激活版

影影绰绰一往直前

[目标检测]基于YOLOV8的自定义数据集实现水印检测

alexgaoyh

yolov8 ultralytics 自定义数据集 水印识别 模型训练/测试

「全栈低代码」时代开启,页面开发/数据处理/复杂逻辑,统统一站搞定!

优秀

低代码 全栈低代码

Python实践:基于Matplotlib实现某产品全年销量数据可视化

华为云开发者联盟

Python 后端 开发 华为云 华为云开发者联盟

青否数字人发布2D超写实数字人直播平台!

青否数字人

数字人

微服务架构的四大杀手锏_架构_技术琐话_InfoQ精选文章