服务演变之路
单体应用架构
在刚开始的时候,企业的用户量、数据量规模都⽐较⼩,项⽬所有的功能模块都放在⼀个⼯程中编码、编译、打包并且部署在⼀个Tomcat容器中的架构模式就是单体应用架构,这样的架构既简单实用、便于维护,成本⼜低,成为了那个时代的主流架构⽅式。这时候由于业务以及规模都⽐较⼩,所以⽆论服务以及DB都是使⽤单节点(all-in-one)的⽅式进⾏部署,这就是单体架构。
优点:
1、项⽬前期开发节奏快,团队成员少的时候能够快速迭代
2、架构简单:MVC架构,只需要借助IDE开发、调试即可
3、易于测试:只需要通过单元测试或者浏览器完成
4、易于部署:打包成单⼀可执行的jar或者打成war包放到容器内启动
缺点:
1、随着时间推移业务增加,功能不断迭代,项⽬会不断变得臃肿,业务耦合严重。
2、新增业务困难:在已经乱如麻的系统中增加新业务,维护成本高,新⼈来了以后很难接手任务,学习成本高。
3、核⼼业务与边缘业务混合在⼀块,出现问题互相影响
垂直应用架构
为了避免单体架构上出现的那些问题,开始对应⽤按照业务做垂直划分,把原来的的⼀个单体架构拆成⼀堆单体应⽤,这时候就由原来的单应⽤变成了多应⽤部署,这就是垂直架构
优点:
1、可以针对不同模块进行优化。
2、⽅便⽔平扩展,负载均衡,容错率提高。
3、系统间相互独⽴,互不影响,新的业务迭代时更加⾼效。
缺点:
1、服务之间相互调用,如果某个服务的端⼝或者ip地址发⽣改变,调用的系统得手动改变。
2、搭建集群之后,实现负载均衡⽐较复杂,如:内网负载,在迁移机器时会影响调用⽅的路由,导致线上故障。
3、服务之间调用⽅式不统⼀,基于 httpclient 、 webservice ,接⼝协议不统⼀。
4、服务监控不到位:除了依靠端口、进程的监控,调用的成功率、失败率、总耗时等等这些监控指标是没有的。
SOA架构
SOA (Service-Oriented Architecture),即⾯向服务的架构。其思想就是根据实际业务,把系统拆分成合适的、独⽴部署的模块,模块之间相互独⽴(通过Webservice/Dubbo等技术进⾏通信)。因此衍⽣出了⼀系列相应的技术,如对服务提供、服务调⽤、连接处理、通信协议、序列化方式、服务发现、服务路由、⽇志输出等⾏为进⾏封装的服务框架。
SOA主要解决的问题
1> 信息孤岛。
2> 共享业务的重用
微服务
那么被SOA拆分出来的服务是否也需要以业务功能为维度来进行拆分和独立部署,以降低业务的耦合及提升容错性?微服务就是这样一种解决方案。
我们可以把SOA看成微服务的超集,也就是多个微服务可以组成一个SOA服务。
伴随着服务颗粒化的细化,会导致原本10个服务可能拆分成了100个微服务,一旦服务规模扩大,就意味着服务的构建、发布、运维的复杂度也会成倍增加。
优点:
1> 每个服务足够小,足够内聚,专注于一个业务功能点提供服务。代码更容易理解。
2> 有代码修改或部署上线,只会影响对应的微服务,而不会是整个服务。
3> 可针对服务是计算型还是IO型进行针对性的硬件升级。
4> 可以针对某些高吞吐服务进行硬件升级或者服务横向扩容,而不是对所有服务都升级,节约投入成本。
缺点:
1> 极大的增加了运维工作量,以前几个war包,现在可能需要部署几百个。
2> 微服务之间的互相调用,会增加通讯成本。
3> 分布式事务问题会引出数据一致性的问题。
4> 服务增多,如果管控成百上千的服务。如何准确并快速定位问题。
SOA与微服务的区别
SOA关注的是服务的重用性及解决信息孤岛问题。
微服务关注的是解耦,虽然解耦和可重用性从特定的角度来看是一样的,但本质上是有区别的;
解耦:降低业务之间的耦合度。
重用性:关注的是服务的复用。
微服务会更多地关注在DevOps的持续交付上,因为服务粒度细化之后使得开发运维变得更加重要,因此微服务与容器化技术的结合更加紧密。
服务发现介绍
服务治理的理念
-
在传统的系统部署中,服务运⾏在⼀个固定的已知的IP和端 ⼝上,如果⼀个服务需要调⽤另⼀个服务,那么可以通过地 址直接调⽤。但是,在虚拟化或者容器化的环境中,服务实例的启动和销毁是很频繁的,那么服务地址也是在动态变化 的。这种情况下,就需要服务发现机制了。
-
服务发现有两种:
- 基于客户端的服务发现
客户端通过查询服务注册中心,获取可用服务的实际网络地址(IP&PORT)。然后通过负载均衡算法来选择一个可用的服务实例,并将请求发送至该服务。
在服务启动的时候,向服务注册中心注册服务;在服务停止的时候,向服务注册中心注销服务。服务注册的一个典型实现方式就是通过heartbeat机制(心跳机制)定时刷新
- 基于服务端的服务发现
客户端向load balancer上发送请求。load balancer查询服务注册中心,找到可用的服务,然后转发请求到该服务上。和客户端发现一样,服务都要到注册中心进行服务的注册和销毁。
服务发现调用流程
客户端服务发现
优点:客端知道所有的服务提供者ip,可以根据⾃⼰的业务情况⾮常⽅便的实现负载均衡。
缺点:耦合性太强,不同语⾔的客户端都需要⾃⼰实现⼀套负载均衡。
服务端服务发现
优点:服务的发现逻辑对客户端透明,客户端⽆需关注服务负载均衡,直接发起调用即可。
缺点:服务端需要关注LB的高可用。
服务发现技术对比
Nacos实战
Nacos介绍
Nacos是阿里的一个开源产品,它是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案。
官方介绍如下:
致力于帮助您发现、配置和管理微服务。
提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
帮助您更敏捷和容易地构建、交付和管理微服务平台。
是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。
Nacos特性
-
服务发现与服务健康检查
Nacos使服务更容易注册,并通过DNS或HTTP接口发现其他服务;
Nacos还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求。
- 动态配置服务
动态配置服务运行在所有环境中以集中和动态的方式管理所有服务的配置。
Nacos消除了在更新配置时重新部署应用程序,这使配置的更改更加高效和灵活。
-
动态DNS服务
Nacos提供基于DNS协议的服务发现能力,旨在支持异构语言的服务发现;
支持将注册在Nacos上的服务以域名的方式暴露端点,让三方应用方便查阅及发现。
-
服务和元数据管理
Nacos能让您从微服务平台建设的视角管理数据中心的所有服务及元数据(即:服务相关的一些配置和状态信息)
包括:管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略。
Nacos架构图
【注】
Provider APP :服务提供者
Consumer APP :服务消费者
Name Server:通过VIP(Vritual IP)或者DNS的方式实现Nacos高可用集群的服务路由。
Nacos Server :Nacos服务提供者,里面包含:
- Open API:功能访问入口。
- Config Service:配置服务模块。在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者。
- Naming Service:名字服务模块。提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务,服务发现和DNS就是名字服务的2大场景。
- Consistency Protocol:一致性协议,用来实现Nacos集群节点的数据同步。使用Raft算法(使用类似算法的中间件还有Etcd、Redis哨兵选举等)。
- Nacos Console:Nacos的控制台。
Nacos快速入门
-
SpringCloud常见的集成方案
【注】
- Ribbon:基于客户端的负载均衡。
- Feign:可以帮我们更快捷、优雅地调用HTTP API。将HTTP报文请求方式伪装为简单的java接口调用方式。
-
搭建Nacos服务端
请参见【配置中心Nacos】中Nacos的安装步骤
Nocos快速开始
- 添加父类pom依赖
<properties> <java.version>1.8</java.version> <spring.boot>2.1.3.RELEASE</spring.boot> <spring.cloud>Greenwich.RELEASE</spring.cloud> <spring.cloud.alibaba>2.1.0.RELEASE</spring.cloud.alibaba> </properties> <dependencyManagement> <dependencies> <!-- 引入Spring Cloud Alibaba依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 引入Spring Cloud依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 引入Spring Boot依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
- 项目主体结构
nacos-provider相关代码
主pom
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Cloud Alibaba Nacos Discovery --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- Spring Cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
- nacos-provider-web
pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
yaml
server: port: 7000 spring: application: name: nacos-provider cloud: nacos: discovery: server-addr: 127.0.0.1:8848
NacosProviderApplication
@EnableDiscoveryClient @EnableFeignClients @SpringBootApplication public class NacosProviderApplication { public static void main(String[] args) { SpringApplication.run(NacosProviderApplication.class, args); } }
ProviderController
@Slf4j @RestController @RequestMapping("/provider") public class ProviderController { @Resource private ProviderService providerService; @RequestMapping("/hello") public String hello() { log.info("客户端hello"); return providerService.hello(); } }
providerService
@FeignClient(name = "nacos-consumer") public interface ProviderService { @GetMapping("/consumer/hello") String hello(); }
nacos-consumer相关代码
主pom
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Cloud Alibaba Nacos Discovery --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- Spring Cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
- nacos-consumer-web
pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
ymal
server: port: 7002 spring: application: name: nacos-consumer cloud: nacos: discovery: server-addr: 127.0.0.1:8848
NacosConsumerApplication
@EnableDiscoveryClient @EnableFeignClients @SpringBootApplication public class NacosConsumerApplication { public static void main(String[] args) { SpringApplication.run(NacosConsumerApplication.class, args); } }
ConsumerController
@Slf4j @RestController @RequestMapping("/consumer") public class ConsumerController { @Resource private ProviderService providerService; /** * 基于Feign调用 * http://localhost:7002/consumer/hello */ @GetMapping("/hello") public String hello() { log.info("Feign invoke!"); return providerService.hello(); } }
基于Feign + Ribbon + Nacos的服务调用
基于Dubbo + Nacos的服务调用