面试题答案
一键面试架构设计
- 异常传播机制:通过返回值或者 channel 来传播异常。在 Go 中,通常函数返回 error 类型的值来表示异常情况,对于 goroutine 间通信,可以使用 channel 来传递异常。
- 本地处理与日志记录:每个 goroutine 内部有一个异常处理逻辑,当捕获到需要本地处理的异常时,记录日志并继续执行或者优雅地结束当前 goroutine。
- 稳定性与性能:为避免过多的异常处理开销影响性能,对于频繁出现的异常,可以采用异步日志记录等方式,减少对主线程的阻塞。同时,在异常传播过程中,尽量减少不必要的数据拷贝。
关键数据结构
- Error类型:Go 语言内置的 error 类型用于表示异常情况。可以自定义 error 类型,以便区分不同类型的异常。
type CustomError struct {
ErrMsg string
}
func (ce CustomError) Error() string {
return ce.ErrMsg
}
- 日志记录结构:可以使用标准库的
log
包或者第三方日志库如zap
。例如,使用zap
时,定义一个全局的 logger。
var logger *zap.Logger
func init() {
var err error
logger, err = zap.NewProduction()
if err != nil {
panic(err)
}
defer logger.Sync()
}
处理流程
- 本地处理:每个 goroutine 在执行核心逻辑时,使用
defer
和recover
来捕获未处理的 panic 异常,并进行本地处理。
func worker() {
defer func() {
if r := recover(); r != nil {
logger.Error("Panic occurred", zap.Any("reason", r))
}
}()
// 核心业务逻辑
// 可能会触发异常的代码
}
- 异常传播:对于需要向上层传播的异常,通过函数返回值传递 error。上层调用者检查 error 并决定如何处理。
func subTask() error {
// 执行任务
if someCondition {
return CustomError{"Sub task failed"}
}
return nil
}
func mainTask() error {
err := subTask()
if err != nil {
// 可以选择继续向上传播
return err
}
return nil
}
代码片段汇总
package main
import (
"fmt"
"go.uber.org/zap"
)
type CustomError struct {
ErrMsg string
}
func (ce CustomError) Error() string {
return ce.ErrMsg
}
var logger *zap.Logger
func init() {
var err error
logger, err = zap.NewProduction()
if err != nil {
panic(err)
}
defer logger.Sync()
}
func subTask() error {
// 执行任务
if someCondition {
return CustomError{"Sub task failed"}
}
return nil
}
func mainTask() error {
err := subTask()
if err != nil {
// 可以选择继续向上传播
return err
}
return nil
}
func worker() {
defer func() {
if r := recover(); r != nil {
logger.Error("Panic occurred", zap.Any("reason", r))
}
}()
// 核心业务逻辑
// 可能会触发异常的代码
}
通过上述架构设计、数据结构和处理流程,可以实现一个在多层嵌套调用且高度并发的 Go 程序中的通用异常捕获和处理框架。在实际应用中,还可以根据具体需求进一步优化和扩展,例如增加分布式追踪等功能。