应用架构的演变
随着互联的发展,使用互联网的人群越来越多,软件应用的体量越来越庞大和复杂,传统的单体应用可能不足以支撑大数据量以及高并发场景,应用的架构也随之进行演变,从最开始的单体应用架构到分布式(SOA)架构再到今天比较火的微服务架构,以及服务网格架构。
一、SpringCloud是什么?
Spring cloud是一个基于Spring Boot实现的服务治理工具包
,用于微服务架构中管理和协调服务的
。Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等
,都可以用Spring Boot的开发风格做到一键启动和部署。通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。有了SpringCloud之后,让微服务架构的落地变得更简单。
简化
springcloud是一套专门解决微服务的方案,包含(注册中心、配置中心、网关、熔断器、feign(网络传输))bus 消息中线 http通信
SpringCloud常用组件
Netflix Eureka 注册中心
注册中心,就是用来管理所有服务的地址 (IP+端口 = 地址)
Ribbon\Feign客户端负载均衡
作用 负载均衡的,就是帮助我们选择调用服务集群的那个服务
负载均衡的算法 轮询 随机 权重
Hystrix 熔断器(豪猪)
作用 就是为了防止服务雪崩,可以帮助我们快速响应,返回兜底数据,可以对服务进行降级
Zuul 网关
作用 是所有服务的大门,我们可以在网关做鉴权等操作(所有的请求都会经过网关)
Config 配置中心
作用 是管理所有服务的配置的组件,后面使用配置中心改了配置动态刷新(application.yml)
什么是Eureka
微服务其中一个特点是服务之间需要进行网络通信,服务器之间发起调用时调用服务得知道被调用众多的服务通信地址,就是用来管理所有服务的地址 (IP+端口 = 地址)
Eureka的工作原理
服务注册
Eureka是一个服务注册与发现组件,简单说就是用来统一管理微服务的通信地址的组件,它包含了EurekaServer 服务端(也叫注册中心)和EurekaClient客户端两部分组成,EurekaServer是独立的服务,而EurekaClient需要集成到每个微服务中。
简单说:
微服务(EurekaClient)在启动的时候会向EurekaServer提交自己的服务信息(通信地址如:服务名,ip,端口等),在 EurekaServer会形成一个微服务的通信地址列表存储起来。 — 这叫服务注册
服务发现
微服务(EurekaClient)会定期(RegistryFetchIntervalSeconds:默认30s)的从EurekaServer拉取一份微服务通信地址列表缓存到本地。当一个微服务在向另一个微服务发起调用的时候会根据目标服务的服务名找到其通信地址,然后基于HTTP协议向目标服务发起请求。—这叫服务发现
服务续约
微服务(EurekaClient)采用定时(LeaseRenewalIntervalInSeconds:默认30s)发送“心跳”请求向EurekaServer发请求进行服务续约,定时告诉EurekaServer自己还活着,不要从服务地址清单中剔除掉,那么当微服务(EurekaClient)宕机未向EurekaServer续约,或者续约请求超时,注册中心机会从服务地址清单中剔除该续约失败的服务。
服务下线
微服务(EurekaClient)关闭服务前向注册中心发送下线请求,注册中心(EurekaServer)接受到下线请求负责将该服务实例从注册列表剔除
二、EurekaServer(注册中心)和RestTemplate(HTTP服务通信)实战
思路步骤
- 多模块项目结构
1.1父子项目
1.2 添加父项目管理依赖 - 搭建集成EurekaServer
2.1: 导入依赖
2.2: 注册中心启动类
2.3: application.yml配置文件
2.4: 启动测试 - 搭建EurekaClient用户服务
3.1: 导入依赖
3.2: 注册中心启动类
3.3: application.yml配置
3.4: 测试EurekaClient - 搭建Eureka Client订单服务
4.1: 导入依赖
4.2: 注册中心启动类
4.3: application.yml配置
4.4: 测试EurekaClient
多模块项目结构
项目结构
Java的普通Maven项目
springcloud-parent //父项目
springcloud-eureka-server-1010 //注册中心EurekaServer
springcloud-user-server-1020 //用户服务EurekaClient ,提供者
springcloud-order-server-1030 //订单服务EurekaClient ,消费者
父项目管理依赖
SpringCloud的Jar包管理参照文档:
https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/multi/multi__quick_start.html#_client_side_usage
SpringCloud是基于SpringBoot的,所以两者的jar包都需要导入,需要注意的是SprinbCloud的版本需要和SpringBoot的版本兼容
<!--公共的一些配置 都是1.8,可以不配-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
搭建集成Eureka Server
导入依赖
<dependencies>
<!--spring-cloud-starter-netflix-eureka-server -->
<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>
注册中心启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/*
* 注册中心启动类
* @EnableEurekaServer : 开启EurekaServer服务端
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp{
public static void main( String[] args ){
SpringApplication.run(EurekaServerApp.class);
}
}
application.yml配置文件
server:
port: 1010 #端口
eureka:
instance:
hostname: 127.0.0.1 #主机 或者(localhost)
#一般使用默认
#lease-expiration-duration-in-seconds: 30 #服务剔除时间
#lease-renewal-interval-in-seconds: 10 # 发送心跳的时间
client: #客户端配置
registerWithEureka: false #EurekaServer自己不要注册到EurekaServer自己 ,只有EurekaClient才注册
fetchRegistry: false #EurekaServer不要拉取服务的通信地址列表 ,只有EurekaClient才拉取地址列表
serviceUrl: #注册中心的注册地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #http://localhost:1010/eureka/
server:
enable-self-preservation: false #关闭自我保护警告
renewal-percent-threshold: 0.85 # 自我保护的阈值
启动测试
启动springcloud-eureka-server-1010工程,浏览器访问 http://localhost:1010 ,出现如下界面代码EurekaServer集成成功:
搭建EurekaClient用户服务
导入依赖
<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>
</dependencies>
注册中心启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/
* 用户的启动类
* @EnableEurekaClient: 标记该应用是 Eureka客户端
*/
@SpringBootApplication
@EnableEurekaClient
public class UserServerApp {
public static void main(String[] args) {
SpringApplication.run(UserServerApp.class);
}
}
application.yml配置
server:
port: 1020
#使用IP进行注册
spring:
application:
name: user-server
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:1010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: user-server:1020 #实例ID port + name
测试EurekaClient
启动springcloud-eureka-server-1010 , 启动springcloud-user-server-1020 , 浏览器再次访问http://localhost:1010
搭建Eureka Client订单服务
导入依赖
同用户服务一样,省略…
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
注册中心启动类
同用户服务一样,省略…
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/
* 订单的启动类
* @EnableEurekaClient: 标记该应用是 Eureka客户端
*/
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApp{
public static void main( String[] args ){
SpringApplication.run(OrderServerApplication1030.class);
}
}
application.yml配置
订单服务除了服务名,端口和用户服务不一样,其他的都一样,如下:
server:
port: 1030
spring:
application:
name: order-server
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:1010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: order-server:1030 #实例ID
测试
启动订单服务,访问Eureka Server的监控界面:http://localhost:1010,可以看到订单服务也注册进去了。
到这里Eureka服务注册与发现案例已经完成,服务端和客户端的搭建都相对简单,一般都是导个包,打个标签,配置文件都搞定了,作为一个合格的程序员,我们不能光停留在用的层面,它的一些重要工作方式与思想也是需要我们去掌握。
RestTemplate服务通信
如何实现简单版的服务通信
目前除了EurekaServer以外我们的微服务有订单服务springcloud-order-server-1030,和用户服务springcloud-user-server-1020, 我们就用这两个服务来演示微服务的通信,他们的调用关系应该是:浏览器 -> 订单服务 -> 用户服务,如下图:
思路步骤
- 搭建公共模块
- 导入公共模块
- 编写controller
- 测试服务通信
搭建公共模块
创建工程模块 springcloud-user-common ,效果如下:
springcloud-eureka-server-1010
springcloud-order-server-1030
springcloud-user-common //公共User模块
springcloud-user-server-1020
在springcloud-user-common中创建User对象如下
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String desc;
}
用户和订单他们的pom.xml都导入springcloud-user-common 模块
那个模块要用就导入
<dependency>
<!--
这是一个自定义的Maven依赖,用于引入springcloud-user-common公共模块。
该库可能包含了一些与用户服务相关的通用代码或配置,比如实体类、DTOs、服务接口等。
groupId: org.example - 这表示该依赖属于哪个组织或项目
artifactId: springcloud-user-common - 这表示具体的项目或模块名称
version: 1.0-SNAPSHOT - 这表示该依赖的版本号,SNAPSHOT版本通常用于开发过程中,表示可能是不稳定的版本
-->
<groupId>org.example</groupId>
<artifactId>springcloud-user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
编写controller
springcloud-user-server-1020 工程,编写controller,返回User对象
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/get/{id}")
public User getUser(@PathVariable("id") Long id) {
return new User(id,"zs:"+id,"我是zs");
}
}
重启测试地址是否可用:http://localhost:1020/user/get/1
springcloud-order-server-1030工程,编写controller之前在主启动配置类配置RestTemplate如下:
那个模块要调用就配置一个RestTemplate
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApp {
/**
* 配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
* @return
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(OrderServerApp.class);
}
}
创建controller,通过RestTemplate调用用户服务,代码如下:
@RestController
public class OrderController {
//需要配置成Bean
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/{id}")
public User getUser(@PathVariable("id") Long id) {
String url = "http://127.0.0.1:1020/user/get"+id;
//向springcloud-user-server-1020 工程发送http请求
return restTemplate.getForObject(url, User.class);
}
}
测试服务通信
依次启动Eureka Server注册中心(不启动也行) , 用户服务 ,订单服务 ‘之前重启过不用’
浏览器访问订单服务:http://localhost:1030/order/get , 返回结果:
这种方式有什么问题?
springcloud-user-server-1020 张三开发
springcloud-order-server-1030 李四开发
突然张三发疯改了String url = “http://127.0.0.1:1020//user/getUser”+id;
李四测试就会出错。
Ribbon客户端负载均衡
我们需要对应用做集群 ,为了防止应用出现单节点故障问题,同时为了提高应用的作业能力
Ribbon的工作机制
我们将user-server(用户服务)做集群处理,增加到2个节点(注意:两个user-server(用户服务)的服务名要一样,ip和端口不一样),在注册中心的服务通信地址清单中user-server(用户服务)这个服务下面会挂载两个通信地址
。ribbon会根据user-server(用户服务)这个服务名找到两个user-server的通信地址
,
然后ribbon会按照负载均衡算法(默认轮询)选择其中的某一个通信地址,发起http请求实现服务的调用
,如下图:
用户服务集群配置
使用SpringBoot多环境配置方式集群,一个配置文件配置多个user-server环境 ,需要注意的是集群中的多个服务名(spring.application.name)应该一样,我们把相同的东西提取到最上面,不同的东西配置在各自的环境中 修改 application.yml 如下:
spring:
application:
name: user-server #服务名都叫user-server
# 激活特定环境的配置
profiles:
active: user-server1
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
---
server:
port: 1020
eureka:
instance:
instance-id: user-server:1020 #实例ID
spring:
profiles: user-server1
---
server:
port: 1021
eureka:
instance:
instance-id: user-server:1021 #实例ID
spring:
profiles: user-server2
但是还是不能开启服务运行多实例启动,IDEA需要如下勾选配置
修改Controller
@RestController
@RequestMapping("/user")
public class UserController {
// 使用@Value注解注入配置文件中的"server.port"属性值,
// 用于获取当前服务运行的端口号
@Value("${server.port}")
private int port;
@GetMapping("/get/{id}")
public User getUser(@PathVariable("id") Long id) {
// 传入id和其他信息(包括从配置文件中获取的端口号)进行初始化
return new User(id, "zs:" + id, "我是zs,port:" + port);
}
}
消费者Order-server集成Ribbon
Ribbon集成官方文档:
https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/multi/multi_spring-cloud-ribbon.html#netflix-ribbon-starter
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
开启负载均衡
修改RestTemplate的Bean的定义方法,加上Ribbon的负载均衡注解@LoadBalanced赋予RestTemplate有负债均衡的能力。
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApp {
/**
* 配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
* @LoadBalanced :让RestTemplate有负载均衡的功能
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(OrderServerApp.class);
}
}
修改Controller调用方式
现在我们把 “localhost:1020” 修改为 用户服务的服务名 。底层会通过服务发现的方式使用Ribbin进行负载均衡调用。
@RestController
public class OrderController {
//需要配置成Bean
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/{id}")
public User getUser(@PathVariable("id") Long id) {
//String url = "http://localhost:1020/user/get"+id;
String url = "http://user-server/user/get"+id;
//向springcloud-user-server-1020 工程发送http请求
return restTemplate.getForObject(url, User.class);
}
}
测试Ribbon
分别启动EurekaServer注册中心 ,启动两个UserServer用户服务,启动OrderServer订单消费者服务,浏览器访问订单服务:http://localhost:1030/order/1 ,发送多次请求。
观察响应的结果中的端口变化 - 端口会交替出现1020,,1021我们可以推断出Ribbon默认使用的是轮询策略。
总结
Ribbon的使用相对比较简单,配合RestTemplate使用注解@LoadBalanced即可完成负载均衡配置,但是再我们的订单服务Controller中向用户服务发起请求的代码就显得不简单了。
微服务技术栈储备_SpringCloudNetflix搭建_02