Golang语言系列-15-数据库

数据库

MySQL

连接数据库

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}
	*/
}

Redis

/*
安装: 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"
*/

消息队列NSQ

简介

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
│   └── 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
		}
	}
}
posted @ 2021-03-25 17:55  李成果  阅读(112)  评论(0编辑  收藏  举报