Golang语言系列-15-数据库
阅读原文时间:2023年07月10日阅读:1

数据库

连接数据库

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql" //这里只要导入即可,使用的是这个包的 init() 函数
)

/*
[Go语言操作MySQL]
Go语言中内置的database/sql包提供了保证SQL或类SQL数据库的泛用接口,并不提供具体的数据库驱动。
使用database/sql包时必须注入(至少)一个数据库驱动

下载数据库驱动
go get -u github.com/go-sql-driver/mysql
go get github.com/go-sql-driver/mysql
加-u和不加的区别
加上它可以利用网络来更新已有的代码包及其依赖包。
如果已经下载过一个代码包,但是这个代码包又有更新了,那么这时候可以直接用 -u 标记来更新本地的对应的代码包。
如果不加这个 -u 标记,执行 go get 一个已有的代码包,会发现命令什么都不执行。
只有加了 -u 标记,命令会去执行 git pull 命令拉取最新的代码包的最新版本,下载并安装
*/

func main() {
    // 1.拼接连接数据库的信息
    // 用户名:密码@tcp(ip:端口)/数据库的名字
    databaseInfo := "root:root123@tcp(192.168.3.121:3306)/sql_test"

    // 2.校验拼接信息格式是否正确,不会去校验数据库的账号密码是否正确
    db, err := sql.Open("mysql", databaseInfo)
    if err != nil {
        fmt.Printf("databaseInfo:%s invalid, err:%v\n", databaseInfo, err)
        return
    }

    // 3.连接数据库
    err = db.Ping() //尝试连接数据库
    if err != nil {
        fmt.Printf("Open %s failed, err:%v\n", databaseInfo, err)
        return
    }
    fmt.Println("connect database sucess!")
}


package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

// 数据库连接对象可以在多个函数中使用

// 在全局中声明一个db变量,好让所有的函数都能调用
var db *sql.DB

// initDB 初始化连接
func initDB() (err error) {
    // 1.数据库信息
    databaseInfo := "root:root123@tcp(192.168.3.121:3306)/sql_test"
    // 2.校验
    db, err = sql.Open("mysql", databaseInfo)
    if err != nil {
        return
    }
    // 3.连接
    err = db.Ping()
    if err != nil {
        return
    }

    db.SetMaxOpenConns(10) //设置数据库连接池的最大连接数
    db.SetMaxIdleConns(5)  //设置空闲时间最大连接数

    return
}

func main() {
    // 初始化数据库连接
    err := initDB()
    if err != nil {
        fmt.Printf("init DB falied, err:%v\n", err)
        return
    }
    fmt.Println("connect database success!")
}

插入数据

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

// 在全局中声明一个db变量,好让所有的函数都能调用
var db *sql.DB

// initDB 初始化连接
func initDB() (err error) {
    // 1.数据库信息
    databaseInfo := "root:root123@tcp(192.168.3.121:3306)/sql_test"
    // 2.校验
    db, err = sql.Open("mysql", databaseInfo)
    if err != nil {
        return
    }
    // 3.连接
    err = db.Ping()
    if err != nil {
        return
    }
    db.SetMaxOpenConns(10) //设置数据库连接池的最大连接数
    db.SetMaxIdleConns(5)  //设置空闲时间最大连接数
    return
}

func main() {
    // 初始化数据库连接
    err := initDB()
    if err != nil {
        fmt.Printf("init DB falied, err:%v\n", err)
        return
    }
    fmt.Println("connect database success!")

    // 执行插入函数
    //insertNo1()
    //insertNo2("李四", 18)
    //insertNo2("王五", 90)
    //insertNo2("马六", 37)
    //insertNo2("马云", 58)
    //insertNo3()
}

//insertNo1 插入数据方法一
func insertNo1() {
    //1.写sql语句
    sqlStr := `insert into user(name, age) values("张三", 20)`
    //2.执行exec
    ret, err := db.Exec(sqlStr)
    if err != nil {
        fmt.Printf("insert failed, err:%v\n", err)
        return
    }
    //拿到插入的数据的在数据库表中的ID
    id, err := ret.LastInsertId()
    if err != nil {
        fmt.Printf("get id failed, err:%v\n", err)
        return
    }
    fmt.Printf("insert ID:%d\n", id)
    //拿到数据库中受影响的数据行数
    row, err := ret.RowsAffected()
    if err != nil {
        fmt.Printf("get RowsAffected failed, err:%v\n", err)
        return
    }
    fmt.Println("受影响的行数:", row)
}

