一、微服务架构
1.1 微服务架构概念
微服务架构,是一种架构概念,就是将一个单体应用中的每个功能分解到各个离散的服务中,以实现对单体应用的解耦,并提供更加灵活的服务支持
1.2 微服务架构优点
- 解决了单体项目的复杂性问题
- 每个服务都可以由单独的团队进行开发
- 每个服务都可以使用单独的技术栈进行开发
- 每个服务都是独立的进行部署和维护
- 每个服务都可以独立进行扩展
1.3 微服务架构缺点
- 微服务架构本身就是一个缺点:如何把握”微“的粒度
- 微服务架构是一个分布式系统,虽然单个服务变得简单了,但是服务之间存在互相调用,整个服务架构的系统变得更复杂了
- 微服务架构需要依赖分布式数据库架构
- 微服务的单元测试及调用变得比单体更为复杂
- 部署基于微服务架构的应用程序变得非常复杂
- 进行微服务架构的应用程序开发的技术成本变得更高
二、微服务架构开发需要解决的问题
在微服务架构开发的系统中必然存在很多个服务,服务之间需要相互感知对方的存在,需要进行服务间的调用,该如何实现呢?
1.如此多的服务间如何互相发现?
2.服务之间如何通信?
3.服务出现故障如何处理?
4.前端访问多个不同的服务时,该如何统一访问路径?
2.1服务之间如何相互发现
微服务架构——每个服务只处理一件事情/一个步骤,在一个复杂的业务中必然存在服务间的互相调用,服务想要相互调用必须先发现对方。
服务注册与发现中心
也是一台独立的服务器
-
服务提供者
在服务注册与发现中心
进行注册 -
服务注册与发现中心
进行服务记录,并与服务提供者
保持心跳 -
服务消费者
通过服务注册与发现中心
进行服务查询(服务发现) -
服务注册与发现中心
返回可用的服务的服务器地址列表 -
服务消费者
通过负载均衡访问服务提供者
2.2服务之间如何通信
服务消费者
在调用服务提供者时,首先需要通过服务注册与发现中心
进行服务查询,返回服务列表给服务消费者。服务消费者
通过LoadBalance
调用服务提供者
。它们之间通过数据传输规则进行通信,即同步调用和异步调用
2.2.1 同步调用
- REST(SpringCloud Netflix、SpringCloud Alibaba)
- 基于HTTP协议的请求和响应
- 更容易实现、技术更灵活
- 支持多语言、同时可以实现跨客户端
- 适用面很广
- RPC(Dubbo)
- 基于网络层协议通信
- 传输效率高
- 安全性更高
- 如果有统一的开发规划或者框架,开发效率比较高
2.2.2 异步调用
服务间的异步通信通常是通过消息队列实现的
2.3 服务故障如何解决
2.3.1 服务故障雪崩
在一条服务调用链中,因为某个服务节点的故障导致依赖这个服务的服务被阻塞,如果此时大量的用户请求涌入,产生阻塞的服务就可能会因为资源耗尽而导致服务瘫痪。
服务之间存在依赖,单个服务的故障可能会对整个系统造成灾难性的后果,这就是服务故障的”雪崩效应“。
2.3.2 解决雪崩问题
- 服务集群——尽量保证每个服务可用
- 服务降级与熔断
2.4 客户端如何访问多个接口服务
通过路由网关实现接口的统一访问
三、微服务架构框架
3.1主流的微服务架构框架
- Dubbo (阿里--开源 Apache)
- Dubbox (当当网基于 Dubbo)
- jd-hydra (京东基于 Dubbo)
-
SpringCloud Netflix
/SpringCloud Alibaba
- ServiceComb(华为)
3.2 SpringCloud简介
SpringCloud 是一个基于 SpringBoot 实现的微服务架构应用开发框架,并提供了针对微服务架构应用开发的
服务注册与发现
、熔断器
、网关路由
、配置管理
、负载均衡
、消息总线
、数据监控
等一系列工具
3.3 SpringCloud核心组件
- SpringCloud Netflix
- Eureka 服务注册与发现中心,用于服务治理
- Ribbon 服务访问组件,用于服务调用,实现负载均衡
- Hystrix 熔断器,用于服务容错管理
- Feign 服务访问组件(对 Ribbon 和 Hystrix 的封装)
- zuul 网关组件
- SpringCloud Config 配置管理组件——分布式配置中心
- SpringCloud Bus 消息总线
- SpringCloud Consul 服务注册与发现中心(功能类似 Eureka)
四、搭建服务注册与发现中心
使用 SpringCloud Netflix 中的 Eureka 搭建服务注册与发现中心
4.1 创建SpringBoot应⽤,添加依赖
- spring web
- eureka server
4.2 配置服务注册与发现中⼼
## 设置服务注册与发现中⼼的端⼝
server:
port: 8761
## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的
## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名
spring:
application:
name: service-eureka
eureka:
client:
## ip 就是服务注册中⼼服务器的ip
## port 就是服务注册与发现中⼼设置的port
service-url:
defaultZone: http://...:8761/eureka
## 设置服务注册与发现中⼼是否为为集群搭建(如果为集群模式,多个eureka节点之间需要相互注册)
register-with-eureka: false
## 设置服务注册与发现中是否作为服务进⾏注册
fetch-registry: false
4.3 在启动类添加@EnableEurekaServer
注解
@SpringBootApplication
@EnableEurekaServer
public class ServiceEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceEurekaApplication.class, args);
}
}
五、服务注册
创建服务注册到服务注册与发现中⼼
5.1 创建SpringBoot应⽤
创建 SpringBoot 应⽤,完成功能开发
5.2 注册服务
将能够完成特定业务的 SpringBoot 应⽤作为服务提供者,注册到服务注册与发现中⼼
5.2.1 添加依赖
-
eureka-server
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
5.2.2 配置application.yml
## 当前服务的port
server:
port: 9001
## 当前应⽤名会作为服务唯⼀标识注册到eureka
spring:
application:
name: ...
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/...?characterEncoding=utf-8
username: ...
password: ...
mybatis:
mapper-locations: classpath:mappers/*
type-aliases-package: ...
## 配置Eureka服务注册与发现中⼼的地址
eureka:
client:
service-url:
defaultZone: http://...:8761/eureka
5.2.3 在当前服务应⽤的启动类添加@EnableEurekaClient
注解
@SpringBootApplication
@MapperScan("...")
@EnableEurekaClient
public class ClientEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(ClientEurekaApplication.class, args);
}
}
六、服务发现-Ribbon
服务消费者通过 eureka 查找服务提供者,通过服务调⽤组件调⽤提供者
- eureka server
- ribbon
6.1 基础配置
6.1.1创建SpringBoot应⽤,添加依赖
- eureka server
- ribbon
6.1.2 配置application.yml
server:
port: 8001
spring:
application:
name: ...
eureka:
client:
service-url:
defaultZone: http://...:8761/eureka
6.1.3 在启动类添加@EnableDiscoveryClient
注解
@SpringBootApplication
@EnableDiscoveryClient
public class ApiDiscoveryClientApplication {
public static void main(String[] args) {
SpringApplication.run(ApiDiscoveryClientApplication.class, args);
}
}
6.2 服务调用
6.2.1 配置RestTemplate
@Configuration
public class AppConfig {
@LoadBalanced //启⽤Ribbon(负载均衡)
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
6.2.2 在Service中注⼊RestTemplate对象调⽤服务
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private RestTemplate restTemplate;
@Override
public ResultVO saveOrder(Order order) {
//1. 调⽤ order-add服务进⾏保存
ResultVO vo = restTemplate.postForObject("http://order-add/order/add", order, ResultVO.class);
//2. 调⽤ orderitem-add 保存订单快照
//3. 调⽤ stock-update 修改商品库存
//4. 调⽤ shopcart-del 删除购物⻋记录
return null;
}
}
6.3 Ribbon服务调⽤说明
@LoadBalanced 注解是 Ribbon 的⼊⼝,在 RestTemplate 对象上添加此注解之后,再使⽤ RestTemplate 发送REST请求的时候,就可以通过 Ribbon 根据服务名称从 Eureka 中查找服务对应的访问地址列表,再根据负载均衡策略(默认轮询)选择其中的⼀个,然后完成服务的调⽤
- 获取服务列表
- 根据负载均衡策略选择服务
- 完成服务调⽤
七、基于Ribbon进⾏服务调⽤的参数传递
7.1 RestTemplate发送调⽤请求的⽅法
SpringCloud的服务调⽤是基于 REST 的,因此当服务提供者规定了请求的⽅式,服务消费者必须发送对应⽅式的请求才能完成服务的调⽤,RestTemplate 提供了多个⽅法⽤于发送不同形式的请求
//post⽅式请求
restTemplate.postForObject();
//get⽅式请求
restTemplate.getForObject();
//delete⽅式请求
restTemplate.delete();
//put⽅式请求
restTemplate.put();
7.2 put/post请求传参
-
服务消费者请求传参
//参数1:访问服务的url //参数2:传递的对象参数 //参数3:指定服务提供者返回的数据类型 ResultVO vo = restTemplate.postForObject("http://order-add/order/add", order, ResultVO.class);
-
服务提供者接收参数
@PostMapping("/add") public ResultVO addOrder(@RequestBody Order order){ return orderService.saveOrder(order); }
7.3 get请求传参
-
服务消费者请求传参
String userId = order.getUserId(); ResultVO vo = restTemplate.getForObject("http://order-add/order/add? userId="+userId, ResultVO.class);
-
服务提供者接收参数
@GetMapping("/add") public ResultVO addOrder(Order order){ return orderService.saveOrder(order); }
八、服务发现-Feign
8.1 基础配置
8.1.1 创建SpringBoot应用,添加依赖
- spring web
- eureka server
- openfeign
8.1.2 配置application.yml
server:
port: 8002
spring:
application:
name: api-order-add-feign
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
8.1.3 在启动类添加注解
@SpringBootApplication
@EnableDiscoveryClient //声明为服务消费者
@EnableFeignClients //声明启⽤feign客户端
public class ApiOrderAddFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddFeignApplication.class, args);
}
}
8.2 服务调用
使⽤Feign进⾏服务调⽤的时候,需要⼿动创建⼀个服务访问客户端(接⼝)
8.2.1 创建Feign客户端
@FeignClient("order-add")
public interface OrderAddClient {
@PostMapping("order/add")
public ResultVO addOrder(Order order);
}
8.2.2 使用Feign客户端调用服务
@Service
public class OrderAddServiceImpl implements OrderAddService {
@Autowired
private OrderAddClient orderAddClient;
@Override
public ResultVO saveOrder(Order order) {
//1. 调⽤ order-add服务进⾏保存
ResultVO vo = orderAddClient.addOrder(order);
//2. 调⽤ orderitem-add 保存订单快照
//3. 调⽤ stock-update 修改商品库存
//4. 调⽤ shopcart-del 删除购物⻋记录
return vo;
}
}
8.3 Feign传参
8.3.1 POST请求
-
通过请求体传递对象
-
服务提供者
@PostMapping("/add") public ResultVO addOrder(@RequestBody Order order){ System.out.println("-------------------order-add"); System.out.println(order); return orderService.saveOrder(order); }
-
服务消费者(Feign客户端)
@FeignClient("order-add") public interface OrderAddClient { @PostMapping("order/add") public ResultVO addOrder(Order order); }
-
-
通过请求行传参
-
服务提供者
@PostMapping("/add") public ResultVO addOrder(@RequestBody Order order,String str){ System.out.println("-------------------order-add"); System.out.println(order); System.out.println(str); return orderService.saveOrder(order); }
-
服务消费者( Feign 客户端)
//1.对⽤POST请求调⽤服务,Feign客户端的⽅法参数默认为body传值(body只能有⼀个值) //2.如果有多个参数,则需要通过 @RequestParam 声明参数为请求⾏传值 @PostMapping("order/add") public ResultVO addOrder(Order order,@RequestParam("str") String str);
-
8.3.2 GET请求
Get请求调⽤服务,只能通过url传参
在Feign客户端的⽅法中,如果不指定参数的传值⽅式,则默认为 body 传参,Get 请求也不例外;因此对于 Get 请求传递参数,必须通过 @RequestParam 注解声明
-
服务提供者
@GetMapping("/get") public Order addOrder(String orderId){ return new Order(); }
-
服务消费者( Feign 客户端)
@GetMapping("order/get") public Order getOrder(@RequestParam("orderId") String orderId);
九、服务注册与发现中⼼的可靠性和安全性
9.1 可靠性(Eureka集群搭建)
在微服务架构系统中,服务消费者是通过服务注册与发现中⼼发现服务、调⽤服务的,服务注册与发现中⼼服务器⼀旦故障,将会导致整个微服务架构系统的崩溃,如何保证 Eureka 的可靠性呢?
——使⽤eureka集群
## 设置服务注册与发现中⼼的端⼝
server:
port: 8761
## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的
## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名
spring:
application:
name: service-eureka
eureka:
client:
## 设置服务注册与发现中⼼是否为集群搭建
register-with-eureka: true
## 设置服务注册与发现中是否作为服务进⾏注册
fetch-registry: true
## ip 就是服务注册中⼼服务器的ip
## port 就是服务注册与发现中⼼设置的port
service-url:
defaultZone: http://IP:8761/eureka
9.2 安全性(整合SpringSecurity)
当完成 Eureka 的搭建之后,只要知道ip和port就可以随意的注册服务、调⽤服务,这是不安全的,我们可以通过设置帐号和密码来限制服务的注册及发现。
——整合Spring Security
## 设置服务注册与发现中⼼的端⼝
server:
port: 8761
## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的
## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名
spring:
application:
name: service-eureka
eureka:
client:
## 设置服务注册与发现中⼼是否为集群搭建
register-with-eureka: true
## 设置服务注册与发现中是否作为服务进⾏注册
fetch-registry: true
## ip 就是服务注册中⼼服务器的ip
## port 就是服务注册与发现中⼼设置的port
service-url:
defaultZone: http://IP:8761/eureka
9.2.1 添加SpringSecurity的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
9.2.2 设置访问eureka的帐号和密码
spring:
security:
user:
name: ...
password: ...
9.2.3 配置Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
//设置当前服务器的所有请求都要使⽤spring security的认证
http.authorizeRequests().anyRequest().authenticated().and().httpBasic(
);
}
}
9.2.4 服务提供者和服务消费者连接到注册中⼼都要帐号和密码
eureka:
client:
service-url:
defaultZone: http://账号:密码@localhost:8761/eureka
十、熔断器-Hystrix
服务故障的雪崩效应:当A服务调⽤B服务时,由于B服务的故障导致A服务处于阻塞状态,当量的请求可能会导致A服务因资源耗尽⽽出现故障。
为了解决服务故障的雪崩效应,出现了熔断器模型。
10.1 简介
熔断器作⽤:
-
服务降级
:⽤户请求A服务,A服务调⽤B服务,当B服务出现故障或者在特定的时间段内不能给A服务响应,为了避免A服务因等待B服务⽽产⽣阻塞,A服务就不等B服务的结果了,直接给⽤户⼀个降级响应 -
服务熔断
:⽤户请求A服务,A服务调⽤B服务,当B服务出现故障的频率过⾼达到特定阈值(5s 20次)时,当⽤户再请求A服务时,A服务将不再调⽤B服务,直接给⽤户⼀个降级响应
10.2 原理
10.3 基于Ribbon服务调⽤的熔断器使⽤
10.3.1 服务消费者的服务降级
-
添加熔断器依赖hystrix
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
在启动类添加
@EnableHystrix
注解@SpringBootApplication @EnableDiscoveryClient @EnableHystrix public class ApiOrderAddApplication { public static void main(String[] args) { SpringApplication.run(ApiOrderAddApplication.class, args); } }
-
在调⽤服务提供者的业务处理⽅法中,进⾏降级配
@Service public class OrderAddServiceImpl implements OrderAddService { @Autowired private RestTemplate restTemplate; @HystrixCommand(fallbackMethod = "fallbackSaveOrder",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000") }) public ResultVO saveOrder(Order order) { //1. 调⽤ order-add服务进⾏保存 //参数1:访问服务的url //参数2:传递的对象参数 //参数3:指定服务提供者返回的数据类型 ResultVO vo = restTemplate.postForObject("http://order-add/order/add",order, ResultVO.class); System.out.println(vo); //2. 调⽤ orderitem-add 保存订单快照 //3. 调⽤ stock-update 修改商品库存 //4. 调⽤ shopcart-del 删除购物⻋记录 return vo; } /** * 降级⽅法:与业务⽅法拥有相同的参数和返回值 * @return */ public ResultVO fallbackSaveOrder(Order order){ return ResultVO.fail("⽹络异常,请重试!",null); } }
10.3.2 服务提供者的服务降级
配置步骤⼀致
-
服务提供者接⼝降级
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @HystrixCommand(fallbackMethod = "fallbackAddOrder",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000") }) @PostMapping("/add") public ResultVO addOrder(@RequestBody Order order){ System.out.println("-------------------order-add"); System.out.println(order); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return orderService.saveOrder(order); } public ResultVO fallbackAddOrder(@RequestBody Order order){ System.out.println("-------------------order-add--fallback"); return ResultVO.fail("订单保存失败!",null); } }
10.3.3 服务熔断配置
熔断器状态:闭合、打开、半开
-
服务熔断配置
@HystrixCommand(fallbackMethod ="fallbackSaveOrder",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000"), @HystrixProperty(name="circuitBreaker.enabled",value="true"),//启⽤服务熔断 @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="10000"),//时间 @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value="10"),//请求次数 @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="50"),//服务错误率 }) public ResultVO saveOrder(Order order) { //1. 调⽤ order-add服务进⾏保存 ResultVO vo = restTemplate.postForObject("http://order-add/order/add", order, ResultVO.class); System.out.println(vo); //2. 调⽤ orderitem-add 保存订单快照 //3. 调⽤ stock-update 修改商品库存 //4. 调⽤ shopcart-del 删除购物⻋记录 return vo; } /** * 降级⽅法:与业务⽅法拥有相同的参数和返回值 * @return */ public ResultVO fallbackSaveOrder(Order order){ return ResultVO.fail("⽹络异常,请重试!",null); }
服务熔断:当⽤户请求服务A,服务A调⽤服务B时,如果服务B的故障率达到特定的阈值时,熔断器就会被打开⼀个时间周期(默认5s,可⾃定义),在这个时间周期内如果⽤户请求服务A,服务A将不再调⽤服务B,⽽是直接响应降级服务。
10.4 基于Feign服务调⽤的熔断器使⽤
Feign是基于Ribbon和Hystrix的封装
10.4.1 Feign中的熔断器使⽤
-
添加依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.11.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR11</spring-cloud.version> </properties> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
在application.yml启⽤熔断器机制
feign: hystrix: enabled: true
-
在启动类添加 @EnableHystrix
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @EnableHystrix public class ApiOrderAddFeignApplication { public static void main(String[] args) { SpringApplication.run(ApiOrderAddFeignApplication.class,args); } }
-
创建服务降级处理类
FeignClient的服务降级类:1.必须实现Feign客户端接⼝,2.必须交给Spring容器管理
@Component public class OrderAddClientFallback implements OrderAddClient { public ResultVO addOrder(Order order, String str) { System.out.println("-------addOrder的降级服务"); return ResultVO.fail("fail",null); } public Order getOrder(String orderId) { System.out.println("-------getOrder的降级服务"); return new Order(); } }
-
在 Feign 客户端指定降级处理类
@FeignClient(value = "order-add", fallback = OrderAddClientFallback.class) public interface OrderAddClient { //1.对⽤POST请求调⽤服务,Feign客户端的⽅法参数默认为body传值(body只能有⼀个值) //2.如果有多个参数,则需要通过@RequestParam声明参数为请求⾏传值 @PostMapping("order/add") public ResultVO addOrder(Order order,@RequestParam("str") String str); @GetMapping("order/get") public Order getOrder(@RequestParam("orderId") String orderId); }
-
Service类通过Feign客户端调⽤服务
@Service public class OrderAddServiceImpl implements OrderAddService { @Autowired private OrderAddClient orderAddClient; //当我们创建Feign客户端的降级类并交给Spring管理后 在Spring容器中就会出现两个OrderAddClient对象 @Override public ResultVO saveOrder(Order order) { //1. 调⽤ order-add服务进⾏保存 ResultVO vo = orderAddClient.addOrder(order,"测试字符串"); Order order1 = orderAddClient.getOrder("订单编号"); System.out.println(order1); //2. 调⽤ orderitem-add 保存订单快照 //3. 调⽤ stock-update 修改商品库存 //4. 调⽤ shopcart-del 删除购物⻋记录 return vo; } }
10.5熔断器仪表盘监控
如果服务器的并发压⼒过⼤、服务器⽆法正常响应,则熔断器状态变为open属于正常的情况;但是如果⼀个熔断器⼀直处于open状态、或者说服务器提供者没有访问压⼒的情况下熔断器依然为open状态,说明熔断器的状态就不属于正常情况了。如何了解熔断器的⼯作状态呢 ?
——熔断器仪表盘
10.5.1 搭建熔断器仪表盘
-
创建SpringBoot项目,添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
-
配置仪表盘的port和appName
server: port: 9999 spring: application: name: hystrix-dashboard hystrix: dashboard: proxy-stream-allow-list: "localhost"
-
启动类添加
@EanbleHystrixDashboard
注解@SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardApplication { public static void main(String[] args) { SpringApplication.run(HystrixDashboardApplication.class,args); } }