格局管理(项目经验)
格局管理和性能优化
日常总结
工作日志记录
及时总结成可输出文档,包括但不限于 业务通用指标库、算法能力(sql能力、业务算法、建模算法、公共算法等)总结及代码实现、,并做成高阶技能快查app
制定规范,包括 需求规范、app开发规范、数据使用规范、数据清洗规范
重大贡献和输出记录,做好当下。
Level答辩:1、做了多少装备,开发的好(设计好,测试充分,交付件齐全);2、做了哪些能力帮助团队成长
绩效自评,数据尽可能量化。包括:重点工作完成情况,正向/负向关键事件等。注意涉及性能、效率提升等一定要有量化数据
进度汇报
负责模块、目标、问题、风险
数据处理术语
1、数据清洗是过滤杂质(冗余数据、非标数据、无效数据、异常数据、错误数据),一是为了解决数据质量问题,二是让数据更适合做挖掘。
2、数据萃取是提纯
Java线程池
Java锁
Spring Framework
https://docs.spring.io/spring-framework/reference/overview.html
ICO容器
https://docs.spring.io/spring-framework/reference/core.html
Spring Web MVC
https://docs.spring.io/spring-framework/reference/web/webmvc.html
- DispatcherServlet
- HTTP 消息转换
- 注解式 Controller:通过注解定义控制器
- View 技术
Spring Bean的生命周期
Spring Web 请求全链路生命周期
终极全链路时序图
下面这张时序图展示了请求从客户端发出,穿透容器、纵深拆解 Spring 核心组件,完成内容协商并携带响应结果返回的完整旅程:
1 | sequenceDiagram |
全链路五个核心阶段深度剖析
Spring Web 的生命周期精炼为以下五个环环相扣的连贯阶段:
阶段 1:底层网络接收与容器解析(Tomcat 阶段)
- 机制:Tomcat 启动后,其
Http11NioProtocol组件会开启Acceptor线程监听 TCP 连接,随后转交给Poller线程基于 NIO 进行异步读写轮询。 - 解析:当检测到网络数据可读时,工作线程(
Http11Processor)将原始的、杂乱的网络字节流解析为符合 HTTP 规范的请求行、请求头和请求体,并将其封装为 Servlet 标准的HttpServletRequest与HttpServletResponse对象。
阶段 2:容器级门禁(Filter 阶段)
- 定位:请求首先进入过滤器链,此处完全属于 Spring 边界之外。Filter 拥有最全局的视野,能拦截包括静态资源、错误页在内的所有流量。
- 关键行为:Filter 依靠
FilterChain.doFilter()驱动请求向后传递。若因权限或安全校验未通过而没有调用该方法,请求将被直接截断。 - 边界陷阱:由于此时尚未进入 Spring 上下文,若 Filter 层抛出异常,Spring 的
@ExceptionHandler(或@RestControllerAdvice)是无法自动捕获的,异常会直通 Tomcat 导致弹出丑陋的 500 页面。必须通过手动注入并调用HandlerExceptionResolver才能将异常转交给 Spring 体系处理。
阶段 3:核心路由分发与前置拦截(Spring MVC 阶段)
- 寻路:请求通过 Filter 放行后正式进入 Spring MVC 的心脏——
DispatcherServlet#doDispatch()。 - 四大金刚之
HandlerMapping登场。它作为“寻路人”,根据请求的 URL 和 HTTP Method,在内存映射表(如RequestMappingHandlerMapping)中查找对应的 Controller 方法(封装为HandlerMethod),并连同该 URL 配置的拦截器列表一起打包,组装成一个执行链对象HandlerExecutionChain。 - 拦截:DispatcherServlet 拿到执行链后,开始按顺序触发所有拦截器的
preHandle()方法。如果某个拦截器返回false,则链路立即熔断,并倒序触发此前已执行通过的拦截器的afterCompletion()方法。
阶段 4:输入输出契约与业务执行(Controller 阶段)
- 输入契约(反序列化):
HandlerInterceptor全部放行后,控制权交由四大金刚之HandlerAdapter(处理器适配器)。针对写法格外灵活的 Controller 方法,适配器负责统一调用接口。在真正触发业务方法前,它会驱动HttpMessageConverter(消息转换器) 读取请求头中的Content-Type。例如,当检测到application/json时,Jackson 转换器开始工作,将请求体中的 JSON 字符串反序列化为 Controller 方法中所声明的 Java 对象(如绑定到@RequestBody User user)。 - 业务执行:
HandlerAdapter调用真正的 Controller 业务方法,向下透传至 Service 和 DAO 层执行核心逻辑。 - 输出契约(内容协商机制决策):Controller 执行完毕返回数据后,
DispatcherServlet会根据以下逻辑树进行内容协商决策:- REST 模式:如果方法或类上标记了
@ResponseBody或@RestController,Spring 会跳过视图解析。它会检查客户端请求头中的Accept(如application/json),匹配最合适的HttpMessageConverter(如 Jackson),将返回的 Java 对象序列化为 JSON 字符串直接写入响应体,并将Content-Type强制设置为application/json;charset=UTF-8。 - 视图渲染模式:如果方法返回的是普通字符串(且无任何响应体注解),Spring 则认为这是一个逻辑视图名。此时四大金刚之
ViewResolver(视图解析器)登场,将逻辑名(如"index") 映射到物理模板文件(如/templates/index.html),并将 Controller 放入Model中的业务数据注入模板引擎,渲染出最终的 HTML 字符串写回客户端,同时设置Content-Type为text/html。
- REST 模式:如果方法或类上标记了
阶段 5:后置收尾与响应返回(清理阶段)
- 回退:业务响应生成后,控制权开始层层向外回退。首先倒序触发拦截器的
postHandle()方法(此时如果是视图模式,仍有机会修改ModelAndView)。 - 清理:在视图渲染完毕或整个请求处理终结后,触发拦截器的
afterCompletion()。这是整个 Spring 链路的最后一站,通常用于执行多线程资源清理(如ThreadLocal.remove()以防内存泄漏)或者记录接口总耗时。 - 归还:最后,控制权离开 Spring 边界回到 Filter 的后置逻辑,Tomcat 将最终的响应字节流刷新(Flush)并发送回客户端,全链路生命周期宣告结束。
核心概念澄清与避坑指南
内容协商熔断(HTTP 406 错误)
如果一个接口使用了
@RestController(只能产出 JSON),但是客户端在请求头中恶意或错误地指定了Accept: text/html,Spring 在遍历完系统内所有消息转换器后,会发现无法达成两端的内容契约。此时,系统不会容错返回 JSON,而是会直接抛出HttpMediaTypeNotAcceptableException,让客户端收到一个 HTTP 406 Not Acceptable 状态码。REST 模式下的字符串误区
在
@RestController标注的类中执行return "login";,Spring 绝对不会去寻找login.html页面。根据内容协商的 REST 契约,它会将"login"仅仅视作一个纯文本字符串,将其放入响应体中,并返回Content-Type: text/plain。
Spring Boot 启动过程
事务
Spring事务抽象,7种事务传播类型。
事务隔离级别,Spring默认隔离级别取决于数据库。脏读、幻读
Innodb中的事务隔离级别和锁的关系
数据库
建立主键
数据库表必须要有主键
优化索引
问题:导出功能走了全表扫描,导致扫描行数过多, mysql swap急速升高。
解决方案:建立合适的索引,尤其是跟在where、order by、关联查询的on后面的字段。
避免子查询
问题:临时表(即 from 后面跟 select xxx from table 这种写法)的用法偏多。因为是加载到内存中的临时表,没有索引可走,会走全表扫描。
解决方案:尽量避免非必要的临时表;
避免过多的表关联
问题:多表连接查询,有的查询甚至能关联到七八张表。
解决方案:禁止超过3张表的关联查询(由公司纳入数据库规范),一切运算等工作全部交由 Java 代码实现,数据库只做简单的存取动作。
运算交给Java
问题:多数业务逻辑都用sql语句实现,Java只做数据封装及展示,处理的业务逻辑非常有限。
解决方案:由于应用服务器资源十分充裕,而数据库存储资源异常紧张,建议一切运算等工作全部交由java代码实现,数据库只做简单的存取动作。
避免串行查询
避免多次串行查询数据库。后一次查询是否依赖前一次查询的结果,如果不是建议改成并行查询。
在业务逻辑设计的时候,尽量避免这种大批量串行查询的操作。
提高吞吐量
读写分离、Redis缓存
使用常规语句
分库分表、读写分离的中间件MyCat不支持复杂SQL语句,开发时尽量使用常规语句,提高软件的可移植性和可扩展性,为后期整改(数据库切换、分库分表、读写分离)做准备。
常规语句:DBMS都支持的语句,整理输出团队文档,避免出现非常规语句。实际项目中只需支持团队可能用到的DBMS即可(无需支持所有市面上的DB,团队把控)。
主从库
MyCat写入数据到master库,从slave库读取数据,master库数据需要同步到slave库。
关键点:主从同步实现方式;主从同步所需时间
阿里巴巴开源的MySQL同步工具 https://github.com/alibaba/canal
数据库调优
哪些情况适合创建索引
- 字段的数值有唯一性的限制
- 频繁作为 WHERE 查询条件的字段
- 经常 GROUP BY 和 ORDER BY 的列
- UPDATE、DELETE 的 WHERE 条件列
- DISTINCT 字段需要创建索引
- 多表 JOIN 连接操作时,创建索引注意事项
- 使用列的类型小的创建索引
- 使用字符串前缀创建索引
- 区分度高(散列性高)的列适合作为索引
- 使用最频繁的列放到联合索引的左侧
- 在多个字段都要创建索引的情况下,联合索引优于单值索引
限制索引的数目,一般不超过6个,原因:
1、空间占用
2、影响增删改性能,因为索引维护有成本
3、当多个索引可用于查询时,会增加优化器生成最优执行计划的时间
哪些情况不适合创建索引
- 在where中使用不到的字段,不要设置索引
- 数据量小的表最好不要使用索引
- 有大量重复数据的列上不要建立索引
- 避免对经常更新的表创建过多的索引
- 不建议用无序的值作为索引
- 删除不再使用或者很少使用的索引
- 不要定义冗余或重复的索引
数据库表结构优化
- 优化数据库结构:数据冗余、字段类型、索引建立、拆分表(冷热数据分离)、禁用索引和唯一性检查(禁止事务自动提交来提升插入速度)、使用非空约束(省去not null判断导致索引失效、索引null需要1bit空间)。
分析表:analyze table。分析字段值的分布,表中某一键所在的列不重复的值的个数,该值越接近表中的总行数,则在表连接查询或者索引查询时,就越优先被优化器选择使用。
检查表:check table。
优化表:optimize table。消除删除或者更新造成的空间碎片并重新利用。只能优化表中的VARCHAR、BLOB或TEXT类型的字段。 - 大表优化:当MySQL单表记录数过大时,数据库的CRUD性能会明显下降。
方案:查询时使用范围条件、读写分离(一主一从、双主双从)、垂直拆分(垂直分库、垂直分表)、水平拆分(单表控制1000万以内,最好进行分库,分担磁盘IO提升并发性)。
注意:尽量避免数据分片,无法避免则尽量选择客户端分片架构。两种常见分片方案:客户端代理(分片逻辑封装在jar中,通过修改或封装jdbc层来实现。比如sharding-jdbc)、中间件代理(分片逻辑封装在中间件服务器中。比如Mycat)。
如何定位调优问题
用户的反馈(主要):用户是我们的服务对象,因此他们的反馈是最直接的。虽然他们不会直接提出技术建议,但是有些问题往往是用户第一时间发现的。我们要重视用户的反馈,找到和数据相关的问题。
日志分析(主要):我们可以通过查看数据库日志和操作系统日志等方式找出异常情况,通过它们来定位遇到的问题。
服务器资源使用监控:通过监控服务器的CPU、内存、I/O等使用情况,可以实时了解服务器的性能使用,与历史情况进行对比。
数据库内部状况监控:在数据库的监控中,
活动会话(Active Session)监控是一个重要的指标。通过它,你可以清楚地了解数据库当前是否处于非常繁忙的状态,是否存在 SQL 堆积等。其它:除了活动会话监控以外,我们也可以对事务、锁等待等进行监控,这些都可以帮助我们对数据库的运行状态有更全面的认识。
MySQL监控分析视图-sys schema
- 主机相关:以host_summary开头,主要汇总了IO延迟的信息。
- Innodb相关:以innodb开头,汇总了innodb buffer信息和事务等待innodb锁的信息。
- I/o相关:以io开头,汇总了等待I/O、I/O使用量情况。
- 内存使用情况:以memory开头,从主机、线程、事件等角度展示内存的使用情况
- 连接与会话信息:processlist和session相关视图,总结了会话相关信息。
- 表相关:以schema_table开头的视图,展示了表的统计信息。
- 索引信息:统计了索引的使用情况,包含冗余索引和未使用的索引情况。
- 语句相关:以statement开头,包含执行全表扫描、使用临时表、排序等的语句信息。
- 用户相关:以user开头的视图,统计了用户使用的文件I/O、执行语句统计信息。
- 等待事件相关信息:以wait开头,展示等待事件的延迟情况。
O/R Mapping实践
让MyBatis更好用的那些工具:
- MyBatis Generator
- MyBatis PageHelper
- MyBatis Mapper https://mapper.mybatis.io/
mybatis分页原理,为什么mapper中的 @Param(“pageNum”) 和 @Param(“pageSize”) 可以被使用?为什么 PageHelper.startPage(pageNum, pageSize); 会生效?为什么 RowBounds 会自动分页?
分布式理论
- 大白话讲解CAP定理(阮一峰):https://blog.51cto.com/u_15367034/3890045
- CAP:https://pdai.tech/md/dev-spec/spec/dev-th-cap.html
- BASE:https://pdai.tech/md/dev-spec/spec/dev-th-base.html
分布式系统 - 知识体系详解:https://pdai.tech/md/arch/arch-z-overview.html
微服务
注册中心、配置中心、SpringCloud Gateway、Spring Cloud Security、Spring Cloud OpenFeign、Spring Cloud LoadBalancer、Spring Cloud Circuit breaker(两种实现Resilience4J、Spring Retry)
Stream 和 Bus
https://blog.csdn.net/qq15035899256/article/details/129972696
SpringCloud Stream:它是通过对消息中间件进行抽象封装,提供一个统一的接口供我们发送和监听消息。主要功能:实现消息队列的相关操作,消息的异步调用能让各个服务充分解耦且更加灵活。
SpringCloud Bus:它是在 Stream 基础之上再次进行抽象封装,使得我们可以在不用理解消息发送、监听等概念的基础上使用消息来完成业务逻辑的处理。主要功能:借助消息驱动来实现将消息(事件)广播到各个服务中,让服务对这些消息进行消费。
注意
为了缩短实时链路,不必要的操作通过消息传递。
Redis
https://blog.zhaolq.com/article/2020/06/Redis%E6%95%99%E7%A8%8B/
数据库缓存、分布式session、分布式系统集中限流、全局ID
分布式锁:string。SETNX不存在则设置KeyValue
排行榜:zset。按分数排序和检索)
消息队列:list。提供的阻塞命令 blpop、brpop,
点赞、签到、打卡:set。微博ID是t1001,用户ID是u3001,用 like:t1001 来维护 t1001 这条微博的所有点赞用户
- 点赞了这条微博:sadd like:t1001 u3001
- 取消点赞:srem like:t1001 u3001
- 是否点赞:sismember like:t1001 u3001
- 点赞的所有用户:smembers like:t1001
- 点赞数:scard like:t1001
MQ
通常被用来解决在高并发压力下类似于 异步处理、应用解耦、流量削锋、消息通讯、最终消息一致性 等这样的问题。
异步处理:用户注册时发送邮件和短信。传统的模式:用户下单—>邮件发送—>短信提醒,三个步骤全部完成,才能返回用户消费成功,因为后面两个步骤完全没有必须是当前时间完成,可以用户下单成功后,直接发送给mq,之后邮件发送和短信提醒,可以其他时间段来消费发送给用户。
应用解耦(事务拆分):订单系统通知库存系统扣减库存。库存系统需要收到订单后库存减一,这时候如果库存系统宕机,会造成订单丢失,把订单消息发入mq,库存系统再去mq消费,就能解决这一问题。
流量削锋:大型双11活动时候,0点有上亿并发,这时候数据库并不能承载那么大的数据冲击,而专门为高并发设计的mq可以承受住海量的请求,发送给mq,存储成功后,再消费。
消息通讯:微信单人发消息、QQ聊天群
最终消息一致性:订单和库存,银行转账
集中日志搜集和分析:
秒杀场景,异步扣减库存解决超卖的问题
1、限流
2、将库存放到redis中、接收用户请求的时候。从redis取库存,判断库存量是否大于本次订单购买量
库存大于本次购买量:扣减redis中的库存、并且将订单信息推送到MQ;
库存小于本次购买量:直接返回、数量不足。
3、MQ消费者获取消息:
1):更新数据库库存(乐观锁)
2):生成订单信息,扣除用户账户的订单金额(余额不足的话、将本次购买量加回到库存里)
3):异步通知用户购买结果。
Redis只是解决性能问题,数据库才是解决库存一致性问题
场景
异步通信
有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
解耦
降低工程间的强依赖程度,针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,当应用发生变化时,可以独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
冗余
有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
扩展性
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容。
过载保护
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
可恢复性
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
顺序保证
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
缓冲
在任何重要的系统中,都会有需要不同的处理时间的元素。消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。
数据流处理
分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。
选型
RabbitMQ,RocketMQ,Kafka
https://support.huaweicloud.com/productdesc-rabbitmq/rabbitmq-pd-190828005.html
https://blog.51cto.com/knifeedge/5011115
解决雪崩问题的常见方案有哪些?
架构之高并发:降级和熔断:https://pdai.tech/md/arch/arch-y-reduce.html
服务降级:手动开关降级(实时生效)、熔断降级、限流降级
- 请求限流:限制流量在服务可以处理的范围,避免因突发流量而故障
- 线程隔离:控制业务可用的线程数量,将故障隔离在一定范围
- 失败处理:定义fallback逻辑,让业务失败时不再抛出异常,而是走fallback逻辑
- 服务熔断:将异常比例过高的接口断开,拒绝所有请求,直接走fallback
业务量动态扩容或者缩容
运维工作
开发建表以后运维这边审核建表是否规范(仅数据库规范方面,不含业务);
测试工作
转测内容如果包含存储过程要在转测邮件中提及,由运维审核存储过程执行效率。
测试环节需进行性能测试,争取把性能问题消灭在测试环节。
已上生产的执行慢的接口,挨个整改。
性能监控
应用性能监控系统
可以跟踪从接收请求到响应的全流程
可以展示某一接口在某个时间段内的访问情况,包括接口所在地域,访问时间,响应时间,响应状态等
可以展示某一接口的完整调用链。其中有
a、该接口调用的所有服务,包括调用其他服务,http请求其他接口,访问数据库等;
b、该接口每个调用过程所耗费时间;
可以捕捉到执行的完整sql语句
社区是否有这样的软件,只需要我们在Java中将它需要的访问数据插入数据库?
高并下数据一致性方案
解决高并发场景下数据一致性的方案有两种
【延时双删策略】:在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。
先删除缓存,再写库,休眠500ms(考虑业务逻辑耗时、redis和数据库主从同步耗时等),再次删除缓存
【双删策略+缓存超时设置】的最差情况:1、在缓存过期时间内发生数据存在不一致。2、同时又增加了写请求的耗时。
【异步更新缓存】:利用MySQL binlog进行增量订阅消费(消息队列将增量数据更新到redis上)
从redis读取热点数据;增删改都在MySQL;redis更新通过消息队列和MySQL的binlog
MySQL的主从备份机制也是通过binlog来实现数据一致性
设置缓存的过期时间是保证数据一致性的关键操作,需结合业务进行合理设置。