//insertNo2 插入数据方法二
func insertNo2(name string, age int) {
    sqlStr := `insert into user(name, age) values(?, ?)`
    ret, err := db.Exec(sqlStr, name, age)
    if err != nil {
        fmt.Printf("insert failed, err:%v\n", err)
        return
    }
    //拿到插入的数据的在数据库表中的ID
    id, err := ret.LastInsertId()
    if err != nil {
        fmt.Printf("get id failed, err:%v\n", err)
        return
    }
    fmt.Printf("insert ID:%d\n", id)
    //拿到数据库中受影响的数据行数
    row, err := ret.RowsAffected()
    if err != nil {
        fmt.Printf("get RowsAffected failed, err:%v\n", err)
        return
    }
    fmt.Println("受影响的行数:", row)
}

/*
预处理插入多条数据 这种方式速度会快一点

普通SQL语句执行过程:
    客户端对SQL语句进行占位符替换得到完整的SQL语句
    客户端发送完整SQL语句到MySQL服务端
    MySQL服务端执行完整的SQL语句并将结果返回给客户端

预处理执行过程:
    把SQL语句分成两部分,命令部分与数据部分
    先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理
    然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换
    MySQL服务端执行完整的SQL语句并将结果返回给客户端

为什么要预处理?
    优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
    避免SQL注入问题。
*/
func insertNo3() {
    //1.拼写SQL语句
    sqlStr := `insert into user(name, age) values(?, ?)`
    //2.预处理SQL语句,发送到MySQL服务器
    stmt, err := db.Prepare(sqlStr)
    if err != nil {
        fmt.Printf("prepare failed, err:%v\n", err)
        return
    }
    //3.关闭连接,释放连接池的资源
    defer stmt.Close()
    //4.SQL数据
    var m = map[string]int{
        "六七强": 30,
        "王相机": 32,
        "天说":  72,
        "白慧姐": 40,
    }
    //5.发送数据到MySQL服务器
    for k, v := range m {
        stmt.Exec(k, v)
    }
}

删除数据

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

//在全局中声明一个db变量,好让所有的函数都能调用
var db *sql.DB

//initDB 初始化连接
func initDB() (err error) {
    //1.数据库信息
    databaseInfo := "root:root123@tcp(192.168.3.121:3306)/sql_test"
    //2.校验
    db, err = sql.Open("mysql", databaseInfo)
    if err != nil {
        return
    }
    //3.连接
    err = db.Ping()
    if err != nil {
        return
    }
    db.SetMaxOpenConns(10) //设置数据库连接池的最大连接数
    db.SetMaxIdleConns(5)  //设置空闲时间最大连接数
    return
}

//deleteRow 删除一行数据
func deleteRow(id int) {
    sqlStr := `delete from user where id=?`
    ret, err := db.Exec(sqlStr, id)
    if err != nil {
        fmt.Printf("delete id:%d failed, err:%v\n", id, err)
        return
    }
    //拿到被删除的行数
    n, err := ret.RowsAffected()
    if err != nil {
        fmt.Printf("get delete id failed, err:%v\n", err)
        return
    }
    fmt.Printf("delete %d row data sucess!\n", n)
}

func main() {
    //初始化数据库连接
    err := initDB()
    if err != nil {
        fmt.Printf("init DB falied, err:%v\n", err)
        return
    }
    fmt.Println("connect database success!")

    //删除一行
    deleteRow(15)
}

更新数据

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

// 在全局中声明一个db变量,好让所有的函数都能调用
var db *sql.DB

// initDB 初始化连接
func initDB() (err error) {
    //1.数据库信息
    databaseInfo := "root:root123@tcp(192.168.3.121:3306)/sql_test"
    //2.校验
    db, err = sql.Open("mysql", databaseInfo)
    if err != nil {
        return
    }
    //3.连接
    err = db.Ping()
    if err != nil {
        return
    }
    db.SetMaxOpenConns(10) //设置数据库连接池的最大连接数
    db.SetMaxIdleConns(5)  //设置空闲时间最大连接数
    return
}

