Go之路(三十六):补充知识

os/exce模块

package main

import (
	"fmt"
	"os/exec"
)

var (
	output []byte
	err error
)

func main()  {
	cmd := exec.Command("/bin/bash","-c","ls")

	if output,err = cmd.CombinedOutput();err != nil{
		fmt.Println(err)
	}
	fmt.Println(string(output))
}

  这是基本的用法,最后输出结果需要用string强制转换一下。

demo2:杀死一个进程,需要用到上下文管理器,context里面的WithCancel

 

package main

import (
	"fmt"
	"golang.org/x/net/context"
	"os/exec"
	"time"
)



func main()  {
	var (
		cmd *exec.Cmd
		ctx context.Context
		CancelFunc context.CancelFunc
		result chan []byte
		res []byte
	)

	ctx,CancelFunc = context.WithCancel(context.TODO())


	go func() {
		var (
			output []byte
			err error
		)
		result = make(chan []byte,1000)
		cmd = exec.CommandContext(ctx,"/bin/bash","-c","sleep 2;echo hello")
		if output,err = cmd.CombinedOutput();err !=nil{
			fmt.Println(err)
		}

		result <- output

	}()

	time.Sleep(time.Second)

	CancelFunc()

	res = <- result
	fmt.Println(string(res))
}

  

cronexpr模块 

package main

import (
	"fmt"
	"github.com/gorhill/cronexpr"
	"time"
)

func main()  {
	var (
		expr *cronexpr.Expression
		err error
		now time.Time
		NextTime time.Time
	)
	if expr , err = cronexpr.Parse("* * * * * * *"); err != nil{
		fmt.Println(err)
	}

	//fmt.Println(expr)
	//当前时间
	now = time.Now()

	//下次调度时间
	NextTime = expr.Next(now)

	//定时器
	time.AfterFunc(NextTime.Sub(now), func() {
		fmt.Println("被调度了:",NextTime)
	})

	time.Sleep(5*time.Second)
}

  cron基础知识:

f1 f2 f3 f4 f5 program
  • 其中 f1 是表示分钟,f2 表示小时,f3 表示一个月份中的第几日,f4 表示月份,f5 表示一个星期中的第几天。program 表示要执行的程序。
  • 当 f1 为 * 时表示每分钟都要执行 program,f2 为 * 时表示每小时都要执行程序,其馀类推
  • 当 f1 为 a-b 时表示从第 a 分钟到第 b 分钟这段时间内要执行,f2 为 a-b 时表示从第 a 到第 b 小时都要执行,其馀类推
  • 当 f1 为 */n 时表示每 n 分钟个时间间隔执行一次,f2 为 */n 表示每 n 小时个时间间隔执行一次,其馀类推
  • 当 f1 为 a, b, c,... 时表示第 a, b, c,... 分钟要执行,f2 为 a, b, c,... 时表示第 a, b, c...个小时要执行,其馀类推

这个模块比linux本身的强大一些,支持到秒

时间加减需要用到sub函数。

 

demo:同时执行多个任务并且检查任务列表

package main

import (
	"fmt"
	"github.com/gorhill/cronexpr"
	"time"
)

type Task struct {
	expr *cronexpr.Expression
	NextTime time.Time
}

func main()  {
	var (
		expr *cronexpr.Expression
		now time.Time
		err error
		TaskTable map[string]*Task
		jobname string
		job *Task
	)

	TaskTable = make(map[string]*Task)

	//创建任务并且添加到任务调度表之中
	if expr,err = cronexpr.Parse("*/5 * * * * * *"); err != nil{
		fmt.Println(err)
	}
	now = time.Now()
	TaskTable["job1"] = &Task{
		expr:expr,
		NextTime:now,
	}

	if expr,err = cronexpr.Parse("*/5 * * * * * *"); err != nil{
		fmt.Println(err)
	}
	now = time.Now()
	TaskTable["job2"] = &Task{
		expr:expr,
		NextTime:now,
	}

	//另起一个协程来检查任务
	go func() {
		for{


			for jobname,job = range TaskTable{
				if job.NextTime.Equal(time.Now()) || job.NextTime.Before(time.Now()){
                        // 执行任务
					go func(jobname string) {
						fmt.Println("执行任务:",jobname)
					}(jobname)

                                        //执行任务完成后更新下次时间
					job.NextTime = job.expr.Next(job.NextTime)

				}
			}

              //相当于等待100毫秒
			select {
			case <-time.NewTimer(100*time.Microsecond).C:

			}
		}
	}()

	time.Sleep(100*time.Second)

}

  Etcd的使用:

package main

