经验总结
2023-04-17 10:16:15 0 举报
AI智能生成
工作8年的java开发的总结
作者其他创作
大纲/内容
管理经验
带应届生
开发
方案设计
1、给出技术方案设计模板:让新人写技术方案设计
2、针对新人技术方案做相关的评审
代码评审
1、新人开发完成之后,需要做相关代码审核,保证新人不能做外了
带新人
晋升
答辩
答辩之前需做好ppt质量把控,可以自己内部先过一下
技术方案设计
性能优化
接口性能设计
1、针对查询接口
1、如果是根据某一单一条件查询,可做redis缓存
1、查询的时候先去查询缓存,缓存没有,再去查询数据并且更新缓存信息;
2、设置合理的超时时间
3、update,delete,insert操作需要更新缓存信息
2、批量查询接口,限制批量查询条数。
3、优化查询接口对应的sql语句。
具体实施
1、通过cat,owl等等监控工具,查询具体的调用链路
链路追踪工具每一步都会有对应的执行时间,耗时最高的步骤,是具有重大优化价值的对象
2、查询调用链路中,每个步骤耗时时间,并找出最高耗时时间(也就是性能的瓶颈点)
3、针对耗时较长的链路分析具体原因:
常见原因如下:
循环调用外部服务接口
1、让外部服务接口提供批量查询接口
2、开启多线程方式查询接口
1、线程池方式
使用场景
1、并行执行有返回值
2、并行执行有返回值
2、java8并行流
使用场景
1、循环中并行执行循环
2、并行执行无返回值
sql查询太慢
1、针对sql语句做sql优化
2、针对id条件做缓存处理
第三方接口服务耗时过高
让第三方接口负责人做接口优化
程序有不必要的循环
1、适时跳出循环;
考虑问题
性能提升对比
优化后的性能对比
测试环境,生产环境各个性能对比
代码回滚
设置开关使用原来老的没有问题的逻辑,以防止新代码有问题;
影响范围
对已有业务的影响范围,数据影响范围
性能优化案例
1、cpu经常100%
排查步骤
1、找出那个进程cpu高
2、该进程的那个线程cpu高
3、导出该线程对应的堆栈信息
4、查找具体哪个方法的耗时比较长
2、系统内存飙高
排查步骤
1、导出堆内存文件
2、使用内存分析文件分析
数据处理相关
表相关设计
新表
考虑重点
1、表的作用,功能
1、数据模型
2、用作查询多,还是修改多
2、表数据量(在未来1年,3年,5年)数据增量,大概会达到多少,会不会影响到查询效率
3、表的索引
老表
考虑重点
1、存量数据没有该字段,怎么处理这些存量数据
1、下次更新的时候处理
2、上线前做数据刷新操作
ES数据同步
1、哪些数据应该存放在ES
1、商品常用的基础信息
2、复合条件查询的条件字段数据
2、维护ES数据同步的成本
1、需要监听正常业务中被修改的信息(需要去更新ES)
2、上线前需要刷新ES中的新增字段值(包括第一次上线,以及后面每次新增字段);
存量数据刷新数据
考虑重点
1、批量刷新
1、能够通过一次触发,刷新所有数据
2、保证幂等性
1、第二次刷新不会造成数据的错乱
2、后面刷新可以更新数据前面错误刷新的数据;
2、单个刷新
1、能做到单个刷新
保证批量刷新后,如有遗漏,可以通过此方式解决;
3、单个修改操作
1、如果数据刷新过程中,有数据刷新出现错乱,可以纠正单个数据的正确性
自测重点
1、刷新个数是否正确
需要刷新的数据条数和实际刷新的数据条数是否一致
2、单个刷新数据是否正确
双写修改单写
考虑重点
1、双写的哪些表
2、还有谁要直接连接我们的youxuan数据库
3、哪些表有同步到hive
4、下游有哪些位置监听了binlog日志
5、哪些MQ会去刷新这些表
存量/增量数据同步
canal
项目搭建此过程省略
操作过程
2、update操作后,会有对应的binlog日志产生;
3、canal服务拉取到日志信息后,解析日志落库
阶段1
上线前(存量数据同步阶段)
1、上线前创建时间在某个静止的时间点之前的所有存量数据,进行update操作
2、获取到同步完成后,最后一条数据的在之前数据库的创建时间;
阶段2
存量数据同步完成-上线完成中间时间
源数据会有新增数据,此时也是需要同步
阶段3
上线后(增量数据同步阶段)
3、拿到上线后增量同步的第一条数据的在源数据源的创建时间;
4、将操作1的时间作为起始时间,将操作3的时间作为结束时间,查询元数据表,获取阶段2中的新增数据
缓存处理
技术方案设计
业务需求方法方式
1、结合上下业务流程
2、开发时间评估
1、拆分任务
2、针对单个任务评估时间
技术改造方案方式
梳理步骤
现状
1、数据在每一个步骤中的CRUD扭转
redis怎么存储
DB怎么存储
ES怎么存取
2、目前存在一些明显痛点问题
效率低
拓展性差
服务不稳定
3、技改设计到的数据量
1、新数据量大小
2、老数据量大小
技改目的
1、解决了什么问题
效率提高了多少
系统的可维护性
系统的可拓展性
数据扭转方案
需要兼容新老数据扭转逻辑
开发实用用具
代码书写效率提升工具
lombok
指定json映射
@JsonProperty("ti_me")
Date time;
Date time;
JSON字符串中的ti_me 对应的数据会映射到time 字段上
@NonNull
使用效果
代码书写:public Student(@NonNull String name, Integer age) {
this.name = name;
this.age = age;
}
this.name = name;
this.age = age;
}
编译后效果
@NoArgsConstructor: 自动生成无参数构造函数。
@AllArgsConstructor: 自动生成全参数构造函数。
@AllArgsConstructor: 自动生成全参数构造函数。
@Data: 自动为所有字段添加@ToString, @EqualsAndHashCode, @Getter方法,为非final字段添加@Setter,和@RequiredArgsConstructor
时间格式化
jackSON包
Date类型字段加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
子主题
yaml配置
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
#格式化输出
indent_output: true
#忽略无法转换的对象
fail_on_empty_beans: false
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
#格式化输出
indent_output: true
#忽略无法转换的对象
fail_on_empty_beans: false
properties配置
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.jackson.serialization.indent_output=true
spring.jackson.serialization.fail_on_empty_beans=false
spring.jackson.time-zone=GMT+8
spring.jackson.serialization.indent_output=true
spring.jackson.serialization.fail_on_empty_beans=false
https://blog.51cto.com/u_3664660/3213798
查询mysql二进制日志工具包
工具包maven依赖
<dependency>
<groupId>com.github.shyiko</groupId>
<artifactId>mysql-binlog-connector-java</artifactId>
<version>0.21.0</version>
</dependency>
<groupId>com.github.shyiko</groupId>
<artifactId>mysql-binlog-connector-java</artifactId>
<version>0.21.0</version>
</dependency>
demo代码
public static void main(String[] args) {
BinaryLogClient client = new BinaryLogClient("127.0.0.1", 3306, "root", "123456");
client.setServerId(2);
client.registerEventListener(event -> {
EventData data = event.getData();
if (data instanceof TableMapEventData) {
System.out.println("Table:");
TableMapEventData tableMapEventData = (TableMapEventData) data;
System.out.println(tableMapEventData.getTableId()+": ["+tableMapEventData.getDatabase() + "-" + tableMapEventData.getTable()+"]");
}
if (data instanceof UpdateRowsEventData) {
System.out.println("Update:");
System.out.println(data.toString());
} else if (data instanceof WriteRowsEventData) {
System.out.println("Insert:");
System.out.println(data.toString());
} else if (data instanceof DeleteRowsEventData) {
System.out.println("Delete:");
System.out.println(data.toString());
}
});
try {
client.connect();
} catch (IOException e) {
e.printStackTrace();
}
}
BinaryLogClient client = new BinaryLogClient("127.0.0.1", 3306, "root", "123456");
client.setServerId(2);
client.registerEventListener(event -> {
EventData data = event.getData();
if (data instanceof TableMapEventData) {
System.out.println("Table:");
TableMapEventData tableMapEventData = (TableMapEventData) data;
System.out.println(tableMapEventData.getTableId()+": ["+tableMapEventData.getDatabase() + "-" + tableMapEventData.getTable()+"]");
}
if (data instanceof UpdateRowsEventData) {
System.out.println("Update:");
System.out.println(data.toString());
} else if (data instanceof WriteRowsEventData) {
System.out.println("Insert:");
System.out.println(data.toString());
} else if (data instanceof DeleteRowsEventData) {
System.out.println("Delete:");
System.out.println(data.toString());
}
});
try {
client.connect();
} catch (IOException e) {
e.printStackTrace();
}
}
系统相关
获取操作系统信息
方式1:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4</version>
</dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4</version>
</dependency>
文档
https://blog.csdn.net/u014295903/article/details/125557810
方式2:
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.20</version>
</dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.20</version>
</dependency>
参考文档
https://www.uoften.com/article/213906.html
获取JVM运行时信息
方式1
java.management工具包
参考文档
https://blog.csdn.net/qq_17522211/article/details/117552950
demo
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
map.put("堆内存", memorymbean.getHeapMemoryUsage());
map.put("方法区内存", memorymbean.getNonHeapMemoryUsage());
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
map.put("运行时设置的JVM参数", inputArgs);
// 总的内存量
long totle = Runtime.getRuntime().totalMemory();
// 空闲的内存量
long free = Runtime.getRuntime().freeMemory();
// 最大的内存量
long max = Runtime.getRuntime().maxMemory();
Map<String, Long> params = new HashMap<>();
params.put("totalMemory", totle);
params.put("freeMemory", free);
params.put("maxMemory", max);
map.put("运行时内存情况", params);
map.put("堆内存", memorymbean.getHeapMemoryUsage());
map.put("方法区内存", memorymbean.getNonHeapMemoryUsage());
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
map.put("运行时设置的JVM参数", inputArgs);
// 总的内存量
long totle = Runtime.getRuntime().totalMemory();
// 空闲的内存量
long free = Runtime.getRuntime().freeMemory();
// 最大的内存量
long max = Runtime.getRuntime().maxMemory();
Map<String, Long> params = new HashMap<>();
params.put("totalMemory", totle);
params.put("freeMemory", free);
params.put("maxMemory", max);
map.put("运行时内存情况", params);
// 堆内存
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
// 方法区内存
MemoryUsage heapMemoryUsage = memorymbean.getHeapMemoryUsage();
long heapInit = heapMemoryUsage.getInit() / conversion;
long heapCommitted = heapMemoryUsage.getCommitted() / conversion;
long heapUsed = heapMemoryUsage.getUsed() / conversion;
long heapMax = heapMemoryUsage.getMax() / conversion;
Map<String, Long> heapMap = new HashMap<>();
heapMap.put("init", heapInit);
heapMap.put("committed", heapCommitted);
heapMap.put("used", heapUsed);
heapMap.put("max", heapMax);
map.put("堆内存", heapMap);
MemoryUsage nonHeapMemoryUsage = memorymbean.getNonHeapMemoryUsage();
long noHeapInit = nonHeapMemoryUsage.getInit() / conversion;
long noHeapCommitted = nonHeapMemoryUsage.getCommitted() / conversion;
long noHeapUsed = nonHeapMemoryUsage.getUsed() / conversion;
long noHeapMax = nonHeapMemoryUsage.getMax() / conversion;
Map<String, Long> noHeapMap = new HashMap<>();
noHeapMap.put("init", noHeapInit);
noHeapMap.put("committed", noHeapCommitted);
noHeapMap.put("used", noHeapUsed);
noHeapMap.put("max", noHeapMax);
map.put("方法区内存", noHeapMap);
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
map.put("运行时设置的JVM参数", inputArgs);
// 总的内存量
long totle = Runtime.getRuntime().totalMemory();
// 空闲的内存量
long free = Runtime.getRuntime().freeMemory();
// 最大的内存量
long max = Runtime.getRuntime().maxMemory();
Map<String, Long> params = new HashMap<>();
params.put("totalMemory", totle / conversion);
params.put("freeMemory", free / conversion);
params.put("maxMemory", max / conversion);
map.put("运行时内存情况", params);
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
// 方法区内存
MemoryUsage heapMemoryUsage = memorymbean.getHeapMemoryUsage();
long heapInit = heapMemoryUsage.getInit() / conversion;
long heapCommitted = heapMemoryUsage.getCommitted() / conversion;
long heapUsed = heapMemoryUsage.getUsed() / conversion;
long heapMax = heapMemoryUsage.getMax() / conversion;
Map<String, Long> heapMap = new HashMap<>();
heapMap.put("init", heapInit);
heapMap.put("committed", heapCommitted);
heapMap.put("used", heapUsed);
heapMap.put("max", heapMax);
map.put("堆内存", heapMap);
MemoryUsage nonHeapMemoryUsage = memorymbean.getNonHeapMemoryUsage();
long noHeapInit = nonHeapMemoryUsage.getInit() / conversion;
long noHeapCommitted = nonHeapMemoryUsage.getCommitted() / conversion;
long noHeapUsed = nonHeapMemoryUsage.getUsed() / conversion;
long noHeapMax = nonHeapMemoryUsage.getMax() / conversion;
Map<String, Long> noHeapMap = new HashMap<>();
noHeapMap.put("init", noHeapInit);
noHeapMap.put("committed", noHeapCommitted);
noHeapMap.put("used", noHeapUsed);
noHeapMap.put("max", noHeapMax);
map.put("方法区内存", noHeapMap);
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
map.put("运行时设置的JVM参数", inputArgs);
// 总的内存量
long totle = Runtime.getRuntime().totalMemory();
// 空闲的内存量
long free = Runtime.getRuntime().freeMemory();
// 最大的内存量
long max = Runtime.getRuntime().maxMemory();
Map<String, Long> params = new HashMap<>();
params.put("totalMemory", totle / conversion);
params.put("freeMemory", free / conversion);
params.put("maxMemory", max / conversion);
map.put("运行时内存情况", params);
mybatis-plus
自带雪花算法
实现逻辑
DefaultIdentifierGenerator类
根据时间戳,数据中心,机器标识部分,序列化,做位于运算
图示
子主题
乐观锁插件
自测工具
jmockit
依赖
maven依赖
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.36</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit-coverage</artifactId>
<version>1.23</version>
<scope>test</scope>
</dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.36</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit-coverage</artifactId>
<version>1.23</version>
<scope>test</scope>
</dependency>
gradle依赖
org.jmockit:jmockit:1.40
使用场景
1、因为bean注入不能注入导致单元测试无法启动
mock spring bean对象
mock spring bean调用的接口的数据,
2、如果远程接口无法调用通,但是需要测试流程
代码demo
注解说明
@Tested
@Tested
private TestDemo testDemo;
private TestDemo testDemo;
调用的时候,会去testDemo下面的接口,然后调用接口的实际实现
@Injectable
@Injectable
private TestDemo testDemo;
private TestDemo testDemo;
调用的时候,不回去调用testDemo下面接口的实现,但是可以mock testDemo 接口的返回值
方法执行时长监听
aop配置方法,打印方法前后的时间
@Aspect
@Component
public class CountTimeAspect {
@org.aspectj.lang.annotation.Pointcut("@annotation(com.homethy.social.annotation.EnableCountTime)")
public void countTime() {
}
@Around("countTime()")
public Object doAround(ProceedingJoinPoint joinPoint) {
long startTime = System.currentTimeMillis();
Object obj = joinPoint.proceed();
long endTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringTypeName();
return obj;
}
}
@Component
public class CountTimeAspect {
@org.aspectj.lang.annotation.Pointcut("@annotation(com.homethy.social.annotation.EnableCountTime)")
public void countTime() {
}
@Around("countTime()")
public Object doAround(ProceedingJoinPoint joinPoint) {
long startTime = System.currentTimeMillis();
Object obj = joinPoint.proceed();
long endTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringTypeName();
return obj;
}
}
自定义EnableCountTime标记注解
技术实现功能总结
redis
消息队列
订阅与发布
键,缓存数据的变化发送消息出来
缺点
消息的发送不是可靠,万一消息发送了,订阅方没有收到消息,那么消息会出现丢失情况
List数据结构
lpush
在链表左右边添加元素
rpop
在链表右边获取元素
Sorted-set
利用排序的set集合
发送消息的时候给消息id做递增,这个id值就是排序的sorted set集合
限流
思想就是滑动窗口数据统计
key的超时时间就是每一个滑动窗口的时间
配置
每个接口单位时间对应的最大请求个数
滑动窗口时间
也就是key的超时时间,单位时间统计的时间
步骤
1、AOP拦截每一个请求
2、获取当前接口对应的key,value值
如果key,value不存在,那么就设置对应的key,value值,超时时间
3、如果存在,就拿到value,然后value值做自增1操作
4、拿到自增1后的数据和最大请求个数对比;
超过了,就表示有多少线程在执行当前接口
线程就直接返回
未超过,就可以接着往下执行
5、执行完成后,再去把key value 对应的值自减1;
错误信息收集
框架
springBoot
没有写明扫描包路径
报错信息
Your ApplicationContext is unlikely to start due to a @ComponentScan of the default package.
解决方式
1、启动类加上@ComponentScan({"com.demo.springboot","com.demo.somethingelse"})
process finished with exit code 1
错误现象
启动自动退出
子主题
错误sql语句
@Select({"<script>",
"SELECT id,agent_id,page_id ,page_name, page_icon ,social_type,fc_advertise_permission FROM social_auth_connect "
+ "WHERE "
+ "1=1 ",
" <if test='agentId != null'> and agent_id = #{agentId} </if>",
" <if test='id != null'> and and id = #{id} </if>",
" <if test='socialTypeList !=null and socialTypeList.size() > 0 '> ",
" and social_type in " ,
"<foreach item = 'id' index = 'index' collection = 'socialTypeList' open='(' separator=',' close=')'>" ,
"#{id}" ,
"</foreach>" ,
"</if>",
" <if test='pageId != null'> and page_id =#{pageId} </if>",
" <if test='businessId != null'> and business_id =#{businessId} </if>",
" <if test='tokenId != null'> and token_id =#{tokenId} </if>",
"</script> "})
"SELECT id,agent_id,page_id ,page_name, page_icon ,social_type,fc_advertise_permission FROM social_auth_connect "
+ "WHERE "
+ "1=1 ",
" <if test='agentId != null'> and agent_id = #{agentId} </if>",
" <if test='id != null'> and and id = #{id} </if>",
" <if test='socialTypeList !=null and socialTypeList.size() > 0 '> ",
" and social_type in " ,
"<foreach item = 'id' index = 'index' collection = 'socialTypeList' open='(' separator=',' close=')'>" ,
"#{id}" ,
"</foreach>" ,
"</if>",
" <if test='pageId != null'> and page_id =#{pageId} </if>",
" <if test='businessId != null'> and business_id =#{businessId} </if>",
" <if test='tokenId != null'> and token_id =#{tokenId} </if>",
"</script> "})
错误原因
经过排查发现mybatis-plus @Select 注解拼写的sql语句中有语法错误,启动的时候并未将这些报错信息打印到控制台,且修改了日志打印级别为debug也是无法打印这些报错信息;
错误排查步骤
通过git提交代码的日志,一次一次的从历史提交的记录中拉出分支,跑程序,看是否报错,知道有报错的时候确定是这次的提交有问题
然后从实体类开始一个类添加,启动;周而复始,直到添加的类报错了, 然后再排查这个报错类的信息,发现是@Select 注解对应的sql语句有问题。重写sql语句;
经验总结
1、有的组件和springBoot整合之后就算日志级别是debug ,有问题也可能不会吧错误打印到控制台
2、自己尝试springBoot启动的时候打印mybatis-plus日志,也无法实现;也就是有的组件,启动压根就没有日志信息
项目启动编译
编译
该文件具有错误的版本
修改项目的编译
统一一下项目的lanuage level 等级一样
代码引入了无用的类导致项目无法加载这些类
初步分析
很容易觉得这个问题是由于jar包版本的问题
觉得是maven依赖了老的版本的jar包;
最终解决
提交之前删除不必要的依赖包
启动
Fatal error compiling: java.lang.ExceptionInInitializerError: com.sun.tools.javac.code.TypeTags
项目配置Jdk版本和idea实际使用的jdk版本不一致
将idea的jdk设置成和当前项目一致
参考文档
https://blog.csdn.net/weixin_44011190/article/details/122982001
生产环境经验总结
快速回滚
使用阿波罗配置,通过配置开关来做新老逻辑的分支处理
问题排查步骤
1、问题的根本原因
方式
cat调用链路
owl链路
Hlog日志
2、问题的影响范围
1、对下游链路的影响
2、确认影响的功能点
1、只做数据展示
2、根据数据做更新
3、确认影响的功能范围
具体的接口
ES数据
数据分析
4、确认影响的用户群体和用户数量
1、用户群体
C端用户
B端用户
2、用户数量
评估影响的用户数量级
3、问题的修改方式
确认修改方式
需要根据问题的影响范围来确认修改方式
如果影响小,可以下个迭代,或者以后上线
如果影响大,需要立即修改上线;
生产问题实战
场景问题
1、线上发送方的bug导致消费方数据被删除
正确处理步骤
先止损
首先针对代码立即做回滚操作,止损,防止被删除的数据量增多
确定影响的数据范围
首次上线和回滚上线中件的时间,已经对应的操作数据量
针对具体的问题做针对性处理
1、如果有相关的删除日志信息,可以通过删除的日志信息确定被误删的数据;
2、如果没有日志信息,通过两次上线的时间段中件的对数据库的delete操作对应的binlog日志,看对应的删除数据
问题点
MQ生产方对需要删除的信息的判断出现逻辑问题,导致不应该被删除的信息作为需要被删除的信息发送出来了。
消费方直接将这些信息做了硬删除操作
事故处理流程
1、生产方发现了问题,然后在控制台中断了消息的发送
2、生产方修改了bug,然后即时上线,在控制台打开了消息发送
3、消费方根据在代码中打印的消息体信息,获取到可能被删除的信息
4、从生产上捞取对应的标记日志信息,输出到文件中
1、确定日志存在哪些日志文件中
1、由于处理问题的时间距离问题的发生已经过去了一段时间,而且日志数据都是根据时间滚动的,所以日志数据可能在多个日志文件中,
2、反向根据日志文件的时间,查询标记日志的信息,都存在哪些日志中,一直到查询不到相关的日志文件了
3、确定了有标记日志信息的日志文件后创建对应的存储标记日志信息的文件
4、通过相关的命令将搜索出来的标记日志文件信息输出到刚刚创建的文件中
5、获取生产环境的文件
1、通过文件上传命令,将所有的刚刚创建的文件上传到文件中心
2、通过下载命令将文件下载到项目中
6、通过java程序解析日志文件,获取相关的id信息
1、书写java程序来解析日志文件中对应的核心信息id
2、获取到每一个日志文件中的关键信息id;
7、将id信息做消费方的数据恢复
将id信息做消费方的数据恢复给发送方确定哪些数据是在本次事故中实际需要做删除的id
消费方得到实际需要删除的id信息
从亚马逊mysql云服务中拉取一个昨日版本的mysql实例
用实际需要做删除的id信息去昨日版本的mysql实例中查询被删除的数据;
获取这些数据对应的insert语句
将insert语句放在当前最新版本的mysql实例上执行,然后数据就恢复了
再删除从亚马逊云mysql服务的昨日实例
事故处理流程存在的问题
1、出现了问题,没有即时汇报相关问题,导致问题的影响范围扩大
处理步骤2中不应该那么早打开消息发送,导致在后续的排查问题的数据范围变大,
如果处理完再打开,需要处理的数据范围只有第一次上线到终端消息发送这段时间的数据
但是实际情况是处理的数据却是第一次上线到捞取日志当时的时间范围内的日志信息
2、从昨日版本的mysql实例中用删除的id来查询被删除的数据,可能数据是不完整的
极端情况下,今日版本的mysql新增了一条数据,然后实际又要被删除;这样的数据是没法做恢复的,因为这样的数据根本就不在昨日版本的实例中
此极端问题是无法避免
事故处理流程中数据恢复逻辑问题
1、开始设想是将数据恢复到上线前的版本,然后将此次实际需要被删除的数据做删除操作
此方案不可取
不可取原因
0、其实不存在所谓上线前的版本,这个版本昨日的数据版本
1、今日版本较昨日版本的增量数据,会在回滚后丢失增量数据
问题优化点
代码层面优化
1、在消费方是否可以主动去判断这个数据是否实际真的应该要被删除;
2、消费方的删除操作的时候,应该做软删除操作,而不是硬删除;
处理流程优化
1、生产方的bug解决完成之后,不要那么快打开消费,这样可以缩小问题时间,从而缩小需要排查的问题
正确处理步骤
先止损
首先针对代码立即做回滚操作,止损,防止被删除的数据量增多
确定影响的数据范围
首次上线和回滚上线中件的时间,已经对应的操作数据量
针对具体的问题做针对性处理
1、如果有相关的删除日志信息,可以通过删除的日志信息确定被误删的数据;
2、如果没有日志信息,通过两次上线的时间段中件的对数据库的delete操作对应的binlog日志,看对应的删除数据
生产力提高
测试
postman
群组
在群组中写入自己的测试用例,可以提高群组中其他人的自测效率
限制
团队只能共享25个请求
自测
快速mock数据
使用快速mock数据包
导包
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>0.17.2</version>
</dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>0.17.2</version>
</dependency>
给实体类直接赋值
联调
非必要使用的方式
如果开发来不及,需要和前端联调,可以现在后端mock数据,将数据返回给前端,先让前端联调起来,后续再写相关业务逻辑
代码
代码生成器
mybatis插件
mybatis-自动化插件,自动生成实体,控制层,service层,dao层,xml文件;
统一处理
控制层统一处理
springBoot实现
实现代码
@RestControllerAdvice
public class ControllerExceptionHandleAdvice {
private final static Logger logger = LoggerFactory.getLogger(ControllerExceptionHandleAdvice.class);
@ExceptionHandler
public ResultEntity handler(HttpServletRequest req, HttpServletResponse res, Exception e) {
logger.info("Restful Http请求发生异常...");
if (res.getStatus() == HttpStatus.BAD_REQUEST.value()) {
logger.info("修改返回状态值为200");
res.setStatus(HttpStatus.OK.value());
}
if (e instanceof NullPointerException) {
logger.error("代码00:" + e.getMessage(), e);
return ResultEntity.fail("发生空指针异常");
} else if (e instanceof IllegalArgumentException) {
logger.error("代码01:" + e.getMessage(), e);
return ResultEntity.fail("请求参数类型不匹配");
} else if (e instanceof SQLException) {
logger.error("代码02:" + e.getMessage(), e);
return ResultEntity.fail("数据库访问异常");
} else {
logger.error("代码99:" + e.getMessage(), e);
return ResultEntity.fail("服务器代码发生异常,请联系管理员");
}
}
}
public class ControllerExceptionHandleAdvice {
private final static Logger logger = LoggerFactory.getLogger(ControllerExceptionHandleAdvice.class);
@ExceptionHandler
public ResultEntity handler(HttpServletRequest req, HttpServletResponse res, Exception e) {
logger.info("Restful Http请求发生异常...");
if (res.getStatus() == HttpStatus.BAD_REQUEST.value()) {
logger.info("修改返回状态值为200");
res.setStatus(HttpStatus.OK.value());
}
if (e instanceof NullPointerException) {
logger.error("代码00:" + e.getMessage(), e);
return ResultEntity.fail("发生空指针异常");
} else if (e instanceof IllegalArgumentException) {
logger.error("代码01:" + e.getMessage(), e);
return ResultEntity.fail("请求参数类型不匹配");
} else if (e instanceof SQLException) {
logger.error("代码02:" + e.getMessage(), e);
return ResultEntity.fail("数据库访问异常");
} else {
logger.error("代码99:" + e.getMessage(), e);
return ResultEntity.fail("服务器代码发生异常,请联系管理员");
}
}
}
优点
控制层不需要做过多的try catch处理,简化代码
缺点
需要根据异常类型才能知道具体的异常信息是什么
改进点
异常定义的时候定义相应状态码,和响应信息字段;
异常抛出的时候,构造相应状态码和响应信息;
需要很多自定义的异常,在同一处理的时候根据异常的类型针对性处理;
工作习惯
整体规划
针对小型修改需求
1、考虑修改成本,耗时,影响范围
2、考虑后续影响
3、考虑修改后严谨的自测
1、修改后的流程自测
2、影响到的其他点的自测
交叉工作
什么是交叉工作
边测试边开发
提高效率方式
1、针对不影响流程的问题,不需要及时解决,上午或者下午5点左右抽时间统一解决;
2、针对影响流程问题,需要及时放下当前任务,解决问题;
良好的工作习惯
代码
提交代码前保证自己本地跑通才能提交代码
工作流程
技术方案设计
1、功能梳理
1、现有逻辑流程图
2、如果有必要带上时序图
2、接口整理
1、对前端,移动端接口
需要提供对应详细接口文档
2、内部接口
3、定时任务
3、表结构设计
1、库,表修改的详细字段信息
2、详细库表sql语句
4、项目设计到服务
1、服务名称,分支,大体改动点
2、包括微服务依赖版本号
3、包括前端,移动端改动
5、配置文件
1、包括动态配置文件
上线流程
1、服务正式版本打包
2、动态配置文件配置
3、数据库sql语句执行
4、数据库初始化脚本执行
5、按照底层-高层服务顺序发布
沟通
线上问题沟通
沟通的重点
1、影响范围
2、影响的数据量
3、重新上线所需时间;
4、修改的代码改动量多大;
与上级领导沟通
即时汇报工作情况
1.影响自己工作进度的事情;
2.影响自己工作任务量的事情;
3.对自己工作造成干扰的问题;
4.项目在什么时间点上线;
5.需要和领导商量的事情,自己需要先有自己有的想法,先汇报自己的想法,然后再询问领导
这个事情是否合理,合理就按照自己的做;
这个事情是否合理,合理就按照自己的做;
1、汇报结果一定是一个肯定或者否定的结果;不要出现模棱两可
2、给上级写功能,交付出去的一定是一个在测试环境完全测试好了的功能;
3、上级看问题需要看一个整体
多人合作的任务,应该是多人合并任务报告之后再向领导汇报已经完成;
任务需求沟通
1、自己复述一下领导要交代的事情,和领导沟通这个我自己对这个事情的理解,看我理解的是不是领导要我做的事情是一致的;
2.询问一下,领导要做这个事情的,目的;
3.做这件事先考虑到会出现哪些意外的事情;
4、在做这件事情中间出现什么事情,需要向领导汇报;出现什么事情,自己可以自己做决定;
所以从领导的角度出发:就是这个事情该怎么做,做成什么程度,为啥要这么做,做这个事情的目的是什么,还有没有其他的方式做这个,出现什么问题需要向你汇报,出现什么事情,我可以自己决定。
1、自己先完全梳理一遍,整理出相关问题,然后再拿着相关问题咨询
切忌:啥都不看,直接上来就问。
意外情况需要汇报
需求
不明白的需求,以及产品需求的改变(让产品发邮件);
其他组的业务,或者是并行的任务给自己造成困扰,这个也是要即使汇报的;
进度情况
1.自己的工作进度情况,如果一个事情感觉时间不充足了,时间不够了需要向领导汇报(如果有延期,需要在jira任务上做相应的备注);
项目设计
项目的设计问题,需要向领导沟通这个确认这个,让后再做,否则出问题了,领导又会觉得这是下属的问题;
团队协助
需要其他组的人员协助做一些事情,这个需要向领导汇报,让领导去沟通这个协调问题;
遇到苦难
工作中遇到的难以解决的问题(技术问题,bug问题),需要领导领导帮忙解决的,这个也是需要主动沟通的;
如果对领导给自己安排的任务,自己觉得有问题,或者自己觉得能力不够,直接和领导说我做个有困难,能不能安排其他的任务;
领到不清楚任务
即时沟通任务相关情况
工作汇报谈话
子主题
与测试沟通
关于prd问题
如果自己对prd有疑问,那么就直接在prd下面的文案里面说明自己对prd有哪些疑问,然后再和产品沟通,让产品在疑问的地方做出相应的回复。
面对不是自己bug
1.如果prd里面没有说明这个,开发是可以不认这个bug;
2.如果这些出bug的代码不是现在开发开发中的bug,或者这个是很久远的问题bug,我们也只是帮忙看看并没有绝对的义务去修改这个。
是在要让测试提bug,那么就让测试按照流程去提bug;
是在要让测试提bug,那么就让测试按照流程去提bug;
与产品沟通
新增需求
1.如果产品需要修改需求,或者新增需求,这个需要产品发邮件通知所有的人,否则不做;
遇到突发问题
1.为啥需要把这个问题交给我?
2.他在解决这个事情的时候,遇到了什么样的问题?
3.解决这个问题的人,已经尝试过了什么样的解决方式去解决这些问题?
4.他对这个问题又什么样的建议还有猜想?
2.他在解决这个事情的时候,遇到了什么样的问题?
3.解决这个问题的人,已经尝试过了什么样的解决方式去解决这些问题?
4.他对这个问题又什么样的建议还有猜想?
甩锅规避责任
1.有问题(技术问题,卡流程问题,),及时向上反馈;
.自己把自己应该做的工作做到位,如果还有问题,那就主要怎人不在自己了;
外部依赖
删除依赖
一定需要沟通好这个删除的情况
硬删除
软删除
其他情况删除
0 条评论
下一页