// updateRow 修改一行数据
func updateRow(newAge int, id int) {
    sqlStr := `update user set age=? where id = ?`
    ret, err := db.Exec(sqlStr, newAge, id)
    if err != nil {
        fmt.Printf("update failed, err:%v\n", err)
        return
    }
    n, err := ret.RowsAffected() //获取受影响的行数
    if err != nil {
        fmt.Printf("get any rows failed, err:%v\n", err)
    }
    fmt.Printf("update %d rows data!\n", n)
}

func main() {
    //初始化数据库连接
    err := initDB()
    if err != nil {
        fmt.Printf("init DB falied, err:%v\n", err)
        return
    }
    fmt.Println("connect database success!")

    //修改一行
    updateRow(9000, 1)
}

查询数据

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

// 在全局中声明一个db变量,好让所有的函数都能调用
var db *sql.DB

// initDB 初始化连接
func initDB() (err error) {
    //1.数据库信息
    databaseInfo := "root:root123@tcp(192.168.3.121:3306)/sql_test"
    //2.校验
    db, err = sql.Open("mysql", databaseInfo)
    if err != nil {
        return
    }
    //3.连接
    err = db.Ping()
    if err != nil {
        return
    }
    db.SetMaxOpenConns(10) //设置数据库连接池的最大连接数
    db.SetMaxIdleConns(5)  //设置空闲时间最大连接数
    return
}

// 声明一个结构体,用来接收查询出来的数据
type user struct {
    id   int
    name string
    age  int
}

// queryOne 查看一行数据
func queryOne(id int) {
    var u1 user
    //1.写查询单条记录的sql语句
    sqlStr := `select id, name, age from user where id = ?`
    //2.执行并拿到结果
    //必须对db.QueryRow()调用Scan方法,因为该方法会释放数据库连接
    //Scan()从连接池里拿出一个连接出来去数据库查询数据
    db.QueryRow(sqlStr, id).Scan(&u1.id, &u1.name, &u1.age)
    //3.打印结果
    fmt.Printf("u1: %#v\n", u1)
}

// queryMore 查询多条数据
func queryMore(n int) (userInfo []user) {
    //1.sql语句
    sqlSstr := `select id, name, age from user where id > ?`
    //2.执行
    rows, err := db.Query(sqlSstr, n)
    if err != nil {
        fmt.Printf("exec %s query falied, err:%v\n", sqlSstr, err)
        return
    }
    //3.一定要关闭rows,节省数据库资源
    defer rows.Close()
    //4.循环读取数据
    for rows.Next() {
        var u1 user
        err := rows.Scan(&u1.id, &u1.name, &u1.age)
        if err != nil {
            fmt.Printf("Scan failed, err:%v\n", err)
            return
        }
        userInfo = append(userInfo, u1)
    }
    return userInfo
}

func main() {
    //初始化数据库连接
    err := initDB()
    if err != nil {
        fmt.Printf("init DB falied, err:%v\n", err)
        return
    }
    fmt.Println("connect database success!")

    //查1行
    queryOne(1)

    //查多行
    ret := queryMore(1)
    fmt.Printf("%#v\n", ret)
    fmt.Println(ret)
}

事务

/*
Go实现MySQL事务
事务:一个最小的不可再分的工作单元
通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元),
同时这个完整的业务需要执行多次的DML(insert、update、delete)语句共同联合完成。
A转账给B,这里面就需要执行两次update操作

在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务。
事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行

通常事务必须满足4个条件(ACID):
原子性(Atomicity,或称不可分割性)、
一致性(Consistency)、
隔离性(Isolation,又称独立性)、
持久性(Durability
*/

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

// initDB 连接数据库
func initDB() (err error) {
    databaseInfo := "root:root123@tcp(192.168.3.121:3306)/sql_test"
    db, err = sql.Open("mysql", databaseInfo)
    if err != nil {
        return
    }
    err = db.Ping()
    if err != nil {
        return
    }
    db.SetMaxOpenConns(10)
    db.SetMaxIdleConns(5)
    return
}

