学习资料整理自B站狂神说
搭建SpringCloud项目
微服务前言
什么是微服务
微服务架构是一种架构模式、一种架构风格,它提倡将单一的应用程序划分成一组细小的服务,每个服务运行在其独自的进程中,服务之间相互协调,相互配置,为用户提供最终价值。服务之间采用轻量级的通信机制相互沟通,每个服务都围绕着具体的业务进行构建,并且能够被单独的部署到生产环境中,另外,应尽量避免统一的集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。
微服务化的核心就是将传统的一站式应用,根据业务拆分成一个个的服务,彻底地解耦,每个微服务提供单一的业务功能服务,从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或销毁,甚至拥有自己的数据库。
微服务优点
- 符合单一职责原则;
- 每个服务足够小,代码容易理解,这样能够聚焦一个指定的业务功能或业务需求;
- 开发简单,效率高;
- 微服务能被小团队单独开发;
- 微服务是松耦合的,在开发还是部署都是独立的,互不影响;
- 能够使用不同的语言开发;
- 易于和第三方集成,微服务允许容易且灵活的方式集成自动部署:Jenkins、Hudson、bamboo;
- 微服务的业务易于开发人员理解、修改和维护;
- 微服务方便容易新技术;
- 微服务是后端业务逻辑代码,与前端html、css完全没关
- 每个微服务都有自己的存储能力
微服务缺点
- 开发人员需要处理分布式系统的复杂性;
- 多服务运维的难度,随着服务的增加而增大;
- 服务部署依赖;
- 服务间通信成本变高;
- 数据一致性;
- 数据集成测试;
- 性能监控。
微服务技术栈
-
服务开发
SpringBoot/Spring/SpringMVC
-
服务配置与管理
Netflix公司的Archaius/阿里的Diamond等
-
服务注册与发现
Eureka/Consul/Zookeeper等
-
服务调用
Rest/RPC/gRpc
-
熔断器
Hystrix/Envoy等
-
负载均衡
Ribbon/Nginx等
-
服务接口调用(客户端调用服务的简化工具)
Feign等
-
消息队列
Kafka/RabbitMQ/ActiveMQ等
-
配置中心
SpringCloudConfig/Chef等
-
服务路由(API网关)
Zuul等
-
服务监控
Zabbix/Nagios/Metrics/Specatator等
-
全链路追踪
Zipkin/Brave/Dapper等
-
服务部署
Docker/OpenStack/Kubernetes等
-
数据流操作开发包
SpringCloud Stream(封装与Redis、Rabbit、Kafka等发送接收消息)
-
事件消息总线
SpringCloud Bus
-
等…
微服务解决方案
-
Spring Cloud Netflix 一站式解决方案
-
Apache Dubbo Zookeeper 半自动,需要整合第三方组件
-
Spring Cloud Alibaba 2020年出版
SpringCloud的优势
- 整体解决方案和框架成熟度高
- 社区热度高
- 可维护性强
- 学习曲线
SpringCloud 五大核心服务
SpringCloud和SpringBoot什么关系?
- SpringBoot专注于快速方便的开发单个服务;
- SpringCloud关注是整个微服务协调治理的框架;
- SpringCloud依赖于SpringBoot。
相关资料网站
英文官网:https://spring.io/projects/spring-cloud/
中文API:https://springcloud.cc/spring-cloud-dalston.html
SpringCloud中国社区:http://springcloud.cn
SpringCloud中文网:https://springcloud.cc
SpringCloud的版本用的是伦敦的地铁站来命名,并通过首字母排序
SpringBoot | SpringCloud |
---|---|
1.2.x | Angel(天使) |
1.3.x | Brixton |
1.4.x | Camden |
1.5.x | Dalston |
1.5.x | Edgware |
2.0.x | Finchley |
2.1.x | Greenwich |
2.2.x, 2.3.x (Starting with SR5) | Hoxton |
1. RESTFul开发环境搭建
1.1 创建maven根项目,导入依赖
<!-- 打包方式 -->
<packaging>pom</packaging>
<properties>
<spring.cloud-version>Greenwich.SR1</spring.cloud-version>
<spring.boot-version>2.1.4.RELEASE</spring.boot-version>
<mysql-version>8.0.13</mysql-version>
<druid-version>1.1.10</druid-version>
<mybatis-version>1.3.0</mybatis-version>
<logback-version>1.2.3</logback-version>
<junit-version>4.12</junit-version>
<log4j-version>1.2.17</log4j-version>
<lombok-version>1.16.18</lombok-version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.complier.source>1.8</maven.complier.source>
<maven.complier.target>1.8</maven.complier.target>
</properties>
<!-- 依赖管理器 -->
<dependencyManagement>
<dependencies>
<!--springcloud依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springboot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid-version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback-version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j-version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
</dependency>
</dependencies>
</dependencyManagement>
1.2 创建数据库实例、表、插入数据
create table dept
(
dept_id bigint auto_increment
primary key,
dept_name varchar(50) null,
db_source varchar(50) null
);
INSERT INTO ssx.dept (dept_id, dept_name, db_source) VALUES (1, '开发部', 'ssx');
INSERT INTO ssx.dept (dept_id, dept_name, db_source) VALUES (2, '人事部', 'ssx');
INSERT INTO ssx.dept (dept_id, dept_name, db_source) VALUES (3, '市场部', 'ssx');
INSERT INTO ssx.dept (dept_id, dept_name, db_source) VALUES (4, '财务部', 'ssx');
INSERT INTO ssx.dept (dept_id, dept_name, db_source) VALUES (5, '运维部', 'ssx');
1.3 创建子maven:springcloud-api,加入实体类
package com.ssx.springcloud.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* @author: stone
* @create: 2020-09-21 23:58
*/
@Data
@NoArgsConstructor
@Accessors(chain = true) //链式写法
public class Dept implements Serializable {
private Long deptId;
private String deptName;
//数据所在数据库实例名
private String dbSource;
public Dept(String deptName){
this.deptName = deptName;
}
}
1.4 创建子maven:springcloud-provider-dept-8001 服务提供者
写好service、dao、mapper、mybatis-config、application.yml
package com.ssx.springcloud.controller;
import com.ssx.springcloud.entity.Dept;
import com.ssx.springcloud.service.IDeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author: stone
* @create: 2020-09-22 14:45
*/
@RestController()
@RequestMapping("/dept")
public class DeptController {
@Autowired
IDeptService deptService;
@PostMapping("/add")
public boolean addDept(Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/get/{id}")
public Dept getDeptById(@PathVariable("id") Long id){
return deptService.getDeptById(id);
}
@GetMapping("/list")
public List<Dept> getDeptAll(){
return deptService.getDeptAll();
}
}
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.ssx.springcloud.entity
spring:
application:
name: springcloud-privoder-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 数据源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ip:port/ssx?serverTimezone=Hongkong
username: root
password: ***
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--开启二级缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
1.5 创建子maven:springcloud-consumer-dept-80
对外提供服务
编写配置类将RestTemplate注入,然后通过restTemplate访问服务提供者
package com.ssx.springcloud.controlller;
import com.ssx.springcloud.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;
/**
* @author: stone
* @create: 2020-09-22 15:59
*/
@RestController
@RequestMapping("/dept/")
public class DeptConsumerController {
/**
* 首先 consumer服务 没有 service层
* 通过RestTemplate访问另一个服务
* 提供多种便捷访问远程http服务的脚手架,简单的RESTFul服务模版。
*/
@Autowired
RestTemplate restTemplate;
private static final String REST_URL_PREFIX = "http://localhost:8001/dept/";
@RequestMapping("add")
public boolean add(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX+"add",dept,Boolean.class);
}
@RequestMapping("get/{id}")
public Dept get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX+"get/"+id, Dept.class);
}
@RequestMapping("list")
public List<Dept> list(){
return restTemplate.getForObject(REST_URL_PREFIX+"list",List.class);
}
}
测试结果
RESTFul项目就搭完了。
2. Eureka
Eureka是Netflix的一个子模块,也是核心模块之一。
2.1 基本介绍
Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于Dubbo的注册中心,比如Zookeeper。
Eureka的基本架构
- SpringCloud封装了NetFlix公司开发的Eureka模块来实现服务发现和注册;
- Eureka采用C-S架构设计,EurekaServer作为服务注册功能的服务器,它是服务注册中心;
- 系统中其它的微服务。使用Eureka的客户端连接到EurekaServer并维持心跳链接,可以通过EurekaServer来监控系统中各个微服务是否正常运行;
- 两个组件:Eureka Server和Eureka Client。
三大角色
- Eureka Server:提供服务的发现与注册;
- Service Provider:将自身服务注册到Eureka,方便消费者使用;
- Server Consumer:提供给其它微服务获取Eureka中的服务注册列表。
2.2 创建子Maven:springcloud-eureka-7001
填好配置文件
server:
port: 7001
eureka:
instance:
hostname: sc-eureka # 服务名
client:
register-with-eureka: false #是否将eureka注册到eureka
fetch-registry: false #false表示 当前服务为注册中心
service-url: # 监控页面
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类启动
@SpringBootApplication
@EnableEurekaServer // 服务端启动类,用于接收其它微服务的注册
public class EurekaServer_7001 {
public static void main(String args[]){
SpringApplication.run(EurekaServer_7001.class,args);
}
}
访问7001端口,Eureka管理页面
2.3 将服务提供者provider 注册到Eureka
springcloud-provider-dept-8001 pom.xml
加入两个依赖
<!-- springcloud client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
启动器加上eureka客户端注解
@SpringBootApplication
@EnableEurekaClient // 启动注册到Eureka Server
public class DeptProvider_8001 {
public static void main(String args[]){
SpringApplication.run(DeptProvider_8001.class,args);
}
}
修改服务提供者的application.yml
eureka:
instance:
instance-id: sc-provider-dept # 对应eureka注册列表上的Status字段
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka/
# info 用于actuator展示信息
info:
app.name: ssx-springcloud
company.name: blog.ssx.com
启动 8001,访问 7001
点击status中的链接跳转actuator服务信息页面
eureka自我保护机制
在正常情况下,EurekaServer在一定时间内(默认90s)没有接收到心跳包则会将该服务注册掉。
但是极端条件下,如突然某个服务的网络中断了,这时此微服务无法与Eureka进行通信,此时EurekaServer通过自我保护机制,临时的将此服务的信息状态保存在服务注册列表中。那么如果在短时间内,微服务的集体或大部分丢失(故障),那么Eureka会进入自我保护模式,进入此模式后,EurekaServer将会把服务注册表保护起来,不再删除服务注册表的数据(也就是不会注销任务微服务)。
等到网络故障恢复后,收到的心跳数恢复到正常阈值以上时,该EurekaServer节点会自动退出自我保护模式。
**自我保护模式是一种应对网络异常的安全保护措施。这种设计的思想是:宁可同时全部微服务,也不盲目注销任务可能健康的微服务。**这种设计可以让eureka服务集群更健壮稳定。
关闭自我保护的配置:(不推荐关闭)
eureka.server.enable-self-preservation = fase
在服务上获取注册中心信息
DeptController.java
@GetMapping("/discoveryClient")
public Object discoveryClient(){
// 团队协同开发时,用到的获取服务注册列表的信息
discoveryClient.getServices().forEach(System.out::println);
List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-privoder-dept");
System.out.println("============================");
instances.forEach(i->{
System.out.println("getHost:"+i.getHost());
System.out.println("getInstanceId:"+i.getInstanceId());
System.out.println("getScheme:"+i.getScheme());
System.out.println("getServiceId:"+i.getServiceId());
System.out.println("getMetadata:"+i.getMetadata());
System.out.println("getPort:"+i.getPort());
System.out.println("getUri:"+i.getUri());
});
return discoveryClient.getServices();
}
启动器加上注解
@EnableDiscoveryClient // 获取注册中心的信息
console
springcloud-privoder-dept
============================
getHost:192.168.0.100
getInstanceId:sc-provider-dept
getScheme:null
getServiceId:SPRINGCLOUD-PRIVODER-DEPT
getMetadata:vlsi.utils.CompactHashMap@3837686d
getPort:8001
getUri:http://192.168.0.100:8001
2.4 Eureka 集群
画了个鬼画符,,,
含义很简单,就是将eureka分别注册到另外两台上,
各种微服务要注册到三台eureka上
2.4.1 将原来的 eureka-7001 拷贝三份,做三台集群架构
2.4.2 分别修改Eureka服务的 yml
7001的配置文件,另外两台按此规律修改
server:
port: 7001
eureka:
instance:
hostname: eureka-7001 # 服务名
client:
register-with-eureka: false #是否将eureka注册到eureka
fetch-registry: false #false表示 当前服务为注册中心
service-url: # 监控页面
# Eureka 单机配置
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# Eureka 集群配置,配置三台:将此eureka注册到另外两台eureka
defaultZone: http://eureka-7002.com:7002/eureka/,http://eureka-7003.com:7003/eureka/
含义很简单,就是将eureka分别注册到另外两台上
2.4.3 修改微服务的yml
# 集群结构
defaultZone: http://eureka-7001.com:7001/eureka/,http://eureka-7002.com:7002/eureka/,http://eureka-7003.com:7003/eureka/
将此服务注册到三台eureka上
2.4.4 启动三台eureka测试
由于在单机上做测试,修改hosts映射
127.0.0.1 eureka-7001.com
127.0.0.1 eureka-7002.com
127.0.0.1 eureka-7003.com
运行~
如果当一台eureka挂了,另外两台还可用
然后eureka启动回来会重新注册到注册中心。
至此Eureka 集群搭建完毕。
集群中的CAP原则
- Consistency:强一致性
- Availability:可用性
- Partition tolerance 分区容错性
著名的CAP理论指出:一个分布式系统不可能同时拥有完美的一致性、可用性、容错性。
所以选择集群结构时,也在选择怎样进行更合适的搭配。
由于分区容错性在分布式系统中是必须保证的,因此Eureka和Zookeeper的抉择是:
Zookeeper —— Consistency 一致性、 Partition Tolerance 容错性
zk中会出现这样一种情况:当master节点因为网络故障与其它节点失去联系时,剩余节点会重新选举leader。但是选举leader的时间为 30s~120s,且选举期间整个zk集群都是不可用的,这样会导致选举期间注册服务处于瘫痪状态。在云部署环境下,因为网络问题是的zk集群失去master节点是大概率会发生的事件,虽然网络最终能够恢复,但是漫长的选举时间导致的注册服务瘫痪不可用是不能容忍的。
Eureka —— Availability 可用性、Partition Tolerance 容错性
eureka看懂了这点,因此在设计之初就优先保证了可用性。eureka集群的各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余节点依然可以提供注册和查询服务。而eureka的客户端在向某个eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台eureka健康则注册服务依旧可用,只是查询的结果可能不是最新的。除此之外eureka还有自我保护机制,如果在15分钟内超过85%的节点都没有正常心跳,那么eureka就会认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务;
- eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用);
- 当网络稳定后,当前实例新的注册信息会被同步到其它注册服务上。
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper整个注册服务瘫痪.
3. Ribbon
3.1 基本概念
Spring Cloud Ribbon 是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon的客户端组件提供一系列完整的配置项如:连接超时、重试等等。
在配置文件中列出Load Balancer(简称LB:负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(简单轮询、随机连接等)去连接这些机器。
3.2 Ribbon的负载均衡
负载均衡(Load Balancer)在微服务或分布式集群中提供负载,达到服务高可用(常用的负载均衡软件还有:Nginx、Lvs等);
负载均衡分类:
-
集中式
在服务的消费方和提供方之间使用独立的Load Balancer软件如Nginx,由该软件负责把访问请求通过某种策略转发到服务的提供方;
-
进程式
在消费方实现Load Balancer,负载服务得到服务提供方的地址集,然后自行使用一些算法将请求分发出去。Ribbon属于进程式Load Balancer,是一个类库并继承与消费方。
3.3 实现Ribbon负载均衡
3.3.1 架构方案
三个服务提供者,三个数据库,一个注册中心,一个服务消费者
3.3.2 代码实现
三个数据库,都拥有Dept表**
提供者服务
拷贝出三份服务提供者
-
修改端口:8001 8002 8003
-
修改链接的数据库url
-
修改eureka注册id
eureka: instance: instance-id: sc-provider-dept8001 # 对应eureka注册列表上的Status字段
-
三个服务提供者的微服务名儿一致,在微服务中体现为集群
消费者服务
-
RestTemplate注入器要加上@LoadBalancer
@Bean @LoadBalanced // 负载均衡模式下,需要使用此注解注入RestTemplate public RestTemplate getRestTemplate(){ return new RestTemplate(); }
-
调用服务提供者的链接改用微服务名儿,通过微服务名儿调用
// Ribbon负载均衡时,通过微服务名访问,无需知道ip端口 private static final String REST_URL_PREFIX = "http://springcloud-privoder-dept/dept/";
-
启动器需要声明为eureka客户端,因为需要获取eureka中心的信息
@SpringBootApplication @EnableEurekaClient // 使用Ribbon负载时,需要在消费方引入eureka客户端 public class DeptConsumer_80 { public static void main(String args[]){ SpringApplication.run(DeptConsumer_80.class,args); } }
-
消费者服务注册配置eureka地址
server: port: 80 eureka: client: register-with-eureka: false # 不需要注册 service-url: defaultZone: http://eureka-7001.com:7001/eureka/,http://eureka-7002.com:7002/eureka/,http://eureka-7003.com:7003/eureka/
3.3.3 启动测试
首先启动注册中心、然后是三个服务提供者,最后是服务消费者
访问
可以看出来自三个数据库
至此服务集群完成!
3.3.4 Ribbon提供的负载均衡算法
Ribbon提供了多种负载均衡的算法模式供大家使用,默认是轮询算法;
通过源码可知,大概有以下几种,常用的为轮询算法、随机算法、权重算法等。
- RoundRobinRule:轮询算法,默认;
- RandomRule:随机算法;
- AvailabilityFilteringRule:先过滤跳闸、故障的服务,再对剩下的服务进行轮询;
- RetryRule:使用轮询算法,若获取服务失败,则在规定时间内重试;
3.3.5 自定义微服务负载均衡算法
-
实现 IRule.interface;
-
自定义算法类不能放在启动类上下文的@ComponentScan中。
官方原话:FooConfiguration必须是@Configuration,但请注意,它不在主应用程序上下文的@ComponentScan中,否则将由所有@RibbonClients共享。如果您使用@ComponentScan(或@SpringBootApplication),则需要采取措施避免包含(例如将其放在一个单独的,不重叠的包中,或者指定要在@ComponentScan)。
自定义负载算法
/**
* 自定义ribbon 微服务 负载均衡算法
* 先拷贝了轮询模式的代码再进行修改
* 算法思想:简单实现 每个服务使用3次再提供下一个服务
* @author: stone
* @create: 2020-09-25 00:08
*/
public class MyRule extends AbstractLoadBalancerRule {
//内容挺长,,,就不贴了,看看源码很容易就能写出来
}
配置类应用算法类
/**
* 自定义负载算法配置类,指定使用那个自定义负载算法
* 此类要单独注入,不然全部ribbon集群都会共用一个负载算法
* @author: stone
* @create: 2020-09-25 00:30
*/
@Configuration
public class UseMyRule {
@Bean
public IRule myRule(){
return new MyRule();
}
}
启动器要加一个注解@RibbonClient指定负载配置类
/**
* @author: stone
* @create: 2020-09-22 16:21
*/
@SpringBootApplication
@EnableEurekaClient // 使用Ribbon负载时,需要在消费方引入eureka客户端
@RibbonClient(name = "springcloud-privoder-dept",configuration = UseMyRule.class) //指定使用自定义负载均衡算法
public class DeptConsumer_80 {
public static void main(String args[]){
SpringApplication.run(DeptConsumer_80.class,args);
}
}
完成~
4. Feign
基本介绍
feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。SpringCloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
Feign的目的是让负载更简单~
主要是java程序员都习惯面向接口编程,也是很多开发人员的规范,所以诞生了Feign。
从此访问微服务有了两种方法:
- Ribbon:通过微服务名访问;
- Feign:通过接口和注解访问。
Feign的作用
- Feign旨在使编写java http客户端更轻松;
- Ribbon + RestTemplate实现服务的调用时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了一步封装,由Feign来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口来并使用注解的方式来配置它(类似于Dao接口上标注Mapper注解),即可完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时自动封装服务调用客户端的开发量。
Feign集成了Ribbon实现负载均衡
利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并通过轮询实现了客户端的负载均衡,与Ribbon不同的是,Feign只需要定义服务绑定接口且以声明式的方法,优雅、简单的实现了服务调用。
代码实现
核心代码,业务层放在 api 项目
/**
* 使用Feign访问微服务
* @author: stone
* @create: 2020-09-25 11:34
*/
@FeignClient("springcloud-privoder-dept") //指定微服务的服务名,将请求转发到这个服务上
@Component
public interface DeptService {
@PostMapping("/dept/add")
public boolean addDept(Dept dept);
@GetMapping("/dept/get/{id}")
public Dept getDeptById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public List<Dept> getDeptAll();
}
新建Feign项目
用于接收客户的请求,通过调用 service 实现转发
@RestController
@RequestMapping("/dept")
public class DeptConsumerController {
@Autowired
DeptService deptService;
@PostMapping("/add")
public boolean add(Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/get/{id}")
public Dept get(@PathVariable("id") Long id){
return deptService.getDeptById(id);
}
@GetMapping("/list")
public List<Dept> list(){
return deptService.getDeptAll();
}
}
/**
* @author: stone
* @create: 2020-09-22 16:21
*/
@SpringBootApplication
@EnableEurekaClient // 使用Feign负载时,需要在消费方引入eureka客户端
@EnableFeignClients(basePackages = "com.ssx.springcloud") //扫描使用Feign的类
public class DeptConsumer_80_Feign {
public static void main(String args[]){
SpringApplication.run(DeptConsumer_80_Feign.class,args);
}
}
启动测试
启动 feign项目,绑定了80端口
5. Hystrix
https://github.com/Netflix/Hystrix/wiki
5.1 基础概念
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败(超时、异常等),Hystrix能够保证在一个依赖出问题的时候,不会导致整个服务失败,避免级联故障,以提高分布式系统的稳定性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似保险丝熔断器),向调用方返回一个服务预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会长时间阻塞占用,从而避免了故障在分布式系统的蔓延乃至雪崩。
分布式系统雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上的某个微服务调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的“雪崩效应”。
对于高流量应用来说,单一的后端依赖可能会导致所有服务器上的所有资源在几秒内饱和,并且更糟糕的是这些应用程序还可能导致服务之间的延迟增加、备份队列,线程和其它系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障系统和延迟进行隔离与管理,以便单个依赖关系的失败导致整个应用系统崩溃。
引用Hystrix官方的三个图说明:
- 当服务都健康的时候,用户发起请求
- 当某一个微服务故障时
- 导致大量请求在此微服务阻塞,占用资源
5.2 服务熔断
在服务提供者对异常或超时等突发问题进行处理。
熔断机制是对应雪崩效应的一中微服务链路保护机制。
当扇出链路的某一个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现,Hystrix可以监控微服务间调用的情况,当失败的调用到达一定的阈值,缺省是5秒内20次调用失败就会启动熔断机制。
@HystrixCommand
代码实现
-
添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.6.RELEASE</version> </dependency>
-
编写熔断方法,使用@HystrixCommand注解
/**
* 使用熔断器
* @author: stone
* @create: 2020-09-22 14:45
*/
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
IDeptService deptService;
/**
* 支持熔断的方法
* @param id
* @return
*/
@GetMapping("/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGetDept") //添加失败时返回的方法
public Dept getDeptById(@PathVariable("id") Long id){
Dept dept = deptService.getDeptById(id);
if(dept==null){
throw new RuntimeException("getDeptById_hystrix() dept is null!!!");
}
return dept;
}
/**
* 根据id查询失败时的回调、备用方法
* @param id
* @return
*/
public Dept hystrixGetDept(@PathVariable("id") Long id){
return new Dept().
setDeptId(id).
setDeptName("find no dept for this id.").
setDbSource("null");
}
}
-
启动熔断服务
@SpringBootApplication @EnableEurekaClient // 启动注册到Eureka Server @EnableDiscoveryClient // 获取注册中心的信息 @EnableCircuitBreaker // 启动hystrix熔断器 public class DeptProvider_8004_hystrix { public static void main(String args[]){ SpringApplication.run(DeptProvider_8004_hystrix.class,args); } }
-
启动测试
5.3 服务降级
在微服务调用方,对不能正常返回的服务进行失败回调的预判,返回预定的信息。
场景:当遇到某些服务的高峰洪流时,关闭一些访问比较少的服务,将服务器资源让给热点服务。
代码实现
-
编写当微服务请求不可达时的fallbackl,在服务调用端
/** * 服务降级 * 若调用的服务不能正常请求时执行 * @author: stone * @create: 2020-09-26 14:12 */ @Component public class DetpServiceFallbackFactory implements FallbackFactory { @Override public DeptService create(Throwable throwable) { //访问服务端失败时,返回预先准备的信息 return new DeptService() { @Override public boolean addDept(Dept dept) { return false; } @Override public Dept getDeptById(Long id) { return new Dept(). setDeptId(id). setDeptName("当前服务不可用,请稍候再试!"). setDbSource(""); } @Override public List<Dept> getDeptAll() { return null; } }; } }
-
feign调用微服务的声明入口需要指定fallback
//指定微服务的服务名,将请求转发到这个服务上 //若服务不可达,则使用fallback回调 @FeignClient(value = "springcloud-privoder-dept",fallbackFactory = DetpServiceFallbackFactory.class) public interface DeptService {}
-
在消费者服务开启feign-hystrix功能
server: port: 80 eureka: client: register-with-eureka: false # 不需要注册 service-url: defaultZone: http://eureka-7001.com:7001/eureka/,http://eureka-7002.com:7002/eureka/,http://eureka-7003.com:7003/eureka/ # 开启 feign-hystrix fallback feign: hystrix: enabled: true
-
启动测试:当服务提供者不存活时,feign-hystrix fallback
5.4 服务熔断、服务降级——小结
-
服务熔断
在服务提供者(provider)实现,当处理的任务发生异常、或者超时等问题是,直接返回缺省值给服务消费者(consumer),避免服务堆积引起雪崩,保证系统整体的稳定性;
-
服务降级
在服务消费者(consumer)实现,由服务消费者发送请求到服务提供者时,由于服务提供者宕机或者其它原因导致请求不可达时,服务消费者使用缺省值返回给用户。应对一些特殊场景如秒杀等导致用户集中使用一个服务时,场面一边倒,关闭一些没人用或者访问量较少的服务,让出系统资源达到服务降级的效果,保证热点服务的可用性。
5.5 服务监控
实则只监控这个注解修饰的接口
@HystrixCommand
代码实现
-
新建module:springcloud-consumer-dashboard-9001
-
添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId> <version>1.4.6.RELEASE</version> </dependency>
-
开启dashboard监控器
@SpringBootApplication @EnableHystrixDashboard // 开启dashboard监控器 public class Dashborard_9001 { public static void main(String args[]){ SpringApplication.run(Dashborard_9001.class,args); } }
-
绑定端口9001
server: port: 9001
-
在所需监控的微服务中注册提供监控的servlet bean。
我这边加到了springcloud-provider-dept-8004-hystrix服务上
@SpringBootApplication @EnableEurekaClient // 启动注册到Eureka Server @EnableDiscoveryClient // 获取注册中心的信息 @EnableCircuitBreaker // 启动hystrix熔断器 public class DeptProvider_8004_hystrix { public static void main(String args[]){ SpringApplication.run(DeptProvider_8004_hystrix.class,args); } /** * 注册一个bean * 将本服务的actuator监控公布出去 * @return */ @Bean public ServletRegistrationBean hystrixMetricsStreamServlet(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet()); registrationBean.addUrlMappings("/actuator/hystrix.stream"); return registrationBean; } }
-
启动访问 http://localhost:8004/actuator/hystrix.stream
获得请求时,显示心跳信息
- 访问dashboard服务 http://localhost:9001/hystrix/
地址栏中输入8004的监控地址:http://localhost:8004/actuator/hystrix.stream
进入图形监控页面:
监控图解释
-
实心圆
- 颜色:表示实例的健康程度,从健康到危险:绿色>黄色>橙色>红色;
- 大小:表示实例的请求流量大小,球形越大表示流量越大,反之则越小。
-
曲线
-
最近2分钟的流量变化曲线图,观察该实例的流量变化趋势。
-
整图说明
6. Zuul
6.1 基本介绍
Zuul包含了对请求的路由和过滤这两个主要的功能。
-
路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;
-
过滤器功能则负责对请求的处理进行干预,是实现请求校验,服务聚合等功能的基础。
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获取其它微服务的消息,即以后的访问微服务都是通过Zuul跳转访问。
提供:代理+路由+过滤 三大功能
6.2 代码实现
-
引入zuul依赖
<dependency> <groupId>com.ssx</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
编写启动类,启动zuul
@SpringBootApplication @EnableZuulProxy //开启zuul public class Zuul9527 { public static void main(String args[]){ SpringApplication.run(Zuul9527.class,args); } }
-
配置zuul路由
zuul: routes: dept1.serviceId: springcloud-privoder-dept dept1.path: /d/** # ignored-services: springcloud-privoder-dept #忽略直接访问这个服务的请求 # ignored-services: "*" # 忽略全部未代理的路由地址
相关配置还有很多很多。
-
启动测试
zuul能配置的东西还很多
官方开发指引:https://www.springcloud.cc/spring-cloud-dalston.html#_router_and_filter_zuul
7. Config
7.1 基本介绍
Spring Cloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个微服务应用的所有环节提供一个配置中心。
SpringCloud config分布式配置中心
配置中心可以是本地仓库,也可以是云端仓库。
SpringCloud Config分为服务端和客户端两部分:
- 服务端:也成为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息;
- 客户端:通过指定的配置中心来管理应用资源,以及业务相关的配置内容,并在启动的时候从配置中心获取和加载信息。配置服务器默认采用Git来存储配置信息,这样有助于对环境配置进行版本管理,并且可以通过Git客户端工具更方便的管理和访问配置内容。
分布式配置中心的功能:
- 集中式管理配置文件
- 不同环境、不同配置、动态更新配置文件,分布式部署:/dev /test /prod /beta /release …
- 运行期间动态调整配置,配置发生改变时,服务自动感知并应用最新配置信息
- 将配置信息以RESTFul形式暴露
SpringCloud Config分布式配置中心与github整合
默认使用Git存储配置文件(也可以用svn/本地),以http或https访问;
7.2 代码实现
7.2.1 config-server
配置中心分服务端和客户端,服务端负责与git连接。
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
配置
server:
port: 3344
spring:
application:
name: springcloud-config-server
cloud:
config:
server:
git:
uri: https://gitee.com/eliott/study-cloud.git
config下有几个相关配置:
- spring.cloud.config.label:指明远程仓库的分支
- spring.cloud.config.profile:指定不同环境配置文件,和git仓库的
application-dev.yml
对应 - spring.cloud.config.name:配置名称,一般和git仓库的
application-dev.yml
对应 - spring.cloud.config.uri:上面的配置中心服务地址
config-client.yml这个放在git上
server:
port: 8010
spring:
profiles: dev
application:
name: springcloud-provider-dept
eureka:
client:
server-uri:
defaultZone: http://eureka-7001.com:7001/eureka/,http://eureka-7002.com:7002/eureka/,http://eureka-7003.com:7003/eureka/
---
server:
port: 8010
spring:
profiles: test
application:
name: springcloud-provider-dept
eureka:
client:
server-uri:
defaultZone: http://eureka-7001.com:7001/eureka/,http://eureka-7002.com:7002/eureka/,http://eureka-7003.com:7003/eureka/
启动测试
@SpringBootApplication
@EnableConfigServer // 开启config 服务
public class ConfigServer3344 {
public static void main(String args[]){
SpringApplication.run(ConfigServer3344.class,args);
}
}
7.2.2 config-client
客户端为具体业务服务
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
配置
使用bootsrap.yml避免引用配置中心的配置文件时起冲突
# application.yml 应用级配置
spring:
application:
name: config-client-3355
# bootstrap.yml 系统级配置
spring:
cloud:
config:
uri: http://localhost:3344 # config server
name: config-client # git上面的资源名称
label: master # 分支
profile: dev # 环境
启动测试
@SpringBootApplication
public class ConfigClient3355 {
public static void main(String args[]){
SpringApplication.run(ConfigClient3355.class,args);
}
}
写一个controller获取配置信息
直接使用@Value获取
@Value("${spring.application.name}")
private String applicationName;
完结撒花~