// DialTimeout acts like Dial for establishing the // connection to the server, writing a command and reading a reply. func Dial(network, address string) (Conn, error)
“我要自定义超时时间!”
“我要设定 Database!”
“我要控制连接池的策略!”
“我要安全使用 Redis,让我填一下 Password!”
“可以提供一下慢查询请求记录,并且可以设置 slowlog 时间?”
要满足这些功能:添加功能 Add Features
// DialTimeout acts like Dial for establishing the
// connection to the server, writing a command and reading a reply.
func Dial(network, address string) (Conn, error)
// DialTimeout acts like Dial but takes timeouts for establishing the
// connection to the server, writing a command and reading a reply.
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error)
// DialDatabase acts like Dial but takes database for establishing the
// connection to the server, writing a command and reading a reply.
func DialDatabase(network, address string, database int) (Conn, error)
// DialPool
func DialPool...
注意:一个 package 的公共方法越多,则意味着这个 package 越脆弱。
package main
import (
"log"
"net/http"
"time"
)
func main() {
s := &http.Server{
Addr: ":8080",
Handler: nil,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
}
从配置文件解析拿到一个对象,然后对redis做一个初始化
// Config redis settings.
type Config struct {
*pool.Config
Addr string
Auth string
DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
}
// NewConn new a redis conn.
func NewConn(c *Config) (cn Conn, err error)
func main() {
c := &redis.Config{
Addr: "tcp://127.0.0.1:3389",
}
r, _ := redis.NewConn(c)
c.Addr = "tcp://127.0.0.1:3390" // 副作用是什么? 不清楚修改会导致什么结果出现
}
// NewConn new a redis conn. 做了一个deep copy传进去的,外面的人是无法修改内部值的。但是其无法区分零值和未设定
func NewConn(c Config) (cn Conn, err error)
// NewConn new a redis conn. 必须要传递参数,但是可以传递nil作为默认配置参数
func NewConn(c *Config) (cn Conn, err error)
// NewConn new a redis conn. 其可以不传参数,但是也可以传多个,所以当传多个的时候不知道是哪一个配置生效
func NewConn(c ...*Config) (cn Conn, err error)
import (
"github.com/go-kratos/kratos/pkg/log"
)
func main() {
log.Init(nil) // 这样使用默认配置,但通常不建议传递一个nil作为参数
// config.fix() // 修正默认配置
}
“I believe that we, as Go programmers, should work hard to ensure that nil is never a parameter that needs to be passed to any public function.” – Dave Cheney
Self-referential functions and the design of options – Rob Pike
Functional options for friendly APIs – Dave Cheney
// DialOption specifies an option for dialing a Redis server.
type DialOption struct {
f func(*dialOptions) // 由于这里的dialOptions是小写,所以别人调用是无法修改其option内部的,只能通过新增DialXxx方法才能修改option
}
// Dial connects to the Redis server at the given network and
// address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error) {
do := dialOptions{
dial: net.Dial,
}
// 当option为nil的时候,也就是没有传参进来表示使用默认值,那么就不会去for循环中替换掉option.f中的do值,那么使用的就是默认值
for _, option := range options {
option.f(&do)
} // ...
}
这时候demo就会修改为这样:
package main
import (
"time"
"github.com/go-kratos/kratos/pkg/cache/redis"
)
func main() {
c, _ := redis.Dial("tcp", "127.0.0.1:3389",
redis.DialDatabase(0),
redis.DialPassword("hello"),
redis.DialReadTimeout(10*time.Second))
}
在某些场景下需要把修改之后的配置还原:
type option func(f *Foo) option
// Verbosity sets Foo's verbosity level to v. 日志级别的可见性
func Verbosity(v int) option {
return func(f *Foo) option {
prev := f.verbosity // 获取之前的值
f.verbosity = v //覆盖成最新的值
return Verbosity(prev) // 再返回给之前的值
}
}
func DoSomethingVerbosely(foo *Foo, verbosity int) {
// Could combine the next two lines,
// with some loss of readability.
prev := foo.Option(pkg.Verbosity(verbosity))
defer foo.Option(prev)
// ... do some stuff with foo under high verbosity.
}
gRPC demo:
type GreeterClient interface {
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
type CallOption interface {
before(*callInfo) error
after(*callInfo)
}
// EmptyCallOption does not alter the Call configuration.
type EmptyCallOption struct{}
// TimeoutCallOption timeout option.
type TimeoutCallOption struct {
grpc.EmptyCallOption
Timeout time.Duration
}
// Dial connects to the Redis server at the given network and
// address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error)
// NewConn new a redis conn.
func NewConn(c *Config) (cn Conn, err error)
// 上面两种方法都作为初始化配置一个配置,存在不知道调用哪一个方法的问题,并且方法签名的表面积增大了。
“JSON/YAML 配置怎么加载,无法映射 DialOption 啊!”
“嗯,不依赖配置的走 options,配置加载走config”
// Dial connects to the Redis server at the given network and
// address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error)
配置工具的实践:
如下:
func ApplyYAML(s *redis.Config, yml string) error { // 传进来的是yaml格式的config
js, err := yaml.YAMLToJSON([]byte(yml)) // 先转换成json格式
if err != nil {
return err
}
return ApplyJSON(s, string(js)) // json格式再转换成protobuf
}
// Options apply config to options.
func Options(c *redis.Config) []redis.Options { // redis.Options是redis client这个API库需要的配置参数信息
return []redis.Options{
redis.DialDatabase(c.Database),
redis.DialPassword(c.Password),
redis.DialReadTimeout(c.ReadTimeout),
}
}
最终代码:
func main() {
// load config file from yaml.
c := new(redis.Config)
_ = ApplyYAML(c, loadConfig())
r, _ := redis.Dial(c.Network, c.Address, Options(c)...)
}