// transactionDemo 事务提交
func transactionDemo() {
    // 1.开启事务
    tx, err := db.Begin()
    if err != nil {
        fmt.Printf("begin failed, err:%v\n", err)
        return
    }

    //2.执行多个sql操作
    sqlStr1 := `update user set age=age-2 where id = 1`
    sqlStr2 := `update user set age=age+2 where id = 2`

    //3.执行sqlStr1
    ret, err := tx.Exec(sqlStr1)
    if err != nil {
        //如果执行失败就回滚
        tx.Rollback()
        fmt.Println("执行SQL1出错了,已经回滚")
        return
    }
    n, err := ret.RowsAffected()
    if err != nil {
        //如果执行失败就回滚
        tx.Rollback()
        fmt.Println("get RowsAffected failed, err:", err)
        return
    }
    fmt.Printf("影响了%d行数据", n)

    //4.执行sqlStr2
    _, err = tx.Exec(sqlStr2)
    if err != nil {
        //如果执行失败就回滚
        tx.Rollback()
        fmt.Println("执行sqlStr2出错了,已经回滚")
        return
    }

    //5.上面两步sql都执行成功,那么就去提交
    err = tx.Commit()
    if err != nil {
        //提交失败 也要回滚
        tx.Rollback()
        fmt.Println("提交失败,回顾完成")
        return
    }
    fmt.Println("事务执行成功!")
}

func main() {
    err := initDB()
    if err != nil {
        fmt.Printf("connect database failed, err:%v\n", err)
        return
    }

    //调用事务执行函数
    transactionDemo()
}

sqlx库

/*
[sqlx库使用指南]

使用sqlx实现批量插入数据的例子,介绍了sqlx中可能被忽视了的 sqlx.In 和 DB.NamedExec 方法

sqlx可以认为是Go语言内置 database/sql 的超集
它在优秀的内置 database/sql 基础上提供了一组扩展
这些扩展中除了大家常用来查询的 Get 和 Select 外还有很多其他强大的功能

sqlx中的exec方法与原生sql中的exec使用基本一致,所以增删改应该跟原生的sql库差不多

安装方法:
    go get github.com/jmoiron/sqlx
*/

package main

import (
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
)

var db *sqlx.DB

//initDB 初始化数据连接
func initDB() (err error) {
    //1.数据库连接信息
    //用户名:密码@tcp(IP:端口)/数据库名字
    databaseInfo := `root:root123@tcp(192.168.3.121:3306)/sql_test`

    //2.连接数据库
    db, err = sqlx.Connect("mysql", databaseInfo)
    if err != nil {
        return
    }

    //3.设置数据库连接池
    db.SetMaxOpenConns(10) //设置数据库连接池的最大连接数
    db.SetMaxIdleConns(5)  //设置最大空闲连接数
    return
}

//##########################################################################################################
//###################################### 查询 start #########################################################
//##########################################################################################################
type user struct {
    ID   int //注意这里的字段要大写,因为db.Get db.Select等用了反射
    Name string
    Age  int
}

//queryRowDemo 查询单条数据Get
func queryRowDemo(id int) {
    sqlStr := `select id, name, age from user where id = ?`

    var u user
    err := db.Get(&u, sqlStr, id)
    if err != nil {
        fmt.Printf("get failed, err:%v\n", err)
        return
    }
    fmt.Printf("%#v\n", u)
    fmt.Printf("id:%d name:%s age:%d\n", u.ID, u.Name, u.Age)
}

//queryMultiRowDemo 查询多行数据 Select
func queryMultiRowDemo(id int) {
    sqlStr := `select id, name, age from user where id > ?`

    var users []user
    err := db.Select(&users, sqlStr, id) //这个id是对应上面的 id > ? 中的问号
    if err != nil {
        fmt.Printf("select more rows failed, err:%v\n", err)
        return
    }

    //fmt.Printf("%#v\n", users)
    for _, v := range users {
        fmt.Printf("%#v\n", v)
    }
}

