范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

SpringCloud升级之路2020。0。x版38。实现自定义WebClient

  本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 实现 WeClient 的 NamedContextFactory
  我们要实现的是不同微服务自动配置装载不同的 WebClient Bean,这样就可以通过 NamedContextFactory 实现。我们先来编写下实现这个 NamedContextFactory 整个的加载流程的代码,其结构图如下所示:
  spring.factories # AutoConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.github.jojotech.spring.cloud.webflux.auto.WebClientAutoConfiguration
  在 spring.factories 定义了自动装载的自动配置类 WebClientAutoConfiguration
  WebClientAutoConfiguration @Import(WebClientConfiguration.class) @Configuration(proxyBeanMethods = false) public class WebClientAutoConfiguration { }
  WebClientAutoConfiguration 这个自动配置类 Import 了 WebClientConfiguration
  WebClientConfiguration @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(WebClientConfigurationProperties.class) public class WebClientConfiguration {     @Bean     public WebClientNamedContextFactory getWebClientNamedContextFactory() {         return new WebClientNamedContextFactory();     } }
  WebClientConfiguration 中创建了 WebClientNamedContextFactory 这个 NamedContextFactory 的 Bean。在这个 NamedContextFactory 中,定义了默认配置 WebClientDefaultConfiguration。在这个默认配置中,主要是给每个微服务都定义了一个 WebClient 定义 WebClient 的配置类
  我们编写下上一节定义的配置,包括: 微服务名称 微服务地址,服务地址,不填写则为 http://微服务名称 连接超时,使用 Duration,这样我们可以用更直观的配置了,例如 5ms,6s,7m 等等 响应超时,使用 Duration,这样我们可以用更直观的配置了,例如 5ms,6s,7m 等等 可以重试的路径,默认只对 GET 方法重试,通过这个配置增加针对某些非 GET 方法的路径的重试;同时,这些路径可以使用 * 等路径匹配符,即 Spring 中的 AntPathMatcher 进行路径匹配多个路径。例如 /query/order/**
  WebClientConfigurationProperties @Data @NoArgsConstructor @ConfigurationProperties(prefix = "webclient") public class WebClientConfigurationProperties {     private Map configs;     @Data     @NoArgsConstructor     public static class WebClientProperties {         private static AntPathMatcher antPathMatcher = new AntPathMatcher();         private Cache retryablePathsMatchResult = Caffeine.newBuilder().build();         /**          * 服务地址,不填写则为 http://serviceName          */         private String baseUrl;         /**          * 微服务名称,不填写就是 configs 这个 map 的 key          */         private String serviceName;         /**          * 可以重试的路径,默认只对 GET 方法重试,通过这个配置增加针对某些非 GET 方法的路径的重试          */         private List retryablePaths;         /**          * 连接超时          */         private Duration connectTimeout = Duration.ofMillis(500);         /**          * 响应超时          */         private Duration responseTimeout = Duration.ofSeconds(8);          /**          * 是否匹配          * @param path          * @return          */         public boolean retryablePathsMatch(String path) {             if (CollectionUtils.isEmpty(retryablePaths)) {                 return false;             }             return retryablePathsMatchResult.get(path, k -> {                 return retryablePaths.stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));             });         }     } }  粘合 WebClient 与 resilience4j
  接下来粘合 WebClient 与 resilience4j 实现断路器以及重试逻辑,WebClient 基于 project-reactor 实现,resilience4j 官方提供了与 project-reactor 的粘合库:       io.github.resilience4j     resilience4j-reactor 
  参考官方文档,我们可以像下面这样给普通的 WebClient 增加相关组件:
  增加重试器 : //由于还是在前面弄好的 spring-cloud 环境下,所以还是可以这样获取配置对应的 retry Retry retry; try {     retry = retryRegistry.retry(name, name); } catch (ConfigurationNotFoundException e) {     retry = retryRegistry.retry(name); }  Retry finalRetry = retry; WebClient.builder().filter((clientRequest, exchangeFunction) -> {     return exchangeFunction.exchange(clientRequest)         //核心就是加入 RetryOperator         .transform(RetryOperator.of(finalRetry)); })
  这个 RetryOperator 其实就是使用了 project-reactor 中的 retryWhen 方法实现了 resilience4j 的 retry 机制:
  RetryOperator @Override public Publisher apply(Publisher publisher) {     //对于 mono 的处理     if (publisher instanceof Mono) {         Context context = new Context<>(retry.asyncContext());         Mono upstream = (Mono) publisher;         return upstream.doOnNext(context::handleResult)             .retryWhen(reactor.util.retry.Retry.withThrowable(errors -> errors.flatMap(context::handleErrors)))             .doOnSuccess(t -> context.onComplete());     } else if (publisher instanceof Flux) {         //对于 flux 的处理         Context context = new Context<>(retry.asyncContext());         Flux upstream = (Flux) publisher;         return upstream.doOnNext(context::handleResult)             .retryWhen(reactor.util.retry.Retry.withThrowable(errors -> errors.flatMap(context::handleErrors)))             .doOnComplete(context::onComplete);     } else {         //不可能是 mono 或者 flux 以外的其他的         throw new IllegalPublisherException(publisher);     } }
  可以看出,其实主要填充了: doOnNext(context::handleResult): 在有响应之后调用,将响应结果传入 retry 的 Context,判断是否需要重试以及重试间隔是多久,并且抛出异常 RetryDueToResultException retryWhen(reactor.util.retry.Retry.withThrowable(errors -> errors.flatMap(context::handleErrors))):捕捉异常 RetryDueToResultException,根据其中的间隔时间,返回 reactor 的重试间隔:Mono.delay(Duration.ofMillis(waitDurationMillis)) doOnComplete(context::onComplete):请求完成,没有异常之后,调用 retry 的 complete 进行清理
  增加断路器 : //由于还是在前面弄好的 spring-cloud 环境下,所以还是可以这样获取配置对应的 circuitBreaker CircuitBreaker circuitBreaker; try {     circuitBreaker = circuitBreakerRegistry.circuitBreaker(instancId, finalServiceName); } catch (ConfigurationNotFoundException e) {     circuitBreaker = circuitBreakerRegistry.circuitBreaker(instancId); }  CircuitBreaker finalCircuitBreaker = circuitBreaker; WebClient.builder().filter((clientRequest, exchangeFunction) -> {     return exchangeFunction.exchange(clientRequest)         //核心就是加入 CircuitBreakerOperator         .transform(CircuitBreakerOperator.of(finalCircuitBreaker)); })
  类似的,CircuitBreakerOperator 其实也是粘合断路器与 reactor 的 publisher 中的一些 stage 方法,将结果的成功或者失败记录入断路器,这里需要注意,可能有的链路能走到 onNext,可能有的链路能走到 onComplete,也有可能都走到,所以 这两个方法都要记录成功,并且保证只记录一次 :
  CircuitBreakerSubscriber class CircuitBreakerSubscriber extends AbstractSubscriber {      private final CircuitBreaker circuitBreaker;      private final long start;     private final boolean singleProducer;      private final AtomicBoolean successSignaled = new AtomicBoolean(false);     private final AtomicBoolean eventWasEmitted = new AtomicBoolean(false);      protected CircuitBreakerSubscriber(CircuitBreaker circuitBreaker,         CoreSubscriber<? super T> downstreamSubscriber,         boolean singleProducer) {         super(downstreamSubscriber);         this.circuitBreaker = requireNonNull(circuitBreaker);         this.singleProducer = singleProducer;         this.start = circuitBreaker.getCurrentTimestamp();     }      @Override     protected void hookOnNext(T value) {         if (!isDisposed()) {              //正常完成时,断路器也标记成功,因为可能会触发多次(因为 onComplete 也会记录),所以需要 successSignaled 标记只记录一次             if (singleProducer && successSignaled.compareAndSet(false, true)) {                 circuitBreaker.onResult(circuitBreaker.getCurrentTimestamp() - start, circuitBreaker.getTimestampUnit(), value);             }             //标记事件已经发出,就是已经执行完 WebClient 的请求,后面判断取消的时候会用到             eventWasEmitted.set(true);              downstreamSubscriber.onNext(value);         }     }      @Override     protected void hookOnComplete() {         //正常完成时,断路器也标记成功,因为可能会触发多次(因为 onNext 也会记录),所以需要 successSignaled 标记只记录一次         if (successSignaled.compareAndSet(false, true)) {             circuitBreaker.onSuccess(circuitBreaker.getCurrentTimestamp() - start, circuitBreaker.getTimestampUnit());         }          downstreamSubscriber.onComplete();     }      @Override     public void hookOnCancel() {         if (!successSignaled.get()) {             //如果事件已经发出,那么也记录成功             if (eventWasEmitted.get()) {                 circuitBreaker.onSuccess(circuitBreaker.getCurrentTimestamp() - start, circuitBreaker.getTimestampUnit());             } else {                 //否则取消                 circuitBreaker.releasePermission();             }         }     }      @Override     protected void hookOnError(Throwable e) {         //记录失败         circuitBreaker.onError(circuitBreaker.getCurrentTimestamp() - start, circuitBreaker.getTimestampUnit(), e);         downstreamSubscriber.onError(e);     } }
  我们会使用这个库进行粘合,但是不会直接使用上面的代码,因为考虑到: 需要在重试以及断路中加一些日志,便于日后的优化 需要定义重试的 Exception,并且与断路器相结合,将非 2xx 的响应码也封装成特定的异常 需要在断路器相关的 Operator 中增加类似于 FeignClient 中的负载均衡的数据更新,使得负载均衡更加智能
  在下面一节我们会详细说明我们是如何实现的有断路器以及重试逻辑和负载均衡数据更新的 WebClient。

女生赶海涨潮后发现遍地鞋套图片来源于网络近日,天津,一女生去赶海,涨潮后发现沙滩上遍地都是鞋套。李女士称,当天去公园玩,到了海滩就看见很多鞋套都扔在了海边上,当时自己很震惊。现在是冬天,怕弄湿鞋子都买鞋套可野鸭湖变天鹅湖,300余只天鹅飞临休憩北京日报客户端记者李瑶通讯员刘嘉豪3月1日,气温回暖,冰面日渐消融,位于延庆区的野鸭湖湿地自然保护区内迎来了300余只天鹅,春日暖阳下,这些美丽的精灵们在广阔的湖面休憩觅食,野鸭湖春日上新!生日派对水上研学人气龙舟三月来福州内河收获惊喜榕城船说我们上新啦这个春天来福州内河你将收获独具特色的微动力龙舟乐享冷暖空调新型游船体验首条内河研学路线度身定制水上生日派对内河文化调研公益活动三月春日模式正式开启!跟着我一起打卡福州内河花开宁德寿宁水洋古村樱开春意浓扮靓一座村,只需一朵花,云端山村,此刻正烂漫。驱车盘旋在山间,两道粉红的山樱花点缀在苍翠之间。进入村庄,走在石梯,拾级而上,两侧,高耸粗砺的夯土墙或新式的民宿高低错落。走在青石板路贵州又到一年赏花季春季旅游百花开春风送暖,新绿正自南向北不断推移,日渐深浓。一年中色彩最丰富的季节来了,赏花地图不断更新上线,吸引人们纷纷走出家门去瞧瞧贵州的春。万峰林景区福字油菜花田此时的万峰林,别有一番风味,到能仁寺观千年大铁镬,去跻鹿岩看芙蓉美山水到能仁寺观千年大铁镬,去跻鹿岩看芙蓉美山水2023年的1月30日是农历癸卯兔年的正月初九,十来天没出去骑行的小伙伴终于相约一起骑去乐清的雁荡。早起,我一师和二马三人驾两车至雁荡镇,TourismmarketrecoveryinChinasHainanaccelerates来源原创稿TourismconsumptioninSouthChinasHainanProvincehasacceleratedthankstothestrongrecoveryo富春江边,即将诞生两家度假酒店,就在文楼矣NO。1壹三山火葬场(富阳殡仪馆)边上要建度假酒店了!前天,市规划资源局富阳分局发布了一则杭州市富阳区新桐1号地块选址(规划条件)论证报告(草案)公示。地块概况选址地块位于富桃醉当下诗意尖山尖山桃花会3月3日盛大开幕自贡网记者张翠娜又到桃花盛开时。近日,笔者从自贡城投集团建工公司获悉,2023四川省花卉(果类)生态旅游节子活动暨自贡市第二十三届自流井尖山桃花会将于3月3日开幕。本届桃花会由自流海南省发布苏东坡居儋十六论研究成果日前,在首届中国(海南)东坡文化旅游大会期间,海南省儋州市举行第六届东坡居儋思想文化研讨会。来自中国苏轼研究学会以及数十家高校科研机构等专家学者150多人参与研讨。中国苏轼研究学会中山I2天游,一路感受历史文化熏陶,顺便看动物,也可假装在国外赞行程攻略轻装出行,交通便利。在广州南站出发,坐高铁到中山南朗站(50元,50分钟左右),然后南朗站坐218路公交到孙中山故居站下车。可以在微信上关注小程序乘车码,开通中山市地区的
家里什么条件,就提供给孩子什么样的生活,拔高养真的不可取你给孩子制造梦境,一片安宁祥和。可梦醒时分,世界就会露出它真实的模样。男孩要穷养,女孩要富养。这句话好似揭示了所有中国父母养儿育女的标准规则。而我觉得这话实在是有点摸不着边际的扯淡家里有孩子的,妈妈一定要学会这6道汤,秋冬常喝既长身体又补钙大家好,这里是小慧今天说美食,秋天是孩子长高的黄金时期,营养要跟得上,聪明的妈妈都会抓住这个时机,常给孩子喝这六道汤,让孩子个头在蹿一蹿,这六道汤秋冬常喝既长身体又补钙,做法简单零宅在家里就能把一个省份吃透,原来也不难朋友们,要说今年编辑部最心水的省份,福建绝对跻身前三。菜菜隔三差五就往那边跑,好酒店基本都住了个遍磊磊也是精神福建人,坐着高铁把厦门泉州沙县逛吃了一整圈。而我因为大学在泉州,本身就新疆,神之禾木媲美北欧同款雪景你不知道的新疆!它占据了960万平方公里的六分之一,比广袤无边的西藏还要大40多万平方公里。也正因如此,它装载了中国80的美景。它地处亚洲大陆的中心,是离海洋最远的地方,在清朝前,北京外援发威,拿下苏州取得两连胜!实力悬殊新疆轻取宁波上一轮刚刚战胜吉林队的北京在本轮CBA联赛中乘胜追击,又以95比82战胜苏州肯帝亚队,第一节开始后,苏州队由外援布莱克尼率先得分,但北京队很快做出回应,张才仁的抛投为球队扳平比分,现代诗(77)东方东方有一颗金太阳,红色传承初心不忘,中华儿女常把她久久仰望,九州大地,岁月激荡,民族历史盛世绵长。光明与希望,青春与梦想,燃烧了中国,虎跃龙腾在世界东方!东方有一轮圆月亮,思亲恩情边界感和分寸感很重要这个世界上,人与人之间的关系是最简单也最复杂,关系太近,怕有隔阂,太过疏远,怕会失去。其实最好的状态就是保持边界感和分寸感。如果距离太近,很有可能会让对方觉得窒息。如果毫无原则和底无论情人还是夫妻,当对方不主动联系你时,这么做才是有远见人在感情里,最怕的是痴心错付,把一腔真情和热忱,给了不值得的人开始时,对对方寄予的希望,所付出的努力和改变,最终都化为泡影,这种滋味非常难受,让人痛苦却也无奈在爱情里,一个人爱了就国乒签位出炉!马龙王楚钦约战张本智和,陈梦有望阻击伊藤美诚北京时间10月18日,乒乓球WTT冠军赛抽签结果正式出炉,其中马龙王楚钦当中的一人有望率先阻击张本智和,陈梦可能成为首位迎战伊藤美诚的国乒选手,而梁靖崑林高远第一轮就会碰头,真是刺家乡的枣儿红了昨日,亲戚从家乡过来,带来了一篮红艳艳的大红枣,一颗颗红玛瑙似的晶莹剔透,咬一口,又脆又甜。使我想起家乡门囗那颗红枣树。在那个物质匮乏的年代。门口树上的大红枣,是我童年不多得的美食遥远的另一个我一睁眼睛,来到了2017年1月1日,大三,我还在上大学。带着5年的记忆来到了过去,我想,从现在回到过去的人应该都会更认真地思考自己的人生吧。大三,我选择了考研,跨专业考现当代文学,沈知渝303欧篮联摩纳哥vs特拉维夫马卡比303欧篮联摩纳哥vs特拉维夫马卡比欧篮联的比赛已经进行了两轮,目前2战全胜的队伍有5个队,其中就有摩纳哥,另外四个队是费内巴切,巴斯克尼亚,奥林匹亚科斯和阿尔巴柏林。摩纳哥和去年120万年薪,加盟湾区翼龙队!刘传兴已经打了6场比赛,表现如何?202021赛季,刘传兴场均能够得到9。4分,8。1篮板,投篮命中率高达66。8。因个人出色的表现,他被选进了当赛季的CBA全明星阵容。可以说,当时的刘传兴,既是国内最有前途的新人MySQL集群搭建1,软件MySQL版本mysql5。7。29下载链接httpsdownloads。mysql。comarchivescommunity注建议使用迅雷下载,速度较快2,系统挂盘查看磁车仔面大叔体验极狐阿尔法S全新HI版自动驾驶2022年5月极狐阿尔法S全新HI版正式上市,新车分为进阶版和高阶版两个版本,进阶版售价39。79万元,高阶版售价42。99万元。极狐阿尔法S全新HI版由极狐与华为联手打造,也是全想在互联网上赚到钱,这四点必须掌握很多人认为,在互联网赚不到钱,是因为没天赋,没资源,运气也不好,实际不是的,只是你没有掌握正确的方法和思路。今天我把多年的互联网赚钱经验分享出来,希望能给你一点启发!想要在互联网赚乡土记忆之千年古镇渔洋对渔洋倾心已久。再闻渔洋,是在一个文化论坛读到关于渔洋的文章,颇为感叹。渔洋亦如一颗璀璨明珠,穿越数千年历史时空,熠熠生辉,愈发厚重。时光不羁,岁月匆匆。历史如此悠久,文化积淀如此两通一达顺丰德邦入选,上海发布快递服务制造业典型案例日前,上海多部门联合发布快递业与制造业融合发展典型案例,其中包括顺丰圆通中通韵达德邦等快递企业在内的15个案例入选。其中包括圆通助力大飞机翱翔蓝天德邦与服装行业融合中通快递服务米其上汽29亿入局,换电赛道开启持久战撰文凯旋编辑华锋近日,媒体报道上汽集团公告称,其子公司上汽集团金控管理拟合资设立合伙企业,出资总额15。51亿元,其中上汽金控为主要出资方,出资高达14亿,以投资新能源汽车充电换电倪萍,三婚嫁杨亚洲,在家也不做饭,如今她还好吗?一次,倪萍在化妆准备出去和朋友吃饭。电话突然响了起来,倪萍忙着化妆没有空去接电话。于是就对王文澜说你去接一下电话。王文澜说了一声好的,然后他看了一眼时间心想这个点谁会打电话来。想了支付宝史诗级更新,网友吐槽不如不更要说现在的互联网APP最大的特点是什么,黑马第一时间就想到了一个词孤岛。这是因为各大互联网巨头旗下的相关APP,除了自己旗下的产品之外基本都不怎么互通,一个二个都在试图圈地为王搞出多位主播坐镇虎牙饭堂,犀利点评S12各战队,并对后续进行预测随着英雄联盟S12世界赛小组赛第二轮结束,八强正式出炉,其中LCK赛区的T1DKDRX和GEN悉数晋级LPL赛区的EDGJDG和RNG携手出线,而2号种子TES则遗憾出局止步16强