import (
	"fmt"
	"go.etcd.io/etcd/clientv3"
	"golang.org/x/net/context"
	"time"
)

func main()  {
	var (
		config clientv3.Config
		client *clientv3.Client
		err error
		kv clientv3.KV
		setresponse *clientv3.PutResponse
		getresponse *clientv3.GetResponse
	)


	config = clientv3.Config{
		Endpoints:[]string{"127.0.0.1:2379"},
		DialTimeout:time.Second,
	}

	if client,err = clientv3.New(config);err !=nil{
		fmt.Println(err)
		return
	}


	//用于操作kv的客户端
	kv = clientv3.NewKV(client)
	if setresponse,err = kv.Put(context.TODO(),"/home/miracle","test",clientv3.WithPrevKV()); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(setresponse.Header.Revision)
	fmt.Println(string(setresponse.PrevKv.Value))




	getresponse,_ = kv.Get(context.TODO(),"/home/miracle")
	fmt.Println(getresponse.Kvs)




}

  

if setresponse,err = kv.Put(context.TODO(),"/home/miracle","test",clientv3.WithPrevKV()); err != nil
如果需要获取之前版本的值,需要设置withPrekv。
put等操作是阻塞的,如果没有开启etcd则会一直等待,所以需要用ctx来设置超时。
直接导入github/etcd/会出错,用
"go.etcd.io/etcd/clientv3"代替

另外,可以在操作中选择其他进阶操作,比如以...开头的kvs
	kv = clientv3.NewKV(client)

	getresponse,err = kv.Get(context.TODO(),"/home/",clientv3.WithPrefix())
	fmt.Println(getresponse.Kvs)

  with...方法有很多

 

 

delete:

	delresponse,_ = kv.Delete(context.TODO(),"/home/miracle")
	fmt.Println(delresponse.Deleted)

	if delresponse.Deleted == 0{
		fmt.Println("删除失败,kv不存在!")
	}

判断是否删除成功可以用deleted属性,如果等于0说明不存在,如果等于1说明删除成功。

 

租约:

lease = clientv3.NewLease(client)
	leaseresp,_ = lease.Grant(context.TODO(),10)
	leaseid = leaseresp.ID
	kv  = clientv3.NewKV(client)

	_,_ = kv.Put(context.TODO(),"/home/miracle/job","just a test",clientv3.WithLease(leaseid))


	for{
		getresp,_ = kv.Get(context.TODO(),"/home/miracle/job")
		if getresp.Kvs == nil{
			fmt.Println("kv过期了")
		}else {
			fmt.Println(getresp.Kvs)
		}

		time.Sleep(2*time.Second)
		break
	}

  需要先获取到lease客户端,然后获取到id,通过Withlease来关联租约

 

自动续租:

//自动续租
	reschan ,_ = lease.KeepAlive(context.TODO(),leaseid)

	go func() {
		for{
			select {
			case keepaliveres = <- reschan:
				if reschan == nil {
					fmt.Println("租约失效")
					goto END
				}else{
					fmt.Println("续约成功:",keepaliveres.ID)
				}
			}
		}
		END:
	}()


	for{
		getresp,_ = kv.Get(context.TODO(),"/home/miracle/job")
		if getresp.Kvs == nil{
			fmt.Println("kv过期了")
			break
		}else {
			fmt.Println(getresp.Kvs)
		}

		time.Sleep(2*time.Second)

	}

 当调用lease.KeepAlive(context.TODO(),leaseid)这个方法的时候,内部会起一个协程,来自动续租,返回一个channel

这时候就需要手动去起另外一个协程去消费里面的数据,当reschan为空的时候有可能是网络问题导致续租失败。

Watcher:监听一个值的变化
package main

import (
	"fmt"
	"go.etcd.io/etcd/clientv3"
	"golang.org/x/net/context"
	"time"
)

func main()  {

	var (
		config clientv3.Config
		client *clientv3.Client
		kv clientv3.KV
		watcher clientv3.Watcher
		watchan clientv3.WatchChan
		watres clientv3.WatchResponse
		watevent *clientv3.Event
		err error
	)

	config = clientv3.Config{
		Endpoints:[]string{"127.0.0.1:2379"},
		DialTimeout:5*time.Second,
	}

	if client,err = clientv3.New(config); err != nil{
		fmt.Println(err)
		return
	}

	//创建客户端
	kv = clientv3.NewKV(client)
	watcher = clientv3.NewWatcher(client)

	go func() {
		for{
			kv.Put(context.TODO(),"/home/miracle/job1","i am job1")

			kv.Delete(context.TODO(),"/home/miracle/job1")
			time.Sleep(time.Second)
		}
	}()

	watchan  = watcher.Watch(context.TODO(),"/home/miracle/job1")
	for watres = range watchan{
		for _,watevent = range watres.Events{
			switch watevent.Type {
			case 0:
				fmt.Println("Put:",string(watevent.Kv.Value))
			case 1:
				fmt.Println("delete")
			}
		}
	}
}

  监听后返回一个watchchan  里面有存着event的数组,数组里面的event记录了每一次的操作,0代表put,1代表delete。 

