什么是微服务?
很高兴与各位读者朋友见面,本篇内容是微服务系列的首篇文章,也是本人后期撰写原创微服务架构文章的开始,希望自己的一些经验分享,能够助力于各位读者朋友在微服务架构之路上顺风顺水。作为首篇文章,我会以最为基础的概念为开始,介绍下什么是微服务及其与SOA服务架构的异同;然后,会以一个整体的微服务架构图及详解作为第二部分内容;最后,会搭建一个基础入门的注册中心(HA)服务,及注册一个微服务在该注册中心作为本篇文章的收尾。
一、什么是微服务
业界目前没有一个对微服务的标准定义,就像NoSql,我们谈论了很久,虽知其大致含义,也可以根据不同的应用场景来选择不同的NoSql数据库,但我们比较难对其下个准确的定义。不过这里,我们要给微服务下个基本的定义:将单一应用划分为一组小的服务,服务之间互相协调、配合,为用户提供最终价值的架构体系。而每个服务运行在独立的进程中,服务之间采用轻量级通信(通常是基于HTTP协议的Rest API),并且每个服务都承载着独立的业务单元责任,能够被独立的部署和发布。
1、关键词“微”
微服务中的“微”,指的是粒度的意思,那么这个微的粒度到底有多微,这是所有设计微服务架构的同学所必须面对的问题。有的同学认为,服务划分的越小越好,比如:修改密码功能可独立作为一服务,其实这种划分没有对与错之说,而是需要根据用户使用度及系统资源消耗来均衡划分,正如上面的修改密码功能,我建议将其与用户相关的服务合并,因为它的使用度不高,再如:用户注册和登录,我建议将其分别作为一个独立服务,因为他们的使用度非常高,并且是用户访问的入口,非常重要。相信很多同学,会将登录和注册放入一个服务,那么试问如果因登录功能导致该服务崩溃,那么注册也就不能使用了,反之不存在这个问题。
那么,我们该如何控制好这个微粒度昵?下面我提供如下几个在设计微服务划分时可考虑的因素,如下图所示:
如上图所示,其中的业务单元和用户体验因素,在上面的文字例子描述中已经体现,业务单元,如:注册、登录等服务;用户体验,如:我们服务不能因为注册的挂掉,而导致登录也不能使用,这是不允许的用户体验;辅助工具,其实是那些通用的工具,根据其使用频率来独立划分,如:短信,邮件等,建议均单独划分,保证高使用度;团队共识,也好理解,因为实际项目中,会存在成百上千的服务,也需要由不同的团队来负责,为了避免造轮子,以及各业务间的功能关联,往往在设计关联服务设计时,需要召集相关团队共同参与相关服务的讨论,并做到有据可循。
2、独立存在度
这里的独立存在指的是在程序交付过程中,在开发、测试及发布部署的独立。在传统的程序交付中,众多程序员共同参与到同一个程序开发中,开发完成后,将测试版本程序交付给测试人员进行测试,测试验收通过后,开发人员进行程序构建编译,最后交给运维人员或开发人员部署发布程序到服务器端,一般都是如下的形式流程:
缺点:
A、多开发人员,共同参与一个程序开发,导致程序挂掉几率非常高;
B、测试人员在测试时,不容易测试程序各个细节点,容易遗漏测试点;
C、在程序构建时,构建复杂度较大,构建时间较长;
D、部署发布程序时,应用程序会短暂停掉,对外不提供服务;
在微服务程序交付时,解决了传统程序交付中的很多问题,除了将程序划分为更小的服务,而各服务均可视为程序进程,均可独立的开发、测试和部署发布,因为其独立性,部分服务在发布时,其它已运行服务可继续提供对外的功能,所以微服务的交付的流程:
当然,凡事有利必有弊,因为在交付过程中,由原来的单一程序的交付,现在变成了多个独立存在的微服务的交付,势必会带来运维上的繁琐,不过不必担心,随着互联网的高速发展,自动化技术的逐渐成熟,持续集成DevOps方法论的出现,以及容器云的诞生,都能很好地解决这些问题。
3、进程的隔离
在单模块架构中,整个程序运行在同一个程序进程中,即使我们将应用划分为多层模块,但这只是逻辑上的划分,而并不是物理上的隔离。经过对不同层次的代码实现,再编译、打包及部署之后,如果不考虑水平拓展情况,如:负载均衡等,应用的所有功能均运行在某个节点的同一个进程中。
另外,因为所有功能运行在同一个进程中,如果在对应用部署发布时,必须停掉当前运行的进程,部署完成后,再重新启动程序进程,所以无法做到功能独立部署。同时,为了提高代码的重用性和可维护性,我们一般会将重复的功能代码抽离出来,单独封装,也就是我们熟悉的组件(可独立升级和部署的程序块集,或叫组件库)。虽然程序被组件化,但在实际运行时,各个组件库最终都会被加载到同一个进程中,如下所示:
对于微服务而言,应用程序由多个服务组成,每个服务运行在单一进程中,并且各个服务可独立开发、测试、构建和部署运行,是高度自治的业务功能单元,具体如下所示:
4、轻量级通信
现阶段的轻量级通信,一般选用HTTP作为通信协议,而选用Rest来实现服务之间的轻量级协调通信。对于数据载体格式,推荐使用JSON和XML格式承载数据,在移动互联网领域,选用JSON作为载体更为多见。另外,轻量级通信机制,一般与语言无关、平台类型无关,比如:采用Java、Node或PHP等语言开发的模块之间,可以使用语言无关和平台无关的方式通信。
当然,服务间的通信也可以采用我们熟悉的RPC机制实现,使消费者端像调用本地接口一样,调用远端的API接口,但是其缺点在于其对语言和平台有较强耦合度,比如:采用Java RMI通信协议,需要两端必须使用基于JVM的开发语言实现。
5、容器云引入
需要说明下,这里提到的“容器”指的是docker容器,它是一个开源的容器引擎,可以将应用和基础设施隔离开来,并能够将基础设施当作程序一样管理维护,可更快的打包、测试及部署服务程序,并可缩短从开发到部署运行的整个周期。
官方的定义,Docker是以docker容器为资源分割和调度的基本单位,承载了整个软件运行时环境,为开发者、系统管理员设计的,用来构建、发布及运行分布式程序的平台,它也是个跨平台、可移植、且简单易用的容器解决方案。另外,Docker是基于Go语言开发,并遵循Apache2.0协议,可在容器内部快速自动化部署程序,并通过操作系统的namespace和cgroups等技术,来实现资源的隔离和安全保障。
因为本篇文章并非是介绍docker容器的,所以这里只提及其对微服务的发展所做的贡献,最后续文章中会继续介绍它。
6、与SOA异同
我们知道,SOA是一种面向服务的技术架构模型,也是一种组件模型,它将应用程序的不同功能单元(服务),通过这些服务间定义好的接口和契约进行通信。其主要有两个角色,分别为服务提供者(Provider)和服务消费者(Consumer),更多关注的是如何通过分布式、单独维护及部署的软件组件的集成,来组成应用程序。并且通信内容可以是简单的数据传递、也可是多个服务间的协调连接等,比如:创建账户和登录等。
微服务架构,在某种程度上可视为SOA架构的进一步抽象。其是开发软件、网络及移动应用作为独立服务套件的特殊方式,这些服务的创建仅限于一个特定的业务功能,如:用户管理、用户角色、电子商务车、搜索引擎、社交媒体登录等。它们是完全独立的,也就是说它们可以写入不同的编程语言并使用不同的数据库。集中式服务管理几乎不存在,微服务使用轻量级HTTP、REST等。
咋一看,它们好像没什么区别,然而,它们是两种不同的架构模型。下面具体对比下它们的异同:
所以,不能简单地说一种架构比另一种架构好,这要取决于正在构建的应用程序的场景。SOA适合需与许多其他应用程序集成的大型企业应用程序,也就是说,小型应用程序不适合SOA架构,因它们不需要消息中间组件。微服务架构,在另一方面,是更适合于较小和良好的分割,适合基于Web的分布式系统。如果你正在开发移动或Web应用程序,那么微服务可以提供更大的功能控制权。因此,我们可以得出结论,它们服务于不同的业务场景,其微服务和SOA是不同类型的技术体系结构。
二、微服务架构图
这里向读者朋友介绍的内容是目前较主流的微服务架构体系,涵盖了注册中心、控制路由、配置中心、队列总线、服务容错、服务统计、服务提供者及服务的消费者,当然,在生产环境中,这些标配的组合服务均须做HA(除了提供者和消费者,需要根据实际情况而定),具体如下图所示:
下面具体介绍下各个服务的作用,如下所示:
1、注册中心
注册中心主要是用来登记服务信息的存储中心,主要用来注册和发现已注册登记的服务,并维护它们间的通信关系。目前主流选用Eureka、Consul及Zookeepr作为注册中心,它们都有各自的特点和适用场景,具体在后续注册中心专题文章进行介绍。那么,登记的服务信息到底是什么?实际上,登记的服务程序信息一般为能够找到服务的信息,比如:IP地址、端口号、服务名及通信协议等能够唯一标志服务的数据,不论选用上面介绍的哪种技术实现,都离不开这些信息,不同的主要在选用的实现技术在集群选举和故障恢复上的异同。
2、API网关
之所以称为“API网关”,主要是因为在微服务架构体系中,服务间的通信大多选用Rest API或RPC来实现,而往往它们的实现形式也就是API,只不过所遵循的通信协议和极限性能不同罢了。那么,微服务架构中的API网关能够做什么事昵?其实,它可以作很多事,比如:控制不论是对外还是对内的服务间的统一入口和路由转发,并能方便地监控服务间的调度、限流及容错回退等功能。
那么,读者会问该如何实现?实现的方式很多种,我们可以完全自己动手写,不过此种方式对技术人员经验有所要求,应该因人而异,比如:我们需要处理服务地址存储和路由逻辑、需要处理容错回退,路由性能及监控和限流等;而我推荐的是借助一些开源成熟的技术框架来实现,比如:可以选用Netflix系列中的Zuul,也可以选用Web服务器,如:Nginx作为代理路由服务器。
3、服务提供者
服务提供者,承载着对外提供的业务服务单元,一般仅对服务消费者开放,而实现方式没有特殊的要求,只要能够合理的实现业务功能即可,比如:可选用传统的MVC架构,或仅仅是一个实现主类。
4、服务消费者
服务消费者,负责对接和消费服务提供者所提供的服务单元,它的实现方式也没有特殊的要求,与提供者类似,但不同的是其负责与前端交互,负责将从提供者获取的数据和行为,赋予给与用户打交道的前端交互,比如:我们熟知的浏览器等。
5、配置中心
配置中心,一个负责统一配置全局参数的地方,做到不需要重新打包、发布版本才能更新数据,而仅仅通过改变统一配置文件,服务就可以动态更新最新的属性数据,大大提高程序服务的灵活性,避免繁琐的运维部署工作。而实现配置中心的方式也有很多种,如果选用开源技术实现,那么推荐用Cloud Bus,它一般会与消息中间件,如:RabbitMQ、ActiveMQ、RocketMQ、Kfka等结合使用,通过集成消息中间件,来实现负载群发配置变更信息给相关的服务同步数据,同时它也负责与版本存储连接,如:Git/Svn等,通过监听它们的变更,来及时通知消息消息广播消息。
6、消息总线
消息总线,在上面已经介绍,主要工作是能够负载均衡地群发事件消息数据给所有相关的服务,使它们及时更新最新属性,其一般选型均为消息队列系统实现,当然,这并非是固定的标准,选用如Redis亦可。
7、服务跟踪
微服务架构中,跟踪服务调用运转的情况很重要,因为通过分析服务调用的频次和时间作为参考,有助于我们预知服务的使用度和延迟问题所发生的时机。细心的读者,会发现服务跟踪与日志跟踪有些类似,没错,传统的架构中,我们为了定位问题,通常借助ELK技术栈来跟踪和记录感兴趣的日志信息,虽然,它们类似,但日志跟踪的数据分析较困难,所记录的数据大多无规律,而服务跟踪,则从服务粒度出发,从功能整体来记录和分析程序问题,使问题定位更加清晰和高效,实际上,服务跟踪也是一种日志跟踪。如果读者选用Spring Cloud栈搭建微服务架构,则可选用Cloud Sleuth,它往往与Zipkin等集成,来实现可视化的跟踪分析;如果采用的是非Spring Cloud,如基于dubbo搭建微服务的化,因其本身并未提供类似Cloud Sleuth的开源栈,所以需要读者自行实现,可选用高效的,且支持持久化的中间件实现,如:Redis或MongoDB等。
8、服务容错
服务容错,或称之为服务降级。我们知道,在微服务环境中,服务往往存在与不同的服务器节点,甚至是不同的网络环境,那么服务间的交互,势必会经常发生错误,如:网络抖动、服务bugs等。那么,如何实现当某个服务故障时,关联的服务程序也能正常对外提供服务昵?答案是,引入一个提高用户体验或与故障服务类似的服务作为降级服务后的服务,这就是服务降级或容错。这里以Spring Cloud方式搭建架构,那么可选用Cloud Hystrix,利用其提供的容错断路器来实现降级,并且往往它需要与API路由集成(实际上,Zuul已经集成了它),因为后者是几乎所有服务的访问控制的入口。
三、入门服务例子
下面我们来实际搭建下微服务项目,就先以实现上面第二部分架构图中的,关于注册中心、服务提供者及服务消费者三个基础核心部分为开始,而后续文章中会陆续介绍其它架构部分的理论及实践,敬请读者耐心等待。那么,下面直接上代码按步骤介绍下吧!
1、环境准备
针对这个例子,我推荐的技术选型是用Spring Boot来作为整个微服务架构的基础实现技术,采用Spring Cloud技术栈中的Eureka 1.x作为注册中心,而本例子并无实际功用,服务提供者和消费者仅利用Spring Boot提供的Spring MVC功能,实现了Web访问能力。另外,虽然官方提示,Eureka 2.x系列不再维护更新,但Eureka 1.x系列已经能够稳定地提供我们需要的服务注册和发现的绝大多功能,所以这里先介绍下如何采用Eureka 1.x实现注册中心。
PS:
本系列文章不介绍相关的基础知识,建议读者先对上面提到的Spring Boot和Spring Cloud有所接触。另外,下面我仅介绍下各个部分的关键点,而相关的代码中也已有注释,就不再赘述,读者一定要最新阅览!
2、注册中心
A、pom.xml
xml version="1.0" encoding="UTF-8"?>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
这里下载依赖了spring-cloud-starter-eureka-server,就是上面提到的Eureka 1.x系列,它支持Server和Client两种模式,前者声明自己为注册中心服务端,来管理注册的服务;而后者为客户端,将自身服务注册到服务端中,具体在下面提供者和消费者均有提到!
同时,选用spring-boot-starter-security来实现访问的账号和密码的安全处理,需要配置开启才能生效,具体在下面会提到!
B、application.yml
spring:
application:
name: sample-registry
profiles:
active: eureka-ha1
security:
basic:
enabled: true
user:
name: cwteam
password: 123456
---
server:
port: 8761
spring:
profiles: eureka-ha1
eureka:
instance:
hostname: eureka-ha1
prefer-ip-address: false # 使用IP通信,如果defaultZone使用域名形式注册,那该处必须为flase
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
client:
registerWithEureka: true # 注册中心的高可用,需要开启下面两个参数,彼此互相注册
fetchRegistry: true
serviceUrl:
defaultZone: http://cwteam:123456@eureka-ha2:8762/eureka/
healthcheck:
enabled: true
server:
enable-self-preservation: false # 建议在生产环境开启
---
server:
port: 8762
spring:
profiles: eureka-ha2
eureka:
instance:
hostname: eureka-ha2
prefer-ip-address: false # 使用IP通信,如果defaultZone使用域名形式注册,那该处必须为false
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
client:
registerWithEureka: true # 注册中心的高可用,需要开启下面两个参数,彼此互相注册
fetchRegistry: true
serviceUrl:
defaultZone: http://cwteam:123456@eureka-ha1:8761/eureka/
healthcheck:
enabled: true
server:
enable-self-preservation: false # 建议在生产环境开启
这里是服务的全局配置文件,采用了yaml格式设置属性,也可采用properties格式,主要配置注册中心的名字、对外提供的端口号、安全处理,比如:security相关配置,另外,这里使用了‘---’分割了两个配置段,分别对应eureka-ha1和eureka-ha2两个实例,这是因为实际构建时,须要对注册中心做HA,一般可通过java命令或是开发工具IDE的参数设置来实现,比如:idea,可设置Program arguments参数内容,如下所示:--spring.profiles.active=eureka-ha1,另外实例则为对应的--spring.profiles.active=eureka-ha2即可。
PS:
eureka-ha1和eureka-ha2须要hosts设置,这里不再赘述!
C、Application.java
// 组合注解,整合了@Configuration、@EnableAutoConfiguration及@ComponentScan
// 另外,该程序文件必须放在根目录下,否则外部无法访问.
@SpringBootApplication
// 声明自己是一个Eureka Server
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
需要特别说明的是,这里及下面的服务提供者和消费者的程序启动,均运行在由Spring Boot内嵌的轻量级Tomcat容器中,而实际生产环境中,一般选用容器docker来作为运行环境,这点请读者一定要弄清楚,而docker技术会在后续文章中详细介绍。
3、服务提供者
A、pom.xml
4.0.0
com.cwteam
sample-provider
1.0.0
jar
sample-provider
micro service of provider
org.springframework.boot
spring-boot-starter-parent
1.5.15.RELEASE
UTF-8
UTF-8
1.8
Camden.SR5
1.2.30
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
LATEST
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
这里的spring-boot-starter-web是用来支持Spring Mvc的,spring-cloud-starter-eureka则为上文提到的Eureka的Client模式的支持,而spring-boot-starter-actuator则是用来监控服务的健康情况。
B、application.yml
server:
port: 8000
### 服务在注册中心显示及服务发现的应用名字
spring:
application:
name: sample-provider
### 将自身服务注册到服务注册中心,供被发现
eureka:
client:
serviceUrl:
defaultZone: http://cwteam:123456@eureka-ha1:8761/eureka/,http://cwteam:123456@eureka-ha2:8762/eureka/
### 服务在注册中心中的健康状态,以便注册中心定期销毁服务
healthcheck:
enabled: true
instance:
### 在注册中心显示服务的IP地址与端口号
prefer-ip-address: true
### 自定义服务实例以IP:PORT形式来显示
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
注意到这里的defaultZone了吗?,它是Eureka所支持的分区配置,这里采用默认的分区,意思是将自己注册到注册中心集群的所有实例中。
C、User.java
public class User {
private String id;
private String account;
private String password;
private String email;
private String phone;
setter … getter
}
D、ProviderController.java
@RestController
@RequestMapping("/sample")
public class ProviderController {
@GetMapping(value="/user/find/{id}",produces="application/json;charset=utf-8")
public User findUserById(@PathVariable String id) {
// 这里是模拟生成数据
User user = new User();
user.setId(id);
user.setAccount("cwteam");
user.setPassword("123456");
user.setEmail("[email protected]");
user.setPhone("11111111111");
// 其它逻辑略...
return user;
}
}
E、Application.java
// 组合注解,整合了@Configuration、@EnableAutoConfiguration及@ComponentScan
// 另外,该程序文件必须放在根目录下,否则外部无法访问.
@SpringBootApplication
// 声明自己是一个Eureka Client服务
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4、服务消费者
A、pom.xml
4.0.0
com.cwteam
sample-consumer
0.0.1
jar
sample-consumer
micro service of consumer
org.springframework.boot
spring-boot-starter-parent
1.5.15.RELEASE
UTF-8
UTF-8
1.8
Camden.SR5
1.2.30
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
LATEST
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
与服务提供者同理,所以不再赘述。
B、application.yml
server:
port: 8010
### 服务在注册中心显示及服务发现的应用名字
spring:
application:
name: sample-consumer
### 将自身服务注册到服务注册中心,供被发现
eureka:
client:
serviceUrl:
defaultZone: http://cwteam:123456@eureka-ha1:8761/eureka/,http://cwteam:123456@eureka-ha2:8762/eureka/
### 服务在注册中心中的健康状态,以便注册中心定期销毁服务
healthcheck:
enabled: true
instance:
### 在注册中心显示服务的IP地址与端口号
prefer-ip-address: true
### 自定义服务实例以IP:PORT形式来显示
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
### 开启服务调用负载均衡,均衡策略为随机方式
sample-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
这里值得提到的是Ribbon的软负载均衡,这里设置为随机负载策略,该Ribbon已被集成到Eureka中,所以不需要单独引入Netflix的Ribbon,而Ribbon主要是提供负载均衡算法,因篇幅有限,详细的见后续文章!
C、User.java
与服务提供者相同
D、ConsumerController.java
@RestController
@RequestMapping("/sample")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping(value="/user/find/{id}",consumes = "application/json;charset=utf-8")
public User findUserById(@PathVariable String id) {
ResponseEntity resp = restTemplate.exchange(
"http://sample-provider/sample/user/find/" + id,
HttpMethod.GET,
null,
User.class
);
return resp.getBody();
}
}
这里使用了RestTemplate模版API,使用其exchange来实现服务间的HTTP通信(Rest API)。另外,读者应该发现了,消费者在消费服务时,采用的是http://sample-provider/…,而非具体的host+port形式,这样更加灵活,即使所对应的服务的IP+port改变了,这里也不需要修改,之所以能做到这点,全是注册中心的服务发现的功劳。
E、Application.java
@SpringBootApplication
// 声明自己是一个Eureka Client服务
@EnableDiscoveryClient
public class Application {
@Bean
@LoadBalanced // 开启Ribbon的负载均衡,默认Eureka已支持
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5、结果验证
分别启动Sample-Registry两个实例(HA)、Sample-provider及Sample-consumer,具体截图如下:
上图为首次访问注册中心时弹出的安全校验弹窗,也就是上面提到的security配置的作用。
上图为eureka-ha1实例注册中心的注册情况。
上图为eureka-ha2实例注册中心的注册情况。
上图是访问服务消费者时,其从服务提供者获取的数据,访问地址如下:
http://localhost:8010/sample/user/find/10001
源码地址:
https://gitee.com/cwteam_dev/micro-service/tree/master/micro-part01
好了,本篇文章介绍到这里,下一篇文章我继续介绍下几种主流注册中心的原理、优缺点及使用,比如:Eureka、Consul及Zookeeper。
作者公众号: