cloudenative2-2-go进阶

发布于:2022-12-14 ⋅ 阅读:(597) ⋅ 点赞:(0)

3. 内存管理
----------------------
P的数量与内核线程一致,上限256
M的数量是内核线程数,大于P,因为线程可能暂停,上限10000
----------------------
关于内存管理的争论
java golang:内存管理太重要了!手动管理麻烦且容易出错,所以我们应该交给机器去管理!
c c++:内存管理太重要了!所以如果交给机器管理我不能放心!
----------------------
堆内存管理
Mutator变更者:用户程序,通过 Allocator 创建对象
Allocator:内存分配器,处理动态内存分配请求
Collector:垃圾回收器,回收内存空间
object Header:对象头,Collector 和 Allocator 同步对象元数据
----------------------
堆内存管理的挑战
1、内存分配需要系统调用,在频繁内存分配时,系统性能较低
2、多线程,同时申请内存,需要加锁。
3、内存碎片问题
----------------------
堆内存管理
• 初始化连续内存块作为堆
• 有内存申请的时候,Allocator 从堆内存的未分配区域分割小内存块
• 用链表将已分配内存连接起来
• 需要信息描述每个内存块的元数据:大小,是否使用,下一个内存块的地址等
----------------------
TCMalloc 概览
ThreadCacheMalloc
Google的
堆内存预分配
每个线程维护各自的私有内存空间,防止多线程竞争

每启动一个线程,申请一个ThreadCache,如果没有了,到CentralCache里面取
最终到PageHeap中取
3级管理
span list 1:1page 8K
span list 2:2page 16K
span list 128:128page 1M
规整了空间
----------------------
TCMalloc
• page:内存页,一块 8K 大小的内存空间。Go 与操作系统之间的内存申请和释放,都是以
page 为单位的
• span: 内存块,一个或多个连续的 page 组成一个 span
• sizeclass : 空间规格,每个 span 都带有一个 sizeclass ,标记着该 span 中的 page 应该如何
使用
• object : 对象,用来存储一个变量数据内存空间,一个 span 在初始化时,会被切割成一堆等大的 object ;假设 object 的大小是 16B ,span 大小是 8K ,那么就会把 span 中的 page 就会被初始化 8K / 16B = 512 个 object 。所谓内存分配,就是分配一个 object 出去
----------------------
TCMalloc
•对象大小定义
    •小对象大小:0~256KB
    •中对象大小:256KB~1MB
    •大对象大小:>1MB
•小对象的分配流程
    •ThreadCache -> CentralCache -> HeapPage,大部分时候,ThreadCache 缓存都是足够的,不需要去访问CentralCache 和 HeapPage,无系统调用配合无锁分配,分配效率是非常高的
•中对象分配流程
    •直接在 PageHeap 中选择适当的大小即可,128 Page 的 Span 所保存的最大内存就是 1MB
大对象分配流程
    •从 large span set 选择合适数量的页面组成 span,用来存储数据
----------------------
Go 语言内存分配
从TCMALLOC衍生出来的,有不同
span class 134个
size class 67个
两个span class 一个存指针,一个存引用,存引用的span无需内存回收。
一个span class有两个列表,一个non empty,empty
mheap:两棵树。 free, scav
----------------------
Go 语言内存分配
• mcache:小对象的内存分配直接走
    •size class 从 1 到 66,每个 class 两个 span
    •Span 大小是 8KB,按 span class 大小切分
• mcentral
    •Span 内的所有内存块都被占用时,没有剩余空间继续分配对象,mcache 会向 mcentral 申请1个span,mcache 拿到 span 后继续分配对象
    •当 mcentral 向 mcache 提供 span 时,如果没有符合条件的 span,mcentral 会向 mheap 申请span
• mheap
    •当 mheap 没有足够的内存时,mheap 会向 OS 申请内存
    •Mheap 把 Span 组织成了树结构,而不是链表
    •然后把 Span 分配到 heapArena 进行管理,它包含地址映射和 span 是否包含指针等位图
        •为了更高效的分配、回收和再利用内存
----------------------
内存回收
• 引用计数(Python,PHP,Swift)
    •对每一个对象维护一个引用计数,当引用该对象的对象被销毁的时候,引用计数减 1,当引用计数为 0 的时候,回收该对象
    •优点:对象可以很快的被回收,不会出现内存耗尽或达到某个阀值时才回收
    •缺点:不能很好的处理循环引用,而且实时维护引用计数,有也一定的代价
