一、单体项目应用存在的问题
一个成功的应用必然有一个趋势:用户量会不断增加,项目的业务也会不断扩展用户量的增加也会带来高并发的问题,高并发问题的解决方案
- 应用服务器:–> 单体优化–>集群(负载均衡,分布式并发)
- 数据库服务器 -->数据库优化 --> 缓存redis–>分布式数据库
项目业务的扩展,也会带来一些问题
- 项目结构越来越臃肿(项目结构和代码复杂,项目体积逐渐变得庞大)
- 项目结构和代码复杂导致项目不易维护和二次开发、扩展和更新就会变得困难
- 项目体积逐渐变得庞大导致启动时间越来越长,生产力大受限制
- 单体应用中任何一个模块的任何一个bug都会导致整个系统不可用(单点故障)
- 复杂的单体项目也会带来持续部署的障碍
- 单体项目使得采用新的技术和框架变得困难
二、微服务架构
2.1 微服务架构的概念
微服务架构,是一种架构概念,就是将一个单体应用中的每个功能分解到各各离散的服务中,以实现对单体项目解耦,并提供更加灵活的服务支持
2.2 微服务架构优点
- 解决了单体项目的复杂性问题
- 每个服务都可以由单独的团队开发
- 每个服务都可以使用单独的技术栈进行开发
- 每个服务都是独立的进行部署和维护
- 每个服务都可以独立进行扩展
2.3 微服务架构缺点
- 微服务架构的本身就是一个缺点,如何把握"微"的粒度
- 微服务架构是一个分布式系统,虽然单个服务变得简单,但是服务之间存在相互的调用,整个服务架构系统变得复杂
- 微服务架构还需要依赖分布式架构
- 微服务的单元测试及调用比较复杂
- 部署基于微服务架构的应用程序变得非常复杂
- 进行微服务架构的应用程序开发的技术成本变得更高
三、微服务架构开发需要解决的问题
在微服务架构开发的系统中,必然会存在很多个服务,服务之间需要需要感知对方的存在,需要进行服务间的调用,该如何实现呢?—进行微服务架构开发需要解决的问题:
- 如此多的服务,服务之间如何相互发现
- 服务与服务之间该如何通信?
- 如果某个服务挂了,该如何处理
- 前端访问多个不同的服务时该如何统一访问路径呢
3.1 服务之间如何相互发现的问题
微服务架构 — 每个服务只处理一件事情/一个步骤,在一个复杂的业务中必然存在服务之间的相互调用,服务想要相互调用就需要先发现对方
- 服务注册与发现中心也是一台独立服务器
- 1.服务提供者在服务注册与发现中心进行注册
- 2.服务注册与发现中心进行服务记录,并与服务提供者保持心跳
- 3.服务消费者通过服务注册与发现中心进行服务查询(服务发现)
- 4.服务注册与发现中心返回可用的服务的服务地址列表
- 5.服务消费通过负载均衡访问服务提供者
3.2 服务之间如何进行通信
服务消费者在调用服务提供者时,首先需要通过
服务注册与发现中心
进行服务查询,返回服务列表给服务消费者,服务消费者通过LoadBalance调用服务提供者
,那他们之间是如何通信的呢 ------数据传输规程服务与服务之间的通信方式有2种:
- 同步调用
- 异步调用
3.2.1 同步调用
- REST(SpringCloud(Netflix,SpringCloud Alibaba)) --------- 学这个√
- 基于HTTP协议的请求和响应
- 更容易实现,技术更灵活
- 支持多种语言,同时可以实现跨客户端
- 适用面广
- RPC(Dubbo)
- 居于网络层协议通信
- 传输效率高
- 安心性更高
- 如果有统一的开发规则或者框架,开发效率是比较高的
3.2.2 异步调用
服务间的异步通信通常是通过消息队列实现的
3.3 服务挂了怎么办
3.3.1 服务故障雪崩
在一条服务中调用链中,因为某个服务节点的故障导致依赖这个服务的服务就会别阻塞,如果此时有大量的用户请求涌入,产生阻塞的用户就可能会因为自由被耗尽而导致服务器瘫痪。
服务之间存在依赖,单个服务的故障可能会导致整个系统造灾难,这就是服务故障的雪崩效应
3.3.2 如何预防(解决)
- 服务集群 ---- 尽量保证每个服务可用
- 服务降级与熔断 ----- 避免请求阻塞,造成正常的服务出现故障
3.4 客户端如何统一访问多个接口服务?
四、微服务架构框架
4.1主流的微服务架构框架
- Dubbo(阿里、开源apache):2012年推出、2014年停更、2015年又继续更新
- Dubbox(当当网基于Dubbo的更新)
- jd-hydra(京东基于Dubbo的更新)
- SpringCloud Netfix
- ServiceComb(CSE)华为2017年
4.2 SpringCloud简介
SpringCloud是一个基于SpringBoot实现的微服务架构应用开发框架,它为我们进行微服务架构应用开发提供了服务注册与发现、熔断器、网关路由、配置管理、负载均衡、消息总线、数据监控等一系列工具。
-
SpringCloud比较成熟的两个体系:
-
Spring Cloud Netflix
-
Spring Cloud Alibaba
-
4.3 Spring Cloud 核心组件
-
Spring Cloud Netflix
- Eureka 服务注册与发现中心,用于服务治理
- Ribbon:服务访问组件、进行服务调用,实现了负载均衡
- Hystrix:熔断器,服务容错管理
- Feign:服务访问组件(对Ribbon和Hystrix封装)
- zuul:网关组件
-
Spring Cloud Config配置管理的组件 ---- 分布式配置中心
-
Spring Cloud Bus 消息总线
-
Spring Cloud CXonsul 服务注册与发现中心(功能类似Eureka)
4.4 SpringCloud 版本介绍
- SpringCloud:A-H,2020.0.2
- SpringCloud 版本对SpringBoot版本是有依赖的
- A ---- 1.2
- B ---- 1.3
- C ---- 1.4
- D ---- 1.5
- F - G - H ---- 2.x+
五、搭建服务注册与发现中心
使用Spring Cloud Netflix 组件中的Eureka 搭建服务注册与发现中心
5.1 创建SpringBoot应用,添加依赖
- spring web
- eureka server
5.2 配置服务注册与发现中心
server:
port: 8761
spring:
application:
name: service-eureka
eureka:
client:
service-url:
defaultzone: http://192.168.43.223:8761/eureka
register-with-eureka: false
fetch-registry: false
5.3 在启动类中添加 @EnableEurekaServer
注解
package com.luo.service;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class ServiceEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceEurekaApplication.class, args);
}
}
5.4 运行及访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j6SqYIZy-1662558983807)(/Users/gen_hua/Desktop/Java_note/Markdown/服务与注册.png)]
六、服务注册
创建保存订单的服务(order-add)注册到服务注册中心
6.1 创建SpringBoot应用
创建Spring Boot 应用,完成功能开发
6.2 注册服务
将能够完成特点业务的Spring Boot 应用作为服务提供者,注册到服务注册与发现中心
6.2.1 添加依赖
- eureka-server 【注意版本】
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
6.2.2 配置application.yml
## 当前服务的服务端口
server:
port: 9001
## 当前服务的服务应用名,会作为服务唯一表示注册到eureke
spring:
application:
name: order-add
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_2010_sc?characterEncoding-utf-8
username: root
password: root
mybatis:
mapper-locations: classpath:mappers/*
type-aliases-package: com.luo.order.beans
## 配置Eureka服务注册与发现的地址
eureka:
client:
service-url:
defaultzone: http://localhost:8761/eureka
6.2.3 在当前服务应用启动类添加注解
package com.luo.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@MapperScan("com.luo.order.dao")
@EnableEurekaClient
public class OrderAddApplication {
public static void main(String[] args) {
SpringApplication.run(OrderAddApplication.class, args);
}
}
九、服务发现-Feign
9.1 基础配置
9.1.1 创建SpringBoot应用,添加依赖
- spring web
- eureka server
- OpenFeign
9.1.2 配置application.yml
server:
port: 8002
spring:
application:
name: api-order-add-feign
eureka:
client:
service-url:
defaultzone: http://localhost:8761/eureka
9.1.3 在启动类添加注解
- @EnableDiscoveryClient // 声明为服务消费者
- @EableFeignClients // 声明启用feign客户端
package com.luo.api.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient // 声明为服务消费者
@EnableFeignClients // 声明启用feign客户端
public class ApiOrderAddFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddFeignApplication.class, args);
}
}
9.2 服务调用
进行Feign进行服务调用的时候,需要手动创建一个服务访问客户端(接口)
9.2.1 创建Feign客户端
@FeignClient("order-add") //代表需要访问的服务客户端
public interface OrderAddClient {
@PostMapping("order/add")
public ResultVO addOrder(Order order);
}
9.2.2 使用Feign客户端调用服务
@Service
public class OrderServiceImpl implements OrderAddService {
// 就是上面的这个接口
@Autowired
private OrderAddClient orderAddClient;
@Override
public ResultVO saveOrder(Order order) {
ResultVO resultVO = orderAddClient.addOrder(order);
return resultVO;
}
}
9.3 Feign传参
9.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); }
-
通过请求行传值
- 服务提供者
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @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客户端)
@FeignClient("order-add") public interface OrderAddClient { //1、对于post调用服务,Feign客户端的方法参数默认为body传值,body每次只能有一个值 //2、如果有多个参数,则需要 @RequestParam 声明参数为请求行传值 @PostMapping("order/add") public ResultVO addOrder(Order order,@RequestParam("str") String str); }
9.3.2 Get请求
Get请求调用服务,只能通过url传参
在Feign客户端的方法中,如果不指定参数的传值方式,则默认为body传参,Get请求也不列为,因此对于Get传递参数,必须通过@RequestParam注解声明
- 服务提供者
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/get")
public Order getOrder(String orderId){
System.out.println(orderId);
return new Order();
}
}
- 服务消费者(Feign客户端中)
@FeignClient("order-add")
public interface OrderAddClient {
//必须加上 @RequestParam 注解才能传参
@GetMapping("order/get")
public Order getOrder(@RequestParam("orderId") String orderId);
}
十、服务注册与发现中心的可靠性和安全性
10.1 可靠性
在微服务的系统架构中,服务消费者是通过服务注册与发现中心发现服务、调用服务的,服务注册与发现中心服务器一旦挂掉,将会导致整个微服务架构系统的崩溃,如何保证Eureka的可靠性呢
- 使用eureka集群
Eureka集群搭建
相互注册、相互发现
server:
port: 8761
spring:
application:
name: service-eureka
eureka:
client:
service-url:
# 这里的 url 填写对方的url,就是把本Eureka搭建到对方的Eureka ,然后对方的 IP 写我的
defaultZone: http://192.168.43.223:8761/eureka
10.2 安全性
当完成 Eureka 的搭建之后,只要知道ip和端口就可以随意的注册服务、调用服务,这时不安全的,我们可以通过设置账号和密码来限制服务的注册及发现。
- 在eureka中中和Spring Security安全框架实现账号和密码验证
10.2.1 添加SpringSecurity依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.3</version>
</dependency>
10.2.2 设置访问eureka的账号和密码
spring:
security:
user:
name: luo
password: 123456
10.2.3 配置Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception{
http.csrf().disable();
//设置当前服务器的所有请求都要使用springsecurity的认证
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
}
10.2.4 服务提供者和服务消费者连接到注册中心都要账号和密码
eureka:
client:
service-url:
defaultZone: http://luo:123456@localhost:8761/eureka
十一、熔断器-Hystrix
服务故障的雪崩效应:当A服务调用B服务时,由于B服务的故障导致A服务处于阻塞状态,当量的请求可能会导致A服务因资源耗尽而出现故障
为了解决故障里的雪崩效应,出现了熔断器模型
在服务消费者A服务加入熔断器,如果B服务出现故障且频率达到一个特定的阈值,熔断器就断开,熔断器一但断开,服务消费者A服务在接收用户请求时将不在调用B服务,从而减少请求在A服务的阻塞
熔断器的作用:
- 服务降级:用户请求A服务,A服务调用B服务,当B服务出现故障或者在特定的时间段内不能给A服务响应,为了避免A服务因等待B服务而产生阻塞,A服务就不等B服务的结果了,直接给用户一个降级响应
- 服务熔断:用户请求A服务,A服务调用B服务,当B服务出现故障的频率过高达到特定阈值(5s 20次)时,当用户再请求A服务时,A服务将不再调用B服务,直接给用户一个降级响应
11.2 熔断器的原理
- 熔断器默认闭合(close)状态,当用户请求A服务时,A服务调用B服务,如果B服务在设定的时间不能给A服务响应,A服务则使用降级方案响应,同时记录B服务的故障
- 当B服务的故障率达到阈值(Hystrix默认 5s/20次),熔断器就会被断开,进入‘打开’状态
- 当熔断器为‘打开’状态时,用户请求A服务,A服务不在调用B服务,而是直接进行降级响应
- 状态为‘打开’状态的熔断器经过一个时间周期后会进入到‘半开’的状态
- 当熔断器为‘半开’状态时,当用户请求A服务时,A服务会对B服务进行一次调用,如果B成功响应A服务,熔断器则进入闭合状态,如果B服务响应失败,熔断器则回到‘打开’状态,再进入到下一个周期的熔断
11.3基于Feign服务调用的熔断使用
Feign是基于Ribbon和Hysgrix的封装
11.3.1 配置熔断器
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
- 在application.yml启用熔断机制
feign:
hystrix:
enable: true
- 在启动类中添加
@EnableHystrix
注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class ApiOrderAddFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ApiOrderAddFeignApplication.class, args);
}
}
- 创建服务降级处理类
package com.luo.api.order.serice.fallback;
import com.luo.beans.Order;
import com.luo.vo.ResultVO;
import org.springframework.stereotype.Component;
@Component
public class OrderAddClientFallback {
public ResultVO addOrder(Order order, String str) {
System.out.println("--------------addOrder 的降级服务");
return ResultVO.fail("调用失败",null);
}
public Order getOrder(String orderId) {
System.out.println("--------------getOrder 的降级服务");
return new Order();
}
}
- 在Feign客户端指定降级处理类 加上@FeignClient(value = “order-add”,fallback= OrderAddClientFallback.class)
@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);
}