Golang runtime.Gosched()函数浅析

分类:技术文档 - Golang | 阅读(118) | 发布于:2019-05-08 10:39 | 标签:无

以下是官方的定义:
// Gosched yields the processor, allowing other goroutines to run. It does not
// suspend the current goroutine, so execution resumes automatically.
func Gosched() {
    mcall(gosched_m)
}

这个函数的作用是让当前goroutine让出CPU,好让其它的goroutine获得执行的机会。同时,当前的goroutine也会在未来的某个时间点继续运行。
请看下面这个例子

package main
import (
	"fmt"
	//"runtime"
)
func say(s string) {
	for i := 0; i < 2; i++ {
		//runtime.Gosched()
		fmt.Println(s, i)
	}
}
func main() {
	go say("world")
	say("hello")
}
//执行输出:
//hello 0
//hello 1

---------取消注释runtime.Gosched()---------

package main
import (
	"fmt"
	"runtime"
)
func say(s string) {
	for i := 0; i < 2; i++ {
		runtime.Gosched()
		fmt.Println(s, i)
	}
}
func main() {
	go say("world")
	say("hello")
}
//执行输出:
//hello 0
//world 0
//hello 1

可以看到使用runtime.Gosched()后,先输出"hello 0",再输出"world 0",再输出"hello 1",最后一个"world 1"没有机会输出线程就结束了。

再看下面这个例子:

package main
import "fmt"
func showNumber (i int) {
	fmt.Println(i)
}
func main() {
	for i := 0; i < 10; i++ {
		go showNumber(i)
	}
	fmt.Println("Haha")
}
//执行输出:
//Haha

---------使用runtime.Gosched()---------

package main
import (
	"fmt"
	"runtime"
)
func showNumber(i int) {
	fmt.Println(i)
}
func main() {
	for i := 0; i < 10; i++ {
		go showNumber(i)
	}
	runtime.Gosched()
	fmt.Println("Haha")
}
//执行输出:(每次结果不一定,但"Haha"一定输出且在最后)
//6
//3
//7
//1
//8
//9
//4
//0
//2
//5
//Haha

分析:默认情况goroutins都是在一个线程里执行的,多个goroutins之间轮流执行,当一个goroutine发生阻塞,Go会自动地把与该goroutine处于线程的其他goroutines转移到另一个线程上去,以使这些goroutines不阻塞。 通过上面的结果可以看出,主线程执行完毕之后程序就退出了。 最后,如果代码中通过 runtime.GOMAXPROCS(n) 其中n是整数,指定使用多核的话,多个goroutines之间就可以实现真正的并行,结果就会上面的有所不同。

阅读更多...

Go原子操作 sync/atomic

分类:技术文档 - Golang | 阅读(131) | 发布于:2019-05-07 13:37 | 标签:无

sync/atomic包提供了底层的原子级内存操作,其执行过程不能被中断,这也就保证了同一时刻一个线程的执行不会被其他线程中断,也保证了多线程下数据操作的一致性。
操作的数据类型共有六种:int32, int64, uint32, uint64, uintptr, unsafe.Pinter,每一种类型又提供了五种操作:Add增减, CompareAndSwap比较并交换, Swap交换, Load载入, Store存储。
函数名以"操作+类型"组合而来。例如AddInt32/AddUint64/LoadInt32/LoadUint64...

以Int32为例:

// AddInt32 atomically adds delta to *addr and returns the new value.
func AddInt32(addr *int32, delta int32) (new int32)
AddInt32可以实现对元素的原子增加或减少,函数会直接在传递的地址上进行修改操作。
addr需要修改的变量的地址,delta修改的差值[正或负数],返回new修改之后的新值。

var a int32 
a += 10 
atomic.AddInt32(&a, 10) 
fmt.Println(a == 20) // true

//需要注意的是如果是uint32,unint64时,不能直接传负数,所以需要利用二进制补码机制
var b uint32
b += 20 
atomic.AddUint32(&b, ^uint32(10-1)) // 等价于 b -= 10 
// atomic.Adduint32(&b, ^uint32(N-1)) //N为需要减少的正整数值
fmt.Println(b == 10) // true

// CompareAndSwapInt32 executes the compare-and-swap operation for an int32 value.
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
函数会先判断参数addr指向的值与参数old是否相等,如果相等,则用参数new替换参数addr的值。最后返回swapped是否替换成功。