• 标记-清除(Golang)
    •从根变量开始遍历所有引用的对象,引用的对象标记为"被引用",没有被标记的进行回收
    •优点:解决引用计数的缺点
    •缺点:需要 STW(stop the word),即要暂停程序运行
• 分代收集(Java)
    •按照生命周期进行划分不同的代空间,生命周期长的放入老年代,短的放入新生代,新生代的回收频率高于老年代的频率
----------------------
mspan -- struct结构
•allocBits
    •记录了每块内存分配的情况
•gcmarkBits
    •记录了每块内存的引用情况,标记阶段对每块内存进行标记,有对象引用的内存标记为1,没有的标记为 0
----------------------
mspan
这两个位图的数据结构是完全一致的,标记结束则进行内存回收,回收的时候,将 allocBits 指向 gcmarkBits,标记过的则存在,未进行标记的则进行回收
----------------------
GC 工作流程
Golang GC 的大部分处理是和用户代码并行的
•Mark
    •Mark Prepare: 初始化 GC 任务,包括开启写屏障 (write barrier) 和辅助 GC(mutator assist),统计root对象的任务数量等。这个过程需要STW
    •GC Drains: 扫描所有 root 对象,包括全局指针和 goroutine(G) 栈上的指针(扫描对应 G 栈时需停止该 G),将其加入标记队列(灰色队列),并循环处理灰色队列的对象,直到灰色队列为空。该过程后台并行执行
•Mark Termination:完成标记工作,重新扫描(re-scan)全局指针和栈。因为 Mark 和用户程序是并行的,所以在 Mark 过程中可能会有新的对象分配和指针赋值,这个时候就需要通过写屏障(write barrier)记录下来,re-scan 再检查一下,这个过程也是会 STW 的
•Sweep:按照标记结果回收所有的白色对象,该过程后台并行执行
•Sweep Termination:对未清扫的 span 进行清扫, 只有上一轮的 GC 的清扫工作完成才可以开始新一轮的 GC
----------------------
GC 工作流程
关闭--GC关闭 写操作是正常的赋值
栈扫描--开启写屏障等准备工作,短暂开启STW;从全局空间和 goroutine 栈空间上收集变量
标记--三色标记法,直到没有灰色对象
标记结束--开启 STW,回头重新扫描 root 区域新变量,对他们进行标记
清除--关闭 STW 和 写屏障,对白色对象进行清除
关闭--循环结束,重启下一阶段GC
----------------------
三色标记
• GC 开始时,认为所有 object 都是 白色,即垃圾。
• 从 root 区开始遍历,被触达的 object 置成 灰色。
• 遍历所有灰色 object,将他们内部的引用变量置成 灰色,自身置成 黑色
• 循环第 3 步,直到没有灰色 object 了,只剩下了黑白两种,白色的都是垃圾。
• 对于黑色 object,如果在标记期间发生了写操作,写屏障会在真正赋值前将新对象标记为 灰色。
• 标记过程中,mallocgc 新分配的 object,会先被标记成 黑色 再返回。
----------------------
垃圾回收触发机制
• 内存分配量达到阀值触发 GC
    每次内存分配时都会检查当前内存分配量是否已达到阀值,如果达到阀值则立即启动 GC。
    阀值 = 上次 GC 内存分配量 * 内存增长率
    内存增长率由环境变量 GOGC 控制,默认为 100,即每当内存扩大一倍时启动 GC。
• 定期触发 GC
    默认情况下,最长 2 分钟触发一次 GC,这个间隔在 src/runtime/proc.go:forcegcperiod 变量中被声明
• 手动触发
    程序代码中也可以使用 runtime.GC()来手动触发 GC。这主要用于 GC 性能测试和统计
----------------------
4. 包引用与依赖管理
----------------------
Go 语言依赖管理的演进
• 回顾 GOPATH
    • 通过环境变量设置系统级的 Go 语言类库目录
    • GOPATH 的问题?
        • 不同项目可能依赖不同版本
        • 代码被 clone 以后需要设置 GOPATH 才能编译
• vendor
    • 自 1.6 版本,支持 vendor 目录,在每个 Go 语言项目中,创建一个名叫 vendor 的目录,并将依赖拷贝至该目录
    • Go 语言项目会自动将 vendor 目录作为自身的项目依赖路径
    • 好处?
        • 每个项目的 vendor目录是独立的,可以灵活的选择版本
        • Vendor 目录与源代码一起 check in 到 github,其他人 checkout 以后可直接编译
        • 无需在编译期间下载依赖包,所有依赖都已经与源代码保存在一起
