P144 今日内容
P145 上周作业思路
带参数的GET请求示例
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
apiUrl := "http://127.0.0.1:9090/get"
// URL param
data := url.Values{}
data.Set("name", "小王子")
data.Set("age", "18")
u, err := url.ParseRequestURI(apiUrl)
if err != nil {
fmt.Printf("parse url requestUrl failed, err:%v\n", err)
}
u.RawQuery = data.Encode() // URL encode
fmt.Println(u.String())
resp, err := http.Get(u.String())
if err != nil {
fmt.Printf("post failed, err:%v\n", err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("get resp failed, err:%v\n", err)
return
}
fmt.Println(string(b))
}
Post请求示例
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
// net/http post demo
func main() {
url := "http://127.0.0.1:9090/post"
// 表单数据
//contentType := "application/x-www-form-urlencoded"
//data := "name=小王子&age=18"
// json
contentType := "application/json"
data := `{"name":"小王子","age":18}`
resp, err := http.Post(url, contentType, strings.NewReader(data))
if err != nil {
fmt.Printf("post failed, err:%v\n", err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("get resp failed, err:%v\n", err)
return
}
fmt.Println(string(b))
}
对应的Server端HandlerFunc如下:
func postHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
// 1. 请求类型是application/x-www-form-urlencoded时解析form数据
r.ParseForm()
fmt.Println(r.PostForm) // 打印form数据
fmt.Println(r.PostForm.Get("name"), r.PostForm.Get("age"))
// 2. 请求类型是application/json时从r.Body读取数据
b, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("read request.Body failed, err:%v\n", err)
return
}
fmt.Println(string(b))
answer := `{"status": "ok"}`
w.Write([]byte(answer))
}
P146 go module简单介绍
go开发的时候一开始没有考虑依赖,包的管理,现在下载的包都在这里。但是现在还没有一个项目告诉你需要依赖什么包,也不能共存不同版本的包。
一开始使用了一个vender文件夹里,不同项目使用不同的vender
开启之后会根据,go.mod里的清单去找依赖。
可以设置代理去下载go代码
类似于webpack
replace可以替换,访问代码的原地址访问不到,可以替换地址访问
老项目可以在项目目录执行go mod init,会生成一个go.mod文件
生成了一个go.mod文件
会去下载包
P147 contex初识
如何优雅控制goroutine的退出,之前是使用wg管理,线程执行完,wg-1,做完了就退出
现在是一直卡着等待执行完,如何可以通知进行退出
5秒后告诉你该退出了。
这是第一种用全局变量的方式
第二种用channel
break loop相当于指定跳出谁,就跳出谁
rowloop就是变量名代表冒号后面的缩进都是它的
这是用之前学的东西,如何让goroutine进行退出。但是这样每个人写的就不标准,就有了context,就是为了解决多个goroutine协同的问题。
多个goroutine嵌套的时候,如何实现后面的goroutine超时通知退出
如何用context解决之前的问题.
执行的时候把context传进去。
如果ctx.Done就直接退出。
这样就退出了
context可以再启动一个goroutine,现在有两个任务
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func worker(ctx context.Context) {
go worker2(ctx)
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case <-ctx.Done(): // 等待上级通知
break LOOP
default:
}
}
wg.Done()
}
func worker2(ctx context.Context) {
LOOP:
for {
fmt.Println("worker2")
time.Sleep(time.Second)
select {
case <-ctx.Done(): // 等待上级通知
break LOOP
default:
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 3)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
通过一个context,让上面起的两个goroutine关闭。等于用一个跟目录写一个ctx,只要一个超时,下面的goroutine所有都超时
context去遍历子节点,我们传 的时候传父节点
P148 context内容介绍
context这个类型其实就是interface,简化处理单个请求的多个goroutine
这两个函数返回的就是context类型
当时间结束相当于context过期就退出了
即使超时了也要cancel
WithValue
WithValue函数能够将请求作用域的数据与 Context 对象建立关系。声明如下:
func WithValue(parent Context, key, val interface{}) Context
WithValue返回父节点的副本,其中与key关联的值为val。
仅对API和进程间传递请求域的数据使用上下文值,而不是使用它来传递可选参数给函数。
所提供的键必须是可比较的,并且不应该是string类型或任何其他内置类型,以避免使用上下文在包之间发生冲突。WithValue的用户应该为键定义自己的类型。为了避免在分配给interface{}时进行分配,上下文键通常具有具体类型struct{}。或者,导出的上下文关键变量的静态类型应该是指针或接口。
package main
import (
"context"
"fmt"
"sync"
"time"
)
// context.WithValue
type TraceCode string
var wg sync.WaitGroup
func worker(ctx context.Context) {
key := TraceCode("TRACE_CODE")
traceCode, ok := ctx.Value(key).(string) // 在子goroutine中获取trace code
if !ok {
fmt.Println("invalid trace code")
}
LOOP:
for {
fmt.Printf("worker, trace code:%s\n", traceCode)
time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
select {
case <-ctx.Done(): // 50毫秒后自动调用
break LOOP
default:
}
}
fmt.Println("worker done!")
wg.Done()
}
func main() {
// 设置一个50毫秒的超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
// 在系统的入口中设置trace code传递给后续启动的goroutine实现日志数据聚合
ctx = context.WithValue(ctx, TraceCode("TRACE_CODE"), "12512312234")
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 5)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
P149 上午内容复习
go1.11开始才启用go module,需要手动开启,后面1.13就默认启用
go.mod记录了所有当前项目依赖包的信息和版本信息
这个是适用新的项目
导入包的时候,就import hello
hello这个包现在并不在gopath下,这样在gomodule使用前是肯定不行的
context.Background默认返回父节点,根节点
context.TODO()当不知道传什么的时候,需要接收一个context的变量,就使用TODO,调用别人的包一定要传一个context变量的时候可以用TODO
withvalue可以给上下文加一下值
P150 日志收集项目架构
logagent具体部署到每台服务器上,根据配置项去收集日志,配置存储到etcd里,etcd用页面管理,logagent从最新的etcd里拿到配置
log transfer把日志取出来发到es和hdaoop里
P151 kafika介绍
同时多个消费者订阅消息
**生产者+kafka集群+消费者。
**
kafka集群的节点称为broker。
topic订阅的主题
partition 分区
分区角色,leader,follower,leader挂了,follower可以升级为leader
leader确认发送消息成功,leader告诉follower来拉取新数据,拉取后,follower告诉leader拉取成功。
all代表需要所有follower都要发送成功。
有三个分区
每个partition都是一个有序并 且不可变的消息记录集合。当新的数据写入时,就被追加到partition的末
尾。在每个partition中,每条消息都会被分配一个顺序的唯一标识, 这个标识被称为offset,即偏移
量。注意,Kafka只保证在同一个partition内部消息是有序的,在不同partition之间, 并不能保证消息
有序。
Kafka可以配置一个保留期限,用来标识日志会在Kafka集群内保留多长时间。Kafka集 群会保留在保留
期限内所有被发布的消息,不管这些消息是否被消费过。比如保留期限设置为两天,那么数据被发布到
Kafka集群的两天以内,所有的这些数据都可以被消费。当超过两天,这些数据将会被清空,以便为后
续的数据腾出空间。由于Kafka会将数据进行持久化存储(即写入到硬盘上),所以保留的数据大小可
以设置为一个比较大的值。
Partition在服务器上的表现形式就是一个- 个的文件夹,每个partition的文件夹下面会有多组segment
文件,每组segment文件又包含. index文件、. log文件、。 timeindex文件三个文件,其中.1og文
件就是实际存储message的地方,而. index和.timeindex文件为索引文件,用于检索消息。
kafka集群里有两台机器,上面各有2个分区。有两个消费者组。
在同一个消费者组中,每个消费者实例可以消费多个分区,但是每个分区最多只能被消费者组中的一个实例消费。
c1如果从p0-p3取了,c2就不能,只能从p1和p2取
为什么快,每个消息都有文件记录位置
P152 kafka启动
安装kafka需要提前安装jdk
配置环境变量
kafka自带了一个zookeeper,查看配置文件
windows执行bat启动脚本
在本机的2181
kafka的配置文件
brokerid。唯一标识
windows起,需要用管理员
P153 zookeeper工作机制
在服务之间调用的时候 ,假如服务后端对应多个节点,就需要有一个登记的地方让调用者知道该如何调用
P154 tailf模块介绍及使用
先去下载包
发生错误的话还是根据上次的记录位置继续记录,保证连续性
一行一行读日志
P155 sarama模块及使用
sarama在go1.20之后假如了zstd压缩,需要gcc
现在就有版本号了
设置代理
config方式给字段设置值
日志级别归根到底是传的数字,这个数字和log level绑定在一起
指定一个topic
现在只有一个分区,里面是真正存数据的log日志文件,索引,时间索引
P156 logAgent实现
上面kafka和taillog两个实现不通的功能
kafka初始化成功就相当于全局变量可用
发送kafka信息
写logagent入口
在main函数里就要做一些操作
这样写,需要加载多少个组件就加载多少个组件 ,很清晰
无限循环读日志
可以把读日志放到一个channel里
logagent进行读取日志
go build
go mod init
报错,传的类型错误
上面是代码依赖和上面的依赖不一样,就使用tidy纠正一下
打印日志查看程序执行到哪一步
现在就等待my.log里面写日志 了
现在是放到kafka了,可以起一个消费者
往日志文件里写,taillog模块从文件里读,发到kafka,消费者终端从kafka里读
P157 配置文件版LogAgent
可以 写个ini配置文件
加载配置好的ini文件
现在信息就出来了
这样可以转换成string
可以造一个结构体对应配置