package main
import (
	"fmt"
	"sync"
	"sync/atomic"
)
func main() {
	var c int32
	wg := sync.WaitGroup{}
	//开启100个goroutine
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			tmp := atomic.LoadInt32(&c)
			if !atomic.CompareAndSwapInt32(&c, tmp, tmp+1) {
				fmt.Println("c 修改失败")
			}
		}()
	}
	wg.Wait()
	//c的值有可能不等于100,频繁修改变量值情况下,CompareAndSwap操作有可能不成功。
	fmt.Println("c : ", c)
}
偶尔输出:
//c 修改失败
//c :  99
多数情况下输出
//c :  100

// SwapInt32 atomically stores new into *addr and returns the previous *addr value.
func SwapInt32(addr *int32, new int32) (old int32)
Swap会直接执行赋值操作,并将原值作为返回值返回

package main
import (
	"fmt"
	"sync"
	"sync/atomic"
)
func main() {
	var e int32
	wg2 := sync.WaitGroup{}
	//开启10个goroutine
	for i := 0; i < 10; i++ {
		wg2.Add(1)
		go func() {
			defer wg2.Done()
			tmp := atomic.LoadInt32(&e)
			old := atomic.SwapInt32(&e, tmp + 1)
			fmt.Println("e old : ", old)
		}()
	}
	wg2.Wait()
	fmt.Println("e : ", e)
}

// e old :  3
// e old :  1
// e old :  2
// e old :  4
// e old :  5
// e old :  6
// e old :  7
// e old :  8
// e old :  9
// e old :  0
// e :  10

// LoadInt32 atomically loads *addr.
func LoadInt32(addr *int32) (val int32)
Load函数参数为需要读取的变量地址,返回值为读取的值
Load和Store操作对应与变量的原子性读写,许多变量的读写无法在一个时钟周期内完成,而此时执行可能会被调度到其他线程,无法保证并发安全。Load只保证读取的不是正在写入的值,Store只保证写入是原子操作。所以在使用的时候要注意。


// StoreInt32 atomically stores val into *addr.
func StoreInt32(addr *int32, val int32)
Store函数参数为需要存储的变量地址及需要写入的值,存储某个值时,任何CPU都不会对该值进行读或写操作,存储操作总会成功,它不关心旧值是什么,与CompareAndSwap不同

var d int32
fmt.Println("d : ", d)
atomic.StoreInt32(&d, 666)
fmt.Println("d : ", d)

阅读更多...

golang mysql select * 优化

分类:技术文档 - Golang | 阅读(277) | 发布于:2019-04-18 16:45 | 标签:无

初学者使用golang mysql做查询时,一般直接使用原生的select * from table来查询数据:
type UserInfo struct {
    Id   int64
    Name string
    Sex  string
    //...
}