//##########################################################################################################
//###################################### 查询 end ###########################################################
//##########################################################################################################
//
//
//##########################################################################################################
//###################################### 插入 start #########################################################
//##########################################################################################################
//insertRowDemo 插入数据
func insertRowDemo() {
    sqlStr := `insert into user(name,age) values (?,?)`
    ret, err := db.Exec(sqlStr, "沙河娜扎", 99)
    if err != nil {
        fmt.Printf("insert failed, err:%v\n", err)
        return
    }
    theID, err := ret.LastInsertId() //新插入数据在数据库中的ID
    if err != nil {
        fmt.Printf("get lastinsertid failed, err:%v\n", err)
        return
    }
    fmt.Printf("insert sucess, the id is %d.\n", theID)

    theRow, err := ret.RowsAffected() //插入了多少行数据
    if err != nil {
        fmt.Printf("get rows failed, err:%v\n", err)
        return
    }
    fmt.Printf("插入了[%d]行数据\n", theRow)
}

//##########################################################################################################
//###################################### 插入 end #########################################################
//##########################################################################################################
//
//
//##########################################################################################################
//###################################### 更新 start #########################################################
//##########################################################################################################
//updateRowDemo 更新数据
func updateRowDemo() {
    sqlStr := `update user set age=? where id=?`

    ret, err := db.Exec(sqlStr, 10000, 1)
    if err != nil {
        fmt.Printf("update failed, err:%v\n", err)
        return
    }

    n, err := ret.RowsAffected() //操作影响了多少行
    if err != nil {
        fmt.Printf("get rowsaffected failed, err:%v\n", err)
        return
    }
    fmt.Printf("更新成功,影响了%d行数据\n", n)

}

//##########################################################################################################
//###################################### 更新 end ###########################################################
//##########################################################################################################
//
//
//##########################################################################################################
//###################################### 删除 start ########################################################
//##########################################################################################################
//deleteRowDemo 删除
func deleteRowDemo() {
    sqlStr := `delete from user where id = ?`

    ret, err := db.Exec(sqlStr, 12)
    if err != nil {
        fmt.Printf("delete failed, err:%v\n", err)
        return
    }

    n, err := ret.RowsAffected() //操作影响的行数
    if err != nil {
        fmt.Printf("get rows failed, err:%v\n", err)
        return
    }

    fmt.Printf("删除了%d行数据\n", n)
}

//##########################################################################################################
//###################################### 删除 end ###########################################################
//##########################################################################################################
//
//
func main() {
    //连接数据库初始化
    err := initDB()
    if err != nil {
        fmt.Printf("connect database failed, err:%v\n", err)
        return
    }

    //查询一条数据
    //queryRowDemo(2)

    //查询多条数据
    //queryMultiRowDemo(10)

    //插入一条数据
    //insertRowDemo()

    //更新一条数据
    //updateRowDemo()

    //删除了一行数据
    //deleteRowDemo()
}

sql注入

/*
[SQL注入问题]
我们任何时候都不应该自己拼接SQL语句!!!
*/

package main

import (
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
)

//连接mysql
var db *sqlx.DB

func initDB() (err error) {
    databaseInfo := "root:root123@tcp(192.168.3.121:3306)/sql_test"
    db, err = sqlx.Connect("mysql", databaseInfo)
    if err != nil {
        return err
    }
    db.SetMaxOpenConns(20)
    db.SetMaxIdleConns(5)
    return err
}

//sql注入
type user struct {
    ID   int
    Name string
    Age  int
}

//SQL注入示例
func sqlInjectDemo(name string) {
    //自己拼接SQL语句
    //注意:我们任何时候都不应该自己拼接SQL语句!!!
    sqlStr := fmt.Sprintf("select id, name, age from user where name = '%s' ", name)
    fmt.Printf("SQL:%s\n", sqlStr)

    //可以使用这种方法防止SQL注入,或者使用之前预编译的方法 03mysql_insert_demo
    //sqlStr := "select id, name, age from user where name = ? "

    var users []user
    err := db.Select(&users, sqlStr)
    if err != nil {
        fmt.Printf("exec SQL failed, err:%v\n", err)
        return
    }

    for _, u := range users {
        fmt.Printf("user:%#v\n", u)
    }
}

