spring cloud概念
微服务架构
单体架构 垂直架构 分布式架构 soa架构 微服务架构
微服务是系统架构上的一种设计风格
将项目的各个模块拆分为可独立运行、部署、测试的设计风格
将一个原本独立的系统拆分成多个小型服务,每一种服务都在各自独立的进程中运行,
服务之间一般通过HTTP的RESTfulAPI进行通信协作
被拆分的每一个小型服务对项目中的耦合度较高的业务功能进行构建,并且每一个服务都维护自身的数据存储、业务开发自动化测试案例以及独立部署机制
拆分 独立的进程运行 通信协作 RESTful风格 支持不同语言
概念
将其他的常用的微服务架构整合起来,并使用Spring boot简化其开发、配置
- 有序集合
解释:它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来并且进行协作,并形成了一个有序集合
- 组合的服务框架
解释:将别的服务框架整合到Spring Cloud体系中
- 基于Spring Boot构建项目,spring boot风格进行封装
解释:在Spring Cloud中利用了Spring boot的自动配置,解决了各个服务型框架之间相互的写的复杂性以及整合的复杂性,屏蔽复杂的配置与实现原理,
所以我们在用Spring Cloud时就会很方便,通俗易懂,易部署,易维护的分布式系统开发工具包
简化分布式系统基础设施的开发,如服务发现注册中心、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署
- 命名
Spring Cloud采用伦敦地铁站的名称,同时根据字母表的顺序来对应时间顺序,比如:最早的Release版本:Angel,第二版:Brixton......
与Doubbo对比
- 共同点
Spring Cloud与Doubo都是实现微服务有效的工具
- 区别
* 1.Doubbo只实现了服务治理,而Spring Cloud子项目分别覆盖了微服务架构下的众多部件
* 2.Doubbo使用RPC通讯协议,Spring Cloud使用RESTful完成通信
- 对比
- doubbo性能高
- spring cloud 功能全
Dubbo使用RPC的通讯协议,而这里的RPC基于Socket,偏向底层的一种通信协议
Spring Cloud是基于Http的REST完成通信,相对与Socket来说,是上层建筑,一般的情况下,上层建筑的效率要低于底层的效率,但是上层的建筑比底层的建筑功能要丰富,
例子:
使用severlet开发,做功能比较复杂,不怎么方便,但是servrlet速度快
使用SpringMVC,SpringMVC是对serlet的封装,SpringMVC功能丰富,但是却没有serlet的速度快,因为封装之后要写一大堆的代码,相当于一个请求链路流程,封装之后,原本只需要写5行的代码,现在却要写10行,导致性能与效率就会受到影响
所以在微服务的架构里,Spring Cloud功能齐全,Doubbo性能更好,
所以,要根据开发的需求来进行选择,比如说,就是要做一个对于性能比较高,功能比较简单的微服务架构项目,就可以选择Doubbo,
对于一个大型的分布式架构项目,功能非常多,涵盖了方方面面,为了开发简便,可以选择Spring Cloud
Spring Cloud服务治理
Doubbo的注册中心 zookeeper注册中心 用于服务的注册于发现的一个组件
Spring Cloud服务治理与注册中心是一回事,注册中心是一个名词是一个组件,而服务治理是一个动词,是一个动作,使用服务中心来完成服务治理相关的工作
Eureka
概念
开源的一个服务注册于发现的组件
Eureka和其他Netflix公司的服务组件(例如负载均衡、熔断器、网关等)一起,被Spring Cloud社区整合为Spring-Cloud-Netflix模块
Eureka包含两个组件:
* Eureka Service(注册中心)
* Eureka Client(服务提供者、服务消费者)
Eureka 入门
-
1.搭建Provider和Consumer服务
- 构建一个普通java工程
- 创建一个maven模板,作为父工程,创建provider和consumer模板作为子模板
- 在父工程中引入坐标Spring Boot坐标,在provider和consumer引入web坐标
<!-- 父工程 搭建spring boot 环境-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/>
</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>
<!-- provider与consumer 引入web坐标-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- provider的模板
- 构建启动类与实体类
package com.beijingkada.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;
}
}
@SpringBootApplication
public class DemoStart {
public static void main(String[] args) {
SpringApplication.run(DemoStart.class,args);
}
}
- 创建dao,并注入springIOC容器中
@Repository //将类注入到spring容器中
public class GoodsDao {
public Goods findById(int id){
return new Goods(1,"华为手机",3999,1000);
}
}
- 创建service层
@Service
public class GoodsService {
@Autowired
private GoodsDao goodsDao;
/**
* 根据id查询
*/
public Goods findById(int id){
return goodsDao.findById(id);
}
}
- 创建controller层,作为服务的提供方
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@RequestMapping("/findById/{id}")
public Goods findById(@PathVariable("id") int id){
return goodsService.findById(id);
}
}
- 创建provider模板的application.yml文件,并指定端口号
# 指定端口号
server:
port: 8080
- 运行启动类,在浏览器访问http://localhost:8080/goods/findById/1,得到
{"id":1,"title":"华为手机","price":3999.0,"count":1000}
- consumer的模板
- 创建consumer模板的yml文件,并指定端口号,不与provider模板的端口号相同
# 指定端口号
server:
port: 8081
* 构建启动类与实体类,应与provider中的实体类一致
package com.beijingkada.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;
}
}
创建controller层,作为服务的调用方
@RestController
@RequestMapping("/orlds")
public class GoodsController {
@RequestMapping("/findById/{id}")
public Goods findById(@PathVariable("id") int id){
System.out.println("findById..."+id);
return null;
}
}
-
2.使用RestTemplate完成远程调用
Spring提供的一种简单便捷的模板类,用于在java代码里访问restful服务
与HttpClient的功能类似,但是RestTemplate实现更优雅,使用更方便
- 在consumer模板添加配置类
/*
* @Configuration注解的配置类中通过@Bean注解在某个方法上将方法返回的对象定义为一个Bean,
* 并使用配置文件中相应的属性初始化该Bean的属性
* */
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- controller层对GoodsController进行修改
@RestController
@RequestMapping("/orlds")
public class GoodsController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/findById/{id}")
public Goods findById(@PathVariable("id") int id){
System.out.println("findById..."+id);
/*
* 远程调用Goods服务中的findId接口
* 使用RestTemplate
* 1.定义bean restTemplte
* 2.注入bean
* 3.调用方法
*/
//调用方法
String url = "http://localhost:8080/goods/findById/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
- 运行consumer模板中的启动类,在浏览器访问
{"id":1,"title":"华为手机","price":3999.0,"count":1000}
- 3.搭建Eureka Server服务
-
创建eureka-server模块
-
引入Spring Cloud和euraka-server相关依赖
- 父依赖
<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>Greenwich.RELEASE</spring-cloud.version>
</properties>
<!-- 引入Spring Cloud配置-->
<dependencyManagement>
<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>
</dependencyManagement>
- euraka-server引入坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
- 创建启动类
@SpringBootApplication
//启动EurekaServer
@EnableEurekaServer
public class EurekaServerStart {
public static void main(String[] args) {
SpringApplication.run(EurekaServerStart.class,args);
}
}
- 创建并编写application.yml文件
# 指定端口号
server:
port: 9000
# eureka配置
# eureka一共有4部分 配置
# 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 # 是否将自己的路径注册到eureka上,eureka server不需要,eureka provider client需要
fetch-registry: false # 是否需要eureka中抓取路径 eureka不需要,eureka consumer client需要
- 启动该模块
- 4.改造Provider和Consumer(Eureka Client)
- 在provider和consumer都引入依赖
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在他们的启动类上加@EnableEurekaClient注解,该注解在新版本中可以忽略
@SpringBootApplication
@EnableEurekaClient
public class DemoStartConsumer {
public static void main(String[] args) {
SpringApplication.run(DemoStartConsumer.class,args);
}
}
@SpringBootApplication
@EnableEurekaClient
public class DemoStartProvider {
public static void main(String[] args) {
SpringApplication.run(DemoStartProvider.class,args);
}
}
- 修改各自的yml文件
- provider模板
# 指定端口号
server:
port: 8080
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
# eureka服务器端的地址,客户端可用该地址与eureka进行通信
defaultZone: http://localhost:9000/eureka
# 设置当前应用的名称,会在eureka中显示
spring:
application:
name: spring-provider
- consumer模板
# 指定端口号
server:
port: 8081
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
# eureka服务器端的地址,客户端可用该地址与eureka进行通信
defaultZone: http://localhost:9000/eureka
# 设置当前应用的名称,会在eureka中显示
spring:
application:
name: spring-consumer
运行启动类,可以得到
- 5.Consumer服务通过从Eureka Server中抓取Provider地址完成远程调用
@RestController
@RequestMapping("/orlds")
public class GoodsController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/findById/{id}")
public Goods findById(@PathVariable("id") int id){
System.out.println("findById..."+id);
/*
* 远程调用Goods服务中的findId接口
* 使用RestTemplate
* 1.定义bean restTemplte
* 2.注入bean
* 3.调用方法
*/
/*
* 动态从Eureka Server中获取provide的ip端口
* 1.注入 DiscoveryClient 对象 激活
* 2.调用方法
*/
List<ServiceInstance> instances = discoveryClient.getInstances("SPRING-PROVIDER");
//判断集合中有没有数据
if (instances == null && instances.size() > 0){
//集合中没有数据
return null;
}
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost(); //主机名 ip
int port = serviceInstance.getPort(); //端口号
System.out.println(host);
System.out.println(port);
//调用方法
String url = "http://"+host+":"+port+"/goods/findById/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
Eureka 属性
eureka一共有4部分配置
1.server:eureka 的服务端配置
2.clint:eureka 的客户端配置
3.instance:eureka的实力配置
4.dashboard:eureka 的web控制台配置
instance属性
eureka:
instance:
hostname: localhost # 主机名
prefer-ip-address: false #是否将自己的ip注册到eureka中,默认false 注册 主机名
ip-address: # 设置当前实例的ip
instance-id: #修改instance-id显示
lease-renewal-interval-in-seconds: 30 #每一次eureka client向 eureka server发送心跳的时间间隔
lease-expiration-duration-in-seconds: 90 # 如果90秒内eureka server没有eureka clina的心跳包,则剔除该服务
- 修改provider的yml文件
prefer-ip-address: true #是否将自己的ip注册到eureka中,默认false 注册 主机名
# 指定端口号
server:
port: 8080
eureka:
instance:
hostname: localhost # 主机名
prefer-ip-address: true #是否将自己的ip注册到eureka中,默认false 注册 主机名
client:
service-url:
# eureka服务器端的地址,客户端可用该地址与eureka进行通信
defaultZone: http://localhost:9000/eureka
# 设置当前应用的名称,会在eureka中显示
spring:
application:
name: spring-provider
运行结果
findById...1
192.168.11.65
8080
对应结果
prefer-ip-address: true
192.168.11.65
ip-address: 127.0.0.1 # 设置当前实例ip
server:
port: 8080
eureka:
instance:
hostname: localhost # 主机名
prefer-ip-address: true #是否将自己的ip注册到eureka中,默认false 注册 主机名
ip-address: 127.0.0.1 # 设置当前实例ip
client:
service-url:
# eureka服务器端的地址,客户端可用该地址与eureka进行通信
defaultZone: http://localhost:9000/eureka
# 设置当前应用的名称,会在eureka中显示
spring:
application:
name: spring-provider
对应结果
prefer-ip-address: true #是否将自己的ip注册到eureka中,默认false 注册 主机名
ip-address: 127.0.0.1 # 设置当前实例ip
127.0.0.1
instance-id: 修改 instance-id 显示
server:
port: 8080
eureka:
instance:
hostname: localhost # 主机名
prefer-ip-address: true #是否将自己的ip注册到eureka中,默认false 注册 主机名
ip-address: 127.0.0.1 # 设置当前实例ip
instance-id: ${eureka.instance.ip-address}:${spring.application.name}:${server.port} # 修改 instance-id web控制台显示的id
client:
service-url:
# eureka服务器端的地址,客户端可用该地址与eureka进行通信
defaultZone: http://localhost:9000/eureka
# 设置当前应用的名称,会在eureka中显示
spring:
application:
name: spring-provider
对应结果
instance-id: ${eureka.instance.ip-address}:${spring.application.name}:${server.port} # 修改 instance-id web控制台显示的id
在Eureka中显示
SPRING-PROVIDER n/a (2) (2) UP (2) - SKY-20210411WID:spring-provider:8080 , 127.0.0.1:spring-provider:8080
server属性
eureka:
server:
# 是否开启自我保护机制 默认是true
enable-self-preservation: true
# 清理间隔(单位毫秒,默认60*1000)
eviction-interval-timer-in-ms:
eureka:
instance:
lease-expiration-duration-in-seconds: 90 # 如果90s内eureka server没有收到eureka client的心跳包,则剔除该服务
lease-renewal-interval-in-seconds: 30 #每一次eureka client向eureka server 发送心跳的时间间隔
Eureka 高可用
测试
* 准备两个Eureka Server
* 分别进行配置,相互注册
* Eureka Clinet分别注册到两个Eureka Server中
- 准备两个Eureka Server
- 分别进行配置,相互注册
主要是配置端口号与服务地址
端口号不能相同
将eureka-server1的eureka服务地址配置到eureka-server2上
将eureka-server2的eureka服务地址配置到eureka-server1上
defaultZone: http://eureka-server2:9002/eureka
defaultZone: http://eureka-server1:9001/eureka
如果说是ip不被识别,就要修改本机的hosts文件
C:\Windows\System32\drivers\etc
# 指定端口号
server:
port: 9001
eureka:
instance:
hostname: eureka-server1 # 主机名
client:
service-url:
# eureka服务器端的地址,客户端可用该地址与eureka进行通信
defaultZone: http://eureka-server2:9002/eureka
register-with-eureka: true # 是否将自己的路径注册到eureka上,eureka server不需要,eureka provider client需要
fetch-registry: true
spring:
application:
name: eureka-server-ha
# 指定端口号
server:
port: 9002
eureka:
instance:
hostname: eureka-server2 # 主机名
client:
service-url:
# eureka服务器端的地址,客户端可用该地址与eureka进行通信
defaultZone: http://eureka-server1:9001/eureka
register-with-eureka: true # 是否将自己的路径注册到eureka上,eureka server不需要,eureka provider client需要
fetch-registry: true # 是否需要eureka中抓取路径 eureka不需要,eureka consumer client需要
spring:
application:
name: eureka-server-ha
- Eureka Clinet分别注册到两个Eureka Server中
修改provider与consumer中的配置文件
defaultZone: http://eureka-server1:9001/eureka,http://eureka-server2:9002/eureka
运行测试
输入http://localhost:9001/得到
输入http://localhost:9002/得到
运行http://localhost:8081/orlds/findById/1
http://localhost:8080/goods/findById/1
为了证明高可用性,将eureka-server2服务停掉
输入http://localhost:9002/得到
再次输入http://localhost:8081/orlds/findById/1
再次输入http://localhost:8080/orlds/findById/1
再次输入http://localhost:9001/
Consul
介绍
- Consul是由HashiCorp基于Go语言开发,支持多数据中心,分布式高可用的服务发布和注册服务软件
- 用于实现分布式系统的服务发现与配置
- 具有天然可移植性(支持Linux、windows和Mac OS)安装包仅包含一个可执行文件,方便部署
- 地址:www.consul.io
双击运行
得到即运行成功
在浏览器输入localhost:8500
得到
入门
Nacos
Ribbom 客户端负载均衡
概述
基于Http和TCP的客户端负载均衡工具
简化远程调用
使用Ribbon 简化 restTemplate调用
1.在声明restTemplate的bean的时候,添加注解:@LoadBalanced
2.在使用restTemplate发起请求时,需要定义url时,host;port 可以替换为 服务提供方的应用名称
package com.beijingkada.controller;
import com.beijingkada.config.RestTemplateConfig;
import com.beijingkada.domain.Goods;
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("/orlds")
public class GoodsController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/findById/{id}")
public Goods findById(@PathVariable("id") int id){
System.out.println("findById..."+id);
/*
* 远程调用Goods服务中的findId接口
* 使用RestTemplate
* 1.定义bean restTemplte
* 2.注入bean
* 3.调用方法
*/
/*
* 动态从Eureka Server中获取provide的ip端口
* 1.注入 DiscoveryClient 对象 激活
* 2.调用方法
*/
List<ServiceInstance> instances = discoveryClient.getInstances("SPRING-PROVIDER");
//判断集合中有没有数据
if (instances == null && instances.size() > 0){
//集合中没有数据
return null;
}
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost(); //主机名 ip
int port = serviceInstance.getPort(); //端口号
System.out.println(host);
System.out.println(port);
//调用方法
String url = "http://"+host+":"+port+"/goods/findById/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
/*
* 使用Ribbon 简化 restTemplate调用
*
* 1.在声明restTemplate的bean的时候,添加注解:@LoadBalanced
* 2.在使用restTemplate发起请求时,需要定义url时,host;port 可以替换为 服务提供方的应用名称
*
* */
@GetMapping("/findById2/{id}")
public Goods findById2(@PathVariable("id") int id){
//调用方法
String url = "http://SPRING-PROVIDER/goods/findById/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
package com.beijingkada.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/*
* @Configuration注解的配置类中通过@Bean注解在某个方法上将方法返回的对象定义为一个Bean,
* 并使用配置文件中相应的属性初始化该Bean的属性
* */
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
负载均衡