Posted in

【仅开放72小时】Go简历诊断服务(附赠:Go项目Dockerfile+Makefile作为能力佐证的写法)

第一章:Go程序员简历的核心定位与价值主张

Go程序员的简历不是技术栈的罗列清单,而是面向目标岗位的价值契约——它必须清晰回答三个关键问题:你能用Go解决什么真实业务问题?你对Go生态的理解深度是否匹配团队工程成熟度?你在并发模型、内存管理、工具链优化等核心维度上能否降低系统长期维护成本?

明确技术角色锚点

避免泛泛而称“全栈”或“后端开发”,应聚焦Go语言在系统中的实际职责边界。例如:

  • 云原生基础设施工程师:主导Kubernetes Operator开发,使用controller-runtime框架实现CRD生命周期自动化;
  • 高并发服务架构师:基于Goroutine池(如goflow)与sync.Pool定制化优化,将订单服务P99延迟压降至85ms以内;
  • CLI工具链开发者:使用spf13/cobra构建企业级命令行套件,集成viper配置热加载与logrus结构化日志,支撑200+内部开发者日均调用超1.2万次。

突出Go特有工程实践

仅写“熟悉goroutine”缺乏说服力,需体现对语言本质的掌控:

// 示例:展示对调度器与内存模型的实操理解  
func handleRequest(w http.ResponseWriter, r *http.Request) {
    // 使用context.WithTimeout显式约束goroutine生命周期,防止goroutine泄漏  
    ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
    defer cancel() // 关键:确保cancel调用,避免上下文泄漏  

    // 避免在HTTP handler中直接启动无管控goroutine  
    // ✅ 正确:通过worker pool分发任务,配合semaphore限流  
    if !sem.TryAcquire(1) {
        http.Error(w, "Too many requests", http.StatusTooManyRequests)
        return
    }
    defer sem.Release(1)
    go processAsync(ctx, w, r) // 在受控范围内异步执行
}

匹配团队技术负债现状

在简历“项目经验”部分,用数据锚定技术决策价值: 项目类型 Go相关改进点 可量化结果
微服务迁移 将Python Flask服务重构成Go Gin微服务 内存占用下降62%,QPS提升3.8倍
日志平台升级 基于Zap构建零分配日志管道 日志写入吞吐达120万条/秒,GC暂停减少90%
CI/CD流水线 使用Go编写自定义Tekton Task 构建步骤执行时间缩短40%,错误定位效率提升70%

第二章:Go技术栈能力的结构化呈现

2.1 Go语言核心机制理解与代码实践(GC、goroutine调度、interface底层)

GC三色标记与写屏障

Go 1.19+ 默认启用混合写屏障(hybrid write barrier),在对象赋值时触发,确保被引用对象不被过早回收。关键在于堆对象写操作前插入屏障指令,维持“黑色不指向白色”的不变量。

// 示例:触发写屏障的典型场景
var global *int
func f() {
    x := 42
    global = &x // 此处触发写屏障:标记*x为灰色,并入扫描队列
}

global = &x 触发写屏障,将栈上新分配的 x 地址写入全局指针时,运行时插入屏障逻辑,防止 x 在当前GC周期被错误回收。

goroutine调度器核心角色

  • G(goroutine):用户协程,轻量级执行单元
  • M(machine):OS线程,绑定P执行G
  • P(processor):逻辑处理器,持有本地运行队列和内存缓存
组件 职责 生命周期
G 执行函数栈、状态(waiting/running) 创建→运行→休眠/结束
P 调度上下文、mcache、runq 启动时固定数量(GOMAXPROCS)
M 系统线程,执行G 可创建/销毁,受runtime.LockOSThread()影响

interface底层结构

空接口 interface{} 底层为 eface 结构体,含类型指针与数据指针:

type eface struct {
    _type *_type // 类型元信息(如 int, *string)
    data  unsafe.Pointer // 指向实际值(栈/堆地址)
}

当 var i interface{} = 42 时,_type 指向 int 的类型描述符,data 指向复制的整数值(非引用),体现值拷贝语义。

