当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
缺点:
当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分
缺点:
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
缺点:
SOA(Service Oriented Architecture)面向服务的架构:它是一种设计方法,其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能。一个服务 通常以独立的形式存在与操作系统进程中。各个服务之间 通过网络调用。
ESB(企业服务总线),简单 来说 ESB 就是一根管道,用来连接各个服务节点。为了集 成不同系统,不同协议的服务,ESB 做了消息的转化解释和路由工作,让不同的服务互联互通。
SOA缺点:每个供应商提供的ESB产品有偏差,自身实现较为复杂;应用服务粒度较大,ESB集成整合所有服务和协议、数据转换使得运维、测试部署困难。所有服务都通过一个通路通信,直接降低了通信速度。
学习目标:了解项目架构的演变历程
总结:
微服务架构是使用一套小服务来开发单个应用的方式或途径,每个服务基于单一业务能力构建,运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,并能够通过自动化部署机制来独立部署。这些服务可以使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
API Gateway网关是一个服务器,是系统的唯一入口。为每个客户端提供一个定制的API。API网关核心是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。如它还可以具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。通常,网关提供RESTful/HTTP的方式访问服务。而服务端通过服务注册中心进行服务注册和管理。
微服务的特点:
微服务架构与SOA都是对系统进行拆分;微服务架构基于SOA思想,可以把微服务当做去除了ESB的SOA。ESB是SOA架构中的中心总线,设计图形应该是星形的,而微服务是去中心化的分布式软件架构。两者比较类似,但其实也有一些差别:
功能 | SOA | 微服务 |
---|---|---|
组件大小 | 大块业务逻辑 | 单独任务或小块业务逻辑 |
耦合 | 通常松耦合 | 总是松耦合 |
管理 | 着重中央管理 | 着重分散管理 |
目标 | 确保应用能够交互操作 | 易维护、易扩展、更轻量级的交互 |
学习目标:了解SOA与微服务架构的区别以及说出微服务架构的特点
解析:
SOA使用了ESB组件的面向服务架构:ESB自身实现复杂;应用服务粒度较大,所有服务之间的通信都经过ESB会降低通信速度;部署、测试ESB比较麻烦。
总结:
微服务架构:是一套使用小服务或者单一业务来开发单个应用的方式或途径。
微服务架构特点:
与使用ESB的SOA架构的区别:微服务架构没有使用ESB,有服务治理注册中心;业务粒度小。
无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?
常见的远程调用方式有以下2种:
RPC:Remote Produce Call远程过程调用,RPC基于Socket,工作在会话层。自定义数据格式,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表
Http:http其实是一种网络传输协议,基于TCP,工作在应用层,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。
现在热门的Rest风格,就可以通过http协议来实现。
区别:RPC的机制是根据语言的API(language API)来定义的,而不是根据基于网络的应用来定义的。如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么Spring Cloud搭建微服务是不二之选。
学习目标:能够说出服务调用方式种类
总结:
区别:
rpc存在服务的依赖,通过NIO进行远程调用
而HTTP是基于http协议进行远程调用,不存在依赖关系.
这里使用Http进行说明,既然微服务选择了Http,那么我们就需要考虑自己来实现对请求和响应的处理。不过开源世界已经有很多的http客户端工具,能够帮助我们做这些事情,例如:
不过这些不同的客户端,API各不相同。而Spring也有对http的客户端进行封装,提供了工具类叫RestTemplate。
Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持
例如:
在项目中的 HttpDemoApplication 注册一个 RestTemplate 对象,可以在启动类位置注册:
@SpringBootApplication
public class HttpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HttpDemoApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
启动springboot项目,在项目中的测试类中直接 @Autowired 注入:
@RunWith(SpringRunner.class)
@SpringBootTest
public class RestTemplateTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void test(){
String url = "http://localhost/user/8";
//restTemplate可以对json格式字符串进行反序列化
User user = restTemplate.getForObject(url, User.class);
System.out.println(user);
}
}
将会转换连接中获取到的json格式的对象为User对象并打印
通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。
学习目标:了解Spring RestTemplate的应用
解析:
一般情况下有如下三种http客户端工具类包都可以方便的进行http服务调用:
spring 提供了RestTemplate的工具类对上述的3种http客户端工具类进行了封装,可在spring项目中使用RestTemplate进行服务调用。
微服务是一种架构方式,最终肯定需要技术架构去实施。
微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?
Spring Cloud是Spring旗下的项目之一,官网地址:http://projects.spring.io/spring-cloud/
Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。
Spring Cloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等功能;协调分布式环境中各个系统,为各类服务提供模板性配置。其主要涉及的组件包括:
Spring Cloud不是一个组件,而是许多组件的集合;它的版本命名比较特殊,是以A到Z的为首字母的一些单词(其实是伦敦地铁站的名字)组成:
学习目标:Spring Cloud整合的组件和版本特征
总结:
eureka
注册中心,Gateway
网关,Ribbon
负载均衡,Feign
服务调用,Hystrix
熔断器。在有需要的时候项目添加对于的启动器依赖即可。微服务中需要同时创建多个项目,为了方便课堂演示,先创建一个父工程,然后后续的工程都以这个工程为父,实现maven的聚合。这样可以在一个窗口看到所有工程,方便讲解。在实际开发中,每个微服务可独立一个工程。
编写项目信息
编写保存位置
然后将 pom.xml 中添加如下
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.5.RELEASEversion>
<relativePath/>
parent>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.SR1spring-cloud.version>
<mapper.starter.version>2.1.5mapper.starter.version>
<mysql.version>5.1.46mysql.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapper-spring-boot-starterartifactId>
<version>${mapper.starter.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
这里已经对大部分要用到的依赖的版本进行了 管理,方便后续使用
子项目在下面会有讲解搭建
学习目标:创建微服务父工程heiheihei-springcloud、用户服务工程user-service、服务消费工程consumer-demo
解析:
需求:查询数据库中的用户数据并输出到浏览器
总结:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
通过 scope
的import可以继承 spring-cloud-dependencies
工程中的依赖
新建一个项目user-service,对外提供查询用户的服务。
选中父工程, 创建module
填写module信息,注意:子模块要在父工程的下级目录:
添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapper-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
编写配置文件,采用yaml语法,而不是properties
server:
port: 9091
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud
username: root
password: root
mybatis:
type-aliases-package: com.itheiheihei.user.pojo
编写代码
编写启动类
@SpringBootApplication
@MapperScan("com.itheiheihei.user.mapper")
public class userApplication {
public static void main(String[] args) {
SpringApplication.run(userApplication.class, args);
}
}
编写实体类
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/10/30 20:51
*/
@Data
@Table(name = "tb_user")
public class User{
// id
@Id
//开启主键自动回填
@KeySql(useGeneratedKeys = true)
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
}
编写Mapper接口
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/10/30 20:54
*/
public interface UserMapper extends Mapper<User> {
}
编写Service
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/10/30 21:01
*/
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 根据主键查询用户
* @param id 用户id
* @return 用户
*/
public User queryById(long id) {
return userMapper.selectByPrimaryKey(id);
}
}
编写Controller,并添加一个对外查询的接口处理器
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/10/30 21:04
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/{id}")
public User queryById(@PathVariable Long id) {
return userService.queryById(id);
}
}
学习目标:配置user-service工程并能够根据用户id查询数据库中用户
解析:
需求:可以访问http://localhost:9091/user/8输出用户数据
实现步骤:
选中父工程, 创建module
填写module信息,注意:子模块要在父工程的下级目录:
添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
编写代码
编写启动类
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/11/1 9:16
*/
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
编写实体类
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/10/30 20:51
*/
@Data
public class User{
// id
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
}
编写Controller,并添加一个对外查询的接口处理器
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/11/1 9:16
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
public RestTemplate restTemplate;
@RequestMapping("/{id}")
public User queryById(@PathVariable Long id) {
User user = restTemplate.getForObject("http://localhost:9091/user/10", User.class);
return user;
}
}
学习目标:编写测试类使用restTemplate访问user-service的路径根据id查询用户
解析:
需求:访问http://localhost:8080/consumer/8 使用RestTemplate获取http://localhost:9091/user/10的数据
实现步骤:
存在问题:
上述的问题都可以通过Spring Cloud的各种组件解决。
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。这就实现了服务的自动注册、发现、状态监控。
学习目标:说出Eureka的主要功能
总结:
Eureka的主要功能是进行服务管理,定期检查服务状态,返回服务地址列表。
创建一个项目 eureka-server ,启动一个Eureka Server Application服务注册中心
项目中的 pom.xml 文件如下:
<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">
<parent>
<artifactId>itheiheihei-springcloudartifactId>
<groupId>com.itheiheiheigroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>enreka-serviceartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
project>
编写启动类
package com.itheiheihei;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EnrekaApplication {
public static void main(String[] args) {
SpringApplication.run(EnrekaApplication.class, args);
}
}
编写配置文件application.yml
server:
port: 10086
spring:
application:
name: enreka-service
eureka:
client:
service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
defaultZone: http://127.0.0.1:10086/enreka
fetch-registry: false #自身不被拉取
register-with-eureka: false # 不注册自己
学习目标:添加eureka对应依赖和编写引导类搭建eureka服务并可访问eureka服务界面
解析:
Eureka是服务注册中心,只做服务注册;自身并不提供服务也不消费服务。可以搭建web工程使用Eureka,可以使用Spring Boot方式搭建。
搭建步骤:
学习目标:将user-service的服务注册到eureka并在consumer-demo中可以根据服务名称调用
解析:
服务注册:在服务提供工程user-service上添加Eureka客户端依赖;自动将服务注册到EurekaServer服务地址列表。
添加依赖;
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
改造启动引导类;添加开启Eureka客户端发现
的注解;
@SpringBootApplication
@MapperScan("com.itheiheihei.user.mapper")
@EnableDiscoveryClient //开启Eureka客户发现功能
public class userApplication {
public static void main(String[] args) {
SpringApplication.run(userApplication.class, args);
}
}
修改配置文件;设置Eureka 服务地址
eureka:
client:
service-url: #eureka注册中心地址
defaultZone: http://127.0.0.1:10086/eureka
服务发现:在服务消费工程consumer-demo上添加Eureka客户端依赖;可以使用工具类根据服务名称获取对应的服务地址列表。
添加依赖;
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
改造启动引导类;添加开启Eureka客户端发现的注解;
@SpringBootApplication
@MapperScan("com.itheiheihei.user.mapper")
@EnableDiscoveryClient //开启Eureka客户发现功能
public class userApplication {
public static void main(String[] args) {
SpringApplication.run(userApplication.class, args);
}
}
修改配置文件;设置Eureka 服务地址
spring:
application:
name: consumer-demo
eureka:
client:
service-url: #eureka注册中心地址
defaultZone: http://127.0.0.1:10086/eureka
改造处理器类ConsumerController,可以使用工具类DiscoveryClient根据服务名称获取对应服务地址列表。
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/11/1 9:16
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
public RestTemplate restTemplate;
@RequestMapping("/{id}")
public User queryById(@PathVariable Long id) {
String url = "http://localhost:9091/user/10";
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("user-service");
ServiceInstance serviceInstance = serviceInstances.get(0);
//拼接URL
url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
总结:
添加Eureka客户端依赖;
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
添加启动引导类注解;
修改配置(服务名称与eureka注册中心地址)
spring:
application:
name: consumer-demo
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
Eureka架构中的三个核心角色:
服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server
服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的user-service
服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的consumer-demo
实际提供者与消费者是一样的配置的,微服务不区分提供者与消费者
Eureka Server即服务的注册中心,EurekaServer也可以是一个集群,形成高可用的Eureka中心。
服务同步
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
而作为客户端,需要把信息注册到每个Eureka中:
如果有三个Eureka,则每一个EurekaServer都需要注册到其它几个Eureka服务中,例如:有三个分别为10086、10087、10088,则:
动手搭建高可用的EurekaServer
我们假设要搭建两台EurekaServer的集群,端口分别为:10086和10087
实际上公司使用时是在不同服务器进行集群搭建,而且eureke牺牲了数据一致性(可能存在服务注册失败,而造成注册数据不一致)
修改原来的EurekaServer配置
server:
port: ${port:10086}
spring:
application:
name: enreka-service
eureka:
client:
service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
defaultZone: ${defaultZone:http://127.0.0.1:10086/eureka}
#fetch-registry: false #自身不被拉取
#register-with-eureka: false # 不注册自己
service-url
:是指定注册的eureka地址(在eureka项目自己启动了就是一个eureka注册中心,而这里需要注册到其他的eureka注册中心,实现高可用)
单机的时候进行配置是为了进行对默认注册eureka的服务配置进行修改
而不将本身进行服务注册
所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务,注册到其它EurekaServer上,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:
注意把register-with-eureka和fetch-registry修改为true或者注释掉
在上述配置文件中的${}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用,若无则使用后面的默认值
把service-url的值改成了另外一台EurekaServer的地址,而不是自己
另外一台在启动的时候可以指定端口port和defaultZone配置:
客户端注册服务到集群
因为EurekaServer不止一个,因此 user-service 项目注册服务或者 consumer-demo 获取服务的时候,service-url参数需要修改为如下:
eureka:
client:
service-url: #eureka注册中心地址
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
学习目标:可以启动两台eureka-server实例;在eureka管理界面看到两个实例
解析:
Eureka Server是一个web应用,可以启动多个实例(配置不同端口)保证Eureka Server的高可用。
高可用配置:将Eureka Server作为一个服务注册到其它Eureka Server,这样多个Eureka Server之间就能够互相发现对方,同步服务,实现Eureka Server集群。
目标:配置eureka客户端user-service的注册、续约等配置项,配置eureka客户端consumer-demo的获取服务间隔时间;了解失效剔除和自我保护
分析:
小结:
user-service
服务续约
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
# 更倾向使用ip地址,而不是host名
prefer-ip-address: true
# ip地址
ip-address: 127.0.0.1
# 续约间隔,默认30秒
lease-renewal-interval-in-seconds: 5
# 服务失效时间,默认90秒
lease-expiration-duration-in-seconds: 5
也就是说,默认情况下每隔30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,
EurekaServer就会认为该服务宕机,会定时(eureka.server.eviction-interval-timer-in-ms设定的时间)从服务列表中移除,这两个值在生产环境不要修改,默认即可。
consumer-demo
获取服务列表
当服务消费者启动时,会检测 eureka.client.fetch-registry=true 参数的值,如果为true,则会从EurekaServer服务的列表拉取只读备份,然后缓存在本地。并且 每隔30秒 会重新拉取并更新数据。可以在项目中通过下面的参数来修改:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
# 获取服务地址列表间隔时间,默认30秒
registry-fetch-interval-seconds: 10
eureka-server
失效剔除和自我保护
服务下线
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。
失效剔除
有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。可以通过 eureka.server.eviction-interval-timer-in-ms
参数对其进行修改,单位是毫秒。
自我保护
当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的
比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。
可以通过下面的配置来关停自我保护:
eureka:
server:
# 服务失效剔除时间间隔,默认60秒
eviction-interval-timer-in-ms: 60000
# 关闭自我保护模式(默认是打开的)
enable-self-preservation: false
但是实际环境中,往往会开启很多个 user-service 的集群。此时获取的服务列表中就会有多个,到底该访问哪一个呢?
一般这种情况下就需要编写负载均衡算法,在多个实例列表中进行选择。
不过Eureka中已经集成了负载均衡组件:Ribbon,简单修改代码即可使用。
什么是Ribbon?
Ribbn是Netfix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为.为Ribbon配置服务提供者地址列表,Ribbon就可以基于某种负载均衡算法,自动地帮助服务消费者去请求.Ribbon默认为我们提供了很多的负载均衡算法,例如轮询,随机等,当然,我们也可为Ribbon实现自定义的负载均衡算法
学习目标:描述负载均衡和ribbon的作用
解析:
负载均衡是一个算法,可以通过该算法实现从地址列表中获取一个地址进行服务调用。
在Spring Cloud中提供了负载均衡器:Ribbon
总结:
Ribbon提供了轮询、随机两种负载均衡算法(默认是轮询)可以实现从地址列表中使用负载均衡算法获取地址进行服务调用。
因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。
在RestTemplate的配置方法上添加 @LoadBalanced
注解:
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2019/11/1 9:16
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
修改Controller 调用方式,不再手动获取ip和端口,而是直接通过服务名称调用;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
public RestTemplate restTemplate;
@RequestMapping("/{id}")
public User queryById(@PathVariable Long id) {
serviceInstance.getPort() + "/user/" + id;
String url = "http://user-service/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
访问页面,查看结果;并可以在9091和9092的控制台查看执行情况:
了解:Ribbon默认的负载均衡策略是轮询。SpringBoot也帮提供了修改负载均衡规则的配置入口在consumerdemo的配置文件中添加如下,就变成随机的了:
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
学习目标:配置启动两个用户服务,在consumer-demo中使用服务名实现根据用户id获取用户
解析:
需求:可以使用RestTemplate访问http://user-service/user/8获取服务数据。
可以使用Ribbon负载均衡:在执行RestTemplate发送服务地址请求的时候,使用负载均衡拦截器拦截,根据服务名获取服务地址列表
,使用Ribbon负载均衡算法从服务地址列表中选择一个服务地址
,访问该地址获取服务数据。
实现步骤:
总结:
在实例化RestTemplate的时候使用@LoadBalanced,服务地址直接可以使用服务名。例如:http://user-service/user/{id}
Hystrix 在英文里面的意思是 豪猪,它的logo 看下面的图是一头豪猪,它在微服务系统中是一款提供保护机制的组件,和eureka一样也是由netflix公司开发。
主页:https://github.com/Netflix/Hystrix/
那么Hystrix的作用是什么呢?具体要保护什么呢?
Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。
微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路:
如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。如果此时,某个服务出现异常:
例如: 微服务I 发生异常,请求阻塞,用户请求就不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:
这就好比,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。 此时如果有很多个车型都需要这个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大。
Hystrix
解决雪崩问题的手段主要是服务降级,包括:
学习目标:了解熔断器Hystrix的作用
总结结:
Hystrix是一个延迟和容错库,用于隔离访问远程服务,防止出现级联失败。(避免雪崩)
服务降级:优先保证核心服务,而非核心服务不可用或弱可用。
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。触发Hystrix服务降级的情况:
目标:了解什么是线程隔离和服务降级
解析:
Hystrix解决雪崩效应:
总结:
consumer-demo中添加依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
开启熔断
可以看到,我们类上的注解越来越多,在微服务中,经常会引入上面的三个注解,于是Spring就提供了一个组合注 解:@SpringCloudApplication
降级逻辑
当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。因此需要提前编写好失败时的降级处理逻辑,要使用HystrixCommand来完成。
默认的Fallback :把fallback写在了某个业务方法上,如果这样的方法很多,那岂不是要写很多。所以可以把Fallback配置加在类 上,实现默认fallback;@DefaultProperties(defaultFallback = “defaultFallBack”)
@RestController
@RequestMapping("/consumer")
@Slf4j
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/{id}")
//@HystrixCommand(fallbackMethod = "queryByIdFallback")
@HystrixCommand
public String queryById(@PathVariable Long id){
/*String url = "http://localhost:9091/user/"+id;
//获取eureka中注册的user-service的实例
List serviceInstances = discoveryClient.getInstances("user-service");
ServiceInstance serviceInstance = serviceInstances.get(0);
url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;*/
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}
public String queryByIdFallback(Long id){
log.error("查询用户信息失败。id:{}", id);
return "对不起,网络太拥挤了!";
}
public String defaultFallback(){
return "默认提示:对不起,网络太拥挤了!";
}
}
在实际开发中@HystrixCommand是加在Feign的接口上
要注意;因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。
失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以把queryById的方法改造为返回String, 反正也是Json数据。这样失败逻辑中返回一个错误说明,会比较方便。
主要1
:@HystrixCommand(fallbackMethod = "queryByIdFallBack"):用来声明一个降级逻辑的方法
主要2
:@DefaultProperties(defaultFallback = "defaultFallBack"):在类上指明统一的失败降级方法;该类中所有方法 返回类型要与处理失败的方法的返回类型一致。
修改超时配置
请求在超过1秒后都会返回错误信息,这是因为Hystrix的默认超时时长为1,我们可以通过配置修改这个值;修改消费者 application.yml 添加如下配置:(毫秒)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
在服务熔断中,使用的熔断器,也叫
断路器
,其英文单词为:Circuit Breaker
熔断机制与家里使用的电路熔断原理类似;当如果电路发生短路的时候能立刻熔断电路,避免发生灾难。在分布式系统中应用服务熔断后;服务调用方可以自己进行判断哪些服务反应慢或存在大量超时,可以针对这些服务进行主动熔断,防止整个系统被拖垮。
Hystrix的服务熔断机制,可以实现弹性容错;当服务请求情况好转之后,可以自动重连。通过断路的方式,将后续请求直接拒绝,一段时间(默认5秒)之后允许部分请求通过,如果调用成功则回到断路器
关闭状态,否则继续打开,拒绝请求的服务。
学习目标:了解熔断器工作原理
解析:
可以通过配置服务熔断参数修改默认:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20
isolation:
thread:
timeoutInMilliseconds: 2000
上述的配置项可以参考 HystrixCommandProperties 类中。
测试方法:
编写Controller
@GetMapping("{id}")
@HystrixCommand
public String queryById(@PathVariable("id") Long id){
if(id == 1){
throw new RuntimeException("太忙了");
}
String url = "http://user-service/user/" + id;
String user = restTemplate.getForObject(url, String.class);
return user;
}
注意:配置了默认的Fallback
这样如果参数是id为1,一定失败,其它情况都成功。(不要忘了清空user-service中的休眠逻辑)
我们准备两个请求窗口:
肯定成功 当我们疯狂访问id为1的请求时(超过20次),就会触发熔断。断路器会打开,一切请求都会被降级处理。