func main() {
    err := initDB()
    if err != nil {
        fmt.Printf("connect databae failed, err:%v\n", err)
        return
    }

    //sql注入
    //sqlInjectDemo("王五") //客户端正常传入

    //sqlInjectDemo("xxx' or 1=1 #") //把数据库整个表查询出来了
    /*
        lichengguo@lichengguodeMacBook-Pro 09sql_injection_demo % ./09sql_injection_demo
        SQL:select id, name, age from user where name = 'xxx' or 1=1 #'
        user:main.user{ID:1, Name:"张三", Age:10000}
        user:main.user{ID:2, Name:"李四", Age:20}
        user:main.user{ID:3, Name:"王五", Age:90}
        user:main.user{ID:4, Name:"马六", Age:37}
        user:main.user{ID:5, Name:"马云", Age:58}
        user:main.user{ID:6, Name:"王相机", Age:32}
        user:main.user{ID:7, Name:"天说", Age:9000}
        user:main.user{ID:10, Name:"沙河小王子", Age:19}
        user:main.user{ID:11, Name:"沙河娜扎", Age:32}
    */

    //sqlInjectDemo("xxx' union select * from user #")
    /*
        lichengguo@lichengguodeMacBook-Pro 09sql_injection_demo % ./09sql_injection_demo
        SQL:select id, name, age from user where name = 'xxx' union select * from user #'
        user:main.user{ID:1, Name:"张三", Age:10000}
        user:main.user{ID:2, Name:"李四", Age:20}
        user:main.user{ID:3, Name:"王五", Age:90}
        user:main.user{ID:4, Name:"马六", Age:37}
        user:main.user{ID:5, Name:"马云", Age:58}
        user:main.user{ID:6, Name:"王相机", Age:32}
        user:main.user{ID:7, Name:"天说", Age:9000}
        user:main.user{ID:10, Name:"沙河小王子", Age:19}
        user:main.user{ID:11, Name:"沙河娜扎", Age:32}
    */
}


/*
安装: go get  github.com/go-redis/redis
*/

package main

import (
    "fmt"
    "github.com/go-redis/redis"
    "time"
)

//在全局中声明一个db变量,好让所有的函数都能调用
var rdb *redis.Client

//initClient 普通连接
func initClient() (err error) {
    //1.配置信息
    rdb = redis.NewClient(&redis.Options{
        Addr: "192.168.3.121:6379",
        DB:   0,
    })
    //2.尝试连接
    str, err := rdb.Ping().Result()
    if err != nil {
        return err
    }
    fmt.Println("str:", str) //str: PONG
    return
}

/*
连接Redis哨兵模式
var rdb *redis.Client

func initClient()(err error){
    rdb := redis.NewFailoverClient(&redis.FailoverOptions{
        MasterName:    "master",
        SentinelAddrs: []string{"x.x.x.x:26379", "xx.xx.xx.xx:26379", "xxx.xxx.xxx.xxx:26379"},
    })
    _, err = rdb.Ping().Result()
    if err != nil {
        return err
    }
    return nil
}
*/

/*
连接Redis集群
var rdb *redis.Client

func initClient()(err error){
    rdb := redis.NewClusterClient(&redis.ClusterOptions{
        Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
    })
    _, err = rdb.Ping().Result()
    if err != nil {
        return err
    }
    return nil
}

*/

//set/get示例 redisExample
func redisExample() {
    //1.插入值
    err := rdb.Set("score", 100, 0).Err() //set(key,values,失效时间 0表示永不失效)
    if err != nil {
        fmt.Printf("set score failed, err:%v\n", err)
        return
    }
    //2.取值
    //可以取到值的情况
    val, err := rdb.Get("score").Result()
    if err != nil {
        fmt.Printf("get score value failed, err:%v\n", err)
        return
    }
    fmt.Println("keys:score , val:", val)

    //键值对不存在或者值为nil的情况
    val2, err := rdb.Get("name").Result()
    if err == redis.Nil {
        fmt.Println("name does not exist")
    } else if err != nil {
        fmt.Printf("get name failed, err:%v\n", err)
        return
    } else {
        fmt.Println("keys: name val: ", val2)
    }
}