----------------------
vendor 管理工具
通过声明式配置,实现 vendor 管理的自动化
• 在早期,Go 语言无自带依赖管理工具,社区方案鱼龙混杂比较出名的包括
    • Godeps, Glide
• Go 语言随后发布了自带的依赖管理工具 Gopkg
• 很快用新的工具 gomod 替换掉了 gopkg
    • 切换 mod 开启模式:export GO111MODULE=on/off/auto
    • Go mod 相比之前的工具更灵活易用,以基本统一了 Go 语言依赖管理
思考:用依赖管理工具的目的?    
• 版本管理
• 防篡改
----------------------
Go mod 使用
• 创建项目
• 初始化 Go 模块
    • go mod init
• 下载依赖包
    • go mod download(下载的依赖包在$GOPATH/pkg,如果没有设置 GOPATH,则下载在项目根目录/pkg)
    • 在源代码中使用某个依赖包,如 github.com/emicklei/go-restful
• 添加缺少的依赖并为依赖包瘦身
    • go mod tidy
• 把 Go 依赖模块添加到 vendor 目录
    • go mod vendor
配置细节会被保存在项目根目录的 go.mod 中
可在 require 或者 replacement 中指定版本    

golang目录:
go mod init -- 建立一个go.mod文件
go mod tidy -- 搜索源代码,代码里面依赖的包,直接下载这些包

如果不行,估计是代理问题:
go env 检查一下GPROPXY
export GOPROXY="https://goroxy.cn,direct

----------------------
go.mod sample
module k8s.io/apiserver
go 1.13
require (
  github.com/evanphx/json-patch v4.9.0+incompatible
  github.com/go-openapi/jsonreference v0.19.3 // indirect
  github.com/go-openapi/spec v0.19.3
  github.com/gogo/protobuf v1.3.2
  github.com/google/go-cmp v0.3.0
  github.com/google/gofuzz v1.1.0
  k8s.io/apimachinery v0.0.0-20210518100737-44f1264f7b6b
)
replace (
    golang.org/x/crypto => golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
    golang.org/x/text => golang.org/x/text v0.3.2
    k8s.io/api => k8s.io/api v0.0.0-20210518101910-53468e23a787
    k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20210518100737-44f1264f7b6b
    k8s.io/client-go => k8s.io/client-go v0.0.0-20210518104342-fa3acefe68f3
    k8s.io/component-base => k8s.io/component-base v0.0.0-20210518111421-67c12a31a26a
)
----------------------
GOPROXY 和 GOPRIVATE
• GOPROXY
    • 为拉取 Go 依赖设置代理
        • export GOPROXY=https://goproxy.cn
• 在设置 GOPROXY 以后,默认所有依赖拉取都需要经过 proxy 连接 git repo,拉取代码,并做checksum 校验
• 某些私有代码仓库是 goproxy.cn 无法连接的,因此需要设置 GOPRIVATE 来声明私有代码仓库
GOPRIVATE=*.corp.example.com
GOPROXY=proxy.example.com
GONOPROXY=myrepo.corp.example.com
----------------------
5. Makefile
----------------------
Go 语言项目多采用 Makefile 组织项目编译
root:
    export ROOT=github.com/cncamp/golang;
.PHONY: root

release:
    echo "building httpserver binary"
    mkdir -p bin/amd64
    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/amd64 .
.PHONY: release
----------------------
6. 动手编写一个 HTTP Server
----------------------
理解 net.http 包
• 注册 handle 处理函数
http.HandleFunc("/healthz", healthz)
//Use the default DefaultServeMux.
ListenAndService
err := http.ListenAndServe(":80", nil)
if err != nil {
    log.Fatal(err)
}
• 定义 handle 处理函数
func healthz(w http.ResponseWriter, r
*http.Request) {
    io.WriteString(w, "ok")
}
----------------------
阻塞 IO 模型

----------------------
非阻塞 IO 模型
----------------------
IO 多路复用
----------------------
异步 IO
----------------------
Linux epoll

