应用架构的演变:
随着互联的发展,使用互联网的人群越来越多,软件应用的体量越来越庞大和复杂,传统的单体应用可能不足以支撑大数据量以及高并发场景,应用的架构也随之进行演变,从最开始的单体应用架构到分布式(SOA)架构再到今天比较火的微服务架构,以及服务网格架构。
微服务架构
什么是微服务:
微服务架构可以认识是在SOA架构上的一种发展,最早由“Martin Fowler”提出:
就目前而言,对于微服务业界并没有一个统一的、标准的定义(While there is no precise definition of this architectural style ) 。但通常而言,微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分成一组小的服务,每个服务运行独立的自己的进程中,服务之间互相协调、互相配合,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通(通常是基于 HTTP 的 RESTful API )
。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。
另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务。可以使用不同的语言来编写服务,也可以使用不同的数据存储。
我们从这段描述中可以看出,微服务架构和SOA架构很像,总体来说,微服务就是把单一应用进行细粒度的拆分成多个小(微)的服务相,每个服务独立运行,每个服务只需要专注一个业务即可,并且每个服务都可以有自己的数据库(分库),服务之间互协调配合完成整个系统的业务,我们用一个图来理解微服务架构:
微服务的特点:
-
由多个服务组成完整的系统
-
每个服务都是独立的,有自己的进程
-
服务之间使用HTTP协议通信
-
不同的服务可以使用不同的编程语言
-
不同的服务的数据库可以多样化选择
-
微服务是一个分布式系统
微服务的优缺点:
优点:
1.单个服务业务简单,方便开发和维护
2.服务之间无耦合,服务之间升级维护互不影响
3.有很强的拓展能力,业务量大的服务可以再次拆分,也可以进行集群部署,剔除服务也很方便
4.对现在流行的敏捷开发支持优化
5.具有更大的系统负载能力和容若能力(也就是集群)
缺点:
1.技术成本高,微服务架构比较复杂,技术成本高,开发人员也需要花费更多的时间学习技术
2.会有更多的网络消耗问题
3.部署麻烦
SpringCloud
SpringCloud的基本概念:
Spring cloud是一个基于Spring Boot实现的服务治理工具包
,用于微服务架构中管理和协调服务的
。Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等
,都可以用Spring Boot的开发风格做到一键启动和部署。通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。有了SpringCloud之后,让微服务架构的落地变得更简单。
SpringCloud常用组件:
当我们的项目采用微服务架构之后就会引发一些列的难题需要去解决,如众多微服务的通信地址应该如何管理,微服务之间应该使用何种方式发起调用,微服务故障该如何处理,众多微服务的配置文件如何集中管理等等,SpringCloud为这一系列的难题提供了相应的组件来解决,下面我们简单来理解一下SpringCloud最核心的几大组件如如下:
Netflix Eureka
当我们的微服务过多的时候,管理服务的通信地址是一个非常麻烦的事情,Eureka就是用来管理微服务的通信地址清单的,有了Eureka之后我们通过服务的名字就能实现服务的调用。
Netflix Ribbon\Feign : 客户端负载均衡
Ribbon和Feign都是客户端负载均衡器,它的作用是在服务发生调用的时候帮我们将请求按照某种规则分发到多个目标服务器上,简单理解就是用来解决微服务之间的通信问题。
Netflix Hystrix :断路器
微服务的调用是非常复杂的,有的时候一个请求需要很多的微服务共同完成,那么一旦某个服务发生故障,导致整个调用链上的微服务全都出现异常,甚至导致整个微服务架构瘫痪。Hystrix就是用来解决微服务故障,保护微服务安全的组件。
Netflix Zuul : 服务网关
zuul作为服务网关,我们可以把它看作是微服务的大门,所有的请求都需要经过zuul之后才能到达目标服务,根据这一特性,我们可以把微服务公共的是事情交给zuul统一处理,如:用户鉴权,请求监控等。
Spring Cloud Config :分布式配置
微服务架构中的服务实例非常的多,服务的配置文件分散在每个服务中,每次修改服务的配置文件和重新服务实例都是一个很麻烦的工作,Spring Cloud Config作为分布式配置管理中心就是用来统一的管理服务的配置文件。
Spring Cloud Bus : 消息总线
消息总线是在微服务中给各个微服务广播消息的一个组件,我们使用消息总线构建一个消息中心,其他微服务来接入到消息中心,当消息总线发起消息,接入的微服务都可以收到消息从而进行消费。
Spring Cloud sleuth :微服务链路追踪
当我们的应用采用微服务架构之后,后台可能有几十个甚至几百个服务在支撑,一个请求请求可能需要多次的服务调用最后才能完成,链路追踪的作用就是来监控维护之间的调用关系,让程序员方便直观的感受到一个请求经历了哪些微服务,以及服务的请求时间,是否有异常等。
Eureka介绍
什么是Eureka:
微服务的其中一个特点是服务之间需要进行网络通信,服务器之间发起调用时调用服务得知道被调用服务的通信地址,试问当微服务数量成百上千之多,程序员该如何管理众多的服务通信地址,对于随时新增加的微服务和下线的微服务,又应该如何去动态添加和删除这些微服务的通信地址呢?所以手工管理服务的通信地址是一件遥不可及的事情,我们需要借助一个强大的工具帮我们实现这一功能 - Eureka,同类型的组件还有 zookeeper,consul等
Eureka的工作原理:
1.服务注册
Eureka是一个服务注册与发现组件,简单说就是用来统一管理微服务的通信地址的组件,它包含了EurekaServer 服务端(也叫注册中心)和EurekaClient客户端两部分组成,EurekaServer是独立的服务,而EurekaClient需要集成到每个微服务中。
微服务(EurekaClient)在启动的时候会向EurekaServer提交自己的服务信息(通信地址如:服务名,ip,端口等),在 EurekaServer会形成一个微服务的通信地址列表存储起来。 --- 这叫服务注册
2.服务发现
微服务(EurekaClient)会定期(RegistryFetchIntervalSeconds:默认30s)的从EurekaServer拉取一份微服务通信地址列表缓存到本地。当一个微服务在向另一个微服务发起调用的时候会根据目标服务的服务名找到其通信地址,然后基于HTTP协议向目标服务发起请求。---这叫服务发现
3.服务续约
另外,微服务(EurekaClient)采用定时(LeaseRenewalIntervalInSeconds:默认30s)发送“心跳”请求向EurekaServer发请求进行服务续约,其实就是定时向 EurekaServer发请求报告自己的健康状况,告诉EurekaServer自己还活着,不要把自己从服务地址清单中剔除掉,那么当微服务(EurekaClient)宕机未向EurekaServer续约,或者续约请求超时,注册中心机会从服务地址清单中剔除该续约失败的服务。
4.服务下线
微服务(EurekaClient)关闭服务前向注册中心发送下线请求,注册中心(EurekaServer)接受到下线请求负责将该服务实例从注册列表剔除
EurekaServer实战
一.多模块项目结构
1.搭建项目结构
springcloud-parent //父项目
pom.xml //父项目的pom
springcloud-eureka-server-1010 //注册中心EurekaServer
springcloud-user-server-1020 //用户服务EurekaClient ,提供者
springcloud-order-server-1030 //订单服务EurekaClient ,消费者
2.父项目管理依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xl</groupId>
<artifactId>springcloud-parent-netfilx</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>service-order</module>
<module>service-user</module>
<module>service-eureka</module>
<module>common-pojo</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--
pom: 父项目: 用来管理子项目和子项目可能用到的jar
jar:项目打成jar包
war:项目打包成war包
maven-plugin:maven插件
-->
<packaging>pom</packaging>
<!--1.管理 SpringBoot的jar包-->
<!--SpringBoot-->
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<!--2.管理 SpringCloud的jar包
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--3.这里是所有子项目都可以用的jar包-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
二:搭建EurekaServer
1.导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xl</groupId>
<artifactId>springcloud-parent-netfilx</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>service-eureka</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
2.主配置类
package com.xl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaStart {
public static void main(String[] args) {
SpringApplication.run(EurekaStart.class);
}
}
3.application.yml配置文件
server:
port: 1000 #端口
eureka:
instance:
hostname: localhost #主机
client: #客户端配置
registerWithEureka: false #EurekaServer自己不要注册到EurekaServer自己 ,只有EurekaClient才注册
fetchRegistry: false #EurekaServer不要拉取服务的通信地址列表 ,只有EurekaClient才拉取地址列表
serviceUrl: #注册中心的注册地址
defaultZone: http://localhost:1000/eureka/ #http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false #关闭Eureka服务的自我保护机制
4.启动测试
启动1000端口工程,浏览器直接访问http://localhost:1000,出现下面界面就说明EurekaServer集成成功
EurkaClient实战-订单服务
1.导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xl</groupId>
<artifactId>springcloud-parent-netfilx</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>service-order</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.xl</groupId>
<artifactId>common-pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
</project>
2.主配置类
@SpringBootApplication
public class OrderStart {
public static void main(String[] args) {
SpringApplication.run(OrderStart.class);
}
}
3.application.yml配置
server:
port: 1100 #端口
eureka:
instance:
hostname: localhost #主机
lease-renewal-interval-in-seconds: 10 #设置10秒拉取一次
prefer-ip-address: true #使用ip地址注册到注册中心
instance-id: service-order:1100 #直接指定注册的实例ID
client: #客户端配置
serviceUrl: #注册中心的注册地址
defaultZone: http://localhost:1000/eureka/
spring:
application:
name: service-order #使用应用名字注册
EurekaClient实战-用户服务
1.导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xl</groupId>
<artifactId>springcloud-parent-netfilx</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>service-user</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.xl</groupId>
<artifactId>common-pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.主配置类
package com.xl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserStart {
public static void main(String[] args) {
SpringApplication.run(UserStart.class);
}
}
3.application.yml配置
server:
port: 1200 #端口
eureka:
instance:
hostname: localhost #主机
instance-id: service-user:1200 #直接指定注册的实例ID
lease-renewal-interval-in-seconds: 10 #设置10秒拉取一次
prefer-ip-address: true #使用ip地址注册到注册中心
client: #客户端配置
serviceUrl: #注册中心的注册地址
defaultZone: http://localhost:1000/eureka/
spring:
application:
name: service-user #使用应用名字注册
RestTemplate服务通信
流程说明:
目前除了Eureka Server以外我们的微服务有订单服务springcloud-order-server-1030,和用户服务springcloud-user-server-1020 , 我们就用这两个服务来演示微服务的通信,他们的调用关系应该是:浏览器 -> 订单服务 -> 用户服务,如下图:
这里订单服务通过RestTemplate向用户服务发起调用,目的是要获取到用户服务返回的User对象,最终是需要浏览器获取到User。
编码实战
搭建公共模块
springcloud-parent
springcloud-eureka-server-1010
springcloud-order-server-1030
springcloud-user-common //公共User模块
springcloud-user-server-1020
在公共模块下创建User对象
package com.xl.domain;
public class User {
private Long id;
private String name;
private Integer sex;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", sex=" + sex +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public User() {
}
public User(Long id, String name, Integer sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
}
用户和订单依赖User模块
修改工程springcloud-order-server-1100 , springcloud-user-server-1200在他们的pom.xml都导入springcloud-user-common 模块
<dependency>
<groupId>com.xl</groupId>
<artifactId>common-pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
用户服务返回User
修改 springcloud-user-server-1200 工程,编写controller,返回User对象
package com.xl.controller;
import com.xl.domain.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Value("${server.port}")
private String port;
@GetMapping("/getUserById/{id}")
public User getUser(@PathVariable("id")Long id){
return new User(id,"xxx"+port,18);
}
}
订单服务获取User
在订单服务中需要使用RestTemplate调用用户服务,我们需要把RestTmplate配置成Bean方便使用(当然也可以不创建Bean,用的时候直接new对象也可以) ,修改工程springcloud-order-server-1200在主配置类配置RestTemplate如下:
package com.xl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class OrderStart {
public static void main(String[] args) {
SpringApplication.run(OrderStart.class);
}
@Bean
@LoadBalanced //开启RestTemplate的负载均衡能力
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
测试服务通信
依次启动Eureka Server注册中心(不启动也行) , 用户服务 ,订单服务 , 浏览器访问订单服务:http://localhost:1100/getUserById/1 , 页面返回结果如果是一个对象则成功
Ribbon客户端负载均衡
基本概念
Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端负载均衡算法
。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试
等。简单的说,Ribbon是一个客户端负载均衡器,Ribbon可以按照负载均衡算法(如简单轮询,随机连接等)向多个服务发起调用
(正好可以解决上面的问题),我们也很容易使用Ribbon实现自定义的负载均衡算法
。
消费者Order-server集成Ribbon
1.导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2.开启负载均衡
修改RestTemplate的Bean的定义方法,加上Ribbon的负载均衡注解@LoadBalanced赋予RestTemplate有负债均衡的能力。
package com.xl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class OrderStart {
public static void main(String[] args) {
SpringApplication.run(OrderStart.class);
}
@Bean
@LoadBalanced //开启RestTemplate的负载均衡能力
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
3.修改Controller调用方式
在之前的案例中,我们调用用户服务是通过用户服务的主机加上端口“localhost:1200”的调用方式,现在我们把 “localhost:1200” 修改为 用户服务的服务名 。底层会通过服务发现的方式使用Ribbin进行负载均衡调用。
package com.xl.controller;
import com.xl.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getUserById/{id}")
public User getUser(@PathVariable("id")Long id){
String url="http://SERVICE-USER/getUserById/"+id;
return restTemplate.getForObject(url, User.class);
}
}
4.进行测试即可
分别启动EurekaServer注册中心 ,启动两个UserServer用户服务,启动OrderServer订单消费者服务,浏览器访问订单服务:http://localhost:1100/getUserById/1 ,发送多次请求。
观察响应的结果中的端口变化 - 端口会交替出现1200,,1300我们可以推断出Ribbon默认使用的是轮询策略。