From b833ff8d4c95cc6585e08d3826545b198447eaf4 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sat, 8 Jun 2019 14:40:31 +0800 Subject: [PATCH 01/14] init mc doc --- doc/wiki-cn/cache-mc.md | 232 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) diff --git a/doc/wiki-cn/cache-mc.md b/doc/wiki-cn/cache-mc.md index e69de29bb..97b2e77c7 100644 --- a/doc/wiki-cn/cache-mc.md +++ b/doc/wiki-cn/cache-mc.md @@ -0,0 +1,232 @@ +# 准备工作 + +1. 推荐使用[kratos工具](kratos-tool.md)快速生成项目,如我们生成一个叫`kratos-demo`的项目。目录结构如下: + +``` +├── CHANGELOG.md +├── CONTRIBUTORS.md +├── LICENSE +├── README.md +├── cmd +│   ├── cmd +│   └── main.go +├── configs +│   ├── application.toml +│   ├── grpc.toml +│   ├── http.toml +│   ├── log.toml +│   ├── memcache.toml +│   ├── mysql.toml +│   └── redis.toml +├── go.mod +├── go.sum +└── internal + ├── dao + │   └── dao.go + ├── model + │   └── model.go + ├── server + │   └── http + │   └── http.go + └── service + └── service.go +``` + +# 开始使用 + +## 配置 + +创建项目成功后,进入项目中的configs目录,打开memcache.toml,我们可以看到: + +```toml +demoExpire = "24h" +[demo] + name = "kratos-demo" + proto = "tcp" + addr = "127.0.0.1:11211" + active = 50 + idle = 10 + dialTimeout = "100ms" + readTimeout = "200ms" + writeTimeout = "300ms" + idleTimeout = "80s" +``` +在该配置文件中我们可以配置memcache的连接方式proto、连接地址addr、连接池的闲置连接数idle、最大连接数active以及各类超时。 + +通过paladin配置管理工具,将memcache.toml中的配置解析到*memcache.Config中,这里可选添加mc的过期时间设置。 + +## 初始化 + +进入项目的internal/dao目录,打开dao.go,其中: + +```go + var ( + mc struct { + Demo *memcache.Config + DemoExpire xtime.Duration + } + ) + checkErr(paladin.Get("memcache.toml").UnmarshalTOML(&mc)) +``` +使用paladin配置管理工具将上文中的memcache.toml中的配置解析为我们需要使用的配置。 + +```go +// Dao dao. +type Dao struct { + mc *memcache.Memcache + mcExpire int32 +} +``` + +在dao的主结构提中定义了memcache的连接池对象和过期时间。 + +```go + dao = &Dao{ + // memcache + mc: memcache.New(mc.Demo), + mcExpire: int32(time.Duration(mc.DemoExpire) / time.Second), + } +``` + +使用memcache包的New方法进行连接池对象的初始化,需要传入上文解析的配置。 + +## Ping + +```go +// Ping ping the resource. +func (d *Dao) Ping(ctx context.Context) (err error) { + return d.pingMC(ctx) +} + +func (d *Dao) pingMC(ctx context.Context) (err error) { + if err = d.mc.Set(ctx, &memcache.Item{Key: "ping", Value: []byte("pong"), Expiration: 0}); err != nil { + log.Error("conn.Set(PING) error(%v)", err) + } + return +} +``` + +生成的dao层模板中自带了memcache相关的ping方法,用于为负载均衡服务的健康监测提供依据,详见[blademaster](blademaster-quickstart.md)。 + +## 关闭 + +```go +// Close close the resource. +func (d *Dao) Close() { + d.mc.Close() +} +``` + +在关闭dao层时,通过调用连接池对象的Close方法,我们可以关闭该连接池,从而释放相关资源。 + +# 常用方法 + +这里我们可以选择使用[memcache代码生成器](kratos-genmc.md)帮助我们生成memcache操作的相关代码。 + +## 单个查询 + +```go +// CacheDemo get data from mc +func (d *Dao) CacheDemo(c context.Context, id int64) (res *Demo, err error) { + key := demoKey(id) + res = &Demo{} + if err = d.mc.Get(c, key).Scan(res); err != nil { + res = nil + if err == memcache.ErrNotFound { + err = nil + } + } + if err != nil { + prom.BusinessErrCount.Incr("mc:CacheDemo") + log.Errorv(c, log.KV("CacheDemo", fmt.Sprintf("%+v", err)), log.KV("key", key)) + return + } + return +} +``` + +如上为代码生成器生成的进行单个查询的代码,使用到mc.Get(c,key)方法获得返回值,再使用scan方法将memcache的返回值转换为我们定义的结构体。 + +## 批量查询使用 + +```go + replies, err := d.mc.GetMulti(c, keys) + for _, key := range replies.Keys() { + v := &Demo{} + err = replies.Scan(key, v) + } +``` + +如上为代码生成器生成的进行批量查询的代码片段,这里使用到mc.GetMulti(c,keys)方法获得返回值,与单个查询类似地,我们需要再使用scan方法将memcache的返回值转换为我们定义的结构体。 + +## 新增 + +```go +// AddCacheDemo Set data to mc +func (d *Dao) AddCacheDemo(c context.Context, id int64, val *Demo) (err error) { + if val == nil { + return + } + key := demoKey(id) + item := &memcache.Item{Key: key, Object: val, Expiration: d.demoExpire, Flags: memcache.FlagJSON | memcache.FlagGzip} + if err = d.mc.Set(c, item); err != nil { + prom.BusinessErrCount.Incr("mc:AddCacheDemo") + log.Errorv(c, log.KV("AddCacheDemo", fmt.Sprintf("%+v", err)), log.KV("key", key)) + return + } + return +} +``` + +如上为代码生成器生成的添加结构体进入memcache的代码,这里需要使用到的是mc.Set方法进行设置。 +这里使用的item为memcache.Item结构体,包含key, value, 超时时间(秒), Flags。 + +### Flags + + +上文添加结构体进入memcache中,使用到的flags为:memcache.FlagJSON | memcache.FlagGzip代表着:使用json作为编码方式,gzip作为压缩方式。 + +Flags的相关常量在memcache包中进行定义,包含编码方式如gob, json, protobuf,和压缩方式gzip。 + +```go +const( + // Flag, 15(encoding) bit+ 17(compress) bit + + // FlagRAW default flag. + FlagRAW = uint32(0) + // FlagGOB gob encoding. + FlagGOB = uint32(1) << 0 + // FlagJSON json encoding. + FlagJSON = uint32(1) << 1 + // FlagProtobuf protobuf + FlagProtobuf = uint32(1) << 2 + // FlagGzip gzip compress. + FlagGzip = uint32(1) << 15 +) +``` + +## 删除 + +```go +// DelCacheDemo delete data from mc +func (d *Dao) DelCacheDemo(c context.Context, id int64) (err error) { + key := demoKey(id) + if err = d.mc.Delete(c, key); err != nil { + if err == memcache.ErrNotFound { + err = nil + return + } + prom.BusinessErrCount.Incr("mc:DelCacheDemo") + log.Errorv(c, log.KV("DelCacheDemo", fmt.Sprintf("%+v", err)), log.KV("key", key)) + return + } + return +} +``` +如上为代码生成器生成的从memcache中删除KV的代码,这里需要使用到的是mc.Delete方法。 +和查询时类似地,当memcache中不存在参数中的key时,会返回error为memcache.ErrNotFound。如果不需要处理这种error,可以和如上代码类似地,将返回出去的error置为nil。 + +# 扩展阅读 + +[memcache代码生成器](kratos-genmc.md) +[redis模块说明](cache-redis.md) \ No newline at end of file From 08dfced46a5aceaab7262e9c169ae6ccc0b8f36d Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sat, 8 Jun 2019 14:45:33 +0800 Subject: [PATCH 02/14] add --- doc/wiki-cn/cache-mc.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/wiki-cn/cache-mc.md b/doc/wiki-cn/cache-mc.md index 97b2e77c7..6f6c295d6 100644 --- a/doc/wiki-cn/cache-mc.md +++ b/doc/wiki-cn/cache-mc.md @@ -1,6 +1,6 @@ # 准备工作 -1. 推荐使用[kratos工具](kratos-tool.md)快速生成项目,如我们生成一个叫`kratos-demo`的项目。目录结构如下: +推荐使用[kratos工具](kratos-tool.md)快速生成项目,如我们生成一个叫`kratos-demo`的项目。目录结构如下: ``` ├── CHANGELOG.md @@ -159,7 +159,7 @@ func (d *Dao) CacheDemo(c context.Context, id int64) (res *Demo, err error) { 如上为代码生成器生成的进行批量查询的代码片段,这里使用到mc.GetMulti(c,keys)方法获得返回值,与单个查询类似地,我们需要再使用scan方法将memcache的返回值转换为我们定义的结构体。 -## 新增 +## 设置KV ```go // AddCacheDemo Set data to mc @@ -205,7 +205,7 @@ const( ) ``` -## 删除 +## 删除KV ```go // DelCacheDemo delete data from mc From b5953f4adb0a334ce21fc248a5c6be02d9e491e6 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sat, 8 Jun 2019 14:49:47 +0800 Subject: [PATCH 03/14] add --- doc/wiki-cn/cache-mc.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/wiki-cn/cache-mc.md b/doc/wiki-cn/cache-mc.md index 6f6c295d6..1b2f9b332 100644 --- a/doc/wiki-cn/cache-mc.md +++ b/doc/wiki-cn/cache-mc.md @@ -53,7 +53,8 @@ demoExpire = "24h" ``` 在该配置文件中我们可以配置memcache的连接方式proto、连接地址addr、连接池的闲置连接数idle、最大连接数active以及各类超时。 -通过paladin配置管理工具,将memcache.toml中的配置解析到*memcache.Config中,这里可选添加mc的过期时间设置。 +这里可选添加mc的过期时间设置。 + ## 初始化 @@ -121,7 +122,9 @@ func (d *Dao) Close() { # 常用方法 -这里我们可以选择使用[memcache代码生成器](kratos-genmc.md)帮助我们生成memcache操作的相关代码。 +推荐使用[memcache代码生成器](kratos-genmc.md)帮助我们生成memcache操作的相关代码。 + +以下我们来逐一解析以下memcache包中提供的常用方法。 ## 单个查询 @@ -224,7 +227,7 @@ func (d *Dao) DelCacheDemo(c context.Context, id int64) (err error) { } ``` 如上为代码生成器生成的从memcache中删除KV的代码,这里需要使用到的是mc.Delete方法。 -和查询时类似地,当memcache中不存在参数中的key时,会返回error为memcache.ErrNotFound。如果不需要处理这种error,可以和如上代码类似地,将返回出去的error置为nil。 +和查询时类似地,当memcache中不存在参数中的key时,会返回error为memcache.ErrNotFound。如果不需要处理这种error,可以参考上述代码将返回出去的error置为nil。 # 扩展阅读 From ddcf89b2c7d7740c506495324ded18b419a19ec4 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sat, 8 Jun 2019 15:58:23 +0800 Subject: [PATCH 04/14] add redis --- doc/wiki-cn/cache-mc.md | 10 +- doc/wiki-cn/cache-redis.md | 234 +++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 5 deletions(-) diff --git a/doc/wiki-cn/cache-mc.md b/doc/wiki-cn/cache-mc.md index 1b2f9b332..ab261f5ee 100644 --- a/doc/wiki-cn/cache-mc.md +++ b/doc/wiki-cn/cache-mc.md @@ -89,7 +89,7 @@ type Dao struct { } ``` -使用memcache包的New方法进行连接池对象的初始化,需要传入上文解析的配置。 +使用kratos/pkg/cache/memcache包的New方法进行连接池对象的初始化,需要传入上文解析的配置。 ## Ping @@ -118,13 +118,13 @@ func (d *Dao) Close() { } ``` -在关闭dao层时,通过调用连接池对象的Close方法,我们可以关闭该连接池,从而释放相关资源。 +在关闭dao层时,通过调用memcache连接池对象的Close方法,我们可以关闭该连接池,从而释放相关资源。 # 常用方法 推荐使用[memcache代码生成器](kratos-genmc.md)帮助我们生成memcache操作的相关代码。 -以下我们来逐一解析以下memcache包中提供的常用方法。 +以下我们来逐一解析以下kratos/pkg/cache/memcache包中提供的常用方法。 ## 单个查询 @@ -148,7 +148,7 @@ func (d *Dao) CacheDemo(c context.Context, id int64) (res *Demo, err error) { } ``` -如上为代码生成器生成的进行单个查询的代码,使用到mc.Get(c,key)方法获得返回值,再使用scan方法将memcache的返回值转换为我们定义的结构体。 +如上为代码生成器生成的进行单个查询的代码,使用到mc.Get(c,key)方法获得返回值,再使用scan方法将memcache的返回值转换为golang中的类型(如string,bool, 结构体等)。 ## 批量查询使用 @@ -189,7 +189,7 @@ func (d *Dao) AddCacheDemo(c context.Context, id int64, val *Demo) (err error) { 上文添加结构体进入memcache中,使用到的flags为:memcache.FlagJSON | memcache.FlagGzip代表着:使用json作为编码方式,gzip作为压缩方式。 -Flags的相关常量在memcache包中进行定义,包含编码方式如gob, json, protobuf,和压缩方式gzip。 +Flags的相关常量在kratos/pkg/cache/memcache包中进行定义,包含编码方式如gob, json, protobuf,和压缩方式gzip。 ```go const( diff --git a/doc/wiki-cn/cache-redis.md b/doc/wiki-cn/cache-redis.md index e69de29bb..c196759ff 100644 --- a/doc/wiki-cn/cache-redis.md +++ b/doc/wiki-cn/cache-redis.md @@ -0,0 +1,234 @@ +# 准备工作 + +推荐使用[kratos工具](kratos-tool.md)快速生成项目,如我们生成一个叫`kratos-demo`的项目。目录结构如下: + +``` +├── CHANGELOG.md +├── CONTRIBUTORS.md +├── LICENSE +├── README.md +├── cmd +│   ├── cmd +│   └── main.go +├── configs +│   ├── application.toml +│   ├── grpc.toml +│   ├── http.toml +│   ├── log.toml +│   ├── memcache.toml +│   ├── mysql.toml +│   └── redis.toml +├── go.mod +├── go.sum +└── internal + ├── dao + │   └── dao.go + ├── model + │   └── model.go + ├── server + │   └── http + │   └── http.go + └── service + └── service.go +``` + +# 开始使用 + +## 配置 + +创建项目成功后,进入项目中的configs目录,打开redis.toml,我们可以看到: + +```toml +demoExpire = "24h" + +[demo] + name = "kratos-demo" + proto = "tcp" + addr = "127.0.0.1:6389" + idle = 10 + active = 10 + dialTimeout = "1s" + readTimeout = "1s" + writeTimeout = "1s" + idleTimeout = "10s" +``` + +在该配置文件中我们可以配置redis的连接方式proto、连接地址addr、连接池的闲置连接数idle、最大连接数active以及各类超时。 + +这里可选添加redis的过期时间设置。 + + +## 初始化 + +进入项目的internal/dao目录,打开dao.go,其中: + +```go + var ( + rc struct { + Demo *redis.Config + DemoExpire xtime.Duration + } + ) + checkErr(paladin.Get("redis.toml").UnmarshalTOML(&rc)) +``` +使用paladin配置管理工具将上文中的redis.toml中的配置解析为我们需要使用的配置。 + +```go +// Dao dao. +type Dao struct { + redis *redis.Pool + redisExpire int32 +} +``` + +在dao的主结构提中定义了redis的连接池对象和过期时间。 + +```go + dao = &Dao{ + // redis + redis: redis.NewPool(rc.Demo), + redisExpire: int32(time.Duration(rc.DemoExpire) / time.Second), + } +``` + +使用kratos/pkg/cache/redis包的New方法进行连接池对象的初始化,需要传入上文解析的配置。 + +## Ping + +```go +// Ping ping the resource. +func (d *Dao) Ping(ctx context.Context) (err error) { + return d.pingRedis(ctx) +} + +func (d *Dao) pingRedis(ctx context.Context) (err error) { + conn := d.redis.Get(ctx) + defer conn.Close() + if _, err = conn.Do("SET", "ping", "pong"); err != nil { + log.Error("conn.Set(PING) error(%v)", err) + } + return +} +``` + +生成的dao层模板中自带了redis相关的ping方法,用于为负载均衡服务的健康监测提供依据,详见[blademaster](blademaster-quickstart.md)。 + +## 关闭 + +```go +// Close close the resource. +func (d *Dao) Close() { + d.redis.Close() +} +``` + +在关闭dao层时,通过调用redis连接池对象的Close方法,我们可以关闭该连接池,从而释放相关资源。 + +# 常用方法 + +## 发送单个命令 Do + +```go +// DemoIncrby . +func (d *Dao) DemoIncrby(c context.Context, pid int) (err error) { + cacheKey := keyDemo(pid) + conn := d.redis.Get(c) + defer conn.Close() + if _, err = conn.Do("INCRBY", cacheKey, 1); err != nil { + log.Error("DemoIncrby conn.Do(INCRBY) key(%s) error(%v)", cacheKey, err) + } + return +} +``` +如上为向redis server发送一个命令的用法示意。这里需要使用redis连接池的Get方法获取一个redis连接conn,再使用conn.Do方法即可发送一条指令。 +注意,在使用该连接完毕后,需要使用conn.Close方法将该连接关闭。 + +## 批量发送命令 Pipeline + +kratos/pkg/cache/redis包除了支持发送单个命令,也支持批量发送命令(redis pipeline) + +```go +// DemoIncrbys . +func (d *Dao) DemoIncrbys(c context.Context, pid int, ) (err error) { + cacheKey1 := keyDemo(pid) + cacheKey2 := keyDemo2(pid) + conn := d.redis.Get(c) + defer conn.Close() + if err = conn.Send("INCRBY", cacheKey1, 1); err != nil { + log.Error("DemoIncrby conn.Do(INCRBY) key(%s) error(%v)", cacheKey, err) + return + } + if err = conn.Send("EXPIRE", cacheKey1, d.redisExpire); err != nil { + log.Error("conn.Do(EXPIRE, %s, %d) error(%v)", hKey, _hkeyExpire, err) + return + } + if err = conn.Send("INCRBY", cacheKey2, 1); err != nil { + log.Error("DemoIncrby conn.Do(INCRBY) key(%s) error(%v)", cacheKey, err) + return + } + if err = conn.Send("EXPIRE", cacheKey2, d.redisExpire); err != nil { + log.Error("conn.Do(EXPIRE, %s, %d) error(%v)", hbvKey, _hkeyExpire, err) + return + } + if err = conn.Flush(); err != nil { + log.Error("conn.Flush error(%v)", err) + return + } + for i := 0; i < 4; i++ { + if _, err = conn.Receive(); err != nil { + log.Error("conn.Receive error(%v)", err) + return + } + } + return +} +``` +如上为向redis server批量发送命令的用法示意。 + +和发送单个命令类似地,这里需要使用redis连接池的Get方法获取一个redis连接conn,在使用该连接完毕后,需要使用conn.Close方法将该连接关闭。 + +这里使用conn.Send方法将命令写入客户端的buffer(缓冲区)中,使用conn.Flush将客户端的缓冲区内的命令打包发送到redis server。redis server按顺序返回的reply可以使用conn.Receive方法进行接收和处理。 + + +## 返回值转换 + +与[memcache包](cache-memcache.md)类似地,kratos/pkg/cache/redis包中也提供了Scan方法将redis server的返回值转换为golang类型。 + +除此之外,kratos/pkg/cache/redis包提供了大量返回值转换的快捷方式: + +### 单个查询 + +单个查询可以使用redis.Uint64/Int64/Float64/Int/String/Bool/Bytes进行返回值的转换,比如: + +```go +// GetDemo get +func (d *Dao) GetDemo(ctx context.Context, key string) (string, error) { + conn := d.redis.Get(ctx) + defer conn.Close() + return redis.String(conn.Do("GET", key)) +} +``` + +### 批量查询 + +批量查询时候,可以使用redis.Int64s,Ints,Strings,ByteSlices方法转换如MGET,HMGET,ZRANGE,SMEMBERS等命令的返回值。 +还可以使用StringMap, IntMap, Int64Map方法转换HGETALL命令的返回值,比如: + +```go +// HGETALLDemo get +func (d *Dao) HGETALLDemo(c context.Context, pid int64) (res map[string]int64, err error) { + var ( + key = keyDemo(pid) + conn = d.redis.Get(c) + ) + defer conn.Close() + if res, err = redis.Int64Map(conn.Do("HGETALL", key)); err != nil { + log.Error("HGETALL %v failed error(%v)", key, err) + } + return +} +``` + +# 扩展阅读 + +[memcache模块说明](cache-memcache.md) \ No newline at end of file From 1d162fb291c516214f36b2ca2c5d349959579a92 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sat, 8 Jun 2019 16:04:55 +0800 Subject: [PATCH 05/14] add --- doc/wiki-cn/cache-redis.md | 50 +++++++++++++++----------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/doc/wiki-cn/cache-redis.md b/doc/wiki-cn/cache-redis.md index c196759ff..d75a5b47b 100644 --- a/doc/wiki-cn/cache-redis.md +++ b/doc/wiki-cn/cache-redis.md @@ -91,7 +91,7 @@ type Dao struct { } ``` -使用kratos/pkg/cache/redis包的New方法进行连接池对象的初始化,需要传入上文解析的配置。 +使用kratos/pkg/cache/redis包的NewPool方法进行连接池对象的初始化,需要传入上文解析的配置。 ## Ping @@ -140,50 +140,38 @@ func (d *Dao) DemoIncrby(c context.Context, pid int) (err error) { return } ``` -如上为向redis server发送一个命令的用法示意。这里需要使用redis连接池的Get方法获取一个redis连接conn,再使用conn.Do方法即可发送一条指令。 +如上为向redis server发送单个命令的用法示意。这里需要使用redis连接池的Get方法获取一个redis连接conn,再使用conn.Do方法即可发送一条指令。 注意,在使用该连接完毕后,需要使用conn.Close方法将该连接关闭。 ## 批量发送命令 Pipeline -kratos/pkg/cache/redis包除了支持发送单个命令,也支持批量发送命令(redis pipeline) +kratos/pkg/cache/redis包除了支持发送单个命令,也支持批量发送命令(redis pipeline),比如: ```go // DemoIncrbys . -func (d *Dao) DemoIncrbys(c context.Context, pid int, ) (err error) { - cacheKey1 := keyDemo(pid) - cacheKey2 := keyDemo2(pid) +func (d *Dao) DemoIncrbys(c context.Context, pid int) (err error) { + cacheKey := keyDemo(pid) conn := d.redis.Get(c) defer conn.Close() - if err = conn.Send("INCRBY", cacheKey1, 1); err != nil { - log.Error("DemoIncrby conn.Do(INCRBY) key(%s) error(%v)", cacheKey, err) + if err = conn.Send("INCRBY", cacheKey, 1); err != nil { return } - if err = conn.Send("EXPIRE", cacheKey1, d.redisExpire); err != nil { - log.Error("conn.Do(EXPIRE, %s, %d) error(%v)", hKey, _hkeyExpire, err) + if err = conn.Send("EXPIRE", cacheKey, d.redisExpire); err != nil { return } - if err = conn.Send("INCRBY", cacheKey2, 1); err != nil { - log.Error("DemoIncrby conn.Do(INCRBY) key(%s) error(%v)", cacheKey, err) - return + if err = conn.Flush(); err != nil { + log.Error("conn.Flush error(%v)", err) + return } - if err = conn.Send("EXPIRE", cacheKey2, d.redisExpire); err != nil { - log.Error("conn.Do(EXPIRE, %s, %d) error(%v)", hbvKey, _hkeyExpire, err) - return - } - if err = conn.Flush(); err != nil { - log.Error("conn.Flush error(%v)", err) - return - } - for i := 0; i < 4; i++ { - if _, err = conn.Receive(); err != nil { - log.Error("conn.Receive error(%v)", err) - return - } - } - return + for i := 0; i < 2; i++ { + if _, err = conn.Receive(); err != nil { + log.Error("conn.Receive error(%v)", err) + return + } + } + return } ``` -如上为向redis server批量发送命令的用法示意。 和发送单个命令类似地,这里需要使用redis连接池的Get方法获取一个redis连接conn,在使用该连接完毕后,需要使用conn.Close方法将该连接关闭。 @@ -192,7 +180,7 @@ func (d *Dao) DemoIncrbys(c context.Context, pid int, ) (err error) { ## 返回值转换 -与[memcache包](cache-memcache.md)类似地,kratos/pkg/cache/redis包中也提供了Scan方法将redis server的返回值转换为golang类型。 +与[memcache包](cache-mc.md)类似地,kratos/pkg/cache/redis包中也提供了Scan方法将redis server的返回值转换为golang类型。 除此之外,kratos/pkg/cache/redis包提供了大量返回值转换的快捷方式: @@ -231,4 +219,4 @@ func (d *Dao) HGETALLDemo(c context.Context, pid int64) (res map[string]int64, e # 扩展阅读 -[memcache模块说明](cache-memcache.md) \ No newline at end of file +[memcache模块说明](cache-mc.md) \ No newline at end of file From 73039dcb2db3bb394c64c67a5ecc3b8e2e95a212 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sat, 8 Jun 2019 16:40:24 +0800 Subject: [PATCH 06/14] add --- doc/wiki-cn/cache.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/wiki-cn/cache.md b/doc/wiki-cn/cache.md index e69de29bb..7ec42fe7a 100644 --- a/doc/wiki-cn/cache.md +++ b/doc/wiki-cn/cache.md @@ -0,0 +1,19 @@ +# 背景 + +我们需要统一的cache包,用于进行各类缓存操作。 + +# 概览 + +* 缓存操作均使用连接池,保证较快的数据读写速度且提高系统的安全可靠性。 + +# Memcache + +[memcache模块说明](cache-mc.md) + +# Redis + +[redis模块说明](cache-redis.md) + +------------- + +[文档目录树](summary.md) From 2bba368402dc76bbe92ac497d84ef0bdb88ea10b Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sat, 8 Jun 2019 16:44:36 +0800 Subject: [PATCH 07/14] add --- doc/wiki-cn/cache.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/wiki-cn/cache.md b/doc/wiki-cn/cache.md index 7ec42fe7a..41f87d184 100644 --- a/doc/wiki-cn/cache.md +++ b/doc/wiki-cn/cache.md @@ -8,10 +8,14 @@ # Memcache +提供protobuf,gob,json序列化方式,gzip的memcache接口 + [memcache模块说明](cache-mc.md) # Redis +提供redis操作的各类接口以及各类将redis server返回值转换为golang类型的快捷方法。 + [redis模块说明](cache-redis.md) ------------- From 61c85829d14ef1098e05606207ac6ca8b0f04d99 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sun, 9 Jun 2019 10:57:37 +0800 Subject: [PATCH 08/14] add --- doc/wiki-cn/database-mysql.md | 228 ++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) diff --git a/doc/wiki-cn/database-mysql.md b/doc/wiki-cn/database-mysql.md index e69de29bb..1b5e4fe30 100644 --- a/doc/wiki-cn/database-mysql.md +++ b/doc/wiki-cn/database-mysql.md @@ -0,0 +1,228 @@ +# 准备工作 + +推荐使用[kratos工具](kratos-tool.md)快速生成项目,如我们生成一个叫`kratos-demo`的项目。目录结构如下: + +``` +├── CHANGELOG.md +├── CONTRIBUTORS.md +├── LICENSE +├── README.md +├── cmd +│   ├── cmd +│   └── main.go +├── configs +│   ├── application.toml +│   ├── grpc.toml +│   ├── http.toml +│   ├── log.toml +│   ├── memcache.toml +│   ├── mysql.toml +│   └── redis.toml +├── go.mod +├── go.sum +└── internal + ├── dao + │   └── dao.go + ├── model + │   └── model.go + ├── server + │   └── http + │   └── http.go + └── service + └── service.go +``` + +# 开始使用 + +## 配置 + +创建项目成功后,进入项目中的configs目录,mysql.toml,我们可以看到: + +```toml +[demo] + addr = "127.0.0.1:3306" + dsn = "{user}:{password}@tcp(127.0.0.1:3306)/{database}?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8mb4,utf8" + readDSN = ["{user}:{password}@tcp(127.0.0.2:3306)/{database}?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8mb4,utf8","{user}:{password}@tcp(127.0.0.3:3306)/{database}?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8,utf8mb4"] + active = 20 + idle = 10 + idleTimeout ="4h" + queryTimeout = "200ms" + execTimeout = "300ms" + tranTimeout = "400ms" +``` + +在该配置文件中我们可以配置mysql的读和写的dsn、连接地址addr、连接池的闲置连接数idle、最大连接数active以及各类超时。 + +如果配置了readDSN,在进行读操作的时候会优先使用readDSN的连接。 + +## 初始化 + +进入项目的internal/dao目录,打开dao.go,其中: + +```go + var ( + dc struct { + Demo *sql.Config + } + ) + checkErr(paladin.Get("mysql.toml").UnmarshalTOML(&dc)) +``` +使用paladin配置管理工具将上文中的mysql.toml中的配置解析为我们需要使用mysql的相关配置。 + +```go +// Dao dao. +type Dao struct { + db *sql.DB +} +``` + +在dao的主结构提中定义了mysql的连接池对象。 + +```go + dao = &Dao{ + db: sql.NewMySQL(dc.Demo), + } +``` + +使用kratos/pkg/database/sql包的NewMySQL方法进行连接池对象的初始化,需要传入上文解析的配置。 + +## Ping + +```go +// Ping ping the resource. +func (d *Dao) Ping(ctx context.Context) (err error) { + return d.db.Ping(ctx) +} +``` + +生成的dao层模板中自带了mysql相关的ping方法,用于为负载均衡服务的健康监测提供依据,详见[blademaster](blademaster-quickstart.md)。 + +## 关闭 + +```go +// Close close the resource. +func (d *Dao) Close() { + d.db.Close() +} +``` + +在关闭dao层时,通过调用mysql连接池对象的Close方法,我们可以关闭该连接池,从而释放相关资源。 + +# 常用方法 + +## 单个查询 + +```go +// GetUserRole 用户角色 +func (d *Dao) GetDemo(c context.Context, did int64) (demo int8, err error) { + err = d.db.QueryRow(c, _getDemoSQL, did).Scan(&demo) + if err != nil && err != sql.ErrNoRows { + log.Error("d.managerDB.Query error(%v)", err) + return + } + return demo, nil +} +``` + +db.QueryRow方法用于返回最多一条记录的查询,在QueryRow方法后使用Scan方法即可将mysql的返回值转换为Golang的数据类型。 + +## 批量查询 + +```go +// ResourceLogs ResourceLogs. +func (d *Dao) GetDemos(c context.Context, dids []int64) (demos []int8, err error) { + rows, err := d.db.Query(c, _getDemosSQL, dids) + if err != nil { + log.Error("query error(%v)", err) + return + } + defer rows.Close() + for rows.Next() { + var tmpD int8 + if err = rows.Scan(&tmpD); err != nil { + log.Error("scan demo log error(%v)", err) + return + } + demos = append(demos, tmpD) + } + return +} +``` + +db.Query方法一般用于批量查询的场景,返回*sql.Rows和error信息。 +我们可以使用rows.Next()方法获得下一行的返回结果,并且配合使用rows.Scan()方法将该结果转换为Golang的数据类型。当没有下一行时,rows.Next方法将返回false,此时循环结束。 + +注意,在使用完毕rows对象后,需要调用rows.Close方法关闭连接,释放相关资源。 + +## 执行语句 + +```go +// DemoExec exec +func (d *Dao) DemoExec(c context.Context, id int64) (rows int64, err error) { + res, err := d.db.Exec(c, _demoUpdateSQL, id) + if err != nil { + log.Error("db.DemoExec.Exec(%s) error(%v)", _demoUpdateSQL, err) + return + } + return res.RowsAffected() +} +``` + +执行UPDATE/DELETE/INSERT语句时,使用db.Exec方法进行语句执行,返回*sql.Result和error信息: + +```go + +// A Result summarizes an executed SQL command. +type Result interface { + LastInsertId() (int64, error) + RowsAffected() (int64, error) +} +``` + +Result接口支持获取影响行数和LastInsertId(一般用于获取Insert语句插入数据库后的主键ID) + + +## 事务 + +kratos/pkg/database/sql包同样支持事务操作。 + +开启一个事务: + +```go + tx := d.db.Begin() + if err = tx.Error; err != nil { + log.Error("db begin transcation failed, err=%+v", err) + return + } +``` + +在事务中执行语句: + +```go + res, err := tx.Exec(_demoSQL, did) + if err != nil { + return + } + rows := res.RowsAffected() +``` + +提交事务: + +```go + if err = tx.Commit().Error; err!=nil{ + log.Error("db commit transcation failed, err=%+v", err) + } +``` + +回滚事务: + +```go + if err = tx.Rollback().Error; err!=nil{ + log.Error("db rollback failed, err=%+v", rollbackErr) + } +``` + +# 扩展阅读 + +[tidb模块说明](database-tidb.md) +[hbase模块说明](database-hbase.md) \ No newline at end of file From 80c59fd1e70dfa17e01fb57239b1cc5a5b206db8 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sun, 9 Jun 2019 11:01:03 +0800 Subject: [PATCH 09/14] add --- doc/wiki-cn/database-mysql.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/wiki-cn/database-mysql.md b/doc/wiki-cn/database-mysql.md index 1b5e4fe30..c3d7c7730 100644 --- a/doc/wiki-cn/database-mysql.md +++ b/doc/wiki-cn/database-mysql.md @@ -113,7 +113,7 @@ func (d *Dao) Close() { ## 单个查询 ```go -// GetUserRole 用户角色 +// GetDemo 用户角色 func (d *Dao) GetDemo(c context.Context, did int64) (demo int8, err error) { err = d.db.QueryRow(c, _getDemoSQL, did).Scan(&demo) if err != nil && err != sql.ErrNoRows { @@ -126,6 +126,8 @@ func (d *Dao) GetDemo(c context.Context, did int64) (demo int8, err error) { db.QueryRow方法用于返回最多一条记录的查询,在QueryRow方法后使用Scan方法即可将mysql的返回值转换为Golang的数据类型。 +当mysql查询不到对应数据时,会返回sql.ErrNoRows,如果不需处理,可以参考如上代码忽略此error。 + ## 批量查询 ```go From 28402a310f98918d70e0ae6077b029e6d82f5fa5 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sun, 9 Jun 2019 11:01:54 +0800 Subject: [PATCH 10/14] add --- doc/wiki-cn/database-mysql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/wiki-cn/database-mysql.md b/doc/wiki-cn/database-mysql.md index c3d7c7730..65e7b6883 100644 --- a/doc/wiki-cn/database-mysql.md +++ b/doc/wiki-cn/database-mysql.md @@ -117,7 +117,7 @@ func (d *Dao) Close() { func (d *Dao) GetDemo(c context.Context, did int64) (demo int8, err error) { err = d.db.QueryRow(c, _getDemoSQL, did).Scan(&demo) if err != nil && err != sql.ErrNoRows { - log.Error("d.managerDB.Query error(%v)", err) + log.Error("d.GetDemo.Query error(%v)", err) return } return demo, nil From 827b6edfd1ad6740868b324f7b255a2b54ac6300 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Sun, 9 Jun 2019 11:03:53 +0800 Subject: [PATCH 11/14] add --- doc/wiki-cn/database-mysql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/wiki-cn/database-mysql.md b/doc/wiki-cn/database-mysql.md index 65e7b6883..562c73891 100644 --- a/doc/wiki-cn/database-mysql.md +++ b/doc/wiki-cn/database-mysql.md @@ -186,7 +186,7 @@ Result接口支持获取影响行数和LastInsertId(一般用于获取Insert ## 事务 -kratos/pkg/database/sql包同样支持事务操作。 +kratos/pkg/database/sql包支持事务操作,具体操作示例如下: 开启一个事务: From 8c323610346ab43cf2682db084b0f64fecb2f9d9 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Mon, 10 Jun 2019 10:30:46 +0800 Subject: [PATCH 12/14] fix tab --- doc/wiki-cn/database-mysql.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/wiki-cn/database-mysql.md b/doc/wiki-cn/database-mysql.md index 562c73891..57ff31dbf 100644 --- a/doc/wiki-cn/database-mysql.md +++ b/doc/wiki-cn/database-mysql.md @@ -60,11 +60,11 @@ 进入项目的internal/dao目录,打开dao.go,其中: ```go - var ( + var ( dc struct { Demo *sql.Config } - ) + ) checkErr(paladin.Get("mysql.toml").UnmarshalTOML(&dc)) ``` 使用paladin配置管理工具将上文中的mysql.toml中的配置解析为我们需要使用mysql的相关配置。 From 70a579e1cc538ee94f179184bf152a162477f6f6 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Mon, 10 Jun 2019 10:32:25 +0800 Subject: [PATCH 13/14] add --- doc/wiki-cn/database-mysql.md | 50 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/doc/wiki-cn/database-mysql.md b/doc/wiki-cn/database-mysql.md index 57ff31dbf..08f9e7fcc 100644 --- a/doc/wiki-cn/database-mysql.md +++ b/doc/wiki-cn/database-mysql.md @@ -60,12 +60,12 @@ 进入项目的internal/dao目录,打开dao.go,其中: ```go - var ( - dc struct { - Demo *sql.Config - } - ) - checkErr(paladin.Get("mysql.toml").UnmarshalTOML(&dc)) +var ( + dc struct { + Demo *sql.Config + } +) +checkErr(paladin.Get("mysql.toml").UnmarshalTOML(&dc)) ``` 使用paladin配置管理工具将上文中的mysql.toml中的配置解析为我们需要使用mysql的相关配置。 @@ -79,9 +79,9 @@ type Dao struct { 在dao的主结构提中定义了mysql的连接池对象。 ```go - dao = &Dao{ - db: sql.NewMySQL(dc.Demo), - } +dao = &Dao{ + db: sql.NewMySQL(dc.Demo), +} ``` 使用kratos/pkg/database/sql包的NewMySQL方法进行连接池对象的初始化,需要传入上文解析的配置。 @@ -191,37 +191,37 @@ kratos/pkg/database/sql包支持事务操作,具体操作示例如下: 开启一个事务: ```go - tx := d.db.Begin() - if err = tx.Error; err != nil { - log.Error("db begin transcation failed, err=%+v", err) - return - } +tx := d.db.Begin() +if err = tx.Error; err != nil { + log.Error("db begin transcation failed, err=%+v", err) + return +} ``` 在事务中执行语句: ```go - res, err := tx.Exec(_demoSQL, did) - if err != nil { - return - } - rows := res.RowsAffected() +res, err := tx.Exec(_demoSQL, did) +if err != nil { + return +} +rows := res.RowsAffected() ``` 提交事务: ```go - if err = tx.Commit().Error; err!=nil{ - log.Error("db commit transcation failed, err=%+v", err) - } +if err = tx.Commit().Error; err!=nil{ + log.Error("db commit transcation failed, err=%+v", err) +} ``` 回滚事务: ```go - if err = tx.Rollback().Error; err!=nil{ - log.Error("db rollback failed, err=%+v", rollbackErr) - } +if err = tx.Rollback().Error; err!=nil{ + log.Error("db rollback failed, err=%+v", rollbackErr) +} ``` # 扩展阅读 From 57b935e80d4db99092919b7f5a864cfb3c617127 Mon Sep 17 00:00:00 2001 From: zhaoshichen Date: Mon, 10 Jun 2019 10:34:10 +0800 Subject: [PATCH 14/14] add --- doc/wiki-cn/cache-mc.md | 34 +++++++++++++++++----------------- doc/wiki-cn/cache-redis.md | 24 ++++++++++++------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/doc/wiki-cn/cache-mc.md b/doc/wiki-cn/cache-mc.md index ab261f5ee..fb27af51f 100644 --- a/doc/wiki-cn/cache-mc.md +++ b/doc/wiki-cn/cache-mc.md @@ -61,13 +61,13 @@ demoExpire = "24h" 进入项目的internal/dao目录,打开dao.go,其中: ```go - var ( - mc struct { - Demo *memcache.Config - DemoExpire xtime.Duration - } - ) - checkErr(paladin.Get("memcache.toml").UnmarshalTOML(&mc)) +var ( + mc struct { + Demo *memcache.Config + DemoExpire xtime.Duration + } +) +checkErr(paladin.Get("memcache.toml").UnmarshalTOML(&mc)) ``` 使用paladin配置管理工具将上文中的memcache.toml中的配置解析为我们需要使用的配置。 @@ -82,11 +82,11 @@ type Dao struct { 在dao的主结构提中定义了memcache的连接池对象和过期时间。 ```go - dao = &Dao{ - // memcache - mc: memcache.New(mc.Demo), - mcExpire: int32(time.Duration(mc.DemoExpire) / time.Second), - } +dao = &Dao{ + // memcache + mc: memcache.New(mc.Demo), + mcExpire: int32(time.Duration(mc.DemoExpire) / time.Second), +} ``` 使用kratos/pkg/cache/memcache包的New方法进行连接池对象的初始化,需要传入上文解析的配置。 @@ -153,11 +153,11 @@ func (d *Dao) CacheDemo(c context.Context, id int64) (res *Demo, err error) { ## 批量查询使用 ```go - replies, err := d.mc.GetMulti(c, keys) - for _, key := range replies.Keys() { - v := &Demo{} - err = replies.Scan(key, v) - } +replies, err := d.mc.GetMulti(c, keys) +for _, key := range replies.Keys() { + v := &Demo{} + err = replies.Scan(key, v) +} ``` 如上为代码生成器生成的进行批量查询的代码片段,这里使用到mc.GetMulti(c,keys)方法获得返回值,与单个查询类似地,我们需要再使用scan方法将memcache的返回值转换为我们定义的结构体。 diff --git a/doc/wiki-cn/cache-redis.md b/doc/wiki-cn/cache-redis.md index d75a5b47b..179cefcfb 100644 --- a/doc/wiki-cn/cache-redis.md +++ b/doc/wiki-cn/cache-redis.md @@ -63,13 +63,13 @@ demoExpire = "24h" 进入项目的internal/dao目录,打开dao.go,其中: ```go - var ( - rc struct { - Demo *redis.Config - DemoExpire xtime.Duration - } - ) - checkErr(paladin.Get("redis.toml").UnmarshalTOML(&rc)) +var ( + rc struct { + Demo *redis.Config + DemoExpire xtime.Duration + } +) +checkErr(paladin.Get("redis.toml").UnmarshalTOML(&rc)) ``` 使用paladin配置管理工具将上文中的redis.toml中的配置解析为我们需要使用的配置。 @@ -84,11 +84,11 @@ type Dao struct { 在dao的主结构提中定义了redis的连接池对象和过期时间。 ```go - dao = &Dao{ - // redis - redis: redis.NewPool(rc.Demo), - redisExpire: int32(time.Duration(rc.DemoExpire) / time.Second), - } +dao = &Dao{ + // redis + redis: redis.NewPool(rc.Demo), + redisExpire: int32(time.Duration(rc.DemoExpire) / time.Second), +} ``` 使用kratos/pkg/cache/redis包的NewPool方法进行连接池对象的初始化,需要传入上文解析的配置。