• [技术干货] 微服务的熔断降级限流通俗易懂
    1、引言在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC)。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这种现象就是“服务雪崩”。1.png如上图图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。另外,在大中型分布式系统中,通常系统很多依赖(HTTP,hession,Netty,Dubbo等),如下图:2.png在高并发访问下,这些依赖的稳定性与否对系统的影响非常大,但是依赖有很多不可控问题:如网络连接缓慢,资源繁忙,暂时不可用,服务脱机等。如下图:QPS为50的依赖 I 出现不可用,但是其他依赖仍然可用:3.png当依赖I 阻塞时,大多数服务器的线程池就出现阻塞,影响整个线上服务的稳定性。如下图:4.png在复杂的分布式架构的应用程序有很多的依赖,都会不可避免地在某些时候失败。高并发的依赖失败时如果没有隔离措施,当前应用服务就有被拖垮的风险。很多服务框架会提供服务重试机制,在很大程度上解决了由于网络瞬时不可达的问题。但是在很多的情况下造成服务雪崩的元凶正是服务重试机制。某个服务本来就已经出现问题了,造成资源占用无法释放、请求延时等问题。这时在请求失败之后又不断的发送重试请求,在原本就无法释放的资源基础上继续膨胀式占用,导致整个系统资源耗尽。导致服务雪崩。要防止系统发生雪崩,就必须要有容错设计。如果遇到突增流量,一般的做法是对非核心业务功能采用熔断和降级的措施来保护核心业务功能正常服务,而对于核心功能服务,则需要采用限流的措施。2、熔断熔断来自英文短语circuit breaker,circuit breaker在电工学里就是断路器的意思。它就相当于一个开关,打开后可以阻止流量通过。比如保险丝,当电流过大时,就会熔断,从而避免元器件损坏。服务熔断是指调用方访问服务时通过断路器做代理进行访问,断路器会持续观察服务返回的成功、失败的状态,当失败超过设置的阈值时断路器打开,请求就不能真正地访问到服务了。5.jpeg一般而言,熔断状态不会一直持续,而是有一个时间范围,时间过了以后再去尝试请求服务提供者,一旦服务提供者的服务能力恢复,请求将继续可以调用服务提供者。断路器的状态转换图如下:6.png断路器默认处于“关闭”状态,当服务提供者的错误率到达阈值,就会触发断路器“开启”。断路器开启后进入熔断时间,到达熔断时间终点后重置熔断时间,进入“半开启”状态在半开启状态下,如果服务提供者的服务能力恢复,则断路器关闭熔断状态。进而进入正常的服务状态。在半开启状态下,如果服务提供者的服务能力未能恢复,则断路器再次触发服务熔断,进入熔断时间。3、降级服务熔断通常适与服务降级配合使用。在服务发生熔断后,一般会让请求走事先配置的处理方法,这个处理方法就是一个降级逻辑。简言之,服务降级是一种兜底的服务策略,体现了一种“实在不行就怎么这么样”的思想。想去北京买不到飞机票,实在不行就坐高铁去吧;感冒了想去看病挂不上号,实在不行就先回家吃点药睡一觉吧;实在不行之后的处理方法,被称为fallback方法。当服务提供者故障触发调用者服务的熔断机制,服务调用者就不再调用远程服务方法,而是调用本地的fallback方法。此时你需要预先提供一个处理方法,作为服务降级之后的执行方法,fallback返回值一般是设置的默认值或者来自缓存。7.png除了可以在服务调用端实现服务降级,还可以在服务提供端实现服务降级。8.png实际上在大型的微服务系统中,服务提供者和服务消费者并没有严格的区分,很多的服务既是提供者,也是消费者。4、限流4.1 为什么需要限流举一个我们生活中的例子:一些热门的旅游景点,往往会对每日的旅游参观人数有严格的限制,比如厦门的鼓浪屿、北京的故宫等,每天只会卖出固定数目的门票,如果你去的晚了,可能当天的票就已经卖完了,当天就无法进去游玩了。为什么旅游景点要做这样的限制呢?多卖一些门票多赚一些钱岂不是更好?原因在于景点的服务资源是有限的,每日能服务的人数是有限的,一旦放开限制了,景点的工作人员就会不够用,卫生情况也得不到保障,安全也有隐患,超密集的人群也会严重的影响游客的体验。 但由于景区名气大,来游玩的旅客络绎不绝,远超出了景区的承载能力,因此景区只好做出限制每日人员流量的举措。同理,在IT软件行业中,系统服务也是这样的。服务限流是指当系统资源不够,不足以应对大量请求,即系统资源与访问量出现矛盾的时候,我们为了保证有限的资源能够正常服务,因此对系统按照预设的规则进行流量限制或功能限制的一种方法。如果你的系统理论是时间单位内可服务100W用户,但是今天却突然来了300W用户,由于用户流量的随机性,如果不加以限流,很有可能这300W用户一下子就压垮了系统,导致所有人都得不到服务。因此为了保证系统至少还能为100W用户提供正常服务,我们需要对系统进行限流设计。有的人可能会想,既然会有300W用户来访问,那为啥系统不干脆设计成能足以支撑这么大量用户的集群呢?这是个好问题。如果系统是长期有300W的用户来访问,肯定是要做上述升级的,但是常常面临的情况是,系统的日常访问量就是100W,只不过偶尔有一些不可预知的特定原因导致的短时间的流量激增,这个时候,公司往往出于节约成本的考虑,不会为了一个不常见的尖峰来把我们的系统扩容到最大的尺寸。转载自作者:冰河winner链接:https://www.jianshu.com/p/5d2b7cea1f11
  • [技术干货] 15 种微服务架构框架
    这几年来,微服务这个概念越来越火了,火到什么程度呢?2019年有一个统计说,两千家企业里,45%在使用微服务,16%在实验开发和测试微服务架构,24%在学习微服务准备转型,只有剩下的15%的企业没有使用微服务。微服务到底有什么好呢?微服务在2013年才被提出,短短几年就有这么快速的发展。微服务架构能够实现由小型自主服务组成一个整体应用,各个组成部分之间是松耦合的,复杂性低,各个部分可以独立部署,修复bug或者引入新特性更容易,能够独立扩展,不同技术栈之间可以使用不同框架、不同版本库甚至不同的操作系统平台。对于中大型架构系统来说,微服务更加便捷,微服务成为很多企业架构重构的方向,同时也对架构师提出更高的挑战。目前有很多常用于微服务构建的框架,对于构建微服务架构能够带来一些帮助。Java语言相关微服务框架 Spring Boot Spring Boot的设计目的是简化新Spring应用初始搭建以及开发过程,2017年有64.4%的受访者决定使用Spring Boot,可以说是最受欢迎的微服务开发框架。利用Spring Boot开发的便捷度简化分布式系统基础设施的开发,比如像配置中心、注册、负载均衡等方面都可以做到一键启动和一键部署。 Spring Cloud Spring Cloud是一个系列框架的合计,基于HTTP(s)的RETS服务构建服务体系,Spring Cloud能够帮助架构师构建一整套完整的微服务架构技术生态链。Dubbo Dubbo是由阿里巴巴开源的分布式服务化治理框架,通过RPC请求方式访问。Dubbo是在阿里巴巴的电商平台中逐渐探索演进所形成的,经历过复杂业务的高并发挑战,比Spring Cloud的开源时间还要早。目前阿里、京东、当当、携程、去哪等一些企业都在使用Dubbo。Dropwizard Dropwizard将Java生态系统中各个问题域里最好的组建集成于一身,能够快速打造一个Rest风格的后台,还可以整合Dropwizard核心以外的项目。国内现在使用Dropwizard还很少,资源也不多,但是与Spring Boot相比,Dropwizard在轻量化上更有优势,同时如果用过Spring,那么基本也会使用Spring Boot。 Akka Akka是一个用Scala编写的库,可以用在有简化编写容错、高可伸缩性的Java和Scala的Actor模型,使用Akka能够实现微服务集群。Vert.x/Lagom/ReactiveX/Spring 5 这四种框架主要用于响应式微服务开发,响应式本身和微服务没有关系,更多用于提升性能上,但是可以和微服务相结合,也可以提升性能。.Net相关微服务框架 .NET Core .NET Core是专门针对模块化微服务架构设计的,是跨平台应用程序开发框架,是微软开发的第一个官方版本。 Service Fabric Service Fabric是微软开发的一个微服务框架,基于Service Fabric构建的很多云服务被用在了Azure上。 Surging Surging是基于RPC协议的分布式微服务技术框架,基于.NET Core而来。 Microdot Framework Microdot Framework用于编写定义服务逻辑代码,不需要解决开发分布式系统的挑战,能够很方便的进行MicrosoftOrleans集成。Node.js相关微服务框架 Seneca Seneca是Node.js的微服务框架开发工具,可以用于编写可用于产品环境的代码。 Hapi/Restify/LoopBack 这三种框架的分工不同,前两种更适合开发简单的微服务后端系统,第三种更适合用在大型复杂应用开发,还可以用在现有微服务上的构建。Go相关微服务框架 Go-Kit/Goa/Dubbogo Go-Kit是分布式开发的工具合集,适合用于大型业务场景下构建微服务;Goa是用Go语言构建的微服务框架;Dubbogo是和阿里巴巴开源的Dubbo能够兼容的Golang微服务框架。Python相关微服务框架Python相关的微服务框架非常少,用的比较多的是Nameko。Nameko让实现微服务变得更简单,同时也提供了很丰富的功能,比如支持负载均衡、服务发现还支持依赖自动注入等,使用起来很方便,但是有限速、超时和权限机制不完善等缺点。总结微服务已经成为很多大型互联网公司的选择,对于架构师和想要成为架构师的工程师来说,掌握微服务不仅要学会使用相关框架来实现,还要掌握具体用法,在具体的实践中仍然要避开很多坑。原文链接:https://blog.csdn.net/qq_37217713/article/details/108114441
  • 微服务框架springcloud
    基础 单体架构:将业务全部功能集中到一个项目中,打成一个war包存储,部署在一台服务器中,只有一个数据库 优点 :架构简单,部署成本低。适合小型项目 问题:高并发性能问题,开发时代码耦合问题,部署升级时停服的问题 垂直架构:拆分模块,每个模块使用自己的数据库,如果有模块需要其他模块数据时需要自己查对方模块数据库 问题:大量代码冗余,系统难以维护,性能问题,部署问题 分布式架构:根据业务功能对系统做拆分,每个业务功能作为独立项目开发,称为一个服务 服务之间相互调用,分布式多节点部署  优点:降低耦合,有利于服务升级和拓展  适合大型互联网项目  缺点:服务调用关系错综复杂  服务拆分的粒度如何界定? 服务之间如何调用? 服务的调用关系如何管理?  SOA架构Service-Oriented Architecture,即面向服务的架构,它可以根据需求通过网络对松散耦合的粗粒度应用组件(服务)进行分布式部署、组合和使用。一个服务通常以独立的形式存在于操作系统进程中。  特点:分布式、可重用、扩展灵活、松耦合  解决的问题:  系统集成 企业系统不断发展过程中,会存在N多系统间相互调用,系统间的关系可能会是一个比较杂乱的网状结构。引入SOA来完成服务之间关系的有序化,这一步需要引入一些产品,比如 ESB、以及技术规范、服务管理规范 系统服务化 完成服务的复用。比如之前可能是 各个系统都写了一套登录注册、发邮件、发短信等功能。现在可以将 登录注册、发邮件、发短信等功能逻辑抽象成可复用、可组装的服务,通过合理的服务编排,实现业务功能的快速复用。 业务服务化 完成企业系统的对外服务能力。将业务单元(如:OA系统、财务系统等)封装成一项服务。 优点:  抽取公共的功能为服务,提高开发效率 对不同的服务进行集群化部署解决系统压力 基于ESB/DUBBO减少系统耦合 缺点:  抽取服务的粒度较大 服务提供方与调用方接口耦合度较高 微服务架构 微服务 微服务不是一种框架,而且一种架构思想。微服务架构的系统是个分布式系统,按业务领域划分为独立的服务单元,有自动化运维、容错、快速演进的特点,它能够解决传统单体架构系统的痛点,同时也能满足越来越复杂的业务需求。简而言之,微服务架构的风格,就是将单一程序开发成一个微服务,每个微服务运行在自己的进程中,并使用轻量级通信机制,通常是HTTP  、 RESTFUL  、API 。这些服务围绕业务能力来划分构建的,并通过完全自动化部署机制来独立部署这些服务,这些服务可以使用不同的编程语言,以及不同的数据存储技术,以保证最低限度的集中式管理。  理解:就是将一个大的应用拆分为多个小的模块,每个模块都有自己的功能和职责,每个模块可以进行交互,这就是微服务。  1.单一职责,每一个服务对应唯一的业务能力,做到单一职责  2.自治,团队独立,技术独立,数据独立,独立部署和交付  3.面向服务,服务提供统一的接口,与语言技术无关  4.隔离性强,服务调用做好隔离,容错,降级避免出现级联问题(级联故障是由于正反馈循环并且随着时间的增加所产生的故障。典型表现:最初由单个节点或子系统故障触发的连锁反应)  微服务进一步降低服务之间的耦合,提供服务的独立性和灵活性。微服务是一种经过良好架构设计的分布式架构方案。  背景 单体架构不足  1.业务越来越复杂,单体应用的代码量越来越大,代码的可读性、可维护性和可扩展性下降,新人接手代码所需的时间成倍增加,业务扩展带来的代价越来越大。 2.随着用户越来越多,程序承受的并发越来越高,单体应用的并发能力有限。 3.测试的难度越来越大,单体应用的业务都在同个程序中,随着业务的扩张、复杂度的增加,单体应用修改业务或者增加业务或许会给其他业务带来定的影响,导致测试难度增加。  微服务技术的对比  CAP 理论 CAP 也就是 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性) 这三个单词首字母组合。 CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细定义 Consistency、Availability、Partition Tolerance 三个单词的明确定义。  在理论计算机科学中,CAP 定理(CAP theorem)指出对于一个分布式系统来说,当设计读写操作时,只能同时满足以下三点中的两个:  一致性(Consistency) : 所有节点访问同一份最新的数据副本 可用性(Availability): 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。 分区容错性(Partition Tolerance) : 分布式系统出现网络分区的时候,仍然能够对外提供服务。 什么是网络分区?  分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫 网络分区。  当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。  简而言之就是:CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。  因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。 比如 ZooKeeper、HBase 就是 CP 架构,Cassandra、Eureka 就是 AP 架构,Nacos 不仅支持 CP 架构也支持 AP 架构。  为啥不可能选择 CA 架构呢? 举个例子:若系统出现“分区”,系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他节点的读写操作,这就和 A 发生冲突了。如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。  选择 CP 还是 AP 的关键在于当前的业务场景,没有定论,比如对于需要确保强一致性的场景如银行一般会选择保证 CP 。  另外,需要补充说明的一点是: 如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。  常见的可以作为注册中心的组件有:ZooKeeper、Eureka、Nacos...。  ZooKeeper 保证的是 CP。 任何时刻对 ZooKeeper 的读请求都能得到一致性的结果,但是, ZooKeeper 不保证每次请求的可用性比如在 Leader 选举过程中或者半数以上的机器不可用的时候服务就是不可用的。 Eureka 保证的则是 AP。 Eureka 在设计的时候就是优先保证 A (可用性)。在 Eureka 中不存在什么 Leader 节点,每个节点都是一样的、平等的。因此 Eureka 不会像 ZooKeeper 那样出现选举过程中或者半数以上的机器不可用的时候服务就是不可用的情况。 Eureka 保证即使大部分节点挂掉也不会影响正常提供服务,只要有一个节点是可用的就行了。只不过这个节点上的数据可能并不是最新的。 Nacos 不仅支持 CP 也支持 AP。 总结 在进行分布式系统设计和开发时,我们不应该仅仅局限在 CAP 问题上,还要关注系统的扩展性、可用性等等  在系统发生“分区”的情况下,CAP 理论只能满足 CP 或者 AP。要注意的是,这里的前提是系统发生了“分区”  如果系统没有发生“分区”的话,节点间的网络连接通信正常的话,也就不存在 P 了。这个时候,我们就可以同时保证 C 和 A 了。  总结:如果系统发生“分区”,我们要考虑选择 CP 还是 AP。如果系统没有发生“分区”的话,我们要思考如何保证 CA   BASE理论 BASE 是 Basically Available(基本可用) 、Soft-state(软状态) 和 Eventually Consistent(最终一致性) 三个短语的缩写。BASE 理论是对 CAP 中一致性 C 和可用性 A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。  BASE 理论的核心思想 即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。  也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。  BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充。  如果系统没有发生“分区”的话,节点间的网络连接通信正常的话,也就不存在 P 了。这个时候,我们就可以同时保证 C 和 A 了。因此,如果系统发生“分区”,我们要考虑选择 CP 还是 AP。如果系统没有发生“分区”的话,我们要思考如何保证 CA 。  因此,AP 方案只是在系统发生分区的时候放弃一致性,而不是永远放弃一致性。在分区故障恢复后,系统应该达到最终一致性。这一点其实就是 BASE 理论延伸的地方。 基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。 什么叫允许损失部分可用性呢?  响应时间上的损失: 正常情况下,处理用户请求需要 0.5s 返回结果,但是由于系统出现故障,处理用户请求的时间变为 3 s。 系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。 软状态  软状态指允许系统中的数据存在中间状态(CAP 理论中的数据不一致),并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。 最终一致性  最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。  分布式一致性的 3 种级别: 强一致性 :系统写入了什么,读出来的就是什么。 弱一致性 :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。 最终一致性 :弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。 业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。  读时修复 : 在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点 的副本数据不一致,系统就自动修复数据。 写时修复 : 在写入数据,检测数据的不一致时,进行修复。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败 就将数据缓存下来,然后定时重传,修复数据的不一致性。 异步修复 : 这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复。 比较推荐 写时修复,这种方式对性能消耗比较低。  总结 ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸。  SpringCloud SpringCloud(微服务框架)集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。  版本对应:https://start.spring.io/actuator/info ①Spring Cloud 是一系列框架的有序集合。  ②Spring Cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来。  netflix eureka 1.1,alibaba 2.2  ③通过 Spring Boot 风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。  ④它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、 断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。  ⑤Spring Cloud项目官方网址:https://spring.io/projects/spring-cloud  ⑥Spring Cloud 版本命名方式采用了伦敦地铁站的名称,同时根据字母表的顺序来对应版本时间顺序,比如:最早的Release版本:Angel,第二个Release版本:Brixton,然后是Camden、Dalston、Edgware,Finchley,Greenwich,Hoxton   springcloud组件:  红色不维护。绿色是alibaba一套,推荐使用。 统一网管路由 SpringCloudGateway,Zuul 服务链路监控 Zipkin,Sleuth 注册发现 服务发现:通过服务的应用名称找到服务的具体实例的过程  根据服务名称发现服务的实例过程 客户端会在本地缓存服务端的列表 拉取列表是有间隔周期的 (导致服务上线 客户端不能第一时间感知到 (可以容忍)) 其实每次做服务发现 都是从本地的列表来进行的  Redis 怎么清除过期的 key LRU(热点 key)                 1 定时(k-thread)                 2 惰性 (在再次访问该 key 时有作用)                 3 定期 (使用一个线程来完成清除任务) 定期(实时性差) + 惰性  Eureka-Server Eureka-Server 不仅提供让别人注册的功能,它也能注册到别人里面,自己注册自己,默认会注册自己,我们也可以关掉这个功能。 常用配置文件设置    服务端常用   客户端常用   原理 Eureka-server 对外提供的是 restful 风格的服务 以 http 动词的形式对 url 资源进行操作 get post put delete  http 服务 + 特定的请求方式 + 特定的 url 地址 只要利用这些 restful 我们就能对项目实现注册和发现。只不过,eureka 已经帮我们使用 java 语言写了 client,让我们的项目只要依赖 client 就能实现注册和发现!只要你会发起 Http 请求,那你就有可能自己实现服务的注册和发现。不管你是什么语言!  集群 nacos 负载均衡 负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目标是尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性。什么是负载均衡,看完文章秒懂_爱铭网络的博客-CSDN博客_负载均衡  高并发:负载均衡通过算法调整负载,尽力均匀的分配应用集群中各节点的工作量,以此提高应用集群的并发处理能力(吞吐量)。  伸缩性:添加或减少服务器数量,然后由负载均衡进行分发控制。这使得应用集群具备伸缩性。  高可用:负载均衡器可以监控候选服务器,当服务器不可用时,自动跳过,将请求分发给可用的服务器。这使得应用集群具备高可用的特性。  安全防护:有些负载均衡软件或硬件提供了安全性功能,如:黑白名单处理、防火墙,防 DDos 攻击等。  常见算法 1.RoundRobinRule--轮询 请求次数 % 机器数量。把每个请求轮流发送到每个服务器上。 2.RandomRule--随机。该算法比较适合服务器性能差不多的场景。 3.加权轮询(Weighted Round Robbin)加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值。 4. 源地址哈希(IP Hash) 根据请求源 IP,通过哈希计算得到一个数值,用该数值在候选服务器列表的进行取模运算,得到的结果便是选中的服务器。 3.最小活跃数(Least Active)算法 将请求分发到连接数/请求数最少的候选服务器(目前处理请求最少的服务器)。 4.Weighted ResponseTimeRule--根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果同统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则 5.RetryRule-- 先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用的服务 6.BestAvailableRule --会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务 7.ZoneAvoidanceRule -- 默认规则,复合判断 Server 所在区域的性能和 Server 的可用行选择服务器。  8.一致性哈希(Consistent Hash)算法的目标是:相同的请求尽可能落到同一个服务器上。  9.AvailabilityFilteringRule --会先过滤掉由于多次访问故障处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对于剩余的服务列表按照轮询的策略进行访问 Ribbon 默认使用哪一个负载均衡算法: ZoneAvoidanceRule :区间内亲和轮询的算法!通过一个 key 来区分  常见的负载均衡方式: 服务端负载均衡 客户端负载均衡 服务端负载均衡  需要建立一个独立的负载均衡服务器。 负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。 可用服务端清单存储在负载均衡服务器上。 客户端负载均衡:相较于服务端负载均衡,客户端服务在均衡则是一个比较小众的概念。 客户端负载均衡也需要心跳机制去维护服务端清单的有效性,这个过程需要配合服务注册中心一起完成。  负载均衡器位于客户端,不需要单独搭建一个负载均衡服务器。 负载均衡是在客户端发送请求前进行的,因此客户端清楚地知道是哪个服务端提供的服务。 客户端都维护了一份可用服务清单,而这份清单都是从服务注册中心获取的。  Ribbon 就是一个基于 HTTP 和 TCP 的客户端负载均衡器,当我们将 Ribbon 和 Eureka 一起使用时,Ribbon 会从 Eureka Server(服务注册中心)中获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。   ribbon  Ribbon 要做什么事情? 先通过 "http://" + serviceId + "/info" 我们思考 ribbon 在真正调用之前需要做什么? restTemplate.getForObject(“http://provider/info”, String.class);   1. 拦截该请求; 2. 获取该请求的 URL 地址:http://provider/info 3. 截取 URL 地址中的 provider 4. 从服务列表中找到 key 为 provider 的服务实例的集合(服务发现) 5. 根据负载均衡算法选出一个符合的实例 6. 拿到该实例的 host 和 port,重构原来 URL 中的 provider 7. 真正的发送 restTemplate.getForObject(“http://ip:port/info”,String.class)  Ribbon 源码核心:          ILoadBalancer 接口:起到承上启下的作用                 1. 承上:从 eureka 拉取服务列表                 2. 启下:使用 IRule 算法实现客户端调用的负载均衡         设计思想:每一个服务提供者都有自己的 ILoadBalancer                 userService---》客户端有自己的 ILoadBalancer                 TeacherService---》客户端有自己的 ILoadBalancer                 在客户端里面就是 Map<String,ILoadBalancer> iLoadBalancers                 Map<String,ILoadBalancer> iLoadBalancers 消费者端                 服务提供者的名称 value (服务列表 算法规则 ) 如何实现负载均衡的呢?  iloadBalancer loadbalance = iloadBalancers.get(“user-service”) List<Server> servers = Loadbalance.getReachableServers();//缓存起来 Server server = loadbalance .chooseServer(key) //key 是区 id,--》IRule 算法 chooseServer 下面有一个 IRule 算法 IRule 下面有很多实现的负载均衡算法 负载均衡之前的服务列表是从何而来呢? 从 eureka 来 Ribbon 里面有没有服务列表?Ribbon 有一个核心接口 ILoadBalance(承上(eureka)启下(Rule)) Ribbon 只做负载均衡和远程调用 我们发现在负载均衡之前,服务列表已经有数据了 loadbalance 服务接口调用 OpenFeign Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,是以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。  Feign 是一个声明式的 REST 客户端,它用了基于接口的注解方式,很方便实现客户端像调用本地接口方法一样,进行远程调用。  Feign 最初由 Netflix 公司提供,但不支持SpringMVC注解,后由 SpringCloud 对其封装,支持了SpringMVC注解,让使用者更易于接受。  Spring Cloud 常见的集成方式是使用Feign+Ribbon技术来完成服务间远程调用及负载均衡的  其实Feign的一个机制就是使用了动态代理:  首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心 Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址 最后针对这个地址,发起请求、解析响应 流程:  在微服务启动时,会向注册中心注册自身实例信息; 微服务会定时向服务发现中心获取服务实例列表,保存到本地; 当ServiceA调用ServiceB时,ribbon组件从本地服务实例列表中查找serviceB实例,如获取了多个实例如:Instance1、Instance2。这时ribbon会通过用户所配置的负载均衡策略从中选择一个实例。 最终,Feign组件会通过ribbon选取的实例发送http请求。 采用Feign+Ribbon的整合方式,是由Feign完成远程调用的整个流程。而Feign集成了Ribbon,Feign使用Ribbon  1. OpenFeign 用过吗?它是如何运作的? 在主启动类上加上@EnableFeignClients 注解后,启动会进行包扫描,把所有加了@FeignClient(value=”xxx-service”)注解的接口进行创建代理对象通过代理对象,使用ribbon 做了负载均衡和远程调用 2. 如何创建的代理对象? 当 项 目在启动时,先扫描,然后拿到标记了@FeignClient 注 解的接口信息 ,由ReflectiveFeign 类的 newInstance 方法创建了代理对象JDK代理  3. OpenFeign 到底是用什么做的远程调用? 使用的是 HttpURLConnection (java.net) 4. OpenFeign 怎么和 ribbon 整合的? 在代理对象执行调用的时候 断路器 -保险丝 三板斧:服务降级、服务熔断、服务限流   服务降级 通常原因为服务器的资源是有限的,而请求是无限的。在用户使用即并发高峰期,会影响整体服务的性能,严重的话会导致宕机,以至于某些重要服务不可用。故高峰期为了保证核心功能服务的可用性,就需要对某些服务降级处理。可以理解为舍小保大,通常处理为不让客户端等待而是立即返回一个友好的提示。 服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(兜底处理)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。  服务熔断 在微服务架构中,微服务是完成一个单一的业务功能,这样做的好处是可以做到解耦,每个微服务可以独立演进。但是,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成。这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。  线程没有及时回收  熔断机制是应对雪崩效应的一种微服务链路保护机制。我们在各种场景下都会接触到熔断这两个字。高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。  服务熔断和服务降级的区别  这里主要从三个原因分开进行比较,最后  触发原因:服务熔断由链路上某个服务引起的,也就是说,服务熔断一般是某个服务(下游服务)故障引起的,而服务降级是从整体的负载考虑。服务熔断是应对系统服务雪崩的一种保险措施,给出的一种特殊降级措施。而服务降级则是更加宽泛的概念,主要是对系统整体资源的合理分配以应对压力。 管理目标层次:服务熔断是一个框架层次的处理,服务降级是业务层次的处理。 实现方式:服务熔断一般是自我熔断恢复,调用的降级处理是在客户端进行降级处理(编写相应的兜底方法),而服务降级相当于是在服务端进行的兜底方案控制。 总的来说:  服务熔断是服务降级的一种特殊情况,是防止服务雪崩而采取的措施。系统发生异常或者延迟或者流量太大,都会触发该服务的服务熔断措施,链路熔断,返回兜底方法。这是对局部的一种保险措施。 服务降级是对系统整体资源的合理分配。区分核心服务和非核心服务。对某个服务的访问延迟时间、异常等情况做出预估并给出兜底方法。这是一种全局性的考量,对系统整体负荷进行管理。 服务限流 在上述两种方式中,最终服务的确可以使用,但是访问的却是缺省的服务,比如服务熔断和服务降级最终都会调用相应的兜底方案返回,也就是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用熔断和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流。  限流的目的是通过对并发请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。  一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。  常见的限流算法有:令牌桶、漏桶(Sentinel采用的)。计数器也可以进行简单的限流实现。  Hystrix(豪猪) Hystix 是 Netflix 开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败(雪崩)。  Hystrix是隔离、熔断以及降级的一个框架,说白了就是Hystrix会搞很多小线程池然后让这些小线程池去请求服务,返回结果,Hystrix相当于是个中间过滤区,如果我们的积分服务挂了,那我们请求积分服务直接就返回了,不需要等待超时时间结束抛出异常,这就是所谓的熔断,但是也不能啥都不干就返回啊,不然我们之后手动加积分咋整啊,那我们每次调用积分服务就在数据库里记录一条消息,这就是所谓的降级,Hystrix隔离、熔断和降级的全流程如下: Sentinel(哨兵) 网关 • 网关旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。  • 在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。  • 网关就是系统的入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、缓存、负载均衡、流量管控、路由转发等  • 在目前的网关解决方案里,有Nginx+ Lua、Netflix Zuul/zuul2 、Spring Cloud Gateway等等  微服务背景下,一个系统被拆分为多个服务,但是像安全认证,流量控制,日志,监控等功能是每个服务都需要的,没有网关的话,我们就需要在每个服务中单独实现,这使得我们做了很多重复的事情并且没有一个全局的视图来统一管理这些功能。  一般情况下,网关可以为我们提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、降级熔断、日志、监控等功能。  网关主要做了一件事情:请求过滤 。 常见网关 Netflix Zuul Zuul 是 Netflix 开发的一款提供动态路由、监控、弹性、安全的网关服务。  Zuul 主要通过过滤器(类似于 AOP)来过滤请求,从而实现网关必备的各种功能。 ————————————————                 原文链接:https://blog.csdn.net/qq_41244407/article/details/126568832 
  • [技术干货] springcloud微服务架构(eureka、nacos、ribbon、feign、gateway等组件的详细介绍和使用)
    一、微服务演变1、单体架构(Monolithic Architecture)是一种传统的软件架构模式,应用程序的所有功能和组件都集中在一个单一的应用中。在单体架构中,应用程序通常由一个大型的、单一的代码库组成,其中包含了所有的功能模块和业务逻辑。这个应用程序作为一个整体部署和运行在一个应用服务器上,并共享相同的内存和数据库。当单体架构项目的性能无法满足需求时,但又希望继续沿用单体架构的话,你可以采取以下一些优化手段来改善性能,如以下方法:使用缓存:引入缓存机制,将经常请求的数据缓存起来,减少对数据库等后端系统的访问,以提高性能。数据库优化:进行数据库性能调优,包括建立索引、优化查询语句、使用合适的数据库引擎等,以提高数据库的响应速度。使用消息队列:引入消息队列来解耦耗时操作,将其转化为异步的处理任务。这样可以提高并发处理能力和系统的响应性能。水平扩展:通过复制多个实例来处理更多的请求。可以使用负载均衡器将请求分发到不同的实例上,从而提高系统的整体性能和容量。引入分布式架构:将应用程序拆分为多个独立的服务,并通过网络进行通信和协作。这样可以将负载分散到多个节点上,提高整体性能和可伸缩性。接下来就说一下单体架构优化中的引入分布式架构。2、分布式架构将系统的各个组件部署在不同的计算机节点上,并通过网络进行通信和协作的软件架构模式。在分布式架构中,各个节点可以独立运行和处理任务,并通过消息传递、远程过程调用或其他通信机制进行数据交换和协调。在使用分布式时,我们就会有一下的疑问:服务拆分到什么程度?服务集群地址如何维护?服务之间如何实现远程调用?服务健康状况如何感知?3、微服务微服务是一种经过改良好的架构设计的分布式架构方案,微服务架构特征:单一职责:微服务拆分力度更小,每个服务都对应唯一的服务能力,做的单一职责,避免重复开发面向服务:微服务对外暴露业务接口自治:团队独立、技术独立、数据独立、部署独立隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题假如我们有一个电商系统,其中有以下一些微服务:用户服务(User Service)负责用户的注册、登录、信息管理等功能。商品服务(Product Service)负责商品的查询、添加、更新等功能。订单服务(Order Service)负责订单的创建、支付、取消等功能。现在假设用户服务依赖于商品服务来获取商品信息,并且订单服务依赖于用户服务来获取用户信息。这里我们可以看到微服务的几个特征是如何应用的:单一职责:每个微服务都具有清晰的职责。例如,用户服务只负责用户相关的功能,而不涉及商品或订单。面向服务:每个微服务都对外暴露业务接口,其他微服务可以通过调用这些接口来访问所需的功能。例如,用户服务可以提供获取用户信息的接口给订单服务使用。自治:每个微服务的团队在技术和数据上都是独立的。例如,用户服务的团队可以独立开发、测试、部署和扩展该服务,无需依赖其他团队。隔离性强:微服务之间的调用需要做好隔离、容错和降级,以避免出现级联问题。例如,当商品服务不可用时,用户服务可以使用缓存或默认数据来避免影响用户操作。这个例子中提供了不同的微服务来处理不同的功能,并且彼此解耦、独立运行。每个微服务都具有单一职责,对外提供明确定义的业务接口,团队在技术和数据上具有自治能力,同时采取适当的隔离措施来保证系统的弹性和稳定性。这些特征有助于提高开发效率、灵活性和可维护性,使得微服务架构在构建大型、复杂系统时具有优势。4、 总结单体架构特点:简单方便,高度耦合,扩展性差,合适小型项目。如:学生管理系统。分布式架构特点:松耦合,扩展性好,但架构复杂,难道大,适合大型互联网项目。如:京东、淘宝微服务:一种良好的分布式架构方案。优点:拆分粒度更小、服务更独立、耦合更低缺点:架构非常复杂,运维、监控、部署难道更高5、微服务架构微服务这方案需要技术框架来落地实现,全球的换联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是SpringCloud和阿里巴巴的Dubbo。5.1、 微服务技术对比Dubbo、Spring Cloud、Spring Cloud和Spring Cloud Alibaba都是用于构建分布式系统的开源框架。尽管它们的目标相同,但它们在实现方式和功能特点上有所不同。Dubbo:Dubbo是一个高性能的分布式服务框架,由阿里巴巴开发。它基于传统的服务治理理念,提供了服务注册、发现、路由、负载均衡、容错等功能。Dubbo的核心特点是高性能和低延迟的RPC调用,适用于大规模的微服务架构。Dubbo提供了对多种协议(如Dubbo协议、REST协议)和注册中心(如ZooKeeper、Consul)的支持。Spring Cloud:Spring Cloud是一个由Pivotal开发的微服务框架,构建在Spring Framework之上,使得构建分布式系统更加便捷。它提供了一系列的组件和模块,用于实现服务注册与发现、负载均衡、断路器、配置管理、消息总线等功能。Spring Cloud采用了Spring Boot作为底层的开发框架,提供了更简洁、快速搭建分布式系统的解决方案。Spring Cloud Alibaba:Spring Cloud Alibaba是Spring Cloud与Alibaba开放平台合作的结果,提供了一些在云原生应用开发中常用的解决方案。它主要基于Spring Cloud框架,结合了一些Alibaba技术栈,如Nacos(服务注册与发现)、Sentinel(流量控制和熔断降级)、RocketMQ(消息驱动)等。Spring Cloud Alibaba旨在提供云原生应用开发的全栈解决方案。虽然Dubbo和Spring Cloud都是用于构建分布式系统的框架,但Dubbo更加注重于高性能的RPC调用和服务治理,而Spring Cloud则提供了一整套更全面的微服务解决方案。而Spring Cloud Alibaba则是在Spring Cloud的基础上,进一步整合了Alibaba的一些技术,为云原生应用提供更全面的开发支持。选择适合的框架取决于具体的需求、技术栈和团队偏好。5.2、企业需求Spring Cloud + Spring Cloud Alibaba:Spring Cloud提供了丰富的微服务组件和解决方案,包括服务注册与发现、负载均衡、断路器、配置管理等。Spring Cloud Alibaba扩展了Spring Cloud,整合了阿里巴巴技术栈,如Nacos(服务注册与发现)、Sentinel(流量控制和熔断降级)、RocketMQ(消息驱动)等。组合使用这两个框架可以获得全面的云原生应用开发解决方案,适用于构建现代化的微服务架构。Dubbo原生模式 + Spring Cloud Alibaba:Dubbo是一个高性能的RPC框架,提供了服务治理、负载均衡、容错等功能。Spring Cloud Alibaba扩展了Dubbo,为Dubbo提供了更多云原生的支持,如Nacos作为注册中心、Sentinel用于流量控制和熔断降级等。通过将Dubbo和Spring Cloud Alibaba集成,可以获得高性能的RPC调用和全面的云原生的服务治理解决方案。无论是使用Spring Cloud + Spring Cloud Alibaba还是Dubbo原生模式 + Spring Cloud Alibaba,都可以受益于Spring Cloud和Spring Cloud Alibaba提供的丰富的微服务功能和云原生支持。具体选择哪种组合取决于企业需求、技术栈和团队实际情况。需要评估技术要求、性能需求、开发复杂度等因素,选择适合的框架组合来构建稳定、高效的分布式系统。二、spring cloudSpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloudSpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:springCloud与SpringBoot的版本兼容关系本文学习版本是Hoxton.SR10,因此对应的springboot版本是2.3.x版本。1、服务拆分及远程调用1.1、服务拆分1.1.1、服务拆分注意事项不同微服务,不要重复开发相同业务微服务数据独立,不要访问其他微服务的数据库微服务可以将自己的业务暴露为接口,供其它微服务调用1.1.2、项目实战实战代码:阿里云下载把数据库文件那到数据库管理工具里执行和idea中导入cloud-demo项目最终数据库结构:对与cloud-demo这个项目主要看一下用户和订单如何进行项目拆分的,结合这单体架构的结果对比一下有什么变化和不同。1.1.3、总结微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务。微服务可以将业务暴露为接口,供其他微服务使用。不同微服务都应该有自己独立的数据库。1.2、远程调用需求:根据订单id查询订单的同时,把订单所属的用户信息一起返回。1.2.1、远程调用方法在我们的controller类中使用@GetMapping("/user/{id}")向外暴露了一个接口来访问,可以将user信息放回为json数据格式,我们可以想一下浏览器可以发一个Ajax请求来获取数据,我们的订单服务可不可以发一个Ajax请求来获取数据呢?1.2.2、实现远程调用步骤1)注册RestTemplateSpring Cloud中也使用了RestTemplate类。RestTemplate是Spring框架中的一部分,它在Spring Cloud项目中被广泛用于进行微服务之间的通信。在微服务架构中,各个微服务之间通常通过RESTful API进行通信。为了简化这个过程,Spring Cloud对RestTemplate进行了增强,以便更好地支持微服务架构。在Spring Cloud中,RestTemplate被称为"服务调用"的一部分。通过使用RestTemplate,开发人员可以方便地发起HTTP请求来调用其他微服务的API。Spring Cloud还提供了一些增强功能,例如服务发现和负载均衡。开发人员可以使用服务名代替具体的URL,Spring Cloud会自动根据服务名找到可用的实例并进行负载均衡,从而实现更灵活和高效的服务调用。需要注意的是,自Spring Cloud 2020.0.0版本(即Hoxton.SR9及之后的版本)开始,官方推荐使用WebClient替代RestTemplate作为HTTP客户端,因为WebClient提供了更强大、更灵活的功能,并且更适用于非阻塞的响应式编程模型。但是,为了向后兼容,RestTemplate仍然可以继续使用并被支持。在order-service的OrderApplication中注册ResteTemplate,将ResteTemplate用@Bean注解注册为spring管理的对象,以后不管在什么地方都可以使用到RestTemplate对象。完成调用主要是在service层中实现,向被调用服务发起一个Rest请求,在需要调用其他服务的服务中使用,实现步骤:1、在服务启动类中注册RestTemplate,如这里是在order的启动类中2、在要调用其他服务的服务中的service层中自动装配RestTemplate对象3、使用RestTemplate的api来实现即可,里面有很多api,这里我使用的是getForObject()方法4、将远程调用返回的数据封装到要封装的对象中即可,这里我使用的是将远程调用获取的user对象信息封装到order对象中。1.3、提供者与消费者服务提供者:一次业务中,被其他微服务调用的服务。(提供接口给其他服务调用)如:user服务服务消费者:一次业务中,调用其他微服务的服务。(调用其他微服务提供的接口)如order服务总结:1.服务调用关系服务提供者:暴露接口给其它微服务调用服务消费者:调用其它微服务提供的接口提供者与消费者角色其实是相对的一个服务可以同时是服务提供者和服务消费者三、Eureka注册中心1、服务调用出现的问题在前面我们使用RestTemplate来实现服务远程调用,在写url时使用的是硬编码方式,就会产生以下的问题,这些问题是值得我们考虑一下的。服务消费者该如何获取服务提供者的地址信息?如果有多个服务提供者,消费者该如何选择?消费者如何得知服务提供者的健康状态?2、 Eureka基本原理Eureka是Netflix开源的服务治理框架,在Spring Cloud中广泛应用。它的基本原理是建立了一个分布式的服务注册中心,用于管理和维护各个微服务实例的注册和发现。以下是Eureka的基本原理:Eureka服务器:Eureka由一个或多个Eureka服务器组成,它们构成了服务注册中心。每个微服务实例都将自己的信息注册到Eureka服务器,包括服务名、主机名、端口号等。服务注册:微服务启动时,会向Eureka服务器发送注册请求,将自己的信息注册到注册中心。注册中心维护一个服务注册表,记录所有已注册的微服务实例。服务发现:其他微服务需要调用某个服务时,首先向注册中心发送查询请求,获得目标服务的实例列表。注册中心将会返回所有可用的服务实例信息,包括IP地址、端口号等。服务监控:Eureka服务器会定期向已注册的微服务实例发送心跳请求,微服务实例返回响应以证明自己的健康状态。如果一个微服务长时间未发送心跳消息或返回异常状态,Eureka服务器将从注册表中删除该实例。服务同步:Eureka服务器之间会相互复制注册表信息,以保证数据的一致性。当有新的微服务实例注册或注销时,注册中心会通知其他服务器进行注册表更新。通过Eureka提供的服务注册和发现机制,微服务之间可以动态地发现和调用其他微服务,从而实现了服务之间的解耦和灵活性。Eureka还提供了负载均衡、故障恢复等一些附加功能,使得微服务架构更加可靠和高效。回顾之前我们的几个问题:消费者该如何获取服务提供者具体信息?服务提供者启动时向eureka注册自己的信息eureka保存这些信息消费者根据服务名称向eureka拉取提供者信息如果有多个服务提供者,消费者该如何选择?服务消费者利用负载均衡算法,从服务列表中挑选一个消费者如何感知服务提供者健康状态?服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态eureka会更新记录服务列表信息,心跳不正常会被剔除消费者就可以拉取到最新的信息总结在Eureka架构中,微服务角色有两类:1、EurekaServer:服务端,注册中心记录服务信息心跳监控2、EurekaClient: 客户端1)Provider:服务提供者,例如案例中的 user-service注册自己的信息到EurekaServer每隔30秒向EurekaServer发送心跳2)consumer:服务消费者,例如案例中的 order-service根据服务名称从EurekaServer拉取服务列表基于服务列表做负载均衡,选中一个微服务后发起远程调用3、手动实战3.1、搭建eureka服务注册中心(服务名称)1、创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖org.springframework.cloudspring-cloud-starter-netflix-eureka-server2、启用Eureka服务器:创建一个启动类,并使用@EnableEurekaServer注解来启用Eureka服务器功能。@SpringBootApplication@EnableEurekaServerpublic class EurekaApplication {public static void main(String[] args) {SpringApplication.run(EurekaApplication.class,args);}}3、配置Eureka服务器:在应用程序的配置文件(如application.properties或application.yml)中配置Eureka服务器的相关信息,例如:# 服务器配置server:port: 10001 # Eureka服务器运行的端口号# Spring应用程序配置spring:application:name: eurekaserver # Eureka服务器应用程序的名称# Eureka客户端配置eureka:client:service-url:defaultZone: http://localhost:10001/eureka/# Eureka客户端注册自身的Eureka服务器的URL# 在本例中,将Eureka服务器的URL设置为运行在10001端口的本地服务器3.2、注册user-service这个操作是在user-service项目下实现的,主要是将服务信息注册到Eureka服务端,eureka将这些服务信息保存到注册表中进行管理。将user-service服务注册到EurekaServer步骤:1、在user-service项目中引入spring-cloud-starter-netflix-eureka-client的依赖org.springframework.cloudspring-cloud-starter-netflix-eureka-client2、在application.yml位置文件中编写配置spring:application:name: userservice #配置服务名称eureka:client:service-url:defaultZone: http://localhost:10001/eureka/ #配置eureka服务地址信息其实orderservice的注册也是这两个步骤,我就写了。做完之后,我我们可以浏览一下eureka的服务网站看一下:http://localhost:10001这里的端口号是你在自己的eureka的配置文件application.yml配置的eureka端口号一致。总结:1.服务注册引入eureka-client依赖在application.yml中配置eureka地址2.无论是消费者还是提供者,引入eureka-client依赖知道eureka(服务注册中心)地址后,都可以完成服务注册3.3、在order-service完成服务拉取服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡1.修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:String url ="http://userservice/user/" + order.getUserId();2.在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:@Bean@LoadBalanced #负载均衡public RestTemplate restTemplate() {return new RestTemplate();}总结1.搭建EurekaServer引入eureka-server依赖添加@EnableEurekaServer注解在application.yml中配置eureka地址2.服务注册引入eureka-client依赖在application.yml中配置连接eureka服务注册中心地址3.服务发现引入eureka-client依赖在application.yml中配置eureka服务注册中心地址给RestTemplate添加@LoadBalanced注解用服务提供者的服务名称远程调用四、Ribbon负载均衡Ribbon是一个负载均衡解决方案,主要用于在分布式系统中将负载均匀地分发给多个服务实例。它是Netflix开 源的一个组件,常用于微服务架构中。1、负载均衡流程Ribbon的负载均衡原理可以概括如下:服务注册:Ribbon首先需要与服务注册中心(如Eureka、Consul等)进行交互,获取可用的服务实例列表。负载均衡策略:Ribbon支持多种负载均衡策略,如随机策略、轮询策略、权重策略等。根据选择的策略,Ribbon会根据服务实例的状态、性能等因素来选择一个合适的服务实例。服务调用:一旦选择了一个服务实例,Ribbon会将请求发送给该实例。它会维护一个与服务实例的长连接,并在需要时将请求发送给该实例。失败处理:如果请求在与服务实例的通信中失败,Ribbon会尝试选择另一个可用的服务实例进行重试,以增加系统的可用性。Ribbon还可以与其他组件配合使用,例如Netflix的Hystrix熔断器,用于实现服务的容错和故障保护。总之,Ribbon通过动态获取服务实例列表并根据负载均衡策略选择合适的实例来进行负载均衡,从而提高系统的性能、可用性和可扩展性。源码执行流程:2、Ribbon负载均衡策略Ribbon负载均衡规则是一个叫做IRule的接口来实现的,每个子接口都是一种规则:2.1、 负载均衡策略2.2、调整负责均衡策略的规则通过定义IRule实现可以修改负载均衡规则,有两种方式:方式一:代码方式如消费者服务order-service,只要在OrderApplication类中定义一个新的IRule:@Beanpublic IRule iRule(){return new RandomRule();}其实也不一定要在OrderApplication启动类中配置,也可以自己创建一个配置类来配置,在有@configuration注解的类就可以。这种配置方案是全局的配置,只要使用了这种配置方案,以后不管你调用的是user-service服务还是order-service服务都是使用这里的配置方案。方式二:配置文件方式在order-service的application.yml文件中,添加新的配置也可以修改规则:userservice: #被调用微服务器的服务名称ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则这种配置方案是只针对某个微服务的,是局部配置。3、Ribbon饿加载Ribbon默认是采用懒加载,即第一次访问才会创建LoadBalanceClient,请求时间会很长。而饿加载则会在项目启动时创建,降低每一次访问的耗时3.1、配置开启配置饥饿加载#当服务饥饿加载ribbon:eager-load:enabled: true #开启饥饿加载clients: userservice #指定对userservice这个服务饥饿加载#当有多个服务需要饥饿加载可以用下面的方式:ribbon:eager-load:enabled: true #开启饥饿加载clients: #指定对userservice这个服务饥饿加载- userservice- xxservice总结1.Ribbon负载均衡规则规则接口是IRule默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询2.负载均衡自定义方式代码方式:配置灵活,但修改时需要重新打包布配置方式:直观,方便,无需重新打包发布但是无法做全局配置3.饥饿加载开启饥饿加载指定饥饿加载的微服务名称五、Nacos注册中心微服务的注册中心我们已经学了eureka,大家肯定会有疑问,为啥还有学校nacos呢?其实nacos不仅有服务注册中心和服务发现功能,还有更加强大的功能。1、nacos和eureka对比Nacos和Eureka是两个常用的服务发现和注册工具,它们都具有类似的功能,但在一些方面存在一些差异。以下是Nacos和Eureka的一些对比:开发者生态圈:Nacos是由Alibaba开发并开源,拥有庞大的Alibaba生态圈支持,而Eureka是由Netflix开发并开源,得到了Netflix和Spring Cloud社区的广泛应用。功能和特性:Nacos提供了更多的功能和特性,除了服务注册与发现外,还包括配置管理、动态DNS、动态路由和流量管理等功能。Eureka主要关注服务注册与发现的功能。容错性:Nacos具有更强的容错性,支持多数据中心的分布式部署,可以保证在网络分区和节点故障情况下的高可用性。Eureka在这方面的容错性相对较弱。数据一致性:Nacos使用Raft算法来实现数据一致性和高可用性,而Eureka使用的是AP模型,即优先保证可用性而不保证强一致性。社区活跃度:Nacos的开源社区活跃度相对较高,有更多的贡献者和更新的版本发布。Eureka的开源社区相对较少活跃,更新较为缓慢。选择使用Nacos还是Eureka可以根据具体需求和项目背景来决定。如果需要更多的功能和特性,以及较强的容错性和高可用性,Nacos可能是更好的选择。如果项目已经依赖于Netflix和Spring Cloud生态圈,或者对于服务注册和发现的简单功能需求,Eureka可能是更适合的选项。2、Nacos下载安装服务注册中心2.1下载nacos在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:GitHub主页:https://github.com/alibaba/nacosGitHub的Release下载页:https://github.com/alibaba/nacos/releases这里我提供了阿里云盘下载:Nacos1.4.12.2、解压Nacos我这里使用的是在Windows下安装的。直接解压到一个没有中文字符的路径下。目录说明:- bin:启动脚本- conf:配置文件2.2.1、配置端口Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。**如果无法关闭占用8848端口的进程**,也可以进入nacos的conf目录,修改配置文件中的端口:修改其中的内容修改为一个没有被占用的端口号。2.2.2.启动nacos服务注册中心启动非常简单,进入bin目录,进入cmd窗口执行下面的命令startup.cmd -m standalone#startup.cmd -m standalone 的含义是执行Nacos服务器的启动脚本,并使用独立模式启动Nacos服务器。#这将在单个节点上运行Nacos,并使用默认的配置文件和端口。这个命令适用于简单部署或测试,并非生产环境下常见的集群模式启动命令。执行后的效果如图:在浏览器输入地址:http://127.0.0.1:8848/nacos即可:用户名和密码都是:nacos网页直接就是中文的,对于英语不太好的比eureka的友好。3、将服务注册到nacos服务注册中心1、在cloud-demo父工程中添加spring-cloud-alibaba的管理依赖:com.alibaba.cloudspring-cloud-alibaba-dependencies2.2.5.RELEASEpomimport2、注释掉order-service和user-service中原有的eureka依赖。3、添加nacos的客户端依赖:com.alibaba.cloudspring-cloud-starter-alibaba-nacos-discovery4、修改user-service & order-service中的application.yml文件,注释掉eureka的地址,添加nacos地址:spring:cloud:nacos:server-addr: localhost:8848启动项目测试:其实eureka和nacos在spring-cloud中除了依赖和端口号配置不同外其他的使用都是一样的,他们底层使用的接口都是一样的,所以代码其他地方根本不用做改变。总结:1.Nacos服务搭建下载安装包解压在bin目录下运行指令:startup.cmd -mstandalone2.Nacos服务注册或发现引入nacos.discovery依赖配置nacos地址spring.cloudnacos.server-addr4、Nacos服务分级存储模型之前我们在学中,我们的服务分层都是只有两层的,第一层是服务,第二层是实例(如user-service、order-service),在我们部署时,都是将这些服务我实例部署到一个机器上或者一个机房里,但是如果这个机房收到破坏(地震、火灾)时,服务就无法访问了。而nacos服务分层存储模型则引出了集群的概念,一个集群就好比是我们的一个机房,而我们的一个服务下面就可以部署很多个集群,每个集群就可以部署在全球各地(北京、上海、深圳等),因此服务的层次结构就变成了三层(服务、集群、实例)。4.1、服务跨集群调用问题服务调用尽可能选择本地集群的服务,跨集群调用延迟高本地集群不可以访问时,在去访问其他集群4.2、服务集群实现1、修改application.yml,添加如下内容,配置服务集群名称spring:cloud:nacos:server-addr: localhost:8848 #nacos 服务端地址cluster-name: YN #配置集群名称,也就是机房位置,例如:YN ,云南2、在Nacos控制台可以看到集群变化现在的集群变成了yn,在没有配置集群名称是default。总结1.Nacos服务分级存储模型一级是服务,例如userservice二级是集群,例如杭州或上海三级是实例,例如杭州机房的某台部署了userservice的服务器2.如何设置实例的集群属性修改application.yml文件,添加spring.cloud.nacos.discovery.cluster-name属性即可4.3、Nacos集群负载均衡Nacos集群负载均衡的规则其实是优先选择本集群内的服务,如果本服务挂了,才会去选择其他集群的服务来访问。配置1、修改order-service中的application.yml,设置集群为HZ:spring:cloud:nacos:server-addr: Localhost:8848 # nacos 服务端地加discovery:cLuster-name: HZdiscovery:cluster-name: bj #配集群名称,也就是机房位置2、然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:userservice: #服务名称ribbon:NFLoadBalancerRulecassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则3、注意将user-service的权重都设置为1总结:1.NacosRule负载均衡策略优先选择同集群服务实例列表本地集群找不到提供者,才去其它集群寻找,并且会报警告确定了可用实例列表后,再采用随机负载均衡挑选实例4.4、根据权重负载均衡假设有一个微服务架构的电子商务网站,其中包括以下几个服务:商品服务、订单服务和用户服务。这些服务都注册到了Nacos中进行服务发现,并且它们有不同的实例数和性能配置。在这种情况下,可以通过基于权重的负载均衡来实现对这些服务实例的合理请求分配。以下是一些示例场景:不同实例性能差异较大:假设商品服务有3个实例,其中实例A和实例B配置较高,实例C配置较低。此时,可以设置实例A和实例B的权重为2,实例C的权重为1。这样,每次有请求到来时,有较高性能的实例A和实例B将会处理更多的请求,而较低性能的实例C将会处理较少的请求,从而实现了性能调控。服务实例容量不均衡:假设订单服务有5个实例,其中实例A、B和C的容量比实例D和E大。可以为实例A、B和C设置较高的权重值,如3,而为实例D和E设置较低的权重值,如1。这样,请求将均衡分配到实例A、B和C之间,并保持实例D和E的请求数较少。降低故障实例的负载:假设用户服务有4个实例,其中实例A由于某种原因,出现了故障或不稳定情况。为了减少对实例A的请求分配,可以为其设置较低的权重值,如0,而将其他正常的实例设置为较高的权重值,如3。这样,请求将主要被分配给其他正常的实例,降低了对故障实例A的负载。通过在Nacos中设置服务实例的权重,可以根据实际情况动态调整请求的负载比例。这样能够充分利用资源、提高系统性能、保证服务稳定性,并对不同实例进行合理分配。Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。配置4.5、环境隔离-namespace1、认识环境隔离-namvespace应用场景:假设有一个电商平台,该平台设计了一个基于Nacos的命名空间配置和服务注册方案,以支持环境隔离和多租户功能。首先,假设该电商平台有三个环境:开发环境、测试环境和生产环境。每个环境都有独立的配置和服务需求。环境隔离和配置管理:使用Nacos的命名空间功能,平台管理员可以创建三个命名空间:dev-namespace、test-namespace和prod-namespace。在dev-namespace中,可以配置开发环境所需的各种参数,如数据库连接信息、调试模式等。在test-namespace中,可以配置测试环境所需的参数,如测试数据库连接信息、测试数据源等。在prod-namespace中,可以配置生产环境所需的参数,如正式数据库连接信息、生产级别的服务配置等。每个命名空间下的配置项是相互隔离的,这样就保证了不同环境配置的独立性,并且可根据需求灵活管理和更新配置信息。服务注册和版本管理:在每个命名空间中,可以注册相应环境需要的服务实例,如商品服务、订单服务等。平台管理员可以使用命名空间功能来管理不同环境的服务注册表,确保每个环境只能访问属于自己的服务实例。每个命名空间可以独立管理服务实例的版本,例如,在开发环境中可以注册和测试新的服务版本,而在生产环境中则使用稳定的服务版本。通过这样的命名空间配置和服务注册方案,电商平台能够实现对不同环境的配置和服务进行隔离,并支持多租户功能。开发团队可以独立管理各自环境的配置和服务,而不会相互干扰。此外,平台还可以利用灰度发布和版本管理功能,在不同命名空间中进行服务版本控制,确保系统稳定性和可用性。2、配置命名空间1)创建命名空间(在nacos服务页面创建)2、将服务配置到指定的命名空间中(修改application.yml配置文件)server:port: 8081spring:datasource:url: jdbc:mysql://192.168.10.130:3306/cloud-user?useSSL=falseusername: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driverapplication:name: userservice #配置服务名称cloud:nacos:server-addr: localhost:8848discovery:cluster-name: yn #集群名称,地址在云南namespace: 074da7cb-c3e3-4848-b893-3d15114e8729 #命令空间id此时,配置了namespace的服务已经被配置到dev中了如果此时使用order-service去调用user-service就会报错了Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No instances available for userservice] with root causeservlet .service()的servlet [dispatcherServlet]在上下文与路径[]抛出异常[请求处理失败;嵌套异常是java.lang.IllegalStateException: No instances available for userservice],有根本原因5、nacos注册中介细节分析服务注册:服务提供者启动时,会向服务注册中心发起注册请求,包括服务名、IP 地址、端口等。注册中心会将这些信息保存起来。服务发现:服务发现有两种(pull和push相结合)pull:服务消费者会每隔30秒定时主动向服务注册中心发起请求,来拉取服务信息,在服务消费者中有一个服务列表缓存,用于缓存从服务注册中心拉取下的服务信息。push:当有服务停掉后服务注册中心会主动推送变更信息给消费者,消费者就会更新服务列表缓存。心跳与健康检查:也是有两种(对临时实力和非临时实例)临时实例:采用心跳检测,是临时实例主动向注册中心发送心跳检测,如果一段时间注册中心没有收到临时实例心跳检测,就会剔除该临时实例。非临时实例:采用nacos主动询问,nacos主动询问非临时实例的健康状态,如果非临时实例停止了,也不会剔除非临时实例,只是修改非临时实例的健康转态,会等待非临时实例健康。负载均衡:负载均衡和流量控制是由服务消费者一侧的客户端组件来实现的,而不是由注册中心来处理。当客户端从Nacos注册中心获取到可用的服务实例列表后,负载均衡和流量控制的责任落在了客户端的实现上。5.1、临时实例与非临时实例Nacos中的实例分为临时实例和非临时实例,它们在生命周期和用途上有所不同:1、非临时实例(Persistent Instance):非临时实例是指注册到Nacos注册中心的服务实例,其生命周期不会受到外部因素的影响,除非主动取消注册或服务下线。这种实例适用于通常情况下稳定运行的服务,它们的注册信息会被持久化存储在Nacos服务器中,提供持久性的服务发现和注册功能。2、临时实例(Ephemeral Instance):临时实例是指服务实例在持有连接的客户端断开连接时,会自动从Nacos注册中心上注销的一种实例类型。当客户端与Nacos注册中心建立心跳连接后,临时实例会周期性地向注册中心进行连接进行检测。如果一段时间注册中心没有收到临时实例的心态检测,注册中心会将对应的实例注销。临时实例适用于动态扩缩容、临时性访问等场景,允许实例根据连接状态进行动态管理。临时实例和非临时实例在实践中有不同的应用场景:对于稳定运行的服务,应使用非临时实例。这样服务实例的注册信息将持久存在于注册中心中,即使服务发生故障或重启,也能保证注册信息的持久性,其他服务能够继续发现和使用该实例。对于临时性的任务,如定时任务、临时数据处理服务等,可以选择使用临时实例。这样在任务完成后,实例会自动注销,降低注册中心中无用实例的数量,也能更好地适应动态需求变化。需要注意的是,临时实例的自动注销是基于与Nacos注册中心之间的心跳连接,而非基于服务实例的具体运行状态。因此,在使用临时实例时,需要相应地配置和管理心跳连接,以保证实例的不间断注册和注销。5.2、配置临时实例与非临时实例(默认是临时实例)服务注册到nacos时,可以选择注册为临时或非临时实例,通过在application.yml文件中添加下面的配置来设置:spring:cloud:nacos:discovery: #discovery:发现,透露 ; ephemeral:短暂的ephemeral: false #设置为非临时实例总结1.Nacos与eureka的共同点都支持服务注册和服务拉取都支持服务提供者心跳方式做健康检测2.Nacos与Eureka的区别Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式临时实例心跳不正常会被剔除,非临时实例则不会被剔除Nacos支持服务列表变更的消息推送模式,服务列表更新更及时Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式6、Nacos配置管理以下是几个使用Nacos配置管理的实际业务场景的例子:微服务配置管理:假设你的公司采用了微服务架构,有多个微服务需要连接到不同的数据库。通过使用Nacos配置管理,你可以将每个微服务的数据库连接信息存储在Nacos中,并动态地更新配置。例如,当你需要更改数据库的连接地址或密码时,你可以直接在Nacos中修改配置,而无需重新部署微服务,所有微服务将自动获取最新的配置信息。多环境配置管理:假设你的应用在开发、测试和生产环境中运行,并且每个环境都有不同的配置。使用Nacos配置管理,你可以为每个环境创建不同的配置文件,并通过Nacos的命名空间和配置组进行组织。开发人员可以通过选择不同的命名空间和配置组,轻松地切换到不同的环境,而不必手动修改配置文件。动态路由配置:假设你的微服务架构中使用了Spring Cloud Gateway作为API网关,并且你希望能够动态路由请求到不同的后端服务。通过使用Nacos配置管理,你可以将路由规则存储在Nacos中,并通过Spring Cloud Gateway与Nacos集成。当需要更新路由规则时,你可以直接在Nacos中修改配置,Spring Cloud Gateway将自动更新路由,并将请求动态地转发到相应的后端服务。定时任务配置:假设你的应用需要执行定时任务,例如生成报表或清理数据。通过使用Nacos配置管理,你可以将定时任务的触发时间、任务参数等信息存储在Nacos中,并由任务调度器定期从Nacos获取最新的配置。当你需要调整定时任务的执行时间或参数时,只需在Nacos中修改配置,任务调度器将自动根据最新的配置执行任务。这些例子只是Nacos配置管理功能的一部分,Nacos还提供了更多的特性,例如配置监听、配置推送等,可以根据具体的业务需求进行灵活使用。6.1、统一配置管理6.1.1、配置更改热更新在Nacos中添加配置信息:在弹出表单中填写配置信息:在nacos服务页面中配置,让服务找到nacos的配置:1、引入nacos的配置管理客户依赖:com.alibaba.cloudspring-cloud-starter-alibaba-nacos-config2、在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:spring:application:name: userservice #配置服务名称cloud:nacos:server-addr: localhost:8848 #配置nacos服务地址config:file-extension: yaml #文件后缀profiles:active: dev #开发环境,这里是dev可以在Usercontroller类中获取一下nacos配置的信息来验证一下:@Value("${pattern.dateformat}")private String dateformat;@GetMapping("/now")public String now(){return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));}6.1.2、配置自动刷新nacos中的配置文件变更后,微服务无需重新启动就可以感知。不过需要通过下面两种配置实现:方式一:在@Value注入的变量所在类上添加注解@RefreshScope方式二:使用@ConfigurationProperties注解这种方式使用的是约束大于配置的配置方法。@Data@Component@ConfigurationProperties(prefix = "pattern")public class PatternProperties {private String dateformat;}使用了Spring框架的注解(Data,Component,ConfigurationProperties)。这段代码定义了一个名为PatternProperties的类。@ConfigurationProperties注解是Spring框架中用于绑定配置属性的注解。它的prefix属性指定了配置文件中的属性前缀为"pattern",这意味着在配置文件中,所有以"pattern"开头的属性会被绑定到PatternProperties类的对应属性上。PatternProperties类中只有一个私有的String类型属性dateformat,它对应配置文件中的"pattern.dateformat"属性。在配置文件中,你可以设置这个属性的值,例如:pattern:dateformat: yyyy-MM-dd HH:mm:ss这样,当你运行程序时,Spring框架会自动将配置文件中的值绑定到PatternProperties类的dateformat属性上,你可以通过获取PatternProperties实例的方式来访问并使用这个属性的值。在类中使用nacos中配置时可以通过以下方式获取:总结Nacos配置更改后,微服务可以实现热更新,方式:通过@Value注解注入,结合@RefreshScope来刷新通过@ConfigurationProperties注入,自动刷新注意事项:不是所有的配置都适合放到配置中心,维护起来比较麻烦建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置7、Nacos集群搭建7.1、集群结构图官方给出的Nacos集群图:其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。DNS(Domain Name System)、SLB(Server Load Balancer)和Nacos可以一起使用来构建一个完整的服务发现和负载均衡的系统。DNS是用于将域名解析为对应的IP地址的系统。在服务发现和负载均衡中,DNS可以被用来解析服务名称为对应的服务实例的IP地址。例如,假设有一个服务名为"my-service",通过将其注册到DNS中,可以将"my-service"解析为具体的服务实例的IP地址。SLB是一种负载均衡器,用于将流量分发到多个后端服务实例上,从而实现负载均衡和高可用性。SLB可以接收客户端请求,并根据负载均衡算法将请求分发到多个服务实例上。例如,当有多个服务实例提供相同的服务时,SLB可以根据负载情况,将请求分发到负载较低的实例上,以实现流量的均衡分布。Nacos是一个用于服务发现、配置管理和服务治理的开源项目。在服务发现和负载均衡中,Nacos作为服务注册中心和配置中心,可以用来管理各个服务实例的注册和注销,以及维护服务实例的元数据信息。Nacos可以提供给SLB和DNS所需的服务实例信息,从而实现服务发现和负载均衡的功能。结合这三个组件,整体的架构可以如下所示:服务实例注册:服务实例可以将自己的元数据信息注册到Nacos中,包括服务名称、IP地址、端口等信息。Nacos服务注册中心:Nacos注册中心负责存储和管理所有服务实例的元数据信息。Nacos配置中心:Nacos配置中心负责存储和管理应用程序的配置信息。DNS解析:客户端可以通过DNS解析服务名称为具体的服务实例的IP地址。SLB负载均衡器:SLB可以接收客户端请求,并根据负载均衡算法将请求分发到多个服务实例上。通过这样的结构,客户端可以通过DNS解析获取到服务实例的IP地址,并通过SLB将请求发送到可用的服务实例上,实现负载均衡。同时,Nacos作为服务注册中心和配置中心,可以管理服务实例的注册和配置信息,以确保服务的可用性和配置的一致性。这样的架构既提供了可靠的服务发现和负载均衡,也保证了配置的集中管理和动态更新能力。我们计划的集群结构:三个nacos节点的地址:| 节点 | ip | port || nacos1 | 192.168.150.1 | 8845 || nacos2 | 192.168.150.1 | 8846 || nacos3 | 192.168.150.1 | 8847 |7.2、集群搭建搭建集群的基本步骤:- 搭建数据库,初始化数据库表结构- 下载nacos安装包- 配置nacos- 启动nacos集群- nginx反向代理六、Feign远程调用RestTemplate方法调用存在的问题先看一下我们以前利用RestTemplate发起远程调用的代码:存在下面的问题:代码可读性差,编程体验不统一参数复杂URL难维护1、Feign的介绍Feign 提供了一种简单且优雅的方式来定义和调用基于HTTP的远程服务。通过使用Feign,您可以在客户端代码中定义接口,然后Feign会根据这些接口的定义自动生成实际的HTTP请求,并将其转发到远程服务。这样,您可以像调用本地方法一样调用远程服务的方法,无需显式地处理HTTP请求和响应。Feign 还支持对请求进行编码和解码、错误处理、请求和响应拦截器等功能,使得在微服务架构中处理远程服务变得更加方便和高效。使用Feign的一些优点包括:简化了客户端代码,使其更易于维护和理解。减少了手动处理HTTP请求和响应的工作量。支持多种编解码器,可处理多种数据格式。可与Spring Cloud等微服务框架无缝集成。在使用Feign时,您需要定义一个Java接口,该接口包含与远程服务相对应的方法和参数。然后,通过在应用程序的配置中启用Feign并使用Spring的依赖注入功能,您可以将Feign客户端注入到您的代码中,从而实现对远程服务的调用。Feign作为一个声明式的HTTP客户端,在微服务架构和分布式系统中有许多应用场景。以下是一些常见的使用场景:微服务间的通信:在微服务架构中,各个服务之间需要频繁地进行通信,Feign可以帮助简化服务间的HTTP通信,使得调用远程服务更加方便。服务消费者:当一个服务需要调用其他服务提供的API时,可以使用Feign来作为客户端来消费这些服务,而无需手动处理HTTP请求和响应。代理远程API:Feign可以将远程服务的API映射为本地接口,使得调用远程服务的过程就像调用本地方法一样简单。负载均衡:结合负载均衡的工具(如Ribbon),Feign可以实现在多个服务实例之间进行负载均衡,从而提高系统的可用性和性能。声明式的错误处理:Feign支持定义统一的错误处理逻辑,使得在发生错误时可以采取一致的处理方式,从而减少重复代码。数据格式处理:Feign支持多种编解码器,可以处理不同的数据格式,例如JSON、XML等,使得数据的传输和解析更加灵活和便捷。请求拦截与日志:Feign支持请求和响应拦截器,可以在发送请求和接收响应时进行拦截和处理,例如记录日志、鉴权等。总体而言,Feign适用于任何需要在微服务架构中进行HTTP通信的场景,特别是当您希望简化远程服务调用的代码并增加可读性和可维护性时,Feign是一个非常有用的工具。2、定义和使用Feign客户端在之前我是在调用其他服务提供的接口是使用的是RestTempale,为什么还要学Feign呢?Feign和RestTemplate都是在Spring框架中用于进行HTTP请求的工具,但它们在使用方式和特点上有一些区别。声明式 vs. 编程式:Feign是一个声明式的HTTP客户端,它允许您通过定义接口来描述对远程服务的请求,并自动生成底层HTTP调用。Feign使用注解来配置请求的URL、HTTP方法、请求参数等信息,使得代码更加简洁和易读。RestTemplate是一个编程式的HTTP客户端,您需要在代码中显式地构建HTTP请求,包括指定URL、HTTP方法、请求头、请求体等信息。虽然可以通过RestTemplate灵活地控制请求细节,但相比Feign,代码可能会更冗长和复杂。整合Spring Cloud vs. 单独使用:Feign是Spring Cloud项目的一部分,它与Spring Cloud的其他组件(如Eureka、Ribbon、Hystrix等)紧密集成,使得在微服务架构中使用Feign更加方便,并且提供了一些额外的特性,如负载均衡、服务发现等。RestTemplate是Spring Framework的一部分,它可以单独使用,没有与Spring Cloud的深度集成。如果您在非微服务环境中,或者不需要Spring Cloud提供的其他功能,RestTemplate是一个不错的选择。自动化的负载均衡:Feign与Ribbon(Spring Cloud中的负载均衡组件)集成,可以自动进行负载均衡,使得在多个服务实例中选择合适的目标服务。RestTemplate在默认情况下不支持自动的负载均衡,您需要手动编写代码来实现负载均衡,或者结合Ribbon来实现自动化负载均衡。请求拦截器和错误处理:Feign允许您定义请求和响应拦截器,从而可以在发送请求和接收响应时进行拦截和处理,例如记录日志、鉴权等。它还提供了声明式的错误处理机制,让您可以统一处理请求错误。RestTemplate也支持请求和响应拦截器,但是在处理错误时可能相对繁琐,需要通过捕获异常等方式来处理请求错误。综上所述,如果您在使用Spring Cloud和微服务架构,特别是在Feign与Ribbon、Eureka等组件进行集成时,Feign可能是更好的选择,因为它提供了一种简单且声明式的方式来定义和调用远程服务。然而,如果您在非微服务环境或不需要Spring Cloud提供的其他功能,RestTemplate仍然是一个可行的选择,尤其是当您需要更多的灵活性和对HTTP请求的直接控制时。2.1、Feign实战定义和使用Feign客户端需要以下步骤:1、添加依赖:首先,您需要在项目中添加Feign的依赖。如果您是使用Spring Boot项目,可以在pom.xml中添加以下依赖:org.springframework.cloudspring-cloud-starter-openfeign2、启用Feign客户端:为了使Feign客户端生效,您需要在Spring Boot应用程序的主类上添加@EnableFeignClients注解,这将启用Feign客户端的自动配置和发现。@MapperScan("cn.itcast.order.mapper")@SpringBootApplication@EnableFeignClientspublic class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}}3、创建Feign客户端接口:接下来,您需要定义一个Java接口,该接口将包含与远程服务相对应的方法和参数。这些方法的定义类似于普通的Spring组件接口,但是您可以使用Spring的注解来定义远程服务的URL、HTTP方法和其他相关信息。package cn.itcast.order.clients;import cn.itcast.order.pojo.User;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient("userservice") //使用@FeignClient来指导这个接口所有接口方法要访问的服务名称public interface UserClient {//定义调用接口@GetMapping("/user/{id}")User findUserById(@PathVariable("id") Long id);}4、使用Feign客户端:现在您可以在其他组件或服务中注入Feign客户端,并使用它来调用远程服务的方法。package cn.itcast.order.service;import cn.itcast.order.clients.UserClient;import cn.itcast.order.mapper.OrderMapper;import cn.itcast.order.pojo.Order;import cn.itcast.order.pojo.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate UserClient userClient; //注入User的Feign客户端public Order queryOrderById(Long id){Order order = orderMapper.findById(id);Long userId = order.getUserId();//使用Feign远程调用User user = userClient.findUserById(userId);order.setUser(user);return order;}}总结Feign的使用步骤引入依赖添加@EnableFeignClients注解编写FeignClient接口使用FeignClient中定义的方法代替RestTemplate3、自定义Feign的配置Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:一般我们需要配置的就是日志级别。日志级别Feign支持四种不同的日志级别,您可以根据需要选择适合的日志级别。这些日志级别用于控制Feign在发送请求和接收响应时记录的日志信息的详细程度。NONE:该日志级别最低,不记录任何日志信息。如果您不想在控制台输出任何关于Feign请求和响应的日志,可以选择此级别。BASIC:在BASIC级别下,Feign仅记录请求方法、URL和响应状态码的基本信息。这对于快速了解请求的基本情况很有帮助,但不会记录请求和响应的详细内容。HEADERS:在HEADERS级别下,Feign将记录请求和响应的头部信息,包括请求头和响应头。这样可以更详细地查看请求和响应的头部信息,有助于调试和了解请求的上下文。FULL:FULL级别是最详细的日志级别,它会记录请求和响应的所有详细信息,包括请求头、请求体、响应头和响应体。如果您需要完整的请求和响应信息来进行详细的调试和排查问题,FULL级别是最合适的选择。3.1自定义Feign的配置方式方式一:配置文件方式当使用配置文件的方式来配置Feign的自定义配置时,您可以借助Spring Boot的属性配置功能来实现。通过在配置文件(如application.properties或application.yml)中添加特定的属性,您可以自定义Feign的行为。首先,您需要在配置文件中添加Feign的相关属性。以YAML格式的配置文件为例,假设您想要配置Feign的日志级别为FULL,可以这样写:1、全局生效# application.ymlfeign:client:config:default: #这里用default就是全局配置,如果是写服务器名称,则是针对在某个微服务的配置loggerLevel: full #日志级别2、局部生效# application.ymlfeign:client:config:orderservice: #这里用服务名称,则只针对这个服务的配置loggerLevel: full #日志级别方式二:java代码方式,需要先声明一个Bean:在使用Feign时,您可以通过自定义配置来修改其行为和属性。为了自定义Feign的配置,您需要创建一个配置类,并在其中添加相关的Bean定义。下面是一个简单的示例来说明如何自定义Feign的配置:package cn.itcast.order.config;import feign.Logger;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class FeignConfig {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC;}}全局配置:如果是全局配置,则把它放到@EnableFeignClients这个注解中:@EnableFeignClients(defaultConfiguration = FeignConfig.class)局部配置:如果是局部配置,则把它放到@FeignClient这个注解中:@FeignClient(value = "userservice",configuration = FeignConfig.class)总结Feign的日志配置1.方式一是配置文件,feign.client.config.xxx.loggerLevelO如果xxx是default则代表全局如果xxx是服务名称,例如userservice则代表某服务2.方式二是java代码配置Logger.Level这个Bean如果在@EnableFeignClients注解声明则代表全局如果在@FeignClient注解中声明则代表某服务4、Feign的性能优化Feign的底层客户端实现是通过集成了其他HTTP客户端库来实现的。具体来说,Feign支持两种主要的HTTP客户端实现:JDK的URLConnection和Apache HttpClient。JDK的URLConnection:这是Java标准库中提供的用于HTTP通信的API。Feign可以直接使用JDK的URLConnection来发送HTTP请求。它是轻量级的,对于简单的HTTP通信,可能足够满足需求。如果您在项目中没有引入其他HTTP客户端库,Feign会默认使用JDK的URLConnection作为底层的客户端实现。Apache HttpClient:Apache HttpClient是Apache基金会提供的一个功能丰富、灵活的HTTP客户端库。它提供了许多高级功能,如连接池、重试机制、认证等。如果您在项目中引入了Apache HttpClient的依赖,Feign会自动选择使用Apache HttpClient作为底层的客户端实现。OkHttp:OkHttp是Square公司开发的一款高效的HTTP客户端库。它支持HTTP/2、连接池、拦截器等现代特性。如果您在项目中引入了OkHttp的依赖,Feign会自动切换到OkHttp作为底层实现。Feign的这种设计使得您可以根据需要灵活地选择底层的HTTP客户端。默认情况下,如果项目中没有引入其他HTTP客户端库,Feign将使用JDK的URLConnection作为底层客户端。如果您希望使用Apache HttpClient或OkHttp,只需在项目中添加相应的依赖,Feign会自动检测并使用它们作为底层实现。通过这种方式,Feign可以同时满足不同项目对HTTP客户端的需求,并提供简便的远程服务调用方式。因此优化Feign的性能主要包括:使用连接池代替默认的URLConnection日志级别,做好使用basic或者none4.1、 Feign性能优化HttpClient的支持:1、Feign添加HttpClient的支持:引入依赖:io.github.openfeignfeign-httpclient配置连接池:#feign自定义配置,配置日志级别feign:client:config:default: #这里用default就是全局配置,如果是写服务器名称,则是针对在某个微服务的配置loggerLevel: basic #日志级别httpclient:enabled: true #开启feign对HttpClient的支持max-connections: 200 #最大连接数量max-connections-per-route: 50 #每个路径的最大连接数量总结Feign的优化1.日志级别尽量用basic2.使用HttpClient或OKHttp代替URLConnection引入feign-httpClient依赖配置文件开启httpClient功能,设置连接池参数5、Feign的最佳实际方式一(继承):给消费者的FeignClient和提供者的controller系统定义一个父接口作为标准。这种实现方式虽然可以让Controller和Feign Client共享同一个接口定义,但存在一些问题和注意事项:潜在的耦合:共享同一个接口定义会让Controller和Feign Client在代码层面产生耦合,导致它们紧密地关联在一起。一旦接口定义发生变化,两者都需要进行相应的修改,这可能影响到多个模块。不符合单一职责原则:Controller负责处理HTTP请求和返回响应,而Feign Client负责远程服务调用。将它们共享同一个接口可能会让代码功能变得混乱,违反了单一职责原则。难以做到完全解耦:尽管接口定义可以共享,但是在Feign Client的实现中,仍然需要涉及到远程服务调用的逻辑。这会让Feign Client的实现和Controller之间仍然有一定的耦合。接口定义可能会变得复杂:为了适应不同的调用场景,共享接口可能会变得复杂,可能需要添加各种参数和注解,从而导致接口的冗长和不易维护。功能不一致问题:在Controller和Feign Client共享同一个接口的情况下,两者的功能可能不完全一致。在Controller中可能需要做一些本地逻辑处理,而在Feign Client中可能需要进行额外的远程服务调用。综上所述,虽然共享接口可以在一定程度上减少重复代码,但也会引入潜在的问题和复杂性。在实际开发中,更常见的做法是将Controller和Feign Client分别定义独立的接口,通过接口定义来规范各自的功能和职责。在需要共享方法定义的情况下,可以使用Java接口继承来实现方法的复用,而避免在Feign Client中直接实现Controller的方法。这样可以更好地保持代码的清晰和可维护性,并符合单一职责原则。方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用以前实现假如:我们有order-service、pay-service和user-service两个服务,现在order-service和pay-service都要去调用user-service提供的接口,我们就得order-service和pay-service服务里都要实现UserClient,假如我们不止只有这两个服务调用user-service呢,有10个、20个服务都要调用,那UserClient就得写10遍、20遍,就会重复的开发代码了。现在我们独立创建一个项目,将哪些重复的代码编写的东西全部放到这个项目/模块中(如UserClient、User实体类、DefaultConfig等),我们可以把这个项目的内容打成jar包,假如以后服务需要的时候直接引用依赖就可以总结Feign的最佳实践让controller和FeignClient继承同一接口将FeignClient、poJo、Feign的默认配置都定义到一个项目中,供所有消费者使用抽取FeignClient实现最佳实践方式二的步骤如下:1、创建一个module,命名为feign-api,然后引入feign的starter依赖引入feign的starter依赖:org.springframework.cloudspring-cloud-starter-openfeign2、将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中之前在order-service服务模块中饿这些东西就可以删除了。3、在order-service中引入feign-api的依赖cn.itcast.demofeign-api1.0这个依赖是我们项目中的feign-api模块里的内容。4、修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包5、重启测试当定义的FeignClient不在springApplication的包扫描范围时,这些FeignClient无法使用问题重启order-service服务后我们会发现报错了Field userClient in cn.itcast.order.service.OrderService required a bean of type 'cn.itcast.feign.clients.UserClient' that could not be found.该错误信息表明在OrderService类中的userClient字段的类型为cn.itcast.feign.clients.UserClient,但是Spring容器中找不到该类型的Bean。说明我们在自动装配UserClient时是找不到这个对象的实例的,原因是我们现在在order-service模块中使用的UserClient是通过添加了feign-api依赖而导进来的,在order-service模块启动时spring只会对启动类所在目录及这个目录里所有类进行扫描注册到spring容器中的,而UserClient所在的包就没有被扫描到,所在在order-service这个模块中的spring容器中就找不到了对于的对象实例了。解决办法方式一:指定FeignClient所在包@EnableFeignClients(basePackages = "cn.itcast.feign.clients")解决办法方式二:指定FeignClient字节码@EnableFeignClients(clients = {UserClient.class})总结不同包的FeignClient的导入有两种方式在@EnableFeignClients注解中添加basePackages,指定FeignClient所在的包在@EnableFeignClients注解中添加clients,指定具体FeignClient的字节码七、Gateway服务网关网关(Gateway)服务在微服务架构中起着重要的作用。它是位于客户端和后端微服务之间的中间层,用于处理和转发请求。为什么要使用网关服务呢?以下是一些主要的原因:统一入口:网关服务提供了一个统一的入口,客户端只需要向网关发送请求,而不需要直接调用各个微服务。这样简化了客户端的调用逻辑,同时也方便地对请求进行统一管理和处理。路由和负载均衡:网关服务可以根据请求的URL路径或其他条件将请求路由到相应的微服务实例。它还可以配合负载均衡算法,确保请求被均匀地分发给不同的服务实例,提高了系统的可用性和性能。安全控制:通过网关服务,可以实现对请求进行安全控制和认证。网关可以验证请求的身份、权限以及合法性,确保只有授权的请求能够访问相应的微服务。聚合和分解请求:网关服务可以聚合多个微服务的请求,将它们合并为一个请求返回给客户端,从而减少了客户端与后端服务之间的请求次数,降低了网络开销。缓存:网关服务可以对请求的响应进行缓存,从而减少重复请求对后端服务的压力,提高了系统的性能和响应速度。降级和容错:网关服务可以实现对后端服务的降级和容错处理。当某个微服务出现故障或不可用时,网关可以提供默认的响应或调用备用服务,避免了系统级的故障。监控和日志:通过网关服务,可以实现对请求和响应进行监控和日志记录。这有助于实时追踪请求的流程和性能,发现问题并进行及时的处理。综上所述,网关服务在微服务架构中扮演了一个重要的角色,它提供了统一入口、路由和负载均衡、安全控制、聚合和分解请求、缓存、降级和容错、监控和日志等功能,为整个系统提供了更高效、更安全、更稳定的请求处理和管理能力。1、网关技术在springcloud中网关的实现有两种:gateway和zuulZuul和Spring Cloud Gateway都是常用的网关实现,用于在微服务架构中处理和转发请求。它们都可以作为反向代理和请求路由器,但在设计和功能上有一些区别。Zuul:Zuul是Netflix提供的网关服务,被称为Netflix Zuul。它是一个基于Servlet的网关实现,构建在传统的Spring Cloud项目上。Zuul 1.x版本采用阻塞式I/O模型,Zuul 2.x版本采用非阻塞式I/O模型,基于Netty。特点:Zuul 1.x的阻塞式I/O模型限制了并发性能,虽然可以通过多实例部署来提高吞吐量,但对于高并发场景可能不够高效。Zuul 2.x基于非阻塞式I/O模型,可以提供更好的性能和吞吐量。Zuul支持动态路由和过滤器等功能,可以实现请求的动态转发和预处理。配置方式较为灵活,可以使用Groovy或Java DSL配置路由和过滤器。Spring Cloud Gateway:Spring Cloud Gateway是Spring Cloud项目中的网关服务,从Spring Cloud 2.x版本开始引入。它是基于Spring 5和Spring Boot 2构建的,采用了WebFlux框架,支持响应式编程。特点:Spring Cloud Gateway采用基于异步非阻塞的WebFlux框架,提供了更好的性能和响应能力,适用于高并发场景。支持动态路由和过滤器等功能,可以实现请求的动态转发和预处理。提供了丰富的过滤器,支持全局和局部过滤器的定义和使用。配置方式灵活,可以使用YAML或Java配置路由和过滤器。综合来说,Zuul和Spring Cloud Gateway都是成熟的网关实现,各有优势。如果您在使用Spring Cloud项目,推荐使用Spring Cloud Gateway,特别是在需要高并发和响应能力的场景下。而如果您在使用Netflix项目,可以选择使用Netflix Zuul 2.x版本或更高的版本,或考虑迁移到Spring Cloud Gateway。选择哪个网关取决于您的项目需求、技术栈和预期的性能要求。总结网关的作用:对用户请求做身份认证、权限校验将用户请求路由到微服务,并实现负载均衡对用户请求做限流2、搭建网关服务2.1、搭建网关服务的步骤:创建Gateway项目:首先,创建一个新的Spring Boot项目作为Gateway服务网关。添加依赖:在Gateway项目中,添加Spring Cloud Gateway和Nacos的依赖,以便使用Gateway和Nacos的功能。在pom.xml文件中添加以下依赖:org.springframework.cloudspring-cloud-starter-gatewaycom.alibaba.cloudspring-cloud-starter-alibaba-nacos-discovery3、配置网关路由:在application.yml中配置网关的路由规则,将请求路由到对应的微服务。例如:server:port: 10009 # 配置服务端口spring:application:name: gateway # 服务名称cloud:nacos:server-addr: localhost:8848 # 配置Nacos注册中心的地址和端口gateway:routes:- id: userservice_route # 路由规则的唯一标识,用于识别该路由规则。自定义的标识可以方便管理和维护。uri: lb://userservice # 指定目标微服务的服务名。lb://前缀表示使用负载均衡器来选择目标微服务的实例。这里目标微服务的服务名是userservice。predicates: # 断言条件列表,用于匹配请求是否符合该路由规则。- Path=/user/** # 匹配的URL路径,当请求的URL路径以/user/开头时,该路由规则会匹配。**表示匹配任意后续路径。- id: orderservice_route # 另一个路由规则的唯一标识,用于识别该路由规则。uri: lb://orderservice # 指定目标微服务的服务名。这里目标微服务的服务名是orderservice。predicates: # 断言条件列表,用于匹配请求是否符合该路由规则。- Path=/order/** # 匹配的URL路径,当请求的URL路径以/order/开头时,该路由规则会匹配。**表示匹配任意后续路径。4、启动nacos服务#Windows系统命令startup.cmd -m standalone#Linux系统命令sh startup.sh -m standalone就可以直接使用gateway的ip+端口来访问数据了,可以直接让gateway来将这些访问地址转发到相应的服务实例上,从而来访问数据(如访问地址:localhost:10009/order/101)2.2、搭建网关服务流程图客户端发起请求:客户端(如Web应用、移动端应用、或其他服务)发起HTTP请求。请求到达Gateway:请求首先到达Gateway服务网关。Gateway进行路由匹配:Gateway根据预先配置的路由规则,匹配请求的URL路径、请求头等信息,找到对应的微服务实例。服务发现:Gateway需要通过服务注册中心(如Nacos)来获取微服务实例的信息。它向Nacos发送服务发现请求,Nacos会返回目标微服务的实例列表。负载均衡:Gateway使用负载均衡算法从目标微服务实例列表中选择一个实例,将请求转发给该微服务实例。微服务接收请求:目标微服务实例接收到请求。微服务处理请求:微服务实例处理请求,并根据业务逻辑执行相应的操作。微服务返回响应:微服务实例生成响应结果,并将响应返回给Gateway。Gateway接收响应:Gateway接收到微服务实例的响应。Gateway处理响应:Gateway可以对微服务实例的响应进行处理,如添加响应头、日志记录等。响应返回客户端:Gateway将处理后的响应返回给客户端。通过服务注册中心(如Nacos)来进行服务发现,Gateway能够动态地获取微服务的实例信息,从而实现请求的动态转发和负载均衡。这样,Gateway就能找到相应的服务并将请求转发给它们。感谢您的指正,希望这次的回答更加准确。总结网关搭建步骤创建项目,引入nacos服务发现和gateway依赖配置application.yml,包括服务基本信息、nacos地址、路由路由配置包括路由id:路由的唯一标示路由目标 (uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡路由断言 (predicates): 判断路由的规则路由过滤器 (filters):对请求或响应做处理3、路由断言工厂Route Predicate Factory路由断言工厂(Route Predicate Factory)是Spring Cloud Gateway中的一种配置方式,用于根据请求的条件来进行路由匹配。它可以根据请求的不同属性(例如URL路径、请求头、请求方法等)来判断请求是否匹配某个路由规则,如果匹配成功,则将请求转发到相应的目标地址(服务实例)。例如:Path=/order/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicata.PathRoutePredicateFactory类来处理像这种短语工厂在SpringCloudGateway还有十几个3.1、spring提供了11种基本的Predicate工厂当使用其他的Predicate工厂时,可到spring官网上看如何使用:spring route总结PredicateFactory的作用是什么?读取用户定义的断言条件,对请求做出判断Path=/user/**是什么含义?路径是以/user开头的就认为是符合的4、路由过滤器(Route Filter)路由过滤器(Route Filter)是Spring Cloud Gateway中的另一个重要组件,它用于在请求被路由到目标服务之前或之后,对请求或响应进行一系列的处理操作。通过路由过滤器,您可以对请求和响应进行修改、增强或验证,从而实现更加灵活和强大的网关功能。例如:假设我们有一个微服务架构,包含多个服务,其中一个是认证服务(Authentication Service),负责处理用户的身份认证。其他服务(如用户服务、订单服务等)需要保护某些资源,只允许经过认证的用户访问。在这种情况下,我们可以使用Spring Cloud Gateway作为网关,通过路由过滤器来实现请求认证和授权:全局过滤器(Global Filter):我们可以创建一个全局过滤器来拦截所有的请求,在这个过滤器中进行用户身份认证的检查。比如,我们可以检查请求中是否包含有效的身份令牌(Token),以确定请求是否是经过认证的用户发起的。局部过滤器(Route-Specific Filter):对于特定需要授权访问的服务,我们可以在路由配置中使用局部过滤器。在这个过滤器中,我们可以对请求进行权限验证,检查用户是否有权限访问该服务提供的资源。如果用户没有合法的权限,则可以拒绝请求或返回相应的错误信息。这样,通过路由过滤器,我们可以实现全局的请求认证,并对需要授权访问的服务进行权限控制。用户在发起请求时,首先经过全局过滤器进行认证,然后再经过局部过滤器进行权限验证。只有通过认证和授权的请求才能继续访问后端的微服务。这种方式使得认证和授权逻辑从业务服务中剥离出来,统一交给网关进行处理,简化了业务服务的实现和管理,同时提高了系统的安全性和可维护性。4.1、过滤工厂 GratewayFilterFactory在Spring Cloud Gateway中,过滤工厂(Filter Factory)是用于创建过滤器的工厂类,它用于生成过滤器实例并配置过滤器的行为。过滤工厂是一种更高级别的抽象,它将过滤器的创建和配置过程封装在一起,使得配置网关过滤器更加简单和灵活。过滤工厂与过滤器的区别在于:过滤器:是过滤器实际的执行逻辑,它需要开发者自己实现并继承特定的Filter接口(如GlobalFilter或GatewayFilter),完成请求和响应的处理。过滤工厂:是用于创建过滤器的工厂类,它将过滤器的创建和配置封装起来,通过配置工厂的参数,可以快速创建不同类型的过滤器,并指定过滤器的行为。过滤工厂的优势在于,它提供了一些预定义的工厂,可以轻松地为常见的过滤器场景创建过滤器,而无需开发者自己编写过滤器的实现。通过简单的配置,开发者可以使用这些预定义的过滤工厂创建自定义的过滤器,从而实现特定的路由和请求处理逻辑。spring提供了31种不同的路由过滤器工厂,一些常用的过滤工厂包括:通过使用过滤工厂,您可以在网关中快速配置和管理过滤器,实现对请求和响应的灵活处理。过滤工厂是Spring Cloud Gateway提供的一种高级配置方式,帮助开发者更加方便地定制和扩展网关的功能。如果想使用其他的可以到过往进行学习:spring GatewayFilterFactory4.1.1、案例给所有进入userservice的请求添加一个请求头默认过滤器总结过滤器的作用是什么?对路由的请求或响应做加工处理,比如添加请求头配置在路由下的过滤器只对当前路由的请求生效defaultFilters的作用是什么?对所有路由都生效的过滤器5、全局过滤器 GlobalFilter全局过滤器(Global Filter):我们可以创建一个全局过滤器来拦截所有的请求,在这个过滤器中进行用户身份认证的检查。比如,我们可以检查请求中是否包含有效的身份令牌(Token),以确定请求是否是经过认证的用户发起的。全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。定义方式是实现GlobalFilter接口。5.1、实现步骤自定义来,实现GlobalFilter接口,添加@Order注解import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import org.springframework.util.MultiValueMap;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;/*** 自定义全局过滤器:用于校验请求中的authorization参数是否合法*/@Component@Order(-1)public class AuthorizeFilter implements GlobalFilter {/*** 过滤器方法,用于校验请求中的authorization参数是否合法* @param exchange 服务器WebExchange对象,包含请求和响应信息* @param chain GatewayFilterChain对象,用于传递请求给下一个过滤器或路由处理器* @return Mono,表示请求的处理结果*/@Overridepublic Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取请求参数MultiValueMap params = exchange.getRequest().getQueryParams();// 获取authorization参数String authorization = params.getFirst("authorization");// 校验authorization参数if ("ltc".equals(authorization)) {// 授权通过,请求继续传递给下一个过滤器或路由处理器return chain.filter(exchange);} else {// 授权不通过,设置响应状态码为403(禁止访问),并结束处理,返回禁止访问的响应exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);return exchange.getResponse().setComplete();}}}总结全局过滤器的作用是什么?对所有路由都生效的过滤器,并且可以自定义处理逻辑实现全局过滤器的步骤?实现GlobalFilter接口添加@Order注解或实现Ordered接口编写处理逻辑6、过滤器执行顺序请求进入网关会碰到三类过滤器: 当前路由的过滤器、DefaultFilter、GlobalFilter请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。这是我们就会想,这三个过滤器相同吗?怎么就可以放在同一个集合中进行排序了,别着急。对于当前路由过滤器和DefaultFilter的配置方法是非常相似的,区别就是当前路由过滤器放在路由内,而DefaultFilter是放在默认的过滤器内的。从java的底层来看他两个本质是一样的,只是作用范围不一样而已。在配置文件中配置的当前路由过滤器和DefaultFilter(默认过滤器)都是由AddRequestHeaderGatewayFilterFactory过滤器工厂来读取,然后生成了GatewayFilter过滤器,所以这两个过滤器本质都是同一类,他们两个都叫GatewayFilter过滤器。但是GlobalFilter全局过滤器为什么又可以和GatewayFilter来进行配置执行呢?在网关中有一个适配器GatewayFilterAdapter,他就可以将GlobalFilter适配成GatewayFilter来用。GatewayFilterAdapter适配器这个类就实现了GatewayFilter接口,他内部还接收了GlobalFilter全局过滤器,如果我们给他传入一个GlobalFilter全局过滤器,他就会将GlobalFilter适配成GatewayFilter来用。所以在网关中所有的过滤器都会被适配成Gateway来使用,所以这些过滤器就可以放到一个集合中来进行排序了。这时就会出现一个新的问题了,那放到了一个集合后,有怎么进行排序呢?6.1、过滤器执如何排序每个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。GlobalFilter通过实现Order接口,或者添加@Order注解来指定order值,由我们自己指定。路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。多余多个路由过滤器 和多个默认路由器:如果有多个路由过滤器,那他们是按照声明的顺序来递增order的值的,来排序他们的执行顺序。多个默认路由也是如此的,也是按声明的顺序来递增order的值,来排序他们的执行顺序。但是当默认过滤器、路由过滤器和GlobalFilter全局过滤器的order值相同呢,那又该怎么办呢?当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobalFilter全局过滤器的顺序执行。底层源码中有两个类是用来加载这三种过滤器的:当服务启动后RouteDefinitionRouteLocator类的getFilter()方法会首先执行,会先去配置文件中加载defaultFilters,然后才会去加载某个路由的route的filter,然后合并。当getFilter()方法执行完后,FilteringWebHandler类的handle()方法才会进行加载全局过滤器,加载后会与之前加载的过滤器合并后更具order排序,组织过过滤器链总结路由过滤器、defaultFilter、全局过滤器的执行顺序?order值越小,优先级越高当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器7、跨域问题处理7.1、跨越问题跨域问题(Cross-Origin Issue)是由浏览器的同源策略引起的。同源策略是一种安全机制,限制了网页文档或脚本从一个源(协议、域名、端口号的组合)加载或操作另一个源的内容。如果两个网页的协议、域名和端口号都相同,它们就是同源的,否则就被认为是跨域的。跨域:域名不一致就是跨域,主要包括:域名不同:www.taobao.com和 www.taobao.org 和 wwwjd.com 和 miaoshajd.com域名相同,端口不同: localhost:8080和localhost8081跨越问题:浏览器禁止请求的发起者和服务端发生跨域Ajax请求,请求被浏览器拦截的问题。跨域问题的出现是为了防止恶意网站利用客户端的漏洞进行攻击,保护用户的隐私和安全。浏览器会强制执行同源策略,禁止网页在不同源之间进行以下操作:通过 XMLHttpRequest 或 Fetch API 发送跨域请求。访问其他源的 Cookie、LocalStorage 和 IndexedDB。获取其他源的 DOM 元素。在其他源的窗口中执行脚本。举个例子来说明跨域问题:假设网站A的域名是 https://www.example.com,网站B的域名是 https://api.example.com。如果网站A的网页中使用 XMLHttpRequest 或 Fetch API 向网站B发送请求,那么由于它们不是同源的,浏览器会阻止这个请求,返回一个错误。这就是跨域问题。7.2、跨越问题解决办法 CORS只需要在application.yml文件中添加下面的配置即可。spring:cloud:gateway:globalcors:corsConfigurations:'[/**]': # 配置所有路径的全局CORS设置allowedOrigins: "https://example.com" # 允许的跨域请求源,这里设置为https://example.comallowedMethods: "GET, POST, PUT, DELETE" # 允许的请求方法,这里设置为GET、POST、PUT、DELETEallowedHeaders: "*" # 允许的请求头,这里设置为"*"表示允许任意请求头allowCredentials: true # 是否允许发送凭证信息(如Cookie),这里设置为true表示允许发送凭证信息maxAge: 3600 # 预检请求的缓存时间(单位:秒),这里设置为3600秒add-to-simple-url-handler-mapping: true #处理option请求被拦截问题配置详细介绍:corsConfigurations: 这个属性用于配置全局的CORS设置。[/**] 表示匹配所有路径,也就是对所有请求生效的全局CORS配置。allowedOrigins: 允许的跨域请求源。这里设置为 https://example.com,表示只允许来自 https://example.com 域名的请求进行跨域访问。你可以根据实际需求设置允许的域名,也可以使用通配符 * 表示允许所有域名的请求。allowedMethods: 允许的请求方法。这里设置为 GET, POST, PUT, DELETE,表示只允许这些HTTP方法的请求进行跨域访问。你可以根据需要添加或删除允许的方法。allowedHeaders: 允许的请求头。这里设置为 *,表示允许任意请求头。你也可以设置具体的请求头名称,比如 "Content-Type, Authorization",只允许这些请求头进行跨域访问。allowCredentials: 是否允许发送凭证信息(如Cookie)。这里设置为 true,表示允许发送凭证信息。如果设置为 false,则不允许发送凭证信息。maxAge: 预检请求的缓存时间(单位:秒)。预检请求是指浏览器在发送跨域请求前先发送一个 OPTIONS 请求来检查服务器是否允许跨域访问。这里设置为 3600 秒,表示预检请求的结果在 3600 秒内可以被缓存,减少预检请求的次数。add-to-simple-url-handler-mapping: 这个属性设置为 true,将CORS配置添加到简单URL处理程序映射中。这是一个内部属性,通常不需要手动设置。通过以上配置,Spring Cloud Gateway 将会在响应中添加相应的CORS响应头,允许来自 https://example.com 域名的跨域请求,允许的方法有 GET、POST、PUT、DELETE,允许任意请求头,允许发送凭证信息(如 Cookie),并且预检请求的缓存时间为 3600 秒。总结·CORS跨域要配置的参数包括哪几个?允许哪些域名跨域?允许哪些请求头?允许哪些请求方式?是否允许使用cookie?有效期是多久?————————————————原文链接:https://blog.csdn.net/m0_62498006/article/details/131927338
  • [技术干货] 微服务架构的初步实践:Spring Cloud Eureka与Feign
    一、引言 在当今的软件开发环境中,微服务架构已经成为了一种越来越流行的设计模式。微服务架构的优点在于其高度的模块化,能够将大型复杂的项目拆分为一系列小型的、独立运行的服务,有利于团队协作开发,也方便后期的维护和扩展。微服务架构因其优良的伸缩性和灵活性,使得团队能够根据业务需求快速地迭代和发布新功能。不仅如此,微服务架构还帮助提升了系统的可靠性,因为单个服务的故障不会影响到整个应用的运行。  然而,微服务架构的实施并非易事,它带来了许多新的挑战,包括服务的发现、配置管理、分布式数据管理、服务间的通信等。要解决这些挑战,我们需要引入新的工具和技术。在我的最近项目中,我有幸使用到了Spring Cloud、Eureka和Feign这三种工具,它们帮助我们解决了上述的挑战,从而更好地实现微服务架构。在这篇博客中,我会详细地分享这个过程。  二、Spring Cloud的基础概念及其在微服务架构中的应用 Spring Cloud是一种基于Spring Boot的微服务架构开发工具,它为微服务应用提供了一种简单、灵活且可靠的方式来构建分布式系统。Spring Cloud集成了多种微服务相关的开源工具,包括Netflix的Eureka、Hystrix、Zuul等,以及Spring自家的Spring Cloud Config、Spring Cloud Bus等。通过Spring Cloud,我们可以轻松地实现服务注册与发现、配置管理、智能路由、负载均衡、熔断器、消息总线等功能。  在我的项目中,Spring Cloud发挥了重要的作用。我们使用Spring Cloud来构建和管理我们的微服务架构,每个业务模块都是一个独立的微服务,可以独立部署,独立扩展。这种方式提高了系统的灵活性,使得我们可以根据每个服务的负载情况进行动态的伸缩。此外,Spring Cloud还提供了一种简单有效的方式来实现服务间的通信和数据交换。我们的服务可以通过Rest API进行通信,数据可以通过JSON或XML的方式进行交换,这种方式既简单又高效。  三、Eureka的基本介绍,服务注册与发现机制的详解 Netflix Eureka是Spring Cloud Netflix项目下的子项目,主要用于实现微服务架构中的服务治理功能。在微服务架构中,服务治理是非常重要的一环。为了提高系统的可靠性和可用性,我们需要保证每个服务都能正确运行,而且需要能够在服务出现故障时,快速地进行故障检测和恢复。这就需要一个能够管理所有服务的服务注册中心,而Eureka就是这样一个服务注册中心。 在我的项目中,我们使用Eureka来管理所有的服务。每个服务在启动后,都会向Eureka注册自己的信息,并定时发送心跳以更新状态。通过Eureka,我们可以轻松地获取到所有服务的状态,从而实现负载均衡和故障转移。 四、Feign的使用场景与应用,如何利用Feign进行服务间调用 在微服务架构中,服务间的通信是一个重要的问题。传统的方式是通过HTTP API进行通信,但这种方式需要手动管理HTTP连接,解析HTTP响应,这不仅复杂,而且容易出错。Feign是一种声明式的HTTP客户端,它可以简化HTTP请求的发送和接收过程。通过Feign,我们可以像调用本地方法一样,进行服务间的调用。  在我的项目中,我们使用Feign来进行服务间的调用。我们为每个需要调用的接口定义了一个Feign客户端,只需在接口上添加注解,就可以像调用本地方法一样,调用其他服务的接口。这种方式既简洁易用,又能保证类型安全。 五、在项目中具体实践微服务架构的过程及经验分享 在我们的项目初期,我们首先明确了各个微服务的功能范围,并根据功能划分进行了微服务的设计。我们使用Spring Boot来构建各个微服务,每个微服务都是一个独立的Spring Boot应用,可以独立部署,独立扩展。 微服务之间的通信,我们主要使用Feign来实现。我们为每个需要调用的接口定义了一个Feign客户端,只需在接口上添加注解,就可以像调用本地方法一样,调用其他服务的接口。 所有的服务都会向Eureka注册,并定时发送心跳。我们可以通过Eureka的控制台,查看到所有服务的状态,实现服务的动态管理。 实践过程中,我们深感微服务架构的优点,它使得我们能够更加灵活地迭代和发布新功能,也提高了系统的可靠性。但同时,我们也感受到了它的挑战,如服务之间的通信、分布式事务管理等。但是,通过使用Spring Cloud, Eureka和Feign这些工具,我们成功地解决了这些问题。  配置Eureka服务:  server:   port: 7001  #Eureka eureka:   instance:     hostname: eureka7001.com # Eureka服务端的实例名称   client:     register-with-eureka: false # 表示是否向eureka注册中心注册自己     fetch-registry: false # fetch-registry如果为false,则表示自己为注册中心     service-url: # 监控页面       # 单机模式:http://${eureka.instance.hostname}:${server.port}/eureka/       defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ openFeign调用服务: 首先,你需要在Spring Boot的启动类上添加@EnableFeignClients注解,启用Feign客户端: @SpringBootApplication @EnableFeignClients public class Application {     public static void main(String[] args) {         SpringApplication.run(Application.class, args);     } } 定义一个Feign客户端,用于调用其他服务:  @FeignClient(name = "user-service") public interface UserClient {     @GetMapping("/users/{id}")     User getUser(@PathVariable("id") Long id); } @RestController public class UserController {     private final UserClient userClient;      public UserController(UserClient userClient) {         this.userClient = userClient;     }      @GetMapping("/users/{id}")     public User getUser(@PathVariable("id") Long id) {         return userClient.getUser(id);     } } 六、总结 微服务架构的实践是一次非常有价值的经验。通过这次项目,我深刻理解到了微服务架构的优点,如高度模块化,容易扩展等。我也认识到,微服务架构并不是银弹,它也带来了一些新的挑战,如服务之间的通信,服务的管理等。但是,通过合理的设计,和适当的工具,如Spring Cloud, Eureka和 Feign,我们可以很好地应对这些挑战,构建出高效、稳定、可扩展的微服务系统。  我希望通过分享我的这个经验,能够帮助到正在或准备实践微服务架构的你。同时,我也期待你能分享你的经验和见解,让我们一起学习,一起进步。 ————————————————    原文链接:https://blog.csdn.net/BigDickGZ/article/details/131146127 
  • [技术干货] Eureka与Zuul的集成
    一、API网关简介 在微服务架构中,通常会有多个服务提供者。设想一个电商系统,可能会有商品、订单、支付、用户等多个类型的服务,而每个类型的服务数量也会随着整个系统体量的增大也会随之增长和变更。作为UI端,在展示页面时可能需要从多个微服务中聚合数据,而且服务的划分位置结构可能会有所改变。网关就可以对外暴露聚合API,屏蔽内部微服务的微小变动,保持整个系统的稳定性。  当然这只是网关众多功能中的一部分,它还可以做负载均衡,统一鉴权,协议转换,监控监测等一系列功能。  二、Zuul简介 Zuul是Spring Cloud全家桶中的微服务API网关。 所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。Zuul底层利用各种filter实现如下功能:  认证和安全 识别每个需要认证的资源,拒绝不符合要求的请求。 性能监测 在服务边界追踪并统计数据,提供精确的生产视图。 动态路由 根据需要将请求动态路由到后端集群。 压力测试 逐渐增加对集群的流量以了解其性能。 负载卸载 预先为每种类型的请求分配容量,当请求超过容量时自动丢弃。 静态资源处理 直接在边界返回某些响应。  Zuul包含了对请求的路由和过滤两个最主要的功能: 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础. Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得. 注意:Zuul服务最终还是会注册进Eureka 提供=代理+路由+过滤三大功能  三、Eureka简介 1、Spring-Cloud Euraka介绍 Spring-Cloud Euraka是Spring Cloud集合中一个组件,它是对Euraka的集成,用于服务注册和发现。Eureka是Netflix中的一个开源框架。它和 zookeeper、Consul一样,都是用于服务注册管理的,同样,Spring-Cloud 还集成了Zookeeper和Consul。  在项目中使用Spring Cloud Euraka的原因是它可以利用Spring Cloud Netfilix中其他的组件,如zull等,因为Euraka是属于Netfilix的。  2、Euraka介绍 Eureka由多个instance(服务实例)组成,这些服务实例可以分为两种:Eureka Server和Eureka Client。为了便于理解,我们将Eureka client再分为Service Provider和Service Consumer。  (1)Eureka Server 提供服务注册和发现 (2)Service Provider 服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到 (3)Service Consumer服务消费方,从Eureka获取注册服务列表,从而能够消费服务  3.cap原理(CAP三个属性对于分布式系统不能同时做到) P:Partition tolerance,网络分区容错。类似多机房部署,保证服务稳定性。 A: Availability,可用性。 C:Consistency ,一致性。  Eureka是AP,高可用与可伸缩的Service发现服务,突出可用性。  四、集成Eureka和Zuul(使用idea) 为了减少占用内存,建议创建maven工程 1.集成 Eureka Server (1)新建Module,选择 spring initializr, 选择如下依赖,如果未选择,不要担心,在pom.xml文件中添加依赖     <dependency>         <groupId>org.springframework.cloud</groupId>         <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>     </dependency> (2)在application.properties文件中添加  #当前服务名  spring.application.name=eureka #端口号 server.port=8761 # #Eureka是为注册中心,是否需要将自己注册到注册中心上(默认为true), # 本次位单机部署,不需要设置为true;但是注册中心集群时候必须为true;因为集群时,其           他Eureka需要从此Eureka中的节点上获取数据; eureka.client.register-with-eureka=false #Erueka是为注册中心,不需要检索服务信息; # (表示是否从Eureka Server获取注册信息,默认为true。 如果这是一个单点的 Eureka Server,不需要同步其他节点的数据,可以设为false) eureka.client.fetch-registry=false  #覆盖默认地址 eureka.client.service-url.defaultZone=http://localhost:8761/eureka (3)在启动类上添加 @EnableEurekaServer (4)浏览器输入 localhost:8761  2.集成 Service Consumer (1)新建Module,选择 spring initializr, 选择如下依赖,如果未选择,不要担心,在pom.xml文件中添加依赖     <dependency>         <groupId>org.springframework.cloud</groupId>         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>     </dependency> (2)在application.properties文件中添加  spring.application.name=consumer server.port=8762  eureka.client.service-url.defaultZone=http://localhost:8761/eureka      #Eureka Server 的端口号 (3)启动类  @SpringBootApplication public class ConsumerApplication {  public static void main(String[] args) {     SpringApplication.run(ConsumerApplication.class, args); }  @Bean RestTemplate restTemplate(){     return new RestTemplate(); }  } (4)controller  @RestController public class TestController {  @Autowired @Qualifier("restTemplate") RestTemplate restTemplate;  /**  * 处理get请求  * @return  */ @GetMapping("/hello") public String hello(){     return  restTemplate.getForObject("http://localhost:8763/hello",String.class); } } (4)浏览器输入 localhost:8761,新增内容如下 3.集成Service Provider,创建方式同 Service Consumer (1)在application.properties文件中添加  spring.application.name=provider server.port=8763 eureka.client.service-url.defaultZone=http://localhost:8761/eureka     #Eureka Server 的端口号 (2)controller  @RestController public class TestController {      @Value("${server.port}")     Integer port;     @GetMapping("/hello")     #路径名称     public String hello(){         return "hello "+port;     } } (3)浏览器输入 localhost:8761,新增内容如下 4. zuul集成 (1)创建同上,多加一个依赖    <dependency>         <groupId>org.springframework.cloud</groupId>         <artifactId>spring-cloud-starter-netflix-zuul</artifactId>    </dependency>     <dependency>         <groupId>org.springframework.cloud</groupId>         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>    </dependency> (2)在application.properties文件中添加 spring.application.name=zuul server.port=8764 eureka.client.service-url.defaultZone=http://localhost:8761/eureka  #匹配规则(最好设计阶段就避免)    将properties改为yml zuul.routes.consumer=/test/** #zuul.routes.consumer-hello=/consumer/hello/** #忽略服务 #zuul.ignored-services=provider #忽略请求 #zuul.ignored-patterns=/**/hello/** #前缀 #zuul.prefix=/javaboy (3)新建类(一定要加@Component)  @Component public class PermissFilter extends ZuulFilter {  /**  * 过滤器类型,一般是pre  * @return  */ @Override public String filterType() {     return "pre"; }  /**  * 过滤优先级  * @return  */ @Override public int filterOrder() {     return 0; }  /**  * 是否过滤  * @return  */ @Override public boolean shouldFilter() {     return true; }  /**  * 处理过滤逻辑  * @return  * @throws ZuulException  */ @Override public Object run() throws ZuulException {     RequestContext ctx = RequestContext.getCurrentContext();     HttpServletRequest req = ctx.getRequest();     String token = req.getParameter("myToken");     //     if(!"123".equals(token)){         ctx .setSendZuulResponse(false);         ctx.setResponseStatusCode(401);         ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");         ctx.setResponseBody("非法访问");     }     return null; } } (4)在启动类上加@EnableZuulProxy (5)浏览器输入 localhost:8761,新增内容如下 (6)测试 浏览器输入 localhost:8764/test/hello 浏览器输入 localhost:8764/test/hello?myToken=123 文章知识点与官方知识档案匹配,可进一步学习相关知识 ————————————————                   原文链接:https://blog.csdn.net/qy88666/article/details/105122023/ 
  • [技术干货] springboot获取当前服务ip_服务注册与发现组件 Eureka 应用实战
    在传统的单体应用中,组件之间的调用通过有规范约束的接口进行,实现不同模块间良好协作。在微服务架构中,原本的’巨石’应用按照业务被分割成相对独立的、提供特定功能的服务,每一个微服务都可以通过集群或者其他方式进行动态的扩展,每一个微服务实例的网络地址都可能动态变化,这使得原本通过硬编码地址的调用方式失去了适用性。微服务架构中,服务跨度之大,数量之多,迫切需要架构建立一个去中心化的组件对各个微服务实例的信息进行登记和管理,同时提供能力让各个服微务实例之间能够互相发现,从而达到互相调用的结果。  通常来说服务注册与发现包括两部分,一个是Server端,另一个是Client。Server是一个公用组件,为Client提供服务注册和发现的功能,维护着注册到自身的Client的相关信息,同时提供接口给Client获取到注册表中其他服务的信息,使得动态变化的Client在服务地址稳定的时间节点能够进行服务间调用。Client将自己的服务信息通过一定的方式登记到Server上,并在正常范围内维护自己信息的一致性,方便其他服务发现自己,同时可以通过Server获取到自己依赖的其他服务信息,完成服务调用。  Eureka简介 Eureka这个词来源于古希腊语,意为“我找到了!我发现了!”,据传,阿基米德在洗澡时发现浮力原理,高兴得来不及穿上裤子,跑到街上大喊:“Eureka(我找到了)!”。  在Netflix中,Eureka是一个REST风格的服务注册与发现的基础服务组件,主要是应用在AWS中定位中间层服务的负载均衡和故障转移。Eureka由两部分组成,一个是Eureka Server,提供服务注册和发现的功能,就是我们上面所说的Server端;另一个是Java客户端,称为Eureka Client,是为了使得与服务端交互更加简单,Eureka Client会定时将自己的信息注册到Eureka Server中,并从Server中发现其他服务。客户端中有一个内置的负载均衡器,用来进行基本的循环负载均衡。 Spring Cloud的版本基于Finchley.M9,spring-cloud-netflix的版本基于2.0.0.M8,Eureka的版本基于v1.8.7。  搭建Eureka服务注册中心 可以通过IDEA快速搭建包含Eurake Server依赖的SpringBoot项目 主要依赖   // eureka-client相关依赖org.springframework.cloudspring-cloud-starter-netflix-eureka-server // actuator相关依赖org.springframework.bootspring-boot-starter-actuator // Spring Web MVC相关依赖org.springframework.bootspring-boot-starter-web 在启动类中的注解 @EnableEurekaServer  @SpringBootApplication// `@EnableEurekaServer`注解会为项目自动配置必须的配置类,标识该服务为注册中心@EnableEurekaServer public class Chapter4EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(Chapter2EurekaServerApplication.class, args);}} 在 application.yml 配置文件中添加以下配置,配置注册中心的端口和标识自己为Eureka Server。  server: port: 8761eureka: instance: hostname: localhost client: register-with-eureka: false // 表明该服务不会向Eureka Server注册自己的信息 fetch-registry: false // 表明该服务不会向Eureka Server获取注册信息 service-url: // Eureka Server注册中心的地址,用于client与server进行交流 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ Eureka Server既可以独立部署,也可以集群部署,在集群部署的条件下,Eureka Server间会进行注册表信息同步的操作,这时被同步注册表信息的Eureka Server将会被同步注册表信息的Eureka Server称为 peer 。  请注意上述配置中service-url其实指向的注册中心为实例本身,通常来讲,每一个Eureka Server也是一个Eureka Client,它会尝试注册自己,所以需要最少一个注册中心的URL来定位对等点 peer 。如果不提供这样一个注册端点,注册中心也能工作,但是会在日志中打出无法向 peer 注册自己。在独立(Standalone)Eureka Server的模式下,Eureka Server一般会关闭作为client端注册自己的行为,注册中心将无注册到它的 peer 中。  Eureka Server与Eureka Client之间的维系主要通过心跳的方式实现,心跳(Heartbeat)即Eureka Client定时向Eureka Server汇报当前的本服务实例的状态。  Eureka Server需要随时维持最新的服务实例信息,所以在注册表中的每个服务实例都需要定期发送心跳到Server中以使自己的注册保持最新的状态(数据一般直接保存在内存中)。这意味着Eureka Client不需要为每次服务间请求都向注册中心请求依赖服务实例信息,Eureka Client将定时从Eureka Server中拉取Eureka Server中的注册表中的所有信息,并将注册表信息缓存到本地,用于服务发现。  启动Eureka Server后,应用会有一个主页面用来展示当前注册表中的服务实例信息和暴露一些基于HTTP协议的的endpoint在 /eureka 路径下被Eureka Client用于注册自身、获取注册表信息以及发送心跳等。 搭建Eureka服务提供者 可以通过IDEA快速搭建包含Eurake Client依赖的SpringBoot项目。 主要依赖有: // eureka-server相关依赖org.springframework.cloudspring-cloud-starter-netflix-eureka-clientorg.springframework.bootspring-boot-starter-web 启动类 @SpringBootApplicationpublic class Chapter4EurekaClientApplication {public static void main(String[] args) {SpringApplication.run(Chapter2EurekaClientApplication.class, args);}} 在 Finchley 版本中的Spring Cloud中,只要引入 spring-cloud-starter-netflix-eureka-client 的依赖,应用就会自动注册到Eureka Server,但是需要在配置文件中添加Eureka Server的地址。 在 application.yml 添加以下配置: eureka: instance: prefer-ip-address: true client: service-url: // Eureka Server注册中心的地址,用于client与server进行交流 defaultZone: http://localhost:8761/eureka/ server: port: 8765spring: application:  name: eureka-client 查看Eureka服务注册中心  搭建好上述的两个的Eureka应用后,依次启动Server和Client。  Eureka Server主页 访问地址Eureka Server的主页 http://localhost:8761 ,可以看到以下界面: 当前注册到Eureka Server上的服务实例信息 Eureka Server运行环境的通用信息 Eureka Server实例的信息 与服务注册中心交换信息 DiscoveryClient 来源于spring-cloud-client-discovery,是SpringCloud中定义的用来服务发现的顶级接口,在SpringCloud的各类服务发现组件中(如Netflix Eureka或者Consul)都有相应的实现。它提供从服务注册中心中根据serviceId的获取到对应的服务实例信息的能力。 当一个服务实例拥有 DiscoveryClient 的具体实现时,就可以从Eureka Server中发现其他的服务实例。 在Eureka Client中注入 DiscoveryClient ,并从Eureka Server获取服务实例的信息。 在 chapter2-eureka-client 添加一个 ServiceInstanceRestController 的controller  @RestControllerpublic class ServiceInstanceRestController { @Autowired private DiscoveryClient discoveryClient; @RequestMapping("/service-instances/{applicationName}") public List serviceInstancesByApplicationName( @PathVariable String applicationName) { return this.discoveryClient.getInstances(applicationName); }} 启动应用后,访问地址 http://localhost:8765/service-instances/eureka-client ,获取应用名为 eureka-client 的(服务本身)服务实例的元数据,结果如下: [ { "host":"192.168.1.168 ————————————————          原文链接:https://blog.csdn.net/weixin_42356829/article/details/112202396 
  • [技术干货] spring boot 最全配置说明
    # 端口号server.port=8000# Web URLserver.context-path=/config# 服务器ip地址server.address=# 设置http header大小   注意此处tomcat6-7 中默认是8192  即8k   并且每一个连接都会开辟一个8k的cache 修改配置一定注意server.max-http-header-size= # 设置返回http头 server.server-header= # 设置servlet path 默认为/ server.servlet-path=# 设置超时时间,单位为毫秒,如不设置将使用容器默认时间,-1表示不设置 server.connection-timeout= # 设置ServletContext parameter server.context-parameters.MyDefinition=我的定义 # 设置应用名 server.display-name=app # 是否启用相应压缩,支持text/html, text/xml, text/plain,text/css, text/javascript, application/javascriptserver.compression.enabled=false# 指定压缩类型,默认text/html, text/xml, text/plain,text/css, text/javascript, application/javascriptserver.compression.mime-types=# 指定不压缩的user-agent,多个以逗号分隔,默认值为:text/html,text/xml,text/plain,text/cssserver.compression.excluded-user-agents=# 超过该值将被压缩,默认是2048server.compression.min-response-size=1024# 错误地址,默认是/errorserver.error.path=# 是否包含异常的堆栈信息,默认是NEVER,其他ALWAYS,ON_TRACE_PARAMserver.error.include-stacktrace=# 当服务器错误是,是否显示错误信息,默认为trueserver.error.whitelabel.enabled=Jetty# Jetty 启用处理请求的线程数,设置值小于或等于处理器的2倍server.jetty.acceptors=# 设置Jetty post content 大小,单位为byteserver.jetty.max-http-post-size=# 设置Jetty selectors 线程数server.jetty.selectors=Jsp# 设置Jsp servlet class,默认为:org.apache.jasper.servlet.JspServlet,其他如:org.springframework.web.servlet.DispatcherServletserver.jsp-servlet.class-name=org.springframework.web.servlet.DispatcherServlet# 设置Jsp初始化参数server.jsp-servlet.init-parameters.MyDefinition=我的定义# 是否将Jsp servlet 注册到servlet 容器中server.jsp-servlet.registered=Cookie# 设置cookie描述server.session.cookie.comment=# 设置cookie作用域server.session.cookie.domain=# 设置cookie是否只读server.session.cookie.http-only=# 设置cookie最大有效时间server.session.cookie.max-age=# 设置cookie名称server.session.cookie.name=# 设置cookie生效路径server.session.cookie.path=# 如果使用SSL,设置为trueserver.session.cookie.secure=# 是否在重启时持久化sessionserver.session.persistent=# 存储路径server.session.store-dir=# session过期时间server.session.timeout=# 设置session跟踪模式(可以设置为"cookie", "url", "ssl")server.session.tracking-modes=SSL# 是否支持SSL ciphersserver.ssl.ciphers=# 设定client验证方式是wanted或neededserver.ssl.client-auth=# 开启ssl支持server.ssl.enabled=# 是否开启ssl协议server.ssl.enabled-protocols=# 存储区的密钥名称server.ssl.key-alias=# 存储区的密钥密码server.ssl.key-password=# ssl证书地址server.ssl.key-store=# 访问密钥的密码server.ssl.key-store-password=# 设置密钥存储的提供者server.ssl.key-store-provider=# 设置密钥存储的类型server.ssl.key-store-type=# 设置ssl协议 默认TLSserver.ssl.protocol=# 持有ssl证书的信任存储库server.ssl.trust-store=# 持有ssl证书存储库的密码server.ssl.trust-store-password=# 持有ssl证书存储库的提供者server.ssl.trust-store-provider=# 持有ssl证书存储类型server.ssl.trust-store-type=Tomcat# 请求队列的最大长度server.tomcat.accept-count=# 是否刷新日志缓冲区server.tomcat.accesslog.buffered=# 日志存储路径,默认logsserver.tomcat.accesslog.directory=# 是否存储log,默认为falseserver.tomcat.accesslog.enabled=# 日志文件后缀名,默认.yyyy-MM-ddserver.tomcat.accesslog.file-date-format=# 设定access logs的格式,默认: commonserver.tomcat.accesslog.pattern=# log文件前缀,默认是access_logserver.tomcat.accesslog.prefix=# 延迟将时间戳包含在文件命中当日志循环时server.tomcat.accesslog.rename-on-rotate=# 是否设置请求的IP地址、主机名、协议和端口的请求属性server.tomcat.accesslog.request-attributes-enabled=# 是否启用日志循环server.tomcat.accesslog.rotate=# 日志文件后缀名,默认.logserver.tomcat.accesslog.suffix=# 忽略文件字符server.tomcat.additional-tld-skip-patterns=# 在调用backgroundProcess方法的时间延迟。默认30秒server.tomcat.background-processor-delay=# tomcat根目录,默认是临时目录server.tomcat.basedir=# 正则表达式匹配内部代理server.tomcat.internal-proxies=# 服务器接受的最大连接数server.tomcat.max-connections=# 服务器接受最大的post内容请求server.tomcat.max-http-post-size=# 最大工作线程server.tomcat.max-threads=# 最少空闲工作线程server.tomcat.min-spare-threads=# 用于覆盖http头名称server.tomcat.port-header=# header包含协议名,通常命名格式为X-Forwarded-Protoserver.tomcat.protocol-header=# 表示请求使用ssl协议,默认为httpsserver.tomcat.protocol-header-https-value=# 是否使用/作为请求重定向的根路径server.tomcat.redirect-context-root=# 提取远程ip地址的头信息名称server.tomcat.remote-ip-header=# 配置uri字符编码server.tomcat.uri-encoding=#---------------------------------------- #CORE PROPERTIES #----- ----------------------------------- debug = false #启用调试日志。trace = false #启用跟踪日志。#logGING logging.config = #日志配置文件的位置。例如,Logback的`classpath:logback.xml`。logging.exception-conversion-word =%wEx #记录异常时使用的转换字。logging.file = #日志文件名(例如,`myapp.log`)。名称可以是精确位置或相对于当前目录。logging.file.max-history = 0 #要保留的归档日志文件的最大值。仅支持默认的logback设置。logging.file.max-size = 10MB #最大日志文件大小。仅支持默认的logback设置。logging.level。* =#日志级别严重等级映射。例如,`logging.level.org.springframework = DEBUG`。logging.path = #日志文件的位置。例如,`/ var / log`。logging.pattern.console = #用于输出到控制台的Appender模式。仅支持默认的Logback设置。logging.pattern.dateformat = yyyy-MM-dd HH:mm:ss.SSS #日志日期格式的Appender模式。仅支持默认的Logback设置。logging.pattern.file = #用于输出到文件的Appender模式。仅支持默认的Logback设置。logging.pattern.level =%5p #日志级别的Appender模式。仅支持默认的Logback设置。logging.register-shutdown-hook = false #在日志记录系统初始化时为其注册一个shutdown hook。#AOP spring.aop.auto =真#添加@EnableAspectJAutoProxy。spring.aop.proxy-target-class = true #是否要创建基于子类的(CGLIB)代理(true),而不是基于标准Java接口的代理(false)。#IDENTITY (ContextIdApplicationContextInitializer)spring.application.name = #应用程序名称。#ADMIN (SpringApplicationAdminJmxAutoConfiguration)spring.application.admin.enabled = false #是否为应用程序启用管理功能。spring.application.admin.jmx-name = org.springframework.boot:type = Admin,name = SpringApplication #JMX 应用程序管理员MBean的名称。#AUTO-CONFIGURATION spring.autoconfigure.exclude = #要排除的自动配置类。#BANNER spring.banner.charset = UTF-8 #横幅文件编码。spring.banner.location = classpath:banner.txt #横幅文本资源位置。spring.banner.image.location = classpath:banner.gif #横幅图像文件位置(也可以使用jpg或png)。spring.banner.image.width = 76 #字符中的横幅图像的宽度。spring.banner.image.height = #crs 中横幅图像的高度(默认基于图像高度)。spring.banner.image.margin = 2 #字符中的左手图像边距。spring.banner.image.invert = false #是否应针对暗终端主题反转图像。#SPRING CORE spring.beaninfo.ignore = true #是否跳过BeanInfo类的搜索。#SPRING CACHE(CacheProperties)spring.cache.cache-names = #Cmama 分隔的要创建的缓存名称列表(如果底层缓存管理器支持)。spring.cache.caffeine.spec = #用于创建缓存的规范。有关规格格式的更多详细信息,请参阅CaffeineSpec。spring.cache.couchbase.expiration = 0ms #条目到期。默认情况下,条目永不过期。请注意,此值最终会转换为秒。spring.cache.ehcache.config = #用于初始化EhCache的配置文件的位置。spring.cache.infinispan.config = #用于初始化Infinispan的配置文件的位置。spring.cache.jcache.config = #用于初始化缓存管理器的配置文件的位置。spring.cache.jcache.provider = #CachingProvider实现的完全限定名称,用于检索符合JSR-107的缓存管理器。仅当类路径上有多个JSR-107实现时才需要。spring.cache.redis.cache-null-values = true #允许缓存空值。spring.cache.redis.key-prefix = #键前缀。spring.cache.redis.time-to-live = 0ms #条目到期。默认情况下,条目永不过期。spring.cache.redis.use-key-prefix = true#写入Redis时是否使用密钥前缀。spring.cache.type = #Cache 类型。默认情况下,根据环境自动检测。#SPRING CONFIG - 仅使用环境属性(ConfigFileApplicationListener)spring.config.additional-location = #配置除默认值之外使用的文件位置。spring.config.location = #配置替换默认值的文件位置。spring.config.name = application #配置文件名。#HAZELCAST(HazelcastProperties)spring.hazelcast.config = #用于初始化Hazelcast的配置文件的位置。#PROJECT INFORMATION(ProjectInfoProperties)spring.info.build.location = classpath:META-INF / build-info.properties #生成的build-info.properties文件的位置。spring.info.git.location =类路径:git.properties 生成的git.properties文件#所在。#JMX spring.jmx.default域 = #JMX域名。spring.jmx.enabled = true #将管理bean公开给JMX域。spring.jmx.server = mbeanServer #MBeanServer bean name。#Email (MailProperties)spring.mail.default-encoding = UTF-8 #默认MimeMessage编码。spring.mail.host = #SMTP 服务器主机。例如,`smtp.example.com`。spring.mail.jndi-name = #会话JNDI名称。设置时,优先于其他会话设置。spring.mail.password = #SMTP 服务器的登录密码。spring.mail.port = #SMTP 服务器端口。spring.mail.properties。* = #其他JavaMail会话属性。spring.mail.protocol = smtp #SMTP服务器使用的协议。spring.mail.test-connection = false#是否在启动时测试邮件服务器是否可用。spring.mail.username = #STP 服务器的登录用户。#APICING SETTINGS(SpringApplication)spring.main.banner-mode = console #模式用于在应用程序运行时显示横幅。spring.main.sources = 要包含在ApplicationContext中的#Sources (类名,包名或XML资源位置)。spring.main.web-application-type = #用于显式请求特定类型的Web应用程序的标志。如果未设置,则根据类路径自动检测。#FILE ENCODING(FileEncodingApplicationListener)spring.mandatory-file-encoding = #应用程序必须使用的预期字符编码。#INTEREMENTIZATION (MessageSourceProperties)spring.messages.always-use-message-format = false #是否始终应用MessageFormat规则,甚至解析不带参数的消息。spring.messages.basename = messages #逗号分隔的basenames列表(本质上是一个完全限定的类路径位置),每个都遵循ResourceBundle约定,轻松支持基于斜杠的位置。spring.messages.cache-duration = #加载的资源包文件缓存持续时间。未设置时,捆绑包将永久缓存。如果未指定持续时间后缀,则将使用秒。spring.messages.encoding = UTF-8 #消息包编码。spring.messages.fallback-to-system-locale = true #如果找不到特定区域设置的文件,是否回退到系统区域设置。spring.messages.use-code-as-default-message = false #是否使用消息代码作为默认消息而不是抛出“NoSuchMessageException”。仅在开发期间推荐。#OUTPUT spring.output.ansi.enabled =检测#配置的ANSI输出。#PID FILE(ApplicationPidFileWriter)spring.pid.fail-on-write-error = #如果使用ApplicationPidFileWriter但它无法写入PID文件,则失败。spring.pid.file = #要写入的PID文件的位置(如果使用ApplicationPidFileWriter)。#PROFILES spring.profiles.active = #逗号分隔的有源配置文件列表。可以通过命令行开关覆盖。spring.profiles.include = #无条件地激活指定的逗号分隔的配置文件列表(如果使用YAML,则激活配置文件列表)。#Quartz调度器(QuartzProperties)spring.quartz.jdbc.comment前缀 = - #前缀在SQL初始化脚本单行注释。spring.quartz.jdbc.initialize-schema = embedded #数据库模式初始化模式。spring.quartz.jdbc.schema = classpath:org / quartz / impl / jdbcjobstore / tables_ @ @ platform @@ .sql #用于初始化数据库模式的SQL文件的路径。spring.quartz.job-store-type = memory #Quartz作业存储类型。spring.quartz.properties。* = #其他Quartz Scheduler属性。#REACTOR (ReactorCoreProperties)spring.reactor.stacktrace -mode.enabled = false #Reactor 是否应该在运行时收集堆栈跟踪信息。#SENDGRID(SendGridAutoConfiguration)spring.sendgrid.api-key = #SendGrid API密钥。spring.sendgrid.proxy.host = #SendGrid代理主机。spring.sendgrid.proxy.port = #SendGrid代理端口。#---------------------------------------- #WEB PROPERTIES #----- -----------------------------------#EmbEDDED SERVER CONFIGURATION(ServerProperties)server.address = #服务器应绑定到的网络地址。server.compression.enabled = false #是否启用了响应压缩。server.compression.excluded-user-agents = #要从压缩中排除的用户代理列表。server.compression.mime-types = text / html,text / xml,text / plain,text / css,text / javascript,application / javascript #应该压缩的以逗号分隔的MIME类型列表。server.compression.min-response-size = 2048 #执行压缩所需的最小“Content-Length”值。server.connection超时= #连接器在关闭连接之前等待另一个HTTP请求的时间。未设置时,将使用连接器的特定于容器的默认值。使用值-1表示没有(即无限)超时。server.error.include-exception = false #包含“exception”属性。server.error.include-stacktrace = never #何时包含“stacktrace”属性。server.error.path = / error #错误控制器的路径。server.error.whitelabel.enabled = true #是否在服务器出错时启用浏览器中显示的默认错误页面。server.http2.enabled = false#是否启用HTTP / 2支持,如果当前环境支持它。server.jetty.acceptors = #要使用的接受者线程数。server.jetty.accesslog.append = false #追加到日志。server.jetty.accesslog.date-format = dd / MMM / yyyy:HH:mm:ss Z #请求日志的时间戳格式。server.jetty.accesslog.enabled = false #启用访问日志。server.jetty.accesslog.extended-format = false #启用扩展NCSA格式。server.jetty.accesslog.file-date-format = #日期格式放在日志文件名中。server.jetty.accesslog.filename =#Log filename。如果未指定,则日志重定向到“System.err”。server.jetty.accesslog.locale = #请求日志的区域设置。server.jetty.accesslog.log-cookies = false #启用请求cookie的记录。server.jetty.accesslog.log-latency = false #启用请求处理时间的记录。server.jetty.accesslog.log-server = false #启用请求主机名的日志记录。server.jetty.accesslog.retention-period = 31 #删除轮换日志文件之前的天数。server.jetty.accesslog.time-zone = GMT #请求日志的时区。server.jetty.max-http-post-size = 0#HTTP post或put内容的最大大小(以字节为单位)。server.jetty.selectors = #要使用的选择器线程数。server.max-http-header-size = 0 #HTTP消息头的最大大小(以字节为单位)。server.port = 8080 #服务器HTTP端口。server.server-header = #用于Server响应头的值(如果为空,则不发送头)。server.use-forward-headers = #是否应将X-Forwarded- *头应用于HttpRequest。server.servlet.context-parameters。* = #Servlet context init参数。server.servlet.context-path = #应用程序的上下文路径。server.servlet.application-display-name = application #显示应用程序的名称。server.servlet.jsp.class-name = org.apache.jasper.servlet.JspServlet #JSP servlet的类名。server.servlet.jsp.init-parameters。* = #用于配置JSP servlet的Init参数。server.servlet.jsp.registered = true #是否已注册JSP servlet。server.servlet.path = / #主调度程序servlet的路径。server.servlet.session.cookie.comment = #会话cookie的评论。server.servlet.session.cookie.domain = #会话cookie的域名。server.servlet.session.cookie.http-only = 会话cookie的#“HttpOnly”标志。server.servlet.session.cookie.max-age = #会话cookie的最大年龄。如果未指定持续时间后缀,则将使用秒。server.servlet.session.cookie.name = #会话cookie名称。server.servlet.session.cookie.path = #会话cookie的路径。server.servlet.session.cookie.secure = 会话cookie的#“Secure”标志。server.servlet.session.persistent = false #是否在重新启动之间保留会话数据。server.servlet.session.store-dir = #用于存储会话数据的目录。server.servlet.session.timeout = #会话超时。如果未指定持续时间后缀,则将使用秒。server.servlet.session.tracking-modes = #会话跟踪模式(以下一项或多项:“cookie”,“url”,“ssl”)。server.ssl.ciphers = #支持的SSL密码。server.ssl.client-auth = #是否需要客户端身份验证(“想要”)或需要(“需要”)。需要信任存储。server.ssl.enabled = #启用SSL支持。server.ssl.enabled-protocols = #启用SSL协议。server.ssl.key-alias = #标识密钥库中密钥的别名。server.ssl.key-password = #用于访问密钥库中密钥的密码。server.ssl.key-store = #保存SSL证书的密钥库的路径(通常是jks文件)。server.ssl.key-store-password = #用于访问密钥库的密码。server.ssl.key-store-provider = #密钥库的提供者。server.ssl.key-store-type = #密钥库的类型。server.ssl.protocol = TLS #要使用的SSL协议。server.ssl.trust-store = #持有SSL证书的信任存储。server.ssl.trust-store-password = #用于访问信任库的密码。server.ssl.trust-store-provider = #信任存储的提供者。server.ssl.trust-store-type = #信任库的类型。server.tomcat.accept-count = 0 #当所有可能的请求处理线程都在使用时,传入连接请求的最大队列长度。server.tomcat.accesslog.buffered = true #是否缓冲输出以使其仅定期刷新。server.tomcat.accesslog.directory = logs #创建日志文件的目录。可以是绝对的或相对于Tomcat基础目录。server.tomcat.accesslog.enabled = false #启用访问日志。server.tomcat.accesslog.file最新格式= .yyyy-MM-dd #要放在日志文件名中的日期格式。server.tomcat.accesslog.pattern = common #访问日志的格式模式。server.tomcat.accesslog.prefix = access_log #日志文件名前缀。server.tomcat.accesslog.rename-on-rotate = false #是否延迟在文件名中包含日期戳,直到旋转时间。server.tomcat.accesslog.request-attributes-enabled = false #设置用于请求的IP地址,主机名,协议和端口的请求属性。server.tomcat.accesslog.rotate = true #是否启用访问日志轮换。server.tomcat.accesslog.suffix = .log#日志文件名后缀。server.tomcat.additional-tld-skip-patterns = #逗号分隔的其他模式列表,这些模式匹配要忽略的TLD扫描的jar。server.tomcat.background-processor-delay = 30s #调用backgroundProcess方法之间的延迟。如果未指定持续时间后缀,则将使用秒。server.tomcat.basedir = #Tomcat 基目录。如果未指定,则使用临时目录。server.tomcat.internal-proxies = 10 \\。\\ d {1,3} \\。\\ d {1,3} \\。\\ d {1,3} | \\。192 \\ 168 \\ d {1,3} \\ d {1,3} | \\。169 \\ 254 \\ d {1,3} \\ d {1,3} | \\。127 \\ d {1,3} \\ d {1,3} \\ d {1,3} | \\172 \\ 1 [6-9] {1} \\ d {1,3} \\ d {1,3} |。。\\172 \\ 2 [0-9] {1} \\ d {1,3} \\ d {1,3} |。。\\172 \\。3 [0-1] {1} \\。\\ d {1,3} \\。\\ d {1,3} #正则表达式匹配可信IP地址。server.tomcat.max-connections = 0 #服务器在任何给定时间接受和处理的最大连接数。server.tomcat.max-http-header-size = 0 #HTTP消息头的最大大小(以字节为单位)。server.tomcat.max-http-post-size = 0 #HTTP 帖子内容的最大大小(以字节为单位)。server.tomcat.max-threads = 0 #最大工作线程数。server.tomcat.min-spare-threads = 0 #最小工作线程数。server.tomcat.port-header = X-Forwarded-Port#用于覆盖原始端口值的HTTP头的名称。server.tomcat.protocol-header = #包含传入协议的标头,通常命名为“X-Forwarded-Proto”。server.tomcat.protocol-header-https-value = https #协议标头的值,指示传入请求是否使用SSL。server.tomcat.redirect-context-root = #是否应通过在路径中附加/来重定向对上下文根的请求。server.tomcat.remote-ip-header = #从中提取远程IP的HTTP头的名称。例如,`X-FORWARDED-FOR`。server.tomcat.resource.cache-ttl = #静态资源缓存的生存时间。server.tomcat.uri-encoding = UTF-8 #用于解码URI的字符编码。server.tomcat.use-relative-redirects = #通过调用sendRedirect生成的HTTP 1.1和更高版本的位置标头是使用相对还是绝对重定向。server.undertow.accesslog.dir = #Undertow 访问日志目录。server.undertow.accesslog.enabled = false #是否启用访问日志。server.undertow.accesslog.pattern = common #访问日志的格式模式。server.undertow.accesslog.prefix = access_log。#日志文件名前缀。server.undertow.accesslog.rotate = true#是否启用访问日志轮换。server.undertow.accesslog.suffix = log #日志文件名后缀。server.undertow.buffer-size = #每个缓冲区的大小,以字节为单位。server.undertow.direct-buffers = #是否在Java堆外部分配缓冲区。server.undertow.io-threads = #为worker创建的I / O线程数。server.undertow.eager-filter-init = true #是否应在启动时初始化servlet过滤器。server.undertow.max-http-post-size = 0 #HTTP 帖子内容的最大大小(以字节为单位)。server.undertow.worker-threads = #工作线程数。#FREEMARKER(FreeMarkerProperties)spring.freemarker.allow-request-override = false #是否允许HttpServletRequest属性覆盖(隐藏)控制器生成的同名模型属性。spring.freemarker.allow-session-override = false #是否允许HttpSession属性覆盖(隐藏)控制器生成的同名模型属性。spring.freemarker.cache = false #是否启用模板缓存。spring.freemarker.charset = UTF-8 #模板编码。spring.freemarker.check-template-location = true #是否检查模板位置是否存在。spring.freemarker.content-type = text / html #Content-Type value。spring.freemarker.enabled = true #是否为此技术启用MVC视图分辨率。spring.freemarker.expose-request-attributes = false #是否应在与模板合并之前将所有请求属性添加到模型中。spring.freemarker.expose-session-attributes = false #是否应在与模板合并之前将所有HttpSession属性添加到模型中。spring.freemarker.expose-spring-macro-helpers = true #是否公开一个RequestContext供Spring的宏库使用,名称为“springMacroRequestContext”。spring.freemarker.prefer-file-system-access = true #是否更喜欢文件系统访问以进行模板加载。文件系统访问可以热检测模板更改。spring.freemarker.prefix = #在构建URL时添加前缀以查看名称的前缀。spring.freemarker.request-context-attribute = #所有视图的RequestContext属性的名称。spring.freemarker.settings。* = #众所周知的FreeMarker密钥,传递给FreeMarker的配置。spring.freemarker.suffix = .ftl #在构建URL时附加到视图名称的后缀。spring.freemarker.template-loader-path = classpath:/ templates /#逗号分隔的模板路径列表。spring.freemarker.view-names = #可以解析的视图名称的白名单。#GLOVY TEMPLATES(GroovyTemplateProperties)spring.groovy.template.allow-request-override = false #是否允许HttpServletRequest属性覆盖(隐藏)控制器生成的同名模型属性。spring.groovy.template.allow-session-override = false #是否允许HttpSession属性覆盖(隐藏)控制器生成的同名模型属性。spring.groovy.template.cache = false #是否启用模板缓存。spring.groovy.template.charset = UTF-8 #模板编码。spring.groovy.template.check-template-location = true#是否检查模板位置是否存在。spring.groovy.template.configuration。* = #请参阅GroovyMarkupConfigurer spring.groovy.template.content-type = text / html #Content-Type value。spring.groovy.template.enabled = true #是否为此技术启用MVC视图分辨率。spring.groovy.template.expose-request-attributes = false #是否应在与模板合并之前将所有请求属性添加到模型中。spring.groovy.template.expose-session-attributes = false #在与模板合并之前是否应将所有HttpSession属性添加到模型中。spring.groovy.template.expose-spring-macro-helpers = true #是否公开一个RequestContext供Spring的宏库使用,名称为“springMacroRequestContext”。spring.groovy.template.prefix = #在构建URL时添加前缀以查看名称的前缀。spring.groovy.template.request-context-attribute = #所有视图的RequestContext属性的名称。spring.groovy.template.resource-loader-path = classpath:/ templates / #Template path。spring.groovy.template.suffix = .tpl #在构建URL时附加到视图名称的后缀。spring.groovy.template.view-names =#可以解析的视图名称的白名单。#SPRING HATEOAS(HateoasProperties)spring.hateoas.use-hal-as-default-json-media-type = true #是否应将application / hal + json响应发送给接受application / json的请求。#HTTP 消息转换spring.http.converters.preferred-json-mapper = #用于HTTP消息转换的首选JSON映射器。默认情况下,根据环境自动检测。#HTTP 编码(HttpEncodingProperties)spring.http.encoding.charset = UTF-8 #HTTP 请求和响应的字符集。如果未明确设置,则添加到“Content-Type”标头。spring.http.encoding.enabled = true #是否启用http编码支持。spring.http.encoding.force = #是否在HTTP请求和响应上强制编码到配置的字符集。spring.http.encoding.force-request = #是否在HTTP请求中强制编码到配置的字符集。未指定“force”时,默认为true。spring.http.encoding.force-response =#是否在HTTP响应中强制编码到配置的字符集。spring.http.encoding.mapping = #用于编码映射的Locale。#MULTIPART (MultipartProperties)spring.servlet.multipart.enabled = true #是否启用对分段上传的支持。spring.servlet.multipart.file-size-threshold = 0 #将文件写入磁盘的阈值。值可以使用后缀“MB”或“KB”分别表示兆字节或千字节。spring.servlet.multipart.location = #上传文件的中间位置。spring.servlet.multipart.max-file-size = 1MB #最大文件大小。值可以使用后缀“MB”或“KB”分别表示兆字节或千字节。spring.servlet.multipart.max-request-size = 10MB#最大请求大小。值可以使用后缀“MB”或“KB”分别表示兆字节或千字节。spring.servlet.multipart.resolve-lazily = false #是否在文件或参数访问时懒惰地解析多部分请求。#JACKSON (JacksonProperties)spring.jackson.date-format = #日期格式字符串或完全限定的日期格式类名。例如,`yyyy-MM-dd HH:mm:ss`。spring.jackson.default-property-inclusion = #控制序列化期间包含的属性。配置了Jackson的JsonInclude.Include枚举中的一个值。spring.jackson.deserialization。* = #Jackon on / off功能会影响Java对象的反序列化方式。spring.jackson.generator。* = #Jackson开/关功能,适用于发电机。spring.jackson.joda-date-time-format =#Joda日期时间格式字符串。如果未配置,如果使用格式字符串配置,则使用“date-format”作为后备。spring.jackson.locale = #用于格式化的区域设置。spring.jackson.mapper。* = #Jackson 通用开/关功能。spring.jackson.parser。* = #Jackson开启/关闭解析器的功能。spring.jackson.property-naming-strategy = #Jackson PropertyNamingStrategy的常数之一。也可以是PropertyNamingStrategy子类的完全限定类名。spring.jackson.serialization。* = #Jacker on / off功能会影响Java对象的序列化方式。spring.jackson.time-zone =#格式化日期时使用的时区。例如,“America / Los_Angeles”或“GMT + 10”。#GSON(GsonProperties)spring.gson.date-format = #序列化Date对象时使用的格式。spring.gson.disable-html-escaping = #是否禁用转义HTML字符,如'<','>'等.chring.gson.disable-inner-class-serialization = #是否在排除内部类时序列化。spring.gson.enable-complex-map-key-serialization = #是否启用复杂映射键(即非基元)的序列化。spring.gson.exclude-fields-without-expose-annotation = #是否排除所有不考虑序列化或反序列化但没有“Expose”注释的字段。spring.gson.field-naming-policy = #在序列化和反序列化期间应该应用于对象字段的命名策略。spring.gson.generate-non-executable-json = #是否通过在输出前添加一些特殊文本来生成不可执行的JSON。spring.gson.lenient = #是否宽容解析不符合RFC 4627的JSON.chring.gson.long-serialization-policy = #长和长类型的序列化策略。spring.gson.pretty-printing = #是否输出适合页面的序列化JSON以进行漂亮的打印。spring.gson.serialize-nulls = #是否序列化空字段。#JERSEY (JerseyProperties)spring.jersey.application-path = #作为应用程序基URI的路径。如果指定,则覆盖“@ApplicationPath”的值。spring.jersey.filter.order = 0 #Jersey过滤链顺序。spring.jersey.init。* = #通过servlet或过滤器传递给Jersey的Init参数。spring.jersey.servlet.load-on-startup = -1 #加载Jersey servlet的启动优先级。spring.jersey.type = servlet #Jersey集成类型。#SPRING LDAP(LdapProperties)spring.ldap.anonymous-read-only = false #只读操作是否应使用匿名环境。spring.ldap.base = #Base 后缀,所有操作都应该从中发起。spring.ldap.base-environment。* = #LDAP 规范设置。spring.ldap.password = #服务器的登录密码。spring.ldap.urls = #服务器的LDAP URL。spring.ldap.username = #登录服务器的用户名。#EmbEDDED LDAP(EmbeddedLdapProperties#EMBEDDED)spring.ldap.embedded.base-dn = #基本DN列表。spring.ldap.embedded.credential.username = #嵌入式LDAP用户名。spring.ldap.embedded.credential.password = #嵌入式LDAP密码。spring.ldap.embedded.ldif = classpath:schema.ldif #Schema (LDIF)脚本资源引用。spring.ldap.embedded.port = 0 #嵌入式LDAP端口。spring.ldap.embedded.validation.enabled = true #是否启用LDAP模式验证。spring.ldap.embedded.validation.schema = #自定义架构的路径。#MUSTACHE TEMPLATES(MustacheAutoConfiguration)spring.mustache.allow-request-override = false #是否允许HttpServletRequest属性覆盖(隐藏)控制器生成的同名模型属性。spring.mustache.allow-session-override = false #是否允许HttpSession属性覆盖(隐藏)控制器生成的同名模型属性。spring.mustache.cache = false #是否启用模板缓存。spring.mustache.charset = UTF-8 #模板编码。spring.mustache.check-template-location = true #是否检查模板位置是否存在。spring.mustache.content-type = text / html #Content-Type value。spring.mustache.enabled = true #是否为此技术启用MVC视图分辨率。spring.mustache.expose-request-attributes = false #是否应在与模板合并之前将所有请求属性添加到模型中。spring.mustache.expose-session-attributes = false #是否应在与模板合并之前将所有HttpSession属性添加到模型中。spring.mustache.expose-spring-macro-helpers = true #是否公开一个RequestContext供Spring的宏库使用,名称为“springMacroRequestContext”。spring.mustache.prefix= classpath:/ templates / #适用于模板名称的前缀。spring.mustache.request-context-attribute = #所有视图的RequestContext属性的名称。spring.mustache.suffix = .mustache #应用于模板名称的后缀。spring.mustache.view-names = #可以解析的视图名称的白名单。#SPRING MVC(WebMvcProperties)spring.mvc.async.request-timeout = #异步请求处理超时之前的时间。spring.mvc.contentnegotiation.favor-parameter = false #是否应使用请求参数(默认情况下为“format”)来确定请求的媒体类型。spring.mvc.contentnegotiation.favor-path-extension = false #是否应使用URL路径中的路径扩展来确定请求的媒体类型。spring.mvc.contentnegotiation.media-types。* = #映射内容协商的媒体类型的文件扩展名。例如,yml到text / yaml。spring.mvc.contentnegotiation.parameter-name =#启用“favor-parameter”时要使用的查询参数名称。spring.mvc.date-format = #要使用的日期格式。例如,`dd / MM / yyyy`。spring.mvc.dispatch-trace-request = false #是否将TRACE请求分派给FrameworkServlet doService方法。spring.mvc.dispatch-options-request = true #是否将OPTIONS请求分派给FrameworkServlet doService方法。spring.mvc.favicon.enabled = true #是否启用favicon.ico的解析。spring.mvc.formcontent.putfilter.enabled = true #是否启用Spring的HttpPutFormContentFilter。spring.mvc.ignore-default-model-on-redirect = true#在重定向场景中是否应忽略“默认”模型的内容。spring.mvc.locale = #要使用的语言环境。默认情况下,“Accept-Language”标头会覆盖此区域设置。spring.mvc.locale-resolver = accept-header #定义应如何解析语言环境。spring.mvc.log-resolved-exception = false #是否启用由“HandlerExceptionResolver”解析的异常的警告日志记录。spring.mvc.message-codes-resolver-format = #格式化消息代码的策略。例如,`PREFIX_ERROR_CODE`。spring.mvc.pathmatch.use-registered-suffix-pattern = false#后缀模式匹配是否仅适用于使用“spring.mvc.contentnegotiation.media-types。*”注册的扩展。spring.mvc.pathmatch.use-suffix-pattern = false #在将模式与请求匹配时是否使用后缀模式匹配(“。*”)。spring.mvc.servlet.load-on-startup = -1 #加载调度程序servlet的启动优先级。spring.mvc.static-path-pattern = / ** #用于静态资源的路径模式。spring.mvc.throw-exception-if-no-handler-found = false #如果没有找到Handler来处理请求,是否应该抛出“NoHandlerFoundException”。spring.mvc.view.prefix = #Spring MVC视图前缀。spring.mvc.view.suffix = #Spring MVC视图后缀。#SPRING RESOURCES HANDLING(ResourceProperties)spring.resources.add-mappings = true #是否启用默认资源处理。spring.resources.cache.cachecontrol.cache-private = #指示响应消息是针对单个用户的,并且不能由共享高速缓存存储。spring.resources.cache.cachecontrol.cache-public = #表示任何缓存都可以存储响应。spring.resources.cache.cachecontrol.max-age = #应该缓存响应的最长时间,如果未指定持续时间后缀,则以秒为单位。spring.resources.cache.cachecontrol.must-revalidate =#表示一旦它变得陈旧,缓存不得使用响应而不用服务器重新验证它。spring.resources.cache.cachecontrol.no-cache = #表示只有在与服务器重新验证时才能重用缓存的响应。spring.resources.cache.cachecontrol.no-store = #表示在任何情况下都不缓存响应。spring.resources.cache.cachecontrol.no-transform = #指示不应转换响应内容的中介(缓存和其他)。spring.resources.cache.cachecontrol.proxy-revalidate = #与“must-revalidate”指令的含义相同,不同之处在于它不适用于私有缓存。spring.resources.cache.cachecontrol.s-max-age = #共享缓存应缓存响应的最长时间,如果未指定持续时间后缀,则以秒为单位。spring.resources.cache.cachecontrol.stale-if-error = #遇到错误时可以使用响应的最长时间,如果未指定持续时间后缀,则以秒为单位。spring.resources.cache.cachecontrol.stale-while-revalidate = #响应变为失效后可以响应的最长时间,如果未指定持续时间后缀,则以秒为单位。spring.resources.cache.period = #资源处理程序所服务资源的缓存周期。如果未指定持续时间后缀,则将使用秒。spring.resources.chain.cache= true #是否在资源链中启用缓存。spring.resources.chain.enabled = #是否启用Spring资源处理链。默认情况下,禁用,除非至少启用了一个策略。spring.resources.chain.gzipped = false #是否启用已解压缩资源的解析。spring.resources.chain.html-application-cache = false #是否启用HTML5应用程序缓存清单重写。spring.resources.chain.strategy.content.enabled = false #是否启用内容版本策略。spring.resources.chain.strategy.content.paths = / **#逗号分隔的模式列表,应用于内容版本策略。spring.resources.chain.strategy.fixed.enabled = false #是否启用固定版本策略。spring.resources.chain.strategy.fixed.paths = / ** #以逗号分隔的模式列表应用于固定版本策略。spring.resources.chain.strategy.fixed.version = #用于固定版本策略的版本字符串。spring.resources.static-locations = classpath:/ META-INF / resources /,classpath:/ resources /,classpath:/ static /,classpath:/ public / #静态资源的位置。#SPRING SESSION(SessionProperties)spring.session.store-type = #会话存储类型。spring.session.timeout = #会话超时。如果未指定持续时间后缀,则将使用秒。spring.session.servlet.filter-order = -2147483598 #会话存储库过滤顺序。spring.session.servlet.filter-dispatcher-types = async,error,request #会话存储库过滤器调度程序类型。#SPRING SESSION HAZELCAST(HazelcastSessionProperties)spring.session.hazelcast.flush-mode = on-save #sessions flush mode。spring.session.hazelcast.map-name = spring:session:sessions #用于存储会话的地图的名称。#SPRING SESSION JDBC(JdbcSessionProperties)spring.session.jdbc.cleanup-cron = 0 * * * * * #cron 表达式用于过期的会话清理作业。spring.session.jdbc.initialize-schema = embedded #数据库模式初始化模式。spring.session.jdbc.schema = classpath:org / springframework / session / jdbc / schema- @@ platform @ @ .SQL #的路径SQL文件,以用于初始化数据库架构。spring.session.jdbc.table-name = SPRING_SESSION #用于存储会话的数据库表的名称。#春节会议MONGODB( MongoSessionProperties)spring.session.mongodb.collection-name = sessions #用于存储会话的集合名称。#SPRING SESSION REDIS(RedisSessionProperties)spring.session.redis.cleanup-cron = 0 * * * * * #Cron 表达式用于过期的会话清理作业。spring.session.redis.flush-mode = on-save #sessions flush mode。spring.session.redis.namespace = spring:session #用于存储会话的密钥的命名空间。#THYMELEAF(ThymeleafAutoConfiguration)spring.thymeleaf.cache = true #是否启用模板缓存。spring.thymeleaf.check-template = true #在呈现模板之前是否检查模板是否存在。spring.thymeleaf.check-template-location = true #是否检查模板位置是否存在。spring.thymeleaf.enabled = true #是否为Web框架启用Thymeleaf视图解析。spring.thymeleaf.enable-spring-el-compiler = false #在SpringEL表达式中启用SpringEL编译器。spring.thymeleaf.encoding = UTF-8 #模板文件编码。spring.thymeleaf.excluded-view-names = #逗号分隔的视图名称列表(允许的模式)应从分辨率中排除。spring.thymeleaf.mode = HTML #要应用于模板的模板模式。另请参见Thymeleaf的TemplateMode枚举。spring.thymeleaf.prefix = classpath:/ templates / #在构建URL时添加前缀以查看名称的前缀。spring.thymeleaf.reactive.chunked-mode-view-names = #逗号分隔的视图名称列表(允许的模式)应该是设置最大块大小时在CHUNKED模式下执行的唯一视图名称。spring.thymeleaf.reactive.full-mode-view-names =#逗号分隔的视图名称列表(允许的模式),即使设置了最大块大小,也应该在FULL模式下执行。spring.thymeleaf.reactive.max-chunk-size = 0 #用于写入响应的数据缓冲区的最大大小(以字节为单位)。spring.thymeleaf.reactive.media-types = #视图技术支持的媒体类型。spring.thymeleaf.servlet.content-type = text / html #Content-Type写入HTTP响应的值。spring.thymeleaf.suffix = .html #在构建URL时附加到视图名称的后缀。spring.thymeleaf.template-resolver-order = #链中模板解析器的顺序。spring.thymeleaf.view-名= #逗号分隔的视图名称列表(允许的模式),可以解析。#SPRING WEBFLUX(WebFluxProperties)spring.webflux.date-format = #要使用的日期格式。例如,`dd / MM / yyyy`。spring.webflux.static路径图案 = / ** #用于静态资源的路径模式。#SPRING WEB SERVICES(WebServicesProperties)spring.webservices.path = / services #作为服务基URI的路径。spring.webservices.servlet.init = #Servlet init参数传递给Spring Web Services。spring.webservices.servlet.load-on-startup = -1 #加载Spring Web Services servlet的启动优先级。spring.webservices.wsdl-locations =#逗号分隔的WSDL位置列表以及要作为bean公开的随附XSD。#---------------------------------------- #SECURITY PROPERTIES #----- ----------------------------------- #SECURITY(SecurityProperties)spring.security.filter.order = -100 #安全过滤器链顺序。spring.security.filter.dispatcher-types = async,error,request #安全过滤器链调度程序类型。spring.security.user.name = user #默认用户名。spring.security.user.password = #默认用户名的密码。spring.security.user.roles = #授予默认用户名的角色。#SECURITY OAUTH2 CLIENT(OAuth2ClientProperties)spring.security.oauth2.client.provider。* = #OAuth提供商详细信息。spring.security.oauth2.client.registration。* = #OAuth客户注册。#---------------------------------------- #DATA PROPERTIES#----- -----------------------------------#FLYWAY (FlywayProperties)spring.flyway.baseline-description = #spring.flyway.baseline-on-migrate = #spring.flyway.baseline-version = 1 #开始迁移的版本spring.flyway.check-location = true #是否检查迁移脚本位置是否存在。spring.flyway.clean-disabled = #spring.flyway.clean-on-validation-error = #spring.flyway.dry-run-output = #spring.flyway.enabled = true #是否启用flyway。spring.flyway.encoding = #spring.flyway.error-handlers = #= #spring.flyway.password =spring.flyway.group = #spring.flyway.ignore-future-migrations = #spring.flyway.ignore-missing-migrations = #spring.flyway.init-sqls = #在获取连接后立即执行以初始化连接的SQL语句。spring.flyway.installed-by = #spring.flyway.locations = classpath:db / migration #迁移脚本的位置。spring.flyway.mixed = #spring.flyway.out-of-order #如果你想让Flyway创建自己的DataSource,可以使用JDBC密码。spring.flyway.placeholder-prefix = #spring.flyway.placeholder-replacement = #spring.flyway.placeholder-suffix = #spring.flyway.placeholders。* = #spring.flyway.repeatable-sql-migration-prefix = #spring.flyway.schemas = #schemas to update spring.flyway.skip-default-callbacks = #spring.flyway.skip-default-resolvers = #spring.flyway.sql-migration-prefix = V #spring.flyway.sql-migration -separator =#spring.flyway.sql-migration-suffix = .sql #spring.flyway.sql-migration-suffixes = #spring.flyway.table = #spring.flyway.target = #spring.flyway.undo-sql-migration-prefix = #spring.flyway.url = 要迁移的数据库的JDBC url。如果未设置,则使用主要配置的数据源。spring.flyway.user = #要迁移的数据库的登录用户。spring.flyway.validate-on-migrate = ##LIQUIBASE(LiquibaseProperties)spring.liquibase.change-log = classpath:/db/changelog/db.changelog-master.yaml# 更改日志配置路径。spring.liquibase.check-change-log-location = true #是否检查更改日志位置是否存在。spring.liquibase.contexts = #逗号分隔的运行时上下文列表。spring.liquibase.default-schema = #默认数据库模式。spring.liquibase.drop-first = false #是否首先删除数据库模式。spring.liquibase.enabled = true #是否启用Liquibase支持。spring.liquibase.labels =#要使用的以逗号分隔的运行时标签列表。spring.liquibase.parameters。* =#更改日志参数。spring.liquibase.password = #要迁移的数据库的登录密码。spring.liquibase.rollback-file = #执行更新时写入回滚SQL的文件。spring.liquibase.url = #JDBC要迁移的数据库的URL。如果未设置,则使用主要配置的数据源。spring.liquibase.user = #要迁移的数据库的登录用户。#COUCHBASE(CouchbaseProperties)spring.couchbase.bootstrap-hosts = #Couchbase节点(主机或IP地址)来自bootstrap。spring.couchbase.bucket.name = default #要连接的存储桶的名称。spring.couchbase.bucket.password = #桶的密码。spring.couchbase.env.endpoints.key-value = 1 #每个节点对密钥/值服务的套接字数。spring.couchbase.env.endpoints.queryservice.min-endpoints = 1 #每个节点的最小套接字数。spring.couchbase.env.endpoints.queryservice.max-endpoints = 1 #每个节点的最大套接字数。spring.couchbase.env.endpoints.viewservice.min-endpoints = 1 #每个节点的最小套接字数。spring.couchbase.env.endpoints.viewservice.max-endpoints = 1 #每个节点的最大套接字数。spring.couchbase.env.ssl.enabled = #是否启用SSL支持。除非另有说明,否则在提供“keyStore”时自动启用。spring.couchbase.env.ssl.key-store = #保存证书的JVM密钥库的路径。spring.couchbase.env.ssl.key-store-password = #用于访问密钥库的密码。spring.couchbase.env.timeouts.connect = 5000ms #Bucket连接超时。spring.couchbase.env.timeouts.key-value = 2500ms #阻止对特定密钥超时执行的操作。spring.couchbase.env.timeouts.query = 7500ms #N1QL查询操作超时。spring.couchbase.env.timeouts.socket-connect = 1000ms #Socket 连接超时。spring.couchbase.env.timeouts.view = 7500ms #常规和地理空间视图操作超时。#DAO (PersistenceExceptionTranslationAutoConfiguration)spring.dao.exceptiontranslation.enabled = true #是否启用PersistenceExceptionTranslationPostProcessor。#CASSANDRA (CassandraProperties)spring.data.cassandra.cluster-name = #Cassandra 集群的名称。spring.data.cassandra.compression = none #Cassandra二进制协议支持的压缩。spring.data.cassandra.connect-timeout = #Socket 选项:连接超时。spring.data.cassandra.consistency-level = #查询一致性级别。spring.data.cassandra.contact-points = localhost #群集节点地址。spring.data.cassandra.fetch-size = #查询默认提取大小。spring.data.cassandra.keyspace-name = #要使用的Keyspace名称。spring.data.cassandra.load-balancing-policy =#负载均衡策略的类名称。spring.data.cassandra.port = #Cassandra 服务器的端口。spring.data.cassandra.password = #服务器的登录密码。spring.data.cassandra.pool.heartbeat-interval = 30s #Heartbeat interval ,在空闲连接上发送消息以确保它仍然存在。如果未指定持续时间后缀,则将使用秒。spring.data.cassandra.pool.idle-timeout = 120s #删除空闲连接之前的空闲超时。如果未指定持续时间后缀,则将使用秒。spring.data.cassandra.pool.max-queue-size = 256#如果没有可用的连接,则排队的最大请求数。spring.data.cassandra.pool.pool-timeout = 5000ms #尝试从主机池获取连接时的池超时。spring.data.cassandra.read超时=选项:读取超时。spring.data.cassandra.reconnection-policy = #重新连接策略类。spring.data.cassandra.repositories.type = auto #要启用的Cassandra存储库的类型。spring.data.cassandra.retry-policy = #重试策略的类名。spring.data.cassandra.serial-consistency-level = #查询串行一致性级别。spring.data.cassandra.schema-action = none #启动时要采取的架构操作。spring.data.cassandra.ssl = false #启用SSL支持。spring.data.cassandra.username = #服务器的登录用户。#DATA COUCHBASE(CouchbaseDataProperties)spring.data.couchbase.auto-index = false #自动创建视图和索引。spring.data.couchbase.consistency = read-your-own-writes #在生成的查询中默认应用的一致性。spring.data.couchbase.repositories.type = auto #要启用的Couchbase存储库的类型。原文链接:https://blog.csdn.net/sinat_39308893/article/details/127882791
  • [技术干货] Servlet相关
    基于web.xml <?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml">     <display-name>manage</display-name>     <welcome-file-list>         <welcome-file>index.html</welcome-file>         <welcome-file>index.htm</welcome-file>         <welcome-file>index.jsp</welcome-file>         <welcome-file>default.html</welcome-file>         <welcome-file>default.htm</welcome-file>         <welcome-file>default.jsp</welcome-file>     <welcome-file-list>     <servlet>         <description></description>         <display-name>user</display-name>         <servlet-name>user</servlet-name>         <servlet-class>com.sec.servlet.UserServlet</servlet-class>     </servlet>     <servlet-mapping>         <servlet-name>user</servlet-name>         <url-pattern>/user</url-pattern>     </servlet-mapping> </web-app> 在web.xml中,Servlet的配置在Servlet标签中,Servlet标签是由Servlet和Servlet-mapping标签组成,两者通过在Servlet和Servlet-mapping标签中相同的Servlet-name名称实现关联,标签的含义如下:  <servlet>:声明Servlet配置入口 <description>:声明Servlet描述信息 <display-name>:定义Web应用的名字 <servlet-name>:声明Servlet名称以便在后面的映射时使用 <servlet-class>:指定当前servlet对应的类的路径 <servlet-mapping>:注册组件访问设置的路径入口 <servlet-name>:指定上文配置的Servlet的名称 <url-pattern>:指定配置这个组件的访问路径 基于注解方式 在Servlet3.0以上的版本中,开发者无须在web.xml里面配置Servlet,只需要添加@WebServlet注解即可修改Servlet的属性  基于注解方式的注解参数 属性名    类型    描述 name    String    指定Servlet的name属性,等价于servlet-name value    String[]    等价于urlPatterns属性 urlPatterns    String[]    指定一组Servlet的URL匹配模式,等价于url-pattern标签 loadOnStartup    int    指定Servlet的加载顺序,等价于load-on-startup标签 asyncSupported    boolean    声明Servlet是否支持异步操作模式,等价于async-supported标签 initParams    WebInitParam[]    指定一组Servlet初始化参数,等价于init-param标签 description    String    Servlet的描述信息,等价于description标签 displayName    String    Servlet的显示名,通常配合工具使用,等价于display-name标签 Servlet的访问流程 首先在浏览器地址栏中输入user,即访问url-pattern标签中的值;然后浏览器发起请求,服务器通过servlet-mapping标签中找到文件名为user的url-pattern,通过其对应的servlet-name寻找servlet标签中servlet-name相同的servlet;再通过servlet标签中的servlet-name,获取servlet-class参数;最后得到具体的class文件路径,继而执行servlet-class标签中class文件的逻辑。  从上述过程中可以看出,servlet和servlet-mapping中都含有<servlet-name></servlet-name>标签,其主要原因是通过servlet-name作为纽带,将servlet-class和url-pattern构成联系,从而使URL映射到servlet-class所指定的类中执行相应逻辑。 Servlet的接口方法 HTTP有8种请求方法,分别为GET、POST、HEAD、OPTIONS、PUT、DELETE、TRACE以及CONNECT方法。Servlet接口中也对应着相应的请求接口:GET、POST、HEAD、OPTIONS、PUT、DELETE以及TRACE,这些接口对应着请求类型,service()方法会检查HTTP请求类型,然后在适当的时候调用doGet、doPost、doPut、doDelete等方法。  init()接口 在Servlet实例化后,Servlet容器会调用init()方法来初始化该对象,主要是使Servlet对象在处理客户请求前可以完成一些初始化工作。init()方法在第一次创建Servlet时被调用,在后续每次用户请求时不再被调用。  public void init() throws ServletException{     //此处内容为开发者定义的初始化代码 } service()接口 service()方法是执行实际任务的主要方法。Servlet容器调用service()方法来处理来自客户端的请求,并将格式化的响应写回给客户端,每次服务器接收到一个Servlet请求时,服务器都会产生一个新的线程并调用服务。在service()方法被Servlet容器调用之前,必须确保init()方法正确完成。  public void service(ServletRequest request,ServletResponse response) throws ServletException, IOException {     //此处内容为开发者处理客户请求的代码 } doGet()/doPost()等接口 doGet()等方法根据HTTP的不同请求调用不同的方法。如果HTTP得到一个来自URL的GET请求,就会调用doGet()方法;如果得到的是一个POST请求,就会调用doPost()方法 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {     //此处内容为开发者处理GET请求的代码     //以此类推,若是POST请求,则调用public void doPost方法 } destroy()接口 当Servlet容器检测到一个Servlet对象应该从服务中被移除时,会调用该对象的destroy()方法,以便Servlet对象释放它所使用的资源,保存数据到持久存储设备中。destroy()方法与init()方法相同,只会被调用一次。  public void destroy() {     //此处内容为开发者进行终止操作的代码 } getServletConfig()接口 getServletConfig()方法返回Servlet容器调用init()方法时传递给Servlet对象的ServletConfig对象,ServletConfig对象包含Servlet的初始化参数。开发者可以在Servlet的配置文件web.xml中,使用<init-param>标签为Servlet配置一些初始化参数。  <servlet>     <servlet-name>servlet</servlet-name>     <servlet-class>org.test.TestServlet</servlet-class>     <init-param>         <param-name>userName</param-name>         <param-value>panda</param-value>     </init-param>     <init-param>         <param-name>E-mail</param-name>         <param-value>test@test.net</param-value>     </init-param> </servlet> getServletInfo()接口 getServletInfo()方法会返回一个String类型的字符串,包括关于Servlet的信息,如作者、版本及版权等。  Servlet生命周期 Sevlet生命周期指的是Servlet从创建知道销毁的整个过程。在一个生命周期中,Servlet经历了被加载、初始化、接收请求、响应请求以及提供服务的过程。 当用户第一次向服务器发起请求时,服务器会解析用户的请求,此时容器会加载Servlet,然后创建Servlet实例,再调用init()方法初始化Servlet,紧接着调用服务的service()方法处理用户GET、POST或者其他类型的请求。当执行完Servlet中对应class文件的逻辑后,将结果返回给服务器,服务器再响应用户请求。当服务器不再需要Servlet实例或重新载入Servlet时,会调用destroy()方法,释放掉所有在init()方法中申请的资源。 ————————————————                      原文链接:https://blog.csdn.net/weixin_45007073/article/details/120020495 
  • [技术干货] Springboot 整合RabbitMq
    该篇文章内容较多,包括有rabbitMq相关的一些简单理论介绍,provider消息推送实例,consumer消息消费实例,Direct、Topic、Fanout的使用,消息回调、手动确认等。 (但是关于rabbitMq的安装,就不介绍了) 在安装完rabbitMq后,输入http://ip:15672/ ,是可以看到一个简单后台管理界面的。  在这个界面里面我们可以做些什么? 可以手动创建虚拟host,创建用户,分配权限,创建交换机,创建队列等等,还有查看队列消息,消费效率,推送效率等等。 以上这些管理界面的操作在这篇暂时不做扩展描述,我想着重介绍后面实例里会使用到的。 首先先介绍一个简单的一个消息推送到接收的流程,提供一个简单的图: RabbitMq -JCccc 黄色的圈圈就是我们的消息推送服务,将消息推送到 中间方框里面也就是 rabbitMq的服务器,然后经过服务器里面的交换机、队列等各种关系(后面会详细讲)将数据处理入列后,最终右边的蓝色圈圈消费者获取对应监听的消息。 常用的交换机有以下三种,因为消费者是从队列获取信息的,队列是绑定交换机的(一般),所以对应的消息推送/接收模式也会有以下几种:  Direct Exchange  直连型交换机,根据消息携带的路由键将消息投递给对应队列。 大致流程,有一个队列绑定到一个直连交换机上,同时赋予一个路由键 routing key 。 然后当一个消息携带着路由值为X,这个消息通过生产者发送给交换机时,交换机就会根据这个路由值X去寻找绑定值也是X的队列。 Fanout Exchange 扇型交换机,这个交换机没有路由键概念,就算你绑了路由键也是无视的。 这个交换机在接收到消息后,会直接转发到绑定到它上面的所有队列。 Topic Exchange 主题交换机,这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。 简单地介绍下规则:  *  (星号) 用来表示一个单词 (必须出现的) #  (井号) 用来表示任意数量(零个或多个)单词 通配的绑定键是跟队列进行绑定的,举个小例子 队列Q1 绑定键为 *.TT.*          队列Q2绑定键为  TT.# 如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到; 如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到;  主题交换机是非常强大的,为啥这么膨胀? 当一个队列的绑定键为 "#"(井号) 的时候,这个队列将会无视消息的路由键,接收所有的消息。 当 * (星号) 和 # (井号) 这两个特殊字符都未在绑定键中出现的时候,此时主题交换机就拥有的直连交换机的行为。 所以主题交换机也就实现了扇形交换机的功能,和直连交换机的功能。  另外还有 Header Exchange 头交换机 ,Default Exchange 默认交换机,Dead Letter Exchange 死信交换机,这几个该篇暂不做讲述。 好了,一些简单的介绍到这里为止,  接下来我们来一起编码。 本次实例教程需要创建2个springboot项目,一个 rabbitmq-provider (生产者),一个rabbitmq-consumer(消费者)。 首先创建 rabbitmq-provider, pom.xml里用到的jar依赖:          <!--rabbitmq-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-amqp</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency> 然后application.yml:  server:   port: 8021 spring:   #给项目来个名字   application:     name: rabbitmq-provider   #配置rabbitMq 服务器   rabbitmq:     host: 127.0.0.1     port: 5672     username: root     password: root     #虚拟host 可以不设置,使用server默认host     virtual-host: JCcccHost ps:里面的虚拟host配置项不是必须的,我自己在rabbitmq服务上创建了自己的虚拟host,所以我配置了;你们不创建,就不用加这个配置项(默认写 :virtual-host: /)。 一定要注意 要注意 要注意!!!!! 那么怎么建一个单独的host呢? 假如我就是想给某个项目接入,使用一个单独host,顺便使用一个单独的账号,就好像我文中配置的 root 这样。 其实也很简便: virtual-host的创建:  账号user的创建:  然后记得给账号分配权限,指定使用某个virtual host: 其实还可以特定指定交换机使用权等等:  回归正题,继续继续。 接着我们先使用下direct exchange(直连型交换机),创建DirectRabbitConfig.java(对于队列和交换机持久化以及连接使用设置,在注释里有说明,后面的不同交换机的配置就不做同样说明了):  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Configuration public class DirectRabbitConfig {       //队列 起名:TestDirectQueue     @Bean     public Queue TestDirectQueue() {         // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效         // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable         // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。         //   return new Queue("TestDirectQueue",true,true,false);           //一般设置一下队列的持久化就好,其余两个就是默认false         return new Queue("TestDirectQueue",true);     }       //Direct交换机 起名:TestDirectExchange     @Bean     DirectExchange TestDirectExchange() {       //  return new DirectExchange("TestDirectExchange",true,true);         return new DirectExchange("TestDirectExchange",true,false);     }       //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting     @Bean     Binding bindingDirect() {         return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");     }     @Bean     DirectExchange lonelyDirectExchange() {         return new DirectExchange("lonelyDirectExchange");     } } 然后写个简单的接口进行消息推送(根据需求也可以改为定时任务等等,具体看需求),SendMessageController.java:  import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.UUID;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @RestController public class SendMessageController {       @Autowired     RabbitTemplate rabbitTemplate;  //使用RabbitTemplate,这提供了接收/发送等等方法       @GetMapping("/sendDirectMessage")     public String sendDirectMessage() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "test message, hello!";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String,Object> map=new HashMap<>();         map.put("messageId",messageId);         map.put("messageData",messageData);         map.put("createTime",createTime);         //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange         rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);         return "ok";     }     } 把rabbitmq-provider项目运行,调用下接口: 因为我们目前还没弄消费者 rabbitmq-consumer,消息没有被消费的,我们去rabbitMq管理页面看看,是否推送成功: 再看看队列(界面上的各个英文项代表什么意思,可以自己查查哈,对理解还是有帮助的): 很好,消息已经推送到rabbitMq服务器上面了。 接下来,创建rabbitmq-consumer项目: pom.xml里的jar依赖:          <!--rabbitmq-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-amqp</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter</artifactId>         </dependency> 然后是 application.yml:    server:   port: 8022 spring:   #给项目来个名字   application:     name: rabbitmq-consumer   #配置rabbitMq 服务器   rabbitmq:     host: 127.0.0.1     port: 5672     username: root     password: root     #虚拟host 可以不设置,使用server默认host     virtual-host: JCcccHost 然后一样,创建DirectRabbitConfig.java(消费者单纯的使用,其实可以不用添加这个配置,直接建后面的监听就好,使用注解来让监听器监听对应的队列即可。配置上了的话,其实消费者也是生成者的身份,也能推送该消息。):  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Configuration public class DirectRabbitConfig {       //队列 起名:TestDirectQueue     @Bean     public Queue TestDirectQueue() {         return new Queue("TestDirectQueue",true);     }       //Direct交换机 起名:TestDirectExchange     @Bean     DirectExchange TestDirectExchange() {         return new DirectExchange("TestDirectExchange");     }       //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting     @Bean     Binding bindingDirect() {         return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");     } } 然后是创建消息接收监听类,DirectReceiver.java:  @Component @RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue public class DirectReceiver {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());     }   } 然后将rabbitmq-consumer项目运行起来,可以看到把之前推送的那条消息消费下来了: 然后可以再继续调用rabbitmq-provider项目的推送消息接口,可以看到消费者即时消费消息: 那么直连交换机既然是一对一,那如果咱们配置多台监听绑定到同一个直连交互的同一个队列,会怎么样? 可以看到是实现了轮询的方式对消息进行消费,而且不存在重复消费。 接着,我们使用Topic Exchange 主题交换机。 在rabbitmq-provider项目里面创建TopicRabbitConfig.java:  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/   @Configuration public class TopicRabbitConfig {     //绑定键     public final static String man = "topic.man";     public final static String woman = "topic.woman";       @Bean     public Queue firstQueue() {         return new Queue(TopicRabbitConfig.man);     }       @Bean     public Queue secondQueue() {         return new Queue(TopicRabbitConfig.woman);     }       @Bean     TopicExchange exchange() {         return new TopicExchange("topicExchange");     }         //将firstQueue和topicExchange绑定,而且绑定的键值为topic.man     //这样只要是消息携带的路由键是topic.man,才会分发到该队列     @Bean     Binding bindingExchangeMessage() {         return BindingBuilder.bind(firstQueue()).to(exchange()).with(man);     }       //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#     // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列     @Bean     Binding bindingExchangeMessage2() {         return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");     }   } 然后添加多2个接口,用于推送消息到主题交换机:      @GetMapping("/sendTopicMessage1")     public String sendTopicMessage1() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: M A N ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> manMap = new HashMap<>();         manMap.put("messageId", messageId);         manMap.put("messageData", messageData);         manMap.put("createTime", createTime);         rabbitTemplate.convertAndSend("topicExchange", "topic.man", manMap);         return "ok";     }       @GetMapping("/sendTopicMessage2")     public String sendTopicMessage2() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: woman is all ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> womanMap = new HashMap<>();         womanMap.put("messageId", messageId);         womanMap.put("messageData", messageData);         womanMap.put("createTime", createTime);         rabbitTemplate.convertAndSend("topicExchange", "topic.woman", womanMap);         return "ok";     } } 生产者这边已经完事,先不急着运行,在rabbitmq-consumer项目上,创建TopicManReceiver.java: import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Component @RabbitListener(queues = "topic.man") public class TopicManReceiver {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("TopicManReceiver消费者收到消息  : " + testMessage.toString());     } } 再创建一个TopicTotalReceiver.java:  package com.elegant.rabbitmqconsumer.receiver;   import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/   @Component @RabbitListener(queues = "topic.woman") public class TopicTotalReceiver {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("TopicTotalReceiver消费者收到消息  : " + testMessage.toString());     } } 同样,加主题交换机的相关配置,TopicRabbitConfig.java(消费者一定要加这个配置吗? 不需要的其实,理由在前面已经说过了。):  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/   @Configuration public class TopicRabbitConfig {     //绑定键     public final static String man = "topic.man";     public final static String woman = "topic.woman";       @Bean     public Queue firstQueue() {         return new Queue(TopicRabbitConfig.man);     }       @Bean     public Queue secondQueue() {         return new Queue(TopicRabbitConfig.woman);     }       @Bean     TopicExchange exchange() {         return new TopicExchange("topicExchange");     }     //将firstQueue和topicExchange绑定,而且绑定的键值为topic.man     //这样只要是消息携带的路由键是topic.man,才会分发到该队列     @Bean     Binding bindingExchangeMessage() {         return BindingBuilder.bind(firstQueue()).to(exchange()).with(man);     }       //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#     // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列     @Bean     Binding bindingExchangeMessage2() {         return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");     }   }  然后把rabbitmq-provider,rabbitmq-consumer两个项目都跑起来,先调用/sendTopicMessage1  接口: 然后看消费者rabbitmq-consumer的控制台输出情况: TopicManReceiver监听队列1,绑定键为:topic.man TopicTotalReceiver监听队列2,绑定键为:topic.# 而当前推送的消息,携带的路由键为:topic.man   所以可以看到两个监听消费者receiver都成功消费到了消息,因为这两个recevier监听的队列的绑定键都能与这条消息携带的路由键匹配上。 接下来调用接口/sendTopicMessage2: 然后看消费者rabbitmq-consumer的控制台输出情况: TopicManReceiver监听队列1,绑定键为:topic.man TopicTotalReceiver监听队列2,绑定键为:topic.# 而当前推送的消息,携带的路由键为:topic.woman 所以可以看到两个监听消费者只有TopicTotalReceiver成功消费到了消息。 接下来是使用Fanout Exchang 扇型交换机。 同样地,先在rabbitmq-provider项目上创建FanoutRabbitConfig.java: import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/   @Configuration public class FanoutRabbitConfig {       /**      *  创建三个队列 :fanout.A   fanout.B  fanout.C      *  将三个队列都绑定在交换机 fanoutExchange 上      *  因为是扇型交换机, 路由键无需配置,配置也不起作用      */         @Bean     public Queue queueA() {         return new Queue("fanout.A");     }       @Bean     public Queue queueB() {         return new Queue("fanout.B");     }       @Bean     public Queue queueC() {         return new Queue("fanout.C");     }       @Bean     FanoutExchange fanoutExchange() {         return new FanoutExchange("fanoutExchange");     }       @Bean     Binding bindingExchangeA() {         return BindingBuilder.bind(queueA()).to(fanoutExchange());     }       @Bean     Binding bindingExchangeB() {         return BindingBuilder.bind(queueB()).to(fanoutExchange());     }       @Bean     Binding bindingExchangeC() {         return BindingBuilder.bind(queueC()).to(fanoutExchange());     } } 然后是写一个接口用于推送消息,        @GetMapping("/sendFanoutMessage")     public String sendFanoutMessage() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: testFanoutMessage ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> map = new HashMap<>();         map.put("messageId", messageId);         map.put("messageData", messageData);         map.put("createTime", createTime);         rabbitTemplate.convertAndSend("fanoutExchange", null, map);         return "ok";     } 接着在rabbitmq-consumer项目里加上消息消费类,  FanoutReceiverA.java:  import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map; /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Component @RabbitListener(queues = "fanout.A") public class FanoutReceiverA {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverA消费者收到消息  : " +testMessage.toString());     }   } FanoutReceiverB.java:  import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map; /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Component @RabbitListener(queues = "fanout.B") public class FanoutReceiverB {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverB消费者收到消息  : " +testMessage.toString());     }   } FanoutReceiverC.java:  import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Component @RabbitListener(queues = "fanout.C") public class FanoutReceiverC {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverC消费者收到消息  : " +testMessage.toString());     }   } 然后加上扇型交换机的配置类,FanoutRabbitConfig.java(消费者真的要加这个配置吗? 不需要的其实,理由在前面已经说过了):  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Configuration public class FanoutRabbitConfig {       /**      *  创建三个队列 :fanout.A   fanout.B  fanout.C      *  将三个队列都绑定在交换机 fanoutExchange 上      *  因为是扇型交换机, 路由键无需配置,配置也不起作用      */         @Bean     public Queue queueA() {         return new Queue("fanout.A");     }       @Bean     public Queue queueB() {         return new Queue("fanout.B");     }       @Bean     public Queue queueC() {         return new Queue("fanout.C");     }       @Bean     FanoutExchange fanoutExchange() {         return new FanoutExchange("fanoutExchange");     }       @Bean     Binding bindingExchangeA() {         return BindingBuilder.bind(queueA()).to(fanoutExchange());     }       @Bean     Binding bindingExchangeB() {         return BindingBuilder.bind(queueB()).to(fanoutExchange());     }       @Bean     Binding bindingExchangeC() {         return BindingBuilder.bind(queueC()).to(fanoutExchange());     } } 最后将rabbitmq-provider和rabbitmq-consumer项目都跑起来,调用下接口/sendFanoutMessage : 然后看看rabbitmq-consumer项目的控制台情况: 可以看到只要发送到 fanoutExchange 这个扇型交换机的消息, 三个队列都绑定这个交换机,所以三个消息接收类都监听到了这条消息。 到了这里其实三个常用的交换机的使用我们已经完毕了,那么接下来我们继续讲讲消息的回调,其实就是消息确认(生产者推送消息成功,消费者接收消息成功)。 在rabbitmq-provider项目的application.yml文件上,加上消息确认的配置项后:  ps: 本篇文章使用springboot版本为 2.1.7.RELEASE ;  如果你们在配置确认回调,测试发现无法触发回调函数,那么存在原因也许是因为版本导致的配置项不起效, 可以把publisher-confirms: true 替换为  publisher-confirm-type: correlated  server:   port: 8021 spring:   #给项目来个名字   application:     name: rabbitmq-provider   #配置rabbitMq 服务器   rabbitmq:     host: 127.0.0.1     port: 5672     username: root     password: root     #虚拟host 可以不设置,使用server默认host     virtual-host: JCcccHost       #确认消息已发送到交换机(Exchange)     #publisher-confirms: true     publisher-confirm-type: correlated       #确认消息已发送到队列(Queue)     publisher-returns: true 然后是配置相关的消息确认回调函数,RabbitConfig.java:  import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;     /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Configuration public class RabbitConfig {       @Bean     public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){         RabbitTemplate rabbitTemplate = new RabbitTemplate();         rabbitTemplate.setConnectionFactory(connectionFactory);         //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数         rabbitTemplate.setMandatory(true);           rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {             @Override             public void confirm(CorrelationData correlationData, boolean ack, String cause) {                 System.out.println("ConfirmCallback:     "+"相关数据:"+correlationData);                 System.out.println("ConfirmCallback:     "+"确认情况:"+ack);                 System.out.println("ConfirmCallback:     "+"原因:"+cause);             }         });           rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {             @Override             public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {                 System.out.println("ReturnCallback:     "+"消息:"+message);                 System.out.println("ReturnCallback:     "+"回应码:"+replyCode);                 System.out.println("ReturnCallback:     "+"回应信息:"+replyText);                 System.out.println("ReturnCallback:     "+"交换机:"+exchange);                 System.out.println("ReturnCallback:     "+"路由键:"+routingKey);             }         });           return rabbitTemplate;     }   } 到这里,生产者推送消息的消息确认调用回调函数已经完毕。 可以看到上面写了两个回调函数,一个叫 ConfirmCallback ,一个叫 RetrunCallback; 那么以上这两种回调函数都是在什么情况会触发呢?  先从总体的情况分析,推送消息存在四种情况:  ①消息推送到server,但是在server里找不到交换机 ②消息推送到server,找到交换机了,但是没找到队列 ③消息推送到sever,交换机和队列啥都没找到 ④消息推送成功  那么我先写几个接口来分别测试和认证下以上4种情况,消息确认触发回调函数的情况:  ①消息推送到server,但是在server里找不到交换机 写个测试接口,把消息推送到名为‘non-existent-exchange’的交换机上(这个交换机是没有创建没有配置的):      @GetMapping("/TestMessageAck")     public String TestMessageAck() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: non-existent-exchange test message ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> map = new HashMap<>();         map.put("messageId", messageId);         map.put("messageData", messageData);         map.put("createTime", createTime);         rabbitTemplate.convertAndSend("non-existent-exchange", "TestDirectRouting", map);         return "ok";     } 调用接口,查看rabbitmq-provuder项目的控制台输出情况(原因里面有说,没有找到交换机'non-existent-exchange'):  2019-09-04 09:37:45.197 ERROR 8172 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'non-existent-exchange' in vhost 'JCcccHost', class-id=60, method-id=40) ConfirmCallback:     相关数据:null ConfirmCallback:     确认情况:false ConfirmCallback:     原因:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'non-existent-exchange' in vhost 'JCcccHost', class-id=60, method-id=40)     结论: ①这种情况触发的是 ConfirmCallback 回调函数。   ②消息推送到server,找到交换机了,但是没找到队列   这种情况就是需要新增一个交换机,但是不给这个交换机绑定队列,我来简单地在DirectRabitConfig里面新增一个直连交换机,名叫‘lonelyDirectExchange’,但没给它做任何绑定配置操作:      @Bean     DirectExchange lonelyDirectExchange() {         return new DirectExchange("lonelyDirectExchange");     } 然后写个测试接口,把消息推送到名为‘lonelyDirectExchange’的交换机上(这个交换机是没有任何队列配置的):      @GetMapping("/TestMessageAck2")     public String TestMessageAck2() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: lonelyDirectExchange test message ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> map = new HashMap<>();         map.put("messageId", messageId);         map.put("messageData", messageData);         map.put("createTime", createTime);         rabbitTemplate.convertAndSend("lonelyDirectExchange", "TestDirectRouting", map);         return "ok";     } 调用接口,查看rabbitmq-provuder项目的控制台输出情况:  ReturnCallback:     消息:(Body:'{createTime=2019-09-04 09:48:01, messageId=563077d9-0a77-4c27-8794-ecfb183eac80, messageData=message: lonelyDirectExchange test message }' MessageProperties [headers={}, contentType=application/x-java-serialized-object, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0]) ReturnCallback:     回应码:312 ReturnCallback:     回应信息:NO_ROUTE ReturnCallback:     交换机:lonelyDirectExchange ReturnCallback:     路由键:TestDirectRouting ConfirmCallback:     相关数据:null ConfirmCallback:     确认情况:true ConfirmCallback:     原因:null 可以看到这种情况,两个函数都被调用了; 这种情况下,消息是推送成功到服务器了的,所以ConfirmCallback对消息确认情况是true; 而在RetrunCallback回调函数的打印参数里面可以看到,消息是推送到了交换机成功了,但是在路由分发给队列的时候,找不到队列,所以报了错误 NO_ROUTE 。   结论:②这种情况触发的是 ConfirmCallback和RetrunCallback两个回调函数。  ③消息推送到sever,交换机和队列啥都没找到  这种情况其实一看就觉得跟①很像,没错 ,③和①情况回调是一致的,所以不做结果说明了。   结论: ③这种情况触发的是 ConfirmCallback 回调函数。   ④消息推送成功 那么测试下,按照正常调用之前消息推送的接口就行,就调用下 /sendFanoutMessage接口,可以看到控制台输出:  ConfirmCallback:     相关数据:null ConfirmCallback:     确认情况:true ConfirmCallback:     原因:null 结论: ④这种情况触发的是 ConfirmCallback 回调函数。 以上是生产者推送消息的消息确认 回调函数的使用介绍(可以在回调函数根据需求做对应的扩展或者业务数据处理)。  接下来我们继续, 消费者接收到消息的消息确认机制。  和生产者的消息确认机制不同,因为消息接收本来就是在监听消息,符合条件的消息就会消费下来。 所以,消息接收的确认机制主要存在三种模式:  ①自动确认, 这也是默认的消息确认情况。  AcknowledgeMode.NONE RabbitMQ成功将消息发出(即将消息成功写入TCP Socket)中立即认为本次投递已经被正确处理,不管消费者端是否成功处理本次投递。 所以这种情况如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。 一般这种情况我们都是使用try catch捕捉异常后,打印日志用于追踪数据,这样找出对应数据再做后续处理。  ② 根据情况确认, 这个不做介绍 ③ 手动确认 , 这个比较关键,也是我们配置接收消息确认机制时,多数选择的模式。 消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功。 basic.ack用于肯定确认  basic.nack用于否定确认(注意:这是AMQP 0-9-1的RabbitMQ扩展)  basic.reject用于否定确认,但与basic.nack相比有一个限制:一次只能拒绝单条消息   消费者端以上的3个方法都表示消息已经被正确投递,但是basic.ack表示消息已经被正确处理。 而basic.nack,basic.reject表示没有被正确处理: 着重讲下reject,因为有时候一些场景是需要重新入列的。 channel.basicReject(deliveryTag, true);  拒绝消费当前消息,如果第二参数传入true,就是将数据重新丢回队列里,那么下次还会消费这消息。设置false,就是告诉服务器,我已经知道这条消息数据了,因为一些原因拒绝它,而且服务器也把这个消息丢掉就行。 下次不想再消费这条消息了。 使用拒绝后重新入列这个确认模式要谨慎,因为一般都是出现异常的时候,catch异常再拒绝入列,选择是否重入列。 但是如果使用不当会导致一些每次都被你重入列的消息一直消费-入列-消费-入列这样循环,会导致消息积压。 顺便也简单讲讲 nack,这个也是相当于设置不消费某条消息。 channel.basicNack(deliveryTag, false, true); 第一个参数依然是当前消息到的数据的唯一id; 第二个参数是指是否针对多条消息;如果是true,也就是说一次性针对当前通道的消息的tagID小于当前这条消息的,都拒绝确认。 第三个参数是指是否重新入列,也就是指不确认的消息是否重新丢回到队列里面去。 同样使用不确认后重新入列这个确认模式要谨慎,因为这里也可能因为考虑不周出现消息一直被重新丢回去的情况,导致积压。 看了上面这么多介绍,接下来我们一起配置下,看看一般的消息接收 手动确认是怎么样的。 ​​​在消费者项目里, 新建MessageListenerConfig.java上添加代码相关的配置代码: import com.elegant.rabbitmqconsumer.receiver.MyAckReceiver; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/4  * @Description :  **/ @Configuration public class MessageListenerConfig {       @Autowired     private CachingConnectionFactory connectionFactory;     @Autowired     private MyAckReceiver myAckReceiver;//消息接收处理类       @Bean     public SimpleMessageListenerContainer simpleMessageListenerContainer() {         SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);         container.setConcurrentConsumers(1);         container.setMaxConcurrentConsumers(1);         container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息         //设置一个队列         container.setQueueNames("TestDirectQueue");         //如果同时设置多个如下: 前提是队列都是必须已经创建存在的         //  container.setQueueNames("TestDirectQueue","TestDirectQueue2","TestDirectQueue3");         //另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues         //container.setQueues(new Queue("TestDirectQueue",true));         //container.addQueues(new Queue("TestDirectQueue2",true));         //container.addQueues(new Queue("TestDirectQueue3",true));         container.setMessageListener(myAckReceiver);           return container;     } } 对应的手动确认消息监听类,MyAckReceiver.java(手动确认模式需要实现 ChannelAwareMessageListener): //之前的相关监听器可以先注释掉,以免造成多个同类型监听器都监听同一个队列。  import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.stereotype.Component;   import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.util.Map;   @Component   public class MyAckReceiver implements ChannelAwareMessageListener {       @Override     public void onMessage(Message message, Channel channel) throws Exception {         long deliveryTag = message.getMessageProperties().getDeliveryTag();         try {             byte[] body = message.getBody();             ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(body));             Map<String,String> msgMap = (Map<String,String>) ois.readObject();             String messageId = msgMap.get("messageId");             String messageData = msgMap.get("messageData");             String createTime = msgMap.get("createTime");             ois.close();             System.out.println("  MyAckReceiver  messageId:"+messageId+"  messageData:"+messageData+"  createTime:"+createTime);             System.out.println("消费的主题消息来自:"+message.getMessageProperties().getConsumerQueue());             channel.basicAck(deliveryTag, true); //第二个参数,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息 //            channel.basicReject(deliveryTag, true);//第二个参数,true会重新放回队列,所以需要自己根据业务逻辑判断什么时候使用拒绝         } catch (Exception e) {             channel.basicReject(deliveryTag, false);             e.printStackTrace();         }     }      } 这时,先调用接口/sendDirectMessage, 给直连交换机TestDirectExchange 的队列TestDirectQueue 推送一条消息,可以看到监听器正常消费了下来: 到这里,我们其实已经掌握了怎么去使用消息消费的手动确认了。 但是这个场景往往不够! 因为很多伙伴之前给我评论反应,他们需要这个消费者项目里面,监听的好几个队列都想变成手动确认模式,而且处理的消息业务逻辑不一样。 没有问题,接下来看代码 场景: 除了直连交换机的队列TestDirectQueue需要变成手动确认以外,我们还需要将一个其他的队列 或者多个队列也变成手动确认,而且不同队列实现不同的业务处理。 那么我们需要做的第一步,往SimpleMessageListenerContainer里添加多个队列: 然后我们的手动确认消息监听类,MyAckReceiver.java 就可以同时将上面设置到的队列的消息都消费下来。 但是我们需要做不用的业务逻辑处理,那么只需要  根据消息来自的队列名进行区分处理即可,如:  import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.util.Map;     @Component public class MyAckReceiver implements ChannelAwareMessageListener {       @Override     public void onMessage(Message message, Channel channel) throws Exception {         long deliveryTag = message.getMessageProperties().getDeliveryTag();         try {             byte[] body = message.getBody();             ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(body));             Map<String,String> msgMap = (Map<String,String>) ois.readObject();             String messageId = msgMap.get("messageId");             String messageData = msgMap.get("messageData");             String createTime = msgMap.get("createTime");             ois.close();               if ("TestDirectQueue".equals(message.getMessageProperties().getConsumerQueue())){                 System.out.println("消费的消息来自的队列名为:"+message.getMessageProperties().getConsumerQueue());                 System.out.println("消息成功消费到  messageId:"+messageId+"  messageData:"+messageData+"  createTime:"+createTime);                 System.out.println("执行TestDirectQueue中的消息的业务处理流程......");                              }               if ("fanout.A".equals(message.getMessageProperties().getConsumerQueue())){                 System.out.println("消费的消息来自的队列名为:"+message.getMessageProperties().getConsumerQueue());                 System.out.println("消息成功消费到  messageId:"+messageId+"  messageData:"+messageData+"  createTime:"+createTime);                 System.out.println("执行fanout.A中的消息的业务处理流程......");               }                          channel.basicAck(deliveryTag, true); //            channel.basicReject(deliveryTag, true);//为true会重新放回队列         } catch (Exception e) {             channel.basicReject(deliveryTag, false);             e.printStackTrace();         }     } } ok,这时候我们来分别往不同队列推送消息,看看效果: 调用接口/sendDirectMessage  和 /sendFanoutMessage , 如果你还想新增其他的监听队列,也就是按照这种方式新增配置即可(或者完全可以分开多个消费者项目去监听处理)。  好,这篇Springboot整合rabbitMq教程就暂且到此。 ————————————————                       原文链接:https://blog.csdn.net/qq_35387940/article/details/100514134 
  • [技术干货] Spring boot之@EnableAutoConfiguration
    一、@EnableAutoConfiguration的作用 简单点说就是Spring Boot根据依赖中的jar包,自动选择实例化某些配置,配置类必须有@Configuration注解。 说白了,还是实例化对象,只是实例化的是依赖包中的类。 另外,我们也可以按照自动装配的规范自己定义装配的类。 二、@EnableAutoConfiguration和 @Configuration 的区别 @Configuration:表示作用的类是个配置类。我们平时也会写配置类,比如我们系统中的DataSourceConfig类,但是由于这个类是在Starter对应的子目录下,会自动加载,所以@EnableAutoConfiguration就作用不到了。 @EnableAutoConfiguration:是一个加载Starter目录包之外的需要Spring自动生成bean对象(是否需要的依据是"META-INF/spring.factories"中org.springframework.boot.autoconfigure.EnableAutoConfiguration后面是有能找到那个bean)的带有@Configuration注解的类。一般就是对各种引入的spring-boot-starter依赖包指定的(spring.factories)类进行实例化。 整体流程: 1、我们启动类中会加上@SpringBootApplication 2、@SpringBootApplication包含了@EnableAutoConfiguration,告知应用启动的时候需要扫描依赖包中需要实例化的类 3、Springboot启动的时候会去扫描META-INF/spring.factories,查看具体是哪些类需要实例化 4、扫描哪些需要实例化的类,看下是否有@Configuration注解,如果有,则实例化 5、实例化的时候可能需要一些配置属性,一般这些类都会加上@EnableConfigurationProperties(RocketMQProperties.class) 6、RocketMQProperties会将属性映射为bean类 三、@EnableAutoConfiguration案例 1、Kafka自动装配 我们会发现,当我们在application.properties中配置spring.kafka.xxx,就可以在程序中直接使用了,说明Kafka已经被实例化了,问题来了,是什么时候实例化的?其实就是@EnableAutoConfiguration注解自动实例化的。实现类:KafkaAutoConfiguration 2、JMX自动装配 当我们引入Spring Boot Actuator相关包之后就能自动采集到Spring boot应用的相关运行指标了,怎么做到的? 其实这些指标都是通过JMX标准采集的,而Spring boot的自动装配也包含了JMX,即SpringApplicationAdminJmxAutoConfiguration。 四、spring-boot-starter与@EnableAutoConfiguration的关系 spring-boot-starter:自动导入组件对应的maven包,目的主要是简单开发人员手动去配置maven各种依赖包,不仅麻烦,碰到版本匹配问题还很容易出错。 @EnableAutoConfiguration:需要依赖已经加载好的包进行bean实例化。但不一定需要依赖spring-boot-starter,自动手动把包加载也可以 五、关于实例化 在Jfinal中我们需要通过Plugin的规范去生成对象到上下文中。 而在Spring boot中,我们可以通过@Configuration以及@Service,@Component等注解生成对象到IOC容器中。  未必所有实例化都是通过@EnableAutoConfiguration,@Configuration以及@Service,@Component等注解,也可以通过其他方式(说白了怎么加载类的问题,因为实例化最终就是找到类,然后通过反射生成对象而已)。  其他实例化方式其实可以从META-INF/spring.factories中看出来,里面不仅有org.springframework.boot.autoconfigure.EnableAutoConfiguration,还有org.springframework.context.ApplicationContextInitializer(初始化的时候实例化),org.springframework.context.ApplicationListener(初始化监听的时候实例化),org.springframework.boot.diagnostics.FailureAnalyzer(应用启动失败分析类)等等。  但是对于这些实例化方式有个共同点,都需要在META-INF/spring.factories中配置。  六、默认自动装配的类 # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\ org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\ org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration ———————————————— 原文链接:https://blog.csdn.net/w2009211777/article/details/122609547 
  • [技术干货] SpringBoot集成RabbitMQ
    spring boot版本  <parent>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-parent</artifactId>     <version>2.2.2.RELEASE</version>     <relativePath/> <!-- lookup parent from repository --> </parent> 首先创建一个生产者rabbitmq-provider,yml配置如下:  server:   port: 8021   servlet:     application-display-name: rabbitmq-provider     context-path: /rabbitmq-provider     session:       timeout: 30m spring:   application:     name: ${server.servlet.application-display-name}   rabbitmq:     sys-name: ${server.servlet.application-display-name}     host: 127.0.0.1     port: 5672     username: guest     password: guest     virtual-host: /     publisher-confirms: true #支持发布确认     publisher-returns: true  #支持发布返回     listener:       simple:         acknowledge-mode: manual #采用手动应答         retry:           enabled: true #是否支持重试           initial-interval: 1000 # 第一次和第二次 尝试发布或交付(此处是消费者交付,template.retry中是消费者发布)的 间隔时间           max-interval: 10000   # 两次重试的最大间隔           max-attempts: 1  # 尝试发布或交付的 最大次数(重试次数)           multiplier: 1.0  # 每次重试都比上一次重试间隔时长大x倍           stateless: true  # 重试是无状态还是有状态的 pom.xml:  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-web</artifactId> </dependency> java版本:  ```java <properties>     <java.version>1.8</java.version> </properties> 创建一个消费者 rabbitmq-consumer: yml文件  server:   port: 8022   servlet:     application-display-name: rabbitmq-consumer     context-path: /rabbitmq-consumer     session:       timeout: 30m spring:   application:     name: ${server.servlet.application-display-name}   rabbitmq:     sys-name: ${server.servlet.application-display-name}     host: 127.0.0.1     port: 5672     username: guest     password: guest     virtual-host: /     publisher-confirms: true #支持发布确认     publisher-returns: true  #支持发布返回     listener:       simple:         acknowledge-mode: manual #采用手动应答         retry:           enabled: true #是否支持重试           initial-interval: 1000 # 第一次和第二次 尝试发布或交付(此处是消费者交付,template.retry中是消费者发布)的 间隔时间           max-interval: 10000   # 两次重试的最大间隔           max-attempts: 1  # 尝试发布或交付的 最大次数(重试次数)           multiplier: 1.0  # 每次重试都比上一次重试间隔时长大x倍           stateless: true  # 重试是无状态还是有状态的 pom文件与jdk与生产者相同。  Direct Exchange例子: 创建配置文件:  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          DirectRabbitConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author:   * @Create Date:   2019/12/19  */ @Configuration public class DirectRabbitConfig {     //队列 起名:TestDirectQueue     @Bean     public Queue TestDirectQueue() {         return new Queue("TestDirectQueue",true);  //true 是否持久     }      //Direct交换机 起名:TestDirectExchange     @Bean     DirectExchange TestDirectExchange() {         return new DirectExchange("TestDirectExchange");     }      //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting     @Bean     Binding bindingDirect() {         return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");     }  } 创建发送消息的Rest  import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;  import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.UUID;  /**  * 方法名:          SendMessageController  * 方法功能描述:  *  * @param:  * @return:  * @Author: 陈超  * @Create Date:   2019/12/19  */ @RestController public class SendMessageController {      @Autowired     RabbitTemplate rabbitTemplate;  //使用RabbitTemplate,这提供了接收/发送等等方法      @GetMapping("/sendDirectMessage")     public String sendDirectMessage() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "test message, hello!";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> map = new HashMap<>();         map.put("messageId", messageId);         map.put("messageData", messageData);         map.put("createTime", createTime);         //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange         rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);         return "ok";     } } 启动项目运行,返回OK说明发送消息成功 打开http://localhost:15672/#/queues 队列TestDirectQueue已经加入到消息队列中。 现在使用消费者来消费消息,同样的创建DirectRabbitConfig类,与生产者的相同。 创建接收消息的类:  import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component;  import java.util.Map;  /**  * 方法名:          DirectReceiver  * 方法功能描述:  *  * @param:  * @return:  * @Author: cc  * @Create Date:   2019/12/20  */  @Component @RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue public class DirectReceiver {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());     }  }  现在已经将生产者与消费者都准备好,启动消费者rabbitmq-consumer,RabbitListener监听器会监听名称为“TestDirectQueue”的队列,将消息接收打印。 同时,生产者发送消息,消费者也会显示发送的消息。  Topic Exchange举例: 首先回顾,topic是按照通配符的方式来进行匹配的。 在生产者中加入配置文件TopicRabbitConfig:  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          TopicRabbitConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author: 陈超  * @Create Date:   2019/12/20  */ @Configuration public class TopicRabbitConfig {      //绑定键     public static final String red = "topic.red";     public static final String green = "topic.green";      @Bean     public Queue firstQueue() {         return new Queue(TopicRabbitConfig.red);     }      @Bean     public Queue secondQueue() {         return new Queue(TopicRabbitConfig.green);     }      @Bean     TopicExchange exchange() {         return new TopicExchange("topicExchange");     }      //将firstQueue和topicExchange绑定,而且绑定的键值为topic.red     //这样只要是消息携带的路由键是topic.red,才会分发到该队列     @Bean     Binding bindingExchangeMessage() {         return BindingBuilder.bind(firstQueue()).to(exchange()).with(red);     }      //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#     // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列     @Bean     Binding bindingExchangeMessage2() {         return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");     }  }  也就是secondQueue绑定了"topic.green",而secondQueue绑定了bindingExchangeMessage2。那么green就可以按照topic的规则进行适配。 创建发送机:  @GetMapping("/sendTopicMessageRed") public String sendTopicMessageRed() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "红灯消息 RED";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> redMap = new HashMap<>();     redMap.put("messageId", messageId);     redMap.put("messageData", messageData);     redMap.put("createTime", createTime);     rabbitTemplate.convertAndSend("topicExchange", "topic.red", redMap);     return "ok"; }  @GetMapping("/sendTopicMessageGreen") public String sendTopicMessageGreen() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "绿灯与红灯消息 ";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> greenMap = new HashMap<>();     greenMap.put("messageId", messageId);     greenMap.put("messageData", messageData);     greenMap.put("createTime", createTime);     rabbitTemplate.convertAndSend("topicExchange", "topic.green", greenMap);     return "ok"; } 接下来在消费者中创建同样的配置类TopicRabbitConfig。创建接收机 @Component @RabbitListener(queues = "topic.red") public class TopicRedReceiver {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("TopicRedReceiver  : " + testMessage.toString());     } }  @Component @RabbitListener(queues = "topic.green") public class TopicGreenReceiver {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("TopicGreenReceiver  : " + testMessage.toString());     } } 接下来发送消息: 生产者发送红灯消息,调用 消费者接收到 可以看出绿灯的接收者也接受到红灯消息,这是因为我们在TopicRabbitConfig 配置中的 //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#     // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列     @Bean     Binding bindingExchangeMessage2() {         return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");     } 发挥了作用。 现在发送绿灯消息  只接收到了绿灯消息,因为红灯的接收机没有进行适配。 Fanout Exchang 适配器 创建配置文件:  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          FanoutRabbitConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author:   * @Create Date:   2019/12/20  */  @Configuration public class FanoutRabbitConfig {      /**      *  创建三个队列 :fanout.A   fanout.B  fanout.C      *  将三个队列都绑定在交换机 fanoutExchange 上      *  因为是扇型交换机, 路由键无需配置,配置也不起作用      */     @Bean     public Queue queueA() {         return new Queue("fanout.A");     }      @Bean     public Queue queueB() {         return new Queue("fanout.B");     }      @Bean     public Queue queueC() {         return new Queue("fanout.C");     }      @Bean     FanoutExchange fanoutExchange() {         return new FanoutExchange("fanoutExchange");     }      @Bean     Binding bindingExchangeA() {         return BindingBuilder.bind(queueA()).to(fanoutExchange());     }      @Bean     Binding bindingExchangeB() {         return BindingBuilder.bind(queueB()).to(fanoutExchange());     }      @Bean     Binding bindingExchangeC() {         return BindingBuilder.bind(queueC()).to(fanoutExchange());     } } 在生产者中创建发送方法: @GetMapping("/sendFanoutMessage") public String sendFanoutMessage() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "message: testFanoutMessage ";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> map = new HashMap<>();     map.put("messageId", messageId);     map.put("messageData", messageData);     map.put("createTime", createTime);     rabbitTemplate.convertAndSend("fanoutExchange", null, map);     return "ok"; } 交换机指向了创建的交换机fanoutExchange。 接下来是消费者 同样创建配置文件FanoutRabbitConfig ,与生产者的相同 创建接受类  @Component @RabbitListener(queues = "fanout.A") public class FanoutReceiverA {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverA消费者收到消息  : " +testMessage.toString());     } } @Component @RabbitListener(queues = "fanout.B") public class FanoutReceiverB {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverB消费者收到消息  : " +testMessage.toString());     }  } @Component @RabbitListener(queues = "fanout.C") public class FanoutReceiverC {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverC消费者收到消息  : " +testMessage.toString());     }  } 创建好之后调用生产者中的发送消息的方法sendFanoutMessage。 三个方法都接受到,说明扇形交换器成功 RabbirMQ消息的回调与消息的确认 在application.yml添加配置项  rabbitmq:   sys-name: ${server.servlet.application-display-name}   host: 127.0.0.1   port: 5672   username: guest   password: guest   virtual-host: /   publisher-confirm-type: correlated #确认消息已发送到交换机   publisher-returns: true  #确认消息已发送到队列(Queue) 在生产者中添加RabbitConfig配置  import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          RabbitConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author:   * @Create Date:   2019/12/23  */  @Configuration public class RabbitConfig {      @Bean     public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {         RabbitTemplate rabbitTemplate = new RabbitTemplate();         rabbitTemplate.setConnectionFactory(connectionFactory);         //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数         rabbitTemplate.setMandatory(Boolean.TRUE);          rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {             @Override             public void confirm(CorrelationData correlationData, boolean b, String s) {                 System.out.println("ConfirmCallback:     " + "相关数据:" + correlationData);                 System.out.println("ConfirmCallback:     " + "确认情况:" + b);                 System.out.println("ConfirmCallback:     " + "原因:" + s);             }         });          rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {             @Override             public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {                 System.out.println("ReturnCallback:     " + "消息:" + message);                 System.out.println("ReturnCallback:     " + "回应码:" + replyCode);                 System.out.println("ReturnCallback:     " + "回应信息:" + replyText);                 System.out.println("ReturnCallback:     " + "交换机:" + exchange);                 System.out.println("ReturnCallback:     " + "路由键:" + routingKey);             }         });         return rabbitTemplate;     } } 到这里,生产者推送消息的消息确认调用回调函数已经完毕。 可以看到上面写了两个回调函数,一个叫 ConfirmCallback ,一个叫 RetrunCallback; 那么以上这两种回调函数都是在什么情况会触发呢? 先从总体的情况分析,推送消息存在四种情况: ①消息推送到server,但是在server里找不到交换机 ②消息推送到server,找到交换机了,但是没找到队列 ③消息推送到sever,交换机和队列啥都没找到 ④消息推送成功  测试①消息推送到server,但是在server里找不到交换机这种情况: 写个测试接口,把消息推送到名为‘non-existent-exchange’的交换机上(这个交换机是没有创建没有配置的)  @GetMapping("/TestMessageAck") public String TestMessageAck() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "message: non-existent-exchange test message ";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> map = new HashMap<>();     map.put("messageId", messageId);     map.put("messageData", messageData);     map.put("createTime", createTime);     rabbitTemplate.convertAndSend("non-existent-exchange", "TestDirectRouting", map);     return "ok"; }  调用接口: 返回消息 报错未找到该交换机,ConfirmCallback发挥作用。  测试②消息推送到server,找到交换机了,但是没找到队列 这种情况 这种情况就是需要新增一个交换机,但是不给这个交换机绑定队列,我来简单地在DirectRabitConfig里面新增一个直连交换机,名叫‘lonelyDirectExchange’,但没给它做任何绑定配置操作:  @Bean DirectExchange lonelyDirectExchange() {     return new DirectExchange("lonelyDirectExchange"); } 然后写个测试接口,把消息推送到名为‘lonelyDirectExchange’的交换机上(这个交换机是没有任何队列配置的):  @GetMapping("/TestMessageAckLoneLy") public String TestMessageAckLoneLy() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "message: lonelyDirectExchange test message ";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> map = new HashMap<>();     map.put("messageId", messageId);     map.put("messageData", messageData);     map.put("createTime", createTime);     rabbitTemplate.convertAndSend("lonelyDirectExchange", "TestDirectRouting", map);     return "ok"; } 调用接口: 发挥了作用。这种情况下,消息是推送成功到服务器了的,所以ConfirmCallback对消息确认情况是true; 而在RetrunCallback回调函数的打印参数里面可以看到,消息是推送到了交换机成功了,但是在路由分发给队列的时候,找不到队列,所以报了错误 NO_ROUTE 。 结论:②这种情况触发的是 ConfirmCallback和RetrunCallback两个回调函数。 测试③消息推送到sever,交换机和队列啥都没找到 这种情况其实一看就觉得跟①很像,没错 ,③和①情况回调是一致的,所以不做结果说明了。 结论: ③这种情况触发的是 ConfirmCallback 回调函数。 测试④消息推送成功 结论: ④这种情况触发的是 ConfirmCallback 回调函数。 以上是生产者推送消息的消息确认 回调函数的使用介绍(可以在回调函数根据需求做对应的扩展或者业务数据处理)。 接下来我们测试消费者接到消息的消息确认机制 和生产者的消息确认机制不同,因为消息接收本来就是在监听消息,符合条件的消息就会消费下来。 所以,消息接收的确认机制主要存在三种模式:  ①自动确认, 这也是默认的消息确认情况。 AcknowledgeMode.NONE RabbitMQ成功将消息发出(即将消息成功写入TCP Socket)中立即认为本次投递已经被正确处理,不管消费者端是否成功处理本次投递。 所以这种情况如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。 一般这种情况我们都是使用try catch捕捉异常后,打印日志用于追踪数据,这样找出对应数据再做后续处理。  ② 不确认, 这个不做介绍 ③ 手动确认 , 这个比较关键,也是我们配置接收消息确认机制时,多数选择的模式。 消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功。 basic.ack用于肯定确认 basic.nack用于否定确认(注意:这是AMQP 0-9-1的RabbitMQ扩展) basic.reject用于否定确认,但与basic.nack相比有一个限制:一次只能拒绝单条消息 消费者端以上的3个方法都表示消息已经被正确投递,但是basic.ack表示消息已经被正确处理,但是basic.nack,basic.reject表示没有被正确处理,但是RabbitMQ中仍然需要删除这条消息。  之前介绍用了很多个交换,现在我们就先给直连型交换机添加消息接收确认机制, 新建MessageListenerConfig.java上添加代码相关的配置代码(可以看到注释掉的代码,就是给扇型交换机配置消息确认,只用在这个里面继续添加对应的队列和对应的接收类即可,当然对应的接收类也需要跟后面介绍一样加上对应方法):  import com.cc.mq.client.DirectReceiver; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.MessageListener; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          MessageListenerConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author:   * @Create Date:   2019/12/23  */ @Configuration public class MessageListenerConfig {      @Autowired     private CachingConnectionFactory connectionFactory;     @Autowired     private DirectReceiver directReceiver;//Direct消息接收处理类     @Autowired     DirectRabbitConfig directRabbitConfig;     @Bean     public SimpleMessageListenerContainer simpleMessageListenerContainer() {         SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);         container.setConcurrentConsumers(1);         container.setMaxConcurrentConsumers(1);         container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息         container.setQueues(directRabbitConfig.TestDirectQueue());         container.setMessageListener(directReceiver);         return container;     }  } 这里的DirectReceiver 是我们之前创建的接受类。 然后在直连型交换机的消息接收处理类上需要添加相关的消息手动确认代码DirectReceiver.java: @Component @RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue public class DirectReceiver implements ChannelAwareMessageListener {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());     }      @Override     public void onMessage(Message message, Channel channel) throws Exception {         long deliveryTag = message.getMessageProperties().getDeliveryTag();         try {             //因为传递消息的时候用的map传递,所以将Map从Message内取出需要做些处理             String msg = message.toString();             String[] msgArray = msg.split("'");//可以点进Message里面看源码,单引号直接的数据就是我们的map消息数据             Map<String, String> msgMap = this.mapStringToMap(msgArray[1].trim());             String messageId=msgMap.get("messageId");             String messageData=msgMap.get("messageData");             String createTime=msgMap.get("createTime");             System.out.println("messageId:"+messageId+"  messageData:"+messageData+"  createTime:"+createTime);             channel.basicAck(deliveryTag, true); //       channel.basicReject(deliveryTag, true);//为true会重新放回队列         } catch (Exception e) {             channel.basicReject(deliveryTag, false);             e.printStackTrace();         }      }          //这种解析方式是有问题的,在生产的时候不要这样做     private Map<String, String> mapStringToMap(String str) {         str = str.substring(1, str.length() - 1);         String[] strs = str.split(",");         Map<String, String> map = new HashMap<String, String>();         for (String string : strs) {             String key = string.split("=")[0].trim();             String value = string.split("=")[1];             map.put(key, value);         }         return map;     }  } 然后现在将rabbitmq-provider 、rabbitmq-consumer两个项目跑起来,调用下/sendDirectMessage接口往直连型交换机推送一条消息,看看监听到的消息结果: 手动的确认模式的投递效率略低于自动,但是可以弥补自动确认模式的不足,更加准确地去记录消息消费情况。 那么如果需要有些消息接收类设置自动确认,有些消息接收类设置手动确认的话,那只需要将需要设置手动确认的相关队列加到之前的MessageListenerConfig的SimpleMessageListenerContainer里面就行。 ———————————————— 原文链接:https://blog.csdn.net/qq_24848931/article/details/104468398 
  • [问题求助] CodeArts IDE for java怎么默认maven配置
    请问默认设置maven的路径,现在每次都要去配置,有点麻烦。并且新的版本如何去运行springboot项目
  • [技术干货] 探索Java中最常用的框架:Spring、Spring MVC、Spring Boot、MyBatis和Netty -转载
     Spring MVC框架  Spring MVC是Spring框架的一部分,用于构建Web应用程序。它提供了一种MVC(Model-View-Controller)的架构,使得Web应用的开发更有组织和易于维护。  模型(Model):模型代表应用程序的数据和业务逻辑。  视图(View):视图负责显示模型的数据。  控制器(Controller):控制器接受来自用户的请求,处理它们并选择适当的视图来响应请求。   Spring MVC的优点:  1.松耦合:Spring MVC使用了松耦合的设计,允许将控制器、模型和视图分开开发,从而提高了代码的可维护性。 2.高度可扩展:Spring MVC支持自定义视图解析器、拦截器等,使得定制化开发变得容易。 3.强大的数据绑定:Spring MVC可以将请求参数绑定到控制器方法的参数,大大减少了开发工作。 4.REST支持:Spring MVC支持构建RESTful Web服务,通过注解和配置来定义REST端点。     Spring Boot框架 Spring Boot是Spring的扩展,旨在简化Spring应用程序的创建和开发。它通过提供默认配置、自动配置和快速开发功能,大大减少了开发者的工作量。  Spring Boot的特点:   1.自动配置:Spring Boot根据项目中使用的库和类自动配置应用程序。如果你需要自定义配置,只需覆盖默认配置即可。 2.嵌入式Web服务器:Spring Boot集成了嵌入式的Web服务器,如Tomcat、Jetty等,无需额外配置。 3.生产就绪特性:Spring Boot内置了用于监控、度量、健康检查的功能,便于生产环境的部署。 4.开箱即用:Spring Boot提供了一系列的“Starter”依赖,可以快速构建特定类型的应用程序,如Web应用、数据访问应用、消息队列等。  MyBatis框架  MyBatis是一种优秀的持久层框架,它简化了数据库访问操作。与其他ORM框架不同,MyBatis使用XML或注解配置SQL语句,提供了更灵活的SQL编写方式。  MyBatis的特点:  1.SQL分离:MyBatis将SQL语句与Java代码分离,提供了更好的可读性和维护性。 2.参数映射:MyBatis可以将Java对象和数据库表之间的字段映射自动处理。 3.高性能:MyBatis执行SQL语句的性能很高,支持懒加载、缓存等特性。 4.灵活性:MyBatis支持自定义类型处理器、插件等扩展功能。  Netty框架 Netty是一个基于事件驱动的网络应用程序框架,用于快速开发高性能的网络服务器和客户端。它支持各种传输协议,如TCP、UDP、HTTP等。  Netty的特点:  1.高性能:Netty的事件驱动架构使得它在高负载情况下表现出色。 2.可扩展性:Netty的组件是可扩展的,可以轻松添加自定义的处理器。 3.多协议支持:Netty支持多种协议,使其适用于各种应用,包括Web服务、实时通信等。 4.成熟的生态系统:Netty有一个活跃的社区,提供了丰富的扩展和文档资源。  适用场景  1.使用Spring来构建企业级应用,特别是那些需要控制反转和面向切面编程的应用。 2.开发Web应用程序时,可以使用Spring MVC来处理Web请求。 3.使用Spring Boot来快速创建独立的Spring应用程序,减少配置工作。 4.需要数据库持久化操作时,可以选择MyBatis作为ORM框架。 5.需要构建高性能的网络应用程序时,可以使用Netty。   结语  Spring、Spring MVC、Spring Boot、MyBatis和Netty是Java开发中最常用的框架之一,每个框架都有自己的优点和适用场景。选择合适的框架取决于你的项目需求和技术栈。熟练掌握这些框架将有助于提高你的Java开发技能,加速项目开发,并提高应用程序的性能和质量。希望本文对你更好地理解这些框架提供了帮助。  关于探索Java中最常用的框架:Spring、Spring MVC、Spring Boot、MyBatis和Netty,懒大王就先分享到这里了,如果你认为这篇文章对你有帮助,请给懒大王点个赞点个关注吧,如果发现什么问题,欢迎评论区留言!!💕💕         ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/weixin_58070962/article/details/135497866 
  • [专题汇总] 新一年的汇总来啦,2024年1月技术干货合集
    本次给大家带来的是CodeArts最新的干货合集25篇,涵盖了,python,java,springboot,kafka,vue,pinia等多个干货,快来看吧。 1.idea一直indexing无法操作的问题解决【转】 https://bbs.huaweicloud.com/forum/thread-0294142071416308014-1-1.html  2.VSCode打开Json文件格式化的简单步骤【转】 https://bbs.huaweicloud.com/forum/thread-0243142071705276018-1-1.html  3.PHP8.3更新内容新特性及支持版本探究【转】 https://bbs.huaweicloud.com/forum/thread-0271142072065293019-1-1.html  4.springboot如何配置多kafka【转】 https://bbs.huaweicloud.com/forum/thread-0243142072165273019-1-1.html  5.java中@NotBlank限制属性不能为空【转】 https://bbs.huaweicloud.com/forum/thread-0297142073070924013-1-1.html  6.正则去除中括号(符号)及里面包含的内容(最新推荐)【转】 https://bbs.huaweicloud.com/forum/thread-0243142071315437017-1-1.html  7.python Dtale库交互式数据探索分析和可视化界面【转】 https://bbs.huaweicloud.com/forum/thread-0255142071244033020-1-1.html  8.python dataprep库简化加速数据科学操作【转】 https://bbs.huaweicloud.com/forum/thread-0294142071061284012-1-1.html  9.python kornia计算机视觉库实现图像变化【转】 https://bbs.huaweicloud.com/forum/thread-0255142070801280018-1-1.html  10.基于Python实现n-gram文本生成的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0271142070636778018-1-1.html  11.python第三方模块xmltodict库优雅处理xml格式为json【转】 https://bbs.huaweicloud.com/forum/thread-0259142068233301011-1-1.html  12.python Stanza处理NLP任务使用详解(多语言处理工具)【转】 https://bbs.huaweicloud.com/forum/thread-0271142068138833017-1-1.html  13.Python Arrow处理时间数据使用详解(标准库之外另一种选择)【转】 https://bbs.huaweicloud.com/forum/thread-0297142067851262012-1-1.html  14.python list 切片倒着取的实现示例【转】 https://bbs.huaweicloud.com/forum/thread-0243142067708249015-1-1.html  15.Python Fire中两种命令行参数灵活设置方式详解【转】 https://bbs.huaweicloud.com/forum/thread-0271142067433292016-1-1.html  16.pinia初学习 https://bbs.huaweicloud.com/forum/thread-0202141875876618017-1-1.html  17.为什么需要使用$nextTick?【转】 https://bbs.huaweicloud.com/forum/thread-0231141875789608020-1-1.html  18.Vue自动生成组件名【转】 https://bbs.huaweicloud.com/forum/thread-0296141875621746009-1-1.html  19.工程化第一步这个package.json要真的搞明白才行【转】 https://bbs.huaweicloud.com/forum/thread-0267141875153948017-1-1.html  20.关于vite的跨域问题【转】 https://bbs.huaweicloud.com/forum/thread-0231141874969502019-1-1.html  21.前端无感知刷新token & 超时自动退出【转】 https://bbs.huaweicloud.com/forum/thread-02118141874791175015-1-1.html  22.按钮防连点终极解决方案 【转】 https://bbs.huaweicloud.com/forum/thread-0224141874429208008-1-1.html  23.5分钟搞定vue3函数式弹窗【转】 https://bbs.huaweicloud.com/forum/thread-0202141874275399016-1-1.html  24.有了Composition API后,有些场景或许你不需要pinia了【转】 https://bbs.huaweicloud.com/forum/thread-02118141874125748014-1-1.html  25.Vue中的$attrs你真的会用吗?【转】 https://bbs.huaweicloud.com/forum/thread-0296141873968992008-1-1.html 
总条数:1653 到第
上滑加载中