- 处理异常程序需要处理三个问题
- 哪里发生异常?
- 谁来处理异常?
- 如何处理异常?
- 哪里发生异常: 代码分为
- 稳定代码
- 非稳定代码: 这里是异常捕获针对的地方
- 谁来处理异常
- throw: 方法内部抛出具体异常类对象的关键字
- throws: 用在方法signature上, 表示方法调用者可以通过此方法声明向上抛出异常对象
- 当前被捕获的异常是否需要自己处理
3.1 直接捕获异常并做相应处理: 在当前方法的处理能力范围之内, 且没有必要对外透出
3.2 向上抛出, 由上层方法或者框架来处理: 在当前方法的处理能力范围之外, 有必要对外透出
- 如何处理异常
- 无论采用哪种方式处理异常, 严禁捕获异常后什么都不做或打印一行日志了事
1.1 方法内部处理异常: 根据不同的业务场景进行定制处理, 如重试, 回滚等操作
1.2 向上抛出异常: 需要在异常对象中添加上下文参数, 局部变量, 运行环境等信息, 这样有助于排查问题
1. 异常分类
- 异常分类结构
所有异常都是Throwable的子类
1 Error
2 Exception
- exception
- checked异常: 需要在代码中显式处理的异常, 否则会编译报错. 如果自行处理则可以在当前方法中捕获异常, 如果无法处理, 则继续向调用方抛出异常对象
1.1 无能为力型, 引起注意型: 针对此类异常, 程序无法处理, 如字段超长等导致的SQLException, 即使做再多的重试对解决异常也没有任何帮助
1.2 力所能及, 坦然处置型: 如发生未授权异常, 程序可跳转至权限申请页面- unchecked异常: 运行时异常, 它们都继承自RuntimeException, 不需要程序进行显示的捕捉和处理
2.1 可预测异常: 如IndexOutOfBoundsException, NullPointerException. 基于对代码的性能和稳定性要求, 此类异常不应该被产生或者抛出, 而应该提前做好边界检查, 空指针判断等处理
2.2 需捕捉异常: 如RPC调用
产生的远程服务超时异常, 此类异常是客户端必须显示处理的异常, 不能因服务端的异常导致客户端不可用, 此事处理方案可以使重试或者降级处理
2.3 可透出异常: 主要指框架或系统产生的且会自行处理的异常, 而程序无需关心.
- 异常处理
根据当前场景自定义具有业务含义的异常, 为了避免异常泛滥, 可以优先使用业界或者团队已定义过的异常
2. try代码块
try代码块与锁的关系
lock方法可能会抛出unchecked异常, 如果把加锁的方法放在try代码块中, 必然触发finally中的unlock方法执行. 虽然是因为加锁失败而造成程序中断的, 但是真正加锁失败的原因可能会被后者覆盖. 所以要在try代码块之前调用lock()方法
3. 异常的抛与接
在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用
- 推荐异常抛
- 对外提供的开放接口: 使用错误码
- 公司内部跨应用远程服务调用: 优先考虑使用Result对象来分装错误码, 错误描述信息
- 应用内部: 直接抛出异常对象
- 为什么使用Result对象封装异常信息
- 如果使用抛异常的返回方式: (1) 一旦调用方没有捕获, 就会产生运行时错误, 导致程序中断; (2) 如果抛出异常中不添加栈信息, 只是new自定义异常并加入自定义的错误信息, 对于调用端解决问题的帮助不会太大. (3) 如果加了栈信息, 在频繁调用出错的情况下, 信息序列化和传输的性能损耗也是问题
4. 日志
- 记录应用日志主要原因
- 记录操作轨迹: 可以数据化地分析用户偏好, 有助于优化业务逻辑, 为用户提供个性化的服务; 例如, 通过access.log记录用户的操作频度和跳转链接, 有助于分析用户的后续行为
- 监控系统运行状况: 全面有效的日志系统有助于建立完善的应用监控体系, 由此工程师可以实时监控系统运行状况, 及时预警, 避免故障发生
- 回溯系统故障
- 监控系统运行状况
- 服务器使用状态: 内存, CPU的使用情况
- 应用运行情况: 响应时间, QPS等交互状态
- 应用错误信息: 空指针, SQL异常等的监控
- 举例: 在CPU使用率大于60%, 四核服务器中load大于4时发出报警, 提醒工程师及时处理, 避免发生故障
4.1 日志规范
- 推荐的日志命名方式
appName_logType_logName.log
- logType为日志类型, 推荐分类: (1) stats, (2). monitor, (3). visit
- logName为日志描述
- 日志文件需要保存多久
至少保存15天, 可以根据日志文件的重要程度, 文件大小以及磁盘空间再自行延长保存时间
- 日志级别, 按照重要程度由低到高排序
- DEBUG级别日志: 记录对调试程序有帮助的信息
- INFO级别日志: 用来记录程序运行现场
- WARN级别日志: 可以用来记录程序运行现场, 但是更偏向于表名此处有出现了潜在错误的可能
- ERROR级别日志: 当前程序运行发生了错误, 需要被关注.
- FATAL级别日志: 当前程序运行出现了严重的错误时间, 并且将会导致应用程序中断
4.1.1 日志处理方式
不同级别的日志优先级和重要性不同, 因此在打印日志时针对不同的日志级别要有不同的日志处理方式
- 预先判断日志级别
DEBUG, INFO级别的日志: 使用条件输出或者使用占位符的方式打印
原因: 避免系统资源的浪费
- 避免无效日志打印
- 生产环境禁止输出DEBUG日志且有选择地输出INFO日志
- 使用INFO,WARN级别来记录业务行为信息时 , 一定要控制日志输出量, 以免磁盘空间不足.
- 为日志文件设置合理的生命周期, 及时清理过期的日志
- 避免重复打印, 务必在日志配置文件中设置additivity=false
- 区别对待错误日志
ERROR级别: 只记录系统逻辑错误, 异常或者违反重要的业务规则
WARN级别: 除了ERROR级别的都归入WARN级别
- 保证记录内容完整
- 记录异常时一定要输出异常堆栈
- 日志中如果输出对象实例, 要确保实例类重写了toString方法, 否则只会输出对象的hashCode值, 没有实际意义.
- 记录日志时思考的问题
- 日志是否有人看
- 看到这条日志能做什么
- 能不能提升问题排查效率
4.2 日志框架
- 日志框架
日志门面
日志适配器
日志库
- 门面设计模式
- logback
日志库, 是log4j的同一个作者, 本身实现了log4j的接口, 是log4j的升级版
- 日志门面适配器
门面规范是后来提出的, 例如log4j没有实现, 就找个门面适配
- 日志库适配器
老工程直接使用了日志库来打印, 日志库到日志门面的适配
- logger被定义为static变量
因为这个logger与当前类绑定, 避免每次都new一个新对象, 造成资源浪费.
- 防止日志冲突
避免依赖的jar包, 间接地引入其他日志库