认证与授权流程
认证
客户端发送用户名和密码给服务端
服务端收到用户名和密码,判断用户名和密码是否正确。正确就发送token给客户端。(token存储用户相关的信息,例如id,token,用户名,失效时间)
客户端收到token之后存储起来
下次只要发请求给服务端就要token这个信息(通常是同通过请求头信息带的)
服务端收到token信息之后,到
mysql
或redis
中验证。成功就解析信息出来使用
ThreadLocal
传递解析出来的用户信息
备注:4、5、6步骤是通过filter来实现的。token使用的是JWT(Java Web Token)
实现。也可以是sa-token
结构:
头部:类型加密方式
载荷:失效时间和存储的内容
签名:头部和载荷
base64
之后,使用密钥进行加密产生签名
缺陷:
不能存放敏感信息
内容携带越多,生成的串很长
JWT
一旦生成就不能续期
授权
上级给下级授权,最高权限用户为admin
,存在5张关系表(用户表,角色表,用户角色中间表,权限表,角色权限中间表),使用菜单树来给权限。通过URL或注解来实现。
鉴权流程(尽量):
执行dofilter
方法,获取请求的目标接口的安全信息(权限信息),调用真正的鉴权过滤器方法accessDecisionManager.decide
。默认使用AffirmativeBased
鉴权实现类(一票通过)。内部使用了投票机制,同时将自身鉴权能力委托各投票器实现
。根据内部的List decisionVoters
投票器列表属性,投票器列表在项目启动时就注入到该属性中了,循环调用投票器进行投票,只要有一票通过即可,即调用AccessDecisionVoter.vote
方法。进行鉴权
百万级数据导入导出
导出
思路:
先默认每个表(sheet)的最大存储为100万条数据,每次查询20万条数据。
根据查询总条数计算有多少个sheet页,计算每个sheet需要执行查询次数。
多线程执行查询任务,收集所有查询数据,关闭线程池。
用
EasyExcel
写入报表数据
导入
JDBC
的效率比MyBatis
高,因为MyBatis
底层也是通过JDBC
对数据库操作的,Mybatis
在处理结果集的时候都是一条一条数据进行循环遍历处理的,所以效率就下将了。
方法:数据分批+JDBC
分批插入+事务
思路:
使用
JDBC
读取 Excel 每一行数据返回的数据类型是 Map<Integer,String>,数据add进入集合数据大小为10万条,就执行一次插入。存入数据小于1万条就使用
MyBatis
批量插入就行设置文件解析器的最大文件大小。
(Saas
业务)数据的隔离有哪些?怎么隔离?
多租户数据隔离:
一个租户独立一个数据库
一个租户独立使用一个数据库,那就意味着我们的SaaS系统需要连接多个数据库,这种实现方案其实就和分库分表架构设计是一样的,好处就是数据隔离级别高、安全性好,毕竟一个租户单用一个数据库,但是物理硬件成本,维护成本也变高了。
独立的表空间
这种方案的实现方式,就是所有租户共用一个数据库系统,但是租户之间相互影响,租户计费困难。
按租户id字段隔离租户
这种方案是多租户方案中最简单的数据隔离方法,即在每张表中都添加一个用于区分租户的字段(如tenant_id或org_id啥的)来标识每条数据属于哪个租户,当进行查询的时候每条语句都要添加该字段作为过滤条件,其特点是所有租户的数据全都存放在同一个表中,数据的隔离性是最低的,完全是通过字段来区分的,很容易把数据搞串或者误操作。
多数据源如何切换
场景①
Saas
项目的运营端为租户手动添加某个应用的账号时候,也就是在该应用里面插入用户数据。
Saas
运营端的库(A库)与应用端的库(B库)是相互隔离的,要操作B库就要使用数据源切换
场景②
MySQL
的读写分离上,读操作走从库,写操作走主库。一套系统又要操作主库,又要操作从库,数据源就要发生切换
场景③
比如管理端要查询订单数据,但是管理端操作的是管理端的库,订单因为数据量大所以单独创建一个订单库。所以管理端要操作订单库,就要使用数据源切换。
怎么切换的
方案一:自己实现,继承AbstractRoutingDataSource
设置数据源,就向ThreadLocal
告诉当前使用那个数据源
改进:使用AOP+Annotation
来实现,告诉ThreadLocal
使用那个数据源。
方案二:mybatis-plus
的动态数据源
方案三:sharding jdbc
自定义Annotation使用过没
权限
自定义一个注解为Perm
实现给注解的功能,并做成一个切面。
放到Spring容器里,启动动态代理
日志记录
跟权限一样的步骤
动态数据源切换
自定义一个注解为
DS
配置
yml
文件主从两个数据库写一个切面完成完成功能,继承Ordered
写一个配置类自动拿到
spring.datasource
中的配置, 创建一个DruidDataSource
完成两个数据的创建使用
AOP
场景在那些地方使用过
权限
日志记录
动态数据源切换
MongoDB
使用场景
MongoDB
是非关系型数据库的(NoSQL(Not Only SQL))
一种。MongoDB
是一种面向文档的NoSQL
数据库系统。它采用了JSON
格式的BSON
文档来存储数据,每个文档都有自己的键值对组成的结构。
订单信息场景
Mysql
利用分库分表存储大量订单信息,一般订单信息存储时间很长。可以分为冷热数据。
近3个月的订单数据放在MySQL
(使用分库分表)中,3个月以上的数据迁移(方法:增量迁移)到MongoDB
中。
社交业务场景
使用 MongoDB
存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
建议安装使用Tidb
,包括整体架构和使用场景
在项目中用到了那些设计模式(MyBatis
,Spring...)
Mybatis
建造者模式
例如
SqlSessionFactoryBuilder
,XMLConfigBuilder
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
在
MyBatis
初始化过程中,SqlSessionFactoryBuilder
会调用一些文件(所有的*Mapper.xml
文件,调用XMLConfigBuilder
读取所有的MybatisMapConfig.xml
),构建Configuration
对象。将Configuration
对象作为参数构建一个SqlSessionFactory
对象
工厂模式
例如
SqlSessionFactory
,但该工厂是一个简单工厂模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常具有共同的父类。
openSession
调用底层方法构建SqlSession
,SqlSession
的执行是交给Executor
执行的
单例模式
ErrorContext
和LogFactory
使用了单例模式三要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例
代理模式
执行
DefaultSqlSession.getMapper()
,拿到Mapper
接口对的MapperProxy
,MapperProxy
中就有DefaultSqlSession
给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。它是一个对象结构型模式。
两个步骤:①、提前创建好一个Proxy,②、使用的时候会自动请求Proxy,然后由Proxy来执行具体的业务。
模板方法模式
是基于继承的代码复用的基本技术
BaseExecutor
就采用了模板方法模式,它实现了大部分的SQL
执行逻辑,然后把一些方法交给子类定制化完成。SimpleExecutor,ReuseExecutor,BatchExecutor
Spring
工厂方法模式
定义了一个用于创建对象的接口,让子类决定实例化哪一个类。
例如
BeanFactory
,ApplicationContext
简单工厂模式
开发中被频繁使用,其核心是 由一个工厂类根据传入的参数,动态决定创建哪一个产品类的实例。
Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 Bean 对象。但是,在传入参数后创建 Bean 还是传入参数前创建 Bean,这个要根据具体情况而定。
目的:松耦合,Bean增强
代理模式
为其他对象提供一个代理以控制对这个对象的访问。
Spring AOP
就是基于动态代理的
单例模式
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
Spring 中 Bean 的作用域就是 singleton(单例)的。可以切换成prototype(多例)
存储服务oss
(工厂)
项目中抽1-2个模块,说清楚表结构(表中有哪些字段)
RPC
的理解
ZooKeeper
的安装和使用
socket程序进行服务的注册和调用(两个负载均衡:随机,轮询)
登录如何实现
hotkey
(计算出热点推到本地缓存中)
SpringBoot3.0有什么新的特性?Spring6新的特性?
SpringBoot3.0的新特性
一、Java版本要求提升(Java 17作为最低版本)
二、依赖升级
三、GraalVM原生镜像支持
四、应用可观察性增强
五、非功能特性改进
六、其他改进
支持Kotlin 1.5:SpringBoot 3.0完全支持Kotlin 1.5,使得开发人员可以在Spring Boot应用中无缝使用Kotlin最新的语言特性。
全新启动器:SpringBoot 3.0提供了若干全新启动器,如spring-boot-starter-rsocket(RSocket支持)、spring-boot-starter-data-r2dbc(响应式关系型数据库)等。
改进的依赖管理:SpringBoot 3.0改进了对第三方依赖的管理,依赖版本现在由Spring官方维护,简化了依赖管理过程。
配置属性弃用警告:SpringBoot 3.0添加了对弃用配置属性的警告信息,提醒开发人员替换为最新配置属性,为未来版本升级做好准备。
改进的测试框架:SpringBoot 3.0改进了测试框架,增加了WebTestClient的支持,增强了MockMvc的功能,改进了集成测试支持等。
SpringBoot6新特性
一、WebFlux的增强
二、HTTP Interface的引入
三、对Kotlin的原生支持增强
四、对微服务的进一步支持
五、响应式编程的增强
六、AOT编译和原生镜像支持
七、Java版本要求提升
基于若依二次开发
Admin微服务
动态数据源切换
权限的分配
OSS存储
认证服务
登录请求先到网关,再到认证服务进行验证,成功就返回token(jwt,redis+token)。
再网关会有一个全局Filter,收到token之后回先进行判断,从而解析出用户信息,再将用户信息放到header中传递给每个服务,每隔服务需要一个拦截器,拦截请求,从header中取出用户服务,放到threadlocal中,进行传递。
鉴权:可以将用户本身具有的权限信息存到jwt或者redis中,在每个服务中使用AOP进行鉴权
门户服务
采用的分级缓存是本地缓存+reids集中式缓存的方式
思路:
提前预热好redis缓存和本地缓存,如果本地缓存失效了,就到redis中去找,然后放到本地缓存中;如果redis中的缓存失效了,就到DB中找,再放到reids和本地缓存中。
实现:
项目中使用guava或caffine,本地缓存设置了失效时间,所以到了失效时间,就会到redis中进行查找,由于访问redis时会造成跨网络延时,形成毛刺现象。解决方法就是设置本地缓存永不过期,就引入双缓存来解决问题,将主缓存和备份缓存设置不同的失效时间,并且备份缓存设置为多长时间不访问就过期这种形式。获取数据时先到本缓存中找,本地缓存没有就到备份缓存中找,但是并没有使用备份缓存数据去填充主缓存。
要保证本地缓存和redis数据的一致性?
采用异步定时任务,每隔1分钟刷新一次。主缓存或备份缓存没有数据的时候,就到redis中查找。
redis和db数据的一致性?
先更新DB在删除缓存,失败就使用过期来兜底
先更新DB在删除缓存,失败就采用延迟双删,还是失败就用过期来兜底
先更新DB在删除缓存,失败就发送消息到mq,让mq来进行数据的更新和删除
详情页
一样的步骤
商品服务
多数据源的切换
自定以注解来做
mybatis-plus插件来做@DS("slave_1")
shardingJDBC
数据模型:品牌,分类,sku,spu,规格属性
商品详情页:采用分级缓存
思路:
线存到本地缓存,本地缓存没有就到redis中去找,有就查找出来,并把数据存到本地缓存中,redis没有就到db中查找,查找出来放到redis和本地缓存中
问题:因为网络原因,造成毛刺现象。redis一致性问题
redis缓存问题:穿透,击穿,雪崩
redis和db数据一致性问题:双删,基于mq,基于cannal(主从)
搜索(elasticSearch)服务
购物车服务
①、流程:
②、基于redis的hash结构来实现的(大key和小key是什么)
大key是当前用户Id,小key是skuId
③、使用hash做购物车的时候,crud的命令如何操作
添加购物车
根据用户选择的skuId,先从threadLocal拿到当前登录用户的Id,从redis中查出这个用户对应的购物车
判断skuId在不在购物车对应的购物项中,在就修改其数量
如果不在,就远程调用商品服务,获取skuId对应的信息最终组装程购物项,放到redis中
查询购物车
根据当前登录用户Id,查询到这个人对应的购物车信息,返回客户端
订单服务
流程:直接购买(或购物车结算)--> 订单确认页 --> 生成订单
订单确认页:展示订单项,选择收货地址,选择优惠券
订单确认页接口:
判断是直接购买还是购物车购买(判断是否带有skuId来进行)
如果是直接购买:通过skuId调用商品服务获取商品信息,转成订单项
如果是购物车:远程调用购物车服务,通过当前登录用户获取对应过物车中选中的购物项,并转换成订单项。
远程调用用户服务,获取该登录用户的收货地址
使用雪花算法生成一个防重token,并将该token存在redis中,然后传递到订单确认页
(异步编排)
下单接口:
①、将订单确认页的相关数据(订单项,选择收货地址,优惠券,金额,数量)传递到下单接口
②、判断是否防重提交
逻辑:先从redis中查找,然后再和你传进来的token进行对比,然后删除redis中的token,但是在上面逻辑会出现并发问题,所以使用lua脚本将上述操作封装成脚本来执行,保证原子性。
③、验价
将前端的价格传递过来
后端订单项中sku的单价和数量要进行重新算价对比(调用商品服务将skuId传递进去,获取该信息,然后进行汇总),一致就就进行下一步操作。
④、锁库存
在什么时间点锁库存(加入购物车锁库存,下单锁库存(采用))
流程:
调用库存服务,拿出每个skuId进行库存锁定,上一把分布式锁(锁sku)
异常:优点成功了,有失败了,将锁定成功的数据进行回滚操作,重新恢复库存,提示商品库存不足。
正常:将锁定成功的这些sku放到redis中(key为订单号,value为锁定成功的这些sku)
放redis的原因:后面如果该订单不支付,到时间就自动释放,需要到redis中查找该订单锁定了哪些商品的库存,并回滚回去。
(将分布式锁)
⑤、调用唯一id生成服务,生成订单号
特点是唯一性
实现方式:uuid,snowflake,基于db
使用leaf-segment,使用双缓冲做高性能()
(雪花算法的组成符号位、时间戳、工作机器ID和序列号)
⑥、产生订单并保存订单
⑦、产生订单明细 ,并保存
⑧、保存订单的收货地址
⑨、远程调用购物车服务删除曾经选中的购物项
⑩、返回订单编号
分布式事务问题: seata/最终消息已知悉
我们这里选择的seata的AT模式,来大致讲下seata的工作原理
在seata中有TC,TM,RM这几个角色。
我们要使用的时候先要部署seata server,在seata server有几张关键的表:global_table,lock_table,branch_table等
在我们的每个服务方需要有undo_log 表
大体流程:TM会请求TC开启一个全局事务,TC返回全局事务id,也就是xid
TM收到xid之后会在每个服务中进行传播,每个微服务会向TC注册自己的分支事务
每个微服务(RM)会执行自己的本地事务并将执行将相关信息以前置镜像后置镜像的形式写到undo_log表中
TM让TC提交该全局事务,TC让每个RM提交自己的分支事务【删除对应库中的undo_log数据】
TM让TC回滚该全局事务,TC让每个RM根据undo_log中的前置镜像和后置镜像来做数据逆向操作,并删除undo_log
超时未支付情况
解决方案:分布式定时任务,延迟消息(基于rabbitmq的私信队列)
用到xxl-job,在xxljob中,要部署一个调度中心,我们写执行器执行的执行逻辑
更新订单的状态,释放库存(远程调用库存服务,从redis找出该订单项信息,惊醒库存的释放,从redis将该订单锁定的库存删掉)
问题:释放哭成,删redis(分布式事务问题)
提升订单的性能
读和写的能力
读:一般情况下,需要提升读的性能,更多是考虑缓存,但是要考虑缓存的本质:数据是经常访问的,不容易改变。但是订单数据的特点:对一般用户而言,下单之后几天比较关注订单,如果这种放到缓存,就浪费了缓存;这些数据和用户强关联
场景:不依靠缓存,也要提升并发能力
依靠数据库层面来提升并发能力:读写分离,分库分表
读写分离:
①、原理:主从同步
②、动态数据源的切换
③、主库和从库短时间数据不一致怎么解决
A、业务层面。操作主库之后,故意放一个页面让其点击一下才能进行下一步,故意延迟一下
B、对时延要求特别高,直接查主库
分库分表
分库:一般情况会垂直拆分(业务维度拆分) 分表: 水平拆分:数据量大 5000w= 2000w+3000w 垂直拆分:学生信息表(id,name,xx,介绍(附件))===》学生基本信息(id,name,xx) + 学生附件(id,附件)
数据量的计算
一个月100w订单量,每天就是3万3左右,一天就6个小时的高峰期(早上9-11,晚上3-5,晚上8-10),每个小时就5500个订单,平均每秒1个订单,有时就两个订单
路由键如何选择
选择的原理:最终会按什么字段来进行搜索
订单的分库分表路由键:订单号,并将会员号的后2位拼接到订单号中,如果还有根据其他字段查询,需要映射表,分表之后分页和join,回到java层面进行分页和join
面试如何回答:
①、在什么场景下用到分库分表?
②、核算数据
③、分库分表 (订单及订单明细)
④、路由键的选择
⑤、历史订单迁移 本身我们进行分表的话需要128张表,最终只用了32张,采用的方案就是冷热数据分离,将3个月前的历史订单迁移到mongodb,mysql中只保留近3个月的数据就需要做历史数据的迁移。
迁移方案:
①、先找到这批迁移数据的最大id,将迁移的数据和最大id保存mongodb的表中(使用mongodb的事务来保证)
②、迁移完成之后会删除mysql中的数据,只需要删除该最大id之前的数据,删除的时候尽量备份,删除之后会稍停顿一下,让mysql来维护一下B+树
支付服务
流程: 根据传递过来的订单号、支付类型进行支付
①、根据订单号调用订单服务获取订单信息
②、避免多端支付产生问题,上了一把分布式锁(锁的粒度是订单)
③、校验订单状态是否是未支付
④、看这笔订单在订单支付表中是否存在,如果没有就生成支付记录存在支付记录表中,如果有了就判断是否是未支付
⑤、根据支付类型发起支付
⑥、支付完成,会调用同步回调地址,验签,验签成功就跳转商户对应页面
⑦、异步回调(修改商户这边状态的) 分布式锁, 只要你没有返回success,24小时会调8次,首先是验签,判断支付记录表中是否是已支付, 是已支付直接返回success,如果不是已支付,校验支付宝返回金额和我们的金额是否一致,修改支付记录表中支付状态,调用库存真实扣库存, 调用订单服务修改这笔订单的状态。 分布式事务问题
评论区