微服务概述与Spring Cloud
微服务:
强调的是服务的大小,它关注的是某一个点,是具体解决某一个问题/提供落地对应服务的一个服务应用,
狭意的看,可以看作Eclipse里面的一个个微服务工程/或者Module
从技术角度而言微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行单独启动或销毁,拥有自己独立的数据库。
微服务架构:
微服务架构是⼀种架构模式,它提倡将单⼀应⽤程序划分成⼀组⼩的服务,服务之间互相协调、互相配合,为⽤户提供最终价值。每个服务运⾏在其独⽴的进程中,服务与服务间采⽤轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进⾏构建,并且能够被独⽴的部署到⽣产环境、类⽣产环境等。另外,应当尽量避免统⼀的、集中式的服务管理机制,对具体的⼀个服务⽽⾔,应根据业务上下⽂,选择合适的语⾔、⼯具对其进⾏构建。
- - -Spring Cloud 项目搭建流程- - -
一、利用idea创建一个空项目
二、创建注册中心Eureka
file–>new–>Module–>Spring Initializer
目录结构:
1、启用注册中心
启动类上
启动一个服务注册中心,只需要一个注解 @EnableEurekaServer
/**
* 注册中心配置步骤
* 配置Eureka信息
* 在启动类中使用注解@EnableEurekaServer启用注册中心
*/
@EnableEurekaServer //启用注册中心
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
2、配置Eureka信息
application.yml
在application.yml文件中配置相关参数
server:
port: 8761 #服务的端口号
# eureka配置
eureka:
instance:
hostname: eureka-server # eureka实例的主机名
client: #eureka客户端配置
register-with-eureka: false #不把自己注册到eureka上
fetch-registry: false #不从eureka上来获取服务的注册信息
service-url: #指定eureka注册中心的地址
defaultZone: http://localhost:8761/eureka/
3、启动注册中心
Eureka Server 是有界面的,启动工程,打开浏览器访问:
http://localhost:8761
三、创建服务提供者
file–>new–>Module–>Spring Initializer
目录结构:
1、配置Eureka信息
application.yml
server:
port: 8762 #服务提供者的端口
spring:
application:
name: spring-cloud-provider #服务提供者的名字
#注册中心的配置
eureka:
instance:
prefer-ip-address: true # 注册服务的时候使用服务的ip地址
client:
serviceUrl: #指定eureka注册中心的地址
defaultZone: http://localhost:8761/eureka/
**注意:**需要指明 spring.application.name
,这个很重要,这在以后的服务与服务之间相互调用一般都是根据这个 name
2、声明为Eureka Client
启动类上
启动类上添加注解@EnableEurekaClient表明自己是一个Eureka Client
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient//表明自己是一个Eureka Client
@SpringBootApplication
public class SpringCloudProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudProviderApplication.class, args);
}
}
3、编写Controller层测试
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AdminController {
@Value("${server.port}")//将配置文件中配配置的端口号读进来
private String port;//8762
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String sayHi(@RequestParam(value = "msg") String msg) {
//格式化字符串返回
return String.format("地址栏信息为: %s ,端口号为 : %s", msg, port);
}
}
这时打开 http://localhost:8762/hi?message=前端数据 ,你会在浏览器上看到 :
打开 http://localhost:8761 ,即 Eureka Server 的网址,你会发现一个服务已经注册在服务中了,服务名为 SPRING-CLOUDPROVIDER
,端口为 8762
四、创建服务消费者
在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于 http restful 的。Spring cloud 有两种服务调用方式,一种是 ribbon + restTemplate,另一种是 feign。首先讲解下基于 ribbon + rest。
*创建基于Ribbon 的服务消费者
Ribbon 是一个负载均衡客户端,可以很好的控制 http
和 tcp
的一些行为。
1、创建消费者
file–>new–>Module–>Spring Initializer
目录结构:
在这里为了后面页面操作,引入了thymeleaf模板引擎。在pom文件中主要是增加了Ribbon的依赖
<!--引入模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--引入ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2、注册到服务中心(注册处中心)
启动类上
通过 @EnableDiscoveryClient
注解注册到服务中心
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient//注册到服务中心
@SpringBootApplication
public class SpringCloudRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudRibbonApplication.class, args);
}
}
3、配置Eureka信息
application.yml
spring:
application:
name: spring-cloud-ribbon #服务发现者的名字
#模板引擎的配置
thymeleaf:
cache: false
mode: LEGACYHTML5
encoding: UTF-8
servlet:
content-type: text/html
server:
port: 8764 #端口为8764
#Eureka设置
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka/
4、编写RestTemplate配置类
新建config.RestTemplateConfiguration类
配置注入 RestTemplate
的 Bean,并通过 @LoadBalanced
注解表明开启负载均衡功能
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 //表明这是一个配置类
public class RestTemplateConfiguration {
@Bean
@LoadBalanced //使用负载均衡机制(轮流模式)
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
5、编写Controller
import com.zzr.ribbon.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AdminController {
@Autowired //将service层注入
private AdminService adminService;
@RequestMapping(value = "hi",method = RequestMethod.GET)
public String sayHi(String msg){
//调用service层的方法,向服务提供者获取数据
return adminService.sayHi(msg);
}
}
6、编写Service层
service.AdminService类
在这里我们直接用的程序名替代了具体的 URL 地址,在 Ribbon 中它会根据服务名来选择具体的服务实例,根据服务实例在请求的时候会用具体的 URL 替换掉服务名,代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class AdminService {
@Autowired //注入配置类RestTemplate
RestTemplate restTemplate;
public String sayHi(String msg){
//利用RestTemplate向服务提供者获取数据 url:http://服务提供者的名字/请求参数
return restTemplate.getForObject("http://spring-cloud-provider/hi?msg="+msg,String.class);
}
}
测试访问
在浏览器上多次访问 http://localhost:8764/hi?msg=前端数据
浏览器交替显示下面的信息。达到了负载均衡的效果(轮流机制):
地址栏信息为: 前端信息 ,端口号为 : 8762
地址栏信息为: 前端信息 ,端口号为 : 8763
此时的架构:
- 一个服务注册中心,Eureka Server,端口号为:
8761
- 服务提供者
spring-cloud-provide
工程运行了两个实例,端口号分别为:8762
,8763
- 服务消费者
spring-cloud-ribbon
工程端口号为:8764
spring-cloud-ribbon
(消费者) 通过RestTemplate
调用spring-cloud-provide
(服务者) 接口时因为启用了负载均衡功能故会轮流调用它的8762
和8763
端口
*创建基于Feign的服务消费者
Feign 是一个声明式的伪 Http 客户端,它使得写 Http 客户端变得更简单。使用 Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用 Feign 注解和 JAX-RS 注解。Feign 支持可插拔的编码器和解码器。Feign 默认集成了 Ribbon,并和 Eureka 结合,默认实现了负载均衡的效果
- Feign 采用的是基于接口的注解
- Feign 整合了 ribbon
1、创建服务消费者
file–>new–>Module–>Spring Initializer
目录结构:
在这里为了后面页面操作,引入了thymeleaf模板引擎。在pom文件中主要是增加了OpenFeign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、开启Feign功能
启动类
通过 @EnableFeignClients
注解开启 Feign 功能
@EnableFeignClients //开启Feign功能
@EnableDiscoveryClient //注册到服务中心
@SpringBootApplication
public class SpringCloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFeignApplication.class, args);
}
}
3、配置Eureka信息
application.yml
spring:
application:
name: spring-cloud-feign #服务发现者的名字
#模板引擎的配置
thymeleaf:
cache: false
mode: LEGACYHTML5
encoding: UTF-8
servlet:
content-type: text/html
server:
port: 8765 #端口为8765
#Eureka设置
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka/
4、创建 Feign 接口
新建service.AdminService类
通过 @FeignClient("服务名")
注解来指定调用哪个服务。代码如下:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "spring-cloud-provider")//value=服务提供者的名字
public interface AdminService {
@RequestMapping(value = "hi",method = RequestMethod.GET)
//如果前端页有参数传过来,此处必须加上RequestParam注解用于获取参数
public String sayHi(@RequestParam(value = "msg") String msg);
}
5、创建测试用的 Controller
import com.zzr.feign.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AdminController {
@Autowired//注入Feign接口
private AdminService adminService;
@RequestMapping(value = "sayhi",method = RequestMethod.GET)
public String sayHi(String msg){
return adminService.sayHi(msg);
}
}
测试访问
在浏览器上多次访问 http://localhost:8765/sayhi?msg=前端数据
浏览器交替显示:
地址栏信息为: 前端信息 ,端口号为 : 8762
地址栏信息为: 前端信息 ,端口号为 : 8763
—使用熔断器防止服务雪崩—
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 RPC 相互调用,在 Spring Cloud 中可以用 RestTemplate + Ribbon 和 Feign 来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。
为了解决这个问题,业界提出了熔断器模型。
Netflix 开源了 Hystrix 组件,实现了熔断器模式,Spring Cloud 对这一组件进行了整合。在微服务架构中,一个请求需要调用多个服务是非常常见的,如下图:
较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystrix 是 5 秒 20 次) 熔断器将会被打开。
熔断器打开后,为了避免连锁故障,通过 fallback
方法可以直接返回一个固定值。
Ribbon 中使用熔断器
1、引入依赖
ribbon服务的pom.xml
在搭建好的项目的基础上,在pom文件中引入熔断器的依赖
<!--引入熔断器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、开启熔断器
启动类
在 启动类 中增加 @EnableHystrix
注解开启熔断器
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@EnableHystrix //开启熔断器
@EnableDiscoveryClient //注册到服务中心
@SpringBootApplication
public class SpringCloudRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudRibbonApplication.class, args);
}
}
3、在 Service 中增加 @HystrixCommand 注解
service.AdminService
在 Ribbon 调用方法上增加 @HystrixCommand
注解并指定 fallbackMethod
熔断方法
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class AdminService {
@Autowired //注入配置类RestTemplate
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "error")//熔断器注解 失败后回调error方法
public String sayHi(String msg){
//利用RestTemplate向服务提供者获取数据 url:http://服务提供者的名字/请求参数
return restTemplate.getForObject("http://spring-cloud-provider/hi?msg="+msg,String.class);
}
//熔断方法 连不上服务后执行此方法返回,就不会一直阻塞了
public String error(String msg){
return String.format("前端的消息为:%s, 但是请求失败了!!",msg);
}
}
测试熔断器
关闭服务提供者,再次请求 http://localhost:8764/hi?msg=前端数据,浏览器会显示:
Feign 中使用熔断器
1、开启熔断器
Feign 是自带熔断器的,但默认是关闭的。需要在配置文件中配置打开它,在配置文件增加以下代码:
application.yml
feign:
hystrix:
enabled: true #开启熔断器
2、创建熔断器类并实现对应的 Feign 接口
/**
* 熔断器类
* 当阻塞时执行这个类中的熔断方法,直接返回
*/
import com.zzr.feign.service.AdminService;
import org.springframework.stereotype.Component;
@Component //交由容器管理
public class AdminServiceHystrix implements AdminService {
//重写Service接口中的方法,当阻塞时执行此类中的方法
@Override
public String sayHi(String msg) {
//直接返回消息,就不会导致一直阻塞
return String.format("前端的消息为:%s, 但是请求失败了!!",msg);
}
}
目录结构:
3、在 Service 中增加 fallback
指定类
import com.zzr.feign.service.hystrix.AdminServiceHystrix;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
//@FeignClient(value = "spring-cloud-provider")//value=服务提供者的名字
//增加熔断器后的写法 fallback=熔断器类.class 本service类的实现类
@FeignClient(value = "spring-cloud-provider",fallback = AdminServiceHystrix.class)
public interface AdminService {
@RequestMapping(value = "hi",method = RequestMethod.GET)
//如果前端页有参数传过来,此处必须加上RequestParam注解用于获取参数
public String sayHi(@RequestParam(value = "msg") String msg);
}
测试熔断器
关闭服务提供者,再次请求 http://localhost:8765/hi?msg=前端数据,浏览器会显示:
使用熔断器仪表盘监控
在 Ribbon 和 Feign 项目增加 Hystrix 仪表盘功能,两个项目的改造方式相同
1、在 pom.xml
中增加依赖
<!--熔断器仪表盘 监视仪熔断器状态-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
2、开启熔断器仪表盘
在 Application 中增加 @EnableHystrixDashboard
注解,开启熔断器仪表盘
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableHystrixDashboard//开启熔断器仪表盘
@EnableFeignClients //开启Feign功能
@EnableDiscoveryClient //注册到服务中心
@SpringBootApplication
public class SpringCloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFeignApplication.class, args);
}
}
3、创建 hystrix.stream
的 Servlet 配置
Spring Boot 2.x 版本开启 Hystrix Dashboard 与 Spring Boot 1.x 的方式略有不同,需要增加一个 HystrixMetricsStreamServlet
的配置,代码如下:
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //表明这是一个配置类
public class HystrixDashboardConfiguration {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
测试 Hystrix Dashboard
浏览器端访问 http://localhost:8765/hystrix 界面如下:
再访问前面的请求,就可以在这里监控到:
什么情况下会触发 fallback
(失败回调-熔断)方法
名字 | 描述 | 触发fallback |
---|---|---|
EMIT | 值传递 | NO |
SUCCESS | 执行完成,没有错误 | NO |
FAILURE | 执行抛出异常 | YES |
TIMEOUT | 执行开始,但没有在允许的时间内完成 | YES |
BAD_REQUEST | 执行抛出HystrixBadRequestException | NO |
SHORT_CIRCUITED | 断路器打开,不尝试执行 | YES |
THREAD_POOL_REJECTED | 线程池拒绝,不尝试执行 | YES |
SEMAPHORE_REJECTED | 信号量拒绝,不尝试执行 | YES |
fallback
方法在什么情况下会抛出异常
名字 | 描述 | 抛异常 |
---|---|---|
FALLBACK_EMIT | Fallback值传递 | NO |
FALLBACK_SUCCESS | Fallback执行完成,没有错误 | NO |
FALLBACK_FAILURE | Fallback执行抛出出错 | YES |
FALLBACK_REJECTED | Fallback信号量拒绝,不尝试执行 | YES |
FALLBACK_MISSING | 没有Fallback实例 | YES |