将项目所有模块(功能)打成jar或者war,然后部署一个进程
优点:
1:部署简单:由于是完整的结构体,可以直接部署在一个服务器上即可。
2:技术单一:项目不需要复杂的技术栈,往往一套熟悉的技术栈就可以完成开发。
缺点:
1:系统启动慢,一个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动、重启时间周期过长;
2:系统错误隔离性差、可用性差,任何一个模块的错误均可能造成整个系统的宕机;
3:可伸缩性差:系统的扩容只能只对这个应用进行扩容,无法结合业务模块的特点进行伸缩。
4: 线上问题修复周期长:任何一个线上问题修复需要对整个应用系统进行全面升级。
5: 跨语言程度差
6: 不利于安全管理,所有开发人员都拥有全量代码
微服务架构论文: Microservices
译文: 微服务译文理解_发了个版的博客-CSDN博客_微服务架构译文
In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.
翻译后:
简单来说,微服务架构风格[1]是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术。
解读微服务特点:
1:微服务是一种项目架构思想(风格)
2:微服务架构是一系列小服务的组合(组件化与多服务)
3:任何一个微服务,都是一个独立的进程(独立开发、独立维护、独立部署)
4:轻量级通信http协议(跨语言,跨平台)
5:服务粒度(围绕业务功能拆分)
6:去中心化管理(去中心化"地治理技术、去中心化地管理数据)
1.易于开发和维护 一个微服务只关注一个特定的业务功能,所以它的业务清晰、代码量较少。开发和维护单个微服务相对比较简单,整个应用是由若干个微服务构建而成,所以整个应用也会维持在可控状态;
⒉.单个微服务启动较快 单个微服务代码量较少,所以启动会比较快;
3.局部修改容易部署 单体应用只要有修改,就要重新部署整个应用,微服务解决了这样的问题。一般来说,对某个微服务进行修改,只需要重新部署这个服务即可;
4.技术栈不受限 在微服务中,我们可以结合项目业务及团队的特点,合理地选择技术栈
5.按需伸缩
1、服务太多,导致服务间的依赖错综复杂,运维难度大
2、微服务放大了分布式架构的系列问题
分布式事务(seata)
分布式锁怎么处理(redisson) ,
服务注册与发现(nacos) .
依赖服务不稳定(sentinel)导致服务雪崩怎么办?
3、运维复杂度陡增,部署数量多、监控进程多导致整体运维复杂度提升。
Springcloud为微服务思想提供了完美的解决方案
Springcloud是一些列框架的集合体(服务的注册与发现【注册中心】、服务间远程调用、服务降级、服务熔断、服务限流、分布式事务等)
一般我们说springc1oud 其实指的是Springc1oud-netflix[netflix],Springcloud并不是造轮子,只是把Netflix公司的组件做二次开发. netflix对这些组件不在维护了,停止更新。
springclou-alibaba,springcloud并不是造轮子,只是把阿里巴巴公司的组件做了二次开发。
SpringBoot专注于快速方便的开发单个个体微服务。
SpringCloud是关注全局的微服务协调、整理、治理的框架,它将SpringBoot开发的单体整合并管理起来。
SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系
springcloud-alibaba的版本。
我们本次是使用的电商项目中的商品微服务、订单微服务为案例进行讲解。
基础知识:
springboot mybatis-plus maven mysql
注意: 因为父工程只负责jar的管理,不负责代码的编写。所以不需要src目录。修改打包方式为pom。
4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.12.RELEASE com.aaa qy158-springcloud-parent 0.0.1-SNAPSHOT pom qy158-springcloud-parent Demo project for Spring Boot 1.8 UTF-8 UTF-8 Hoxton.SR8 2.2.3.RELEASE org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import org.springframework.boot spring-boot-maven-plugin
所有微服务都需要的内容,可以提取到该模块中。【实体类,工具类等】
加入这些依赖
com.baomidou mybatis-plus-boot-starter 3.4.1 org.projectlombok lombok com.alibaba fastjson 1.2.56 mysql mysql-connector-java 创建实体类
@Data @TableName("shop_order") public class Order { @TableId(value = "oid",type = IdType.AUTO) private Integer oid; private Integer uid; private String username; private Integer pid; private String pname; private BigDecimal pprice; private Integer number; } @Data @TableName(value = "shop_product") public class Product { @TableId(value = "pid",type = IdType.AUTO) private Integer pid; private String pname; private BigDecimal pprice; private Integer stock; }
com.aaa qy158-springcloud-common 0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-web 修改配置文件
# 端口号 server.port=8080 # 数据源 spring.datasource.username=xxxx spring.datasource.password=xxxx spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?serverTimezone=Asia/Shanghai # 日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
业务功能
dao:
public interface ProductDao extends BaseMapper
{ } service:
@Service public class ProductServiceImpl implements ProductService { @Autowired private ProductDao productDao; @Override public Product findById(Integer pid) { return productDao.selectById(pid); } }
controller:
@RestController @RequestMapping("product") public class ProductController { @Autowired private ProductService productService; @GetMapping("getById/{pid}") public Product getById(@PathVariable Integer pid){ Product product = productService.findById(pid); return product; } }
启动类:
@SpringBootApplication @MapperScan(basePackages = "com.aaa.dao") public class ProductApp { public static void main(String[] args) { SpringApplication.run(ProductApp.class,args); } }
com.aaa qy158-springcloud-common 0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-web com.rabbitmq http-client 3.12.1 修改配置文件:
# 端口号 server.port=8080 # 数据源 spring.datasource.username=xxxx spring.datasource.password=xxxx spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?serverTimezone=Asia/Shanghai # 日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
业务功能:
dao:
public interface OrderDao extends BaseMapper
{ } service:
@Service public class OrderServiceImpl implements OrderService { @Resource private OrderDao orderDao; @Override public ComResult save(Order order) { int insert = orderDao.insert(order); return new ComResult(2000,"下单成功",null); } }
controller:
@RestController @RequestMapping("order") public class OrderController { @Autowired private OrderService orderService; @Autowired private RestTemplate restTemplate; @RequestMapping("/save/{pid}/{number}") public ComResult save(@PathVariable Integer pid,@PathVariable Integer number){ //封装一个订单类 Order order=new Order(); order.setNumber(number); //获取用户的信息 order.setUid(1); order.setUsername("刘晨晨"); Product product=restTemplate.getForObject("http://localhost:8080/product/getById/"+pid,Product.class); order.setPid(pid); order.setPname(product.getPname()); order.setPprice(product.getPprice()); ComResult save = orderService.save(order); return save; } }
基于http协议完成服务之间的调用。
启动类:
@SpringBootApplication @MapperScan(basePackages = "com.wjk.dao") public class OrderApp { public static void main(String[] args) { SpringApplication.run(OrderApp.class,args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
我们把服务的地址写死在代码中【硬编码】,这样写会出现什么问题?
1.如果服务提供者的地址或者端口号发生改变,服务消费者的代码也要改变。
2.消费者可以看到提供者的地址,不安全。
3.无法完成负载均衡的调用。
我们可以使用服务治理的方案来解决上述问题。
服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现。
服务注册:在服务治理框架中,都会构建一个*注册中心*,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的*清单*,服务注册中心需要以*心跳30s 90s*的方式去监测清单中 的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
服务发现:服务调用方向服务注册中心咨询服务,并获取*所有服务*的实例清单,实现对具体服务实例的访问
Zookeeper
zookeeper是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
Eureka
Eureka是Springcloud Netflix中的重要组件,主要作用就是做服务注册和发现。但是现在已经闭源 ,停更不停用。
Consul
Consul是基于GO语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册、服务发现和配置管理的功能。Consul的功能都很实用,其中包括:服务注册/发现、健康检查、Key/Value存储、多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件,所以
安装和部署都非常简单,只需要从官网下载后,在执行对应的启动脚本即可。
Nacos(服务治理,配置中心)
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 SpringCloud Alibaba 组件之一,负责服务注册发现和服务配置. [服务治理的作用和微服务配置管理]
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
从上面的介绍就可以看出,nacos的作用就是一个注册中心,用来管理注册上来的各个微服务。
nacos注册中心的搭建
https://github.com/alibaba/nacos/releases
注意: nacos从1.3以后,默认的启动模式为集群模式。修改它为单机启动模式
注意: 账号和密码都是nacos
引入nacos的依赖
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery
修改配置文件
# nacos注册中心的配置 spring.cloud.nacos.discovery.server-addr=localhost:8848 spring.application.name=qy158-product
重启微服务
在nacos界面可以看到如下
解决硬编码问题:
@Autowired
private DiscoveryClient discoveryClient;
//在springcloud中提供了一个类DiscoveryClient,该类可以获取注册中心的清单列表
@GetMapping("save/{pid}/{num}")
public String save(@PathVariable Integer pid,@PathVariable Integer num){
//封装一个订单类对象
Order order=new Order();
order.setNumber(num);
//用户的信息--token session中获取
order.setUid(1);
order.setUsername("柳昌江");
//商品信息---调用商品微服务提高的接口。微服务之间调用:基于http协议调用。
// 原始的方式: 引用httpclient依赖。完成服务之间的调用.
// spring框架基于http协议封装了一个工具类RestTemplate。
List instances = discoveryClient.getInstances("qy158-product");
ServiceInstance serviceInstance = instances.get(0);
String uri = serviceInstance.getUri().toString();
Product product = restTemplate.getForObject(uri+"/product/getById/" + pid, Product.class);
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
String save = orderService.save(order);
return save;
}
springcloud封了一个工具类DiscoveryClient,该类可以获取注册中心的服务清单列表,从列表中获取自己想要的服务信息【ip:port】。
通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
nginx该组件可以实现负载均衡
1.可以编写完成负载均衡-修改order中的代码
//在springcloud中提供了一个类DiscoveryClient,该类可以获取注册中心的清单列表 @GetMapping("save/{pid}/{num}") public String save(@PathVariable Integer pid,@PathVariable Integer num){ //封装一个订单类对象 Order order=new Order(); order.setNumber(num); //用户的信息--token session中获取 order.setUid(1); order.setUsername("柳昌江"); //商品信息---调用商品微服务提高的接口。微服务之间调用:基于http协议调用。 // 原始的方式: 引用httpclient依赖。完成服务之间的调用. // spring框架基于http协议封装了一个工具类RestTemplate。 List
instances = discoveryClient.getInstances("qy158-product"); int i = new Random().nextInt(instances.size());//随机产生一个下标 不能超过instances.size长度 ServiceInstance serviceInstance = instances.get(i); String uri = serviceInstance.getUri().toString(); Product product = restTemplate.getForObject(uri+"/product/getById/" + pid, Product.class); order.setPid(pid); order.setPname(product.getPname()); order.setPprice(product.getPprice()); String save = orderService.save(order); return save; } 负载均衡的策略:随机策略,如果策略想使用其他策略,那需要修改代码。
Ribbon是Netflix发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中,nacos一般配合Ribbon客户端负载均衡的功能,Ribbon利用从nacos中读 取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。 在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务。
如何使用ribbon完成负载均衡。
1.在RestTemplate上@LoadBalance注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
2.修改controller代码
//在springcloud中提供了一个类DiscoveryClient,该类可以获取注册中心的清单列表
@GetMapping("save/{pid}/{num}")
public String save(@PathVariable Integer pid,@PathVariable Integer num){
//封装一个订单类对象
Order order=new Order();
order.setNumber(num);
//用户的信息--token session中获取
order.setUid(1);
order.setUsername("柳昌江");
//商品信息---调用商品微服务提高的接口。微服务之间调用:基于http协议调用。
// 原始的方式: 引用httpclient依赖。完成服务之间的调用.
// spring框架基于http协议封装了一个工具类RestTemplate。
Product product = restTemplate.getForObject("http://qy158-product/product/getById/" + pid, Product.class);
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
String save = orderService.save(order);
return save;
}
注意: 使用restTemplate调用远程服务时,"http://服务名/接口路径"
4.3、ribbon的负债均衡策略
Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为 com.netflix.loadbalancer.IRule , 具体的负载策略如下图所示:
如何指定相应的策略:
shop-product: # 这里使用服务的名称 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #使用的的负载均衡策略
原来使用restTemplate完成服务之间的调用: 它不符合我们的编程习惯。---在某层需要另一层的对象时,直接通过@Autowire注入,并通过对象调用其他的方法,传入相关的参数。
什么是OpenFeign
OpenFeign是Spring Cloud提供的一个声明式的伪Httjp客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了OpenFeign, Feign负载均衡默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。
如何使用openFeign
(1)、引入依赖
org.springframework.cloud spring-cloud-starter-openfeign (2)、创建feign接口
@FeignClient(value = "qy158-product") public interface ProductFeign { @GetMapping("/product/getById/{pid}") //必须和提供者的路径保持一致 public Product findById(@PathVariable Integer pid); }
(3)、开启openfeign
@SpringBootApplication @MapperScan(basePackages = "com.aaa.dao") @EnableFeignClients //开启openfeign注解驱动 public class OrderApp { public static void main(String[] args) { SpringApplication.run(OrderApp.class,args); } @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
(4)、修改controller代码
@Autowired private ProductFeign productFeign; //在springcloud中提供了一个类DiscoveryClient,该类可以获取注册中心的清单列表 @GetMapping("save/{pid}/{num}") public String save(@PathVariable Integer pid,@PathVariable Integer num){ //封装一个订单类对象 Order order=new Order(); order.setNumber(num); //用户的信息--token session中获取 order.setUid(1); order.setUsername("柳昌江"); //调用远程接口,就像调用本地方法一样 Product product = productFeign.findById(pid); order.setPid(pid); order.setPname(product.getPname()); order.setPprice(product.getPprice()); String save = orderService.save(order); return save; }
在实际开发过程中,如果使用Nacos的话,为了确保高可用,我们一般都会对其进行集群的部署。Nacos规定集群中Nacos节点的数量需要大于等于3个;同时,单机模式下Nacos的数据默认保存在其内嵌数据库中deby,不方便观察数据存储的基本情况。而且如果集群中启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储;此外,我们还需要借助Nginx实现负载均衡。这一过程的部署架构图如下所示:
默认每个nacos都内置了一个deb数据库,如果使用内置的数据库则nacos集群之间无法共享数据。
注意: 虚拟机的网卡关闭
application.properties
把端口号修改为所需的端口号
并修改每一份的端口号
l 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
l 认证复杂,每个服务都需要独立认证。
l 存在跨域请求,在一定场景下处理相对复杂。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服 务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控(黑白名单)、路由转发等等。 添加上API网关之后,系统的架构图变成了如下所示:
Ngnix+lua
使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用
lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本
Kong
基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:
只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
Zuul 1.0(慢 servlet 2.0 ) zuul2.0 没出来
Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配
置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx
Spring Cloud Gateway
Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。
Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。
优点:
性能强劲:是第一代网关Zuul的1.6倍
功能强大:内置了很多实用的功能,例如转发、监控、限流等
设计优雅,容易扩展.
缺点:
其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行 web.Jar
需要Spring Boot 2.0及以上的版本,才支持.
因为gateway它内置的服务器Netty,所以不能在使用Tomcat服务器。
(1)创建一个gateway模块
(2)引用相关的gateway依赖
org.springframework.cloud
spring-cloud-starter-gateway
(3)修改application.yml文件
server:
port: 7777
#配置路由
spring:
cloud:
gateway:
routes:
- id: qy158-product #路由的标识,值唯一,默认随机产生一个唯一值
uri: http://localhost:8080 #它是网关真实转发到的地址
order: 0 #路由的优先级,值越小优先级越高
predicates: #断言: 它返回boolean值,如果返回的为true,则转发到真实的地址 http://localhost:8080/product/getById/1
- Path=/product/**
- id: qy158-order
uri: http://localhost:8090
order: 0
predicates:
- Path=/order/**
(4)主启动类
package com.aaa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApp {
public static void main(String[] args) {
SpringApplication.run(GatewayApp.class,args);
}
}
(5) 测试
思考:
微服务的个数非常多,需要在网关中一一添加配置。
uri地址写死了,如果微服务地址或者端口号以及集群模式发生改变 需要修改网关。
(1)引入nacos注册依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
(2)修改配置文件
(1)修改配置
#简约模式
spring.cloud.gateway.discovery.locator.enabled=true
这句代码就是简约模式
(2)前端访问:
注意: 前端在访问时,必须加上微服务的名称
自己定义一个断言方式 设置年龄大小
package com.aaa.predicate; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; import javax.validation.constraints.NotNull; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; @Component public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory
{ public static final String MIN = "min"; public static final String MAX = "max"; public AgeRoutePredicateFactory() { super(AgeRoutePredicateFactory.Config.class); } @Override public List shortcutFieldOrder() { //读取配置文件中的内容 return Arrays.asList("min", "max"); } @Override public Predicate apply(Config config) { return (t)->{ ServerHttpRequest request = t.getRequest(); String age = request.getHeaders().getFirst("age").toString(); if(StrUtil.isNotEmpty(age)&& NumberUtil.isInteger(age)){ int i = Integer.parseInt(age); if(i>=config.getMin() &&i<=config.getMax()){ return true; } } return false; }; } @Validated public static class Config { @NotNull private Integer min; @NotNull private Integer max; public Integer getMin() { return min; } public void setMin(Integer min) { this.min = min; } public Integer getMax() { return max; } public void setMax(Integer max) { this.max = max; } } }
修改请求和响应的设置
package com.aaa.filter; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @Component public class LoginFilter implements GlobalFilter, Ordered { @Override public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); //哪些路径允许放行 RequestPath path = request.getPath(); if("/login".equals(path)){ return chain.filter(exchange); } //获取请求头 String token = request.getHeaders().getFirst("token"); if(StrUtil.isNotEmpty(token)&&"admin".equals(token)){ return chain.filter(exchange); } //3.2封装返回数据 Map map = new HashMap<>(); map.put("msg", "未登录"); map.put("code", "NOTLOGING"); //3.3作JSON转换 byte[] bytes = JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8); //3.4调用bufferFactory方法,生成DataBuffer对象 DataBuffer buffer = response.bufferFactory().wrap(bytes); //4.调用Mono中的just方法,返回要写给前端的JSON数据 return response.writeWith(Mono.just(buffer)); } @Override public int getOrder() { return 0; } }
之后再搭建nginx集群 就形成了一套完整的体系
在大型系统的微服务化构建中,一个系统被拆分成了许多微服务。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心【区域】,也就意味着这种架构形式也会存在一些问题:
如何快速发现问题?
如何判断故障影响范围?
如何梳理服务依赖?
如何分析链路性能问题以及实时容量规划?
分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上IP、每个服务节点的请求状态200 500等等。
常见的链路追踪技术有下面这些:
cat 由大众点评开源,基于Java开发的实时应用监控平台,包括实时应用监控,业务监控 。 集成方案是通过代码埋点的方式来实现监控,比如: 拦截器,过滤器等。 对代码的侵入性很大,集成成本较高。风险较大。
zipkin 由Twitter公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括:数据的收集、存储、查找和展现《图形化》。该产品结合spring-cloud-sleuth 使用较为简单, 集成很方便, 但是功能较简单。
pinpoint Pinpoint是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI功能强大,接入端无代码侵入。
skywalking 【未来企业会使用的多】SkyWalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多
种插件,UI功能较强,接入端无代码侵入。目前已加入Apache孵化器。
Sleuth (日志记录每一条链路上的所有节点,以及这些节点所在的机器,和耗时。)log4jSpringCloud 提供的分布式系统中链路追踪解决方案。
注意:SpringCloud alibaba技术栈中并没有提供自己的链路追踪技术的,我们可以采用Sleuth
Zipkin来做链路追踪解决方案。 sleuth+zipkin
8.2.1、Sleuth
SpringCloud Sleuth主要功能就是在分布式系统中提供追踪解决方案。它大量借用了Google Dapper的设计, 先来了解一下Sleuth中的术语和相关概念。
Trace(一条完整链路--包含很多span(微服务接口))
由一组Trace Id(贯穿整个链路)相同的Span串联形成一个树状结构。为了实现请求跟踪,当请求到达分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的标识(即TraceId),同时在分布式系统内部流转的时候,框架始终保持传递该唯一值,直到整个请求的返回。那么我们就可以使用该唯一标识将所有的请求串联起来,形成一条完整的请求链路。
Span代表了一组基本的工作单元。为了统计各处理单元的延迟,当请求到达各个服务组件的时候,也通过一个唯一标识(SpanId)来标记它的开始、具体过程和结束。通过SpanId的开始和结束时间戳,就能统计该span的调用时间,除此之外,我们还可以获取如事件的名称。请求信息等元数据。
Annotation用它记录一段时间内的事件,内部使用的重要注释:
cs(Client Send)客户端发出请求,开始一个请求的命令
sr(Server Received)服务端接受到请求开始进行处理, sr-cs = 网络延迟(服务调用的时间)
ss(Server Send)服务端处理完毕准备发送到客户端,ss - sr = 服务器上的请求处理时间
cr(Client Reveived)客户端接受到服务端的响应,请求结束。 cr - cs = 请求的总时间
(1)在父工程添加sleuth依赖
org.springframework.cloud
spring-cloud-starter-sleuth
(2)访问相应的接口
缺点:查看日志文件并不是一个很好的方法,当微服务越来越多日志文件也会越来越多,通过Zipkin可以将日志聚合,并进行可视化展示和全文检索。
Zipkin的介绍:Zipkin 是 Twitter 的一个开源项目,它基于Google Dapper实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储展现、查找和我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源
除了面向开发的 API 接口之外,它也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。
Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。
上图展示了 Zipkin 的基础架构,它主要由 4 个核心组件构成:
Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为Zipkin 内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。
Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。
RESTful API:API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
Web UI:UI 组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和分析跟踪信息。
Zipkin 分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端也就是微服务的应用。客户端会配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监听,并生成相应的 Trace 和 Span 信息发送给服务端。
第一步:下载Zipkin的jar包: Central Repository: io/zipkin
第二部:通过命令行,输入下面的命令启动ZipKin Server java -jar xxxx.jar
第三步:通过浏览器访问http://localhost:9411 访问
(1)zipkin的依赖
org.springframework.cloud
spring-cloud-starter-zipkin
(2)指定zipkin的服务端地址
# zipkin的配置
spring.zipkin.base-url=http://localhost:9411
#zipkin不注册到nacos上
spring.zipkin.discovery-client-enabled=false
# zipkin要把sleuth产生的日志,抽取的比例。 0.03
spring.sleuth.sampler.probability=1.0
(3)查看zipkin界面
可以在数据库追中,redis中,ES中
CREATE TABLE IF NOT EXISTS zipkin_spans ( `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` BIGINT NOT NULL, `id` BIGINT NOT NULL, `name` VARCHAR(255) NOT NULL, `remote_service_name` VARCHAR(255), `parent_id` BIGINT, `debug` BIT(1), `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL', `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query', PRIMARY KEY (`trace_id_high`, `trace_id`, `id`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds'; ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames'; ALTER TABLE zipkin_spans ADD INDEX(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames'; ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range'; CREATE TABLE IF NOT EXISTS zipkin_annotations ( `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id', `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id', `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1', `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB', `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation', `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp', `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address', `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null' ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds'; ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames'; ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job'; CREATE TABLE IF NOT EXISTS zipkin_dependencies ( `day` DATE NOT NULL, `parent` VARCHAR(255) NOT NULL, `child` VARCHAR(255) NOT NULL, `call_count` BIGINT, `error_count` BIGINT, PRIMARY KEY (`day`, `parent`, `child`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
启动:java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=数据库账号--MYSQL_PASS=数据库密码
现在还存在的问题
这些我们需要配置中心来解决上述问题.
Apollo**------>很多使用apollo**
Apollo是由携程开源的分布式配置中心。特点有很多,比如:配置更新之后可以实时生效,支持灰度发布功能,并且能对所有的配置进行版本管理、操作审计等功能,提供开放平台API。并且资料 也写的很详细。
Disconf
Disconf是由百度开源的分布式配置中心。它是基于Zookeeper来实现配置变更后实时通知和生效的。
SpringCloud Config
这是Spring Cloud中带的配置中心组件。它和Spring是无缝集成,使用起来非常方便,并且它的配置存储支持Git
。不过它没有可视化的操作界面,配置的生效也不是实时的,需要重启或去刷新。 Nacos
这是SpingCloud alibaba技术栈中的一个组件,前面我们已经使用它做过服务注册中心。其实它也集成了服务配置的功能,我们可以直接使用它作为服务配置中心。
使用nacos作为配置中心,其实就是将nacos当做一个服务端,将各个微服务看成是客户端,我们将各个微服务的配置文件统一存放在nacos上,然后各个微服务从nacos上拉取配置即可。
(1)、在配置中心建立配置文件
dataId 必须和微服务的名称一致。
(2)、微服务中引入nacos依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
(3)创建一个bootstrap.properties配置文件
bootstarp可以读取外部配置的任务
#指定配置中心的地址--默认读取dataId和微服务名称一致的配置文件spring.cloud.nacos.config.server-addr=localhost:8848
(4)、验证
@Value("${student.name}")
private String name; //验证读取配置的内容
@GetMapping("info")
public String info(){
return "你读取的nacos配置内容:"+name;
}
在类上@RefreshScope注解即可。
@RestController @RequestMapping("product") @RefreshScope public class ProductController { @Autowired private ProductService productService; @GetMapping("getById/{pid}") public Product getById(@PathVariable Integer pid){ Product product = productService.findById(pid); return product; } @Value("${student.name}") private String name; @GetMapping("info") public String info(){ return "你读取的nacos配置内容:"+name; } }
我们可以把数据源都放入到nacos中 单独创建一个datasource.properties放入数据源 你切记这个创建一定要添加后缀
然后在idea中添加额外的配置
#额外的配置 spring.cloud.nacos.config.extension-configs[0].data-id=datasource.properties spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP spring.cloud.nacos.config.extension-configs[0].refresh=true
服务的雪崩现象
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。
解决雪崩问题的常见方式有四种:
超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离
熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。
什么是雪崩问题?
微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。
如何避免因瞬间高并发流量而导致服务故障?
流量控制
如何避免因服务故障引起的雪崩问题?
超时处理,线程隔离,降级熔断
服务保护技术对比
Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:https://sentinelguard.io/zh-cn/index.html
Sentinel 具有以下特征:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
sentinel官方提供了UI控制台,方便我们对系统做限流设置。大家可以在GitHub下载。课前资料提供了下载好的jar包
然后访问:localhost:8080 即可看到控制台页面,默认的账号和密码都是sentinel
1、引入依赖
com.alibaba.cloud spring-cloud-starter-alibaba-sentinel 2、配置控制台地址
spring.cloud.sentinel.transport.dashboard=localhost:8888
我这里的8888是因为8080端口被占用了
簇点链路:
簇点链路:就是项目内的调用链路,链路中被监控的每个接口就是一个资源。默认情况下sentinel会监控SpringMVC的每一个端(Endpoint),因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。
流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:
点击资源/product/{info}后面的流控按钮,就可以弹出表单。表单中可以添加流控规则,如下图所示:
其含义是限制 /product/{info}这个资源的单机QPS为1,即每秒只允许1次请求,超出的请求会被拦截并报错。