第一章:Go语言速学
Go语言以简洁语法、内置并发支持和高效编译著称,是构建云原生服务与CLI工具的首选之一。安装Go后,可通过go version验证环境是否就绪;官方推荐使用Go 1.21+版本以获得最佳泛型与错误处理支持。
快速启动第一个程序
创建hello.go文件,内容如下:
package main // 声明主模块,可执行程序必须为main包
import "fmt" // 导入标准库fmt包用于格式化I/O
func main() {
fmt.Println("Hello, Go!") // 程序入口函数,输出字符串并换行
}
在终端中执行:
go run hello.go # 编译并立即运行(不生成二进制)
# 输出:Hello, Go!
该命令会自动解析依赖、编译为临时可执行文件并运行——无需手动构建步骤。
变量与类型推导
Go支持显式声明与短变量声明(仅限函数内):
var name string = "Alice"age := 30→ 编译器自动推导为int类型- 多变量可批量声明:
name, age, active := "Bob", 25, true
并发模型:goroutine与channel
Go原生支持轻量级线程(goroutine)和通信同步机制(channel):
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond) // 模拟耗时操作
fmt.Println(s)
}
}
func main() {
go say("world") // 启动新goroutine,非阻塞
say("hello") // 主goroutine执行
}
上述代码将交替打印hello与world,体现并发调度特性。注意:若主函数结束,所有goroutine将被强制终止,实际项目中需用sync.WaitGroup或channel协调生命周期。
标准工具链常用命令
| 命令 | 用途 |
|---|---|
go mod init example.com/hello |
初始化模块,生成go.mod文件 |
go build -o hello . |
编译当前目录为独立二进制文件 |
go test ./... |
运行所有子目录下的测试用例 |
Go强调“约定优于配置”,多数工程实践(如测试命名、目录结构、错误处理模式)均由工具链统一约束,大幅降低团队协作成本。
第二章:Go tool trace深度解读
2.1 trace数据采集原理与runtime事件模型
Trace 数据采集依赖于 Go runtime 提供的底层事件钩子(event hooks),在 Goroutine 调度、网络阻塞、GC 触发等关键路径插入轻量级探针。
事件注册与生命周期管理
Go 1.20+ 通过 runtime/trace 包暴露 Start 和 Stop 接口,内部注册 traceEvent 回调至 runtime 的 event ring buffer:
// 启动 trace 采集(简化版)
import _ "runtime/trace"
func init() {
f, _ := os.Create("trace.out")
trace.Start(f) // 注册全局事件监听器
}
trace.Start 触发 runtime 初始化环形缓冲区(默认 64MB),并启用 trace.enable 标志位;所有 trace.printEvent 调用最终写入该 buffer。
核心事件类型与语义
| 事件类型 | 触发时机 | 典型用途 |
|---|---|---|
GoCreate |
go func() 执行时 |
分析 Goroutine 创建热点 |
GoSched |
主动让出 P(如 runtime.Gosched) |
定位协作式调度瓶颈 |
NetPollBlock |
网络 I/O 阻塞进入等待队列 | 识别连接池耗尽问题 |
数据同步机制
采集数据通过双缓冲区 + 原子指针切换实现零停顿写入:
graph TD
A[Runtime Event] --> B[Per-P Event Buffer]
B --> C{Buffer Full?}
C -->|Yes| D[原子交换缓冲区指针]
C -->|No| E[追加到当前 buffer]
D --> F[Writer Goroutine 读取旧 buffer]
F --> G[序列化为 binary format]
事件模型本质是异步采样 + 确定性重放:每个事件携带纳秒级时间戳、G/P/M ID 及上下文快照,确保跨线程因果关系可重建。
2.2 可视化轨迹分析:Goroutine调度与阻塞链路定位
Go 程序的性能瓶颈常隐匿于 Goroutine 的调度延迟与同步阻塞中。runtime/trace 提供了细粒度的执行轨迹,配合 go tool trace 可生成交互式可视化报告。
核心分析流程
- 启动带追踪的程序:
go run -gcflags="-l" -trace=trace.out main.go - 打开分析界面:
go tool trace trace.out - 定位高亮区域:关注
SCHEDULING、BLOCKED和SYNCHRONIZATION时间轴
阻塞链路识别示例
func worker(ch <-chan int) {
for range ch { // ← 此处可能长期阻塞
runtime.Gosched()
}
}
该循环在 ch 无数据时进入 Gwaiting 状态,trace 中显示为红色“Block”条;ch 的发送方若未就绪,则形成跨 Goroutine 的阻塞依赖链。
调度延迟关键指标
| 指标 | 含义 | 健康阈值 |
|---|---|---|
Goroutine creation latency |
新 Goroutine 创建到首次运行耗时 | |
Runnable → Running delay |
就绪队列等待调度时间 |
graph TD
A[G1: ch<-1] -->|channel send| B[G2: <-ch]
B -->|blocks until data| C[Scheduler wakes G2]
C --> D[G2 resumes execution]
2.3 网络IO与系统调用延迟的trace精准识别
精准定位网络IO瓶颈需穿透内核态与用户态边界,结合eBPF探针与/proc/sys/kernel/perf_event_paranoid权限协同采集。
关键trace点选择
sys_enter_sendto/sys_exit_sendto(测量syscall入口到返回耗时)tcp_sendmsg(内核协议栈实际处理延迟)netif_receive_skb(网卡驱动到协议栈移交延迟)
eBPF延迟采样示例
// trace_send_delay.c:捕获sendto syscall往返延迟
SEC("tracepoint/syscalls/sys_enter_sendto")
int trace_enter(struct trace_event_raw_sys_enter *ctx) {
u64 ts = bpf_ktime_get_ns();
bpf_map_update_elem(&start_ts, &ctx->id, &ts, BPF_ANY);
return 0;
}
bpf_ktime_get_ns()提供纳秒级单调时钟;start_ts为per-CPU哈希映射,避免锁竞争;ctx->id复用syscall号作key,确保上下文隔离。
| 指标 | 典型阈值 | 含义 |
|---|---|---|
sys_enter→sys_exit |
>100μs | 用户态准备+内核调度开销 |
tcp_sendmsg |
>50μs | 协议栈序列化/内存拷贝延迟 |
graph TD A[用户进程调用send] –> B[进入sys_enter_sendto] B –> C[记录起始时间戳] C –> D[tcp_sendmsg执行] D –> E[网卡DMA传输] E –> F[sys_exit_sendto触发] F –> G[计算端到端延迟]
2.4 GC周期与内存分配行为的trace时序解构
JVM 的 GC trace 并非孤立事件,而是与对象分配、TLAB 填充、晋升阈值动态联动的时序系统。
分配与回收的共生关系
每次 new 操作触发 TLAB 分配;当 TLAB 耗尽或对象过大时,直接在 Eden 区分配,并可能触发 Minor GC:
// 示例:触发 Eden 分配与后续 GC trace 关键点
Object obj = new byte[1024 * 1024]; // > TLAB size → 直接 Eden 分配
System.gc(); // 强制触发,生成 -XX:+PrintGCDetails 下的 trace 时间戳序列
该代码强制暴露分配路径切换(TLAB bypass)与 GC 触发边界;-Xlog:gc+allocation=debug 可捕获每次分配的线程ID、size、目标区域。
GC trace 核心时序字段对照
| 字段 | 含义 | 典型值 |
|---|---|---|
GC pause |
STW 持续时间 | 0.024s |
Allocation Rate |
每秒分配 MB | 125.3MB/s |
TLAB Waste |
TLAB 内碎片占比 | 3.7% |
GC 阶段状态流转
graph TD
A[Allocation] --> B{TLAB sufficient?}
B -->|Yes| C[Fast-path TLAB alloc]
B -->|No| D[Eden direct alloc]
D --> E[Eden occupancy > threshold?]
E -->|Yes| F[Minor GC trigger]
F --> G[Copy surviving to Survivor/OLD]
这种时序耦合决定了:分配速率决定 GC 频率,而 GC 周期又反向重置 TLAB 大小与晋升年龄阈值。
2.5 实战:基于trace诊断高并发服务goroutine泄漏
场景还原
某支付网关在QPS破万后,runtime.NumGoroutine() 持续攀升且不回落,内存占用线性增长。
关键诊断步骤
- 启动
pproftrace:curl "http://localhost:6060/debug/pprof/trace?seconds=30" -o trace.out - 解析并可视化:
go tool trace trace.out
goroutine 状态分布(采样数据)
| 状态 | 数量 | 典型原因 |
|---|---|---|
| runnable | 12 | CPU 密集型任务阻塞 |
| waiting | 892 | channel recv 阻塞 |
| syscall | 47 | 未超时的 HTTP client |
核心泄漏代码片段
func handlePayment(w http.ResponseWriter, r *http.Request) {
ch := make(chan Result)
go func() { // ❌ 无超时、无缓冲、无关闭,goroutine 永久阻塞
res, _ := callLegacyService(r.Context())
ch <- res // 若接收端未读,此 goroutine 泄漏
}()
select {
case res := <-ch:
json.NewEncoder(w).Encode(res)
case <-time.After(2 * time.Second):
http.Error(w, "timeout", http.StatusGatewayTimeout)
}
}
逻辑分析:
ch是无缓冲 channel,若主协程因 timeout 提前退出,子 goroutine 在ch <- res处永久挂起。r.Context()未传递至callLegacyService,导致无法主动取消下游调用。
修复方案要点
- 使用带缓冲 channel(
make(chan Result, 1))避免发送阻塞 - 将
r.Context()透传到底层调用,并配合context.WithTimeout - 统一使用
errgroup管理协程生命周期
graph TD
A[HTTP 请求] --> B[启动 goroutine]
B --> C{callLegacyService}
C -->|成功| D[发送到 channel]
C -->|失败/超时| E[goroutine 退出]
D --> F[主协程 select 接收]
F -->|超时| G[关闭 context]
G --> C
第三章:pprof性能剖析核心机制
3.1 pprof采样策略与CPU/heap/block/profile类型差异
pprof通过采样而非全量记录实现低开销性能分析,不同profile类型采用截然不同的采样机制:
CPU Profiling:基于信号中断的周期性采样
内核每毫秒向进程发送 SIGPROF 信号,触发栈快照捕获。采样频率受 runtime.SetCPUProfileRate() 控制,默认100Hz。
import "runtime/pprof"
func main() {
f, _ := os.Create("cpu.pprof")
pprof.StartCPUProfile(f) // 启动时注册信号处理器
defer pprof.StopCPUProfile()
// ... 业务逻辑
}
StartCPUProfile注册SIGPROF处理器并启动定时器;采样精度受系统负载影响,无法捕获短于采样间隔的函数调用。
Heap vs Block Profile:分配/阻塞事件驱动
| 类型 | 触发条件 | 默认启用 | 数据粒度 |
|---|---|---|---|
| heap | 内存分配(含GC标记) | 否 | 分配点+大小+栈 |
| block | goroutine阻塞(如channel、mutex) | 否 | 阻塞位置+持续时间 |
采样策略对比流程
graph TD
A[pprof.StartXXXProfile] --> B{Profile类型}
B -->|CPU| C[注册SIGPROF + 定时器]
B -->|Heap| D[Hook malloc/free + GC hook]
B -->|Block| E[Hook sync/chan阻塞点]
3.2 符号解析与调用栈还原的底层实现原理
符号解析依赖于二进制中嵌入的调试信息(如 DWARF 或 PDB),运行时通过 .eh_frame 或 __unwind_info 定位帧描述符,结合寄存器状态(RIP、RBP、RSP)推导调用链。
栈帧遍历的关键寄存器
RBP:传统帧指针,指向当前栈帧基址,前一个RBP存于[RBP]RSP:当前栈顶,用于验证栈边界有效性RIP:返回地址,需映射至符号表获取函数名
DWARF CFI 指令解析示例
// 伪代码:从 .eh_frame 解析 CFI(Call Frame Information)
uint8_t *cie_start = find_cie_for_pc(current_rip);
int64_t ra_offset = read_cfi_instruction(cie_start, "DW_CFA_advance_loc");
// ra_offset 表示返回地址在栈中的偏移(通常为 +8 x86_64)
该逻辑动态计算返回地址位置,避免硬编码偏移;ra_offset 由编译器生成,反映 ABI 与优化级别影响。
| 字段 | 含义 | 典型值 |
|---|---|---|
CIE.version |
CFI 版本 | 1/3/4 |
CIE.augment |
扩展标识(如 “zR” 启用 RA) | “zR” |
FDE.cie_off |
关联 CIE 偏移 | 0x1a8 |
graph TD
A[捕获 SIGSEGV/SIGABRT] --> B[读取当前 RSP/RBP/RIP]
B --> C[查找对应 .eh_frame 段]
C --> D[执行 CFI 指令解码]
D --> E[逐帧回溯并符号化]
3.3 Web UI与火焰图交互式分析的最佳实践
数据同步机制
Web UI 需实时响应火焰图的缩放、悬停与点击操作,推荐采用 WebSocket + 增量 Diff 同步策略:
// 前端监听火焰图交互事件,并发送轻量级指令
flameChart.on('zoom', (range) => {
socket.send(JSON.stringify({
type: 'zoom',
start: range.start, // 时间戳(ns)
end: range.end,
depth: 3 // 仅同步顶层3层调用栈
}));
});
该设计避免全量重绘,depth 参数控制同步粒度,减少带宽消耗;start/end 使用纳秒级时间戳确保与后端采样对齐。
交互响应优化
- ✅ 悬停显示函数耗时占比(含内联汇编提示)
- ✅ 右键跳转至源码行(需 sourcemap 映射表预加载)
- ❌ 禁止在火焰图上直接执行
eval()或动态import()
关键参数对照表
| 参数 | 推荐值 | 说明 |
|---|---|---|
sampleRate |
100Hz | 平衡精度与性能 |
maxDepth |
16 | 防止栈过深导致渲染阻塞 |
tooltipDelay |
300ms | 避免误触,提升可访问性 |
graph TD
A[用户悬停帧] --> B{是否命中符号表?}
B -->|是| C[渲染函数名+耗时+调用频次]
B -->|否| D[显示地址+模块名+模糊匹配建议]
第四章:pprof一键诊断脚本工程化落地
4.1 自动化采集脚本:多维度profile并行抓取与时间对齐
为保障跨设备、跨服务的 profile 数据可比性,采集脚本需同步拉取用户行为、设备状态、网络 QoS 及地理位置四维 profile,并以毫秒级时间戳对齐。
数据同步机制
采用 NTP 校准 + 本地高精度时钟(time.time_ns())双源打标,确保各维度采集起始时刻偏差
并行采集架构
from concurrent.futures import ThreadPoolExecutor
import asyncio
# 启动四路异步采集任务,共享统一基准时间
base_ts = time.time_ns() // 1_000_000 # 毫秒级锚点
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [
executor.submit(fetch_user_profile, base_ts),
executor.submit(fetch_device_metrics, base_ts),
executor.submit(fetch_network_qoe, base_ts),
executor.submit(fetch_geo_context, base_ts),
]
results = [f.result() for f in futures] # 返回带 ts_ms 字段的 dict 列表
逻辑分析:base_ts 作为全局时间锚点传入各采集函数,避免各线程独立调用 time.time() 引入时序漂移;每个 fetch_* 函数内部仅做数据获取,不重打时间戳,保证时间对齐语义一致。参数 base_ts 单位为毫秒,适配后端时序数据库写入要求。
对齐验证指标
| 维度 | 允许最大偏移 | 校验方式 |
|---|---|---|
| 用户行为 | ±5ms | 与 base_ts 差值绝对值 |
| 设备状态 | ±3ms | 硬件 RTC 读数校验 |
| 网络 QoS | ±8ms | TCP timestamp 选项反推 |
| 地理位置 | ±10ms | GNSS UTC 时间戳比对 |
graph TD
A[启动采集] --> B[生成 base_ts]
B --> C[并发调用四维 fetch]
C --> D{各结果携带 ts_ms}
D --> E[入库前校验 Δt ≤ 阈值]
E --> F[写入统一 time-series 表]
4.2 智能分析模块:异常指标检测(如goroutine暴涨、alloc高频)
核心检测策略
采用滑动窗口 + 动态基线双机制识别突变:
- 每30秒采集一次
runtime.NumGoroutine()与/gc/heap/allocs:bytes指标 - 基线基于过去1小时P95分位值,容忍度设为2.5σ
实时告警代码示例
// 检测goroutine异常增长(>基线×3且持续2个周期)
func detectGoroutineBurst(now int, baseline int64) bool {
return int64(now) > baseline*3 &&
atomic.LoadInt32(&burstCounter) >= 2 // 防抖计数器
}
burstCounter由上游采样协程原子递增,避免瞬时抖动误报;baseline每5分钟动态重算,适配业务峰谷。
关键阈值配置表
| 指标类型 | 静态阈值 | 动态基线权重 | 触发延迟 |
|---|---|---|---|
| Goroutine数量 | — | ×3.0 | 60s |
| Heap alloc速率 | 50MB/s | P95×2.2 | 30s |
异常判定流程
graph TD
A[采集指标] --> B{是否超静态阈值?}
B -->|是| C[立即触发L1告警]
B -->|否| D[计算动态偏离度]
D --> E{偏离度 > 阈值?}
E -->|是| F[启动滑动窗口验证]
F --> G[连续2次命中→L2精准告警]
4.3 报告生成引擎:Markdown+SVG火焰图+关键路径摘要
报告生成引擎将性能分析结果转化为可交付的可视化文档,核心由三部分协同构成:结构化 Markdown 模板、动态嵌入的 SVG 火焰图、以及自提取的关键路径摘要。
渲染流程
# 将 flame.svg 注入 Markdown 模板(Jinja2)
template.render(
title="API Latency Report",
flame_svg=svg_content, # Base64 编码或内联 SVG 字符串
critical_path=["/auth → /api/v2/query → /cache/hit"]
)
svg_content 必须为合法内联 SVG(无外部引用),确保离线可读;critical_path 是从调用链中自动识别的耗时最长、不可并行的线性子路径。
输出组件对比
| 组件 | 格式 | 可交互性 | 生成时机 |
|---|---|---|---|
| Markdown | .md |
静态文本 | 模板渲染时 |
| SVG 火焰图 | <svg>...</svg> |
缩放/悬停提示 | 分析器输出后即时转义嵌入 |
| 关键路径摘要 | 纯文本列表 | 无 | 基于 Span duration 排序与依赖拓扑推导 |
数据流
graph TD
A[Profile Data] --> B(Trace Aggregator)
B --> C{Critical Path Detector}
C --> D[Path Summary]
B --> E[Flame Graph Generator]
E --> F[Inline SVG]
D & F --> G[Markdown Renderer]
4.4 容器/K8s环境适配:sidecar模式与metrics注入集成
在云原生可观测性体系中,sidecar 模式是解耦应用逻辑与监控采集的主流实践。通过将 metrics exporter 作为独立容器与业务 Pod 共享网络命名空间,实现零侵入指标暴露。
Sidecar 注入示例(Istio 风格)
# sidecar-injector-config.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: metrics-sidecar-injector
webhooks:
- name: injector.metrics.example.com
clientConfig:
service:
name: metrics-injector
namespace: kube-system
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
该配置声明了对 Pod 创建事件的动态拦截能力;clientConfig.service 指向注入服务,确保每次 Pod 启动时自动注入 metrics sidecar 容器。
Sidecar 与主容器协同机制
- 共享
localhost网络栈,主应用无需开放额外端口 - 通过
volumeMounts共享/proc和/sys/fs/cgroup实现进程与资源指标采集 - 使用
initContainer预热指标 schema,避免冷启动延迟
| 组件 | 作用 | 启动顺序 |
|---|---|---|
| initContainer | 初始化 cgroup 权限 | 1 |
| main container | 业务逻辑 | 2 |
| metrics sidecar | 拉取/转换/暴露 Prometheus 指标 | 3 |
graph TD
A[Pod 创建请求] --> B[Mutating Webhook 拦截]
B --> C[注入 initContainer + metrics sidecar]
C --> D[共享 volume & network]
D --> E[sidecar 通过 localhost:9090 抓取主进程指标]
第五章:Go语言速学
快速启动与环境配置
安装 Go 语言开发环境仅需三步:从官网下载对应平台的安装包(如 go1.22.3.darwin-arm64.pkg),执行安装向导,随后验证 go version 输出。关键配置项包括 GOPATH(默认为 $HOME/go)和 GOBIN(推荐设为 $HOME/go/bin),并确保 PATH 包含 $GOBIN。终端中运行 go env -w GOPROXY=https://proxy.golang.org,direct 可显著加速模块拉取。
Hello World 与模块初始化
创建项目目录后执行 go mod init example.com/hello 初始化模块,生成 go.mod 文件。编写 main.go:
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界!")
}
运行 go run main.go 即可输出结果;使用 go build -o hello main.go 生成可执行二进制文件,无需外部依赖即可部署至 Linux/Windows/macOS。
并发模型实战:并发爬虫片段
以下代码启动 5 个 goroutine 并发请求 URL 列表,使用 sync.WaitGroup 确保全部完成:
func fetchURLs(urls []string) {
var wg sync.WaitGroup
ch := make(chan string, len(urls))
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
resp, _ := http.Get(u)
defer resp.Body.Close()
ch <- fmt.Sprintf("✅ %s: %d", u, resp.StatusCode)
}(url)
}
go func() {
wg.Wait()
close(ch)
}()
for msg := range ch {
fmt.Println(msg)
}
}
错误处理与结构体定义
Go 强制显式处理错误,避免静默失败。定义用户结构体并实现 JSON 序列化:
| 字段名 | 类型 | JSON 标签 | 说明 |
|---|---|---|---|
| ID | int | "id" |
唯一标识 |
| Name | string | "name" |
用户姓名 |
| string | "email,omitempty" |
邮箱(空则忽略) |
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
u := User{ID: 101, Name: "张伟"}
data, _ := json.Marshal(u) // 输出 {"id":101,"name":"张伟"}
接口与多态实践
定义 Notifier 接口并实现邮件与短信两种通知方式:
type Notifier interface {
Notify(message string) error
}
type EmailNotifier struct{ To string }
func (e EmailNotifier) Notify(msg string) error {
fmt.Printf("📧 发送邮件至 %s: %s\n", e.To, msg)
return nil
}
type SMSNotifier struct{ Phone string }
func (s SMSNotifier) Notify(msg string) error {
fmt.Printf("📱 发送短信至 %s: %s\n", s.Phone, msg)
return nil
}
func sendAlert(n Notifier, content string) {
n.Notify(content)
}
sendAlert(EmailNotifier{"admin@example.com"}, "服务异常")
sendAlert(SMSNotifier{"138****1234"}, "CPU 使用率超 95%")
工程化构建与测试
使用 go test -v ./... 运行全部测试用例。编写 calculator_test.go 验证加法逻辑:
func TestAdd(t *testing.T) {
cases := []struct{
a, b, want int
}{
{2, 3, 5},
{-1, 1, 0},
{0, 0, 0},
}
for _, tc := range cases {
if got := Add(tc.a, tc.b); got != tc.want {
t.Errorf("Add(%d,%d) = %d, want %d", tc.a, tc.b, got, tc.want)
}
}
}
性能优化技巧
避免字符串拼接使用 +,改用 strings.Builder 提升 3–5 倍性能:
var sb strings.Builder
sb.Grow(1024)
for i := 0; i < 1000; i++ {
sb.WriteString(fmt.Sprintf("item%d,", i))
}
result := sb.String()
部署与交叉编译
通过 GOOS=linux GOARCH=arm64 go build -o app-linux-arm64 main.go 直接生成树莓派可用二进制;配合 Dockerfile 构建最小镜像:
FROM gcr.io/distroless/static:nonroot
COPY app-linux-arm64 /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
依赖管理与版本锁定
go.mod 中声明依赖时使用语义化版本,例如 github.com/go-sql-driver/mysql v1.7.1;升级时执行 go get github.com/go-sql-driver/mysql@v1.8.0 自动更新 go.sum 校验和。
实战:REST API 微服务骨架
使用 net/http 和 gorilla/mux 快速搭建用户管理接口:
r := mux.NewRouter()
r.HandleFunc("/users", getUsers).Methods("GET")
r.HandleFunc("/users/{id}", getUser).Methods("GET")
http.ListenAndServe(":8080", r) 