聚合项目并进行环境初始化
微服务项目一般都会采用聚合工程结构,可通过聚合工程结构实现共享资源的复用,简化项目的管理方式。本小节以一个聚合工程结构为案例,讲解微服务架构方案中的maven聚合工程的基本结构,例如:
GitCGB2105IVProjects (工作区/空项目)
├── 01-sca //(微服务父工程)
├── sca-provider //服务提供方法
├── sca-consumer //服务消费方法
├── sca-gateway //网关服务
————————————————
配置maven路径,本地库等。
1.父工程不要编辑代码,所以最好把src文件删除,以免搞混
2.父工程配置要设置可供子工程使用
2.新建的父工程pom没有
父工程POM文件,对依赖进行配置:
如有需要
如Spring Cloud 使用2.2.6.RELEASE,spring boot 必须为2.3.2.RELEASE:
如只是供子工程使用不需要版本控制,
2.1. 1微服务架构诞生的背景(分而治之~北京一个火车站到多个火车站)
2.1.2微服务架构解决方案(大厂自研,Spring Cloud ~Netflix,Alibaba,…)
2.1.3微服务架构下Maven聚合项目的创建方式?(Maven聚合项目~资源复用,简化编译,打包,部署方式)
2.1.4微服务架构入门聚合项目创建?(01-sca,sca-consumer,sca-provider,sca-gateway,sca-common)
2.1.5微服务中聚合工程之间的引用设计?(将一个工程作为依赖添加到其它工程)
2.2.1为什么需要微服务?
(对系统分而治,解决因并发访问过大带来的系统复杂性(例如:业务,开发,测试,升级,可靠性等)
2.2.2微服务设计的特点?
(单一职责,独立进程,开发测试效率高,可靠性高,升级难度小,但会带来一定的维护成本)
2.2.3微服务解决方案有哪些?
(大厂自研,中小企业采用开源Spring Cloud Alibaba,Spring Cloud Netfix等 )
2.2.4微服务设计中需要哪些关键组件
(服务的注册,发现,配置,限流降级,访问入口管理,分布式事务管理等)
2.2.5创建聚合工程的目的?
(实现工程之间资源的的共享,简化工程管理)
2.2.6如何修改聚合工程中项目的编译和运行版本?
(build->plugins->plugin->maven-compiler-plugin)
2.2.7 maven工程中build元素的作用?
(定义项目的编译,打包方式)
2.2.8 maven父工程的packaging元素内的值是什么?
(父工程默认是pom打包方式)
2.2.9 maven父工程中dependencyManagement元素的作用是什么?
(定义项目的版本,当前工程或子工程不需要再指定版本)
2.2.10 Maven父工程中如何统一定义JDK编译和运行版本?
(配置maven编译插件:maven-compiler-plugin)
如果接口实现类的方法上加@Override报错:JDK版本低于1.7
接口中的方法只有在JDK1.8以下时方法才只存在抽象方法
方法只有在JDK1.8及以上时才可被static修饰。
2.3.1依赖无法下载或加载?
(本地库冲突,网络不好,maven镜像配置,指定版本在远程服务器不存在,清idea缓存后重启)
2.3.2项目的pom.xml文件有删除线?
(idea/setting/build,Execution,Deployment/build Tools/maven/ignore Files),将对勾去掉
2.3.3项目单元测试失败,提示找不到@SpringBootConfiguration?
检查项目中是否定义了启动类
检查单元测试,是否在启动类的同包或者子包中
2.3.4报异常found multiple@SpringBootConfiguration
项目中出现了多个启动类时,在项目启动或单元测试时会报这个异常
2.3.5@SpringBootConfiguration和@Test报错
单元测试依赖添加的有问题或者单元测试类写错了位置
2.3.6 通过类名调用方法,方法报红
检查是否导错包,检查该包是否存在这个方法,检查这个方法是否被static修饰
2.3.7 空指针异常,NullPointerException
找到报错的空指针指向的代码,然后检查哪个对象在访问属性或方法,再检查属性或方法是否复制了
2.4.1背景分析
在微服务中,首先需要面对的问题就是如何查找服务(软件即服务),其次,就是如何在不同的服务之间进行通信?如何更好更方便的管理应用中的每一个服务,如何建立各个服务之间联系的纽带,由此注册中心诞生(例如淘宝网卖家提供服务,买家调用服务)。
市面上常用注册中心有Zookeeper(雅虎Apache),Eureka(Netfix),Nacos(Alibaba),Consul(Google),那他们分别都有什么特点,我们如何进行选型呢?我们主要从社区活跃度,稳定性,功能,性能等方面进行考虑选择的注册中心.本次微服务的学习,我们选择Nacos,它很好的支持了阿里的双11活动,不仅可以做注册中心,还可以作为配置中心,稳定性和性能都很好。
2.4.2 Nacos是什么
Nacos(DynamicNaming and Configuration Service)是一个应用于服务注册与发现、配置管理的平台。它孵化于阿里巴巴,成长于十年双十一的洪峰考验,沉淀了简单易用、稳定可靠、性能卓越的核心竞争力。其官网地址如下:
https://nacos.io/zh-cn/docs/quick-start.html
2.4.3 Nacos安装配置
1.版本选择
您可以在Nacos的release notes及博客中找到每个版本支持的功能的介绍,当前推荐的稳定版本为2.0.3。
2.预备环境准备
Nacos 依赖 Java 环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 Maven环境,请确保是在以下版本环境中安装使用:
64 bit OS,支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac。
JDK 版本:64 bit JDK 1.8+。
Maven 版本:Maven 3.2.x+。
3.下载源码或者安装包
你可以通过源码和发行包两种方式来获取 Nacos。
https://github.com/alibaba/nacos.git
选择对应版本,直接下载.zip为windows系统,.tar.gz为Linux系统
4. 解压Nacos(最好不要解压到中文目录下),其目录结构如下:
5.配置Nacos的数据库和数据库连接
登陆mysql,执行老师发给同学们的sql脚本。
可直接用sqlyog导入数据库(老师说最好不用sqlyog)
也可使用dos命令开启打开数据库执行如下指令:
source d:/nacos-mysql.sql(此处是老师的nacos-mysql.sql路径),数据库文件内缺少创建数据库指令,如果用dos命令登录数据库则文件内需要手动添加以下sql语句(在执行此文件时,要求mysql的版本大于5.7版本(MariaDB最好10.5.11)):
DROP DATABASE IF EXISTS `nacos_config`;
CREATE DATABASE `nacos_config` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
USE `nacos_config`;
打开Nacos的/conf/application.properties配置文件,并基于你当前环境配置要连接的数据库,连接数据库时使用的用户名和密码
6.服务启动与访问
启动Nacos服务(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone ——windows系统
./startup.sh -m standalone——Linux/Unix/Mac系统
启动前提是环境变量中配置了JAVA_HOME(对应jdk的安装目录), 连接的数据库(nacos_config)是存在的且数据程序最好只有一种。
访问Nacos服务:
打开浏览器,输入http://localhost:8848/nacos地址 默认账号密码为nacos/nacos
2.4.4服务创建、注册与调用入门
1.业务需求:
创建两个项目Module分别为服务提供者和服务消费者,两者都要注册到NacosServer中(这个server本质上就是一个web服务,端口默认为8848),然后服务提供者可以为服务消费者提供远端调用服务(例如支付服务为服务提供方,订单服务为服务消费方)
2. 服务提供者创建及注册:
创建服务提供者工程(module名为sca-provider) 继承parent工程(01-sca):
父工程的pom文件版本控制已经配置过,sca-provider的pom文件单独需要的依赖如下:
3.创建并修改配置文件application.yml(或者application.properties),实现服务注册:
server:
port: 8081
spring:
application:
name: sca-provider #进行服务注册必须配置服务名,服务名要用横线而不能用下划线(fiegn)
cloud:
nacos:
discovery:
server-addr: localhost:8848
4. 在sca-provider项目中创建服务提供方对象,基于此对象对外提供服务
定义Controller对象(这个对象在spring mvc中给他的定义是handler),基于此对象处理客户端的请求
@RestController
public class ProviderController{
//@Value默认读取项目配置文件中配置的内容
//8080为没有读到server.port的值时,给定的默认值
@Value("${server.port:8080}")
private String server;
//http://localhost:8081/provider/echo/tedu
@GetMapping("/provider/echo/{msg}")
public String doRestEcho1(@PathVariable String msg){
return server+" say hello "+msg;
} }
5. 为sca-provider创建启动类:如果有则不用再创建
6. 启动启动类,然后刷新nacos服务,在服务管理的服务列表中检测是否服务注册成功
7. 打开浏览器,输入http://localhost:8081/provider/echo/msa,然后进行访问测试
8. 服务消费者创建及注册
创建及pom文件配置过程同服务提供者,但服务名和端口号要根据消费者工程自身设置,
9. 创建消费端启动类并在其中添加交给spring管理的RestTemplate对象
@Bean
public RestTemplate restTemplate(){//基于此对象实现远端服务调用
return new RestTemplate();
}
10. 在sca- consumer项目中创建服务消费者对象,基于此对象调用远程服务
定义服务消费端Controller
/**
* 定义服务消费端Controller,在这个Controller对象
* 的方法中实现对远端服务sca-provider的调用
*/
@RestController
public class ConsumerController {
/**从spring容器获取一个RestTemplate对象,
* 基于此对象实现远端(remote)服务调用
*/
@Autowired
private RestTemplate restTemplate;
/**
* 在此方法中通过一个RestTemplate对象调用远端sca-provider中的服务
*/
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho01(){
//1.定义要调用的远端服务的url
String url="http://localhost:8081/provider/echo/8090";
//2.基于restTemplate对象中的相关方法进行服务调用
return restTemplate.getForObject(url, String.class); //我要访问的连接和需要返回给我的值
}
}
11. 启动消费者服务,并在浏览器输入http://localhost:8090/consumer/doRestEcho1地址进行访问,假如访问成功会出现: 8081 say hello 8090
2.4.5核心知识点
服务注册中心诞生背景? (服务多了,需要统一管理,例如所有公司需要在工商局进行备案)
服务注册中心的选型?(社区活跃度,稳定性,功能,性能,学习成本)
Nacos下载,安装,配置,启动,访问(http://ip:port/nacos)
基于Nacos实现服务的注册?(添加依赖,服务配置,启动服务并检查)
2.4.6常见问题分析
如何理解服务中心?(存储服务信息的一个服务)
服务注册中心诞生的背景? (服务多了,需要对服务进行更好管理)
市场上常用的注册中心?(Zookeeper,Eureka,Nacos,Consul)
如何对注册中心进行选型?(社区活跃度,稳定性,功能,性能,学习成本)
Nacos 是什么?(是Alibaba公司基于SpringBoo技术t实现的一个注册中心,本质上也是一个服务)
Nacos 的基本架构?(Client/Server架构)
Nacos 主要提供了什么核心功能?(服务的注册,发现,配置)
Nacos 服务启动需要什么前置条件?(配置JDK的JAVA_HOME目录,安装MySQL5.7以上版本,配置连接的数据库)
Nacos 服务单机模式,window平台下启动时的指令是什么?(startup.cmd -m standalone)
实现Nacos服务注册需要添加什么依赖?(两个)
实现Nacos服务注册时,必须做哪些配置?(服务名,假如是本机服务注册可以省略服务地址)
Nacos如何检查服务状态?(通过心跳包实现)
服务之间进行服务调用时,使用了什么API?(RestTemplate)
2.4.7常见Bug分析
1、Nacos启动不起来,需检查JAVA环境变量有没有配置,是否正确
2、执行nacos-mysql.sql文件时报长度不能超过767字节,说明MySQL版本比较低(建议mysql5.7或mariadb10.5及以上版本)
3、执行source d:/nacos-mysql.sql报sql文件不存在 ,检查相应盘符下是否存在这个sql文件
4、执行source d:/nacos-mysql.sql报SQL文件应用错误,数据库文件内缺少创建、打开数据库指令。
5、Nacos启动报no datasource 异常,Nacos的application.properties配置文件中,连接数据库的配置错误
6、Nacos启动报Address already in use,服务启动时,端口被占用了。
7、服务器启动报param‘serviceName’is illegal ,服务注册时,服务名不正确,格式不正确,配置文件名字不正确,或者没有配置文件
8、服务器启动报nacosexception引发connectException,基于Nacos实现服务注册失败,可能是引入依赖,或配置错误
9、客户端500异常,看后台具体错误类型
10、服务消费方调用服务时, 引发connectException连接异常,服务提供方没有启动,或访问的ip地址和端口错误。
11、服务消费方调用服务时,后端底层报404服务不存在,检查服务提供方url是否正确。
2.4.8小节面试分析
为什么要将服务注册到nacos?(为了更好的查找这些服务)
在Nacos中服务提供者是如何向Nacos注册中心(Registry)续约的?(5秒心跳)
对于Nacos服务来讲它是如何判定服务实例的状态?(检测心跳包,15,30)
服务启动时如何找到服务启动注册配置类?(NacosNamingService)
服务消费方是如何调用服务提供方的服务的?(RestTemplate)
2.4.9 如何在idea中使用一键启动nacos而不是再dos中输入启动命令:
2.4.10如何在idea中链接数据库
服务端消费方单纯使用RestTemplate对象设置多个服务器连接,缺点是提供方修改请求内容后,每个客户端的每个请求都需要修改。不可行。
3.2.1 原理:服务提供方注册了多个服务器,注册中心(nacos)基于服务名存有多个服务器实例,并且通过loadBalancerClient接口实现类从注册中心获取所有服务提供者的实例,实现类底层基于Ribben组件的irule接口的各种算法规则,获取其中一个实例,进行远程服务调用。(loadBalancerClient接口是引入的spring cloud alibaba依赖包提供的)
loadBalancerClient接口>实现类Ribben组件使用>irule接口>各种算法规则实现调用
算法包括:随机、轮询、响应速度权重、性能权重等,当系统提供的负载均衡策略不能满足我们需求时,我们还可以基于IRule接口自己定义策略.
3.2.2 操作:
ServiceInstance serviceInstance = loadBalancerClient.choose("sca-provider")
从筛出的实例中获取IP(host)和端口号(port):
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
//将从nacos获取的IP和port赋值给URL(%s是String.format的占位符)
String url= String.format("http://%s:%s/provider/echo/%s:", host,port,appName);
return restTemplate.getForObject(url, String.class)
3.3.1 @LoadBalanced加在交给Spring管理的RestTemplate类型对象上
1.代码:
@Bean//交给spring管理
@LoadBalanced//声明该方法为负载均衡用,由springmvc提供的拦截器
public RestTemplate loadBalancedRestTemplate(){ //基于此对象实现远端服务调用
return new RestTemplate(); //springweb的类
}
2.底层逻辑:
等于RestTemplate loadBalancedRestTemplate =new RestTemplate();将这个对象交给spring管理,并且由于@LoadBalanced的存在,在使用RestTempalte进行服务调用时,这个调用过程会被@LoadBalanced的一个拦截器LoadBalancerInterceptor进行拦截,然后在拦截器内部,启动负载均衡策略。
@LoadBalanced注解属于Spring,而不是Ribbon的,他们都使用了Irule接口下的规则。
Irule接口的唯一实现类abstractloadbalance是现有负载均衡的顶级父类。其下有各种算法规则的子类,默认规则为RoundRobinRule轮询规则,我们可通过三种方法修改默认规则,当这些算法规则都不能满足需求时,需要自己根据这个接口,最好是继承它的唯一实现类
写规则,实现负载均衡。
3.5.1通过服务消费端配置文件修改:
sca-provider: //要访问的服务名(这个服务可能会对应多个服务实例)
ribbon:
NFLoadBalancerRuleClassNma: com.netflix.loadbalancer.RandomRule
//修改后的负载均衡使用的策略,这个策略方法可以是自己的
3.5.2通过@Bean 注解(配置类或启动类中)
@Bean
public IRule iRule(){
return new RandomRule();//修改后的负载均衡使用的策略,这个策略方法可以是自己的
}
3.6.1Feign是什么?
Spring Cloud微服务规范中的一组远程调用API,是一种声明式Web服务客户端,底层封装了对rest技术的应用。
3.6.2为什么使用Feign?
优化服务调用结构、优化代码结构,不优化性能
3.6.3如何使用Feign实现服务调用?
1、需要添加feign依赖(sapring-cloud-starter-openfeign)
2、然后在启动类或配置类上加@EnableFeignClients,项目启动时Feign会在底层使用FeignStarter扫描所有@FeignClient描述的接口,并为接口创建实现类对象。
3、创建在远程调用服务的接口,接口上加@FeignClient,openfeign中的@FeignClient支持springMVC,可以解析其@RequestMapping注解下的接口,并以此动态产生代理实现类。
@FeignClient(name="sca-provider") //feign会从注册中心获取服务实例(底层用ribbon)
public interface RemoteProviderService { //创建调用服务接口
@GetMapping("/provider/echo/{string}") //服务提供者需要的请求方式
String echoMessage(@PathVariable("string") String string); //创建方法,feign会在底层实现,
返回需要的值,Feign要完成代理实现类,Feign中的@PathVariable不能缺参数
}
当@FeignClient在多个接口上调用同一个服务名的时候需要添加一个contextID=“”作为名
@FeignClient(name="sca-provider",contextID=“”),否则会报错。
4、接收前端请求
@RestController
@RequestMapping("/consumer")
public class RemoteProviderController { //接收前端请求类
@Autowired //注入完成远程服务调用的接口
private RemoteProviderService RemoteProviderService;
@GetMapping("/echo/{msg}")
public String doFeign(@PathVariable("msg" )String msg){
return RemoteProviderService.echoMessage(msg); //接口调用代理实现类的方法,并返回值
}
}
5、如果远端出现错误或者请求超时前端返回500错误怎么解决:
需要在对应的接口的@FeignClient注解中加一个fallbackFactory属性,作为服务异常返回数据的出口,出口方法需要我们自己指定。
@FeignClient(name="sca-provider",contextID=“”,
fallbackFactory = ProviderFallbackFactory.class)
出口方法:
@Component
public class ProviderFallbackFactory implements FallbackFactory<RemoteProviderService> {
//此方法会在RemoteProviderService接口服务调用时,出现了异常后执行.
//@param throwable 用于接收异常
@Override
public RemoteProviderService create(Throwable throwable) {
return (msg)->{
return "服务维护中,稍等片刻再访问";
};
}
}
配合这个出口方法还需要打开熔断机制,在配置文件或配置类中设置:
feign:
hystrix: //熔断
enabled: true #设置开启熔断机制,出现异常则立马启用本地服务信息,无论默认连接超时设置的多长,默认值为false
client:
config:
default:
connectTimeout: 5000 #设置默认连接超时,开启熔断机制则无效
readTimeout: 5000
控制熔断机制时间长短(不只是feign)
hystrix:
command:
default:
execution:
isolation:
thread:
# 一般情况下 hystrix熔断时间 > ribbon超时时间
timeoutInMilliseconds: 4000 //自定义熔断时长
3.6.4 Feign方式的服务调用原理是怎样的?
底层基于代理对象实现,@FeignClient描述的接口无需创建实现类,Feign会在底层创建代理实现类,类似于mybatis的@mapper注解的作用
使用idea工具直接访问服务器:
不用再使用前端浏览器,直接使用本方法,可以代替浏览器发送请求测试后端代码,每个请求的URL要用三个#隔离。
负载均衡是否能自己写:是
负载均衡怎么写:根据irule接口实现 client
4.1.1 向浏览器发送信息
public class Tomcat {
public static void main(String[] args) throws IOException {
//1.构建一个ServerSocket对象(java网络编程中的服务对象),并在指定端口进行监听
ServerSocket server=new ServerSocket(9000);
System.out.println("server start ...");
//2.等待客户端连接(可以循环处理多个客户端的请求)
while(true){
Socket client = server.accept();//阻塞式方法
OutputStream out = client.getOutputStream();
/*浏览器需要的HTTP请求头,tcp协议不需要这个请求头*/
byte[] data=("HTTP/1.1 200 OK \n\r" +
"Content-Type: text/html;charset=utf-8 \n\r" +
"\n\r" +
"hello wo shi lin").getBytes();
out.write(data);
out.flush();
}//http
}
}
//sca-consumer通讯过程 (tomcat->ServerSocket->provider的Socket)
//sca-provider 通讯过程(tomcat->ServerSocket->consumer的Socket)
4.2.1浏览器模式
public class Browser {
public static void main(String[] args) throws IOException {
//1.构建网络客户端对象Socket,并通过此对象对远端服务进行连接
Socket socket=new Socket("127.0.0.1", 9000);
//2.获取流对象
InputStream in = socket.getInputStream();
byte[] buf=new byte[1024];
int len=in.read(buf);
System.out.println("server say:"+new String(buf,0,len));
//3.释放资源
socket.close();
}
}
需要修改配置文件时候,如修改日志级别,线程池大小,调整负载均衡算法等,通常需要重启服务器,十分影响项目正常运转,影响较大。所以使用配置中心动态修改配置。
配置中心最基础的功能就是存储一个键值对,用户发布一个配置(configKey),然后客户端获取这个配置项(configValue);进阶的功能就是当某个配置项发生变更时,不停机就可以动态刷新服务内部的配置项
市场上主流配置中心有Apollo(携程开源),nacos(阿里开源),Spring Cloud Config(Spring Cloud 全家桶成员),本次课程我们选择nacos,此组件不仅提供了注册中心,还具备配置中心的功能。
5.3.1 修改日志级别:日志级别trace
/*输出日志,设定不同级别规定输出日志起点*/
log.trace("==trace==");
log.debug("==debug==");
log.info("==info==");
log.warn("==warn==");
/*log.error("==error==");*//*输出日志为error时服务将停止*/
1、类上加注解实现日志输出:
类上加@log4J(Java程序提供日志输出的统一接口)或@logback
2、或类中加Logger对象实现日志输出:
private static Logger log = LoggerFactory.getLogger( ProviderLogController.class类字节码对象 );
3、修改配置中心日志级别:
项目引入依赖:
将项目sca-provider的application.yml的名字要修改为bootstrap.yml(启动优先级最高),并添加配置中心配置:
spring:
application:
name: sca-provider //与配置中心的Data ID相同
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yml
新建Nacos配置(先读取项目配置文件再读取配置中心保存的最新配置):
其中,Data ID的值要与bootstrap.yml中定义的spring.application.name的值相同(服务名-假如有多个服务一般会创建多个配置实例,不同服务对应不同的配置实例)。
5.3.2 配置中心修改配置文件后的反应
外界修改配置文件后,服务器内部会有感应,但是外界无法获取这种感应,因为服务器启动创建对象后只读取一次配置文件内容,我们获取的属性值是第一次创建服务器对象的配置属性,如果外界要实时获取配置中心对配置文件的修改,需要实时创建服务器对象,此时需要在调取配置文件的类上添加@RefreshScope注解,该注解会实时感应变化,当有变化时,其会在缓存中创建一个代理对象,而每次配置文件变化则会刷新这个对象,则配置文件的属性值也会实时刷新。
@RefreshScope注解是spring管理的依据标准参数以外的参数变化在新的Spring Context容器中比较后,对有变化的参数创建新的Bean对象
5.3.3 nacos配置中心的GROUP
每个GROUP可以指定给多个服务对象,每个服务对象也可以设置多个GROUP,但默认使用DEFAULT_GROUP。
GROUP的配置可以克隆给其他命名空间,其中namespace为配置中心的命名空间码。
5.3.4 nacos配置中心的命名空间
可以创建多个每个命名空间,进行配置隔离,⽐如隔离开发环境和⽣产环境,默认使用无名空间,也可以在服务器中配置文件通过命名空间的码指定命名空间。
5.3.5 nacos配置中心的共享配置
共享配置只能在同一个命名空间下共享不同data-id(服务器)的配置。
spring:
cloud:
nacos:
config:
server-addr: localhost:8848//指定的配置中心地址
namespace: 83ed55a5-1dd9-4b84-a5fe-a734e4a6ec6d //指定的命名空间
file-extension: yml
shared-configs[0]: //第一个共享配置
data-id: app-public.yml //制定命名空间下的目标服务的配置
refresh: true //指定实时共享配置
1、mysql中没有数据配置中心有数据:
说明nacos服务连接的数据库不对,需要检查nacos/conf/application.properties
2、@value值参数输入没问题但是报错:
可能是value的注解包导入错误。
3、后端报错信息为logging.level绑定错误:
配置文件或配置中心的logging.level格式不正确
4、启动时报bean管理错误,没有value注解占位符中参数结果:
配置文件或配置中心没有这个配置。
我们可以在系统负载过高时,采用限流、降级和熔断,三种措施来保护系统,由此一些流量控制中间件诞生。例如Sentinel
6.2.1 Sentinel两个部分核心:
核心库(Java 客户端):能够运行于所有 Java 运行时环境,同时对Dubbo /Spring Cloud 等框架也有较好的支持。
控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。
6.3.1控制台安装步骤:
打开sentinel下载网址:https://github.com/alibaba/Sentinel/releases
下载Jar包(可以存储到一个sentinel目录)
或者从老师的资料里直接复制。
6.3.2控制台启动
在sentinel对应目录,打开命令行(cmd),启动运行sentinel,启动命令:8180为自定义端口
java-Dserver.port=8180-Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
6.3.3 idea中启动:
6.3.4访问Sentinal服务
登陆sentinel,默认用户和密码都是sentinel
6.3.1服务器中引入依赖(对被访问的如需限流的服务器加此依赖)
6.3.2 被访问的如需限流的服务器添加配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8180 # 指定sentinel控制台地址。
6.3.3 原理
服务器中引入依赖作为Sentinel的核心库和客户端,其中包含拦截器interceptor组件。
当服务触发时拦截器会从Sentinel的控制台调取设置的限流规则,当触发规则时,会反馈给用户控制台对请求者设置的限流提示信息。
6.3.4 Sentinel控制台对被访问服务器设置限流(流控)操作
选择要限流的簇点链路,添加流控规则,单机阈值为单台客户端每秒最多提交请求。
直接限流模式:只限制对本服务器的访问
关联限流模式:如果触发第一个服务器限流规则,就对第二个服务器进行限流。
链路限流模式:
需要关闭URL PATH聚合,否则入口只能指定为默认的聚合的URL入口为:sentinel_spring_web_context:web-context-unify: false
需要指定服务名: @SentinelResource("doGetResource")//指定资源名
链路模式只记录指定链路入口的流量。也就是当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流。被调用的方法用@SentinelResource进行注解,然后分别用不同业务方法对此业务进行调用,假如A业务设置了链路模式的限流,在B业务中是不受影响的。
此时限流会报500异常。
处理500异常:
6.3.5 Sentinel控制台对被访问服务器设置降级(熔断)操作
慢调用比例方式:
最大rt(响应时间):该值为允许的最长延时响应,超过这个响应时间将被统计
比例阈值:超过响应时间的操作超过该比例的将被统计
熔断时长:触发熔断降级操作的将被降级的时长
最小请求数:统计时间内的请求数超过这个数的将被统计
统计时长:以上操作需要在这个时间内,会触发降级
异常比例:
以比例统计异常,类似于慢调用比例,将统计所有异常。
异常数:
以数量统计异常
6.3.6 Sentinel控制台对被访问服务器设置热点限流操作
热点参数限流会统计传入参数中的热点数据,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。其中,Sentinel会利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。
指定服务和资源名
@GetMapping("/sentinel/findById")
@SentinelResource("resource") //指定自原名
public String doFindById(@RequestParam("id") Integer id){
return "resource id is "+id;
}
6.3.7 Sentinel控制台对被访问服务器设置系统限流操作
6.3.8 Sentinel控制台对被访问服务器设置黑白名单限流操作
preHandle:controller执行之前执行本方法
postHandle:controller执行之后,且页面渲染之前调用执行本方法
afterCompletion:页面渲染之后执行本方法
package com.jt.provider.interceptor;
public class TimeInterceptor implements HandlerInterceptor {
// HandlerInterceptor方法之一: controller执行之前执行本方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("==preHandle==");
//1.获取当前时间
LocalTime now = LocalTime.now();//LocalTime为jdk8中的一个获取当前时间的api
//2.获取当前的小时并进行逻辑判断
int hour = now.getHour();//8:10~8
System.out.println("hour="+hour);
if(hour<9||hour>18){
throw new RuntimeException("请在9~18时间范围内访问");//return false
}
return true;//false请求到此结束,true表示放行,会去执行后续的拦截器或controller对象
}
}
@Configuration
public class SpringWebConfig implements WebMvcConfigurer {
/**
* 注册拦截器(添加到spring容器),并指定拦截规则
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TimeInterceptor())
.addPathPatterns("/provider/sentinel01");
}
}
一种web服务,不同于springMVC是同步阻塞型web I/O 模型,底层实现是tomcat,每个线程只能处理一个请求, Gateway网关是基于Spring WebFlux是异步非阻塞型web I/O 模型,底层实现是Netty每个线程能处理多个请求。
springMVC是基于http协议的web容器,Gateway网关可以自定义协议的web容器
Spring MVC 的前端控制器是 DispatcherServlet,Gateway网关是 DispatcherHandler。
8.2.1优点:
性能强劲:是第一代网关Zuul的1.6倍。
功能强大:内置了很多实用的功能,例如转发、监控、限流等
设计优雅,容易扩展。
8.2.2缺点:
依赖Netty与WebFlux(Spring5.0),不是传统的Servlet编程模型(Spring MVC就是基于此模型实现),学习成本高。
需要Spring Boot 2.0及以上的版本,才支持
WebFlux(spring5.0)和netty比springMVC支持的并发更大
不再需要springweb依赖
spring:
application:
name: sca-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway: #服务保证,统一认证,同于跨域,限流等
discovery:
locator:
enabled: true #开启通过服务注册中心的serviceId创建路由
routes: #配置网关路由规则
- id: route01 #路由id,自己指定一个唯一值即可
#uri: http://localhost:8081/ #网关帮我们转发的url,确认目标url可用,正常应该需要动态变化
uri: lb://sca-provider # lb为服务前缀(负载均衡单词的缩写),不能随意写,通过服务名寻找服务,代替了指定URL,
#底层使用了全局过滤器(GlobalFilter)
predicates: ###断言(谓词):匹配请求规则true or false;http://localhost:9000/nacos/provider/echo/**
- Path=/nacos/provider/echo/** #请求路径定义,此路径对应uri中的资源,nacos位置可以随便起名,但请求时必须有。
filters: ##网关过滤器,用于对谓词中的内容进行判断分析以及处理
- StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos
globalcors: #跨域问题解决
corsConfigurations:
'[/**]':
allowCredentials: true
allowedHeaders: '*' #允许所有请求头
allowedMethods: '*' #允许所有方法跨域
allowedOrigins: '*' #允许所有方法跨域
给定url:
http://localhost:9000/nacos/provider/echo/8000
Gateway访问流程:
Gateway网关 的谓词工厂常用谓词(只读):
- Path=/nacos/provider/echo/** //请求路径,是否满足本谓词
- After= 2020-12-31T23:59:59.789+08:00[Asia/Shanghai] //判断请求日期是否在此之后
- Before=... //判断请求日期是否在此之前
-Between=.... //判断请求日期是否在此之内
- Header=X-Request-Id,\d+ //规定请求头
- Method=Get //规定请求类型
- Host= //规定ip地址
- Query= //规定参数正则是否可用
- Cookie= //规定Cookie正则是否可用
- RemoteAddr //规定远程地址
- Weight= //规定路由组的指定权重
8.6.1 Gateway网关自带拦截器
在Spring Cloud Gateway中,有Filter过滤器,因此可以在“pre”类型的Filter中自行实现计数器算法、漏桶算法、令牌桶算法三种过滤器。
计数器算法:
当限流qps为100,如果设定单位时间内累加请求的数量达到了100,那么后续的请求就会被全部拒绝。这种现象称为“突刺现象”。
漏桶算法:
为了消除"突刺现象",规定请求出口为匀速,无论多少请求出口依旧按规定速度处理,请求入口不同于计数器算法超过限流数马上抛弃,而是存于类似于桶的容器中,当集中的请求过后慢慢处理。但容器也有上限。
令牌桶算法:
令牌桶算法是对漏桶算法的一种改进,漏桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。令牌桶中的令牌是匀速产生的,既然是桶其也有上限。
如服务器处理能力为10万次每秒,计数器规定10秒内可访问10万次,多余的抛弃,当有漏桶算法时多余的将被存储,然后规定每秒处理1万次,此时如果第一秒就出现10万次访问时,将出现拥堵,所以令牌桶算法排上用处,其规定每秒产生1万个令牌,所以当之前十秒没有访问时就可以产生10万令牌,突发出现的10万请求将获取令牌被处理,此后继续产生令牌。
8.6.2 Gateway网关通过sentinel自定义拦截器
1、添加两个依赖:
2、添加sentinel及路由规则
routes:
- id: route01
uri: lb://sca-provider
predicates: ###匹配断言规则
- Path=/provider/echo/**
sentinel:
transport:
dashboard: localhost:8180 #Sentinel 控制台地址
eager: true #取消Sentinel控制台懒加载,即项目启动即连接
9.1.1 背景分析
传统的登录系统中,每个站点需要单独实现登录模块,切换站点需要重新登录和授权,单点登录系统目的就是一处登录多个站点共用。
9.1.2 概述
即多个站点共用一台认证授权服务器,用户在其中任何一个站点登录后,可以免登录访问其他所有站点。而且,各站点间可以通过该登录状态直接交互。
9.2.1 方案一
用户登陆成功以后,将用户登陆状态存储到redis数据库。
在这套方案中,用户登录成功后,会基于UUID生成一个token,然后与用户信息绑定在一起存储到数据库.后续用户在访问资源时,每次都要基于token从数据库查询用户状态,这种方式因为要基于数据库存储和查询用户状态,所以性能表现一般.
9.2.2 方案二
用户登陆成功以后,将用户信息存储到token(令牌),然后写到客户端进行存储。
在这套方案中,用户登录成功后,会基于JWT技术生成一个token,用户信息可以存储到这个token中.后续用户在访问资源时,对token内容解析,检查登录状态以及权限信息,无须再访问数据库.默认的令牌策略使用内存中的UUID,需要从数据库获取用户信息。
9.3.1服务设计
其服务基于业务进行划分,进行初步服务架构设计
系统(system)服务:只提供基础数据(例如用户信息,日志信息等),
认证(auth)服务:负责完成用户身份的校验,密码的比对,
资源(resource)服务:代表一些业务服务(例如我的订单,我的收藏等等).
9.3.2工程结构设计
基于服务设计进行工程结构设计
9.3.3创建父工程(sso)
父工程的pom文件需要的版本控制的依赖:
spring-boot-dependencies:2.3.2
spring-cloud-dependencies:Hoxton.SR9
spring-cloud-alibaba-dependencies:2.2.6.RELEASE
子工程的所需要的公共依赖,在父工程统一引入:
lombok
spring-boot-starter-test
排除一些不需要的依赖:
junit-jupiter-engine
定义当前工程模块及子工程的的统一编译和运行版本
maven-compiler-plugin:3.8.1
9.3.4系统基础服务工程设计及实现 (sso-system)
根据服务业务涉及系统服务(System),主要用于提供基础数据服务,例如日志信息,用户信息等。
子工程需要添加的项目依赖:
mysql 数据库驱动:mysql-connector-java
mybatis plus 插件:mybatis-plus-boot-starter
注册中心:spring-cloud-starter-alibaba-nacos-discovery
配置中心: spring-cloud-starter-alibaba-nacos-config
Web 服务相关:spring-boot-starter-web
子工程配置文件:
在项目中添加bootstrap.yml文件
server:
port: 8061
spring:
application:
name: sso-system
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
datasource:
url:jdbc:mysql:///jt-sso?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root
@Autowired
private DataSource dataSource;// 数据源对象(DataSource) 可以基于此对象从连接池(享元对象模式)获取一个链接,我们当前项目默认使用连接池为HikariCP:springboot会自动查找mybatis中的HikariCP可以在数据库配置中设置最大连接数.
@Test
void testGetConnection() throws SQLException {
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
接收账号、密码、ID、状态、序列化ID
数据库数据持久化操作
业务层,获取User信息,获取用户权限等。
9.3.5认证授权工程设计及实现 (sso-auth),登录默认必须是post请求
1、基于资源服务工程(sso-system)添加单点登陆认证和授权服务
2、创建子工程系统服务工程并初始化
子工程需要添加的项目依赖:
不用连数据库所以不需要mysql 数据库驱动:mysql-connector-java
不用连数据库所以不需要mybatis plus 插件:mybatis-plus-boot-starter
注册中心:spring-cloud-starter-alibaba-nacos-discovery
配置中心: spring-cloud-starter-alibaba-nacos-config
Web 服务相关:spring-boot-starter-web
认证授权依赖: spring-cloud-starter-oauth2
子工程配置文件:在项目中添加bootstrap.yml文件
同基础服务工程
3、启动类 :启动时候认证授权依赖会默认生成一个登陆密码.
此时打开http://localhost:8071会呈现登陆页面,用户名为user,密码为系统启动时,在控制台呈现的密码。
4、Pojo对象
用于接收账号、密码、ID、状态、序列化ID
5、Dao对象
因本工程连接的是数据基础服务工程,所以本工程不使用数据库,无需Dao层
6、service对象
默认使用AuthenticationProvider接口下的类调用缓存中的数据进行认证,此处自定义通过系统基础服务调用数据库数据认证。
用于进行认证业务。
//基于feign方式获取远程数据并封装
//1.基于用户名获取用户信息 (基于OpenFeign)
com.jt.auth.pojo.User user = remoteUserService.selectUserByUsername(username);
if(user==null){ throw new UsernameNotFoundException("用户不存在")};
//2.基于用户id查询用户权限 (基于OpenFeign)
List
//3. 对查询结果进行封装并返回
List
返回给认证中心AuthorityUtils,会基于用户输入的密码以及数据库的密码做一个比对
AuthorityUtils.createAuthorityList ( permissions.toArray(new String[]{}));
封装
User userInfo = new User(username, user.getPassword(), authorityList)
7、config配置对象,方法加@Bean交给spring管理
配置一:认证中心对密码的加密算法
public BCryptPasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
配置二:用户信息的认证,基于oauth2协议完成认证时,需要此对象
public AuthenticationManager authenticationManagerBean()throws Exception {
return super.authenticationManagerBean();}
配置三:配置认证规则
protected void configure(HttpSecurity http)
throws Exception {
//super.configure(http);// configure方法默认设置,所有请求都要认证
1.禁用跨域攻击 (先这么写,不写会报403异常)
http.csrf().disable();
2.放行所有资源的访问(后续可以基于选择对资源进行认证和放行)
http.authorizeRequests().anyRequest().permitAll();
//
3.自定义定义登录成功和失败以后的处理逻辑(可选)
//假如没有如下设置登录成功会显示404
http.formLogin() //这句话会对外暴露一个默认的登录路径/login
.successHandler(successHandler()) //成功后返回数据内容可自定义
.failureHandler(failureHandler());} //失败后返回数据内容可自定义
3.1定义认证成功处理器//登录成功以后返回json数据
@Bean
public AuthenticationSuccessHandler successHandler(){
//此处使用lambda表达式
return (request,response,authentication)->{
//构建map对象封装到要响应到客户端的数据
Map
map.put("state",200);
map.put("message", "login ok");
//将map对象转换为json格式字符串并写到客户端
writeJsonToClient(response,map);
}
3.2定义登录失败处理器
@Bean
public AuthenticationFailureHandler failureHandler(){
return (request,response,exception)->{
//构建map对象封装到要响应到客户端的数据
Map
map.put("state",500);
map.put("message", "login error");
//将map对象转换为json格式字符串并写到客户端
writeJsonToClient(response,map);
};
}
3.3
private void writeJsonToClient(HttpServletResponse response,Map
String json=new ObjectMapper().writeValueAsString(map); //将map对象,转换为json
response.setCharacterEncoding("utf-8");//设置响应数据的编码方式
response.setContentType("application/json;charset=utf-8"); //设置响应数据的类型
PrintWriter out=response.getWriter();//将数据响应到客户端
out.println(json);
out.flush();}