(golang学习)1.安装、web应用: mysql+redis+curl
前言
静态化处理提高运行效率,可以编译go文件到二进制脚本。进阶使用
1.安装golang环境
a.本机测试
wget -O /tmp/go.tgz "https://dl.google.com/go/go1.12.7.src.tar.gz" tar -C /usr/local -xzf /tmp/go.tgz && rm -f /tmp/go.tgz ln -s /usr/local/go/bin/* /usr/local/bin/
[]:~/tmp/dk/golang# vim test.go package main import "fmt" func main() { fmt.Println("hello go!") }
可以直接go run test.go测试效果,使用go build test.go生成二进制代码:test文件,看到权限自动调整好了不用再chmod,运行./test 得到hello go!。
b.docker容器
docker-hub的golang官方示例。
# -w:指定工作目录 docker run --name golang -v ~/tmp/dk/golang:/usr/src/app -w /usr/src/app -itd golang
进入容器中使用的一个编译环境。go代码的编译环境齐全,移植方便。
2.连接数据库
参考《go语言之行--golang操作redis、mysql大全》
go get "github.com/go-redis/redis"
go get "github.com/go-sql-driver/mysql"
go get "github.com/jmoiron/sqlx"
go get "github.com/mikemintang/go-curl"
redis集群操作官方参考\
使用win10 go get超时的解决参考百度 “git clone超时”。
数据库MySQL的操作,代码参考下篇。
a.mysql读写
mysql.go,参考官方,比php使用不够方便:
package main import ( "crypto/md5" "crypto/sha1" "encoding/hex" "fmt" "log" "strconv" "time" "database/sql" _"github.com/go-sql-driver/mysql" ) type User struct { id int `db:"id"` username string `db:"username"` last_login string `db:"last_login"` } func md5V(str string) string { h := md5.New() h.Write([]byte(str)) return hex.EncodeToString(h.Sum(nil)) } func sha1V (str string) string { hs := sha1.New() hs.Write([]byte(str)) return hex.EncodeToString((hs.Sum(nil))) } var mysqlDB *sql.DB func main() { t1 := time.Now() //https://github.com/jmoiron/sqlx postgres dsn := "root:123456@tcp(172.1.11.11:3306)/test?charset=utf8" mysqlDB, err := sql.Open("mysql", dsn) //fmt.Println(mysqlDB, err) var ( id int username string passwd string last_login string sum int ) //id, username, passwd, last_login err = mysqlDB.QueryRow("select count(id) from user").Scan(&sum) if err != nil { log.Fatal(err) } fmt.Println(`目前行数:`, sum) if sum<10 { stmt, err := mysqlDB.Prepare("INSERT INTO user (username,passwd,last_login) VALUES(?,?,?)") if err != nil { log.Fatal(err) } for i:=0; i<20; i++ { username = "Dolly"+strconv.Itoa(i) last_login = "2000-01-01 10:00:00" passwd = sha1V(md5V(username + last_login)) _, err := stmt.Exec(username, passwd, last_login) if err != nil { log.Fatal(err) } } err = mysqlDB.QueryRow("select count(id) from user").Scan(&sum) if err != nil { log.Fatal(err) } fmt.Println(`行数=`, sum) }else { rows, _ := mysqlDB.Query(`SELECT * FROM user ORDER BY id ASC`) users := []User{} for rows.Next() { err := rows.Scan(&id, &username, &passwd, &last_login) users = append(users, User{id, username, last_login}) if err != nil { log.Fatalln(err) } } log.Println("\n", users) } t2 := time.Now() fmt.Println() fmt.Println(t2.Sub(t1)) fmt.Printf("main OK!\n") }
b.redis集群操作
redis.go,主要参考官方,pipeline参考《golang. 批量获取redis中的缓存值》:
package main import ( "encoding/hex" "crypto/md5" "fmt" "log" "strconv" "strings" "github.com/go-redis/redis" "reflect" "time" ) func redisClient(addr string) { client := redis.NewClient(&redis.Options{ Addr: addr, }) pong, err := client.Ping().Result() fmt.Println(pong, err) // Output: PONG <nil> } func clusterClient(addr []string) { fmt.Println("type:", reflect.TypeOf(redisCsDB)) redisCsDB = redis.NewClusterClient(&redis.ClusterOptions{ Addrs: addr, }) fmt.Println("type:", reflect.TypeOf(redisCsDB)) // Output: PONG <nil> pong, err := redisCsDB.Ping().Result() fmt.Println(pong, err) if err != nil { panic(err) } //fmt.Println(redisCsDB) } func md5V(str string) string { h := md5.New() h.Write([]byte(str)) return hex.EncodeToString(h.Sum(nil)) } var redisDB *redis.Client var redisCsDB *redis.ClusterClient func main() { var servers string = "172.1.50.11:6379,172.1.50.12:6379,172.1.50.13:6379,172.1.30.11:6379,172.1.30.12:6379,172.1.30.13:6379" serverInfo := strings.Split(servers, ",") fmt.Println(serverInfo) clusterClient(serverInfo) //设置时区 l,_ := time.LoadLocation("Asia/Shanghai") fmt.Println(time.Now().In(l)) val, err := redisCsDB.Get("set-10").Result() fmt.Println(val) fmt.Println(err) if len(val) < 1 { //清空当前的 redisCsDB.FlushDB() for i:=0; i<20000; i++ { //fmt.Println("set-"+ strconv.Itoa(i)) key := "set-"+ strconv.Itoa(i) str := time.Now().Format("2006-01-02 15:04:05 ") + strconv.Itoa(i) val, err = redisCsDB.Set(key, md5V(str), 0).Result() fmt.Println(str) fmt.Println(val) } } t1 := time.Now() for i:=10; i<20000; i++ { key := "set-"+ strconv.Itoa(i) _, err := redisCsDB.Get(key).Result() if err != nil { panic(err) } //fmt.Println("set-"+ strconv.Itoa(i), val) } t2 := time.Now() fmt.Println() fmt.Println(t2.Sub(t1)) //pipeline t1 = time.Now() pipe := redisCsDB.Pipeline() for i:=10; i<20000; i++ { key := "set-"+ strconv.Itoa(i) pipe.Get(key).Result() } _, err = pipe.Exec() t2 = time.Now() fmt.Println(t2.Sub(t1)) fmt.Println() log.Println(err == redis.Nil) //执行命令 slotsInfo, err := redisCsDB.Do("cluster", "slots").Result() fmt.Println("slotsInfo type:", reflect.TypeOf(slotsInfo), "\n", slotsInfo, err) info, err := redisCsDB.Do("cluster", "info").Result() fmt.Println("cluster info:", reflect.TypeOf(info), "\n", info, err) //这里被for阻塞 fmt.Printf("main OK!\n") }
c.curl请求
参考 IT无崖子《深入浅出Golang的协程池设计》:
package main import ( "fmt" "math/rand" "strconv" //"sync" "time" "github.com/mikemintang/go-curl" ) /* 有关Task任务相关定义及操作 */ //定义任务Task类型,每一个任务Task都可以抽象成一个函数 type Task struct { f func() error //一个无参的函数类型 } //通过NewTask来创建一个Task func NewTask(f func() error) *Task { t := Task{ f: f, } return &t } //执行Task任务的方法 func (t *Task) Execute(work_ID string, p *Pool) { p.CurJobs += 1 fmt.Println(p, " >> ", work_ID) t.f() //调用任务所绑定的函数 fmt.Println("worker: ", work_ID, " 执行完毕") p.CurJobs -= 1 p.Dealed += 1 } /* 有关协程池的定义及操作 */ //定义池类型 type Pool struct { //对外接收Task的入口 EntryChannel chan *Task //协程池最大worker数量,限定Goroutine的个数 workerNum int //协程池内部的任务就绪队列 JobsChannel chan *Task CurJobs int Dealed int } //创建一个协程池 func NewPool(max int) *Pool { p := Pool{ EntryChannel: make(chan *Task), workerNum: max, JobsChannel: make(chan *Task), CurJobs: 0, Dealed: 0, } return &p } //协程池创建一个worker并且开始工作 func (p *Pool) worker(work_ID string) { //worker不断的从JobsChannel内部任务队列中拿任务 for task := range p.JobsChannel { //如果拿到任务,则执行task任务 fmt.Println("______task:", p.Dealed, "", p.CurJobs, "/", p.workerNum, p) task.Execute(work_ID, p) fmt.Println("----第 ", p.Dealed, "个任务 执行完毕") } } //让协程池Pool开始工作 func (p *Pool) Run() { //1,首先根据协程池的worker数量限定,开启固定数量的Worker, // 每一个Worker用一个Goroutine承载 for i := 1; i <= p.workerNum; i++ { go p.worker(time.Now().Format("2006.01.02 15:04:05 ")+ strconv.Itoa(i)) } //2, 从EntryChannel协程池入口取外界传递过来的任务 // 并且将任务送进JobsChannel中 for task := range p.EntryChannel { p.JobsChannel <- task } //3, 执行完毕需要关闭JobsChannel close(p.JobsChannel) //4, 执行完毕需要关闭EntryChannel close(p.EntryChannel) } func curlV (url string) string { headers := map[string]string{ "User-Agent": "Chrome/63.0.3239.132 (Windows NT 10.0; WOW64)", "Authorization": "Bearer access_token", "Content-Type": "application/octet-stream", } // 链式操作 req := curl.NewRequest() req.SetResponseTimeOut(10) resp, err := req.SetUrl(url).SetHeaders(headers).Get() if resp != nil { if resp.Raw.StatusCode == 200 { //fmt.Println(url, `StatusCode =`, resp.Raw.StatusCode) return resp.Body } else { fmt.Println(err, resp.Raw.StatusCode) return "" } }else{ fmt.Println(err) return "" } } func main() { //curl urls := []string {"https://www.baidu.com", "https://www.php.net", "https://www.runoob.com"} fmt.Println() fmt.Println(urls) t := NewTask(func() error { fmt.Print("__") t1 := time.Now() r := rand.Int() % len(urls) curlV(urls[r]) t2 := time.Now() fmt.Println(time.Now().Format("-- 2006-01-02 15:04:05"), t2.Sub(t1)) return nil }) //创建一个协程池,最大开启10个协程worker max := 10 p := NewPool(max) //开一个协程 不断的向 Pool 输送打印一条时间的task任务 go func() { for { p.EntryChannel <- t //time.Sleep(time.Duration(time.Microsecond * 200)) } }() //启动协程池p p.Run() fmt.Printf("main OK!\n") }
运行可能会报错:__panic: read tcp xx: i/o timeout,这里猜想是随机的、部分请求被拒绝,调长时间会缓解,暂无法解决。
3.使用小结
与php+swoole对比:
go变量作用域--很大:各种函数能使用公共变量、不用php的传参,好处坏处都很明显,基本上一个协程环境内(包括主进程的一个)公共变量是通用的,指针类型数据传参效率很高。
协程数据/事件使用指针:代码难以理解。