graph TD A[goroutine创建] –> B[G入P本地队列] B –> C{P有空闲M?} C –>|是| D[M执行G] C –>|否| E[唤醒或新建M] D –> F[G阻塞?] F –>|是| G[转入netpoll或syscall等待] F –>|否| B

2.2 高并发系统设计能力佐证(基于channel+select的限流/熔断器实现)

核心设计思想

利用 Go 的 channel 非阻塞特性与 select 的多路复用机制,构建轻量级、无锁的限流与熔断协同模型。无需依赖第三方库,天然适配 goroutine 高并发场景。

限流器实现(令牌桶语义)

type RateLimiter struct {
    tokenChan chan struct{}
    refillDur time.Duration
}

func NewRateLimiter(capacity int, rps int) *RateLimiter {
    ch := make(chan struct{}, capacity)
    lim := &RateLimiter{tokenChan: ch, refillDur: time.Second / time.Duration(rps)}
    // 启动后台填充协程(非阻塞预填充)
    go func() {
        ticker := time.NewTicker(lim.refillDur)
        defer ticker.Stop()
        for range ticker.C {
            select {
            case ch <- struct{}{}:
            default: // 已满,跳过
            }
        }
    }()
    return lim
}

func (l *RateLimiter) Allow() bool {
    select {
    case <-l.tokenChan:
        return true
    default:
        return false
    }
}

逻辑分析:tokenChan 为带缓冲 channel,容量即桶大小;Allow() 使用 select + default 实现零延迟非阻塞获取。后台 ticker 按 RPS 匀速注入令牌,default 分支确保瞬时超载时快速失败,避免排队等待。

熔断器状态协同机制

状态 允许请求 触发条件 恢复方式
Closed ✅ 连续成功 ≥ threshold 自动计时切换
Open ❌ 错误率 > 50% 且窗口内≥10次调用 经过 sleepWindow 后转 Half-Open
Half-Open ⚠️(限1) Open 状态超时后首次试探 成功则 Closed,失败重置 Open

熔断+限流联合控制流程

graph TD
    A[请求进入] --> B{熔断器状态?}
    B -- Closed --> C[尝试获取令牌]
    B -- Open --> D[立即返回熔断错误]
    B -- Half-Open --> E[允许1个探测请求]
    C -- 获取成功 --> F[执行业务]
    C -- 获取失败 --> G[返回限流错误]
    F -- 失败率上升 --> H[触发熔断]
    E -- 探测成功 --> I[切换为Closed]
    E -- 探测失败 --> J[重置为Open]

2.3 Go工程化能力落地(go mod依赖管理+vendor策略+版本兼容性处理)

依赖声明与最小版本选择

go.mod 文件是模块元数据的核心载体,通过 require 显式声明依赖及语义化版本:

module github.com/example/app

go 1.21

require (
    github.com/go-sql-driver/mysql v1.7.1 // 精确锁定主版本
    golang.org/x/net v0.14.0                // 允许自动升级补丁/小版本
)

go mod tidy 自动解析最小可行版本组合,避免隐式升级引入不兼容变更;// indirect 标记表示该依赖未被直接引用,仅由其他模块引入。

vendor 策略:可重现构建的基石

启用 vendor 后,所有依赖副本固化至 ./vendor/ 目录:

go mod vendor      # 拉取并快照当前依赖树
go build -mod=vendor # 强制仅使用 vendor 内代码编译

此模式保障 CI/CD 构建环境完全隔离,规避网络波动或上游删库风险。

版本兼容性处理矩阵

场景 推荐策略 风险等级
主版本升级(v1→v2) 使用模块路径后缀 /v2 ⚠️ 高
小版本修复(v1.6→v1.7) go get -u=patch ✅ 低
私有模块替换 replace github.com/foo => ./internal/foo ✅ 安全

依赖图谱演进流程

graph TD
    A[go mod init] --> B[go get 添加依赖]
    B --> C[go mod tidy 解析最小版本]
    C --> D{是否需离线构建?}
    D -->|是| E[go mod vendor]
    D -->|否| F[CI 中启用 GOPROXY=direct]
    E --> G[go build -mod=vendor]

2.4 Go可观测性建设实操(OpenTelemetry集成+自定义metric埋点+trace链路验证)

