51Reboot 将在 2020.1.16日 21:00 为您带来分享主题
《大佬教你如何从 ES 初学者到 ES专家》
参与方式:
再次整理了一下这个日志收集系统的框,如下图来源
https://www.cnblogs.com/zhaof/p/8910761.html
- 服务发现和服务注册
- 配置中心(我们实现的日志收集客户端需要用到)
- 分布式锁
- master 选举
[root@localhost etcd-v3.2.18-linux-amd64]# ./etcdctl set name zhaofanzhaofan[root@localhost etcd-v3.2.18-linux-amd64]# ./etcdctl get namezhaofan[root@localhost etcd-v3.2.18-linux-amd64]#
context 介绍和使用
其实这个东西翻译过来就是上下文管理,那么 context 的作用是做什么,主要有如下两个作用:
- 控制 goroutine 的超时
- 保存上下文数据
package mainimport ( "fmt" "time" "net/http" "context" "io/ioutil")type Result struct{ r *http.Response err error}func process(){ ctx,cancel := context.WithTimeout(context.Background(),2*time.Second) defer cancel() tr := &http.Transport{} client := &http.Client{Transport:tr} c := make(chan Result,1) req,err := http.NewRequest("GET","http://www.google.com",nil) if err != nil{ fmt.Println("http request failed,err:",err) return } // 如果请求成功了会将数据存入到管道中 go func(){ resp,err := client.Do(req) pack := Result{resp,err} c }() select{ case tr.CancelRequest(req) fmt.Println("timeout!") case res := defer res.r.Body.Close() out,_:= ioutil.ReadAll(res.r.Body) fmt.Printf("server response:%s",out) } return}func main() { process()}
写一个通过 context 保存上下文,代码例子如:
package mainimport ( "github.com/Go-zh/net/context" "fmt")func add(ctx context.Context,a,b int) int { traceId := ctx.Value("trace_id").(string) fmt.Printf("trace_id:%v\n",traceId) return a+b}func calc(ctx context.Context,a, b int) int{ traceId := ctx.Value("trace_id").(string) fmt.Printf("trace_id:%v\n",traceId) //再将ctx传入到add中 return add(ctx,a,b)}func main() { //将ctx传递到calc中 ctx := context.WithValue(context.Background(),"trace_id","123456") calc(ctx,20,30)}
结合 etcd 和 context 使用
关于通过 go 连接 etcd 的简单例子:
(这里有个小问题需要注意就是 etcd 的启动方式,默认启动可能会连接不上,尤其你是在虚拟你安装,所以需要通过如下命令启动:
./etcd --listen-client-urls http://0.0.0.0:2371 --advertise-client-urls http://0.0.0.0:2371 --listen-peer-urls http://0.0.0.0:2381
)
package mainimport ( etcd_client "github.com/coreos/etcd/clientv3" "time" "fmt")func main() { cli, err := etcd_client.New(etcd_client.Config{ Endpoints:[]string{"192.168.0.118:2371"}, DialTimeout:5*time.Second, }) if err != nil{ fmt.Println("connect failed,err:",err) return } fmt.Println("connect success") defer cli.Close()}
下面一个例子是通过连接 etcd,存值并取值
package mainimport ( "github.com/coreos/etcd/clientv3" "time" "fmt" "context")func main() { cli,err := clientv3.New(clientv3.Config{ Endpoints:[]string{"192.168.0.118:2371"}, DialTimeout:5*time.Second, }) if err != nil{ fmt.Println("connect failed,err:",err) return } fmt.Println("connect succ") defer cli.Close() ctx,cancel := context.WithTimeout(context.Background(),time.Second) _,err = cli.Put(ctx,"logagent/conf/","sample_value") cancel() if err != nil{ fmt.Println("put failed,err",err) return } ctx, cancel = context.WithTimeout(context.Background(),time.Second) resp,err := cli.Get(ctx,"logagent/conf/") cancel() if err != nil{ fmt.Println("get failed,err:",err) return } for _,ev := range resp.Kvs{ fmt.Printf("%s:%s\n",ev.Key,ev.Value) }}
关于 context 官网也有一个例子非常有用,用于控制开启的 goroutine 的退出,代码如下:
package mainimport ( "context" "fmt")func main() { // gen generates integers in a separate goroutine and // sends them to the returned channel. // The callers of gen need to cancel the context once // they are done consuming generated integers not to leak // the internal goroutine started by gen. gen := func(ctx context.Context) chan int { dst := make(chan int) n := 1 go func() { for { select { case return // returning not to leak the goroutine case dst n++ } } }() return dst } ctx, cancel := context.WithCancel(context.Background()) defer cancel() // cancel when we are finished consuming integers for n := range gen(ctx) { fmt.Println(n) if n == 5 { break } }}
关于官网文档中的 WithDeadline 演示的代码例子:
package mainimport ( "context" "fmt" "time")func main() { d := time.Now().Add(50 * time.Millisecond) ctx, cancel := context.WithDeadline(context.Background(), d) // Even though ctx will be expired, it is good practice to call its // cancelation function in any case. Failure to do so may keep the // context and its parent alive longer than necessary. defer cancel() select { case After(1 * time.Second): fmt.Println("overslept") case Done(): fmt.Println(ctx.Err()) }}
通过上面的代码有了一个基本的使用,那么如果我们通过 etcd 来做配置管理,如果配置更改之后,我们如何通知对应的服务器配置更改,通过下面例子演示:
package mainimport ( "github.com/coreos/etcd/clientv3" "time" "fmt" "context")func main() { cli,err := clientv3.New(clientv3.Config{ Endpoints:[]string{"192.168.0.118:2371"}, DialTimeout:5*time.Second, }) if err != nil { fmt.Println("connect failed,err:",err) return } defer cli.Close() // 这里会阻塞 rch := cli.Watch(context.Background(),"logagent/conf/") for wresp := range rch{ for _,ev := range wresp.Events{ fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) } }}
实现一个 kafka 的消费者代码的简单例子:
package mainimport ( "github.com/Shopify/sarama" "strings" "fmt" "time")func main() { consumer,err := sarama.NewConsumer(strings.Split("192.168.0.118:9092",","),nil) if err != nil{ fmt.Println("failed to start consumer:",err) return } partitionList,err := consumer.Partitions("nginx_log") if err != nil { fmt.Println("Failed to get the list of partitions:",err) return } fmt.Println(partitionList) for partition := range partitionList{ pc,err := consumer.ConsumePartition("nginx_log",int32(partition),sarama.OffsetNewest) if err != nil { fmt.Printf("failed to start consumer for partition %d:%s\n",partition,err) return } defer pc.AsyncClose() go func(partitionConsumer sarama.PartitionConsumer){ for msg := range pc.Messages(){ fmt.Printf("partition:%d Offset:%d Key:%s Value:%s",msg.Partition,msg.Offset,string(msg.Key),string(msg.Value)) } }(pc) } time.Sleep(time.Hour) consumer.Close()}
但是上面的代码并不是最佳代码,因为我们最后是通过 time.sleep 等待 goroutine 的执行,我们可以更改为通过 sync.WaitGroup 方式实现
package mainimport ( "github.com/Shopify/sarama" "strings" "fmt" "sync")var ( wg sync.WaitGroup)func main() { consumer,err := sarama.NewConsumer(strings.Split("192.168.0.118:9092",","),nil) if err != nil{ fmt.Println("failed to start consumer:",err) return } partitionList,err := consumer.Partitions("nginx_log") if err != nil { fmt.Println("Failed to get the list of partitions:",err) return } fmt.Println(partitionList) for partition := range partitionList{ pc,err := consumer.ConsumePartition("nginx_log",int32(partition),sarama.OffsetNewest) if err != nil { fmt.Printf("failed to start consumer for partition %d:%s\n",partition,err) return } defer pc.AsyncClose() go func(partitionConsumer sarama.PartitionConsumer){ wg.Add(1) for msg := range partitionConsumer.Messages(){ fmt.Printf("partition:%d Offset:%d Key:%s Value:%s",msg.Partition,msg.Offset,string(msg.Key),string(msg.Value)) } wg.Done() }(pc) } //time.Sleep(time.Hour) wg.Wait() consumer.Close()}
将客户端需要收集的日志信息放到 etcd 中
关于 etcd 处理的代码为:
package mainimport ( "github.com/coreos/etcd/clientv3" "time" "github.com/astaxie/beego/logs" "context" "fmt")var Client *clientv3.Clientvar logConfChan chan string// 初始化etcdfunc initEtcd(addr []string,keyfmt string,timeout time.Duration)(err error){ var keys []string for _,ip := range ipArrays{ //keyfmt = /logagent/%s/log_config keys = append(keys,fmt.Sprintf(keyfmt,ip)) } logConfChan = make(chan string,10) logs.Debug("etcd watch key:%v timeout:%v", keys, timeout) Client,err = clientv3.New(clientv3.Config{ Endpoints:addr, DialTimeout: timeout, }) if err != nil{ logs.Error("connect failed,err:%v",err) return } logs.Debug("init etcd success") waitGroup.Add(1) for _, key := range keys{ ctx,cancel := context.WithTimeout(context.Background(),2*time.Second) // 从etcd中获取要收集日志的信息 resp,err := Client.Get(ctx,key) cancel() if err != nil { logs.Warn("get key %s failed,err:%v",key,err) continue } for _, ev := range resp.Kvs{ logs.Debug("%q : %q\n", ev.Key, ev.Value) logConfChan string(ev.Value) } } go WatchEtcd(keys) return}func WatchEtcd(keys []string){ // 这里用于检测当需要收集的日志信息更改时及时更新 var watchChans []clientv3.WatchChan for _,key := range keys{ rch := Client.Watch(context.Background(),key) watchChans = append(watchChans,rch) } for { for _,watchC := range watchChans{ select{ case wresp := for _,ev:= range wresp.Events{ logs.Debug("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) logConfChan string(ev.Kv.Value) } default: } } time.Sleep(time.Second) } waitGroup.Done()}func GetLogConf()chan string{ return logConfChan}
同样的这里增加对了限速的处理,毕竟日志收集程序不能影响了当前业务的性能,所以增加了 limit.go 用于限制速度:
package mainimport ( "time" "sync/atomic" "github.com/astaxie/beego/logs")type SecondLimit struct { unixSecond int64 curCount int32 limit int32}func NewSecondLimit(limit int32) *SecondLimit { secLimit := &SecondLimit{ unixSecond:time.Now().Unix(), curCount:0, limit:limit, } return secLimit}func (s *SecondLimit) Add(count int) { sec := time.Now().Unix() if sec == s.unixSecond { atomic.AddInt32(&s.curCount,int32(count)) return } atomic.StoreInt64(&s.unixSecond,sec) atomic.StoreInt32(&s.curCount, int32(count))}func (s *SecondLimit) Wait()bool { for { sec := time.Now().Unix() if (sec == atomic.LoadInt64(&s.unixSecond)) && s.curCount == s.limit { time.Sleep(time.Microsecond) logs.Debug("limit is running,limit:%d s.curCount:%d",s.limit,s.curCount) continue } if sec != atomic.LoadInt64(&s.unixSecond) { atomic.StoreInt64(&s.unixSecond,sec) atomic.StoreInt32(&s.curCount,0) } logs.Debug("limit is exited") return false }}
小结
这次基本实现了日志收集的前半段的处理,后面将把日志扔到 es 中,并最终在页面上呈现51Reboot 课程信息最新一期 Golang 课程
开课时间:2020.2.16
1、GO 并发
goroutine
channel
有缓存 chan 和无缓存 chan,以及通信的一些细节
常见并发模式
锁与条件竞争
实战
并发 Web 爬虫
2、Beego 实战
Go Web 浅析
HTTP 协议
net/http
Gin/Beego 框架对比介绍
请求校验包的使用
中间件 middleware ORM
日志模块
3、堡垒机权限管理系统
架构设计
HTTP 协议 加密传输
分布式 Agent/Server 开发
Beego 可视化 Web 界面统管
思考:如何对接服务树系统
4、分布式监控
架构设计
GRPC 传输
Etcd 服务注册发现
Influxdb 存储
Granfan 展示
实战项目一:SQL 自动化上线平台
手动 VS 自动的现状对比
Mysql、Inception、SQLAdvisor 讲解
用户权限设计、执行流程梳理、平台登录双因子安全认证
敏感配置加解密实现、人员/数据库配置设计,整体代码实现
实战项目二:公有云管理平台
通过 API 对[Aws、阿里云、腾讯云、青云、百度云]管理
公有云账单、比价、资源管理
实战项目三:任务管理系统
Ansible 简介及常用场景分析
Ansible API 二次开发入门
基于 Ansible Playbook API 快速实现任务管理系统
实战项目四:工单系统
工作中流程规范的设计思路
典型工单系统的实现原理-状态机
实战项目五:代码管理系统及发布平台
基于 Gitlab + Jenkins + DevOps 平台实现 CI/CD 的设计思路
结合运维平台流程规范实现持续集成与交付
结合 ELK 的日志分析平台,实现代码上线运维无人值守
第一阶段:Docker 基础与进阶
第二阶段:Kubernetes 基础
第三阶段:Pod 与生命周期管理
Kubernetes 集群资源管理与调度管理
Kubernetes 控制器和常用资源对象
第四阶段:Kubernetes 服务发现
持久化存储
第五阶段:Helm 包管理工具
第六阶段:Kubernetes 集群网络
Kubernetes 集群网络常用方案比较及选型建议
Flannel 网络组件详解
Flannel 网络组件配置及应用
Flannel 生产环境应用经验
Calico 网络组件详解
Calico 网络组件配置及应用
......
第七阶段:Kubernetes 集群监控
Prometheus 介绍
部署 Prometheus
监控 Kubernetes 集群及应用
NodeExporter 的安装使用
Prometheus的自动发现
Kubernetes常用资源对象监控
Grafana的安装与使用
Grafana的插件与监控
.......
第八阶段:日志收集
日志收集架构
Elasticsearch 集群
Kibana 可视化组件
Fluentd 采集组件
生产环境采集日志方案详解
第九、十阶段:DevOps
动态 Jenkins Slave
Jenkins Pipeline
Jenkins Blue Ocean
Harbor 详解
Gitlab 安装与使用
Gitlab CI Runner
Gitlab CI 示例
Kubernetes 开源管理平台
完整 devops 项目实例
![3eee5f0e081b3fe15f7c93eafd085e64.png](https://i-blog.csdnimg.cn/blog_migrate/5f40377ff29f99a173e574c491f18e1d.jpeg)
关注我们 ,一起成长吧!