----------------------
Go 语言高性能 httpserver 的实现细节
• Go 语言将协程与 fd 资源绑定
    • 一个 socket fd 与一个协程绑定
    • 当 socket fd 未就绪时,将对应协程设置为 Gwaiting 状态,将 CPU 时间片让给其他协程
    • Go 语言 runtime 调度器进行调度唤醒协程时,检查 fd 是否就绪,如果就绪则将协程置为Grunnable 并加入执行队列
    • 协程被调度后处理 fd 数据
----------------------
代码实现细节
https://pouncing-waterfall-7c4.notion.site/http-server-socket-detail-
e1f350d63c7c4d9f86ce140949bd90c2
----------------------
7. 调试
----------------------
debug
• gdb:
    •Gccgo 原生支持 gdb,因此可以用 gdb 调试 Go 语言代码,但 dlv 对 Go 语言 debug 的支持比 gdb 更好
    •Gdb 对 Go 语言的栈管理,多线程支持等方面做的不够好,调试代码时可能有错乱现象
• dlv:
    •Go 语言的专有 debugger
----------------------
dlv 的配置
配置
•在 vscode 中配置 dlb
•菜单:View -> Command Palette
•选择 Go : Install/Update Tools,选择安装
•安装完后,从改入口列表中可以看到 dlv 和 dlv-dap 已经安装好
Debug 方法
•在代码中设置断点
•菜单中选择 Run -> Start Debugging 即可进入调试
----------------------
更多 debug 方法
添加日志
•在关键代码分支中加入日志
•基于fmt包将日志输出到标准输出 stdout
•fmt.Println()
•fmt 无日志重定向,无日志分级
即与日志框架将日志输出到对应的 appender
•比如可利用 glog 进行日志输出
•可配置 appender,将标准输出转至文件
•支持多级日志输出,可修改配置调整日志等级
•自带时间戳和代码行,方便调试
----------------------
Glog 使用方法示例
import (
"flag"
"github.com/golang/glog"
)
func main() {
    flag.Set("v", "4")
    glog.V(2).Info("Starting http server...")
    mux := http.NewServeMux()
    mux.HandleFunc("/", rootHandler)
    err := http.ListenAndServe(":80", mux)
    if err != nil {
        log.Fatal(err)
    }
}
----------------------
性能分析(Performance Profiling)
CPU Profiling: 在代码中添加 CPUProfile 代码,runtime/pprof 包提供支持
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func main() {
    flag.Parse()
    if *cpuprofile != "" {
        f, err := os.Create(*cpuprofile)
        if err != nil {
            log.Fatal(err)
        }
        pprof.StartCPUProfile(f)
        defer pprof.StopCPUProfile()
    }
}
----------------------
分析 CPU 瓶颈
•运行 cpuprofilie 代码后,会在 /tmp/cpuprofile 中记录 cpu 使用时间
•运行 go tool pprof /tmp/cpuprofile 进入分析模式
•运行 top10 查看 top 10线程,显示 30ms 花费在 main.main
Showing nodes accounting for 30ms, 100% of 30ms total
flat flat% sum%
cum cum%
30ms 100% 100%
0

0% 100%
30ms 100% main.main
30ms 100% runtime.main
•(pprof) list main.main 显示 30 毫秒都花费在循环上
Total: 30ms
30ms