//zset示例
func redisExample2() {
    zsetKey := "language_rank"
    languages := []redis.Z{
        redis.Z{Score: 90.0, Member: "Golang"}, //Socre:相当于值 Member:相当于键
        redis.Z{Score: 98.0, Member: "Java"},
        redis.Z{Score: 95.0, Member: "Python"},
        redis.Z{Score: 97.0, Member: "JavaScript"},
        redis.Z{Score: 99.0, Member: "C/C++"},
    }

    //zadd
    num, err := rdb.ZAdd(zsetKey, languages...).Result()
    if err != nil {
        fmt.Printf("zadd failed, err:%v\n", err)
        return
    }
    fmt.Printf("zadd %d success!\n", num)

    //把Golang的分数加10
    newScore, err := rdb.ZIncrBy(zsetKey, 10.0, "Golang").Result()
    if err != nil {
        fmt.Printf("zincrby failed, err:%v\n", err)
        return
    }
    fmt.Printf("Golang's score is %f now.\n", newScore)

    //取分数最高的3个
    ret, err := rdb.ZRevRangeWithScores(zsetKey, 0, 2).Result()
    if err != nil {
        return
    }
    for _, z := range ret {
        fmt.Println(z)
    }

    //取95-100分的
    op := redis.ZRangeBy{
        Min: "95",
        Max: "100",
    }
    ret, err = rdb.ZRangeByScoreWithScores(zsetKey, op).Result()
    if err != nil {
        fmt.Printf("zrangebyscore failed, err:%v\n", err)
        return
    }
    for _, z := range ret {
        fmt.Println(z.Member, z.Score)
    }
}

//inserRedis1W 测试redis
func inserRedis1W() {
    startTime := time.Now().Unix()
    for i := 0; i < 10000; i++ {
        keyName := fmt.Sprintf("name%00000d", i)
        err := rdb.Set(keyName, "我是很占内存的一串字符串!!!!!!", time.Second*60).Err() //设置了key过期的时间
        if err != nil {
            fmt.Printf("insert failed, err:%v\n", err)
            return
        }
    }
    fmt.Printf("耗时:%v\n", time.Now().Unix()-startTime) //耗时:41
}

/*
pipLine 测试
Pipeline 主要是一种网络优化。它本质上意味着客户端缓冲一堆命令并一次性将它们发送到服务器。
这些命令不能保证在事务中执行。这样做的好处是节省了每个命令的网络往返时间(RTT)
*/
func pipLine() {
    startTime := time.Now().Unix()

    pipe := rdb.Pipeline()

    for i := 0; i < 10000; i++ {
        keyName := fmt.Sprintf("name%00000d", i)
        pipe.Set(keyName, "我是很占内存的一串字符串!!!!!!", time.Second*60)
    }

    pipe.Exec()

    fmt.Printf("耗时:%v\n", time.Now().Unix()-startTime) //耗时:0
}

func main() {
    err := initClient()
    if err != nil {
        fmt.Printf("connect redis server failed, err:%v\n", err)
        return
    }
    fmt.Println("redis connect sucess!")

    //set/get示例
    redisExample()

    //zadd
    //redisExample2()

    //测试插入1万键值对
    //inserRedis1W()

    //pipLine 测试
    //pipLine()

}

/*
192.168.3.121:6379> zrange language_rank 0 100
1) "Golang"
2) "Python"
3) "JavaScript"
4) "Java"
5) "C/C++"

192.168.3.121:6379> zrange language_rank 0 100 withscores
 1) "Golang"
 2) "90"
 3) "Python"
 4) "95"
 5) "JavaScript"
 6) "97"
 7) "Java"
 8) "98"
 9) "C/C++"
10) "99"
*/

简介

github下载地址: https://github.com/nsqio/nsq/releases/tag/v1.2.0

简介
NSQ是目前比较流行的一个分布式的消息队列,本文主要介绍了NSQ及Go语言如何操作NSQ。

NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优异。 NSQ的优势:
    NSQ提倡分布式和分散的拓扑,没有单点故障,支持容错和高可用性,并提供可靠的消息交付保证
    NSQ支持横向扩展,没有任何集中式代理。
    NSQ易于配置和部署,并且内置了管理界面