OpenTelemetry SDK 初始化

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/trace"
)

func initTracer() {
    exporter, _ := otlptracehttp.New(
        otlptracehttp.WithEndpoint("localhost:4318"),
        otlptracehttp.WithInsecure(), // 开发环境禁用TLS
    )
    tp := trace.NewTracerProvider(
        trace.WithBatcher(exporter),
        trace.WithResource(resource.MustNewSchema1(
            semconv.ServiceNameKey.String("user-api"),
        )),
    )
    otel.SetTracerProvider(tp)
}

该代码初始化OTLP HTTP导出器,连接本地Collector;WithInsecure()仅用于开发,生产需启用TLS与认证;ServiceNameKey确保服务在Jaeger/Grafana中可识别。

自定义业务Metric埋点

import "go.opentelemetry.io/otel/metric"

var (
    reqCounter = meter.MustInt64Counter("http.requests.total",
        metric.WithDescription("Total number of HTTP requests"),
    )
)

func handleUserRequest(w http.ResponseWriter, r *http.Request) {
    reqCounter.Add(r.Context(), 1,
        metric.WithAttributeSet(attribute.NewSet(
            attribute.String("method", r.Method),
            attribute.String("status_code", strconv.Itoa(http.StatusOK)),
        )),
    )
}

reqCounter按请求方法与状态码打标,支持多维聚合分析;Add()在请求入口调用,保证计数原子性与上下文绑定。

Trace链路端到端验证

组件 是否参与Span生成 验证方式
Gin中间件 ✅ tracing.Middleware()
PostgreSQL ✅ pgx.Instrumentor
Redis ✅ redisotel.InstrumentClient
graph TD
    A[HTTP Request] --> B[Gin Middleware]
    B --> C[DB Query]
    C --> D[Redis Cache]
    D --> E[Response]
    B -.-> F[TraceID Propagation]
    C -.-> F
    D -.-> F

2.5 Go安全编码实践(SQL注入/XSS防御+crypto/rand安全随机数使用+HTTP头安全加固)

防御SQL注入:参数化查询是唯一正解

永远避免字符串拼接构造SQL,使用database/sql的Query/Exec参数化接口:

// ✅ 安全:预编译 + 绑定参数
err := db.QueryRow("SELECT name FROM users WHERE id = ?", userID).Scan(&name)
// ❌ 危险:字符串插值,直接触发注入
// query := "SELECT name FROM users WHERE id = " + strconv.Itoa(userID)

?占位符由驱动底层绑定,确保输入被当作数据而非SQL语句执行,彻底阻断注入路径。

XSS防护:HTML模板自动转义机制

Go标准库html/template默认启用上下文敏感转义:

t := template.Must(template.New("page").Parse(`<div>{{.Content}}</div>`))
// 输入: "<script>alert(1)</script>"
// 输出: &lt;script&gt;alert(1)&lt;/script&gt;

仅在明确需渲染HTML时使用template.HTML类型标记,避免全局禁用转义。

安全随机数:弃用math/rand,拥抱crypto/rand

b := make([]byte, 32)
_, err := rand.Read(b) // 使用操作系统熵源(/dev/urandom或CryptGenRandom)

crypto/rand.Read提供密码学安全伪随机数,适用于token、salt、密钥生成;math/rand仅用于非安全场景(如模拟)。

HTTP安全头加固(关键项)

头字段 推荐值 作用
Content-Security-Policy default-src 'self' 防XSS与数据劫持
Strict-Transport-Security max-age=31536000; includeSubDomains 强制HTTPS
X-Content-Type-Options nosniff 阻止MIME类型嗅探
graph TD
    A[HTTP响应] --> B[添加安全头]
    B --> C{是否启用HTTPS?}
    C -->|是| D[设置HSTS]
    C -->|否| E[警告并跳过]

第三章:项目经验的专业化表达范式

3.1 用Dockerfile体现架构思维(多阶段构建+最小化镜像+非root用户运行+build-arg参数化)

Dockerfile 不是脚本清单,而是系统架构的声明式蓝图。从单阶段到多阶段,本质是构建关注点与运行关注点的分离:

# 构建阶段:完整工具链
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -a -o app .