30ms (flat, cum) 100% of Total
20ms20ms21:
10ms10ms22:
for i := 0; i < 100000000; i++ {
result += I
•可执行 web 命令生成 svg 文件,在通过浏览器打开 svg 文件查看图形化分析结果
----------------------
其他可用 profiling 工具分析的问题
•CPU profile
    •程序的 CPU 使用情况,每 100 毫秒采集一次 CPU 使用情况
• Memory Profile
    •程序的内存使用情况
• Block Profiling
    •非运行态的 goroutine 细节,分析和查找死锁
• Goroutine Profiling
    •所有 goroutines 的细节状态,有哪些 goroutine,它们的调用关系是怎样的
----------------------
针对 http 服务的 pprof
net/http/pprof 包提供支持
•如果采用默认 mux handle,则只需 import _ "net/http/pprof”
如果采用自定义 mux handle,则需要注册 pprof handler
Import (
    "net/http/pprof"

func startHTTP(addr string, s *tnetd.Server) {
    mux := http.NewServeMux()
    mux.HandleFunc(“/debug/pprof/”, pprof.Index)
    mux.HandleFunc(“/debug/pprof/profile”, pprof.Profile)
    mux.HandleFunc(“/debug/pprof/symbol”, pprof.Symbol)
    mux.HandleFunc(“/debug/pprof/trace”, pprof.Trace)
    server := &http.Server{
        Addr: addr,
        Handler: mux,
    }
    server.ListenAndServe()
}
----------------------
分析 go profiling 结果
在运行了开启 pprof 的服务器以后,可以通过访问对应的 URL 获得 profile 结果
•allocs: A sampling of all past memory allocations
•block: Stack traces that led to blocking on synchronization primitives
•cmdline: The command line invocation of the current program
•goroutine: Stack traces of all current goroutines
•heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to
run GC before taking the heap sample
----------------------
分析 go profiling 结果
•mutex: Stack traces of holders of contended mutexes
•profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the
profile file, use the go tool pprof command to investigate the profile.
•threadcreate: Stack traces that led to the creation of new OS threads
•trace: A trace of execution of the current program. You can specify the duration in the seconds
GET parameter. After you get the trace file, use the go tool trace command to investigate the
trace
----------------------
结果分析示例
•分析 goroutine 运行情况
    •curl localhost/debug/pprof/goroutine?debug=2
• 分析堆内存使用情况
    •curl localhost/debug/pprof/heap?debug=2
----------------------
8. Kubernetes 中常用代码解读
----------------------
Rate Limit Queue
func (r *ItemExponentialFailureRateLimiter) When(item interface{}) time.Duration {
    r.failuresLock.Lock()
    defer r.failuresLock.Unlock()

    exp := r.failures[item]
    r.failures[item] = r.failures[item] + 1

    // The backoff is capped such that ‘calculated’ value never overflows.
    backoff := float64(r.baseDelay.Nanoseconds()) * math.Pow(2, float64(exp))
    if backoff > math.MaxInt64 {
        return r.maxDelay
    }
    calculated := time.Duration(backoff)
    if calculated > r.maxDelay {
        return r.maxDelay
    }
    return calculated
}
----------------------
9. Kubernetes 日常运维中的代码调试场景
----------------------
案例1:空指针
问题描述

Kubenetes 调度器在调度有外挂存储需求的 pod 的时候,在获取节点信息失败时会异常退出

panic: runtime error: invalid memory address or nil pointer dereference

[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x105e283]
----------------------
案例1:空指针
根因分析
nil pointe 是 Go 语言中最常出现的一类错误,也最容易判断,通常在 call stack 中就会告诉
你哪行代码有问题
在调度器 csi.go 中的如下代码,当 node 为 nil 的时候,对 node 的引用 node.Name 就会
引发空指针
node := nodeInfo.Node()
if node == nil {
    return framework.NewStatus(framework.Error, fmt.Sprintf("node not found: %s", node.Name))
}
•解决办法
当指针为空时,不要继续引用。
https://github.com/kubernetes/kubernetes/pull/102229
----------------------
案例2:Map 的读写冲突
• 问题描述:
程序在遍历 Kubernetes 对象的 Annotation 时异常退出
• 根因分析
Kubernetes 对象中 Label 和 Annotation 是 map[string]string
经常有代码需要修改这两个 Map
同时可能有其他线程 for...range 遍历
• 解决方法:
•用 sync.RWMutex 加锁
•使用线程安全 Map,比如 sync.Map{}
----------------------
案例3:kube-proxy 消耗 10 个 CPU
通过 pprof 分析内存占用情况
curl 127.0.0.1:10249/debug/pprof/heap?debug=2
1: 245760 [301102: 73998827520] @ 0x11ddcda 0x11f306e 0x11f35f5 0x11fbdce 0x1204a8a 0x114ed76
0x114eacb 0x11
#
0x11ddcd9
k8s.io/kubernetes/vendor/github.com/vishvananda/netlink.(*Handle).RouteListFiltered+0x679
#0x11f306dk8s.io/kubernetes/pkg/proxy/ipvs.(*netlinkHandle).GetLocalAddresses+0xed
#0x11f35f4k8s.io/kubernetes/pkg/proxy/ipvs.(*realIPGetter).NodeIPs+0x64
#0x11fbdcdk8s.io/kubernetes/pkg/proxy/ipvs.(*Proxier).syncProxyRules+0x47dd
----------------------
案例3:kube-proxy 消耗 10 个
• heap dump 分析
•GetLocalAddresses 函数调用创建了 301102 个对象,占用内存 73998827520
•如此多的对象被创建,显然会导致 kube-proxy 进程忙于 GC,占用大量 CPU
•对照代码分析 GetLocalAddresses 的实现,发现该函数的主要目的是获取节点本机 IP 地址,获取的方法是
通过 ip route 命令获得当前节点所有 local 路由信息并转换成 go struct 并过滤掉 ipvs0网口上的路由信息
•ip route show table local type local proto kernel
•因为集群规模较大,该命令返回 5000 条左右记录,因此每次函数调用都会有数万个对象被生成
•而 kube-proxy 在处理每一个服务的时候都会调用该方法,因为集群有数千个服务,因此,kube-proxy在
反复调用该函数创建大量临时对象
• 修复方法
•函数调用提取到循环外
•https://github.com/kubernetes/kubernetes/pull/79444
----------------------
案例4:线程池耗尽
• 问题描述:
在 Kubernetes 中有一个控制器,叫做 endpoint controller,该控制器符合生产者消费者模式,默认有5
个 worker 线程作为消费者。该消费者在处理请求时,可能调用的 LBaaS 的 API 更新负载均衡配置。我们
发现该控制器会时不时不工作,具体表现为,该做的配置变更没发生,相关日志也不打印了。
• 根因分析:
通过 pprof 打印出该进程的所有 go routine 信息,发现 worker 线程都卡在 http 请求调用处。
当worker线程调用 LBaaS API 时,底层是 net/http 包调用,而客户端在发起连接请求时,未设置客户端
超时时间。这导致当出现某些网络异常时,客户端会永远处于等待状态。
• 解决方法:
修改代码加入客户端超时控制。
----------------------
课后练习 2.2
• 编写一个 HTTP 服务器
接收客户端请求并将请求的 Header 打印出来返回给客户端
----------------------
end

http server例子:
cncamp/golang$ go mod tidy
zxl@qwq:~/go/src/github.com/cncamp/golang$ cd httpserver/
zxl@qwq:~/go/src/github.com/cncamp/golang/httpserver$ go build main.go
# command-line-arguments
./main.go:7:2: undefined: glog
zxl@qwq:~/go/src/github.com/cncamp/golang/httpserver$ 
增加了glog后,不能更新?

重新设置环境
golang$ export GOROOT=/usr/local/go/
golang$ export GOPATH=$HOME/go
golang$ GOPROXY=https://goproxy.cn
golang$ code

$ go get github.com/golang/glog
go: downloading github.com/golang/glog v1.0.0
go: added github.com/golang/glog v1.0.0
zxl@qwq:~/go/src/github.com/cncamp/golang$ cat go.mod
module github.com/cncamp/golang

go 1.18

require github.com/stretchr/testify v1.8.0

require (
        github.com/davecgh/go-spew v1.1.1 // indirect
        github.com/golang/glog v1.0.0 // indirect
        github.com/pmezard/go-difflib v1.0.0 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
)
然后就可以使用了
http://localhost/?user=hehe
可以返回:
hello [hehe]
===================Details of the http request header:============
Sec-Ch-Ua-Platform=["Linux"]
Sec-Fetch-Dest=[document]
Accept-Language=[zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6]
Accept=[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9]
Sec-Fetch-Site=[none]
Accept-Encoding=[gzip, deflate, br]
Connection=[keep-alive]
Sec-Ch-Ua-Mobile=[?0]
Upgrade-Insecure-Requests=[1]
Sec-Ch-Ua=["Chromium";v="104", " Not A;Brand";v="99", "Microsoft Edge";v="104"]
User-Agent=[Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36 Edg/104.0.1293.70]
Sec-Fetch-Mode=[navigate]
Sec-Fetch-User=[?1]
 

HTTPSERVER代码:

package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"

	"github.com/golang/glog"
)

func main() {
	flag.Set("v", "4")
	flag.Parse()
	glog.V(2).Info("Starting http server...")
	http.HandleFunc("/", rootHandler)
	c, python, java := true, false, "no!"
	fmt.Println(c, python, java)
	err := http.ListenAndServe(":80", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func healthz(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "ok\n")
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Println("entering root handler")
	user := r.URL.Query().Get("user")
	if user != "" {
		io.WriteString(w, fmt.Sprintf("hello [%s]\n", user))
	} else {
		io.WriteString(w, "hello [stranger]\n")
	}
	io.WriteString(w, "===================Details of the http request header:============\n")
	for k, v := range r.Header {
		io.WriteString(w, fmt.Sprintf("%s=%s\n", k, v))
	}
}