NSQ组件
1.nsqd: nsqd是一个守护进程,它接收、排队并向客户端发送消息
启动nsqd,指定-broadcast-address=127.0.0.1来配置广播地址
./nsqd -broadcast-address=127.0.0.1

如果是在搭配nsqlookupd使用的模式下需要还指定nsqlookupd地址:
./nsqd -broadcast-address=127.0.0.1 -lookupd-tcp-address=127.0.0.1:4160
如果是部署了多个nsqlookupd节点的集群,那还可以指定多个-lookupd-tcp-address

2.nsqlookupd
nsqlookupd是维护所有nsqd状态、提供服务发现的守护进程。它能为消费者查找特定topic下的nsqd提供了运行时的自动发现服务。
它不维持持久状态,也不需要与任何其他nsqlookupd实例协调以满足查询。
因此根据你系统的冗余要求尽可能多地部署nsqlookupd节点。
它们小豪的资源很少,可以与其他服务共存。我们的建议是为每个数据中心运行至少3个集群

3.nsqadmin
一个实时监控集群状态、执行各种管理任务的Web管理平台。 启动nsqadmin,指定nsqlookupd地址
./nsqadmin -lookupd-http-address=127.0.0.1:4161
我们可以使用浏览器打开http://127.0.0.1:4171/访问如下管理界面

目录结构

├── README.md
├── consumer
│&nbsp;&nbsp; └── main.go
└── producer
    └── main.go

consumer/main.go 文件

// nsq_consumer/main.go
package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/nsqio/go-nsq"
)

// NSQ Consumer Demo

// MyHandler 是一个消费者类型
type MyHandler struct {
    Title string
}

// HandleMessage 是需要实现的处理消息的方法
func (m *MyHandler) HandleMessage(msg *nsq.Message) (err error) {
    fmt.Printf("%s recv from %v, msg:%v\n", m.Title, msg.NSQDAddress, string(msg.Body))
    return
}

// 初始化消费者
func initConsumer(topic string, channel string, address string) (err error) {
    config := nsq.NewConfig()
    config.LookupdPollInterval = 15 * time.Second
    c, err := nsq.NewConsumer(topic, channel, config)
    if err != nil {
        fmt.Printf("create consumer failed, err:%v\n", err)
        return
    }
    consumer := &MyHandler{
        Title: "dsb1号",
    }
    c.AddHandler(consumer)

    //if err := c.ConnectToNSQD(address); err != nil { // 直接连NSQD
    if err := c.ConnectToNSQLookupd(address); err != nil { // 通过lookupd查询
        return err
    }
    return nil

}

func main() {
    err := initConsumer("topic_demo", "first", "127.0.0.1:4161")
    if err != nil {
        fmt.Printf("init consumer failed, err:%v\n", err)
        return
    }
    c := make(chan os.Signal)        // 定义一个信号的通道
    signal.Notify(c, syscall.SIGINT) // 转发键盘中断信号到c
    <-c                              // 阻塞
}

producer/main.go文件

// nsq_producer/main.go
package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"

    "github.com/nsqio/go-nsq"
)

// NSQ Producer Demo 生产者

var producer *nsq.Producer

// 初始化生产者
func initProducer(str string) (err error) {
    config := nsq.NewConfig()
    producer, err = nsq.NewProducer(str, config)
    if err != nil {
        fmt.Printf("create producer failed, err:%v\n", err)
        return err
    }
    return nil
}

func main() {
    nsqAddress := "127.0.0.1:4150"
    err := initProducer(nsqAddress)
    if err != nil {
        fmt.Printf("init producer failed, err:%v\n", err)
        return
    }

    reader := bufio.NewReader(os.Stdin) // 从标准输入读取
    for {
        data, err := reader.ReadString('\n')
        if err != nil {
            fmt.Printf("read string from stdin failed, err:%v\n", err)
            continue
        }
        data = strings.TrimSpace(data)
        if strings.ToUpper(data) == "Q" { // 输入Q退出
            break
        }
        // 向 'topic_demo' publish 数据
        err = producer.Publish("topic_demo", []byte(data))
        if err != nil {
            fmt.Printf("publish msg to nsq failed, err:%v\n", err)
            continue
        }
    }
}