另外,可以从指定版本开始监听

 

可以用op操作代替上面的kv.put等操作

package main

import (
	"fmt"
	"go.etcd.io/etcd/clientv3"
	"golang.org/x/net/context"
	"time"
)

func main()  {

	var (
		config clientv3.Config
		client *clientv3.Client
		err error
		kv clientv3.KV
		opresp clientv3.OpResponse
		put clientv3.Op
		get clientv3.Op
	)

	config = clientv3.Config{
		Endpoints:[]string{"127.0.0.1:2379"},
		DialTimeout:5*time.Second,
	}

	if client,err  = clientv3.New(config); err != nil{
		fmt.Println(err)
		return
	}

	kv = clientv3.NewKV(client)

	put = clientv3.OpPut("/home/miracle/job2","i am job2")
	opresp,err = kv.Do(context.TODO(),put)

	fmt.Println(opresp.Put().Header.Revision)

	get = clientv3.OpGet("/home/miracle/job2")
	opresp,err = kv.Do(context.TODO(),get)
	fmt.Println(string(opresp.Get().Kvs[0].Value))

}

  手动实现一个分布式锁:

逻辑大概如下,首先创建一个租约客户端,然后实现自动续租,之后尝试获取etcd里面一个kv,如果获取到了那么认为拿到了这把锁,那么允许进行业务逻辑,如果没有拿到,直接return,结束程序。

获取etcd值的逻辑:判断这个值的revision(版本号)是否为0,如果为0,说明没有,那么创建该值(获取到锁),如果不为0,说明有锁已经被获取。用完之后,删除该值

package main

import (
	"fmt"
	"time"
	"go.etcd.io/etcd/clientv3"
	"golang.org/x/net/context"
)

func main()  {

	var (
		config clientv3.Config
		client *clientv3.Client
		err error
		kv clientv3.KV
		lease clientv3.Lease
		LeaseKeepAliveResponse *clientv3.LeaseGrantResponse
		leasechan <-chan *clientv3.LeaseKeepAliveResponse
		leaseresp *clientv3.LeaseKeepAliveResponse
		ctx context.Context
		cancalfunc context.CancelFunc
		txn clientv3.Txn
		txnresp *clientv3.TxnResponse

	)

	config = clientv3.Config{
		Endpoints:[]string{"127.0.0.1:2379"},
		DialTimeout:5*time.Second,
	}

	if client,err  = clientv3.New(config); err != nil{
		fmt.Println(err)
		return
	}

	kv = clientv3.NewKV(client)

	// 分布式锁
	// 1.创建租约,自动续租,拿着租约去抢占key
	lease = clientv3.NewLease(client)
	LeaseKeepAliveResponse,_ = lease.Grant(context.TODO(),5)

	//创建上下文管理器来取消自动续租
	ctx ,cancalfunc = context.WithCancel(context.TODO())
	defer cancalfunc()
	defer lease.Revoke(context.TODO(),LeaseKeepAliveResponse.ID)
	//删除租约


	leasechan,err = lease.KeepAlive(ctx,LeaseKeepAliveResponse.ID)

	go func() {
		for{
			if leasechan == nil{
				fmt.Println("续约失败!")
				goto END
			}else{
				select {
				case leaseresp = <-leasechan:
					fmt.Println("续约成功!,",leaseresp.ID)
				}
			}
		}
		END:
	}()

	txn = kv.Txn(context.TODO())

	txn.If(clientv3.Compare(clientv3.CreateRevision("/home/miracle/job8"),"=",0)).
		Then(clientv3.OpPut("/home/miracle/job8","xxx",clientv3.WithLease(LeaseKeepAliveResponse.ID))).
		Else(clientv3.OpGet("/home/miracle/job8"))

	txnresp,_ = txn.Commit()

	//succeeded 如果为false代表进入了else分支
	if !txnresp.Succeeded{
		fmt.Println("抢锁失败:",string(txnresp.Responses[0].GetResponseRange().Kvs[0].Value))
		return
	//	这一串是获取else分支的get的结果
	}

	//业务逻辑
	fmt.Println("处理业务")
	time.Sleep(5*time.Second)

}

  

 

posted @ 2019-01-04 23:19  __Miracle  阅读(337)  评论(0)    收藏  举报