# 运行阶段:仅含二进制与必要依赖
FROM alpine:3.19
RUN addgroup -g 1001 -f appgroup && \
    adduser -S appuser -u 1001
USER appuser
COPY --from=builder --chown=appuser:appgroup /app/app /usr/local/bin/app
EXPOSE 8080
CMD ["/usr/local/bin/app"]

逻辑分析:--chown=appuser:appgroup 确保复制文件归属权正确;CGO_ENABLED=0 生成静态二进制,消除 libc 依赖;adduser -S 创建无家目录、无密码的安全非 root 用户。

关键参数化能力通过 build-arg 实现:

参数名 用途 示例值
APP_ENV 控制启动配置加载路径 production
BUILD_TIME 注入构建时间戳用于健康检查 $(date -u)

多阶段构建天然支持最小化——最终镜像仅含 /usr/local/bin/app 与 Alpine 基础运行时(≈7MB),无编译器、源码或 package manager。

3.2 用Makefile展现工程规范意识(test/bench/lint/ci-target分层设计+跨平台兼容性处理)

Makefile 不仅是构建工具,更是工程契约的载体。通过语义化目标分层,可清晰表达研发生命周期阶段:

  • make test:运行单元测试(依赖 go test 或 pytest,隔离 ./internal/)
  • make bench:执行性能基准(自动启用 -benchmem -count=3)
  • make lint:静态检查(集成 golangci-lint 或 ruff,拒绝 --fix 自动修正)
  • make ci:全链路验证(串行执行 lint → test → bench,失败即止)
# 跨平台兼容:检测 shell 类型并适配路径分隔符
ifeq ($(OS),Windows_NT)
  PATH_SEP := \\
else
  PATH_SEP := /
endif

.PHONY: test
test:
    @echo "Running tests$(PATH_SEP)..."
    go test -v ./... -timeout 30s

此段逻辑:$(OS) 是 GNU Make 内置变量,Windows 下值为 Windows_NT;PATH_SEP 抽象路径差异,避免硬编码 / 导致 Windows 失败;.PHONY 确保 test 总被执行,不依赖同名文件。

目标 触发条件 输出约束
test 开发本地验证 实时输出、超时保护
bench PR 提交前 三次采样取中位数
lint Git pre-commit 零容忍警告
graph TD
  A[make ci] --> B[make lint]
  B --> C[make test]
  C --> D[make bench]
  D --> E[生成 coverage.xml]

3.3 用Go Module依赖图说明系统复杂度管理能力(replace/direct/retract实际应用案例)

依赖图可视化与复杂度感知

go mod graph 输出可映射为 Mermaid 依赖拓扑:

graph TD
  A[app] --> B[github.com/lib/pq]
  A --> C[github.com/go-sql-driver/mysql]
  B --> D[github.com/hashicorp/errwrap@v1.0.0]
  C --> D

关键指令实战场景

  • replace:本地调试时将远程模块映射至本地分支
  • retract:声明已发布但存在严重缺陷的版本不可用
  • // indirect 标记揭示隐式依赖链深度

版本冲突解决示例

# 修复因间接依赖引入的不兼容 errwrap v1.1.0
go mod edit -replace github.com/hashicorp/errwrap=github.com/hashicorp/errwrap@v1.0.0
go mod tidy

该命令强制统一 errwrap 版本,消除多路径引入导致的类型不一致。-replace 直接重写 go.sum 中校验和,并更新 go.mod 的 replace 指令行,确保构建确定性。

第四章:技术深度与影响力的可视化呈现

4.1 开源贡献佐证(PR被mainline合并的commit分析+issue诊断闭环记录)

PR核心变更摘要

Linux kernel v6.8-rc3 合并 commit a1b2c3d 修复 ext4 inode 释放竞态:

// fs/ext4/ialloc.c: fix use-after-free in ext4_new_inode()
if (unlikely(!inode)) {
    drop_nlink(dir); // ← 新增:确保目录链接数及时更新
    goto out_unlock;
}

该补丁在 ext4_new_inode() 失败路径中补全资源清理逻辑,避免 dir 的 i_nlink 滞后导致 rmdir 挂起。

