SpringCloud基础知识:概述、服务治理
写在前面
微服务架构
"微服务"
一词源于Martin Fowler的名为Microservices的博文,可以在他的官方博客上找到:http://martinfowler.com/articles/microservices.html- 微服务是系统架构上的一种设计风格,
它的主旨是将一个原本独立的系统拆分成多个小型服务
,这些小型服务都在各自独立的进程中运行,服务之间一般通过HTTP的 RESTfuLAPI进行通信协作。 - 被拆分成的
每一个小型服务都围绕着系统中的某一项或些耦合度较高的业务功能进行构建
,并且每个服务都维护着白身的数据存储、业务开发自动化测试案例以及独立部署机制。 - 由于有了
轻量级的通信协作基础
,所以这些微服务可以使用不同的语言来编写。
1. SpringCloud概述
Spring Cloud是一系列框架的
有序集合。
SpringCloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来。通过Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理
,最终给开发者留出了一套简单易懂
、易部署
和易维护
的分布式系统开发工具包
。- 它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如
服务发现注册
、配置中心
、消息总线
、负载均衡
、断路器
、数据监控
等,都可以用Spring Boot的开发风格做到一键启动和部署。
1.1 Spring Cloud版本命名方式
- Spring Cloud版本命名方式采用了
伦敦地铁站
的名称,同时根据字母表的顺序来对应版本时间顺序,比如:- 最早的Release版本:
Angel
,第二个Release版本:Brixton
,然后是Camden
、Dalston
、Edgware
、Finchley
、Greenwich
、Hoxton
。
- 最早的Release版本:
1.2 Spring Cloud与Spring Boot版本对应关系
- 注意:笔记中的实验均采用Spring Cloud 3.14、Spring Boot 2.7.4进行实验
2. SpringCloud 服务治理
2.1 Eureka 快速入门
- Eureka是 Netflix公司开源的一个
服务注册与发现
的组件。 - Eureka和其他Netflix公司的服务组件(例负载均衡、熔断器、网关等)一起,被Spring Cloud社区整合为
Spring-Cloud-Netflix模块
。 - Eureka包含两个组件:
Eureka Server (注册中心)
和Eureka Client(服务提供者、服务消费者
)。
2.1.1 框架搭建思路
- 搭建Provider和Consumer服务。
- 使用RestTemplate完成远程调用。
- 搭建Eureka Server 服务。
- 改造 Provider和Consumer称为 Eureka Client。
- Consumer 服务通过从Eureka Server中抓取Provider地址完成远程调用
2.1.2 搭建Provider和Consumer服务
- 首先,创建一个
空的工程(spring-cloud)
,在工程中使用spring boot创建一个父模块(spring-clould-parent)
。然后创建两个为web项目的spring boot模块作为服务提供者(eureka-provider)和服务消费者(eureka-consumer)
这里需要区分服务提供者(eureka-provider)和服务消费者(eureka-consumer)是在工程中创建的,还是在父模块(spring-clould-parent)中创建的。 我实在工程中创建的。和父模块同级,所以到最后使用父模块时,需要一些改动。
- 父模块
- 父模块结构和pom.xml文件代码
<?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.itheima</groupId>
<artifactId>spring-cloud-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--
如果从父模块和服务提供方和消费方模块同级,在引入下面两个模块时,记得加上packaging为pom,
不然报错:
packaging‘ with value ‘jar‘ is invalid. Aggregator projects require ‘pom‘ as packaging.
-->
<packaging>pom</packaging>
<modules>
<module>../eureka-provider</module>
<module>../eureka-consumer</module>
</modules>
<!--spring boot 环境-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
</project>
服务提供者(eureka-provider)
- 服务提供者结构和pom.xml、application.yml核心配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
server:
port: 8000
- 服务提供者(eureka-provider)
- domain实体类
package com.itheima.eurekaprovider.domain;
public class Goods {
private int id;
private String title;
private double price;
private int count;
public Goods() {
}
public Goods(int id, String title, double price, int count) {
this.id = id;
this.title = title;
this.price = price;
this.count = count;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
- 服务提供者(eureka-provider)
- dao层代码
package com.itheima.eurekaprovider.dao;
import com.itheima.eurekaprovider.domain.Goods;
import org.springframework.stereotype.Repository;
@Repository
public class Goodsdao {
public Goods findOne(int id){
return new Goods(1,"华为手机",3999,10000);
}
}
- 服务提供者(eureka-provider)
- service层代码
package com.itheima.eurekaprovider.service;
import com.itheima.eurekaprovider.dao.Goodsdao;
import com.itheima.eurekaprovider.domain.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class GoodsService {
@Autowired
private Goodsdao goodsdao;
public Goods findOne(int id){
return goodsdao.findOne(id);
}
}
- 服务提供者(eureka-provider)
- controller层代码
package com.itheima.eurekaprovider.controller;
import com.itheima.eurekaprovider.domain.Goods;
import com.itheima.eurekaprovider.service.GoodsService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 服务的提供方
*/
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@GetMapping("/findOne/{id}")
public Goods findOne(@PathVariable("id") int id){
Goods goods = goodsService.findOne(id);
return goods;
}
}
运行服务的提供方的启动类进行测试
服务消费者(eureka-consumer)
- 服务消费者结构和pom.xml、application.yml核心配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
server:
port: 9000
- 服务消费者(eureka-consumer)
- domain实体类与服务提供方一致,这里不再赘述
- controller中的OrderController类用来调用
服务提供者
的服务
package com.itheima.eurekaconsumer.controller;
import com.itheima.eurekaconsumer.domain.Goods;
/**
* 服务的调用方
*/
@RestController
@RequestMapping("order")
public class OrderController {
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
System.out.println("findGoodsById...."+id);
return null;
}
}
运行服务的消费方的启动类进行测试
控制台打印:findGoodsById…1
2.1.3 使用RestTemplate完成远程调用
- Spring提供的一种简单便捷的模板类,用于在java代码里访问restful服务。
- 功能与HttpClient类似,但是
RestTemplate实现更优雅,使用更方便
。
书写消费方config包中restTemplate配置类
package com.itheima.eurekaconsumer.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
修改消费方OrderController类代码
package com.itheima.eurekaconsumer.controller;
import com.itheima.eurekaconsumer.domain.Goods;
/**
* 服务的调用方
*/
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
System.out.println("findGoodsById...."+id);
/*
//远程调用Goods服务中的findOne接口
使用 RestTemplate
1.定义:Bean restTemplate
2.注入: Bean
3.调用方法
*/
String url = "http://localhost:8000/goods/findOne/"+id;
//调用方法
Goods goods = restTemplate.getForObject(url,Goods.class);
return goods;
}
}
执行服务提供者和消费者的启动类
2.1.4 搭建Eureka Server 服务
1.在spring-cloud-parent模块中引入spring cloud依赖
,修改spring-cloud-parent的pom.xml文件
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--spring-cloud 版本-->
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
2.创建一个web工程的springboot模块(eureka-server)
,在pom.xml文件中引入下面信息,并修改启动类
- eureka-server模块
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>3.1.4</version>
</dependency>
</dependencies>
- eureka-server模块
- 启动类
package com.itheima.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer //添加该注解
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
- eureka-server模块
- application.yml文件
server:
port: 8761
# eureka配置
# 1. dashboard:eureka的web控制台信息
# 2. server: eureka的服务端配置
# 3. client: eureka的客户端配置
# 4. instance:eureka的实例配置
eureka:
instance:
hostname: localhost # 主机名
client:
service-url: # eureka的服务端地址,将来客户端使用该地址和eureka通信
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
register-with-eureka: false # 默认true, 是否将自己的路径注册到eureka上,eureka server不需要 eureka provider client需要
fetch-registry: false # 是否从eureka 中抓取路径,默认true, eureka server不需要 eureka consumer client需要
启动测试类
,访问eureka成功。
2.1.5 改造 Provider 和 Consumer
服务提供方引入eureka信息
- eureka-provider服务提供方
- 修改eureka-provider服务提供方pom.xml
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.1.4</version>
</dependency>
- eureka-provider服务提供方
- 修改启动类
package com.itheima.eurekaprovider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient //添加该注解,在新版本中可以省略,但是不建议
@SpringBootApplication()
public class EurekaProviderApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication.class, args);
}
}
- eureka-provider服务提供方
- 修改application.yml
server:
port: 8000
eureka:
instance:
hostname: localhost
client:
service-url: # eureka服务端地址,将来客户端使用该地址和eureka进行通信
defaultZone: http://localhost:8761/eureka # 不能使用https,会报错
spring:
application:
name: eureka-provider # 设置当前应用的名称,将来会在eureka中application显示。将来需要该名称获取路径。
启动server和provider启动类
服务消费方引入eureka信息
- 同上,不再赘述
2.1.6 使用 Eureka Server 完成远程调用
修改服务消费者的启动类
package com.itheima.eurekaconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableDiscoveryClient //激活 DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class EurekaConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerApplication.class, args);
}
}
修改服务消费方的OrderController类
package com.itheima.eurekaconsumer.controller;
import com.itheima.eurekaconsumer.domain.Goods;
//import com.netflix.discovery.DiscoveryClient; 不是这一个包
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient; //是这个包
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* 服务的调用方
*/
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
System.out.println("findGoodsById...."+id);
/*
//远程调用Goods服务中的findOne接口
使用 RestTemplate
1.定义:Bean restTemplate
2.注入: Bean
3.调用方法
//动态从Eureka Server中获取provider的ip和端口
1.启动类中注入DiscoveryClient对象,激活
2.调用方法
*/
//演示DiscoveryClient使用
List<ServiceInstance> instances = discoveryClient.getInstances("EUREKA-PROVIDER"); // 不区分大小写
if(instances == null || instances.size() == 0){
return null;
}
ServiceInstance instance = instances.get(0); //因为只有一个,这里我们就获取第一个
String host = instance.getHost();
int port = instance.getPort();
System.out.println(host);
System.out.println(port);
//String url = "http://localhost:8000/goods/findOne/"+id;
String url = "http://"+host+":"+port+"/goods/findOne/"+id;
//调用方法
Goods goods = restTemplate.getForObject(url,Goods.class);
return goods;
}
}
开启测试
2.2 Eureka相关配置及特性
eureka 一共有4部分配置
- server: eureka的服务端配置
- client: eureka的客户端配置
- instance: eureka的实例配置
- dashboard: eureka的web控制台配置
1. instance
eureka:
instance:
hostname: localhost #主机名
# 是否将自己的ip注册到eureka中,默认false, 注册主机名
prefer-ip-address: false # false:localhost, true:172.16.98.134
# 设置当前实例ip
ip-address: 127.0.0.1 # 设置之后,172.16.98.134--->变成127.0.0.1
# 修改instance-id显示,即eureka的web服务中显示的id. localhost:eureka-consumer:9000-->127.0.0.1:eureka-consumer:9000
instance-id: ${eureka.instance.ip-address}:{spring.application.name}:{server.port} # ip:应用名称:端口
lease-renewal-interval-in-seconds: 30 #每一次eureka client向eureka server 发送心跳的时间间隔
lease-expiration-duration-in-seconds: 90 #如果90 秒内eureka server没有收到eureka client的心跳包,则剔除该服务
2. server
eureka:
server:
# 是否开启自我保护机制,默认true
enable-self- preservation:
# 清理间脂(单位毫秒,默认60*1000)
eviction-interval-timer-in-ms:
2.3 Eureka高可用
高可用演示
1. 使用springboot快速搭建两个Eureka Servers
pom.xml添加相关依赖
- 为了实现下面配置,修改主机的hosts文件
- http://eureka-server1:8761/eureka
- http:// eureka-server2:8762/eureka
2. 分别进行配置,相互注册
运行启动类
- 查看
3. Eureka Client分别注册到这两个Eureka Server中
分别启动eureka-provider和eureka-consumer