You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

430 lines
12 KiB

  1. package redis
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "net/url"
  9. "runtime"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/go-redis/redis/v8/internal/pool"
  15. )
  16. // Limiter is the interface of a rate limiter or a circuit breaker.
  17. type Limiter interface {
  18. // Allow returns nil if operation is allowed or an error otherwise.
  19. // If operation is allowed client must ReportResult of the operation
  20. // whether it is a success or a failure.
  21. Allow() error
  22. // ReportResult reports the result of the previously allowed operation.
  23. // nil indicates a success, non-nil error usually indicates a failure.
  24. ReportResult(result error)
  25. }
  26. // Options keeps the settings to setup redis connection.
  27. type Options struct {
  28. // The network type, either tcp or unix.
  29. // Default is tcp.
  30. Network string
  31. // host:port address.
  32. Addr string
  33. // Dialer creates new network connection and has priority over
  34. // Network and Addr options.
  35. Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
  36. // Hook that is called when new connection is established.
  37. OnConnect func(ctx context.Context, cn *Conn) error
  38. // Use the specified Username to authenticate the current connection
  39. // with one of the connections defined in the ACL list when connecting
  40. // to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
  41. Username string
  42. // Optional password. Must match the password specified in the
  43. // requirepass server configuration option (if connecting to a Redis 5.0 instance, or lower),
  44. // or the User Password when connecting to a Redis 6.0 instance, or greater,
  45. // that is using the Redis ACL system.
  46. Password string
  47. // Database to be selected after connecting to the server.
  48. DB int
  49. // Maximum number of retries before giving up.
  50. // Default is 3 retries; -1 (not 0) disables retries.
  51. MaxRetries int
  52. // Minimum backoff between each retry.
  53. // Default is 8 milliseconds; -1 disables backoff.
  54. MinRetryBackoff time.Duration
  55. // Maximum backoff between each retry.
  56. // Default is 512 milliseconds; -1 disables backoff.
  57. MaxRetryBackoff time.Duration
  58. // Dial timeout for establishing new connections.
  59. // Default is 5 seconds.
  60. DialTimeout time.Duration
  61. // Timeout for socket reads. If reached, commands will fail
  62. // with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
  63. // Default is 3 seconds.
  64. ReadTimeout time.Duration
  65. // Timeout for socket writes. If reached, commands will fail
  66. // with a timeout instead of blocking.
  67. // Default is ReadTimeout.
  68. WriteTimeout time.Duration
  69. // Type of connection pool.
  70. // true for FIFO pool, false for LIFO pool.
  71. // Note that fifo has higher overhead compared to lifo.
  72. PoolFIFO bool
  73. // Maximum number of socket connections.
  74. // Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS.
  75. PoolSize int
  76. // Minimum number of idle connections which is useful when establishing
  77. // new connection is slow.
  78. MinIdleConns int
  79. // Connection age at which client retires (closes) the connection.
  80. // Default is to not close aged connections.
  81. MaxConnAge time.Duration
  82. // Amount of time client waits for connection if all connections
  83. // are busy before returning an error.
  84. // Default is ReadTimeout + 1 second.
  85. PoolTimeout time.Duration
  86. // Amount of time after which client closes idle connections.
  87. // Should be less than server's timeout.
  88. // Default is 5 minutes. -1 disables idle timeout check.
  89. IdleTimeout time.Duration
  90. // Frequency of idle checks made by idle connections reaper.
  91. // Default is 1 minute. -1 disables idle connections reaper,
  92. // but idle connections are still discarded by the client
  93. // if IdleTimeout is set.
  94. IdleCheckFrequency time.Duration
  95. // Enables read only queries on slave nodes.
  96. readOnly bool
  97. // TLS Config to use. When set TLS will be negotiated.
  98. TLSConfig *tls.Config
  99. // Limiter interface used to implemented circuit breaker or rate limiter.
  100. Limiter Limiter
  101. }
  102. func (opt *Options) init() {
  103. if opt.Addr == "" {
  104. opt.Addr = "localhost:6379"
  105. }
  106. if opt.Network == "" {
  107. if strings.HasPrefix(opt.Addr, "/") {
  108. opt.Network = "unix"
  109. } else {
  110. opt.Network = "tcp"
  111. }
  112. }
  113. if opt.DialTimeout == 0 {
  114. opt.DialTimeout = 5 * time.Second
  115. }
  116. if opt.Dialer == nil {
  117. opt.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
  118. netDialer := &net.Dialer{
  119. Timeout: opt.DialTimeout,
  120. KeepAlive: 5 * time.Minute,
  121. }
  122. if opt.TLSConfig == nil {
  123. return netDialer.DialContext(ctx, network, addr)
  124. }
  125. return tls.DialWithDialer(netDialer, network, addr, opt.TLSConfig)
  126. }
  127. }
  128. if opt.PoolSize == 0 {
  129. opt.PoolSize = 10 * runtime.GOMAXPROCS(0)
  130. }
  131. switch opt.ReadTimeout {
  132. case -1:
  133. opt.ReadTimeout = 0
  134. case 0:
  135. opt.ReadTimeout = 3 * time.Second
  136. }
  137. switch opt.WriteTimeout {
  138. case -1:
  139. opt.WriteTimeout = 0
  140. case 0:
  141. opt.WriteTimeout = opt.ReadTimeout
  142. }
  143. if opt.PoolTimeout == 0 {
  144. opt.PoolTimeout = opt.ReadTimeout + time.Second
  145. }
  146. if opt.IdleTimeout == 0 {
  147. opt.IdleTimeout = 5 * time.Minute
  148. }
  149. if opt.IdleCheckFrequency == 0 {
  150. opt.IdleCheckFrequency = time.Minute
  151. }
  152. if opt.MaxRetries == -1 {
  153. opt.MaxRetries = 0
  154. } else if opt.MaxRetries == 0 {
  155. opt.MaxRetries = 3
  156. }
  157. switch opt.MinRetryBackoff {
  158. case -1:
  159. opt.MinRetryBackoff = 0
  160. case 0:
  161. opt.MinRetryBackoff = 8 * time.Millisecond
  162. }
  163. switch opt.MaxRetryBackoff {
  164. case -1:
  165. opt.MaxRetryBackoff = 0
  166. case 0:
  167. opt.MaxRetryBackoff = 512 * time.Millisecond
  168. }
  169. }
  170. func (opt *Options) clone() *Options {
  171. clone := *opt
  172. return &clone
  173. }
  174. // ParseURL parses an URL into Options that can be used to connect to Redis.
  175. // Scheme is required.
  176. // There are two connection types: by tcp socket and by unix socket.
  177. // Tcp connection:
  178. // redis://<user>:<password>@<host>:<port>/<db_number>
  179. // Unix connection:
  180. // unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
  181. // Most Option fields can be set using query parameters, with the following restrictions:
  182. // - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
  183. // - only scalar type fields are supported (bool, int, time.Duration)
  184. // - for time.Duration fields, values must be a valid input for time.ParseDuration();
  185. // additionally a plain integer as value (i.e. without unit) is intepreted as seconds
  186. // - to disable a duration field, use value less than or equal to 0; to use the default
  187. // value, leave the value blank or remove the parameter
  188. // - only the last value is interpreted if a parameter is given multiple times
  189. // - fields "network", "addr", "username" and "password" can only be set using other
  190. // URL attributes (scheme, host, userinfo, resp.), query paremeters using these
  191. // names will be treated as unknown parameters
  192. // - unknown parameter names will result in an error
  193. // Examples:
  194. // redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
  195. // is equivalent to:
  196. // &Options{
  197. // Network: "tcp",
  198. // Addr: "localhost:6789",
  199. // DB: 1, // path "/3" was overridden by "&db=1"
  200. // DialTimeout: 3 * time.Second, // no time unit = seconds
  201. // ReadTimeout: 6 * time.Second,
  202. // MaxRetries: 2,
  203. // }
  204. func ParseURL(redisURL string) (*Options, error) {
  205. u, err := url.Parse(redisURL)
  206. if err != nil {
  207. return nil, err
  208. }
  209. switch u.Scheme {
  210. case "redis", "rediss":
  211. return setupTCPConn(u)
  212. case "unix":
  213. return setupUnixConn(u)
  214. default:
  215. return nil, fmt.Errorf("redis: invalid URL scheme: %s", u.Scheme)
  216. }
  217. }
  218. func setupTCPConn(u *url.URL) (*Options, error) {
  219. o := &Options{Network: "tcp"}
  220. o.Username, o.Password = getUserPassword(u)
  221. h, p, err := net.SplitHostPort(u.Host)
  222. if err != nil {
  223. h = u.Host
  224. }
  225. if h == "" {
  226. h = "localhost"
  227. }
  228. if p == "" {
  229. p = "6379"
  230. }
  231. o.Addr = net.JoinHostPort(h, p)
  232. f := strings.FieldsFunc(u.Path, func(r rune) bool {
  233. return r == '/'
  234. })
  235. switch len(f) {
  236. case 0:
  237. o.DB = 0
  238. case 1:
  239. if o.DB, err = strconv.Atoi(f[0]); err != nil {
  240. return nil, fmt.Errorf("redis: invalid database number: %q", f[0])
  241. }
  242. default:
  243. return nil, fmt.Errorf("redis: invalid URL path: %s", u.Path)
  244. }
  245. if u.Scheme == "rediss" {
  246. o.TLSConfig = &tls.Config{ServerName: h}
  247. }
  248. return setupConnParams(u, o)
  249. }
  250. func setupUnixConn(u *url.URL) (*Options, error) {
  251. o := &Options{
  252. Network: "unix",
  253. }
  254. if strings.TrimSpace(u.Path) == "" { // path is required with unix connection
  255. return nil, errors.New("redis: empty unix socket path")
  256. }
  257. o.Addr = u.Path
  258. o.Username, o.Password = getUserPassword(u)
  259. return setupConnParams(u, o)
  260. }
  261. type queryOptions struct {
  262. q url.Values
  263. err error
  264. }
  265. func (o *queryOptions) string(name string) string {
  266. vs := o.q[name]
  267. if len(vs) == 0 {
  268. return ""
  269. }
  270. delete(o.q, name) // enable detection of unknown parameters
  271. return vs[len(vs)-1]
  272. }
  273. func (o *queryOptions) int(name string) int {
  274. s := o.string(name)
  275. if s == "" {
  276. return 0
  277. }
  278. i, err := strconv.Atoi(s)
  279. if err == nil {
  280. return i
  281. }
  282. if o.err == nil {
  283. o.err = fmt.Errorf("redis: invalid %s number: %s", name, err)
  284. }
  285. return 0
  286. }
  287. func (o *queryOptions) duration(name string) time.Duration {
  288. s := o.string(name)
  289. if s == "" {
  290. return 0
  291. }
  292. // try plain number first
  293. if i, err := strconv.Atoi(s); err == nil {
  294. if i <= 0 {
  295. // disable timeouts
  296. return -1
  297. }
  298. return time.Duration(i) * time.Second
  299. }
  300. dur, err := time.ParseDuration(s)
  301. if err == nil {
  302. return dur
  303. }
  304. if o.err == nil {
  305. o.err = fmt.Errorf("redis: invalid %s duration: %w", name, err)
  306. }
  307. return 0
  308. }
  309. func (o *queryOptions) bool(name string) bool {
  310. switch s := o.string(name); s {
  311. case "true", "1":
  312. return true
  313. case "false", "0", "":
  314. return false
  315. default:
  316. if o.err == nil {
  317. o.err = fmt.Errorf("redis: invalid %s boolean: expected true/false/1/0 or an empty string, got %q", name, s)
  318. }
  319. return false
  320. }
  321. }
  322. func (o *queryOptions) remaining() []string {
  323. if len(o.q) == 0 {
  324. return nil
  325. }
  326. keys := make([]string, 0, len(o.q))
  327. for k := range o.q {
  328. keys = append(keys, k)
  329. }
  330. sort.Strings(keys)
  331. return keys
  332. }
  333. // setupConnParams converts query parameters in u to option value in o.
  334. func setupConnParams(u *url.URL, o *Options) (*Options, error) {
  335. q := queryOptions{q: u.Query()}
  336. // compat: a future major release may use q.int("db")
  337. if tmp := q.string("db"); tmp != "" {
  338. db, err := strconv.Atoi(tmp)
  339. if err != nil {
  340. return nil, fmt.Errorf("redis: invalid database number: %w", err)
  341. }
  342. o.DB = db
  343. }
  344. o.MaxRetries = q.int("max_retries")
  345. o.MinRetryBackoff = q.duration("min_retry_backoff")
  346. o.MaxRetryBackoff = q.duration("max_retry_backoff")
  347. o.DialTimeout = q.duration("dial_timeout")
  348. o.ReadTimeout = q.duration("read_timeout")
  349. o.WriteTimeout = q.duration("write_timeout")
  350. o.PoolFIFO = q.bool("pool_fifo")
  351. o.PoolSize = q.int("pool_size")
  352. o.MinIdleConns = q.int("min_idle_conns")
  353. o.MaxConnAge = q.duration("max_conn_age")
  354. o.PoolTimeout = q.duration("pool_timeout")
  355. o.IdleTimeout = q.duration("idle_timeout")
  356. o.IdleCheckFrequency = q.duration("idle_check_frequency")
  357. if q.err != nil {
  358. return nil, q.err
  359. }
  360. // any parameters left?
  361. if r := q.remaining(); len(r) > 0 {
  362. return nil, fmt.Errorf("redis: unexpected option: %s", strings.Join(r, ", "))
  363. }
  364. return o, nil
  365. }
  366. func getUserPassword(u *url.URL) (string, string) {
  367. var user, password string
  368. if u.User != nil {
  369. user = u.User.Username()
  370. if p, ok := u.User.Password(); ok {
  371. password = p
  372. }
  373. }
  374. return user, password
  375. }
  376. func newConnPool(opt *Options) *pool.ConnPool {
  377. return pool.NewConnPool(&pool.Options{
  378. Dialer: func(ctx context.Context) (net.Conn, error) {
  379. return opt.Dialer(ctx, opt.Network, opt.Addr)
  380. },
  381. PoolFIFO: opt.PoolFIFO,
  382. PoolSize: opt.PoolSize,
  383. MinIdleConns: opt.MinIdleConns,
  384. MaxConnAge: opt.MaxConnAge,
  385. PoolTimeout: opt.PoolTimeout,
  386. IdleTimeout: opt.IdleTimeout,
  387. IdleCheckFrequency: opt.IdleCheckFrequency,
  388. })
  389. }