Issue诊断闭环关键节点

  • 🐞 原始 issue #12947(”ext4 rmdir hangs under high concurrency”)
  • 🔍 根因定位:ext4_new_inode() 错误路径未调用 drop_nlink()
  • ✅ 验证方式:xfs_io -f -c "pwrite 0 4k" /mnt/test && rm -rf /mnt/test 复现率从 100%→0%

补丁影响范围对比

维度 合并前 合并后
并发 rmdir 延迟 ≥5s(超时) ≤12ms(P99)
内存泄漏触发率 每 3.2k 次操作 1 次 0(持续 72h 压测)
graph TD
    A[Issue报告] --> B[复现脚本验证]
    B --> C[静态分析定位竞态点]
    C --> D[补丁开发+KASAN测试]
    D --> E[MAINTAINERS确认+CI通过]
    E --> F[mainline合并]

4.2 技术文档产出能力(godoc注释覆盖率+CLI help文案设计+API OpenAPI 3.0规范输出)

高质量技术文档是工程可维护性的基石,需三位一体协同落地。

godoc 注释:从函数签名到可执行示例

// DownloadFile downloads a remote file and saves it locally.
// It supports HTTP/HTTPS and returns error if status code >= 400.
// Example:
//   err := DownloadFile("https://example.com/data.json", "data.json")
func DownloadFile(url, filepath string) error {
    // ...
}

✅ 函数首行摘要清晰;✅ 参数与返回值隐含语义;✅ Example: 块被 go doc -examples 自动识别并渲染为可运行测试用例。

CLI help 文案设计原则

  • 使用 cobra 时,Cmd.Short 用于一级帮助摘要,Cmd.Long 支持多段 Markdown 渲染;
  • 所有标志(flag)必须附带 Usage 字符串,且避免缩写歧义(如 --dry-run 而非 -d);
  • 错误提示需包含上下文动作建议(如“请检查网络连接或重试 --timeout=30”)。

OpenAPI 3.0 输出关键项

字段 必填 说明
components.schemas ✅ 复用类型定义,避免重复描述
paths.*.responses.default ⚠️ 至少声明默认错误响应结构
x-go-package ❌ 非标准扩展,但利于生成器绑定源码位置
graph TD
    A[Go source] --> B[godoc parser]
    A --> C[cobra root cmd]
    A --> D[swaggo annotations]
    B --> E[HTML/PDF docs]
    C --> F[CLI --help]
    D --> G[openapi.json]

4.3 性能优化实证(pprof火焰图解读+GC pause优化前后对比+benchstat统计显著性验证)

火焰图定位热点

go tool pprof -http=:8080 cpu.pprof 启动可视化服务后,发现 encoding/json.(*decodeState).object 占比达 37%,为首要优化入口。

GC pause 对比

优化前平均 STW 时间:12.4ms;启用 GOGC=50 并复用 sync.Pool 中的 *json.Decoder 后降至 3.1ms:

场景 P95 pause (ms) GC 次数/10s
优化前 18.2 42
优化后 4.3 11

benchstat 显著性验证

benchstat old.txt new.txt

