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)
}

浙公网安备 33010602011771号