单体式应用程序(Monolithic application)
与微服务相对的另一个概念是传统的单体式应用程序( Monolithic application ),单体式应用内部包含了所有需要的服务。而且各个服务功能模块有很强的耦合性,也就是相互依赖彼此,很难拆分和扩容。
单体应用程序的优点:
当然也有一些缺点,单体式应用程序由于服务之间的紧密度、相依性过高,这将导致测试、升级有所困难,且开发曲线有可能会在后期大幅度地上升,令开发不易。相较之下「微服务架构」能够解决这个问题。
微服务(Microservices)
维基上对其定义为:一种软件开发技术- 面向服务的体系结构(SOA)架构样式的一种变体,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够独立地部署到生产环境、类生产环境等。
微服务优点:
总的来说,微服务架构是一种将单个应用程序作为一套小型服务开发的方式,每种应用程序都运行在自己的进程中,并使用轻量机制进行通讯,这些服务围绕业务功能构建,可以全自动化独立部署。这些服务集中管理最少,可以使用不同的编程语言编写,并使用不同数据存储技术。
微服务是基于分而治之的思想演化出来的。过去传统的一个大型而又全面的系统,随着互联网的发展已经很难满足市场对技术的需求,于是我们从单独架构发展到分布式架构,又从分布式架构发展到 SOA 架构,服务不断的被拆分和分解,粒度也越来越小,直到微服务架构的诞生。微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。微服务架构和分布式架构的区别是部署方式不一样。分布式是将一个大的系统划分为多个业务模块,这些业务模块会分别部署到不同的机器上,通过接口进行数据交互。微服务的应用可以部署在是同一个服务器,不一定是分散在多个服务器上。
微服务是真正的分布式的、去中心化的。把所有的“思考”逻辑包括路由、消息解析等放在服务内部,去掉一个大一统的 ESB,服务间轻通信,是比 SOA 更彻底的拆分。
微服务架构强调的重点是业务系统需要彻底的组件化和服务化,原有的单个业务系统会拆分为多个可以独立开发,设计,运行和运维的小应用,这些小应用之间通过服务完成交互和集成。
微服务解决的是系统复杂度问题,分布式解决的是系统性能问题。两者概念层面也是不一样的: 微服务是设计层面的东西,一般考虑如何将系统从逻辑上进行拆分,也就是垂直拆分;而分布式是部署层面的东西,即强调物理层面的组成,即系统的各子系统部署在不同计算机上。微服务可以是分布式的,即可以将不同服务部署在不同计算机上,当然如果量小也可以部署在单机上。
SOA(Service Oriented Architecture)“面向服务的架构”:是一种设计方法,其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能。一个服务 通常以独立的形式存在与操作系统进程中。各个服务之间 通过网络调用。
ESB和微服务API网关:
ESB(企业服务总线), 就是一根管道,用来连接各个服务节点。为了集 成不同系统,不同协议的服务,ESB 做了消息的转化解释和路由工作,让不同的服务互联互通;
API网关:API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。
微服务架构是一项在云中部署应用和服务的新技术。大部分围绕微服务的争论都集中在容器或其他技术是否能很好的实施微服务。
微服务可以在“自己的程序”中运行,并通过“轻量级设备与HTTP型API进行沟通”。关键在于该服务可以在自己的程序中运行。通过这一点我们就可以将服务公开与微服务架构(在现有系统中分布一个API)区分开来。在服务公开中,许多服务都可以被内部独立进程所限制。如果其中任何一个服务需要增加某种功能,那么就必须缩小进程范围。在微服务架构中,只需要在特定的某种服务中增加所需功能,而不影响整体进程的架构。
从概念理解,分布式服务架构强调的是服务化以及服务的分散化,微服务则更强调服务的专业化和精细分工;从实践的角度来看,微服务架构通常是分布式服务架构,反之则未必成立。所以,选择微服务通常意味着需要解决分布式架构的各种难题。
区别分布式的方式是根据不同机器不同业务。
将一个大的系统划分为多个业务模块,业务模块分别部署到不同的机器上,各个业务模块之间通过接口进行数据交互。区别分布式的方式是根据不同机器不同业务。
微服务更加强调单一职责、轻量级通信(HTTP)、独立性并且进程隔离。
微服务与分布式的细微差别是,微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器。
传统开发模式:
客户端如何访问这些服务:
Monolithic方式开发,所有的服务都是本地的,UI可以直接调用,现在按功能拆分成独立的服务,跑在独立的一般都在独立的虚拟机上的 Java进程了。客户端UI如何访问呢?
后台有N个服务,前台就需要记住管理N个服务,一个服务下线/更新/升级,前台就要重新部署,这明显不服务我们 拆分的理念,特别当前台是移动应用的时候,通常业务变化的节奏更快。
另外,N个小服务的调用也是一个不小的网络开销。还有一般微服务在系统内部,通常是无 状态的,用户登录信息和权限管理最好有一个统一的地方维护管理(OAuth)。
所以,一般在后台N个服务和UI之间一般会一个代理或者叫API Gateway,他的作用包括:
① 提供统一服务入口,让微服务对前台透明
② 聚合后台的服务,节省流量,提升性能
③ 提供安全,过滤,流控等API管理功能
其实这个API Gateway可以有很多广义的实现办法,可以是一个软硬一体的盒子,也可以是一个简单的MVC框架,甚至是一个Node.js的服务端。他们最重要的作 用是为前台(通常是
移动应用)提供后台服务的聚合,提供一个统一的服务出口,解除他们之间的耦合,不过API Gateway也有可能成为单点故障点或者性能的瓶颈。
所有的微服务都是独立的Java进程跑在独立的虚拟机上,所以服务间的通信就是IPC(inter process communication),已经有很多成熟的方案。现在基本最通用的有两种方式:
同步调用:
①REST(JAX-RS,Spring Boot)
②RPC(Thrift, Dubbo)
异步消息调用(Kafka, Notify, MetaQ)
微服务架构是使用一套小服务来开发单个应用的方式或途径,每个服务基于单一业务能力构建,运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,并能够通过自动化部署机制来独立部署。这些服务可以使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行单独启动或销毁,拥有自己独立的数据库。
参考文档
1、服务集群
单体项目拆分成独立项目(服务,每个服务完成一部分的业务功能)形成服务集群。一个业务往往需要多个服务去完成。
2、注册中心
大量且复杂的业务会使服务之间的调用关系非常复杂,人工无法完成,注册中心就是用于记录每个服务的IP、端口及作用这些信息,当一个服务需要调用其他服务时,只需要从注册中心拉取对应的服务信息即可,无需自己记录。
注册中心的作用一句话概括就是存放和调度服务,实现服务和注册中心,服务和服务之间的相互通信。注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用。
常用的注册中心中间件
特性 | Eureka | Nacos | Consul | Zookeeper |
---|---|---|---|---|
访问协议 | HTTP | HTTP/DNS | HTTP/DNS | TCP |
监听支持 | 支持· | 支持· | 支持· | 支持· |
多数据中心 | 支持· | 支持· | 支持· | 不支持· |
springcloud | 支持· | 支持· | 支持· | 支持· |
健康检查 | Client Beat | TCP/HTTP/MYSQL/Client Beat | TCP/HTTP/CMD | Keep All Live |
3、配置中心
服务越来越多,每个服务都会有很多配置信息,配置中心可以做到统一管理集群中的所有配置。若有配置变更,找到配置中心,它会通知相关的服务实现配置的“热更新”。
在微服务架构中,当系统从一个单体应用,被拆分成分布式系统上一个个服务节点后,配置文件也必须跟着分割,这样配置就分散了,再考虑到多环境配置文件参数不同的情况。所以这时候我们就需要配置中心。
配置中心的作用就是将配置从各应用中剥离出来,对配置进行统一管理,应用自身不需要自己去管理配置。
常见的配置中心有Nacos、Apollo、Disconf。
4、服务网关
一方面对用户身份做校验,将用户请求路由到具体的微服务,也可以做一些负债均衡。网关是微服务架构中的一个关键的角色,用来保护、增强和控制对于微服务的访问。网关是一个处于应用程序或服务之前的系统,用来管理授权、访问控制和流量限制等,这样微服务就会被微服务网关保护起来,对所有的调用者透明。因此,隐藏在微服务网关后面的业务系统就可以更加专注于业务本身。同时,微服务网关还可以为服务提供和沉淀更多附加功能。
5、数据库
数据库也可能是集群,但仍可能无法抗住并发,需要引入缓存。主要职责就是做一些写操作或者事务类型的对数据安全要求较高的数据存储。
6、分布式缓存
缓存就是将数据存在内存中,数据库则是存在磁盘上,优劣可见。应对高并发需要将缓存做成分布式缓存。用户请求先到缓存,缓存未命中再访问数据库。
7、分布式搜索
海量数据的搜索,统计和分析可以用分布式搜索完成,简单的查询可以走缓存。
8、消息队列
正常情况一个业务可能需要走多个服务,A-B-C-D,由A调用B,B调用C,C调用D,时间就是这几个服务调用的时间之和,往往实际业务可能更复杂,这样长链路调用会对性能造成影响,于是引进了消息队列。调用A后,A发一个消息给BCD,让BCD开始工作,A则是结束就直接关掉了,链路变短以及吞吐能力加强,做到异步通信的效果,从而提高效率。
常见的消息队列有:RabbitMQ、RocketMQ、ActiveMQ、Kafka等。
10、分布式日志服务
庞大复杂的系统不易排查定位问题,需引入分布式日志服务,方便分体追踪排查。
9、系统监控链路追踪
实时监控集群中每个服务节点的运行状态,cup负载以及内存的占用等等情况,一旦出现任何问题可以直接定位到异常信息。
微服务架构有以下6种:1. Dubbo;2. Motan;3. Tars;4. Spring Cloud;5. gRPC;6. Thrift。Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
spring boot专注于快捷方便的开发一个个微服务个体,spring cloud是关注全局微服务协调整治的框架,将一个个的spring boot开发的单个微服务整合并管理起来。
参考文档感谢作者!
spring cloud 中文网
spring cloud子项目包括:
Spring Cloud Config:配置管理开发工具包,可以让你把配置放到远程服务器,目前支持本地存储、Git以及Subversion。
Spring Cloud Bus:事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。
Spring Cloud Netflix:针对多种Netflix组件提供的开发工具包,其中包括Eureka、Hystrix、Zuul、Archaius等。
Netflix Eureka:云端负载均衡,一个基于 REST 的服务,用于定位服务,以实现云端的负载均衡和中间层服务器的故障转移。
Netflix Hystrix:容错管理工具,旨在通过控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
Netflix Zuul:边缘服务工具,是提供动态路由,监控,弹性,安全等的边缘服务。
Netflix Archaius:配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。
Spring Cloud for Cloud Foundry:通过Oauth2协议绑定服务到CloudFoundry,CloudFoundry是VMware推出的开源PaaS云平台。
Spring Cloud Sleuth:日志收集工具包,封装了Dapper,Zipkin和HTrace操作。
Spring Cloud Data Flow:大数据操作工具,通过命令行方式操作数据流。
Spring Cloud Security:安全工具包,为你的应用程序添加安全控制,主要是指OAuth2。
Spring Cloud Consul:封装了Consul操作,consul是一个服务发现与配置工具,与Docker容器可以无缝集成。
Spring Cloud Zookeeper:操作Zookeeper的工具包,用于使用zookeeper方式的服务注册和发现。
Spring Cloud Stream:数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。
Spring Cloud CLI:基于 Spring Boot CLI,可以让你以命令行方式快速建立云组件。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
1、Registry:服务注册与发现中心,作为服务提供者和消费者注册与发现的中心。
2、Provider:服务提供者,在注册中心注册作为服务提供的一方,发布服务到服务注册中心。
3、Consumer:服务消费者,通过注册中心协调,订阅可用的已注册的服务。
4、Container:服务运行容器,独立的容器类似于tomcat/jboss的作用,作为服务运行的容器。
5、Monitor:dubbo的监控中心,用来显示接口暴露、注册情况,也可以看接口的调用明细,调用时间等。
1、dubbo由于是二进制的传输,占用带宽会更少
2、springCloud是http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大
3、dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决
4、springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级
5、dubbo的注册中心可以选择zk,redis等,springcloud的注册中心用eureka或者Consul
1、客户端如何访问这些服务?
传统的开发方式,所有的服务都是本地的,UI可以直接调用,现在按功能拆分成独立的服务,跑在独立的一般都在独立的虚拟机上的 Java进程了。客户端UI如何访问他的?后台有N个服务,前台就需要记住管理N个服务,一个服务下线/更新/升级,前台就要重新部署,这明显不符合我们拆分的理念,特别当前台是移动应用的时候,通常业务变化的节奏更快。另外,N个小服务的调用也是一个不小的网络开销。还有一般微服务在系统内部,通常是无状态的,用户登录信息和权限管理最好有一个统一的地方维护管理(OAuth)。所以,一般在后台N个服务和UI之间会有一个代理或者叫API Gateway,他的作用包括提供统一服务入口,让微服务对前台透明,聚合后台的服务,节省流量,提升性能,提供安全,过滤,流控等API管理功能。
2、服务之间如何通信?
因为所有的微服务都是独立的Java进程跑在独立的虚拟机上,所以服务间的通行就是IPC(inter process communication),已经有很多成熟的方案。现在基本最通用的有:HTTP(JAX-RS,Spring Boot)、RPC(Thrift, Dubbo),异步消息调用(Kafka, Notify)
3、这么多服务怎么查找?
在微服务架构中,一般每一个服务都是有多个拷贝,来做负载均衡。一个服务随时可能下线,也可能应对临时访问压力增加新的服务节点。服务之间如何相互感知?服务如何管理?这就是服务发现的问题了。一般有两类做法,也各有优缺点。基本都是通过zookeeper等类似技术做服务注册信息的分布式管理。当服务上线时,服务提供者将自己的服务信息注册到Zookeeper(或类似框架),并通过心跳维持长链接,实时更新链接信息。服务调用者通过Zookeeper寻址,根据可定制算法,找到一个服务,还可以将服务信息缓存在本地以提高性能。当服务下线时,Zookeeper会发通知给服务客户端。
4、服务挂了怎么办?
分布式最大的特性就是对网络的依赖。如果遇到某个服务可能会影响了所在应用的性能,从而影响所有调用这个应用服务的前台应用。所以当系统是由一系列的服务调用链组成的时候,我们必须确保任一环节出问题都不至于影响整体链路。相应的技术有:
常见的重试主要有两种模式:原地重试、异步重试。原地重试很好理解,就是程序在调用下游服务失败的时候重新发起一次;异步重试是将请求信息丢到某个 MQ中,后续有一个程序消费到这个事件进行重试。
依赖服务的业务请求被阻塞,用户不会得到响应,服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用。
限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。
一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。
熔断机制是对系统的防护,比如受到一些恶意攻击,那么需要熔断机制来保护系统的微服务,做出响应,避免资源被耗尽。既要能响应,又要能防护,当我们的请求达到一个负载阈值,就启用熔断,把真实接口关掉,给客户端请求一个响应,这个响应,我们可以设置。服务熔断就是对该服务的调用执行熔断,对应后续请求,不在继续调用该目标服务,而是直接返回,从而可以快速释放资源,或者服务出现故障,会把故障信息返回给客户端。
熔断器的功能:异常处理、日志记录、测试失败的操作、手动复位、并发、加速断路、重试失败请求
熔断与降级的区别:触发条件不同,熔断一般是故障引起,降级一般是整体性能。管理目标层次不同。
负载均衡
降级(本地缓存)
为了保证核心服务的正常运行,会对一些服务、接口、页面做降级处理,降级处理一般都是人工干预的,可以进行配置的。降级和熔断的框架Hystrix。
庞大的系统手动部署不合实际,需进行持续集成。
1、Jenkins
自动化编译
2、docker
打包镜像
3、kubernetes/RANCHER
实现自动化部署
spring cloud技术栈
spring cloud只是微服务的一种流行的技术体系,也还有其他Dubbo的技术。
spring cloud netflix组件
spring cloud abibaba组件
spring cloud原生及其它组件
SpringCloud Gateway 是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。其目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,而且支持安全,监控/指标,和限流功能。
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。方便部署与运维、分客户端、服务端。服务端也称分布式配置中心,是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。客户端则是通过指定配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。默认采用 git,并且可以通过 git 客户端工具来方便管理和访问配置内容。
Spring Cloud Bus将分布式系统的节点与轻量级消息代理链接。这可以用于广播状态更改或其他管理指令。一个Bus就像一个扩展的Spring Boot应用程序的分布式执行器,但也可以用作应用程序之间的通信渠道。
Spring Cloud Eureka 是对Netflix公司的Eureka的二次封装,它实现了服务治理的功能,Spring Cloud Eureka提供服务端与客户端,服务端即是Eureka服务注册中心,客户端完成微服务向Eureka服务的注册与发现。
在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
Fegin是一个声明式的Http客户端,它使得写Http客户端变得更简单,使用Fegin只需要创建一个接口并注解,它具有可插拔的注解特性,Nacos很好的兼容了Fegin,默认实现了负载均衡的效果,底层使用了HttpClient作为服务框架。
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。
Sleuth
Spring Cloud Sleuth为Spring Cloud实现了分布式跟踪解决方案。
Spring Cloud Stream
Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架。它可以基于 Spring Boot 来创建独立的、可用于生产的 Spring 应用程序。Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,并引入了发布-订阅、消费组、分区这三个核心概念。通过使用 Spring Cloud Stream,可以有效简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。但是目前 Spring Cloud Stream 只支持 RabbitMQ 和 Kafka 的自动化配置。
搭建注册中心
在构建的项目中每个模块就是一个独立的服务,例如product_service
应为一个独立的spring boot程序,可以单独启动的服务:
**创建工程并时独立的模块都能单独运行。但是微服务之间时存在互相调用的,不可能都借助浏览器进行,因此调用发生在硬代码之间,基于HTTP
协议的调用通过RestTemplate
对象调用。
创建过程:
...
@RestController
@RequestMapping(value = "/order")
public class OderController {
@Autowired
private UserService userService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/roles")
public List<User> method1(){
List<User> users = userService.selectColumn();
return users;
}
@GetMapping(value = "/order_role")
public List<Role> method2(){
List<Role> roles =restTemplate.getForObject("http://localhost:8080/role",List.class);
return roles;
}
}
在启动类注入了RestTemplate
,调用类中主动装配,调用方法的第一个参数时url,第二个参数是对象类型,它的返回值类型和第二个参数是一致的,用法和JdbcTemplate类似。
访问对应路径成功调用:
通过
RestTemplate
对象返回的是List
泛型类型,所以还要为order_service
创建Role
对象,显然这不符合已服务为核心。这是模拟微服务的调用过程,后续的创建注册中心部分才正式进入微服务。
但同时还有许多的问题,现在每个服务的地址都是硬编码写的,如果要添加服务就需要重新编写代码,删除也是,这样不符合微服务特性。因此需要服务发现,配置中心,消息总线,负载均衡,熔断器,数据监控等技术来完成微服务的开发。
注册中心的搭建
构建注册中心模块,在注册中心的配置模块导入eureka依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
<version>...version>
dependency>
#配置eureka server
server.port=9000
# eureka的地址
eureka.instance.hostname=localhost
#是否将eureka注册到注册中心
eureka.client.register-with-eureka=false
#是否从eureka中获取注册地址
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
启动注册中心模块:
报错:Error creating bean with name 'configurationPropertiesBeans' defined in class path resource [org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.class]: Bean instantiation via factory method failed;
Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
报错原因是:问题在spring-cloud-starter-netflix-eureka-client
版本和SpringBoot
版本不兼容,版本的搭配了具体我也不太清楚,就试出了一组可以正常启动的版本:
spring boot版本:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.9version>
<relativePath/>
parent>
eureka版本:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
<version>3.1.0version>
dependency>
输入:http://localhost:9000/
创建的工程中,eureka模块相当于注册中心,相当于一个服务器server,其他模块就是一个客户端client,所以需要在服务模块导入eureka-client:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
<version>3.1.0version>
dependency>
关联客户端与服务端,在服务的配置文件追加:
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
# 使用ip地址注册
eureka.instance.prefer-ip-address=true
注解激活eureka-client,在启动类添加@EnableEurekaClient
或@EnableDiscoveryClient
注解。
@SpringBootApplication
@MapperScan("com.example.product.dao")
@EnableEurekaClient
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
服务配置成功后,启动注册中心,进入后台管理查看服务:
注意所有模块的spring boot的版本要保持一致,如果熟悉Maven的集成与融合可以通过继承来优化资源。
当启动一个服务后就变成了这样:
有一个服务但不知道名字,所以还需要配置,再服务模块的配置文件中追加:
# 配置服务的名称
spring.application.name=product_service
现在已将服务注册到注册中心了,并不是通过硬编码(像之前那样通过RestTemplate对象获取信息),而是将服务注册到注册中心由其自己管理。
Eureka中的元数据,例如主机名,ip信息服务名称,ip都可以通过eureka-server来获取,用于服务间的相互调用。
eureka-server的服务调用通过spring-cloud的DiscoveryClient
对象来获取和RestTemplate```对象相似,注意别导错坐标了是spring cloud提供的对象:
导入后如下:
如果导错坐标后会报错未发现报错的类,但是在IoC容器中,开发时也没注入spring cloud提供的DiscoveryClient
对象啊,原因是使用了@EnableEurekaClient
注解,已注入了org.springframework.cloud.client.discovery.DiscoveryClient
对象。
org.springframework.cloud.client.discovery.DiscoveryClient
对象提供了5个方法,
//实现类的功能描述,一般用在HealthIndicator的打印日志中
String description();
//根据服务Id获取对应的服务实例集合
List<ServiceInstance> getInstances(String serviceId);
//获取所有的服务Id
List<String> getServices();
调用getInstances
方法获取所有服务,注意服务不止有一个,同一个服务可能存在与不同服务器,或不同端口上,因此获取是一个集合。
ServiceInstance
对象包含一个服务的基本信息:
public interface ServiceInstance {
//实例Id
default String getInstanceId() {
return null;
}
//服务Id
String getServiceId();
//主机名或地址
String getHost();
//端口号
int getPort();
//是否使用安全协议HTTPS
boolean isSecure();
//服务的URL
URI getUri();
//服务实例相关的元数据
Map<String, String> getMetadata();
//使用方案,一般表示协议,比如http或https
default String getScheme() {
return null;
}
}
想了解更多服务的实例对象可以参考该文章SpringCloud源码学习笔记之Eureka客户端感谢作者@姠惢荇者
使用DiscoveryClient
对象从注册中心获取服务的相关信息:
@RestController
@RequestMapping(value = "/role")
public class RoleController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping(value = "/product1")
public String method(){
List<ServiceInstance> product_service = discoveryClient.getInstances("product_service");
for (ServiceInstance instance:product_service
) {
System.out.println(instance);
}
return "ok";
}
}
从运行的结果可以看出,已经获取到了服务的ip地址,那么就可以取代之前编写服务时硬编码的部分:
...
List<Role> roles =restTemplate.getForObject("http://localhost:8082/role/test",List.class);
...
此时http://localhost:8080
相关信息都可以从注册中心获取的ServiceInstance
中得到(启动另一个服务作为例子order_service
):
@RestController
@RequestMapping(value = "/order")
public class OderController {
@Autowired
private UserService userService;
@Autowired
private RestTemplate restTemplate;
//服务调用
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/roles")
public List<User> method1(){
List<User> users = userService.selectColumn();
return users;
}
@GetMapping(value = "/order_role")
public List<Role> method2(){
List<Role> roles =restTemplate.getForObject("http://localhost:8082/role/test",List.class);
return roles;
}
@GetMapping(value = "/order_role1")
public List<Role> method3(){
//获取一个服务的实例
//服务实例的赋值对象是ServiceInstance
List<ServiceInstance> service_list = discoveryClient.getInstances("product_service");
ServiceInstance instance = service_list.get(0);
System.out.println(instance.getHost());
System.out.println(instance.getUri());
System.out.println(instance.getPort());
List<Role> list = restTemplate.getForObject("http://"+instance.getHost()+":"+instance.getPort()+"role/test",List.class);
return list;
}
}
该控制器有三个接口/roles
为原始api、/order_role
为模拟微服务api、/order_role1
为微服务api,他们实现相同的功能。
启动两个服务:
微服务api:
哈哈哈,竟然报错了,看到状态码是500,就放心了,至少思路没问题。回到eureka后台发现了问题:
首先,ipaddr不是localhost,然后Status显示的也是显示的电脑名。原因在于:
在eureka.instance
配置下:
hostname
即主机名不配置的话默认为电脑名,
instanceID
不配置的话默认值为主机名+服务名+端口,
prefer-ip-address
表示猜测主机名(hostname)为ip形式,不配置的话默认为false。
服务模块没有配置hostname导致地址不对,默认是电脑名且随机分配一个静态ip,所以服务器错误。对每个服务配置
eureka.instance.hostname=localhost
,再配置eureka.instance.instance-id=${eureka.instance.hostname}:${spring.application.name}:${server.port}
上线时再将ip改为服务器地址。
# 数据源及驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/smbms?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=root
# 端口
server.port=8081
# 关联注册中心
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
# 配置eureka的状态显示
eureka.instance.hostname=localhost
eureka.instance.instance-id=${eureka.instance.hostname}:${spring.application.name}:${server.port}
# 使用ip地址注册
eureka.instance.prefer-ip-address=true
# 配置服务的名称
spring.application.name=order_service
修改每个服务的配置使其生效:
该部分参考于Eureka Server 的 Instance Status 一直显示主机名问题感谢作者!
接下来解决ipAddr
显示的主机ip而不是localhost的问题:
# 忽略网卡的影响
#Npcap Loopback Adapter:为忽略的多网卡对注册中心注册的影响
spring.cloud.inetutils.ignored-interfaces[0]=Npcap Loopback Adapter
# 配置服务ip地址为localhost
eureka.instance.ip-address=localhost
再会看代码:
服务的ip是注册中心自动完成的,获取也是,url的后缀是特定的,注册中心负责完成服务的注册与获取。
注册中心只是微服务的一个小分支,微服务时一个庞大的生态,各种组件支持解决不同的问题。
该案例只是简单介绍了微服务,服务是在同一个模块下的,每个模块是一个独立的spring boot程序,单独的spring boot程序也可以通过配置注册到注册中心并获取实际的技术核心还很多包括安全及过滤的网关,配置中心,消息总线,监控与维护等。
期待下次更新吧!