func GetUserInfo(uid int64) (info UserInfo, err error) {
    Db := lib.MysqlConn()
    sql := fmt.Sprintf("select * from user where uid=%d", uid)
    rows, err := Db.Query(sql)
    if err != nil {
        return info, err
    }
    defer Db.Close()

    if rows.Next() {
        var (
            id   int64
            name string
            sex  string
            //...
        )
        //得一个个的字段罗列出来
        err = rows.Scan(&id, &name, &sex, //...)
        info = UserInfo{
            Id:   id,
            Name: name,
            Sex:  sex,
            //...
        }
    }

    return info, nil
}
后来数据表结构变动直接导致GetUserInfo()报错了,因为字段对应不上了。然后优化了一版:
func GetUserInfo(uid int64) (info UserInfo, err error) {
    Db := lib.MysqlConn()
    sql := fmt.Sprintf("select `id`,`user_name`,`sex`,... from user where uid=%d", uid)
    rows, err := Db.Query(sql)
    if err != nil {
        return
    }
    defer Db.Close()

    if rows.Next() {
        //得一个个的字段罗列出来
        err = rows.Scan(&info.Id, &info.Name, &info.Sex, //...)
        if err != nil {
            return
        }
    }

    return
}
此时问题解决了,然后继续写类似的代码。
时间久了,就发现这样写好累,每次都得把所有的字段都罗列出来,而且上下一一对应。
所以就想能不能自动生成上下对应的变量,然后直接用变量来替换上。
如是,就用到了golang的反射,先定义一个结构体和数据表的字段一一对应,并在字段说明里面定义数据库的字段名:
type UserInfo struct {
	Id   int64  `sql:"id"`
	Name string `sql:"user_name"`
	Sex  string `sql:"sex"`
	//...
}
可以使用反射提取出类似`sql:"id"`里面id,然后拼装起来,具体如下
func (ui *UserInfo) allFields() (sqlFields string) {
	arr := []string{}
	el := reflect.TypeOf(ui).Elem()
	for i := 0; i < el.NumField(); i++ {
		arr = append(arr, el.Field(i).Tag.Get("sql"))
	}

	sqlFields = "`" + strings.Join(arr, "`,`") + "`"
	return
}
所以上面的函数中sql语句的定义就改为了:
//...
sql := fmt.Sprintf("select "+info.allFields()+" from user where uid=%d", uid)
//...
问题又来了,
rows.Scan(&info.Id, &info.Name, &info.Sex, //...)
rows.Scan怎么优化?
这里实际上就是每个字段的地址,如果取出该变量的地址,组成一个切片,然后展开切片就可以了,再来定义方法:
func (ui *UserInfo) allValues() (sqlValues []interface{}) {
	vl := reflect.ValueOf(ui).Elem()
	num := reflect.TypeOf(ui).Elem().NumField()
	for i := 0; i < num; i++ {
		sqlValues = append(sqlValues, vl.Field(i).Addr().Interface())
	}
	return
}

func GetUserInfo(uid int64) (info UserInfo, err error) {
	Db := lib.MysqlConn()
	sql := fmt.Sprintf("select "+info.allFields()+" from user where uid=%d", uid)
	rows, err := Db.Query(sql)
	if err != nil {
		return
	}
	defer Db.Close()

	if rows.Next() {
		//获取每个字段的切片
		fields := info.allValues()
		err = rows.Scan(fields...)
		if err != nil {
			return
		}
	}

	return
}
把 info.allFields()和info.allValues()合并在一起返回:
type UserInfo struct {
	Id   int64  `sql:"id"`
	Name string `sql:"user_name"`
	Sex  string `sql:"sex"`
	//...
}

func (ui *UserInfo) allFieldsAndValues() (sqlFields string, sqlValues []interface{}) {
	arr := []string{}
	el := reflect.TypeOf(ui).Elem()
	vl := reflect.ValueOf(ui).Elem()
	for i := 0; i < el.NumField(); i++ {
		arr = append(arr, el.Field(i).Tag.Get("sql"))
		sqlValues = append(sqlValues, vl.Field(i).Addr().Interface())
	}

	sqlFields = "`" + strings.Join(arr, "`,`") + "`"

	return
}

func GetUserInfo(uid int64) (info UserInfo, err error) {
	Db := lib.MysqlConn()

	sqlFields, sqlValues := info.allFieldsAndValues()
	sql := fmt.Sprintf("select "+sqlFields+" from user where uid=%d", uid)
	rows, err := Db.Query(sql)
	if err != nil {
		return
	}
	defer Db.Close()

	if rows.Next() {
		err = rows.Scan(sqlValues...)
	}

	return
}
现在封装起来后就方便多了,不担心数据表结构变动:这里只需要修改结构体UserInfo的信息。
现在又有一个方法需要批量的获取UserInfo,方法UserInfo.allFieldsAndValues()就大有用处了:
func GetAllUserInfo() (all []UserInfo, err error) {
	Db := lib.MysqlConn()
	info := UserInfo{}
	sqlFields, sqlValues := info.allFieldsAndValues()
	rows, err := Db.Query("select " + sqlFields + " from user ")
	if err != nil {
		return
	}
	defer Db.Close()

	for rows.Next() {
		err = rows.Scan(sqlValues...)
		if err != nil {
			return
		}
		all = append(all, info)
		info = UserInfo{}
	}

	return
}
这里在循环内[for rows.Next(){//...}]因为结构体的传值是值传递,all = append(all, info)操作的是info的一个拷贝。
用完之后在把变量info的值给重置掉:info = UserInfo{}。最后返回all。

Go语言中有的传参是值传递(传值),是一个副本,一个拷贝。
因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;
有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据

阅读更多...

汉语的精粹

分类:散文阅读 - 科普阅读 | 阅读(181) | 发布于:2019-04-09 12:50 | 标签:无

1.中国就足球和乒乓球的赛事最无聊,一个看都不用看,另一个看都不用看;一个谁都打不过,另一个谁都打不过。

2.单身的原因:原来是喜欢一个人,现在是喜欢一个人。

3.冬天:能穿多少穿多少; 夏天:能穿多少穿多少。

阅读更多...

golang实现一个简单的FTP服务器

分类:技术文档 - Golang | 阅读(198) | 发布于:2019-04-02 14:53 | 标签:无

使用golang实现一个简单的ftp服务器,方便传文件

使用:
ftpd.exe [-user=admin] [-pass=123456] [-root=C:\www] [-host=0.0.0.0] [-post=21]
可双击打开直接运行,即使用默认配置.
默认 用户名:admin (-user=admin)
密码:123456 (-pass=123456)
根目录:(-root=当前执行文件所在目录)

下载Windows版本:ftpd.exe

源码:(运行后程序会自动生成 code/main.go 的源文件)

package main

import (
	"flag"
	"log"

	"github.com/goftp/file-driver"
	"github.com/goftp/server"
	"path/filepath"
	"os"
	"strings"
	"fmt"
)

func init() {

	/*
	err := asset.RestoreAssets("./", "code")
	if err != nil {
		fmt.Println("RestoreAssets code error", "\n", err, "\n")
	}
	*/

	fmt.Println("to build exe file usage:")
	fmt.Println("go-bindata -o asset/bindata.go -pkg asset code")
	fmt.Println(`go build -o ftpd.exe code\main.go`)
	fmt.Println()
}

func main() {
	var (
		root = flag.String("root", getCurrentDirectory(), "Root directory to serve")
		user = flag.String("user", "admin", "Username for login")
		pass = flag.String("pass", "123456", "Password for login")
		port = flag.Int("port", 21, "Port")
		host = flag.String("host", "0.0.0.0", "Port")
	)
	flag.Parse()

	if *root == "" {
		*root = getCurrentDirectory()
	}

	factory := &filedriver.FileDriverFactory{
		RootPath: *root,
		Perm:     server.NewSimplePerm("user", "group"),
	}

	opts := &server.ServerOpts{
		Factory:  factory,
		Port:     *port,
		Hostname: *host,
		Auth:     &server.SimpleAuth{Name: *user, Password: *pass},
	}

	log.Printf("Starting ftp server on \n"+
		"Host      %v \n"+
		"Port      %v \n"+
		"Username  %v \n"+
		"Password  %v \n"+
		"RootDir   %s \n\n",
		opts.Hostname, opts.Port, *user, *pass, factory.RootPath)

	ftpServer := server.NewServer(opts)
	err := ftpServer.ListenAndServe()
	if err != nil {
		log.Fatal("Error starting server:", err)
	}
}

func getCurrentDirectory() string {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatal(err)
	}
	return strings.Replace(dir, "\\", "/", -1)
}

阅读更多...

golang打包封装静态资源 go-bindata

分类:技术文档 - Golang | 阅读(180) | 发布于:2019-04-01 20:55 | 标签:无

使用 Go 开发应用的时候,有时会遇到需要读取静态资源的情况。 比如开发 Web 应用,程序需要加载模板文件生成输出的 HTML。 在程序部署的时候,除了发布应用可执行文件外,还需要发布依赖的静态资源文件。这给发布过程添加了一些麻烦。 既然发布单独一个可执行文件是非常简单的操作,就有人会想办法把静态资源文件打包进 Go 的程序文件中。
下面就来看一个解决方案:go-bindata

go-bindata 可以把多个静态文件内容转码嵌入到一个 go 文件中,并提供一些操作方法。

安装 go-bindata :

go get -u github.com/jteeuwen/go-bindata/...
注意 go get 地址最后的三个点 ...。这样会分析所有子目录并下载依赖编译子目录内容。go-bindata 的命令工具在子目录中。

使用 go-bindata :

go-bindata -o=app/asset/asset.go -pkg=asset source/... theme/... doc/source/... doc/theme/... 
-o 输出文件到 app/asset/asset.go,包名 -pkg=asset,然后是需要打包的目录,三个点包括所有子目录。
这样就可以把所有相关文件打包到asset包的asset.go文件中,且开头是 package asset 保持和目录一致。

项目中释放静态文件的代码 :

dirs := []string{"source", "theme", "doc"} // 设置需要释放的目录

for _, dir := range dirs {
    // 解压dir目录到当前目录
    if err := asset.RestoreAssets("./", dir); err != nil {
        fmt.Println("RestoreAssets ", dir, "error", "\n", err)
    }
}
asset.go 内的静态内容还是根据实际的目录位置索引。所以我们可以直接通过目录或者文件地址去操作。

遇到的坑:

1. 安装时没有权限 : Windows上运行没有权限的使用管理员身份运行
2. go-bindata找不到路径: 记得把 $GOPATH/bin 加入系统 PATH
3. 生成的压缩包文件太大,goland编辑器加载出错:[the file size(10M) exceeds configured limit(4M).Code insight features are not available] :重新设置编辑器IDE的idea.max.intellisense.filesize值:(idea.max.intellisense.filesize=10240)
4. 解压操作[RestoreAssets]可以放到init()的函数中,项目启动后优先启动。
5. 流程:先使用go-bindata打包静态资源目录及文件[go-bindata -o=asset\asset.go -pkg=asset webServer/...],然后使用go build命令生成二进制可执行文件,最后把该可执行文件复制到其运行目录就可以直接运行而不丢失静态资源文件。
6. go-bindata命令必须指定一个"-pkg asset",并且"-o asset/asset.go"? 此处的pkg不能直接使用main吗,直接放生成main包里面的文件并且放入main包后 代码内调用RestoreAssets函数报错。。。

阅读更多...

小巧HTTP静态资源服务器

分类:技术文档 - Golang | 阅读(231) | 发布于:2019-03-04 17:06 | 标签:无

工作中难免遇到使用http去传输一些文件,为了避免每次去下载安装一些软件,自己使用golang快速实现一个。双击打开,填入端口号及路径,直接访问浏览器“http://IP:端口”号即可浏览文件

代码如下:(后面提供windows版本下载地址)

package main

import (
	"os"
	"fmt"
	"time"
	"strings"
	"net/http"
	"path/filepath"
)

func main() {

	//设定默认值
	defaultPort, defaultDir := "8080", getCurrentDirectory()
	port, dir := "", ""

	if len(os.Args) > 1 {
		//使用命令行直接指定参数
		for k, v := range os.Args {
			if k == 0 {
				continue
			}

			if v == "" {
				continue
			}

			val := strings.Split(v, "=")

			if strings.ToLower(val[0]) == "-port" {
				port = strings.ToLower(val[1])
				continue
			}

			if strings.ToLower(val[0]) == "-dir" {
				dir = strings.ToLower(val[1])
				continue
			}
		}
	}

	//Usage
	if len(os.Args) < 3 || port == "" || dir == "" {
		fmt.Println("Usage: " + os.Args[0] + " [-port=?] [-dir=?] ")
	}

	//使用命令交互模式指定参数port
	if port == "" {
		fmt.Println("Please input http port [default is \"" + defaultPort + "\"] :")
		fmt.Scanln(&port)
		if port == "" {
			port = defaultPort
		}
	}

	//使用命令交互模式指定参数dir
	if dir == "" {
		fmt.Println("Please input http static resource directory [default is \"" + defaultDir + "\"] :")
		fmt.Scanln(&dir)
		if dir == "" {
			dir = defaultDir
		}
	}

	fmt.Println("HttpServer is running at port:" + port + " directory:" + dir)
	fmt.Println("Input \"Ctrl+C\" to stop it.")
	http.Handle("/", http.FileServer(http.Dir(dir)))
	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		fmt.Println(err)
		time.Sleep(time.Duration(5) * time.Second)
	}
}

//获取当前程序所在目录
func getCurrentDirectory() string {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		return "./"
	}
	return strings.Replace(dir, "\\", "/", -1)
}

自己编译命令:

go build -o fileServer.exe fileServer.go

直接下载Windows版本:fileServer.exe

阅读更多...

golang http 路由的轻量实现

分类:技术文档 - Golang | 阅读(232) | 发布于:2019-02-28 17:30 | 标签:无

package main

import (
	"net/http"
	"fmt"
)

type Application struct {
	Route map[string]map[string]http.HandlerFunc
}

func NewApplication(route map[string]map[string]http.HandlerFunc) *Application{
	return &Application{Route:route}
}

//路由表初始化/注册路由
func (app *Application) HandleFunc(method, path string, f http.HandlerFunc) {
	if app.Route == nil {
		app.Route = make(map[string]map[string]http.HandlerFunc)
	}
	if app.Route[method] == nil {
		app.Route[method] = make(map[string]http.HandlerFunc)
	}
	app.Route[method][path] = f
}

//实现路由核心处理ServeHTTP方法
func (app *Application) ServeHTTP(res http.ResponseWriter, req *http.Request) {
	//写log
	fmt.Println(req.Method, req.URL.Path)

	//路由执行
	if f, ok := app.Route[req.Method][req.URL.Path]; ok {
		f(res, req)
	} else {
		res.WriteHeader(404)
		fmt.Fprintf(res, "404 Page Not Found")
	}
}

//Get路由注册
func (app *Application) Get(path string, HandlerFunc http.HandlerFunc) {
	app.HandleFunc("GET", path, HandlerFunc)
}

//Post路由注册
func (app *Application) Post(path string, HandlerFunc http.HandlerFunc) {
	app.HandleFunc("POST", path, HandlerFunc)
}

//启动Http
func (app *Application) Run(addr string) error {
	return http.ListenAndServe(addr, app)
}

//Get操作
func IndexHandel(res http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(res, "Index Page")
}

//Get操作
func HomePageHandel(res http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(res, "Show Homepage")
}

//Post操作
func EditHomePageHandel(res http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(res, "Edit Homepage")
}

func main() {
	app := NewApplication(nil)
	app.Get("/", IndexHandel)
	app.Get("/home", HomePageHandel)
	app.Post("/home", EditHomePageHandel)

	err := app.Run(":8080")
	if err != nil {
		fmt.Println(err.Error())
	}
}

阅读更多...

php数据压缩函数gzcompress使用

分类:技术文档 - PHP文档 | 阅读(322) | 发布于:2018-11-05 19:22 | 标签:无

压缩:gzcompress 解压:gzuncompress

 123,
    'name' => '张三',
    'age' => 24
];

$json = json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
$compressed= gzcompress($json);
$mysql_insert_data = base64_encode($compressed);

$compressed_new = base64_decode($mysql_insert_data);
$json_new = gzuncompress($compressed_new);
$data_new = json_decode($json_new, true);

print_r($data_new);

1.如果要json_encode的话,要先于gzcompress执行。如果gzcompress先执行,json_encode返回的是空值。
2.gzcompress(json_encode(数组))这种写法是不对的,得到的结果是一堆乱码。必须分开写,json_encode处理结果赋值给一个变量,然后gzcompress处理这一变量
3.gzcompress结果直接存入数据库不会成功。可以base64_encode一下。

经研究发现, gzuncompress的处理结果与zlib_decode的处理结果相同. (在gzcompress时使用了zlib格式). 把gzuncompress函数,换成zlib_decode即可. 完全不影响使用.

阅读更多...

redis管道pipeline的运用

分类:技术文档 - Redis | 阅读(264) | 发布于:2018-11-05 18:04 | 标签:无

Redis使用的是客户端-服务器(CS)模型和请求/响应协议的TCP服务器。这意味着通常情况下一个请求会遵循以下步骤:
客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。服务端处理命令,并将结果返回给客户端。

普通模式与管道模式
普通模式:由于通信会有网络延迟,假如client和server之间的包传输时间需要0.125秒。那么上面的三个命令6个报文至少需要0.75秒才能完成。这样即使redis每秒能处理100个命令,而我们的client也只能一秒钟发出四个命令。这显然没有充分利用 redis的处理能力。
管道模式:(pipeline)可以一次性发送多条命令并在执行完后一次性将结果返回,pipeline通过减少客户端与redis的通信次数来实现降低往返延时时间,而且Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性。 Pipeline 的默认的同步的个数为53个,也就是说arges中累加到53条数据时会把数据提交。其过程如下图所示:client可以将三个命令放到一个tcp报文一起发送,server则可以将三条命令的处理结果放到一个tcp报文返回。 需要注意到是用 pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。具体多少合适需要根据具体情况测试。

$redis = new Redis();
$redis->connect('127.0.0.1', 5678);
$redis->select(9);
$memberId = 12345678;
$memberHandler = ServBox()->Member();
$pipeline = $redis->pipeline();
while ($memberId > 0) {
    $pipeline->del($memberHandler->getKeyMemberInfo($memberId));
    if ($memberId % 50 == 0) {
        $pipeline->exec();
        $pipeline = $redis->pipeline();
    }
    $memberId--;
}
$pipeline->exec();
$redis = new Redis();

$redis->connect('127.0.0.1', 5678);
$redis->pipeline();
for ($i=0;$i<1000;$i++)
{
    $redis->set("test_{$i}",pow($i,2));
    $redis->get("test_{$i}");
}
$redis->exec();
$redis->close();

阅读更多...