输出 p=0.0021(

内存复用关键代码

var decoderPool = sync.Pool{
    New: func() interface{} {
        return json.NewDecoder(nil) // 注意:实际使用时需 Reset io.Reader
    },
}

// 使用时:
dec := decoderPool.Get().(*json.Decoder)
dec.Reset(r) // 替换底层 reader
err := dec.Decode(&v)
decoderPool.Put(dec) // 归还前确保无引用

Reset() 避免重复分配 *bytes.Buffer,Put() 前清空内部字段可防止内存泄漏。

4.4 架构演进推演(从单体到Service Mesh迁移路径图+Envoy配置与Go服务协同逻辑)

迁移路径关键阶段

  • 阶段1:单体应用容器化(Docker封装,无服务发现)
  • 阶段2:引入Sidecar代理(Envoy注入,流量劫持)
  • 阶段3:控制平面接入(Istio Pilot / Consul Connect)
  • 阶段4:业务代码解耦(HTTP客户端替换为gRPC+Context传播)

Envoy与Go服务协同核心逻辑

# envoy.yaml:透明拦截8080端口入站流量
static_resources:
  listeners:
  - address:
      socket_address: { address: 0.0.0.0, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: backend
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route: { cluster: "go-service" }

该配置使Envoy将所有/请求路由至go-service集群;Go服务无需修改监听地址,仍绑定localhost:8080——因iptables重定向,实际流量经Envoy中转,实现零侵入式接入。

数据同步机制

组件 同步方式 触发条件
Envoy xDS 增量gRPC流 控制平面推送配置变更
Go服务健康检查 HTTP GET /health Envoy每5秒轮询
graph TD
  A[单体Go服务] -->|1. 容器化| B[Pod内共置Envoy]
  B -->|2. iptables劫持| C[入向流量→Envoy]
  C -->|3. xDS动态路由| D[转发至本地127.0.0.1:8081]
  D --> E[Go服务真实监听端口]

第五章:附录:72小时Go简历诊断服务说明

服务定位与适用人群

本服务专为具备基础Go语言开发经验(6个月以上实际项目经历)的中级工程师设计,覆盖Web后端、微服务、CLI工具及云原生方向求职者。典型用户包括:正在投递字节跳动/腾讯云/蚂蚁集团Go岗位的候选人、转岗至Go技术栈的Python/Java开发者、以及参与CNCF开源项目但缺乏工业级简历表达的Contributor。

诊断流程与时效承诺

服务严格遵循72小时闭环机制:

  • T0小时:提交简历PDF+目标JD(至少1份)+ GitHub链接(需含≥3个可运行Go项目)
  • T+24小时内:交付《Go技术能力映射表》(见下表),标注每项技能在简历中的证据强度(强/中/弱/缺失)
  • T+48小时内:提供带行号批注的Markdown版优化稿(含// TODO: 补充pprof性能调优案例类可执行建议)
  • T+72小时内:安排30分钟Zoom深度复盘(含实时修改简历并演示go mod graph | grep "gin"等技术细节溯源技巧)
简历模块 Go专项检查点 常见问题示例
技术栈描述 是否注明Go版本及module依赖管理方式 “熟悉Gin框架”未说明v1.9.1+Go1.21
项目经历 是否体现并发模型选择依据(goroutine池/errgroup) 仅写“使用goroutine”,无超时控制代码片段
开源贡献 是否列出具体PR编号及解决的issue类型 “参与etcd开发”未附#15234链接

技术证据强化策略

针对Go工程师简历普遍存在的“技术黑箱”问题,我们强制要求所有项目经历必须包含可验证的技术锚点。例如:

// 在“订单服务重构”项目中,需明确写出:
// - 使用sync.Map替代map+mutex的QPS提升数据(实测从8.2k→12.7k)
// - pprof火焰图定位到time.Sleep导致goroutine泄漏的具体行号
// - go list -m all | grep "github.com/uber-go/zap" 输出版本锁定证明

工具链验证清单

所有提交材料将通过自动化脚本验证:

  • go version -m ./main 检查二进制构建环境一致性
  • git log --oneline -n 5 <repo> 验证GitHub项目活跃度
  • go mod verify 校验依赖完整性(失败则标记“依赖污染风险”)

真实案例对比

某电商公司Go工程师原简历中“优化Redis缓存”被批注为无效表述,经诊断后重写为:

“将商品详情页缓存层从单体Redis升级为Redis Cluster,通过go-redis/v9的NewClusterClient实现自动分片,结合WithContext(ctx, time.Second*3)设置上下文超时,在双十一流量峰值期间将P99延迟从420ms降至89ms(监控截图见附件)”

交付物构成

  • 主文件:resume_go_optimized.md(含语法高亮的Go代码块及性能数据标注)
  • 辅助文件:go_skill_gap_analysis.csv(CSV格式,含23项Go核心能力维度评分)
  • 可视化报告:Mermaid时序图展示诊断各环节耗时分布
gantt
    title 72小时诊断时间轴
    dateFormat  X
    section 处理阶段
    材料解析       :a1, 2024-06-01, 86400
    技术证据比对   :a2, after a1, 172800
    人工深度校验   :a3, after a2, 86400

记录 Golang 学习修行之路,每一步都算数。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注