(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的传参,好处坏处都很明显,基本上一个协程环境内(包括主进程的一个)公共变量是通用的,指针类型数据传参效率很高。
协程数据/事件使用指针:代码难以理解。