目录
01-计算机软件架构发展历史
软件架构初识
概述
基本概念
软件架构演进过程
单体架构初步设计
Web服务与数据库分开
本地缓存和分布式缓存
反向代理与负载均衡设计
数据库读写分离设计
数据库按业务进行分库
大表拆分为小表
LVS或F5让多个Nginx负载均衡
DNS轮询实现机房的负载均衡
大应用拆分成小应用
抽离微服务实现工程复用
容器化技术设计及应用
云平台服务部署
总结(Summary)
重难点分析
FAQ分析
Bug分析
02-若依权限管理子系统简介
若依系统简介
若依概述
官方文档地址
微服务技术选型
系统微服务模块骨架
系统微服务技术架构
若依微服务项目部署
背景
准备工作
安装Redis
安装MySql数据库
安装Nacos服务治理业务
初始化RuoYi-Cloud微服务项目数据
若依后端微服务启动运行
启动Nacos服务
基于IDEA打开项目
启动并检测后端项目服务
若依前端项目配置及运行
安装项目依赖
启动运行前端项目
总结(Summary)
重难点分析
FAQ分析
BUG分析
03-微服务架构及解决方案
微服务简介
背景分析
什么是微服务
SpringCloud Alibaba微服务解决方案
概述
核心组件分析
解决方案架构设计
构建SpringCloud 聚合项目并进行环境初始化
工程结构
创建空项目
项目初始化配置
创建聚合父工程
创建服务提供方模块
创建服务消费方模块
创建API网关服务模块
服务关系以及调用关系设计
总结(Summary)
04-Nacos服务注册中心最佳实践
Nacos注册中心简介
背景分析
Nacos概述
构建Nacos服务
准备工作
下载与安装
初始化配置
服务启动与访问
服务注册与调用入门(重点)
业务描述
生产者服务创建及注册
消费者服务发现及调用
小节面试分析
服务负载均衡设计及实现(重点)
业务描述
LoadBalancerClient应用
@LoadBalanced
Ribbon负载均衡策略(了解)
小节面试分析
基于Feign的远程服务调用(重点)
背景分析
Feign是什么
Feign应用实践(掌握)
Feign配置进阶实践
Feign 调用过程分析(了解)
小节面试分析
总结(Summary)
重难点分析
FAQ分析
Bug分析
05-Nacos服务配置中心应用实践
配置中心简介
背景分析
配置中心概述
配置中心的选型
小节面试分析
Nacos配置快速入门
业务描述
配置准备工作
新建Nacos配置
测试Nacos数据读取
@RefreshScope注解的应用
小节面试分析
Nacos配置管理模型
概述
命名空间设计
分组设计及实现
共享配置设计及读取
小节面试分析
总结(Summary)
重难点分析
FAQ分析
Bug分析
06-Sentinel限流熔断应用实践
Sentinel简介
背景分析
Sentinel概述
安装Sentinel服务
访问Sentinal服务
Sentinel限流入门
概述
Sentinel集成
Sentinel限流快速入门
Sentinel流控规则分析
阈值类型分析
设置限流模式
设计限流效果(了解)
小节面试分析
Sentinel降级入门
概述
准备工作
Sentinel降级入门
Sentinel 异常处理
小节面试分析
Sentinel降级策略分析(拓展)
慢调用比例
异常比例
异常数量
小节面试分析
Sentinel热点规则分析(重点)
概述
快速入门
特定参数设计
小节面试分析
Sentinel系统规则(了解)
概述
快速入门
小节面试分析
Sentinel授权规则(重要)
概述
快速入门
小节面试分析
总结(Summary)
重难点分析
FAQ分析
Bug分析
07-网关Gateway 应用实践
网关简介
背景分析
网关概述
快速入门
业务描述
入门业务实现
小节面试分析?
负载均衡设计
为什么负载均衡?
Gateway中负载均衡实现?
执行流程分析(重要)
小节面试分析?
断言(Predicate)增强分析(了解)
Predicate 简介
Predicate 内置工厂
Predicate 应用案例实践
小节面试分析
过滤器(Filter)增强分析(了解)
概述
局部过滤器设计及实现
全局过滤器设计及实现
小节面试分析
限流设计及实现
限流简述
限流快速入门
基于请求属性限流
自定义API维度限流(重点)
定制流控网关返回值
小节面试分析?
总结(Summay)
重难点分析
FAQ 分析
BUG分析
08-微服务文件上传实战(总结与练习)
项目简介
业务描述
初始架构设计
工程创建及初始化
工程结构
创建父工程
创建文件服务工程
创建客户端服务工程
父工程初始化
文件资源服务实现
添加项目依赖
服务初始化配置
构建项目启动类
Controller逻辑实现
跨域配置实现
客户端工程逻辑实现
添加依赖
构建项目启动类
创建文件上传页面
启动服务访问测试
API网关(Gateway)工程实践
概述
服务调用架构
工程项目结构设计
创建网关工程及初始化
网关跨域配置
启动工程进行服务访问
网关上对文件上传限流
AOP方式操作日志记录
页面描述
添加项目依赖
创建切入点注解
定义切入点方法
定义日志操作切面
AOP 方式日志记录原理分析
总结(Summary)
09-微服务版的单点登陆系统设计及实现
简介
背景分析
单点登陆系统
快速入门实践
工程结构如下
创建认证授权工程
添加项目依赖
构建项目配置文件
添加项目启动类
启动并访问项目
自定义登陆逻辑
业务描述
定义安全配置类
定义用户信息处理对象
网关中登陆路由配置
基于Postman进行访问测试
自定义登陆页面
Security 认证流程分析(了解)
颁发登陆成功令牌
构建令牌配置对象
定义认证授权核心配置
配置网关认证的URL
Postman访问测试
登陆页面登陆方法设计
资源服务器配置
业务描述
添加项目依赖
令牌处理器配置
启动和配置认证和授权规则
ResourceController 方法配置
启动服务访问测试
文件上传JS方法设计
技术摘要应用实践说明
背景分析
Spring Security 技术
Jwt 数据规范
Oauth2规范
总结(Summary)
重难点分析
FAQ 分析
Bug 分析
10-单点登录系统拓展实现
拓展业务描述
增加数据库访问
增加服务之间的调用
系统服务设计及实现
业务描述
工程结构设计
工程数据初始化
创建系统工程
添加项目核心依赖
创建项目配置文件
创建项目启动及测试类
Pojo对象逻辑实现
Dao对象逻辑实现
Service对象逻辑实现
Controller对象逻辑实现
启动服务进行访问测试
认证服务工程中Feign应用
业务描述
添加Feign依赖
Pojo对象逻辑实现
Feign接口逻辑实现
调用Feign接口逻辑
启动服务进行访问测试
总结(Summary)
11-微服务课上l历史问题总结
Day01
服务分析
项目结构分析
pom.xml 文件删除线
MySQL的版本问题
Nacos服务启动问题
idea 中启动nacos
Maven 依赖问题
Day02
项目克隆问题
JDK 配置问题
Maven配置编译问题
服务注册异常(NacosException)
服务配置读取问题
配置文件格式问题
无法访问此网站
pom.xml有删除横线
访问404异常
访问500异常
端口占用问题
服务调用案例分析
服务ID问题分析
Day03
Application Failed to Start
Maven Helper插件应用
客户端调用异常(ClientException)
@PathVariable 注解在@FeignClient中应用
依赖注入失败(Bean Not Found)
请求映射路径重复定义
@FeignClient注解名字问题
Feign远程调用超时
图解服务调用方案
Day04
配置文件加载顺序
拉取(Pull)配置信息
Nacos配置中心模型
Tomcat 请求处理分析
Java线程池构成分析
线程池任务执行过程
线程拒绝执行异常
Day05
JDK 版本问题
Sentinel 控制台显示
微服务中的Sentinel日志
回顾Spring MVC 请求处理
Sentinel 请求拦截分析
Day06
Idea中启动Nacos
跨域访问问题
Nacos 服务注册问题
文件上传404问题
请求资源405异常
请求资源500异常
BeanCreationException 异常
服务名无效或没有定义
Day07
网关配置文件问题
服务访问被拒绝(Connection refused)
网关负载均衡调用流程分析
503访问异常
Postman请求携带请求头
作业(homework)
Day08
Debug调试
http Client 应用
503 异常分析
修改文件上传工程结构
Day09
网关执行流程分析
存在多个跨域设计
自定义执行链设计
Spring MVC 拦截器
Spring框架生态设计
Day10
JVM 参数说明
AOP 执行流程分析
Day11
文件上传架构升级
SpringSecurity 执行流程
JS中的断点调试
Day12
单点登陆方案
401 认证异常
检查令牌
权限校验过程分析
postman 上传文件
403 访问被拒绝
资源请求图标问题
Day13
Postman测试刷新令牌
生成序列化ID
Idea&@Autowired
12-2107课上问题分析及总结
Day01~微服务架构入门
核心知识点
常见问题分析
常见Bug分析
课后作业
作业答案
Day02~Nacos 注册中心入门
核心知识点
常见问题分析
常见Bug分析
课后作业
Day03~服务发现及调用
核心知识点
常见问题分析
常见Bug分析
课后作业
作业答案
Day04~Nacos 配置中心入门
核心知识点
常见问题分析
常见Bug分析
课后作业
为了更好理解互联网软件架构,我们现在介绍一下,一百万到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知。
在介绍架构之前,为了避免初学者对架构设计中的一些概念不了解,下面对几个最基础的概念进行介绍。
分布式
系统中的多个模块在不同服务器上部署,即可称为分布式系统,如Tomcat和数据库分别部署在不同的服务器上,或两个相同功能的Tomcat分别部署在不同服务器上。
高可用
系统中部分节点失效时,其他节点能够接替它继续提供服务,则可认为系统具有高可用性。保证系统的高可用性,可从如下几个9说起,如图所示:
为了提高可用性,我们要么提高系统的无故障时间,要么减少系统的故障恢复时间,这就需要我们知道故障的原因。这个原因通常分为两大部分:
无计划的系统故障
1)系统级故障:包括主机、操作系统、中间件、数据库、网络、电源以及外围设备。
2)自然灾害、人为破坏,以及供电问题等。
有计划的日常任务:
1)运维相关:数据库维护、应用维护、中间件维护、操作系统维护、网络维护。
2)升级相关:数据库、应用、中间件、操作系统、网络,包括硬件升级。
我们再对这些故障做个归类:
总之,我们要正确认识故障,故障不可避免。尤其是在大型分布式系统中,出现故障是一种常态。有时出现故障根本就不知道出现在了什么地方。所以我们要对故障原因先有一个认识,与此同时我们要基于故障有应对的策略,也就是我们所说的“弹力设计”,就类似三国中的赵云猛将,在搏杀中能进能退。
集群
一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群。在常见的集群中,客户端往往能够连接任意一个节点获得服务,并且当集群中一个节点掉线时,其他节点往往能够自动的接替它继续提供服务,这时候说明集群具有高可用性。
在互联网应用初期,互联网用户数相对都较少,可以把web服务器(例如Tomcat)和数据库部署在同一台服务器上。浏览器往www.taobao.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。如图所示:
在单体架构下,所有功能模块(例如用户,商品,社区等)都会部署到一个web服务器(例如tomcat)中,所有用户都对同一个web服务进行访问,随着用户数的增长,这个web服务器的并发压力就会越来越大,Tomcat和数据库之间还要竞争计算机资源,单机性能就会越来越差,不足以支撑更加庞大业务。
Web服务器(Tomcat)和数据库器放在同一个计算机上时,tomcat和数据库会竞争CPU,内存等资源,web服务的性能就会相对较差。此时可以将tomcat和数据库进行独立部署,独占服务器资源,显著提高两者各自性能。
在这种架构下,随着用户数的增长,并发读写数据库的操作也会越来越多,此时数据库的读写方面就产生性能瓶颈问题(数据库支持的连接数是有限的,连接用尽时,其它用户就会阻塞,同时频繁磁盘读写也会使系统性越来越差),并发读写数据库成为瓶颈。
如何降低数据库的访问压力呢,无非就是减少对数据库的访问。此时,我们可以考虑应用缓存(cache)。例如,在Tomcat同服务器上增加本地缓存,并在外部增加分布式缓存,缓存一些相对热门的数据(热门商品信息或热门商品的html页面等)。通过缓存把绝大多数请求在读写数据库前拦截掉,这样降低数据库的访问频次,读写压力。提高请求响应速度。如图所示:
其中Cache这块,涉及的技术包括:基于JVM等技术的为本地缓存,使用Redis作为分布式缓存等。当然,引入缓存以后,性能方面可以得到一定程度的改善,但也会带来一些问题,例如缓存一致性、缓存穿透/击穿、缓存雪崩等问题。
缓存虽然抗住了大部分的访问请求,但随着用户数的增长,并发压力主要落在单机的Tomcat上。还有, 一个Tomcat的并发处理能力是有限的,请求越来越多时,部分请求的响应就会越来越慢,还有就是可靠性比较差,一旦这个tomcat服务宕机了,所有资源就不能访问了。
在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。其中涉及的技术包括:Nginx、HAProxy,如图所示:
反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈。
当一个tomcat服务无法处理更多并发时,我们就使用多个tomcat,分别部署在多台服务器上。然后,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。如图所示:
其中涉及的技术包括:Mycat,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能。
把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。
这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。对于这种方案,随着用户数的增长,单机的写库会逐渐会达到性能瓶颈。
比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。其中前面提到的Mycat也支持在大表拆分为小表情况下的访问控制。
这种做法显著的增加了数据库运维的难度,对DBA的要求较高。数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的,如分库分表的管理和请求分发,由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实是MPP(大规模并行处理)架构的一类实现。
数据库和Tomcat都能够水平扩展,可支撑的并发大幅提高,随着用户数的增长,最终单机的Nginx会成为瓶颈。
由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。此时采用LVS和F5作为网络负载均衡解决方案,如图所示:
其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。
此种方案中,由于LVS也是单机的,随着并发数增长到几十万时,LVS服务器最终会达到瓶颈,此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同。
在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡,至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问题。
随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求
按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。
不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级。
如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务,应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。如图所示:
个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想
目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。
在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响(在3.14节之前,服务运行在新增机器上需要修改系统配置来适配服务,这会导致机器上其他服务需要的运行环境被破坏)。
系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。
所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体,在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用,用户不需要关系应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。
若依微服务版RuoYi-Cloud,基于Spring Boot、Spring Cloud & Alibaba、OAuth2的前后端分离的后台管理系统。此系统内置模块如部门管理、角色用户管理、菜单及按钮授权、数据权限、系统参数、日志管理、代码生成等。在线定时任务配置,并且支持集群,支持多数据源。此系统还是我们公司内部的一套 Java EE 企业级快速开发平台.
若依官网 http://ruoyi.vip/。
若依微服务官网地址 https://doc.ruoyi.vip/ruoyi-cloud/
后端技术栈:
MyBatis、Spring、Spring Boot、Spring Cloud & Alibaba、Nacos、Sentinel
前端VUE技术栈:
ES6、vue、vuex、vue-router、vue-cli、axios、element-ui
后端项目骨架,如图所示:
com.ruoyi
├── ruoyi-ui // 前端框架 [80]
├── ruoyi-gateway // 网关模块 [8080]
├── ruoyi-auth // 认证中心 [9200]
├── ruoyi-api // 接口模块
│ └── ruoyi-api-system // 系统接口
├── ruoyi-common // 通用模块
│ └── ruoyi-common-core // 核心模块
│ └── ruoyi-common-datascope // 权限范围
│ └── ruoyi-common-datasource // 多数据源
│ └── ruoyi-common-log // 日志记录
│ └── ruoyi-common-redis // 缓存服务
│ └── ruoyi-common-security // 安全模块
│ └── ruoyi-common-swagger // 系统接口
├── ruoyi-modules // 业务模块
│ └── ruoyi-system // 系统模块 [9201]
│ └── ruoyi-gen // 代码生成 [9202]
│ └── ruoyi-job // 定时任务 [9203]
│ └── ruoyi-file // 文件服务 [9300]
├── ruoyi-visual // 图形化管理模块
│ └── ruoyi-visual-monitor // 监控中心 [9100]
├──pom.xml // 公共依赖
前端项目骨架结构,如图所示:
├── build // 构建相关
├── bin // 执行脚本
├── public // 公共文件
│ ├── favicon.ico // favicon图标
│ └── index.html // html模板
├── src // 源代码
│ ├── api // 所有请求
│ ├── assets // 主题字体等静态资源
│ ├── components // 全局公用组件
│ ├── directive // 全局指令
│ ├── layout // 布局
│ ├── router // 路由
│ ├── store // 全局 store管理
│ ├── utils // 全局公用方法
│ ├── views // view
│ ├── App.vue // 入口页面
│ ├── main.js // 入口 加载组件 初始化等
│ ├── permission.js // 权限管理
│ └── settings.js // 系统配置
├── .editorconfig // 编码格式
├── .env.development // 开发环境配置
├── .env.production // 生产环境配置
├── .env.staging // 测试环境配置
├── .eslintignore // 忽略语法检查
├── .eslintrc.js // eslint 配置项
├── .gitignore // git 忽略项
├── babel.config.js // babel.config.js
├── package.json // package.json
└── vue.config.js // vue.config.js
若依分布式架构设计,如图所示:
一般进入公司以后,可能team leader会给我们一套项目代码,然后我们需要基于代码规范或者原有业务去进行新的业务开发,这样的话我们就需要首先将系统跑起来,然后对其业务和代码进行分析,学习.
项目需要的基础环境需求如下:
JDK >= 1.8 (推荐1.8版本)
Mysql >= 5.7.0 (推荐5.7版本)
Redis >= 5.0 (市场主流版本,最新版本为6.xx)
Maven >= 3.6 (http://maven.apache.org/download.cgi)
Node >= 10 (稳定版14.X,https://nodejs.org/en/download/)
nacos >= 1.4.1 (https://github.com/alibaba/nacos/releases)
当这些软件不知道如何去下载时候,打开搜索引擎,使用组合查询方法,去找下载路径,
例如 redis download
Redis官网:
http://www.redis.cn/
http://www.redis.io/
Windows版本下载以后,网址如下:
https://github.com/tporadowski/redis/releases
windows下的安装,直接解压,其个目录如下:
安装windows下安装服务,在redis根目录启动命令行(cmd),执行
redis-server --service-install redis.windows.conf
启动redis服务(启动成功以后会出现successful)
redis-server --service-start
其它redis指令(这个操作可以不执行)
redis-server --service-stop //停止服务
redis-server --service-uninstall //卸载服务
连接测试redis,在redis根路径执行(默认端口6379)
redis-cli -h 127.0.0.1 -p 6379
(省略)
说明:mysql数据库的版本一定要在5.7以上,MariaDB最后用10.5.
第一步:Nacos下载,可在浏览器直接输入如下地址:
https://github.com/alibaba/nacos/releases
第二步:选择对应版本,直接下载,如图所示:
第三步:解压Nacos(最好不要解压到中文目录下),其目录结构如下:
第四步:打开/conf/application.properties里打开默认配置,并基于你当前环境配置要连接的数据库,连接数据库时使用的用户名和密码:
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/ry-config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
说明,后续我们配置RuoYi-Cloud项目时,这个项目很多配置信息需要存储到ry-config数据库中。
第一步:登录mysql,然后基于source执行指令去执行课前资料中的RuoYi-Cloud.sql文件(不推荐使用SQLYog执行),例如:
source d:/RuoYi-Cloud.sql
当执行成功以后,在ry-cloud数据库中就有如下表:
第二步:基于source指令执行RuoYi-Config.sql,例如:
source d:/RuoYi-Config.sql
执行成功以后会创建一个数据库ry-config,其内部的表如图所示:
第一步:进入nacos的bin目录,启动nacos(standalone代表着单机模式运行,非集群模式,此服务的启动对JDK有要求,必须jdk8):
startup.cmd -m standalone
第二步:访问Nacos服务
打开浏览器,输入http://localhost:8848/nacos地址,出现如下登陆页面:
默认登陆用户名和密码都为nacos,登陆成功以后,进入如下页面:
打开配置列表,如图所示:
然后从”上往下挨个编辑”,把设计到连接Mysql和Redis的所有地方,改为自己对应的用户名和密码,以网关的配置文件ruoyi-auth-dev.yml为例,如图所示:
第一步:基于IDEA打开项目,如图所示:
第二步:项目打开以后,配置maven下载项目依赖。
第一步:依次启动ruoyi-gateway,ruoyi-auth,ruoyi-modules下的ruoyi-system。
第二步:检测nacos的服务列表,如图所示:
第一步:选中前端项目,如图所示:
第二步:右键前端项目,打开终端命令行,如图所示:
第三步:在命令行输入npm install 安装项目依赖,如图所示:
第一步:在命令行输入 npm run dev 启动运行前端服务,如图所示:
第二步:浏览器输入localhost进行访问,进入登陆页面,如图所示
第三步:输入账号(admin/admin123),登陆系统,如图所示:
讲微服务之前,我们先分析以下单体应用。所谓单体应用一般是基于idea/eclipse,maven等建一个工程,然后基于SpringBoot,spring,mybatis框架进行整合,接下来再写一堆dao、mapper、service、controller,再加上一些的配置文件,有可能还会引入redis、elasticsearch、mq等其它项目的依赖,开发好之后再将项目打包成一个jar包/war包。然后再将包扔到类似tomcat这样的web服务中,最后部署到公司提供给你的linux服务器上。 接下来,你针对服务提供的访问端口(例如8080端口)发起http请求,请求会由tomcat直接转交给你的spring web组件,进行一层一层的代码调用。对于这样的设计一般适合企业的内部应用,访问量不大,体积比较小,5人以内的团队即可开发和维护。但对于一些大型互联网项目,假如需要10人以上的开发和维护团队,仅频繁的创建代码分支,编写业务功能,然后合并分支,就会出现很多代码冲突。每次解决这些大量的代码冲突,可能就会耗费好几天的时间。基于这样的背景微服务诞生了.
在微服务架构设计中,建议超出需要10人开发和维护的项目要进行系统拆分,就是把大系统拆分为很多小系统,几个人负责一个服务这样每个服务独立的开发、测试和上线,代码冲突少了,每次上线就回归测试自己的一个服务即可,测试速度快了,上线是独立的,只要向后兼容接口就行了,不需要跟别人等待和协调,技术架构和技术版本的升级,几个人ok就行,成本降低,更加灵活了。
微服务架构(MSA)的基础是将单个应用程序开发为一组小型独立服务,这些独立服务在自己的进程中运行,独立开发和部署。如图所示:
这些服务使用轻量级 API 通过明确定义的接口进行通信。这些服务是围绕业务功能构建的,每项服务执行一项功能。由于它们是独立运行的,因此可以针对各项服务进行更新、部署和扩展,以满足对应用程序特定功能的需求。
生活中的微服务,如图所示:
程序中的微服务,就是将各个业务系统的共性再进行抽取,做成独立的服务,如图所示:
总之,微服务是分布式系统中的一种流行的架构模型,它并不是银弹,所以,也不要寄希望于微服务构架能够解决所有的问题。微服务架构主要解决的是如何快速地开发和部署我们的服务,这对于一个能够适应快速开发和成长的公司是非常必要的。同时,微服务设计中有很多很不错的想法和理念,通过学习微服务架构我们可以更快的迈向卓越。
Spring Cloud Alibaba 是Spring Cloud的一个子项目,致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
Spring Cloud Alibaba 默认提供了如下核心功能(先了解):
基于Spring Cloud Alibaba实现的微服务,解决方案设计架构如图所示:
微服务项目一般都会采用聚合工程结构,可通过聚合工程结构实现共享资源的复用,简化项目的管理方式。本小节以一个聚合工程结构为案例,讲解微服务架构方案中的maven聚合工程的基本结构,例如:
GitCGB2105IVProjects (工作区/空项目)
├── 01-sca //(微服务父工程)
├── sca-provider //服务提供方法
├── sca-consumer //服务消费方法
├── sca-gateway //网关服务
打开Idea,创建一个空项目(Empty Project),项目名为GitCGB2105IVProjects,例如:
其中,这个空项目就类似磁盘中的一个空的文件夹,可以将此文件夹作为一个代码工作区。
第一步:配置maven环境(只要是新的工作区,都要重新配置),注意本地库选择新的位置不要与其它项目共用本地库,因为多个项目所需要依赖的版本不同时,可能会有一些依赖版本的冲突。.
说明,这里的本地库名字最要不要选择中文,单词之间也不要有空格。
第二步:配置JDK编译环境
聚合工程在编译时,需要对相关依赖的工程进行一起编译,所以需要做一些配置,例如:
指定一下当前工作区的jdk编译版本,例如:
第三步:配置工作区中项目编码方式
我们后续在创建微服务工程进行学习时,相关服务依赖版本的管理,公共依赖,项目编译,打包设计等都可以放在此工程下,进行统一的配置,然后实现共享。
第一步:创建父工程模块,例如:
第二步:删除工程中的src目录(父工程不需要这个目录),例如
第三步:修改项目pom.xml文件内容,例如:
4.0.0
com.jt
01-sca
1.0-SNAPSHOT
org.springframework.boot
spring-boot-dependencies
2.3.2.RELEASE
pom
import
org.springframework.cloud
spring-cloud-dependencies
Hoxton.SR9
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.6.RELEASE
pom
import
org.projectlombok
lombok
provided
org.springframework.boot
spring-boot-starter-test
test
org.junit.jupiter
junit-jupiter-engine
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
8
8
其中,服务核心依赖版本可参考如下网址(涉及到一个兼容性问题,不能随意指定其版本):
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
创建服务提供方工程,继承01-sca,例如:
创建服务消费方工程,继承01-sca,例如:
创建网关工程(这个工程后续会作为API服务访问入口),继承01-sca,例如:
基于前面章节创建的项目,后续我们会讲解服务的注册,服务的配置,服务之间的调用,负载均衡,限流,熔断,网关等相关知识,现在先了解一个简易结构,例如:
我们最终会基于这个结构的设计,实现一个从网关到服务消费方,再从服务消费方到服务提供方的一个调用链路的业务及代码实践过程。
总之,微服务是一个架构设计方式,此架构中的每个服务(service)都是针对一组功能而设计的,并专注于解决特定的问题。如果开发人员逐渐将更多代码增加到一项服务中并且这项服务变得复杂,那么可以将其拆分成多项更小的服务(软件即服务,所有软件对外的表现形式就诗提供一种或多种业务服务)。接下来进行独立的开发、测试、部署、运行、维护。进而更好,更灵活的处理客户端的请求并提高系统的可靠性,可扩展性。
在微服务中,首先需要面对的问题就是如何查找服务(软件即服务),其次,就是如何在不同的服务之间进行通信?如何更好更方便的管理应用中的每一个服务,如何建立各个服务之间联系的纽带,由此注册中心诞生(例如淘宝网卖家提供服务,买家调用服务)。
市面上常用注册中心有Zookeeper(雅虎Apache),Eureka(Netfix),Nacos(Alibaba),Consul(Google),那他们分别都有什么特点,我们如何进行选型呢?我们主要从社区活跃度,稳定性,功能,性能等方面进行考虑即可.本次微服务的学习,我们选择Nacos,它很好的支持了阿里的双11活动,不仅可以做注册中心,还可以作为配置中心,稳定性和性能都很好。
Nacos(DynamicNaming and Configuration Service)是一个应用于服务注册与发现、配置管理的平台。它孵化于阿里巴巴,成长于十年双十一的洪峰考验,沉淀了简单易用、稳定可靠、性能卓越的核心竞争力。其官网地址如下:
https://nacos.io/zh-cn/docs/quick-start.html
第一:确保你电脑已配置JAVA_HOME环境变量(Nacos启动时需要),例如:
第二:确保你的MySQL版本为5.7以上(MariaDB10.5以上),例如
第一步:Nacos下载,可在浏览器直接输入如下地址:
https://github.com/alibaba/nacos/releases
第二步:选择对应版本,直接下载,如图所示:
第三步:解压Nacos(最好不要解压到中文目录下),其目录结构如下:
第一步:登陆mysql,执行老师发给同学们的sql脚本。例如,我们可以使用mysql自带客户端,在命令行首先登录mysql,然后执行如下指令:
source d:/nacos-mysql.sql
执行成功以后,会创建一个nacos_config数据库,打开数据库会看到一些表,例如;
说明:在执行此文件时,要求mysql的版本大于5.7版本(MariaDB最好10.5.11),否则会出现如下错误:
第二步:打开/conf/application.properties里打开默认配置,并基于你当前环境配置要连接的数据库,连接数据库时使用的用户名和密码(假如前面有"#"要将其去掉):
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
第一步:启动Nacos服务。
Linux/Unix/Mac启动命令(standalone代表着单机模式运行,非集群模式):
./startup.sh -m standalone
Windows启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
说明:
1)执行执行令时要么配置环境变量,要么直接在nacos/bin目录下去执行.
2)nacos启动时需要本地环境变量中配置了JAVA_HOME(对应jdk的安装目录),
3)一定要确保你连接的数据库(nacos_config)是存在的.
4)假如所有的配置都正确,还连不上,检查一下你有几个数据库(mysql,…)
第二步:访问Nacos服务。
打开浏览器,输入http://localhost:8848/nacos地址,出现如下登陆页面:
其中,默认账号密码为nacos/nacos.
创建两个项目Module分别为服务提供者和服务消费者,两者都要注册到NacosServer中(这个server本质上就是一个web服务,端口默认为8848),然后服务提供者可以为服务消费者提供远端调用服务(例如支付服务为服务提供方,订单服务为服务消费方),如图所示:
第一步:创建服务提供者工程(module名为sca-provider,假如已有则无需创建),继承parent工程(01-sca),其pom.xml文件内容如下:
01-sca
com.jt
1.0-SNAPSHOT
4.0.0
sca-provider
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
第二步:创建并修改配置文件application.yml(或者application.properties),实现服务注册,关键代码如下:
server:
port: 8081
spring:
application:
name: sca-provider #进行服务注册必须配置服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848
注意:服务名不要使用下划线(“_”),应使用横杠(“-”),这是规则。
第三步:创建启动类(假如已有则无需定义),关键代码如下:
package com.cy;
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
第四步:启动启动类,然后刷先nacos服务,检测是否服务注册成功,如图所示:
第六步:打开浏览器,输入http://localhost:8081/provider/echo/msa,然后进行访问测试。
第一步: 在sca-provider项目中创建服务提供方对象,基于此对象对外提供服务,例如:
package com.jt.provider.controller;
/**定义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;
}
}
第二步:创建服务消费者工程(module名为sca-consumer,假如已有则无需创建),继承parent工程(01-sca),其pom.xml文件内容如下:
01-sca
com.jt
1.0-SNAPSHOT
4.0.0
sca-consumer
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
第三步:修改配置文件application.yml,关键代码如下:
server:
port: 8090
spring:
application:
name: sca-consumer #服务注册时,服务名必须配置
cloud:
nacos:
discovery:
server-addr: localhost:8848 #从哪里去查找服务
第四步:创建消费端启动类并实现服务消,关键代码如下:
package com.cy;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
第五步:在启动类中添加如下方法,用于创建RestTemplate对象.
@Bean
public RestTemplate restTemplate(){//基于此对象实现远端服务调用
return new RestTemplate();
}
第六步:定义服务消费端Controller,在此对象方法内部实现远端服务调用
package com.jt.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 定义服务消费端Controller,在这个Controller对象
* 的方法中实现对远端服务sca-provider的调用
*/
@RestController
public class ConsumerController {
/**
* 从spring容器获取一个RestTemplate对象,
* 基于此对象实现远端服务调用
*/
@Autowired
private RestTemplate restTemplate;
/**
* 在此方法中通过一个RestTemplate对象调用远端sca-provider中的服务
* @return
* 访问此方法的url: http://localhost:8090/consumer/doRestEcho1
*/
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho01(){
//1.定义要调用的远端服务的url
String url="http://localhost:8081/provider/echo/8090";
//2.基于restTemplate对象中的相关方法进行服务调用
return restTemplate.getForObject(url, String.class);
}
}
第七步:启动消费者服务,并在浏览器输入http://localhost:8090/consumer/doRestEcho1地址进行访问,假如访问成功会出现,如图所示效果:
一个服务实例可以处理请求是有限的,假如服务实例的并发访问比较大,我们会启动多个服务实例,让这些服务实例采用一定策略均衡(轮询,权重,随机,hash等)的处理并发请求,在Nacos中服务的负载均衡(Nacos客户端负载均衡)是如何应用的?
LoadBalancerClient对象可以从nacos中基于服务名获取服务实例,然后在工程中基于特点算法实现负载均衡方式的调用,案例实现如下:
第一步:修改ConsumerController类,注入LoadBalancerClient对象,并添加doRestEcho2方法,然后进行服务访问.
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/consumer/doRestEcho02")
public String doRestEcho02(){
ServiceInstance serviceInstance = loadBalancerClient.choose("sca-provider");
String url = String.format("http://%s:%s/provider/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
System.out.println("request url:"+url);
return restTemplate.getForObject(url, String.class);
}
}
第二步:打开Idea服务启动配置,如图所示:
第三步:修改并发运行选项(假如没有找到这个选项我们需要通过搜索引擎基于组合查询的方法,去找到对应的解决方案,例如搜索 idea allow parallel run),如图所示:
第四步:修改sca-provider的配置文件端口,分别以8081,8082端口方式进行启动。
server:
port: 8082
spring:
application:
name: sca-provider
cloud:
nacos:
server-addr: localhost:8848
启动成功以后,访问nacos的服务列表,检测服务是否成功注册,如图所示:
第四步:启动sca-consumer项目模块,打开浏览器,输入如下网址进行反复服务访问:
http://localhost:8090/consumer/doRestEcho02
然后会发现sca-provider的两个服务都可以处理sca-consumer的请求。
这里多个实例并发提供服务的方式为负载均衡,这里的负载均衡实现默认是因为Nacos集成了Ribbon来实现的,Ribbon配合RestTemplate,可以非常容易的实现服务之间的访问。Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是客户端的负载均衡(客户端可以采用一定算法,例如轮询访问,访问服务端实例信息),这个功能可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡方式的服务调用。
当使用RestTemplate进行远程服务调用时,假如需要负载均衡,可以在RestTemplate对象构建时,使用@LoadBalanced对构建RestTemplate的方法进行修饰,例如在ConsumerApplication中构建名字为loadBalancedRestTemplate的RestTemplate对象:
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
在需要RestTemplate实现负载均衡调用的地方进行依赖注入.例如在ConsumerController类中添加loadBalancedRestTemplate属性
@Autowired
private RestTemplate loadBalancedRestTemplate;
接下来,可以在对应的服务端调用方的方法内,基于RestTemplate借助服务名进行服务调用, 例如:
@GetMapping("/consumer/doRestEcho3")
public String doRestEcho03(){
String url=String.format("http://%s/provider/echo/%s","sca-provider",appName);
//向服务提供方发起http请求,获取响应数据
return loadBalancedRestTemplate.getForObject(
url,//要请求的服务的地址
String.class);//String.class为请求服务的响应结果类型
}
RestTemplate在发送请求的时候会被LoadBalancerInterceptor拦截,它的作用就是用于RestTemplate的负载均衡,LoadBalancerInterceptor将负载均衡的核心逻辑交给了loadBalancer,核心代码如下所示(了解):
public ClientHttpResponse intercept(final HttpRequest request,
final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName,
requestFactory.createRequest(request, body, execution));
}
@LoadBalanced注解是属于Spring,而不是Ribbon的,Spring在初始化容器的时候,如果检测到Bean被@LoadBalanced注解,Spring会为其设置LoadBalancerInterceptor的拦截器。
基于Ribbon方式的负载均衡,Netflix默认提供了七种负载均衡策略,对于SpringCloud Alibaba解决方案中又提供了NacosRule策略,默认的负载均衡策略是轮训策略。如图所示:
当系统提供的负载均衡策略不能满足我们需求时,我们还可以基于IRule接口自己定义策略.
服务消费方基于rest方式请求服务提供方的服务时,一种直接的方式就是自己拼接url,拼接参数然后实现服务调用,但每次服务调用都需要这样拼接,代码量复杂且不易维护,此时Feign诞生。
Feign 是一种声明式Web服务客户端,底层封装了对Rest技术的应用,通过Feign可以简化服务消费方对远程服务提供方法的调用实现。如图所示:
Feign 最早是由 Netflix 公司进行维护的,后来 Netflix 不再对其进行维护,最终 Feign 由一些社区进行维护,更名为 OpenFeign。
第一步:在服务消费方,添加项目依赖(SpringCloud团队基于OpenFeign研发了starter),代码如下:
org.springframework.cloud
spring-cloud-starter-openfeign
第二步:在启动类上添加@EnableFeignClients注解,代码如下:
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {…}
第三步:定义Http请求API,基于此API借助OpenFeign访问远端服务,代码如下:
package com.jt.consumer.service;
@FeignClient(name="sca-provider")//sca-provider为服务提供者名称
public interface RemoteProviderService{
@GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
public String echoMessage(@PathVariable("string") String string);
}
其中,@FeignClient描述的接口底层会为其创建实现类。
第四步:创建FeignConsumerController中并添加feign访问,代码如下:
package com.jt.consumer.controller;
@RestController
@RequestMapping("/consumer/ ")
public class FeignConsumerController {
@Autowired
private RemoteProviderService remoteProviderService;
/**基于feign方式的服务调用*/
@GetMapping("/echo/{msg}")
public String doFeignEcho(@PathVariable String msg){
//基于feign方式进行远端服务调用(前提是服务必须存在)
return remoteProviderService.echoMessage(msg);
}
}
第五步:启动消费者服务,在浏览器中直接通过feign客户端进行访问,如图所示(反复刷新检测其响应结果):
一个服务提供方通常会提供很多资源服务,服务消费方基于同一个服务提供方写了很多服务调用接口,此时假如没有指定contextId,服务
启动就会失败,例如,假如在服务消费方再添加一个如下接口,消费方启动时就会启动失败,例如:
@FeignClient(name="sca-provider")
public interface RemoteOtherService {
@GetMapping("/doSomeThing")
public String doSomeThing();
}
其启动异常:
The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
此时我们需要为远程调用服务接口指定一个contextId,作为远程调用服务的唯一标识即可,例如:
@FeignClient(name="sca-provider",contextId="remoteProviderService")//sca-provider为服务提供者名称
interface RemoteProviderService{
@GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
public String echoMessage(@PathVariable("string") String string);
}
还有,当我们在进行远程服务调用时,假如调用的服务突然不可用了或者调用过程超时了,怎么办呢?一般服务消费端会给出具体的容错方案,例如,在Feign应用中通过FallbackFactory接口的实现类进行默认的相关处理,例如:
第一步:定义FallbackFactory接口的实现,代码如下:
package com.cy.service.factory;
/**
* 基于此对象处理RemoteProviderService接口调用时出现的服务中断,超时等问题
*/
@Component
public class ProviderFallbackFactory implements FallbackFactory {
/**
* 此方法会在RemoteProviderService接口服务调用时,出现了异常后执行.
* @param throwable 用于接收异常
*/
@Override
public RemoteProviderService create(Throwable throwable) {
return (msg)->{
return "服务维护中,稍等片刻再访问";
};
}
}
第二步:在Feign访问接口中应用FallbackFactory对象,例如:
@FeignClient(name = "sca-provider", contextId = "remoteProviderService",
fallbackFactory = ProviderFallbackFactory.class)//sca-provider为nacos中的服务名
public interface RemoteProviderService {
@GetMapping("/provider/echo/{msg}")
public String echoMsg(@PathVariable String msg);
}
第三步:在配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制.
feign:
hystrix:
enabled: true #默认值为false
第四步:在服务提供方对应的调用方法中添加Thread.sleep(500000)模拟耗时操作,然后启动服务进行访问测试.
Feign应用过程分析(底层逻辑先了解):
1)通过 @EnableFeignCleints 注解告诉springcloud,启动 Feign Starter 组件。
2) Feign Starter 会在项目启动过程中注册全局配置,扫描包下所由@FeignClient注解描述的接口,然后由系统底层创建接口实现类(JDK代理类),并构建类的对象,然后交给spring管理(注册 IOC 容器)。
3) Feign接口被调用时会被动态代理类逻辑拦截,然后将 @FeignClient 请求信息通过编码器创建 Request对象,基于此对象进行远程过程调用。
4) Feign客户端请求对象会经Ribbon进行负载均衡,挑选出一个健康的 Server 实例(instance)。
5) Feign客户端会携带 Request 调用远端服务并返回一个响应。
6) Feign客户端对象对Response信息进行解析然后返回客户端。
我们知道,除了代码之外,软件还有一些配置信息,比如数据库的用户名和密码,还有一些我们不想写死在代码里的东西,例如像线程池大小、队列长度等运行参数,以及日志级别、算法策略等, 还有一些是软件运行环境的参数,如Java 的内存大小,应用启动的参数,包括操作系统的一些 参数配置…… 所有这些东西,我们都叫做软件配置。以前,我们把软件配置写在一个配置文件中,就像 Windows 下的 ini 文件,或是 Linux 下的 conf 文件。然而,在分布式系统下,这样的方式就变得非常不好管理,并容易出错。假如生产环境下,项目现在正在运行,此时修改了配置文件,我们需要让这些配置生效,通常的做法是不是要重启服务。但重启是不是会带来系统服务短时间的暂停,从而影响用户体验呢,还有可能会带来经济上的很大损失(例如双11重启下服务)。基于这样的背景,配置中心诞生了。
配置中心最基础的功能就是存储一个键值对,用户发布一个配置(configKey),然后客户端获取这个配置项(configValue);进阶的功能就是当某个配置项发生变更时,不停机就可以动态刷新服务内部的配置项,例如,在生产环境上我们可能把我们的日志级别调整为 error 级别,但是,在系统出问题我们希望对它 debug 的时候,我们需要动态的调整系统的行为的能力,把日志级别调整为 debug 级别。还有,当你设计一个电商系统时,设计大促预案一定会考虑,同时涌进来超过一亿人并发访问的时候,假如系统是扛不住的,你会怎么办,在这个过程中我们一般会采用限流,降级。系统的限流和降级本质上来讲就是从日常的运行态切换到大促态的一个行为的动态调整,这个本身天然就是配置起到作用的一个相应的场景。
在面向分布式的微服务系统中,如何通过更高效的配置管理方式,实现微服务系统架构持续“无痛”的演进,并动态调整和控制系统的运行时态,配置中心的选型和设计起着举足轻重的作用。市场上主流配置中心有Apollo(携程开源),nacos(阿里开源),Spring Cloud Config(Spring Cloud 全家桶成员)。我们在对这些配置中心进行选型时重点要从产品功能、使用体验、实施过程和性能等方面进行综合考量。本次课程我们选择nacos,此组件不仅提供了注册中心,还具备配置中心的功能。
在sca-provider项目中添加一个Controller对象,例如ProviderLogController,基于此Controller中的方法演示日志级别的配置。
第一步:创建ProviderLogController对象,例如:
package com.jt.provider.controller;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 基于此controller演示配置中心的作用.
* 在这个controller中我们会基于日志对象
* 进行日志输出测试.
*/
//@Slf4j
@RestController
public class ProviderLogController {
//创建一个日志对象
//org.slf4j.Logger (Java中的日志API规范,基于这个规范有Log4J,Logback等日志库)
//org.slf4j.LoggerFactory
//log对象在哪个类中创建,getLogger方法中的就传入哪个类的字节码对象
//记住:以后只要Java中使用日志对象,你就采用下面之中方式创建即可.
//假如在log对象所在的类上使用了@Slf4j注解,log不再需要我们手动创建,lombok会帮我们创建
private static Logger log=
LoggerFactory.getLogger(ProviderLogController.class);
@GetMapping("/provider/log/doLog01")
public String doLog01(){//trace
第二步:在已有的sca-provider项目中添加如配置依赖,例如:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
第三步: 将项目sca-provider的application.yml的名字修改为bootstrap.yml(启动优先级最高),并添加配置中心配置,代码如下:
spring:
application:
name: sca-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yml # Configure the data format of the content, default to properties
打开nacos配置中心,新建配置,如图所示:
其中,Data ID的值要与bootstrap.yml中定义的spring.application.name的值相同(服务名-假如有多个服务一般会创建多个配置实例,不同服务对应不同的配置实例)。配置发布以后,会在配置列表中,显示我们的配置,例如:
配置创建好以后,启动sca-provider服务,然后打开浏览器,输入http://localhost:8081/provider/log/doLog01,检测idea控制台日志输出。然后再打开nacos控制台动态更新日志级别,再访问资源并检测后台日志输出.
然后,修改nacos配置中心的日志级别,再刷新浏览器,检测日志的输出,是否会发生变化.
对于nacos配置中心而言,有系统内部对配置变化的感知,还有外部系统对配置的感知,假如我们系统在浏览器中能看到日志级别的变化,该如何实现呢?我们现在来实现一个案例.
第一步:在ProviderLogController类的上面添加一个@RefreshScope注解,例如:
@RefreshScope
@RestController
public class ProviderLogController{
//.....
}
其中,@RefreshScope的作用是在配置中心的相关配置发生变化以后,能够及时看到类中属性值的更新(底层是通过重新创建Controller对象的方式,对属性进行了重新初始化)。
第二步:添加ProviderLogController中添加一个获取日志级别(debug 第三步:启动sca-provider服务,然后打开浏览器并输入http://localhost:8081/provider/log/doLog02进行访问测试。 说明,假如对配置的信息访问不到,请检测项目配置文件的名字是否为bootstrap.yml,检查配置文件中spring.application.name属性的值是否与配置中心的data-id名相同,还有你读取的配置信息缩进以及空格写的格式是否正确. Nacos 配置管理模型由三部分构成,如图所示: Nacos中的命名空间一般用于配置隔离,这种命名空间的定义一般会按照环境(开发,生产等环境)进行设计和实现.我们默认创建的配置都存储到了public命名空间,如图所示: 创建新的开发环境并定义其配置,然后从开发环境的配置中读取配置信息,该如何实现呢? 第一步:创建新命名空间,如图所示: 命名空间成功创建以后,会在如下列表进行呈现。 在指定命名空间下添加配置,也可以直接取配置列表中克隆,例如: 此时我们修改dev命名空间中Data Id的sca-provider配置,如图所示: 其中,namespace后面的字符串为命名空间的id,可直接从命名空间列表中进行拷贝. 当我们在指定命名空间下,按环境或服务做好了配置以后,有时还需要基于服务做分组配置,例如,一个服务在不同时间节点(节假日,活动等)切换不同的配置,可以在新建配置时指定分组名称,如图所示: 配置发布以后,修改boostrap.yml配置类,在其内部指定我们刚刚创建的分组,代码如下: 在指定的Controller类中添加属性和方法用于获取和输出DEV_GROUP_51配置中设置的线程数,代码如下: 然后重启服务,进行测试,检测内容输出。 当同一个namespace的多个配置文件中都有相同配置时,可以对这些配置进行提取,然后存储到nacos配置中心的一个或多个指定配置文件,哪个微服务需要,就在服务的配置中设置读取即可。例如: 第一步:在nacos中创建一个共享配置文件,例如: 第二步:在指定的微服务配置文件(bootstrap.yml)中设置对共享配置文件的读取,例如: 第三步:在指定的Controller类中读取和应用共享配置即可,例如: 第四步:启动服务,然后打开浏览器进行访问测试。 。。。。 在我们日常生活中,经常会在淘宝、天猫、京东、拼多多等平台上参与商品的秒杀、抢购以及一些优惠活动,也会在节假日使用12306 手机APP抢火车票、高铁票,甚至有时候还要帮助同事、朋友为他们家小孩拉投票、刷票,这些场景都无一例外的会引起服务器流量的暴涨,导致网页无法显示、APP反应慢、功能无法正常运转,甚至会引起整个网站的崩溃。 Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。 Sentinel核心分为两个部分: Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能,其控制台安装步骤如下: 第二步:下载Jar包(可以存储到一个sentinel目录),如图所示: 第三步:在sentinel对应目录,打开命令行(cmd),启动运行sentinel 检测启动过程,如图所示: 第一步:假如Sentinal启动ok,通过浏览器进行访问测试,如图所示: 第二步:登陆sentinel,默认用户和密码都是sentinel,登陆成功以后的界面如图所示: 我们系统中的数据库连接池,线程池,nginx的瞬时并发,MQ消息等在使用时都会跟定一个限定的值,这本身就是一种限流的设计。限流的目的防止恶意请求流量、恶意攻击,或者防止流量超过系统峰值。 第一步:Sentinel 应用于服务消费方(Consumer),在消费方添加依赖如下: 第二步:打开服务消费方配置文件application.yml,添加sentinel配置,代码如下: 第三步:启动服务提供者,服务消费者,然后在浏览器访问消费者url,如图所示: 第四步:刷新sentinel 控制台,检测服务列表,如图所示: 我们设置一下指定接口的流控(流量控制),QPS(每秒请求次数)单机阈值为1,代表每秒请求不能超出1次,要不然就做限流处理,处理方式直接调用失败。 第一步:选择要限流的链路,如图所示: 第二步:设置限流策略,如图所示: 第三步:反复刷新访问消费端端服务,检测是否有限流信息输出,如图所示: QPS(Queries Per Second):当调用相关url对应的资源时,QPS达到单机阈值时,就会限流。 线程数:当调用相关url对应的资源时,线程数达到单机阈值时,就会限流。 Sentinel的流控模式代表的流控的方式,默认【直接】,还有关联,链路。 直接模式 Sentinel默认的流控处理就是【直接->快速失败】。 关联模式 当关联的资源达到阈值,就限流自己。例如设置了关联资源为/ur2时,假如关联资源/url2的qps阀值超过1时,就限流/url1接口(是不是感觉很霸道,关联资源达到阀值,是本资源接口被限流了)。这种关联模式有什么应用场景呢?我们举个例子,订单服务中会有2个重要的接口,一个是读取订单信息接口,一个是写入订单信息接口。在高并发业务场景中,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能。业务中如果我们希望写入订单比较重要,要优先考虑写入订单接口。那就可以利用关联模式;在关联资源上面设置写入接口,资源名设置读取接口就行了;这样就起到了优先写入,一旦写入请求多,就限制读的请求。例如: 链路模式 链路模式只记录指定链路入口的流量。也就是当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流。被调用的方法用@SentinelResource进行注解,然后分别用不同业务方法对此业务进行调用,假如A业务设置了链路模式的限流,在B业务中是不受影响的。例如现在设计一个业务对象,代码如下(为了简单,可以直接写在启动类内部): 接下来我们在/consumer/doRestEcho1对应的方法中对ConsumerService中的doGetResource方法进行调用(应用consumerService对象之前,要先在doRestEcho01方法所在的类中进行consumerService值的注入)。例如: 其路由规则配置如下: 说明,流控模式为链路模式时,假如是sentinel 1.7.2以后版本,Sentinel Web过滤器默认会聚合所有URL的入口为sentinel_spring_web_context,因此单独对指定链路限流会不生效,需要在application.yml添加如下语句来关闭URL PATH聚合,例如: 修改配置以后,重新sentinel,并设置链路流控规则,然后再频繁对链路/consumer/doRestEcho1进行访问,检测是否会出现500异常。 此模块做为课后了解内容,感兴趣自学即可. 快速失败 流量达到指定阀值,直接返回报异常。(类似路前方坍塌,后面设定路标,让后面的车辆返回) WarmUp (预热) WarmUp也叫预热,根据codeFactor(默认3)的值,(阀值/codeFactor)为初始阈值,经过预热时长,才到达设置的QPS的阈值,假如单机阈值为100,系统初始化的阈为 100/3 ,即阈值为33,然后过了10秒,阈值才恢复到100。这个预热的应用场景,如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。例如: 排队等待 从字面上面就能够猜到,匀速排队,让请求以均匀的速度通过,阈值类型必须设成QPS,否则无效。比如有时候系统在某一个时刻会出现大流量,之后流量就恢复稳定,可以采用这种排队模式,大流量来时可以让流量请求先排队,等恢复了在慢慢进行处理,例如: 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。 修改ConumserController 类中的doRestEcho01方法,假如没有创建即可,基于此方法演示慢调用过程下的限流,代码如下: 第一步:服务启动后,选择要降级的链路,如图所示: 第二步:选择要降级的链路,如图所示: 这里表示熔断策略为慢调用比例,表示链路请求数超过3时,假如平均响应时间假如超过200毫秒的有50%,则对请求进行熔断,熔断时长为10秒钟,10秒以后恢复正常。 第三步:对指定链路进行刷新,多次访问测试,假如出现了降级熔断,会出现如下结果: 我们也可以进行断点调试,在DefaultBlockExceptionHandler中的handle方法内部加断点,分析异常类型,假如异常类型DegradeException则为降级熔断。 系统提供了默认的异常处理机制,假如默认处理机制不满足我们需求,我们可以自己进行定义。定义方式上可以直接或间接实现BlockExceptionHandler接口,并将对象交给spring管理。 Sentinel熔断降级支持慢调用比例、异常比例、异常数三种策略。 慢调用指耗时大于阈值RT(Response Time)的请求称为慢调用,阈值RT由用户设置。其属性具体含义说明如下: 当资源的每秒请求数大于等于最小请求数,并且异常总数占通过量的比例超过比例阈值时,资源进入降级状态。其属性说明如下: 当资源近1分钟的异常数目超过阈值(异常数)之后会进行服务降级。注意,由于统计时间窗口是分钟级别的,若熔断时长小于60s,则结束熔断状态后仍可能再次进入熔断状态。其属性说明如下: 基于异常数的状态分析如下: 何为热点?热点即经常访问的数据。比如: 热点参数限流会统计传入参数中的热点数据,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。其中,Sentinel会利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。 第一步:定义热点业务代码,如图所示: 第二步:服务启动后,选择要限流的热点链路,如图所示: 第三步:设置要限流的热点,如图所示: 热点规则的限流模式只有QPS模式(这才叫热点)。参数索引为@SentinelResource注解的方法参数下标,0代表第一个参数,1代表第二个参数。单机阈值以及统计窗口时长表示在此窗口时间超过阈值就限流。 第四步:多次访问热点参数方法,前端会出现如下界面,如图所示: 然后,在后台出现如下异常表示限流成功。 其中,热点参数其实说白了就是特殊的流控,流控设置是针对整个请求的;但是热点参数他可以设置到具体哪个参数,甚至参数针对的值,这样更灵活的进行流控管理。 配置参数例外项,如图所示: 这里表示参数值为5时阈值为100,其它参数值阈值为1,例如当我们访问http://ip:port/consumer/doRestEcho1?id=5时的限流阈值为100。 系统在生产环境运行过程中,我们经常需要监控服务器的状态,看服务器CPU、内存、IO等的使用率;主要目的就是保证服务器正常的运行,不能被某些应用搞崩溃了;而且在保证稳定的前提下,保持系统的最大吞吐量。 Sentinel的系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load(负载)、RT(响应时间)、入口 QPS 、线程数和CPU使用率五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。如图所示: 系统规则是一种全局设计规则,其中, 说明,系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务。 很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。例如微信中的黑名单。 sentinel可以基于黑白名单方式进行授权规则设计,如图所示: 案例实现: 定义请求解析器,用于对请求进行解析,并返回解析结果,sentinel底层 在拦截到用户请求以后,会对请求数据基于此对象进行解析,判定是否符合黑白名单规则 第一步:定义RequestOriginParser接口的实现类,基于业务在接口方法中解析请求数据并返回,底层会基于此返回值进行授权规则应用。 第二步:定义流控规则,如图所示: 第三步:执行资源访问,检测授权规则应用,当我们配置的流控应用值为app1时,假如规则为黑名单,则基于 第四步:设计过程分析,如图所示: 拓展:尝试基于请求ip,请求头方式进行黑白名单的规则设计,例如: 总之,Sentinel可为秒杀、抢购、抢票、拉票等高并发应用,提供API接口层面的流量限制,让突然暴涨而来的流量用户访问受到统一的管控,使用合理的流量放行规则使得用户都能正常得到服务。 我们知道,一个大型系统在设计时,经常会被拆分为很多个微服务。那么作为客户端要如何去调用 这么多的微服务呢?客户端可以直接向微服务发送请求,每个微服务都有一个公开的URL,该URL可以直接映射到具体的微服务,如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。这样的架构,会存在着诸多的问题,例如,客户端请求不同的微服务可能会增加客户端代码或配置的复杂性。还有就是每个服务,在调用时都需要独立认证。并且存在跨域请求,也在一定程度上提高了代码的复杂度。基于微服务架构中的设计及实现上的问题,为了在项目中简化前端的调用逻辑,同时也简化内部服务之间互相调用的复杂度,更好保护内部服务,提出了网关的概念。 网关本质上要提供一个各种服务访问的入口,并提供服务接收并转发所有内外部的客户端调用,还有就是权限认证,限流控制等等。Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 等技术开发的一个网关组件,它旨在为微服务架构提供一种简单有效的统一的 API入口,负责服务请求路由、组合及协议转换,并且基于 Filter 链的方式提供了权限认证,监控、限流等功能。 Spring Cloud Gateway优缺点分析: 通过网关作为服务访问入口,对系统中的服务进行访问,例如通过网关服务去访问sca-provider服务. 第一步:创建sca-gateway模块(假如已有则无须创建),其pom.xml文件如下: 第二步:创建application.yml(假如已有则无须创建),添加相关配置,代码如下: 其中:路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息: 第三步:创建项目启动类,例如: 第四步:启动项目进行访问测试, 依次启动sca-provider,sca-gateway服务,然后打开浏览器,进行访问测试,例如: 网关才是服务访问的入口,所有服务都会在网关层面进行底层映射,所以在访问服务时,要基于服务serivce id(服务名)去查找对应的服务,让请求从网关层进行均衡转发,以平衡服务实例的处理能力。 第一步:项目中添加服务发现依赖,代码如下: 第二步:修改其配置文件,代码如下 其中,lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略。同时建议开发阶段打开gateway日志,代码如下: 第三步:启动服务,进行访问测试,并反复刷新分析,如图所示: 根据官方的说明,其Gateway具体工作流程,如图所示: 客户端向Spring Cloud Gateway发出请求。 如果Gateway Handler Mapping 通过断言predicates(predicates)的集合确定请求与路由(Routers)匹配,则将其发送到Gateway Web Handler。 Gateway Web Handler 通过确定的路由中所配置的过滤器集合链式调用过滤器(也就是所谓的责任链模式)。 Filter由虚线分隔的原因是, Filter可以在发送代理请求之前和之后运行逻辑。处理的逻辑是 在处理请求时 排在前面的过滤器先执行,而处理返回相应的时候,排在后面的过滤器先执行。 Predicate(断言)又称谓词,用于条件判断,只有断言结果都为真,才会真正的执行路由。断言其本质就是定义路由转发的条件。 SpringCloud Gateway包括一些内置的断言工厂(所有工厂都直接或间接的实现了RoutePredicateFactory接口),这些断言或谓词工程负责创建谓词对象,并通过这些谓词对象判断http请求的合法性,常见谓词工厂如下: 基于Datetime类型的断言工厂 此类型的断言根据时间做判断,主要有三个: 1) AfterRoutePredicateFactory:判断请求日期是否晚于指定日期 -After=2020-12-31T23:59:59.789+08:00[Asia/Shanghai] 当且仅当请求时的时间After配置的时间时,才转发该请求,若请求时的时间不是After配置的时间时,则会返回404 not found。时间值可通过ZonedDateTime.now()获取。 基于header的断言工厂HeaderRoutePredicateFactory 判断请求Header是否具有给定名称且值与正则表达式匹配。例如: -Header=X-Request-Id, \d+ 基于Method请求方法的断言工厂, MethodRoutePredicateFactory接收一个参数,判断请求类型是否跟指定的类型匹配。例如: -Method=GET 基于Query请求参数的断言工厂,QueryRoutePredicateFactory : 接收两个参数,请求param和正则表达式, 判断请求参数是否具 有给定名称且值与正则表达式匹配。例如: -Query=pageSize,\d+ 内置的路由断言工厂应用案例,例如: 说明:当条件不满足时,则无法进行路由转发,会出现404异常。 过滤器(Filter)就是在请求传递过程中,对请求和响应做一个处理。Gateway 的Filter从作用范围可分为两种:GatewayFilter与GlobalFilter。其中: 在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下: 基于AddRequestHeaderGatewayFilterFactory,为原始请求添加Header。 例如,为原始请求添加名为 X-Request-Foo ,值为 Bar 的请求头: 基于AddRequestParameterGatewayFilterFactory,为原始请求添加请求参数及值, 例如,为原始请求添加名为foo,值为bar的参数,即:foo=bar。 基于PrefixPathGatewayFilterFactory,为原始的请求路径添加一个前缀路径 例如,该配置使访问${GATEWAY_URL}/hello 会转发到uri/mypath/hello。 基于RequestSizeGatewayFilterFactory,设置允许接收最大请求包的大小 ,配置示例: 如果请求包大小超过设置的值,则会返回 413 Payload Too Large以及一个errorMessage 全局过滤器(GlobalFilter)作用于所有路由, 无需配置。在系统初始化时加载,并作用在每个路由上。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。一般内置的全局过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们 自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。 例如,当客户端第一次请求服务时,服务端对用户进行信息认证(登录), 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证 以后每次请求,客户端都携带认证的token 服务端对token进行解密,判断是否有效。学过spring中的webflux技术的同学可以对如下代码进行尝试实现(没学过的可以忽略). 启动Gateway服务,假如在访问的url中不带“user=admin”这个参数,可能会出现异常. 网关是所有外部请求的公共入口,所以可以在网关进行限流,而且限流的方式也很多,我们采用Sentinel组件来实现网关的限流。Sentinel支持对SpringCloud Gateway、Zuul等主流网关进行限流。参考网址如下: 第一步:添加依赖 第二步:添加sentinel及路由规则(假如已有则无需设置) 第三步:启动网关项目,检测sentinel控制台的网关菜单。 假如是在idea中,可以参考下面的图进行配置 Sentinel 控制台启动以后,界面如图所示: 第四步:在sentinel面板中设置限流策略,如图所示: 第五步:通过url进行访问检测是否实现了限流操作 定义指定routeId的基于属性的限流策略如图所示: 通过postman进行测试分析 自定义API分组,是一种更细粒度的限流规则定义,它允许我们利用sentinel提供的API,将请求路径进行分组,然后在组上设置限流规则即可。 第一步:新建API分组,如图所示: 第二步:新建分组流控规则,如图所示: 第三步:进行访问测试,如图所示 定义配置类,设计流控返回值,代码如下: 其中,Mono 是一个发出(emit)0-1个元素的Publisher对象。 基于Spring Cloud Alibaba解决方案实现文件上传,例如 本次项目实践,整体上基于前后端分离架构,服务设计上基于spring cloud alibaba解决方案进行实现,例如: 创建项目父工程用来管理项目依赖. 创建用于处理文件上传业务的工程,例如: 创建一个客户端工程,在此工程中定义一些静态页面,例如文件上传页面. 打开父工程的pom.xml文件,添加如下依赖: 在sca-resource工程中添加如下依赖: 在项目的resources目录下创建bootstrap.yml配置文件(假如后续配置信息要写到配置中心配置文件名必须为bootstrap.yml),并添加如下内容: 在当前工程中,创建项目启动类,例如: 类创建以后,启动当前项目,检测是否可以启动成功,是否有配置错误. 定义处理上传请求的Controller对象,例如: 我们在通过客户端工程,访问文件上传服务时,需要进行跨域配置,在服务端的跨域配置中有多种方案,最常见是在过滤器的层面进行跨域设计,例如: 本次项目我们的客户端工程基于springboot工程进行设计,项目上线时可以将其静态资源直接放到一个静态资源目录中. 在sca-resource-ui工程的pom文件中添加web依赖,例如: 在工程的resources目录下创建static目录(假如这个目录已经存在则无需创建),然后在此目录创建fileupload.html静态页面,例如: 第一步:启动nacos服务(在这里做服务的注册和配置管理) API 网关是外部资源对服务内部资源访问的入口,所以文件上传请求应该首先请求的是网关服务,然后由网关服务转发到具体的资源服务上。 第一步:创建sca-resource-gateway工程,例如: 第三步:创建配置文件bootstrap.xml,然后进行初始配置,例如: 第四步:构建项目启动类,并进行服务启动,检测是否正确,例如: 当我们基于Ajax技术访问网关时,需要在网关层面进行跨域设计,例如: Spring Gateway工程中的跨域设计,除了可以在网关项目中以java代码方式进行跨域过滤器配置,还可以直接在配置文件进行跨域配置,例如: 首先打开网关(Gateway),资源服务器(Resource),客户端工程服务(UI),然后修改fileupload.html文件中访问资源服务端的url,例如 第一步:在网关pom文件中添加依赖 第二步:在网关配置文件中添加sentinel配置 第三步:在网关项目启动时,配置jvm启动参数,例如: 第四步:先执行一次上传,然后对上传进行限流规则设计 第六步:启动服务进行文件上传测试,检测限流效果 在实现文件上传业务时,添加记录日志的操作. 在sca-resource工程中添加AOP依赖,例如: 我们项目要为目标业务实现功能增强,锦上添花,但系统要指定谁是目标业务,这里我们定义一个注解,后续用此注解描述目标业务。 通过上面定义的注解RequiredLog,对sca-resources工程中的ResourceController文件上传方法进行描述,例如: 说明:通过@RequiredLog注解描述的方法可以认为锦上添花的“锦”,后续添花的行为可以放在切面的通知方法中。 在AOP编程设计中,我们会通过切面封装切入点(Pointcut)和扩展业务逻辑(Around,…)的定义,例如: 我们在基于AOP方式记录用户操作日志时,其底层工作流程如下: 本章节已经文件上传为例回顾和加强微服务基础知识的掌握和实践。 传统的登录系统中,每个站点都实现了自己的专用登录模块。各站点的登录状态相互不认可,各站点需要逐一手工登录。例如: 单点登录,英文是 Single Sign On(缩写为 SSO)。即多个站点共用一台认证授权服务器,用户在其中任何一个站点登录后,可以免登录访问其他所有站点。而且,各站点间可以通过该登录状态直接交互。例如: 基于资源服务工程添加单点登陆认证和授权服务,工程结构定义如下: 在sca-auth工程中创建bootstrap.yml文件,例如: 项目启动时,系统会默认生成一个登陆密码,例如: 我们的单点登录系统最终会按照如下结构进行设计和实现,例如: 我们在实现登录时,会在UI工程中,定义登录页面(login.html),然后在页面中输入自己的登陆账号,登陆密码,将请求提交给网关,然后网关将请求转发到auth工程,登陆成功和失败要返回json数据,在这个章节我们会按这个业务逐步进行实现 修改SecurityConfig配置类,添加登录成功或失败的处理逻辑,例如: 在spring security应用中底层会借助UserDetailService对象获取数据库信息,并进行封装,最后返回给认证管理器,完成认证操作,例如: 在网关配置文件中添加登录路由配置,例如 启动sca-gateway,sca-auth服务,然后基于postman访问网关,执行登录测试,例如: 在sca-resource-ui工程的static目录中定义登陆页面,例如: 启动sca-resource-ui服务后,进入登陆页面,输入用户名jack,密码123456进行登陆测试。 目前的登陆操作,也就是用户的认证操作,其实现主要基于Spring Security框架,其认证简易流程如下: 本次我们借助JWT(Json Web Token-是一种json格式)方式将用户相关信息进行组织和加密,并作为响应令牌(Token),从服务端响应到客户端,客户端接收到这个JWT令牌之后,将其保存在客户端(例如localStorage),然后携带令牌访问资源服务器,资源服务器获取并解析令牌的合法性,基于解析结果判定是否允许用户访问资源. 第一步:在SecurityConfig中添加如下方法(创建认证管理器对象,后面授权服务器会用到): 第二步:所有零件准备好了开始拼装最后的主体部分,这个主体部分就是授权服务器的核心配置 第一步:启动服务 第二步:检测sca-auth服务控制台的Endpoints信息,例如: 第三步:打开postman进行登陆访问测试 令牌校验测试,例如: 刷新令牌测试,例如: 登陆成功以后,将token存储到localStorage中,修改登录页面的doLogin方法,也可以直接定义login-sso.html页面,例如 页面写好以后,启动服务进行登录测试即可。 用户在访问受限资源时,一般要先检测用户是否已经认证(登录),假如没有认证要先认证,认证通过还要检测是否有权限,没有权限则给出提示,有权限则直接访问。例如。 打开资源服务的pom.xml文件,添加oauth2依赖,基于此依赖实现授权业务。 用户登陆成功以后可以携带token访问服务端资源服务器,资源服务器中需要有解析token的对象,例如: 定义配置类,在类中定义资源访问规则例如: 在controller的上传方法上添加 @PreAuthorize(“hasAuthority(‘sys:res:create’)”)注解,用于告诉底层框架方法此方法需要具备的权限,例如 第一步:启动服务(sca-auth,sca-resource-gateway,sca-resource) 设置请求头(header),要携带令牌并指定请求的内容类型,例如 企业中数据是最重要的资源,对于这些数据而言,有些可以直接匿名访问,有些只能登录以后才能访问,还有一些你登录成功以后,权限不够也不能访问.总之这些规则都是保护系统资源不被破坏的一种手段.几乎每个系统中都需要这样的措施对数据(资源)进行保护.我们通常会通过软件技术对这样业务进行具体的设计和实现.早期没有统一的标准,每个系统都有自己独立的设计实现,但是对于这个业务又是一个共性,后续市场上就基于共性做了具体的落地实现,例如Spring Security,Apache shiro,JWT,Oauth2等技术诞生了. Spring Security 是一个企业级安全框架,由spring官方推出,它对软件系统中的认证,授权,加密等功能进行封装,并在springboot技术推出以后,配置方面做了很大的简化.现在市场上分布式架构中的安全控制,正在逐步的转向Spring Security。Spring Security 在企业中实现认证和授权业务时,底层构建了大量的过滤器,如图所示: JWT(JSON WEB Token)是一个标准,采用数据自包含方式进行json格式数据设计,实现各方安全的信息传输,其官方网址为:https://jwt.io/。官方JWT规范定义,它构成有三部分,分别为Header(头部),Payload(负载),Signature(签名),其格式如下: Header部分 Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。 上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(简写HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。最后,将这个 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。 Payload部分 Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT规范中规定了7个官方字段,供选用(了解)。 注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。 这个 JSON 对象也要使用 Base64URL 算法转成字符串。 Signature部分 Signature 部分是对前两部分的签名,其目的是防止数据被篡改。 首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。 算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。 oauth2定义了一种认证授权协议,一种规范,此规范中定义了四种类型的角色: 第一:登录用户信息来自数据库(用户自身信息以及用户对应的权限信息) 第一:认证服务调用系统服务(获取用户以及用户权限) 本次实践,我们的认证服务(auth)中需要的用户信息以及用户的权限信息由系统服务(sca-system)提供,我们可以在auth工程中通过feign方式对系统服务(sca-system)工程进行调用,例如: 将jt-sso.sql文件在mysql中执行一下,其过程如下: 第二:通过source指令执行jt-sso.sql文件 其中,数据库中表与表之间的关系如下: 创建sca-system工程,此工程作为02-sca-files的子工程进行业务实现,例如: 在项目中添加bootstrap.yml文件,其内容如下: 第一步:在项目中添加启动类,例如: 第二步:在项目中添加单元测试类,测试数据库连接,例如: 添加项目User对象,用于封装用户信息。 第一步:创建UserMapper接口,并定义基于用户名查询用户信息,基于用户id查询用户权限信息的方法,代码如下: 第二步:创建UserMapperTests类,对业务方法做单元测试,例如: 创建UserService接口及实现泪,定义用户及用户权限查询逻辑,代码如下: 第一步:定义service接口,代码如下: 第二步:定义service接口实现类,代码如下: 启动sca-system工程服务,打开浏览器分别对用户及用户权限信息的获取进行访问测试 在认证sca-auth工程中,我们通过调用sca-system服务获取登录用户信息,用户权限信息. 在sca-auth工程中添加如下依赖,例如: 创建Feign接口,基于feign实现远程调用逻辑,例如: 说明,feign接口定义后,需要在sca-auth启动类上添加@EnableFeignClients注解. 在sca-auth工程中的UserDetailServiceImpl中添加对feign接口的调用,例如: 启动sca-auth,sca-resource,sca-resource-gateway,sca-system,sca-resource-ui工程,然后从登录开始进行测试. 本章节利用同学们学过的知识点,在单点登录系统中添加了数据访问,Feign方式的服务调用逻辑,可以基于这里的逻辑实现,自己拓展日志等逻辑的实现。 当我们在执行一些SQL脚本时(例如 nacos-mysql.sql文件),假如出现如下错误,请升级你的mysql(建议mysql5.7以上或MariaDB 10.5.11) 问题1:没有配置JAVA_HOME环境变量,或者JDK有问题 问题2:nacos链接mysql时,链接配置问题 第一步:添加或编辑配置,例如: 假如出现依赖下载不下来,或者pom.xml文件出错,一定要检查: 克隆下的项目导入以后是如下问题: 打开项目代码,是如下问题,例如 我们使用的maven一般默认编译版本会采用JDK1.5,假如你希望为JDK8,可以在当前工程或父工程中添加如下,代码: 我们也可以在每个工程的properties元素中设置指定当前工程的编译版本,例如: 启动服务时,出现如下问题,例如: 项目配置文件中的spring.application.name属性值为服务id,服务注册到nacos以后的服务名就是这个id,后续可以基于这个服务id找到具体的服务实例,然后进行调用. Spring MVC 是spring框架中基于MVC设计思想实现的一个WEB模块,这个模块下的请求响应处理流程如下: Sentinel对请求进行限流的原理分析,如图所示: 第一步:打开服务编辑配置,例如: 第一:安装postman或者在idea中配置rest client应用 输出JVM类加载信息(假如想看类启动时,相关类的加载顺序,可以配置JVM参数: -XX:+TraceClassLoading) 调整JVM堆内存大小,并输出JVM 垃圾回收基本信息(假如设置JVM堆内存大小可以通过 -Xmx设置最大堆内存,-Xms设置最小堆内存,-XX:+PrintGC输出程序运行时的GC信息) 方案1:将登录状态存储到redis等数据库,例如: 方案2:将登录状态存储到jwt令牌中,例如: 当使用@Autowired注解描述属性时,假如属性下有红色波浪线提示,可参考如下配置 第二步:在sca-provider工程中添加spring-boot-start依赖,例如: 第三步:在sca-provider工程中的test目录下定义单元测试类,并进行测试,例如: 检查自己电脑的JAVA_HOME环境变量? 检查自己电脑环境中的JVM虚拟机是否为64位的,例如: 登录mysq,检查mysql或maridb的版本,例如: JAVA_HOME环境变量定义错误,例如: MySQL版本比较低,例如: SQL文件应用错误,例如: Nacos的application.properties配置文件中,连接数据库的配置错误. 服务注册时,服务名不正确,例如: Nacos 服务注册失败,例如 客户端500异常,例如 服务调用时,连接异常,例如: 服务调用时底层404问题,例如: 第二步:构建Browser对象,例如: 第三步:运行tomcat和Browser,检测Browser输出.@Value("${logging.level.com.jt:error}")
private String logLevel;
@GetMapping("/provider/log/doLog02")
public String doLog02(){
log.info("log level is {}",logLevel);
return "log level is "+logLevel;
}
小节面试分析
Nacos配置管理模型
概述
其中:
命名空间设计
克隆成功以后,我们会发现在指定的命名空间中有了我们克隆的配置,如图所示:
修改项目module中的配置文件bootstrap.yml,添加如下配置,关键代码如下:spring:
cloud:
nacos:
config:
namespace: 6058fd3f-0d4d-44f2-85d6-5fc7d2348046
……
重启服务,继续刷新http://localhost:8081/config/doGetLogLevel地址。检测输出,看看输出的内容是什么,是否为dev命名空间下配置的内容。分组设计及实现
其中,这里的useLocalCache为自己定义的配置值,表示是否使用本地缓存.server:
port: 8081
spring:
application:
name: sca-provider
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
group: DEV_GROUP_51 # Group, default is DEFAULT_GROUP
file-extension: yml # Configure the data format of the content, default to properties
namespace: 7da4aa75-f64c-43c6-b101-9d77ad96f1c0
package com.jt.provider.controller;
@RefreshScope
@RestController
public class ProviderCacheController {
@Value("${useLocalCache:false}")
private boolean useLocalCache;
@RequestMapping("/provider/cache")
public String doUseLocalCache(){
return "useLocalCache'value is "+useLocalCache;
}
}
共享配置设计及读取
见红色区域内容。spring:
application:
name: sca-provider
cloud:
nacos:
config:
server-addr: localhost:8848
# 命名空间
namespace: 83ed55a5-1dd9-4b84-a5fe-a734e4a6ec6d
# 分组名
# group: DEFAULT_GROUP
# 配置中心文件扩展名
file-extension: yml
# 共享配置
shared-configs[0]:
data-id: app-public-dev.yml
refresh: true #默认false,共享配置更新,引用此配置的地方是否要更新
package com.jt.provider.controller;
@RefreshScope
@RestController
public class ProviderPageController {
@Value("${page.pageSize:10}")
private Integer pageSize;
@GetMapping("/provider/page/doGetPageSize")
public String doGetPageSize(){
//return String.format()
return "page size is "+pageSize;
}
}
小节面试分析
总结(Summary)
重难点分析
FAQ分析
Bug分析
06-Sentinel限流熔断应用实践
Sentinel简介
背景分析
我们如何在这些业务流量变化无常的情况下,保证各种业务安全运营,系统在任何情况下都不会崩溃呢?我们可以在系统负载过高时,采用限流、降级和熔断,三种措施来保护系统,由此一些流量控制中间件诞生。例如Sentinel。Sentinel概述
Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
安装Sentinel服务
第一步:打开sentinel下载网址https://github.com/alibaba/Sentinel/releases
java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
访问Sentinal服务
Sentinel限流入门
概述
Sentinel集成
spring:
cloud:
sentinel:
transport:
port: 8099 #跟sentinel控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:8180 # 指定sentinel控制台地址。
Sentinel的控制台其实就是一个SpringBoot编写的程序,我们需要将我们的服务注册到控制台上,即在微服务中指定控制台的地址,并且还要在消费端开启一个与sentinel控制台传递数据端的端口,控制台可以通过此端口调用微服务中的监控程序来获取各种信息。Sentinel限流快速入门
Sentinel流控规则分析
阈值类型分析
设置限流模式
@Service
public class ConsumerService{
@SentinelResource("doGetResource")
public String doGetResource(){
return "doGetResource";
}
}
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho01() throws InterruptedException {
consumerService.doGetResource();
//Thread.sleep(200);
String url="http://localhost:8081/provider/echo/"+server;
//远程过程调用-RPC
return restTemplate.getForObject(url,String.class);//String.class调用服务响应数据类型
}
sentinel:
web-context-unify: false
设计限流效果(了解)
小节面试分析
我们在访问web应用时,在web应用内部会有一个拦截器,这个拦截器会对请求的url进行拦截,拦截到请求以后,读取sentinel 控制台的流控规则,基于流控规则对流量进行限流操作。Sentinel降级入门
概述
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。准备工作
//AtomicLong 类支持线程安全的自增自减操作
private AtomicLong atomicLong=new AtomicLong(1);
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho01() throws InterruptedException {
//consumerService.doGetResource();
//获取自增对象的值,然后再加1
long num=atomicLong.getAndIncrement();
if(num%2==0){//模拟50%的慢调用比例
Thread.sleep(200);
}
String url="http://localhost:8081/provider/echo/"+server;
//远程过程调用-RPC
return restTemplate.getForObject(url,String.class);//String.class调用服务响应数据类型
}
Sentinel降级入门
Sentinel 异常处理
@Component
public class ServiceBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,BlockException e) throws Exception {
//response.setStatus(601);
//设置响应数据的编码
response.setCharacterEncoding("utf-8");
//告诉客户端要响应的数据类型以及客户端以什么编码呈现数据
response.setContentType("application/json;charset=utf-8");
PrintWriter pw=response.getWriter();
Map
小节面试分析
Sentinel降级策略分析(拓展)
慢调用比例
慢调用逻辑中的状态分析如下:
注意:Sentinel默认统计的RT上限是4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置异常比例
异常比例中的状态分析如下:
异常数量
小节面试分析
Sentinel热点规则分析(重点)
概述
快速入门
//http://ip:port/consumer/doFindById?id=10
@GetMapping("/consumer/findById")
@SentinelResource("res")
public String doFindById(@RequestParam("id") Integer id){
return "resource id is "+id;
}
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: 2
一般应用在某些特殊资源的特殊处理,如:某些商品流量大,其他商品流量很正常,就可以利用热点参数限流的方案。特定参数设计
小节面试分析
Sentinel系统规则(了解)
概述
快速入门
小节面试分析
Sentinel授权规则(重要)
概述
快速入门
黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:
@Component
public class DefaultRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
String origin = request.getParameter("origin");
return origin;
}
}
http://ip:port/path?origin=app1的请求不可以通过,会出现如下结果:@Component
public class DefaultRequestOriginParser implements RequestOriginParser {
//解析请求源数据
@Override
public String parseOrigin(HttpServletRequest request) {
//获取请求参数数据,参数名可以自己写,例如origin,然后基于参数值做黑白名单设计
// http://ip:port/path?origin=app1
return request.getParameter("origin");
//获取访问请求中的ip地址,基于ip地址进行黑白名单设计(例如在流控应用栏写ip地址)
String ip= request.getRemoteAddr();
System.out.println("ip="+ip);
return ip;
//获取请求头中的数据,基于请求头中token值进行限流设计 (例如在监控应用栏写jack,tony)
//String token=request.getHeader("token");//jack,tony
//return token;
}//授权规则中的黑白名单的值,来自此方法的返回值
}
小节面试分析
总结(Summary)
重难点分析
FAQ分析
Bug分析
07-网关Gateway 应用实践
网关简介
背景分析
网关概述
快速入门
业务描述
入门业务实现
server:
port: 9000
spring:
application:
name: sca-gateway
cloud:
gateway:
routes: #配置网关路由规则
- id: route01 #路由id,自己指定一个唯一值即可
uri: http://localhost:8081/ #网关帮我们转发的url
predicates: ###断言(谓此):匹配请求规则
- Path=/nacos/provider/echo/** #请求路径定义,此路径对应uri中的资源
filters: ##网关过滤器,用于对谓词中的内容进行判断分析以及处理
- StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos
package com.cy;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
小节面试分析?
负载均衡设计
为什么负载均衡?
Gateway中负载均衡实现?
server:
port: 9000
spring:
application:
name: sca-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #开启通过服务注册中心的serviceId创建路由
routes:
- id: route01
##uri: http://localhost:8081/
uri: lb://sca-provider # lb为服务前缀(负载均衡单词的缩写),不能随意写
predicates: ###匹配规则
- Path=/nacos/provider/echo/**
filters:
- StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos
logging:
level:
org.springframework.cloud.gateway: debug
执行流程分析(重要)
小节面试分析?
断言(Predicate)增强分析(了解)
Predicate 简介
Predicate 内置工厂
2) BeforeRoutePredicateFactory:判断请求日期是否早于指定日期
3) BetweenRoutePredicateFactory:判断请求日期是否在指定时间段内
Predicate 应用案例实践
server:
port: 9000
spring:
application:
name: sca-gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #开启通过服务中心的serviceId 创建路由的功能
routes:
- id: bd-id
##uri: http://localhost:8081/
uri: lb://sca-provider
predicates: ###匹配规则
- Path=/nacos/provider/echo/**
- Before=2021-01-30T00:00:00.000+08:00
- Method=GET
filters:
- StripPrefix=1 # 转发之前去掉1层路径
小节面试分析
过滤器(Filter)增强分析(了解)
概述
局部过滤器设计及实现
案例分析:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-Foo, Bar
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=foo, bar
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://example.org
filters:
- PrefixPath=/mypath
spring:
cloud:
gateway:
routes:
- id: request_size_route
uri: http://localhost:8080/upload
predicates:
- Path=/upload
filters:
- name: RequestSize
args:
# 单位为字节
maxSize: 5000000
全局过滤器设计及实现
package com.cy.filters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono
小节面试分析
限流设计及实现
限流简述
https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
限流快速入门
在原有spring-cloud-starter-gateway依赖的基础上再添加如下两个依赖,例如:
routes:
- id: route01
uri: lb://nacos-provider
predicates: ###匹配规则
- Path=/provider/echo/**
sentinel:
transport:
dashboard: localhost:8180 #Sentinel 控制台地址
port: 8719 #客户端监控API的端口
eager: true #取消Sentinel控制台懒加载,即项目启动即连接
启动时,添加sentinel的jvm参数,通过此菜单可以让网关服务在sentinel控制台显示不一样的菜单,代码如下。-Dcsp.sentinel.app.type=1
说明,假如没有发现请求链路,API管理,关闭网关项目,关闭sentinel,然后重启sentinel,重启网关项目.基于请求属性限流
自定义API维度限流(重点)
定制流控网关返回值
@Configuration
public class GatewayConfig {
public GatewayConfig(){
GatewayCallbackManager.setBlockHandler( new BlockRequestHandler() {
@Override
public Mono
小节面试分析?
总结(Summay)
重难点分析
FAQ 分析
BUG分析
08-微服务文件上传实战(总结与练习)
项目简介
业务描述
初始架构设计
说明,为了降低学习难度,这里只做了初始架构设计,后续会逐步基于这个架构进行演进,例如我们会加上网关工程,认证工程等.工程创建及初始化
工程结构
创建父工程
创建文件服务工程
创建客户端服务工程
父工程初始化
文件资源服务实现
添加项目依赖
服务初始化配置
server:
port: 8881
spring:
application:
name: sca-resource
servlet:
multipart:
max-file-size: 100MB #控制上传文件的大小
max-request-size: 110MB #请求数据大小
resources: #定义可以访问到上传资源的路径
static-locations: file:d:/uploads #静态资源路径(原先存储到resources/static目录下的资源可以存储到此目录中)
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
jt: #这里的配置,后续会在一些相关类中通过@Value注解进行读取
resource:
path: d:/uploads #设计上传文件存储的根目录(后续要写到配置文件)
host: http://localhost:8881/ #定义上传文件对应的访问服务器
构建项目启动类
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FileApplication {
public static void main(String[] args) {
SpringApplication.run(FileApplication.class, args);
}
}
Controller逻辑实现
package com.jt.resource.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
@Slf4j
@RestController
@RequestMapping("/resource/")
public class ResourceController {
//当了类的上面添加了@Slf4J就不用自己创建下面的日志对象了
// private static final Logger log=
// LoggerFactory.getLogger(ResourceController.class);
@Value("${jt.resource.path}")
private String resourcePath;//="d:/uploads/";
@Value("${jt.resource.host}")
private String resourceHost;//="http://localhost:8881/";
@PostMapping("/upload/")
public String uploadFile(MultipartFile uploadFile) throws IOException {
//1.创建文件存储目录(按时间创建-yyyy/MM/dd)
//1.1获取当前时间的一个目录
String dateDir = DateTimeFormatter.ofPattern("yyyy/MM/dd")
.format(LocalDate.now());
//1.2构建目录文件对象
File uploadFileDir=new File(resourcePath,dateDir);
if(!uploadFileDir.exists())uploadFileDir.mkdirs();
//2.给文件起个名字(尽量不重复)
//2.1获取原文件后缀
String originalFilename=uploadFile.getOriginalFilename();
String ext = originalFilename.substring(
originalFilename.lastIndexOf("."));
//2.2构建新的文件名
String newFilePrefix=UUID.randomUUID().toString();
String newFileName=newFilePrefix+ext;
//3.开始实现文件上传
//3.1构建新的文件对象,指向实际上传的文件最终地址
File file=new File(uploadFileDir,newFileName);
//3.2上传文件(向指定服务位置写文件数据)
uploadFile.transferTo(file);
String fileRealPath=resourceHost+dateDir+"/"+newFileName;
log.debug("fileRealPath {}",fileRealPath);
//后续可以将上传的文件信息写入到数据库?
return fileRealPath;
}
}
跨域配置实现
package com.jt.files.config;
/**
* 跨域配置(基于过滤器方式进行配置,并且将过滤优先级设置高一些)
*/
@Configuration
public class CorsFilterConfig {
@Bean
public FilterRegistrationBean
客户端工程逻辑实现
添加依赖
构建项目启动类
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication .class, args);
}
}
创建文件上传页面
启动服务访问测试
第二步:启动sca-resource服务,此服务提供文件上传功能
第三步:启动sca-resource-ui服务,此服务为客户端工程,提供静态资源的访问.所有页面放到此工程中.
第四步:打开浏览器,访问sca-resource-ui工程下的文件上传页面,例如:API网关(Gateway)工程实践
概述
服务调用架构
工程项目结构设计
创建网关工程及初始化
server:
port: 9000
spring:
application:
name: sca-resource-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
gateway:
discovery:
locator:
enabled: true
routes:
- id: router01
uri: lb://sca-resource
predicates:
- Path=/sca/resource/upload/**
filters:
- StripPrefix=1
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResourceApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class,args);
}
}
网关跨域配置
package com.jt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
//@Configuration
public class CorsFilterConfig {
@Bean
public CorsWebFilter corsWebFilter(){
//1.构建基于url方式的跨域配置
UrlBasedCorsConfigurationSource source= new UrlBasedCorsConfigurationSource();
//2.进行跨域配置
CorsConfiguration config=new CorsConfiguration();
//2.1允许所有ip:port进行跨域
config.addAllowedOrigin("*");
//2.2允许所有请求头跨域
config.addAllowedHeader("*");
//2.3允许所有请求方式跨域:get,post,..
config.addAllowedMethod("*");
//2.4允许携带有效cookie进行跨域
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**",config);
return new CorsWebFilter(source);
}
}
spring:
cloud:
gateway:
globalcors: #跨域配置
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowedMethods: "*"
allowCredentials: true
启动工程进行服务访问
let url="http://localhost:9000/sca/resource/upload/";
网关上对文件上传限流
sentinel:
transport:
dashboard: localhost:8180
eager: true
-Dcsp.sentinel.app.type=1
function upload(file){
//定义一个表单(axios中提供的表单对象)
let form=new FormData();
//将文件添加到表单中
form.append("uploadFile",file);
//异步提交(现在是提交到网关)
//let url="http://localhost:8881/resource/upload/"
let url="http://localhost:9000/sca/resource/upload/";
axios.post(url,form)
.then(function (response){
alert("upload ok")
console.log(response.data);
})
.catch(function (e){//失败时执行catch代码块
//被限流后服务端返回的状态码为429
if(e.response.status==429){
alert("上传太频繁了");
}
console.log("error",e);
})
}
AOP方式操作日志记录
页面描述
添加项目依赖
创建切入点注解
package com.jt.resource.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
String value() default "";
}
定义切入点方法
@RequiredLog("upload file")
@PostMapping("/upload/")
public String uploadFile(MultipartFile uploadFile) throws IOException {...}
定义日志操作切面
package com.jt.resource.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class LogAspect {
//定义切入点
@Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
public void doLog(){}//锦上添花的锦(注解描述的方法)
//定义扩展业务逻辑
@Around("doLog()")
//@Around("@annotation(com.jt.resource.annotation.RequiredLog)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.debug("Before {}",System.currentTimeMillis());
Object result=joinPoint.proceed();//执行执行链(其它切面,目标方法-锦)
log.debug("After {}",System.currentTimeMillis());
return result;//目标方法(切入点方法)的执行结果
}
}
AOP 方式日志记录原理分析
说明:当我们在项目中定义了AOP切面以后,系统启动时,会对有@Aspect注解描述的类进行加载分析,基于切入点的描述为目标类型对象,创建代理对象,并在代理对象内部创建一个执行链,这个执行链中包含拦截器(封装了切入点信息),通知(Around,…),目标对象等,我们请求目标对象资源时,会直接按执行链的顺序对资源进行调用。总结(Summary)
09-微服务版的单点登陆系统设计及实现
简介
背景分析
这样的系统,我们又称之为多点登陆系统。应用起来相对繁琐(每次访问资源服务都需要重新登陆认证和授权)。与此同时,系统代码的重复也比较高。由此单点登陆系统诞生。单点登陆系统
快速入门实践
工程结构如下
创建认证授权工程
添加项目依赖
构建项目配置文件
server:
port: 8071
spring:
application:
name: sca-auth
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
添加项目启动类
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResourceAuthApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceAuthApplication.class, args);
}
}
启动并访问项目
打开浏览器输入http://localhost:8071呈现登陆页面,例如:
其中,默认用户名为user,密码为系统启动时,在控制台呈现的密码。执行登陆测试,登陆成功进入如下界面(因为没有定义登陆页面,所以会出现404):自定义登陆逻辑
业务描述
定义安全配置类
package com.jt.auth.config;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* spring security 配置类,此类中要配置:
* 1)加密对象
* 2)配置认证规则
* 当我们在执行登录操作时,底层逻辑(了解):
* 1)Filter(过滤器)
* 2)AuthenticationManager (认证管理器)
* 3)AuthenticationProvider(认证服务处理器)
* 4)UserDetailsService(负责用户信息的获取及封装)
*/
@Configuration
public class SecurityConfig
extends WebSecurityConfigurerAdapter {
//初始化加密对象
//此对象提供了一种不可逆的加密方式,相对于md5方式会更加安全
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**配置认证规则*/
@Override
protected void configure(HttpSecurity http)
throws Exception {
//super.configure(http);//默认所有请求都要认证
//1.禁用跨域攻击(先这么写,不写会报403异常)
http.csrf().disable();
//2.放行所有资源的访问(后续可以基于选择对资源进行认证和放行)
http.authorizeRequests()
.anyRequest().permitAll();
//3.自定义定义登录成功和失败以后的处理逻辑(可选)
//假如没有如下设置登录成功会显示404
http.formLogin()//这句话会对外暴露一个登录路径/login
.successHandler(successHandler())
.failureHandler(failureHandler());
}
//定义认证成功处理器
//登录成功以后返回json数据
@Bean
public AuthenticationSuccessHandler successHandler(){
// return new AuthenticationSuccessHandler() {
// @Override
// public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//
// }
// };
//lambda
return (request,response,authentication)->{
//构建map对象封装到要响应到客户端的数据
Map
定义用户信息处理对象
package com.jt.auth.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 登录时用户信息的获取和封装会在此对象进行实现,
* 在页面上点击登录按钮时,会调用这个对象的loadUserByUsername方法,
* 页面上输入的用户名会传给这个方法的参数
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
//UserDetails用户封装用户信息(认证和权限信息)
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
//1.基于用户名查询用户信息(用户名,用户状态,密码,....)
//Userinfo userinfo=userMapper.selectUserByUsername(username);
String encodedPassword=passwordEncoder.encode("123456");
//2.查询用户权限信息(后面会访问数据库)
//这里先给几个假数据
List
网关中登陆路由配置
- id: router02
uri: lb://sca-auth #lb表示负载均衡,底层默认使用ribbon实现
predicates: #定义请求规则(请求需要按照此规则设计)
- Path=/auth/login/** #请求路径设计
filters:
- StripPrefix=1 #转发之前去掉path中第一层路径
基于Postman进行访问测试
自定义登陆页面
Please Login
Security 认证流程分析(了解)
颁发登陆成功令牌
构建令牌配置对象
package com.jt.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* 在此配置类中配置令牌的生成,存储策略,验签方式(令牌合法性)。
*/
@Configuration
public class TokenConfig {
/**
* 配置令牌的存储策略,对于oauth2规范中提供了这样的几种策略
* 1)JdbcTokenStore(这里是要将token存储到关系型数据库)
* 2)RedisTokenStore(这是要将token存储到redis数据库-key/value)
* 3)JwtTokenStore(这里是将产生的token信息存储客户端,并且token
* 中可以以自包含的形式存储一些用户信息)
* 4)....
*/
@Bean
public TokenStore tokenStore(){
//这里采用JWT方式生成和存储令牌信息
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 配置令牌的创建及验签方式
* 基于此对象创建的令牌信息会封装到OAuth2AccessToken类型的对象中
* 然后再存储到TokenStore对象,外界需要时,会从tokenStore进行获取。
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter=
new JwtAccessTokenConverter();
//JWT令牌构成:header(签名算法,令牌类型),payload(数据部分),Signing(签名)
//这里的签名可以简单理解为加密,加密时会使用header中算法以及我们自己提供的密钥,
//这里加密的目的是为了防止令牌被篡改。(这里密钥要保管好,要存储在服务端)
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//设置密钥
return jwtAccessTokenConverter;
}
/**
* JWT 令牌签名时使用的密钥(可以理解为盐值加密中的盐)
* 1)生成的令牌需要这个密钥进行签名
* 2)获取的令牌需要使用这个密钥进行验签(校验令牌合法性,是否被篡改过)
*/
private static final String SIGNING_KEY="auth";
}
定义认证授权核心配置
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
package com.jt.auth.config;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.Arrays;
/**
* 在这个对象中负责将所有的认证和授权相关配置进行整合,例如
* 业务方面:
* 1)如何认证(认证逻辑的设计)
* 2)认证通过以后如何颁发令牌(令牌的规范)
* 3)为谁颁发令牌(客户端标识,client_id,...)
* 技术方面:
* 1)SpringSecurity (提供认证和授权的实现)
* 2)TokenConfig(提供了令牌的生成,存储,校验方式)
* 3)Oauth2(定义了一套认证规范,例如为谁发令牌,都发什么内容,...)
*/
@AllArgsConstructor //生成一个全参构造函数
@Configuration
@EnableAuthorizationServer//启动认证和授权
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
//@Autowired
private AuthenticationManager authenticationManager;
//@Autowired
private UserDetailsService userDetailsService;
//@Autowired
private TokenStore tokenStore;
//@Autowired
private PasswordEncoder passwordEncoder;
//@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/**
* oauth2中的认证细节配置
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
//super.configure(endpoints);
endpoints
//由谁完成认证?(认证管理器)
.authenticationManager(authenticationManager)
//谁负责访问数据库?(认证时需要两部分信息:一部分来自客户端,一部分来自数据库)
.userDetailsService(userDetailsService)
//支持对什么请求进行认证(默认支持post方式)
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
//认证成功以后令牌如何生成和存储?(默认令牌生成UUID.randomUUID(),存储方式为内存)
.tokenServices(tokenService());
}
//系统底层在完成认证以后会调用TokenService对象的相关方法
//获取TokenStore,基于tokenStore获取token对象
@Bean
public AuthorizationServerTokenServices tokenService(){
//1.构建TokenService对象(此对象提供了创建,获取,刷新token的方法)
DefaultTokenServices tokenServices=
new DefaultTokenServices();
//2.设置令牌生成和存储策略
tokenServices.setTokenStore(tokenStore);
//3.设置是否支持令牌刷新(访问令牌过期了,是否支持通过令牌刷新机制,延长令牌有效期)
tokenServices.setSupportRefreshToken(true);
//4.设置令牌增强(默认令牌会比较简单,没有业务数据,
//就是简单随机字符串,但现在希望使用jwt方式)
TokenEnhancerChain tokenEnhancer=new TokenEnhancerChain();
tokenEnhancer.setTokenEnhancers(Arrays.asList(
jwtAccessTokenConverter));
tokenServices.setTokenEnhancer(tokenEnhancer);
//5.设置访问令牌有效期
tokenServices.setAccessTokenValiditySeconds(3600);//1小时
//6.设置刷新令牌有效期
tokenServices.setRefreshTokenValiditySeconds(3600*72);//3天
return tokenServices;
}
/**
* 假如我们要做认证,我们输入了用户名和密码,然后点提交
* ,提交到哪里(url-去哪认证),这个路径是否需要认证?还有令牌过期了,
* 我们要重新生成一个令牌,哪个路径可以帮我们重新生成?
* 如下这个方法就可以提供这个配置
* @param security
* @throws Exception
*/
@Override
public void configure(
AuthorizationServerSecurityConfigurer security)
throws Exception {
//super.configure(security);
security
//1.定义(公开)要认证的url(permitAll()是官方定义好的)
//公开oauth/token_key端点
.tokenKeyAccess("permitAll()") //return this
//2.定义(公开)令牌检查的url
//公开oauth/check_token端点
.checkTokenAccess("permitAll()")
//3.允许客户端直接通过表单方式提交认证
.allowFormAuthenticationForClients();
}
/**
* 认证中心是否要给所有的客户端发令牌呢?假如不是,那要给哪些客户端
* 发令牌,是否在服务端有一些规则的定义呢?
* 例如:老赖不能做飞机,不能做高铁
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//super.configure(clients);
clients.inMemory()
//定义客户端的id(客户端提交用户信息进行认证时需要这个id)
.withClient("gateway-client")
//定义客户端密钥(客户端提交用户信息时需要携带这个密钥)
.secret(passwordEncoder.encode("123456"))
//定义作用范围(所有符合规则的客户端)
.scopes("all")
//允许客户端基于密码方式,刷新令牌方式实现认证
.authorizedGrantTypes("password","refresh_token");
}
}
配置网关认证的URL
- id: router02
uri: lb://sca-auth
predicates:
#- Path=/auth/login/** #没要令牌之前,以前是这样配置
- Path=/auth/oauth/** #微服务架构下,需要令牌,现在要这样配置
filters:
- StripPrefix=1
Postman访问测试
依次启动sca-auth服务,sca-resource-gateway服务。登陆成功会在控制台显示令牌信息,例如:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Mjk5OTg0NjAsInVzZXJfbmFtZSI6ImphY2siLCJhdXRob3JpdGllcyI6WyJzeXM6cmVzOmNyZWF0ZSIsInN5czpyZXM6cmV0cmlldmUiXSwianRpIjoiYWQ3ZDk1ODYtMjUwYS00M2M4LWI0ODYtNjIyYjJmY2UzMDNiIiwiY2xpZW50X2lkIjoiZ2F0ZXdheS1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.-Zcmxwh0pz3GTKdktpr4FknFB1v23w-E501y7TZmLg4",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqYWNrIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImFkN2Q5NTg2LTI1MGEtNDNjOC1iNDg2LTYyMmIyZmNlMzAzYiIsImV4cCI6MTYzMDI1NDA2MCwiYXV0aG9yaXRpZXMiOlsic3lzOnJlczpjcmVhdGUiLCJzeXM6cmVzOnJldHJpZXZlIl0sImp0aSI6IjIyOTdjMTg2LWM4MDktNDZiZi1iNmMxLWFiYWExY2ExZjQ1ZiIsImNsaWVudF9pZCI6ImdhdGV3YXktY2xpZW50In0.1Bf5IazROtFFJu31Qv3rWAVEtFC1NHWU1z_DsgcnSX0",
"expires_in": 3599,
"scope": "all",
"jti": "ad7d9586-250a-43c8-b486-622b2fce303b"
}
登陆页面登陆方法设计
doLogin() {
//1.定义url
let url = "http://localhost:9000/auth/oauth/token"
//2.定义参数
let params = new URLSearchParams()
params.append('username',this.username);
params.append('password',this.password);
params.append("client_id","gateway-client");
params.append("client_secret","123456");
params.append("grant_type","password");
//3.发送异步请求
axios.post(url, params).then((response) => {
alert("login ok");
let result=response.data;
localStorage.setItem("accessToken",result.access_token);
location.href="/fileupload.html";
}).catch((error)=>{
console.log(error);
})
}
资源服务器配置
业务描述
这里,我们做文件的上传也会采用这样的逻辑进行实现。添加项目依赖
令牌处理器配置
package com.jt.resource.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* 在此配置类中配置令牌的生成,存储策略,验签方式(令牌合法性)。
*/
@Configuration
public class TokenConfig {
/**
* 配置令牌处理对象
*/
@Bean
public TokenStore tokenStore(){
//这里采用JWT方式生成和存储令牌信息
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 配置令牌的创建及验签方式
* 基于此对象创建的令牌信息会封装到OAuth2AccessToken类型的对象中
* 然后再存储到TokenStore对象,外界需要时,会从tokenStore进行获取。
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter=
new JwtAccessTokenConverter();
//JWT令牌构成:header(签名算法,令牌类型),payload(数据部分),Signing(签名)
//这里的签名可以简单理解为加密,加密时会使用header中算法以及我们自己提供的密钥,
//这里加密的目的是为了防止令牌被篡改。(这里密钥要保管好,要存储在服务端)
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//设置密钥
return jwtAccessTokenConverter;
}
/**
* JWT 令牌签名时使用的密钥(可以理解为盐值加密中的盐)
* 1)生成的令牌需要这个密钥进行签名
* 2)获取的令牌需要使用这个密钥进行验签(校验令牌合法性,是否被篡改过)
*/
private static final String SIGNING_KEY="auth";
}
启动和配置认证和授权规则
package com.jt.resource.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* 思考?对于一个系统而言,它资源的访问权限你是如何进行分类设计的
* 1)不需要登录就可以访问(例如12306查票)
* 2)登录以后才能访问(例如12306的购票)
* 3)登录以后没有权限也不能访问(例如会员等级不够不让执行一些相关操作)
*/
@Configuration
@EnableResourceServer
//启动方法上的权限控制,需要授权才可访问的方法上添加@PreAuthorize等相关注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
//super.configure(http);
//1.关闭跨域攻击
http.csrf().disable();
//2.放行相关请求
http.authorizeRequests()
.antMatchers("/resource/upload/**")
.authenticated()
.anyRequest().permitAll();
}
}
ResourceController 方法配置
@PreAuthorize("hasAuthority('sys:res:create')")
@PostMapping("/upload/")
public String uploadFile(MultipartFile uploadFile) throws IOException {
...
}
启动服务访问测试
第二步:执行登陆获取access_token令牌
第三步:携带令牌访问资源(url中的前缀"sca"是在资源服务器中自己指定的,你的网关怎么配置的,你就怎么写)
设置请求体(body),设置form-data,key要求为file类型,参数名与你服务端controller文件上传方法的参数名相同,值为你选择的文件,例如
上传成功会显示你访问文件需要的路径,假如没有权限会提示你没有访问权限。文件上传JS方法设计
function upload(file){
//定义一个表单(axios中提供的表单对象)
let form=new FormData();
//将文件添加到表单中
form.append("uploadFile",file);
//异步提交(现在是提交到网关)
//let url="http://localhost:8881/resource/upload/"
let url="http://localhost:9000/sca/resource/upload/";
//获取登录后,存储到浏览器客户端的访问令牌
let token=localStorage.getItem("accessToken");
//发送请求时,携带访问令牌
axios.post(url,form,{headers:{"Authorization":"Bearer "+token}})
.then(function (response){
alert("upload ok")
console.log(response.data);
})
.catch(function (e){//失败时执行catch代码块
//debugger
if(e.response.status==401){
alert("请先登录");
location.href="/login-sso.html";
}else if(e.response.status==403){
alert("您没有权限")
}else if(e.response.status==429){
alert("上传太频繁了")
}
console.log("error",e);
})
}
技术摘要应用实践说明
背景分析
Spring Security 技术
其中:
图中绿色部分为认证过滤器,黄色部分为授权过滤器。Spring Security就是通过这些过滤器然后调用相关对象一起完成认证和授权操作.Jwt 数据规范
xxxxx.yyyyy.zzzzz
{
"alg": "HS256",
"typ": "JWT"
}
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Oauth2规范
1)资源有者(User)
2)认证授权服务器(jt-auth)
3)资源服务器(jt-resource)
4)客户端应用(jt-ui)
同时,在这种协议中规定了认证授权时的几种模式:
1)密码模式 (基于用户名和密码进行认证)
2)授权码模式(就是我们说的三方认证:QQ,微信,微博,。。。。)
3)…总结(Summary)
重难点分析
FAQ 分析
Bug 分析
10-单点登录系统拓展实现
拓展业务描述
增加数据库访问
第二:将上传的文件信息写入到数据库(自己做)
第三:将登录操作,文件上传操作的操作日志写入到数据库.(自己做)增加服务之间的调用
第二:认证服务与资源服务都调用系统服务(将日志传递给系统服务,进行数据的持久化)-自己做系统服务设计及实现
业务描述
工程结构设计
工程数据初始化
第一:登录mysqlmysql -uroot -proot
source d:/jt-sso.sql
创建系统工程
添加项目核心依赖
创建项目配置文件
server:
port: 8061
spring:
application:
name: sca-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
创建项目启动及测试类
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SystemApplication {
public static void main(String[] args) {
SpringApplication.run(SystemApplication.class,args);
}
}
package com.jt;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
public class DataSourceTests {
@Autowired
private DataSource dataSource;//HikariDataSource
@Test
void testGetConnection() throws SQLException {
Connection conn=
dataSource.getConnection();
System.out.println(conn);
}
}
Pojo对象逻辑实现
package com.jt.system.pojo;
import lombok.Data;
import java.io.Serializable;
/**
* 通过此对象封装用户信息
*/
@Data
public class User implements Serializable {
private static final long serialVersionUID = 4831304712151465443L;
private Long id;
private String username;
private String password;
private String status;
}
Dao对象逻辑实现
package com.jt.system.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.system.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper
package com.jt;
import com.jt.system.pojo.User;
import com.jt.system.dao.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class UserMapperTests {
@Autowired
private UserMapper userMapper;
@Test
void testSelectUserByUsername(){
User user =
userMapper.selectUserByUsername("admin");
System.out.println(user);
}
@Test
void testSelectUserPermissions(){
List
Service对象逻辑实现
package com.jt.system.service;
import com.jt.system.pojo.User;
import java.util.List;
public interface UserService {
User selectUserByUsername(String username);
List
package com.jt.system.service.impl;
import com.jt.system.dao.UserMapper;
import com.jt.system.pojo.User;
import com.jt.system.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User selectUserByUsername(String username) {
return userMapper.selectUserByUsername(username);
}
@Override
public List
Controller对象逻辑实现
package com.jt.system.controller;
import com.jt.system.pojo.User;
import com.jt.system.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/user/")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/login/{username}")
public User doSelectUserByUsername(
@PathVariable("username") String username){
return userService.selectUserByUsername(username);
}
@GetMapping("/permission/{userId}")
public List
启动服务进行访问测试
认证服务工程中Feign应用
业务描述
添加Feign依赖
Pojo对象逻辑实现
package com.jt.auth.pojo;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private static final long serialVersionUID = 4831304712151465443L;
private Long id;
private String username;
private String password;
private String status;
}
Feign接口逻辑实现
package com.jt.auth.feign;
import com.jt.auth.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@FeignClient(name = "sca-system",contextId = "remoteUserService")
public interface RemoteUserService {
/**定义基于用户查询用户信息的方法*/
@GetMapping("/user/login/{username}")
User selectUserByUsername(
@PathVariable("username") String username);
/**基于用户名查询用户权限信息*/
@GetMapping("/user/permission/{userId}")
List
调用Feign接口逻辑
package com.jt.auth.service;
import com.jt.auth.feign.RemoteUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 登录时用户信息的获取和封装会在此对象进行实现,
* 在页面上点击登录按钮时,会调用这个对象的loadUserByUsername方法,
* 页面上输入的用户名会传给这个方法的参数
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
private RemoteUserService remoteUserService;
//UserDetails用户封装用户信息(认证和权限信息)
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
//1.基于用户名查询用户信息(用户名,用户状态,密码,....)
com.jt.auth.pojo.User user=
remoteUserService.selectUserByUsername(username);
//2.查询用户权限信息(后面会访问数据库)
List
启动服务进行访问测试
总结(Summary)
11-微服务课上l历史问题总结
Day01
服务分析
项目结构分析
pom.xml 文件删除线
MySQL的版本问题
Nacos服务启动问题
问题3:启动命令用错位置idea 中启动nacos
第二步:新建 shell Script脚本,例如:
第三步:输入nacos信息,例如Maven 依赖问题
第一:依赖添加的是否正确(groupId,artifactId,version)
第二:检查元素嵌套关系(dependencies,dependency)
第三:检查父工程依赖是否有问题,单词是否正确
第四:检查maven环境配置(最好一个项目一个本地库,假如多个项目共用一个,要确保多各项目使用的依赖版本是一致的。)Day02
项目克隆问题
这个问题,可以右键项目的pom.xml文件,然后执行如下操作,将项目添加到maven区,例如:JDK 配置问题
如上问题,可以打开项目结构,然后配置JDK,例如Maven配置编译问题
服务注册异常(NacosException)
服务配置读取问题
配置文件格式问题
无法访问此网站
pom.xml有删除横线
访问404异常
访问500异常
例如,打开后台,假如出现了如下问题,就是你远程调用的服务url应该是写错了
例如:假如你的Consumer服务调用Provider服务,但是Provider服务没有启动就会出现如下问题:端口占用问题
服务调用案例分析
服务ID问题分析
Day03
Application Failed to Start
Maven Helper插件应用
客户端调用异常(ClientException)
@PathVariable 注解在@FeignClient中应用
依赖注入失败(Bean Not Found)
请求映射路径重复定义
@FeignClient注解名字问题
Feign远程调用超时
图解服务调用方案
这张图描述了远程服务调用的几中方式:
第一种:服务比较少,例如就两个服务,一个服务消费,一个服务提供,就不需要注册中心,不需要负载均衡.
第二种:并发比较大,服务服务比较多,我们需要管理服务,就需要注册中心,我们还需要服务间的负载均衡.但代码编写的复杂多相对高一些,我们需要自己获取ip,获取端口,拼接字符串等.
第三种:我们要基于第二种进行代码简化,底层提供了一种拦截器,把基于服务名获取服务实例的过程在拦截器中做了封装,简化了代码的开发.但是加了拦截器多少会在性能少有一点损耗.
第四种方式主要是从代码结构上做一个挑战,我们前面三种基于RestTemplate进行服务调用,本身属于一种远程服务调用业务,能够将这种业务写到一个业务对象中,Feign方式就诞生了,它主要对代码结构的一种优化.Day04
配置文件加载顺序
拉取(Pull)配置信息
Nacos配置中心模型
Tomcat 请求处理分析
Java线程池构成分析
线程池任务执行过程
线程拒绝执行异常
Day05
JDK 版本问题
Sentinel 控制台显示
微服务中的Sentinel日志
回顾Spring MVC 请求处理
其中:
第一:客户端向web服务(例如tomcat)发起请求。
第二:tomcat会调用Filter对请求进行预处理(例如请求编码处理,请求认证分析等)。
第三:请求经过滤器Filter处理过后会交给DispatcherServlet对象(负责资源调度,前端控制器),此对象基于url找到对应的请求处理链对象(HandlerExecutionChain)。
第四:DispatcherServlet获取了请求执行链之后,会首先调用请求执行链中拦截器(HandlerInterceptor)对象(这个对象会在@RestController之前执行).
第五:拦截器对象获取请求以后可以对请求先进行分析,例如记录请求次数,请求时间,然后控制对后端Controller的调用。
第六:拦截器允许请求去传递到Controller时,Controller对象会对请求进行处理,然后将处理结果还会交给MVC 拦截器。
第七:拦截器拿到响应结果以后对其进行分析处理(例如记录Controller方法执行结束的时间)
第八:拦截器将响应结果传递到DispatcherServlet对象。
第九:DispatcherServlet拿到响应结果以后,会基于响应数据的类型,调用相关处理器(Processer)进行处理。
第十:响应结果处理器对象对响应数据处理以后,会将其结果交给DispatcherServlet对象。
第十一:DispatcherServlet对象拿到响应数据的处理结果时,会将结果基于ServletResponse对象响应到客户端。Sentinel 请求拦截分析
当我们在服务中添加了Sentinel依赖以后,Sentinel会为我们的服务提供一个SpringMVC拦截器,这个拦截器会对请求进行拦截,然后基于请求url获取sentinel控制台中设置好的流控规则,然后采用一定的算法对请求url要访问的资源进行流量限制。Day06
Idea中启动Nacos
第二步:添加Shell Script,例如:跨域访问问题
Nacos 服务注册问题
文件上传404问题
请求资源405异常
请求资源500异常
BeanCreationException 异常
服务名无效或没有定义
Day07
网关配置文件问题
服务访问被拒绝(Connection refused)
网关负载均衡调用流程分析
503访问异常
Postman请求携带请求头
作业(homework)
第二:尝试在文件上传服务上创建网关项目,客户端请求直接请求网关实现文件上传.Day08
Debug调试
http Client 应用
503 异常分析
修改文件上传工程结构
Day09
网关执行流程分析
存在多个跨域设计
自定义执行链设计
Spring MVC 拦截器
Spring框架生态设计
Day10
JVM 参数说明
AOP 执行流程分析
Day11
文件上传架构升级
SpringSecurity 执行流程
JS中的断点调试
Day12
单点登陆方案
401 认证异常
检查令牌
权限校验过程分析
postman 上传文件
403 访问被拒绝
资源请求图标问题
Day13
Postman测试刷新令牌
生成序列化ID
Idea&@Autowired
12-2107课上问题分析及总结
Day01~微服务架构入门
核心知识点
常见问题分析
常见Bug分析
课后作业
作业答案
第一步:在sca-common工程中创建StringUtil.java工具类,例如:package com.jt.common.util;
/**
* 自己定义一个操作字符串的工具类
*/
public class StringUtils {
/**
* 判断字符串的值是否为空
* @param str 这个参数就是你要验证的字符串
* @return true表示空
*/
public static boolean isEmpty(String str){
return str==null||"".equals(str);
}
}
package com.example;
import com.jt.common.util.StringUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @SpringBootTest 注解描述的类为Spring工程的单元测试类
*/
@SpringBootConfiguration //(假如工程中有启动类,注解就不需要加了)
@SpringBootTest
public class StringTests {
/**
* 在当前测试类方法中,使用了sca-common项目中的StringUtils工具类
*/
@Test
void testStringEmpty(){
String content="helloworld";
boolean flag= StringUtils.isEmpty(content);
System.out.println(flag);
}
}
方式1:打开电脑的命令行窗口,输入set JAVA_HOME 指令,例如:
方式2:假如是window平台还可以通过,如下方式进行检查,例如:Day02~Nacos 注册中心入门
核心知识点
常见问题分析
常见Bug分析
说明,这里一定要注意JAVA_HOME单词的拼写,JAVA_HOME中定义的JDK是存在的.
当执行nacos-mysql.sql文件时,出现如下错误:课后作业
Day03~服务发现及调用
核心知识点
常见问题分析
常见Bug分析
课后作业
作业答案
package com.jt.common.net;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 模拟一个简易的tomcat(服务)
* 基于Java中的网络编程实现(java.net)
* 1)网络服务端(ServerSocket)
* 2)网络客户端(Socket)
*/
public class Tomcat {//企业规范
public static void main(String[] args) throws IOException {
//1.创建服务(例如:启动nacos,启动....),并在9999端口进行监听
//网络中计算机的唯一标识是什么?ip
//计算机中应用程序的唯一标识是什么?port
ServerSocket server=new ServerSocket(9999);
System.out.println("server start ...");
//2.启动服务监听
while(true){
//监听客户端的链接(这里的socket代码客户端对象)
Socket socket=server.accept();//阻塞方法
//在这里可以将socket对象的信息记录一下.(服务注册)
//创建输出流对象,向客户端输出hello client
OutputStream out =
socket.getOutputStream();
//byte[] responseContent="hello client".getBytes();
byte[] responseContent=("HTTP/1.1 200 ok \r\n" +
"Content-Type: text/html;charset=utf-8 \r\n" +
"\r\n" +
"
hello client
").getBytes();
out.write(responseContent);
out.flush();
}
}
}
package com.jt.common.net;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
/**
* 模拟一个简易浏览器(Client)
*/
public class Browser {
public static void main(String[] args) throws IOException {
//1.创建网络编程中的客户端对象(Socket)
//构建Socket对象时要执行连接个计算机(ip),访问计算机中的哪个应用(port)
Socket socket=new Socket("127.0.0.1",9999);//TCP
//2.创建一个输入流对象,读取服务端写到客户端的数据
InputStream in = socket.getInputStream();
byte[] buf=new byte[1024];
int len=in.read(buf);
String content=new String(buf,0,len);
System.out.println(content);
//3.释放资源
socket.close();
}
}
也可以打开,浏览器直接访问http://localhost:9999Day04~Nacos 配置中心入门
核心知识点
*@RefreshScope注解的应用(描述类,实现类中属性值@Value与配置中心中内容的同步)常见问题分析
常见Bug分析
课后作业