Compare commits
	
		
			13 Commits
		
	
	
		
			feature/et
			...
			feature/lo
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9daa1ba251 | |||
| af174ba501 | |||
| e152823b21 | |||
| ad39fe5b68 | |||
| 63b0d09e35 | |||
| 6601f4e3c7 | |||
| ee4830c83f | |||
| cb7dd59b73 | |||
| 8947ef0d6b | |||
| b7d7dba4e7 | |||
| 34c9f035ee | |||
| 628eac60b4 | |||
| 840ca01f87 | 
							
								
								
									
										586
									
								
								demo/context.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										586
									
								
								demo/context.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,586 @@ | ||||
| // Package demo ... | ||||
| // | ||||
| // Description : demo ... | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2021-12-02 12:43 下午 | ||||
| package demo | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Copyright 2014 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package context defines the Context type, which carries deadlines, | ||||
| // cancellation signals, and other request-scoped values across API boundaries | ||||
| // and between processes. | ||||
| // | ||||
| // Incoming requests to a server should create a Context, and outgoing | ||||
| // calls to servers should accept a Context. The chain of function | ||||
| // calls between them must propagate the Context, optionally replacing | ||||
| // it with a derived Context created using WithCancel, WithDeadline, | ||||
| // WithTimeout, or WithValue. When a Context is canceled, all | ||||
| // Contexts derived from it are also canceled. | ||||
| // | ||||
| // The WithCancel, WithDeadline, and WithTimeout functions take a | ||||
| // Context (the parent) and return a derived Context (the child) and a | ||||
| // CancelFunc. Calling the CancelFunc cancels the child and its | ||||
| // children, removes the parent's reference to the child, and stops | ||||
| // any associated timers. Failing to call the CancelFunc leaks the | ||||
| // child and its children until the parent is canceled or the timer | ||||
| // fires. The go vet tool checks that CancelFuncs are used on all | ||||
| // control-flow paths. | ||||
| // | ||||
| // Programs that use Contexts should follow these rules to keep interfaces | ||||
| // consistent across packages and enable static analysis tools to check context | ||||
| // propagation: | ||||
| // | ||||
| // Do not store Contexts inside a struct type; instead, pass a Context | ||||
| // explicitly to each function that needs it. The Context should be the first | ||||
| // parameter, typically named ctx: | ||||
| // | ||||
| // 	func DoSomething(ctx context.Context, arg Arg) error { | ||||
| // 		// ... use ctx ... | ||||
| // 	} | ||||
| // | ||||
| // Do not pass a nil Context, even if a function permits it. Pass context.TODO | ||||
| // if you are unsure about which Context to use. | ||||
| // | ||||
| // Use context Values only for request-scoped data that transits processes and | ||||
| // APIs, not for passing optional parameters to functions. | ||||
| // | ||||
| // The same Context may be passed to functions running in different goroutines; | ||||
| // Contexts are safe for simultaneous use by multiple goroutines. | ||||
| // | ||||
| // See https://blog.golang.org/context for example code for a server that uses | ||||
| // Contexts. | ||||
|  | ||||
| // A Context carries a deadline, a cancellation signal, and other values across | ||||
| // API boundaries. | ||||
| // | ||||
| // Context's methods may be called by multiple goroutines simultaneously. | ||||
| type Context interface { | ||||
| 	// Deadline returns the time when work done on behalf of this context | ||||
| 	// should be canceled. Deadline returns ok==false when no deadline is | ||||
| 	// set. Successive calls to Deadline return the same results. | ||||
| 	Deadline() (deadline time.Time, ok bool) | ||||
|  | ||||
| 	// Done returns a channel that's closed when work done on behalf of this | ||||
| 	// context should be canceled. Done may return nil if this context can | ||||
| 	// never be canceled. Successive calls to Done return the same value. | ||||
| 	// The close of the Done channel may happen asynchronously, | ||||
| 	// after the cancel function returns. | ||||
| 	// | ||||
| 	// WithCancel arranges for Done to be closed when cancel is called; | ||||
| 	// WithDeadline arranges for Done to be closed when the deadline | ||||
| 	// expires; WithTimeout arranges for Done to be closed when the timeout | ||||
| 	// elapses. | ||||
| 	// | ||||
| 	// Done is provided for use in select statements: | ||||
| 	// | ||||
| 	//  // Stream generates values with DoSomething and sends them to out | ||||
| 	//  // until DoSomething returns an error or ctx.Done is closed. | ||||
| 	//  func Stream(ctx context.Context, out chan<- Value) error { | ||||
| 	//  	for { | ||||
| 	//  		v, err := DoSomething(ctx) | ||||
| 	//  		if err != nil { | ||||
| 	//  			return err | ||||
| 	//  		} | ||||
| 	//  		select { | ||||
| 	//  		case <-ctx.Done(): | ||||
| 	//  			return ctx.Err() | ||||
| 	//  		case out <- v: | ||||
| 	//  		} | ||||
| 	//  	} | ||||
| 	//  } | ||||
| 	// | ||||
| 	// See https://blog.golang.org/pipelines for more examples of how to use | ||||
| 	// a Done channel for cancellation. | ||||
| 	Done() <-chan struct{} | ||||
|  | ||||
| 	// If Done is not yet closed, Err returns nil. | ||||
| 	// If Done is closed, Err returns a non-nil error explaining why: | ||||
| 	// Canceled if the context was canceled | ||||
| 	// or DeadlineExceeded if the context's deadline passed. | ||||
| 	// After Err returns a non-nil error, successive calls to Err return the same error. | ||||
| 	Err() error | ||||
|  | ||||
| 	// Value returns the value associated with this context for key, or nil | ||||
| 	// if no value is associated with key. Successive calls to Value with | ||||
| 	// the same key returns the same result. | ||||
| 	// | ||||
| 	// Use context values only for request-scoped data that transits | ||||
| 	// processes and API boundaries, not for passing optional parameters to | ||||
| 	// functions. | ||||
| 	// | ||||
| 	// A key identifies a specific value in a Context. Functions that wish | ||||
| 	// to store values in Context typically allocate a key in a global | ||||
| 	// variable then use that key as the argument to context.WithValue and | ||||
| 	// Context.Value. A key can be any type that supports equality; | ||||
| 	// packages should define keys as an unexported type to avoid | ||||
| 	// collisions. | ||||
| 	// | ||||
| 	// Packages that define a Context key should provide type-safe accessors | ||||
| 	// for the values stored using that key: | ||||
| 	// | ||||
| 	// 	// Package user defines a User type that's stored in Contexts. | ||||
| 	// 	package user | ||||
| 	// | ||||
| 	// 	import "context" | ||||
| 	// | ||||
| 	// 	// User is the type of value stored in the Contexts. | ||||
| 	// 	type User struct {...} | ||||
| 	// | ||||
| 	// 	// key is an unexported type for keys defined in this package. | ||||
| 	// 	// This prevents collisions with keys defined in other packages. | ||||
| 	// 	type key int | ||||
| 	// | ||||
| 	// 	// userKey is the key for user.User values in Contexts. It is | ||||
| 	// 	// unexported; clients use user.NewContext and user.FromContext | ||||
| 	// 	// instead of using this key directly. | ||||
| 	// 	var userKey key | ||||
| 	// | ||||
| 	// 	// NewContext returns a new Context that carries value u. | ||||
| 	// 	func NewContext(ctx context.Context, u *User) context.Context { | ||||
| 	// 		return context.WithValue(ctx, userKey, u) | ||||
| 	// 	} | ||||
| 	// | ||||
| 	// 	// FromContext returns the User value stored in ctx, if any. | ||||
| 	// 	func FromContext(ctx context.Context) (*User, bool) { | ||||
| 	// 		u, ok := ctx.Value(userKey).(*User) | ||||
| 	// 		return u, ok | ||||
| 	// 	} | ||||
| 	Value(key interface{}) interface{} | ||||
| } | ||||
|  | ||||
| // Canceled is the error returned by Context.Err when the context is canceled. | ||||
| var Canceled = errors.New("context canceled") | ||||
|  | ||||
| // DeadlineExceeded is the error returned by Context.Err when the context's | ||||
| // deadline passes. | ||||
| var DeadlineExceeded error = deadlineExceededError{} | ||||
|  | ||||
| type deadlineExceededError struct{} | ||||
|  | ||||
| func (deadlineExceededError) Error() string   { return "context deadline exceeded" } | ||||
| func (deadlineExceededError) Timeout() bool   { return true } | ||||
| func (deadlineExceededError) Temporary() bool { return true } | ||||
|  | ||||
| // An emptyCtx is never canceled, has no values, and has no deadline. It is not | ||||
| // struct{}, since vars of this type must have distinct addresses. | ||||
| type emptyCtx int | ||||
|  | ||||
| func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (*emptyCtx) Done() <-chan struct{} { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (*emptyCtx) Err() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (*emptyCtx) Value(key interface{}) interface{} { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *emptyCtx) String() string { | ||||
| 	switch e { | ||||
| 	case background: | ||||
| 		return "context.Background" | ||||
| 	case todo: | ||||
| 		return "context.TODO" | ||||
| 	} | ||||
| 	return "unknown empty Context" | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	background = new(emptyCtx) | ||||
| 	todo       = new(emptyCtx) | ||||
| ) | ||||
|  | ||||
| // Background returns a non-nil, empty Context. It is never canceled, has no | ||||
| // values, and has no deadline. It is typically used by the main function, | ||||
| // initialization, and tests, and as the top-level Context for incoming | ||||
| // requests. | ||||
| func Background() Context { | ||||
| 	return background | ||||
| } | ||||
|  | ||||
| // TODO returns a non-nil, empty Context. Code should use context.TODO when | ||||
| // it's unclear which Context to use or it is not yet available (because the | ||||
| // surrounding function has not yet been extended to accept a Context | ||||
| // parameter). | ||||
| func TODO() Context { | ||||
| 	return todo | ||||
| } | ||||
|  | ||||
| // A CancelFunc tells an operation to abandon its work. | ||||
| // A CancelFunc does not wait for the work to stop. | ||||
| // A CancelFunc may be called by multiple goroutines simultaneously. | ||||
| // After the first call, subsequent calls to a CancelFunc do nothing. | ||||
| type CancelFunc func() | ||||
|  | ||||
| // WithCancel returns a copy of parent with a new Done channel. The returned | ||||
| // context's Done channel is closed when the returned cancel function is called | ||||
| // or when the parent context's Done channel is closed, whichever happens first. | ||||
| // | ||||
| // Canceling this context releases resources associated with it, so code should | ||||
| // call cancel as soon as the operations running in this Context complete. | ||||
| func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { | ||||
| 	if parent == nil { | ||||
| 		panic("cannot create context from nil parent") | ||||
| 	} | ||||
| 	c := newCancelCtx(parent) | ||||
| 	propagateCancel(parent, &c) | ||||
| 	return &c, func() { c.cancel(true, Canceled) } | ||||
| } | ||||
|  | ||||
| // newCancelCtx returns an initialized cancelCtx. | ||||
| func newCancelCtx(parent Context) cancelCtx { | ||||
| 	return cancelCtx{Context: parent} | ||||
| } | ||||
|  | ||||
| // goroutines counts the number of goroutines ever created; for testing. | ||||
| var goroutines int32 | ||||
|  | ||||
| // propagateCancel arranges for child to be canceled when parent is. | ||||
| func propagateCancel(parent Context, child canceler) { | ||||
| 	done := parent.Done() | ||||
| 	if done == nil { | ||||
| 		return // parent is never canceled | ||||
| 	} | ||||
|  | ||||
| 	select { | ||||
| 	case <-done: | ||||
| 		// parent is already canceled | ||||
| 		child.cancel(false, parent.Err()) | ||||
| 		return | ||||
| 	default: | ||||
| 	} | ||||
|  | ||||
| 	if p, ok := parentCancelCtx(parent); ok { | ||||
| 		p.mu.Lock() | ||||
| 		if p.err != nil { | ||||
| 			// parent has already been canceled | ||||
| 			child.cancel(false, p.err) | ||||
| 		} else { | ||||
| 			if p.children == nil { | ||||
| 				p.children = make(map[canceler]struct{}) | ||||
| 			} | ||||
| 			p.children[child] = struct{}{} | ||||
| 		} | ||||
| 		p.mu.Unlock() | ||||
| 	} else { | ||||
| 		atomic.AddInt32(&goroutines, +1) | ||||
| 		go func() { | ||||
| 			select { | ||||
| 			case <-parent.Done(): | ||||
| 				child.cancel(false, parent.Err()) | ||||
| 			case <-child.Done(): | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // &cancelCtxKey is the key that a cancelCtx returns itself for. | ||||
| var cancelCtxKey int | ||||
|  | ||||
| // parentCancelCtx returns the underlying *cancelCtx for parent. | ||||
| // It does this by looking up parent.Value(&cancelCtxKey) to find | ||||
| // the innermost enclosing *cancelCtx and then checking whether | ||||
| // parent.Done() matches that *cancelCtx. (If not, the *cancelCtx | ||||
| // has been wrapped in a custom implementation providing a | ||||
| // different done channel, in which case we should not bypass it.) | ||||
| func parentCancelCtx(parent Context) (*cancelCtx, bool) { | ||||
| 	done := parent.Done() | ||||
| 	if done == closedchan || done == nil { | ||||
| 		return nil, false | ||||
| 	} | ||||
| 	p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) | ||||
| 	if !ok { | ||||
| 		return nil, false | ||||
| 	} | ||||
| 	pdone, _ := p.done.Load().(chan struct{}) | ||||
| 	if pdone != done { | ||||
| 		return nil, false | ||||
| 	} | ||||
| 	return p, true | ||||
| } | ||||
|  | ||||
| // removeChild removes a context from its parent. | ||||
| func removeChild(parent Context, child canceler) { | ||||
| 	p, ok := parentCancelCtx(parent) | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
| 	p.mu.Lock() | ||||
| 	if p.children != nil { | ||||
| 		delete(p.children, child) | ||||
| 	} | ||||
| 	p.mu.Unlock() | ||||
| } | ||||
|  | ||||
| // A canceler is a context type that can be canceled directly. The | ||||
| // implementations are *cancelCtx and *timerCtx. | ||||
| type canceler interface { | ||||
| 	cancel(removeFromParent bool, err error) | ||||
| 	Done() <-chan struct{} | ||||
| } | ||||
|  | ||||
| // closedchan is a reusable closed channel. | ||||
| var closedchan = make(chan struct{}) | ||||
|  | ||||
| func init() { | ||||
| 	close(closedchan) | ||||
| } | ||||
|  | ||||
| // A cancelCtx can be canceled. When canceled, it also cancels any children | ||||
| // that implement canceler. | ||||
| type cancelCtx struct { | ||||
| 	Context | ||||
|  | ||||
| 	mu       sync.Mutex            // protects following fields | ||||
| 	done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call | ||||
| 	children map[canceler]struct{} // set to nil by the first cancel call | ||||
| 	err      error                 // set to non-nil by the first cancel call | ||||
| } | ||||
|  | ||||
| func (c *cancelCtx) Value(key interface{}) interface{} { | ||||
| 	if key == &cancelCtxKey { | ||||
| 		return c | ||||
| 	} | ||||
| 	return c.Context.Value(key) | ||||
| } | ||||
|  | ||||
| func (c *cancelCtx) Done() <-chan struct{} { | ||||
| 	d := c.done.Load() | ||||
| 	if d != nil { | ||||
| 		return d.(chan struct{}) | ||||
| 	} | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	d = c.done.Load() | ||||
| 	if d == nil { | ||||
| 		d = make(chan struct{}) | ||||
| 		c.done.Store(d) | ||||
| 	} | ||||
| 	return d.(chan struct{}) | ||||
| } | ||||
|  | ||||
| func (c *cancelCtx) Err() error { | ||||
| 	c.mu.Lock() | ||||
| 	err := c.err | ||||
| 	c.mu.Unlock() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| type stringer interface { | ||||
| 	String() string | ||||
| } | ||||
|  | ||||
| func contextName(c Context) string { | ||||
| 	if s, ok := c.(stringer); ok { | ||||
| 		return s.String() | ||||
| 	} | ||||
| 	return reflect.TypeOf(c).String() | ||||
| } | ||||
|  | ||||
| func (c *cancelCtx) String() string { | ||||
| 	return contextName(c.Context) + ".WithCancel" | ||||
| } | ||||
|  | ||||
| // cancel closes c.done, cancels each of c's children, and, if | ||||
| // removeFromParent is true, removes c from its parent's children. | ||||
| func (c *cancelCtx) cancel(removeFromParent bool, err error) { | ||||
| 	if err == nil { | ||||
| 		panic("context: internal error: missing cancel error") | ||||
| 	} | ||||
| 	c.mu.Lock() | ||||
| 	if c.err != nil { | ||||
| 		c.mu.Unlock() | ||||
| 		return // already canceled | ||||
| 	} | ||||
| 	c.err = err | ||||
| 	d, _ := c.done.Load().(chan struct{}) | ||||
| 	if d == nil { | ||||
| 		c.done.Store(closedchan) | ||||
| 	} else { | ||||
| 		close(d) | ||||
| 	} | ||||
| 	for child := range c.children { | ||||
| 		// NOTE: acquiring the child's lock while holding parent's lock. | ||||
| 		child.cancel(false, err) | ||||
| 	} | ||||
| 	c.children = nil | ||||
| 	c.mu.Unlock() | ||||
|  | ||||
| 	if removeFromParent { | ||||
| 		removeChild(c.Context, c) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithDeadline returns a copy of the parent context with the deadline adjusted | ||||
| // to be no later than d. If the parent's deadline is already earlier than d, | ||||
| // WithDeadline(parent, d) is semantically equivalent to parent. The returned | ||||
| // context's Done channel is closed when the deadline expires, when the returned | ||||
| // cancel function is called, or when the parent context's Done channel is | ||||
| // closed, whichever happens first. | ||||
| // | ||||
| // Canceling this context releases resources associated with it, so code should | ||||
| // call cancel as soon as the operations running in this Context complete. | ||||
| func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { | ||||
| 	if parent == nil { | ||||
| 		panic("cannot create context from nil parent") | ||||
| 	} | ||||
| 	if cur, ok := parent.Deadline(); ok && cur.Before(d) { | ||||
| 		// The current deadline is already sooner than the new one. | ||||
| 		return WithCancel(parent) | ||||
| 	} | ||||
| 	c := &timerCtx{ | ||||
| 		cancelCtx: newCancelCtx(parent), | ||||
| 		deadline:  d, | ||||
| 	} | ||||
| 	propagateCancel(parent, c) | ||||
| 	dur := time.Until(d) | ||||
| 	if dur <= 0 { | ||||
| 		c.cancel(true, DeadlineExceeded) // deadline has already passed | ||||
| 		return c, func() { c.cancel(false, Canceled) } | ||||
| 	} | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	if c.err == nil { | ||||
| 		c.timer = time.AfterFunc(dur, func() { | ||||
| 			c.cancel(true, DeadlineExceeded) | ||||
| 		}) | ||||
| 	} | ||||
| 	return c, func() { c.cancel(true, Canceled) } | ||||
| } | ||||
|  | ||||
| // A timerCtx carries a timer and a deadline. It embeds a cancelCtx to | ||||
| // implement Done and Err. It implements cancel by stopping its timer then | ||||
| // delegating to cancelCtx.cancel. | ||||
| type timerCtx struct { | ||||
| 	cancelCtx | ||||
| 	timer *time.Timer // Under cancelCtx.mu. | ||||
|  | ||||
| 	deadline time.Time | ||||
| } | ||||
|  | ||||
| func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { | ||||
| 	return c.deadline, true | ||||
| } | ||||
|  | ||||
| func (c *timerCtx) String() string { | ||||
| 	return contextName(c.cancelCtx.Context) + ".WithDeadline(" + | ||||
| 		c.deadline.String() + " [" + | ||||
| 		time.Until(c.deadline).String() + "])" | ||||
| } | ||||
|  | ||||
| func (c *timerCtx) cancel(removeFromParent bool, err error) { | ||||
| 	c.cancelCtx.cancel(false, err) | ||||
| 	if removeFromParent { | ||||
| 		// Remove this timerCtx from its parent cancelCtx's children. | ||||
| 		removeChild(c.cancelCtx.Context, c) | ||||
| 	} | ||||
| 	c.mu.Lock() | ||||
| 	if c.timer != nil { | ||||
| 		c.timer.Stop() | ||||
| 		c.timer = nil | ||||
| 	} | ||||
| 	c.mu.Unlock() | ||||
| } | ||||
|  | ||||
| // WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). | ||||
| // | ||||
| // Canceling this context releases resources associated with it, so code should | ||||
| // call cancel as soon as the operations running in this Context complete: | ||||
| // | ||||
| // 	func slowOperationWithTimeout(ctx context.Context) (Result, error) { | ||||
| // 		ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) | ||||
| // 		defer cancel()  // releases resources if slowOperation completes before timeout elapses | ||||
| // 		return slowOperation(ctx) | ||||
| // 	} | ||||
| func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { | ||||
| 	return WithDeadline(parent, time.Now().Add(timeout)) | ||||
| } | ||||
|  | ||||
| // WithValue returns a copy of parent in which the value associated with key is | ||||
| // val. | ||||
| // | ||||
| // Use context Values only for request-scoped data that transits processes and | ||||
| // APIs, not for passing optional parameters to functions. | ||||
| // | ||||
| // The provided key must be comparable and should not be of type | ||||
| // string or any other built-in type to avoid collisions between | ||||
| // packages using context. Users of WithValue should define their own | ||||
| // types for keys. To avoid allocating when assigning to an | ||||
| // interface{}, context keys often have concrete type | ||||
| // struct{}. Alternatively, exported context key variables' static | ||||
| // type should be a pointer or interface. | ||||
| func WithValue(parent Context, key, val interface{}) Context { | ||||
| 	if parent == nil { | ||||
| 		panic("cannot create context from nil parent") | ||||
| 	} | ||||
| 	if key == nil { | ||||
| 		panic("nil key") | ||||
| 	} | ||||
| 	if !reflect.TypeOf(key).Comparable() { | ||||
| 		panic("key is not comparable") | ||||
| 	} | ||||
| 	return &valueCtx{parent, key, val} | ||||
| } | ||||
|  | ||||
| // A valueCtx carries a key-value pair. It implements Value for that key and | ||||
| // delegates all other calls to the embedded Context. | ||||
| type valueCtx struct { | ||||
| 	Context | ||||
| 	key, val interface{} | ||||
| } | ||||
|  | ||||
| // stringify tries a bit to stringify v, without using fmt, since we don't | ||||
| // want context depending on the unicode tables. This is only used by | ||||
| // *valueCtx.String(). | ||||
| func stringify(v interface{}) string { | ||||
| 	switch s := v.(type) { | ||||
| 	case stringer: | ||||
| 		return s.String() | ||||
| 	case string: | ||||
| 		return s | ||||
| 	} | ||||
| 	return "<not Stringer>" | ||||
| } | ||||
|  | ||||
| func (c *valueCtx) String() string { | ||||
| 	return contextName(c.Context) + ".WithValue(type " + | ||||
| 		reflect.TypeOf(c.key).String() + | ||||
| 		", val " + stringify(c.val) + ")" | ||||
| } | ||||
|  | ||||
| func (c *valueCtx) Value(key interface{}) interface{} { | ||||
| 	if c.key == key { | ||||
| 		return c.val | ||||
| 	} | ||||
| 	return c.Context.Value(key) | ||||
| } | ||||
|  | ||||
| // ======================== 以上为 context.go 源码 | ||||
|  | ||||
| // MyContext 自定义 context | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 12:45 下午 2021/12/2 | ||||
| type MyContext struct { | ||||
| 	Context | ||||
| } | ||||
							
								
								
									
										42
									
								
								demo/context_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								demo/context_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // Package demo ... | ||||
| // | ||||
| // Description : demo ... | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2021-12-02 12:46 下午 | ||||
| package demo | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // TestCancelCtx_UDC 测试自定义context | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 12:46 下午 2021/12/2 | ||||
| func TestCancelCtx_UDC(t *testing.T) { | ||||
| 	childCancel := true | ||||
|  | ||||
| 	parentCtx, parentFunc := WithCancel(Background()) | ||||
| 	mctx := MyContext{parentCtx} | ||||
|  | ||||
| 	childCtx, childFun := WithCancel(mctx) | ||||
|  | ||||
| 	if childCancel { | ||||
| 		childFun() | ||||
| 	} else { | ||||
| 		parentFunc() | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println("parent context => ", parentCtx, reflect.TypeOf(parentCtx).String()) | ||||
| 	fmt.Println("my context => ", mctx, reflect.TypeOf(mctx).String()) | ||||
| 	fmt.Println("child context => ", childCtx, reflect.TypeOf(childCtx).String()) | ||||
|  | ||||
| 	// 防止主协程退出太快,子协程来不及打印 | ||||
| 	time.Sleep(5 * time.Second) | ||||
| } | ||||
							
								
								
									
										18
									
								
								gin/static/define.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								gin/static/define.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| // Package static ... | ||||
| // | ||||
| // Description : static | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2021/12/24 2:18 PM | ||||
| package static | ||||
|  | ||||
| // MapRule 定义映射规则 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:18 PM 2021/12/24 | ||||
| type MapRule struct { | ||||
| 	URIPrefix     string `json:"uri_prefix"`      // 路由前缀 | ||||
| 	StaticDirPath string `json:"static_dir_path"` // 静态资源路由 | ||||
| } | ||||
							
								
								
									
										25
									
								
								gin/static/static.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								gin/static/static.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| // Package static ... | ||||
| // | ||||
| // Description : 启动静态资源服务器 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2021/12/24 2:14 PM | ||||
| package static | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
|  | ||||
| // Register 静态资源服务器 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:17 PM 2021/12/24 | ||||
| func Register(router *gin.Engine, ruleList []*MapRule) { | ||||
| 	for _, rule := range ruleList { | ||||
| 		router.StaticFS(rule.URIPrefix, http.Dir(rule.StaticDirPath)) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							| @ -4,8 +4,6 @@ go 1.17 | ||||
|  | ||||
| replace github.com/coreos/bbolt v1.3.4 => go.etcd.io/bbolt v1.3.4 | ||||
|  | ||||
| replace github.com/gin-gonic/gin v1.7.5 => github.com/gin-gonic/gin v1.7.6 | ||||
|  | ||||
| replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 | ||||
|  | ||||
| require ( | ||||
| @ -13,7 +11,7 @@ require ( | ||||
| 	github.com/apolloconfig/agollo/v4 v4.0.9 | ||||
| 	github.com/coreos/etcd v3.3.27+incompatible | ||||
| 	github.com/ddliu/go-httpclient v0.6.9 | ||||
| 	github.com/gin-gonic/gin v1.7.5 | ||||
| 	github.com/gin-gonic/gin v1.7.6 | ||||
| 	github.com/go-redis/redis/v8 v8.11.4 | ||||
| 	github.com/go-redis/redis_rate/v9 v9.1.2 | ||||
| 	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible | ||||
| @ -29,7 +27,7 @@ require ( | ||||
| 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df | ||||
| 	gopkg.in/yaml.v2 v2.4.0 | ||||
| 	gorm.io/driver/mysql v1.2.0 | ||||
| 	gorm.io/gorm v1.22.3 | ||||
| 	gorm.io/gorm v1.22.4 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| @ -70,7 +68,7 @@ require ( | ||||
| 	github.com/jcmturner/gokrb5/v8 v8.4.2 // indirect | ||||
| 	github.com/jcmturner/rpc/v2 v2.0.3 // indirect | ||||
| 	github.com/jinzhu/inflection v1.0.0 // indirect | ||||
| 	github.com/jinzhu/now v1.1.2 // indirect | ||||
| 	github.com/jinzhu/now v1.1.4 // indirect | ||||
| 	github.com/jonboulle/clockwork v0.2.2 // indirect | ||||
| 	github.com/json-iterator/go v1.1.11 // indirect | ||||
| 	github.com/klauspost/compress v1.13.6 // indirect | ||||
|  | ||||
							
								
								
									
										5
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								go.sum
									
									
									
									
									
								
							| @ -294,6 +294,9 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD | ||||
| github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= | ||||
| github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= | ||||
| github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= | ||||
| github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= | ||||
| github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= | ||||
| github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= | ||||
| github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= | ||||
| github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= | ||||
| github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | ||||
| @ -933,6 +936,8 @@ gorm.io/driver/mysql v1.2.0 h1:l8+9VwjjyzEkw0PNPBOr2JHhLOGVk7XEnl5hk42bcvs= | ||||
| gorm.io/driver/mysql v1.2.0/go.mod h1:4RQmTg4okPghdt+kbe6e1bTXIQp7Ny1NnBn/3Z6ghjk= | ||||
| gorm.io/gorm v1.22.3 h1:/JS6z+GStEQvJNW3t1FTwJwG/gZ+A7crFdRqtvG5ehA= | ||||
| gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= | ||||
| gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM= | ||||
| gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk= | ||||
| honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
|  | ||||
| @ -11,14 +11,16 @@ import ( | ||||
| 	"context" | ||||
| 	"math" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.etcd.io/etcd/clientv3" | ||||
| ) | ||||
|  | ||||
| // WatchKey 监听key的变化,永久监听 | ||||
| // WatchKeyWithOption ... | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:58 下午 2021/11/23 | ||||
| func WatchKey(ctx context.Context, watchKey string, callbackFunc WatcherHandler) { | ||||
| // Date : 2:29 下午 2021/11/26 | ||||
| func WatchKeyWithOption(ctx context.Context, watchKey string, callbackFunc WatcherHandler, optionList ...clientv3.OpOption) { | ||||
| 	if nil == callbackFunc { | ||||
| 		// 变化之后,没有任何逻辑处理,视为不需要监听变化 | ||||
| 		return | ||||
| @ -27,7 +29,7 @@ func WatchKey(ctx context.Context, watchKey string, callbackFunc WatcherHandler) | ||||
| 		ctx = context.Background() | ||||
| 	} | ||||
|  | ||||
| 	rch := Client.Watch(ctx, watchKey) // <-chan WatchResponse | ||||
| 	rch := Client.Watch(ctx, watchKey, optionList...) // <-chan WatchResponse | ||||
| 	for watchResp := range rch { | ||||
| 		for _, ev := range watchResp.Events { | ||||
| 			callbackFunc(ev) | ||||
| @ -35,6 +37,24 @@ func WatchKey(ctx context.Context, watchKey string, callbackFunc WatcherHandler) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WatchKey 监听key的变化,永久监听 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:58 下午 2021/11/23 | ||||
| func WatchKey(ctx context.Context, watchKey string, callbackFunc WatcherHandler) { | ||||
| 	WatchKeyWithOption(ctx, watchKey, callbackFunc) | ||||
| } | ||||
|  | ||||
| // WatchWithKeyPrefix ... | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:30 下午 2021/11/26 | ||||
| func WatchWithKeyPrefix(ctx context.Context, watchKey string, callbackFunc WatcherHandler) { | ||||
| 	WatchKeyWithOption(ctx, watchKey, callbackFunc, clientv3.WithPrefix()) | ||||
| } | ||||
|  | ||||
| // WatchKeyWithCancel 可以随时取消的 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
|  | ||||
							
								
								
									
										301
									
								
								middleware/redis/define.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								middleware/redis/define.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,301 @@ | ||||
| // Package redis ... | ||||
| // | ||||
| // Description : redis ... | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2021-11-26 21:07 下午 | ||||
| package redis | ||||
|  | ||||
| // FullServerInfo 获取服务器信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 21:07 下午 2021/11/26 | ||||
| type FullServerInfo struct { | ||||
| 	ServerInfo   *ServerInfo  `json:"server_info"` | ||||
| 	ClientInfo   *ClientInfo  `json:"client_info"` | ||||
| 	MemoryInfo   *MemoryInfo  `json:"memory_info"` | ||||
| 	Persistence  *Persistence `json:"persistence"` | ||||
| 	Stats        *Stats       `json:"stats"` | ||||
| 	Replication  *Replication `json:"replication"` | ||||
| 	CPU          *CPU         `json:"cpu"` | ||||
| 	CommandStats []CmdStat    `json:"command_stats"` | ||||
| 	Keyspace     []DB         `json:"keyspace"` | ||||
| 	Cluster      *Cluster     `json:"cluster"` | ||||
| 	ErrorStats   []Error      `json:"error_stats"` | ||||
| 	Modules      interface{}  `json:"modules"` | ||||
| } | ||||
|  | ||||
| // ServerInfo 服务器信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 21:09 下午 2021/11/26 | ||||
| type ServerInfo struct { | ||||
| 	RedisVersion      string `json:"redis_version" yaml:"redis_version"`           // Redis 服务器版本 | ||||
| 	RedisGitSha1      string `json:"redis_git_sha1" yaml:"redis_git_sha1"`         // Git SHA1 | ||||
| 	RedisGitDirty     string `json:"redis_git_dirty" yaml:"redis_git_dirty"`       // Git dirty flag | ||||
| 	RedisBuildID      string `json:"redis_build_id" yaml:"redis_build_id"`         // 构建ID | ||||
| 	RedisMode         string `json:"redis_mode" yaml:"redis_mode"`                 // 运行模式(“独立”,“哨兵”或“集群”) | ||||
| 	OS                string `json:"os" yaml:"os"`                                 // Redis 服务器的宿主操作系统 | ||||
| 	ArchBits          string `json:"arch_bits" yaml:"arch_bits"`                   // 架构(32 或 64 位) | ||||
| 	MultiplexingApi   string `json:"multiplexing_api" yaml:"multiplexing_api"`     // Redis 所使用的事件处理机制 | ||||
| 	AtomicvarApi      string `json:"atomicvar_api" yaml:"atomicvar_api"`           // 原子处理api | ||||
| 	GCCVersion        string `json:"gcc_version" yaml:"gcc_version"`               // 编译 Redis 时所使用的 GCC 版本 | ||||
| 	ProcessID         int    `json:"process_id" yaml:"process_id"`                 // 服务器进程的 PID | ||||
| 	ProcessSupervised string `json:"process_supervised" yaml:"process_supervised"` // 是否有进程监控 | ||||
| 	RunID             string `json:"run_id" yaml:"run_id"`                         // Redis 服务器的随机标识符(用于 Sentinel 和集群) | ||||
| 	TCPPort           int    `json:"tcp_port" yaml:"tcp_port"`                     // TCP/IP 监听端口 | ||||
| 	ServerTimeUsec    int64  `json:"server_time_usec" yaml:"server_time_usec"`     // 当前服务器时间,微秒 | ||||
| 	UptimeInSeconds   int64  `json:"uptime_in_seconds" yaml:"uptime_in_seconds"`   // 自 Redis 服务器启动以来,经过的秒数 | ||||
| 	UptimeInDays      int64  `json:"uptime_in_days" yaml:"uptime_in_days"`         // 自 Redis 服务器启动以来,经过的天数 | ||||
| 	Hz                int64  `json:"hz" yaml:"hz"`                                 // redis内部调度(进行关闭timeout的客户端,删除过期key等等)频率,程序规定serverCron每秒运行10次。 | ||||
| 	ConfiguredHz      int64  `json:"configured_hz" yaml:"configured_hz"`           // 服务器的频率设置 | ||||
| 	LRUClock          int64  `json:"lru_clock" yaml:"lru_clock"`                   // 以秒为单位进行自增的时钟,用于 LRU 管理 | ||||
| 	Executable        string `json:"executable" yaml:"executable"`                 // 可执行文件位置 | ||||
| 	ConfigFile        string `json:"config_file" yaml:"config_file"`               // 服务所使用的配置文件 | ||||
| 	IOThreadsActive   int    `json:"io_threads_active" yaml:"io_threads_active"`   // 活跃的io线程数 | ||||
| } | ||||
|  | ||||
| // ClientInfo 客户端信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 21:29 下午 2021/11/26 | ||||
| type ClientInfo struct { | ||||
| 	ConnectedClients            int   `json:"connected_clients" yaml:"connected_clients"`                             // 客户端连接数 | ||||
| 	ClusterConnections          int   `json:"cluster_connections" yaml:"cluster_connections"`                         // 集群连接数 | ||||
| 	MaxClients                  int   `json:"maxclients" yaml:"max_clients"`                                          // 最大客户端连接数 | ||||
| 	ClientRecentMaxInputBuffer  int64 `json:"client_recent_max_input_buffer" yaml:"client_recent_max_input_buffer"`   // 最近最大输入缓存 | ||||
| 	ClientRecentMaxOutputBuffer int64 `json:"client_recent_max_output_buffer" yaml:"client_recent_max_output_buffer"` // 最近最大输出缓存 | ||||
| 	BlockedClients              int   `json:"blocked_clients" yaml:"blocked_clients"`                                 // 阻塞客户端数量, 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量 | ||||
| 	TrackingClients             int   `json:"tracking_clients" yaml:"tracking_clients"`                               // tracking_clients | ||||
| 	ClientsInTimeoutTable       int   `json:"clients_in_timeout_table" yaml:"clients_in_timeout_table"`               // clients_in_timeout_table | ||||
| } | ||||
|  | ||||
| // MemoryInfo 内存使用信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 22:08 下午 2021/11/26 | ||||
| type MemoryInfo struct { | ||||
| 	UsedMemory             int64   `json:"used_memory" yaml:"used_memory"`                             // 由 Redis 分配器分配的内存总量,以字节(byte)为单位 | ||||
| 	UsedMemoryHuman        string  `json:"used_memory_human" yaml:"used_memory_human"`                 // UsedMemory 可读化表示 | ||||
| 	UsedMemoryRss          int64   `json:"used_memory_rss" yaml:"used_memory_rss"`                     // 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致。 | ||||
| 	UsedMemoryRssHuman     string  `json:"used_memory_rss_human" yaml:"used_memory_rss_human"`         // UsedMemoryRssHuman 可读化表示 | ||||
| 	UsedMemoryPeak         int64   `json:"used_memory_peak" yaml:"used_memory_peak"`                   // Redis 的内存消耗峰值(以字节为单位) | ||||
| 	UsedMemoryPeakHuman    string  `json:"used_memory_peak_human" yaml:"used_memory_peak_human"`       // UsedMemoryPeak 可读化表示 | ||||
| 	UsedMemoryPeakPerc     string  `json:"used_memory_peak_perc" yaml:"used_memory_peak_perc"`         // 峰值系统内存使用率 | ||||
| 	UsedMemoryOverhead     int64   `json:"used_memory_overhead" yaml:"used_memory_overhead"`           // Redis为了维护数据集的内部机制所需的内存开销,包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog | ||||
| 	UsedMemoryStartup      int64   `json:"used_memory_startup" yaml:"used_memory_startup"`             // Redis服务器启动时消耗的内存 | ||||
| 	UsedMemoryDataset      int64   `json:"used_memory_dataset" yaml:"used_memory_dataset"`             // 数据占用的内存大小,即used_memory-used_memory_overhead | ||||
| 	UsedMemoryDatasetPerc  int64   `json:"used_memory_dataset_perc" yaml:"used_memory_dataset_perc"`   // 数据占用的内存大小的百分比,100%*(used_memory_dataset/(used_memory-used_memory_startup)) | ||||
| 	AllocatorAllocated     int64   `json:"allocator_allocated" yaml:"allocator_allocated"`             // 内存分配器申请的内存,以字节为单位 | ||||
| 	AllocatorActive        int64   `json:"allocator_active" yaml:"allocator_active"`                   // 内存分配器正在使用的内存,以字节为单位 | ||||
| 	AllocatorResident      int64   `json:"allocator_resident" yaml:"allocator_resident"`               // 内存分配器的常驻内存,以字节为单位 | ||||
| 	TotalSystemMemory      int64   `json:"total_system_memory" yaml:"total_system_memory"`             // 操作系统内存(以字节为单位) | ||||
| 	TotalSystemMemoryHuman string  `json:"total_system_memory_human" yaml:"total_system_memory_human"` // TotalSystemMemory 可读化表示 | ||||
| 	UsedMemoryLua          int64   `json:"used_memory_lua" yaml:"used_memory_lua"`                     // Lua脚本存储占用的内存(以字节为单位) | ||||
| 	UsedMemoryLuaHuman     int64   `json:"used_memory_lua_human" yaml:"used_memory_lua_human"`         // UsedMemoryLua 可读化表示 | ||||
| 	UsedMemoryScripts      int64   `json:"used_memory_scripts" yaml:"used_memory_scripts"`             // Lua脚本使用的内存大小(以字节为单位) | ||||
| 	UsedMemoryScriptsHuman int64   `json:"used_memory_scripts_human" yaml:"used_memory_scripts_human"` // UsedMemoryScripts 可读化表示 | ||||
| 	NumberOfCachedScripts  int64   `json:"number_of_cached_scripts" yaml:"number_of_cached_scripts"`   // 缓存的lua脚本数量 | ||||
| 	Maxmemory              int64   `json:"maxmemory" yaml:"maxmemory"`                                 // Redis实例的最大内存配置(以字节为单位) | ||||
| 	MaxmemoryHuman         int64   `json:"maxmemory_human" yaml:"maxmemory_human"`                     // Maxmemory 可读化表示 | ||||
| 	MaxmemoryPolicy        string  `json:"maxmemory_policy" yaml:"maxmemory_policy"`                   // 当数据达到最大内存之后的淘汰策略 | ||||
| 	AllocatorFragRatio     float64 `json:"allocator_frag_ratio" yaml:"allocator_frag_ratio"`           // 内存分配器碎片比例 | ||||
| 	AllocatorFragBytes     int64   `json:"allocator_frag_bytes" yaml:"allocator_frag_bytes"`           // 内存分配器碎片大小,以字节为单位 | ||||
| 	AllocatorRssRatio      float64 `json:"allocator_rss_ratio" yaml:"allocator_rss_ratio"`             // 从操作系统角度看, 内存分配器碎片比例 | ||||
| 	AllocatorRssBytes      int64   `json:"allocator_rss_bytes" yaml:"allocator_rss_bytes"`             // 从操作系统角度看, 内存分配器碎片大小,以字节为单位 | ||||
| 	RssOverheadRatio       float64 `json:"rss_overhead_ratio" yaml:"rss_overhead_ratio"`               // 从操作系统角度看, 开销的比例 | ||||
| 	RssOverheadBytes       int64   `json:"rss_overhead_bytes" yaml:"rss_overhead_bytes"`               // 从操作系统角度看, 开销的大小, 以字节为单位 | ||||
| 	MemFragmentationRatio  float64 `json:"mem_fragmentation_ratio" yaml:"mem_fragmentation_ratio"`     // 碎片率,used_memory_rss/ used_memory,正常情况下稍大于1。低于1,Redis实例可能会把部分数据交换到硬盘上,内存交换会严重影响Redis的性能,所以应该增加可用物理内存。大于1.5表示碎片过多。额外碎片的产生是由于Redis释放了内存块,但内存分配器并没有返回内存给操作系统,这个内存分配器是在编译时指定的,可以是libc、jemalloc或者tcmalloc。 | ||||
| 	MemFragmentationBytes  int64   `json:"mem_fragmentation_bytes" yaml:"mem_fragmentation_bytes"`     // 内存碎片大小(字节表示) | ||||
| 	MemNotCountedForEvict  int64   `json:"mem_not_counted_for_evict" yaml:"mem_not_counted_for_evict"` // 不应驱逐的内存大小,以字节为单位 | ||||
| 	MemReplicationBacklog  int64   `json:"mem_replication_backlog" yaml:"mem_replication_backlog"`     // 复制backlog的内存大小, 以字节为单位 | ||||
| 	MemClientsSlaves       int64   `json:"mem_clients_slaves" yaml:"mem_clients_slaves"`               // mem_clients_slaves | ||||
| 	MemClientsNormal       int64   `json:"mem_clients_normal" yaml:"mem_clients_normal"`               // mem_clients_normal | ||||
| 	MemAofBuffer           int64   `json:"mem_aof_buffer" yaml:"mem_aof_buffer"`                       // AOF内存缓冲区大小 | ||||
| 	MemAllocator           string  `json:"mem_allocator" yaml:"mem_allocator"`                         // 内存分配器,Redis支持glibc’s malloc、jemalloc11、tcmalloc几种不同的内存分配器,每个分配器在内存分配和碎片上都有不同的实现。不建议普通管理员修改Redis默认内存分配器,因为这需要完全理解这几种内存分配器的差异,也要重新编译Redis。 | ||||
| 	ActiveDefragRunning    int64   `json:"active_defrag_running" yaml:"active_defrag_running"`         // defrag:表示内存碎片整理, 0表示没有活动的defrag任务正在运行,1表示有活动的defrag任务正在运行 | ||||
| 	LazyfreePendingObjects int64   `json:"lazyfree_pending_objects" yaml:"lazyfree_pending_objects"`   // 延迟释放的挂起对象, 0表示不存在 | ||||
| 	LazyfreedObjects       int64   `json:"lazyfreed_objects" yaml:"lazyfreed_objects"`                 // 延迟释放的对象数量 | ||||
| } | ||||
|  | ||||
| // Persistence 持久化信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 22:15 下午 2021/11/26 | ||||
| type Persistence struct { | ||||
| 	Loading                   int     `json:"loading" yaml:"loading"`                                           // 服务器是否正在进行持久化 0 - 否 1 -是 | ||||
| 	CurrentCowSize            int     `json:"current_cow_size" yaml:"current_cow_size"`                         // current_cow_size | ||||
| 	CurrentCowSizeAge         int     `json:"current_cow_size_age" yaml:"current_cow_size_age"`                 // current_cow_size_age | ||||
| 	CurrentForkPerc           float64 `json:"current_fork_perc" yaml:"current_fork_perc"`                       // current_fork_perc | ||||
| 	CurrentSaveKeysProcessed  int     `json:"current_save_keys_processed" yaml:"current_save_keys_processed"`   // current_save_keys_processed | ||||
| 	CurrentSaveKeysTotal      int64   `json:"current_save_keys_total" yaml:"current_save_keys_total"`           // current_save_keys_total | ||||
| 	RdbChangesSinceLastSave   int64   `json:"rdb_changes_since_last_save" yaml:"rdb_changes_since_last_save"`   // 离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化 | ||||
| 	RdbBgsaveInProgress       int     `json:"rdb_bgsave_in_progress" yaml:"rdb_bgsave_in_progress"`             // 服务器是否正在创建rdb文件 0 - 否 1 - 是 | ||||
| 	RdbLastSaveTime           int64   `json:"rdb_last_save_time" yaml:"rdb_last_save_time"`                     // 最近一次创建rdb文件的时间戳,单位秒 | ||||
| 	RdbLastBgsaveStatus       string  `json:"rdb_last_bgsave_status" yaml:"rdb_last_bgsave_status"`             // 最近一次rdb持久化是否成功 ok 成功 | ||||
| 	RdbLastBgsaveTimeSec      int64   `json:"rdb_last_bgsave_time_sec" yaml:"rdb_last_bgsave_time_sec"`         // 最近一次成功生成rdb文件耗时秒数 | ||||
| 	RdbCurrentBgsaveTimeSec   int64   `json:"rdb_current_bgsave_time_sec" yaml:"rdb_current_bgsave_time_sec"`   // 如果服务器正在创建rdb文件,那么这个字段记录的就是当前的创建操作已经耗费的秒数 | ||||
| 	RdbLastCowSize            int64   `json:"rdb_last_cow_size" yaml:"rdb_last_cow_size"`                       // RDB过程中父进程与子进程相比执行了多少修改(包括读缓冲区,写缓冲区,数据修改等)。 | ||||
| 	AofEnabled                int     `json:"aof_enabled" yaml:"aof_enabled"`                                   // 是否开启了AOF 0 - 否 1 - 是 | ||||
| 	AofRewriteInProgress      int     `json:"aof_rewrite_in_progress" yaml:"aof_rewrite_in_progress"`           // 标识aof的rewrite操作是否在进行中 0 - 否 1- 是 | ||||
| 	AofRewriteScheduled       int     `json:"aof_rewrite_scheduled" yaml:"aof_rewrite_scheduled"`               // rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite | ||||
| 	AofLastRewriteTimeSec     int     `json:"aof_last_rewrite_time_sec" yaml:"aof_last_rewrite_time_sec"`       // 最近一次aof rewrite耗费的时长 | ||||
| 	AofCurrentRewriteTimeSec  int     `json:"aof_current_rewrite_time_sec" yaml:"aof_current_rewrite_time_sec"` // 如果rewrite操作正在进行,则记录所使用的时间,单位秒 | ||||
| 	AofLastBgrewriteStatus    string  `json:"aof_last_bgrewrite_status" yaml:"aof_last_bgrewrite_status"`       // 上次 bgrewrite aof 操作的状态 ok 成功 | ||||
| 	AofLastWriteStatus        string  `json:"aof_last_write_status" yaml:"aof_last_write_status"`               // 上次aof写入状态 | ||||
| 	AofLastCowSize            int64   `json:"aof_last_cow_size" yaml:"aof_last_cow_size"`                       // AOF过程中父进程与子进程相比执行了多少修改(包括读缓冲区,写缓冲区,数据修改等) | ||||
| 	IOThreadedReadsProcessed  int     `json:"io_threaded_reads_processed" yaml:"io_threaded_reads_processed"`   // 读取线程数 | ||||
| 	IOThreadedWritesProcessed int     `json:"io_threaded_writes_processed" yaml:"io_threaded_writes_processed"` // 写入线程数 | ||||
| } | ||||
|  | ||||
| // Stats 服务运行状态 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 10:57 下午 2021/11/26 | ||||
| type Stats struct { | ||||
| 	TotalConnectionsReceived   int64   `json:"total_connections_received" yaml:"total_connections_received"`         // 所有连接数, 累积值, 只增不减, 连接断开也不会减少 | ||||
| 	TotalCommandsProcessed     int64   `json:"total_commands_processed" yaml:"total_commands_processed"`             // 服务器执行的命令数 累积值, 只增不减 | ||||
| 	InstantaneousPpsPerSec     int     `json:"instantaneous_pps_per_sec" yaml:"instantaneous_pps_per_sec"`           // 每秒执行的命令数 | ||||
| 	TotalNetInputBytes         int64   `json:"total_net_input_bytes" yaml:"total_net_input_bytes"`                   // 网络流量-流入 以字节(byte)为单位 | ||||
| 	TotalNetOutputBytes        int64   `json:"total_net_output_bytes" yaml:"total_net_output_bytes"`                 // 网络流量-流出 以字节(byte)为单位 | ||||
| 	InstantaneousInputKbps     float64 `json:"instantaneous_input_kbps" yaml:"instantaneous_input_kbps"`             // 网络流量-流入-KB/s | ||||
| 	InstantaneousOutputKbps    float64 `json:"instantaneous_output_kbps" yaml:"instantaneous_output_kbps"`           // 网络流量-流出-KB/s | ||||
| 	RejectedConnections        int64   `json:"rejected_connections" yaml:"rejected_connections"`                     // 因达到最大连接数而被拒绝的连接数量 | ||||
| 	SyncFull                   int64   `json:"sync_full" yaml:"sync_full"`                                           // 主从全量同步的次数 | ||||
| 	SyncPartialOk              int64   `json:"sync_partial_ok" yaml:"sync_partial_ok"`                               // 主从部分同步成功的次数 | ||||
| 	SyncPartialErr             int64   `json:"sync_partial_err" yaml:"sync_partial_err"`                             // 主从部分同步失败次数 | ||||
| 	ExpiredKeys                int     `json:"expired_keys" yaml:"expired_keys"`                                     // 过期key的数量 | ||||
| 	ExpiredStalePerc           float64 `json:"expired_stale_perc" yaml:"expired_stale_perc"`                         // 过期过时的百分比 | ||||
| 	ExpiredTimeCapReachedCount int64   `json:"expired_time_cap_reached_count" yaml:"expired_time_cap_reached_count"` // 过期时间达到上限的数量 | ||||
| 	ExpireCycleCpuMilliseconds int64   `json:"expire_cycle_cpu_milliseconds" yaml:"expire_cycle_cpu_milliseconds"`   // 过期循环CPU毫秒数 | ||||
| 	EvictedKeys                int64   `json:"evicted_keys" yaml:"evicted_keys"`                                     // 超过 maxmemory 之后, 剔除的 key 的数量 | ||||
| 	KeyspaceHits               int64   `json:"keyspace_hits" yaml:"keyspace_hits"`                                   // 访问命中次数 | ||||
| 	KeyspaceMisses             int64   `json:"keyspace_misses" yaml:"keyspace_misses"`                               // 访问未命中次数 | ||||
| 	PubsubChannels             int64   `json:"pubsub_channels" yaml:"pubsub_channels"`                               // 当前频道数量  发布 - 订阅 模式 | ||||
| 	PubsubPatterns             int64   `json:"pubsub_patterns" yaml:"pubsub_patterns"`                               // 当前使用中的模式数量 | ||||
| 	LatestForkUsec             int64   `json:"latest_fork_usec" yaml:"latest_fork_usec"`                             // 最近一次fork 操作消耗的时间, 单位微秒 | ||||
| 	TotalForks                 int64   `json:"total_forks" yaml:"total_forks"`                                       // fork 的总次数 | ||||
| 	MigrateCachedSockets       int64   `json:"migrate_cached_sockets" yaml:"migrate_cached_sockets"`                 // 记录当前 Redis 正在 migrate 操作的目标 Redis 个数, 例如 A 向 B 和 C 执行 migrate操作, 这个值为2 | ||||
| 	SlaveExpiresTrackedKeys    int64   `json:"slave_expires_tracked_keys" yaml:"slave_expires_tracked_keys"`         // 从实例到期的 key 的数量 | ||||
| 	ActiveDefragHits           int64   `json:"active_defrag_hits" yaml:"active_defrag_hits"`                         // 主动碎片整理命中次数 | ||||
| 	ActiveDefragMisses         int64   `json:"active_defrag_misses" yaml:"active_defrag_misses"`                     // 主动碎片整理未命中次数 | ||||
| 	ActiveDefragKeyHits        int64   `json:"active_defrag_key_hits" yaml:"active_defrag_key_hits"`                 // 主动整理碎片, key命中次数 | ||||
| 	ActiveDefragKeyMisses      int64   `json:"active_defrag_key_misses" yaml:"active_defrag_key_misses"`             // 主动整理碎片, key未命中次数 | ||||
| 	TrackingTotalKeys          int64   `json:"tracking_total_keys" yaml:"tracking_total_keys"`                       // key 查询的总数 | ||||
| 	TrackingTotalItems         int64   `json:"tracking_total_items" yaml:"tracking_total_items"`                     // item查询的总数 | ||||
| 	TrackingTotalPrefixes      int64   `json:"tracking_total_prefixes" yaml:"tracking_total_prefixes"`               // 前缀查询的总数 | ||||
| 	UnexpectedErrorReplies     int64   `json:"unexpected_error_replies" yaml:"unexpected_error_replies"`             // unexpected 异常响应次数 | ||||
| 	TotalErrorReplies          int64   `json:"total_error_replies" yaml:"total_error_replies"`                       // 异常响应总次数 | ||||
| 	DumpPayloadSanitizations   int64   `json:"dump_payload_sanitizations" yaml:"dump_payload_sanitizations"`         // dump_payload_sanitizations | ||||
| 	TotalReadsProcessed        int64   `json:"total_reads_processed" yaml:"total_reads_processed"`                   // 正在读取的请求数 | ||||
| 	TotalWritesProcessed       int64   `json:"total_writes_processed" yaml:"total_writes_processed"`                 // 正在写入的请求数 | ||||
| 	IOThreadedReadsProcessed   int64   `json:"io_threaded_reads_processed" yaml:"io_threaded_reads_processed"`       // 正在读取的线程数 | ||||
| 	IOThreadedWritesProcessed  int64   `json:"io_threaded_writes_processed" yaml:"io_threaded_writes_processed"`     // 正在写入的线程数 | ||||
| } | ||||
|  | ||||
| // Replication 复制相关 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 12:42 上午 2021/11/27 | ||||
| type Replication struct { | ||||
| 	Role                       string      `json:"role" yaml:"role"`                                                     // 节点的角色 master / slave | ||||
| 	ReplBacklogActive          int         `json:"repl_backlog_active" yaml:"repl_backlog_active"`                       // 复制缓冲区是否开启 0 - 未开启 1 - 已开启 | ||||
| 	ReplBacklogSize            int64       `json:"repl_backlog_size" yaml:"repl_backlog_size"`                           // 复制缓冲区大小(以字节为单位) | ||||
| 	ReplBacklogFirstByteOffset int64       `json:"repl_backlog_first_byte_offset" yaml:"repl_backlog_first_byte_offset"` // 复制缓冲区里偏移量的大小 | ||||
| 	ReplBacklogHistlen         int64       `json:"repl_backlog_histlen" yaml:"repl_backlog_histlen"`                     // 此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小 | ||||
| 	ConnectedSlaves            int         `json:"connected_slaves" yaml:"connected_slaves"`                             // 仅主节点属性 : 连接的从节点数量 | ||||
| 	MasterFailoverState        string      `json:"master_failover_state" yaml:"master_failover_state"`                   // 仅主节点 : 故障转移状态 no-failover 无故障转移 | ||||
| 	MasterReplid               string      `json:"master_replid" yaml:"master_replid"`                                   // 仅主节点 : 实例启动的随机字符串 | ||||
| 	MasterReplid2              string      `json:"master_replid2" yaml:"master_replid2"`                                 // 仅主节点 : 实例启动的随机字符串2 | ||||
| 	MasterReplOffset           int64       `json:"master_repl_offset" yaml:"master_repl_offset"`                         // 仅主节点 : 主从同步偏移量 | ||||
| 	SecondReplOffset           int64       `json:"second_repl_offset" yaml:"second_repl_offset"`                         // 仅主节点 : 主从同步偏移量2 | ||||
| 	MasterHost                 string      `json:"master_host" yaml:"master_host"`                                       // 仅从节点 : 主节点host | ||||
| 	MasterPort                 int         `json:"master_port" yaml:"master_port"`                                       // 仅从节点 : 主节点端口 | ||||
| 	MasterLinkStatus           string      `json:"master_link_status" yaml:"master_link_status"`                         // 仅从节点 : 与主节点连接状态 up - 正常连接 down - 断开 | ||||
| 	MasterLastIOSecondsAgo     int         `json:"master_last_io_seconds_ago" yaml:"master_last_io_seconds_ago"`         // 仅从节点 : 主节点与从节点最后通信的时间间隔, 单位: s | ||||
| 	MasterSyncInProgress       int         `json:"master_sync_in_progress" yaml:"master_sync_in_progress"`               // 仅从节点 : 从节点是否正在全量同步主节点rdb文件 0 - 否 1 - 是 | ||||
| 	SlaveReplOffset            int64       `json:"slave_repl_offset" yaml:"slave_repl_offset"`                           // 仅从节点 : 复制偏移量 | ||||
| 	SlavePriority              int         `json:"slave_priority" yaml:"slave_priority"`                                 // 仅从节点 : 从节点优先级 | ||||
| 	SlaveReadOnly              int         `json:"slave_read_only" yaml:"slave_read_only"`                               // 仅从节点 : 从节点是否只读 0 - 否 1 - 是 | ||||
| 	SlaveList                  []SlaveNode `json:"slave_list" yaml:"slave_list"`                                         // 从节点列表 | ||||
| } | ||||
|  | ||||
| // SlaveNode 从库数据结构,基于原始数据解析 eg : slave0:ip=x.x.x.x,port=6379,state=online,offset=123456,lag=1 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:51 上午 2021/11/27 | ||||
| type SlaveNode struct { | ||||
| 	ID     string `json:"id" yaml:"id"`         // 从库ID | ||||
| 	IP     string `json:"ip" yaml:"ip"`         // 从库IP | ||||
| 	Port   int    `json:"port" yaml:"port"`     // 从库端口 | ||||
| 	State  string `json:"state" yaml:"state"`   // 从库状态 online - 在线 offline - 离线 | ||||
| 	Offset int64  `json:"offset" yaml:"offset"` // 数据偏移量 | ||||
| 	Lag    int64  `json:"lag" yaml:"lag"`       // 数据延迟量大小 | ||||
| } | ||||
|  | ||||
| // CPU 信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:54 上午 2021/11/27 | ||||
| type CPU struct { | ||||
| 	UsedCpuSys          float64 `json:"used_cpu_sys" yaml:"used_cpu_sys"`                     // Redis主进程在内核态所占用CPU时钟总和 | ||||
| 	UsedCpuUser         float64 `json:"used_cpu_user" yaml:"used_cpu_user"`                   // Redis主进程在用户态所占用CPU时钟总和 | ||||
| 	UsedCpuSysChildren  float64 `json:"used_cpu_sys_children" yaml:"used_cpu_sys_children"`   // Redis子进程在内核态所占用CPU时钟总和 | ||||
| 	UsedCpuUserChildren float64 `json:"used_cpu_user_children" yaml:"used_cpu_user_children"` // Redis子进程在用户态所占用CPU时钟总和 | ||||
| } | ||||
|  | ||||
| // CmdStat 指令状态 eg : cmdstat_get:calls=1,usec=42121,usec_per_call=42121.00,rejected_calls=0,failed_calls=0 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:02 上午 2021/11/27 | ||||
| type CmdStat struct { | ||||
| 	Cmd           string  `json:"cmd" yaml:"cmd"`                         // 执行的指令 | ||||
| 	Calls         int64   `json:"calls" yaml:"calls"`                     // 执行了多少次 | ||||
| 	TotalUsedTime float64 `json:"total_used_time" yaml:"total_used_time"` // 累计总耗时 微秒 | ||||
| 	AvgUsedTime   float64 `json:"avg_used_time" yaml:"avg_used_time"`     // 平均耗时 微秒 | ||||
| 	RejectedCalls int64   `json:"rejected_calls" yaml:"rejected_calls"`   // 拒绝执行指令次数 | ||||
| 	FailedCalls   int64   `json:"failed_calls" yaml:"failed_calls"`       // 指令执行失败次数 | ||||
| 	SuccessCalls  int64   `json:"success_calls" yaml:"success_calls"`     // 指令执行成功次数 Calls - FailedCalls | ||||
| } | ||||
|  | ||||
| // DB 数据库的数据结构, eg : db0:keys=3,expires=0,avg_ttl=0 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:09 上午 2021/11/27 | ||||
| type DB struct { | ||||
| 	Name    string `json:"name" yaml:"name"`       // 数据库名称 | ||||
| 	Keys    int64  `json:"keys" yaml:"keys"`       // 数据库key的数量 | ||||
| 	Expires int64  `json:"expires" yaml:"expires"` // 数据库过期key的数量 | ||||
| 	AvgTTL  int64  `json:"avg_ttl" yaml:"avg_ttl"` // 平均存活时间 | ||||
| } | ||||
|  | ||||
| // Cluster ... | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:13 上午 2021/11/27 | ||||
| type Cluster struct { | ||||
| 	ClusterEnabled int `json:"cluster_enabled" yaml:"cluster_enabled"` // 是否启用 0 - 否 1 - 是 | ||||
| } | ||||
|  | ||||
| // Error 错误的状态 eg : errorstat_WRONGTYPE:count=4615 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:16 上午 2021/11/27 | ||||
| type Error struct { | ||||
| 	Type  string `json:"type" yaml:"type"`   // 错误类型 | ||||
| 	Count int64  `json:"count" yaml:"count"` // 错误出现次数 | ||||
| } | ||||
							
								
								
									
										326
									
								
								middleware/redis/monitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								middleware/redis/monitor.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,326 @@ | ||||
| // Package redis ... | ||||
| // | ||||
| // Description : redis 系统信息监控 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2021-11-27 12:22 下午 | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.zhangdeman.cn/zhangdeman/gopkg/convert" | ||||
|  | ||||
| 	yml "gopkg.in/yaml.v2" | ||||
|  | ||||
| 	"github.com/go-redis/redis/v8" | ||||
| ) | ||||
|  | ||||
| // GetRedisServerInfo 获取 redis server info | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 12:24 下午 2021/11/27 | ||||
| func GetRedisServerInfo(client *redis.Client) (*ServerInfo, error) { | ||||
| 	var result ServerInfo | ||||
| 	if err := infoToStruct(client, "server", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetMemoryInfo 获取内存信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 12:59 下午 2021/11/27 | ||||
| func GetMemoryInfo(client *redis.Client) (*MemoryInfo, error) { | ||||
| 	var result MemoryInfo | ||||
| 	if err := infoToStruct(client, "memory", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetClientInfo 获取客户端信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:10 下午 2021/11/27 | ||||
| func GetClientInfo(client *redis.Client) (*ClientInfo, error) { | ||||
| 	var result ClientInfo | ||||
| 	if err := infoToStruct(client, "clients", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetPersistence 获取持久化相关信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:11 下午 2021/11/27 | ||||
| func GetPersistence(client *redis.Client) (*Persistence, error) { | ||||
| 	var result Persistence | ||||
| 	if err := infoToStruct(client, "persistence", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetStats 获取状态 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:19 下午 2021/11/27 | ||||
| func GetStats(client *redis.Client) (*Stats, error) { | ||||
| 	var result Stats | ||||
| 	if err := infoToStruct(client, "stats", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetReplication 复制相关信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:23 下午 2021/11/27 | ||||
| func GetReplication(client *redis.Client) (*Replication, error) { | ||||
| 	var result Replication | ||||
| 	if err := infoToStruct(client, "replication", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	result.SlaveList = GetSlaveList(client) | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetSlaveList 获取从库信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 4:26 下午 2021/11/27 | ||||
| func GetSlaveList(client *redis.Client) []SlaveNode { | ||||
| 	// 解析从库信息 | ||||
| 	slaveList := make([]SlaveNode, 0) | ||||
| 	var data map[string]string | ||||
| 	_ = infoToStruct(client, "replication", &data) | ||||
| 	slaveKey := []string{"slave0", "slave1", "slave2", "slave3", "slave4", "slave5", "slave6", "slave7", "slave8", "slave9"} | ||||
| 	for k, v := range data { | ||||
| 		isSlave := false | ||||
| 		for _, item := range slaveKey { | ||||
| 			if strings.Contains(k, item) { | ||||
| 				isSlave = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !isSlave { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		vArr := strings.Split(v, ",") | ||||
| 		if len(vArr) < 3 { | ||||
| 			continue | ||||
| 		} | ||||
| 		slave := SlaveNode{ | ||||
| 			ID:     k, | ||||
| 			IP:     "", | ||||
| 			Port:   0, | ||||
| 			State:  "", | ||||
| 			Offset: 0, | ||||
| 			Lag:    0, | ||||
| 		} | ||||
| 		for _, prop := range vArr { | ||||
| 			propArr := strings.Split(prop, "=") | ||||
| 			if len(propArr) != 2 { | ||||
| 				continue | ||||
| 			} | ||||
| 			switch strings.ToLower(propArr[0]) { | ||||
| 			case "ip": | ||||
| 				slave.IP = propArr[1] | ||||
| 			case "port": | ||||
| 				_ = convert.ConvertAssign(&slave.Port, propArr[1]) | ||||
| 			case "state": | ||||
| 				slave.State = propArr[1] | ||||
| 			case "offset": | ||||
| 				_ = convert.ConvertAssign(&slave.Offset, propArr[1]) | ||||
| 			case "lag": | ||||
| 				_ = convert.ConvertAssign(&slave.Lag, propArr[1]) | ||||
| 			} | ||||
| 		} | ||||
| 		slaveList = append(slaveList, slave) | ||||
| 	} | ||||
| 	return slaveList | ||||
| } | ||||
|  | ||||
| // GetCPUInfo 获取cpu信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:24 下午 2021/11/27 | ||||
| func GetCPUInfo(client *redis.Client) (*CPU, error) { | ||||
| 	var result CPU | ||||
| 	if err := infoToStruct(client, "cpu", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetCommandStats 命令状态 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:27 下午 2021/11/27 | ||||
| func GetCommandStats(client *redis.Client) (*Stats, error) { | ||||
| 	var result Stats | ||||
| 	if err := infoToStruct(client, "stats", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetCommandInfo 获取命令信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 1:32 下午 2021/11/27 | ||||
| func GetCommandInfo(client *redis.Client) ([]CmdStat, error) { | ||||
| 	var result map[string]string | ||||
| 	if err := infoToStruct(client, "commandstats", &result); nil != err { | ||||
| 		return make([]CmdStat, 0), err | ||||
| 	} | ||||
| 	cmdList := make([]CmdStat, 0) | ||||
| 	// 解析数据 | ||||
| 	for cmd, detail := range result { | ||||
| 		infoArr := strings.Split(detail, ",") | ||||
| 		if len(infoArr) < 2 { | ||||
| 			// 去掉开始的描述信息 | ||||
| 			continue | ||||
| 		} | ||||
| 		info := CmdStat{ | ||||
| 			Cmd:           strings.ReplaceAll(strings.ToLower(cmd), "cmdstat_", ""), | ||||
| 			Calls:         0, | ||||
| 			TotalUsedTime: 0, | ||||
| 			AvgUsedTime:   0, | ||||
| 			RejectedCalls: 0, | ||||
| 			FailedCalls:   0, | ||||
| 			SuccessCalls:  0, | ||||
| 		} | ||||
| 		for _, item := range infoArr { | ||||
| 			itemArr := strings.Split(item, "=") | ||||
| 			if len(itemArr) != 2 { | ||||
| 				continue | ||||
| 			} | ||||
| 			switch strings.ToLower(itemArr[0]) { | ||||
| 			case "calls": | ||||
| 				_ = convert.ConvertAssign(&info.Calls, itemArr[1]) | ||||
| 			case "usec": | ||||
| 				_ = convert.ConvertAssign(&info.TotalUsedTime, itemArr[1]) | ||||
| 			case "usec_per_call": | ||||
| 				_ = convert.ConvertAssign(&info.AvgUsedTime, itemArr[1]) | ||||
| 			case "rejected_calls": | ||||
| 				_ = convert.ConvertAssign(&info.RejectedCalls, itemArr[1]) | ||||
| 			case "failed_calls": | ||||
| 				_ = convert.ConvertAssign(&info.FailedCalls, itemArr[1]) | ||||
| 			} | ||||
| 		} | ||||
| 		info.SuccessCalls = info.Calls - info.FailedCalls - info.RejectedCalls | ||||
| 		cmdList = append(cmdList, info) | ||||
| 	} | ||||
| 	return cmdList, nil | ||||
| } | ||||
|  | ||||
| // GetKeyspace 获取 keyspace 信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 2:44 下午 2021/11/27 | ||||
| func GetKeyspace(client *redis.Client) ([]DB, error) { | ||||
| 	var result map[string]string | ||||
| 	if err := infoToStruct(client, "keyspace", &result); nil != err { | ||||
| 		return make([]DB, 0), err | ||||
| 	} | ||||
| 	dbList := make([]DB, 0) | ||||
| 	for dbName, item := range result { | ||||
| 		itemArr := strings.Split(item, ",") | ||||
| 		if len(itemArr) < 3 { | ||||
| 			continue | ||||
| 		} | ||||
| 		dbInfo := DB{ | ||||
| 			Name:    dbName, | ||||
| 			Keys:    0, | ||||
| 			Expires: 0, | ||||
| 			AvgTTL:  0, | ||||
| 		} | ||||
| 		for _, kv := range itemArr { | ||||
| 			kvArr := strings.Split(kv, "=") | ||||
| 			if len(kvArr) != 2 { | ||||
| 				continue | ||||
| 			} | ||||
| 			switch strings.ToLower(kvArr[0]) { | ||||
| 			case "keys": | ||||
| 				_ = convert.ConvertAssign(&dbInfo.Keys, kvArr[1]) | ||||
| 			case "expires": | ||||
| 				_ = convert.ConvertAssign(&dbInfo.Expires, kvArr[1]) | ||||
| 			case "avg_ttl": | ||||
| 				_ = convert.ConvertAssign(&dbInfo.AvgTTL, kvArr[1]) | ||||
| 			} | ||||
| 		} | ||||
| 		dbList = append(dbList, dbInfo) | ||||
| 	} | ||||
| 	return dbList, nil | ||||
| } | ||||
|  | ||||
| // GetCluster 获取 cluster 信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 3:30 下午 2021/11/27 | ||||
| func GetCluster(client *redis.Client) (*Cluster, error) { | ||||
| 	var result Cluster | ||||
| 	if err := infoToStruct(client, "cluster", &result); nil != err { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| // GetErrorStats 获取错误状态信息 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 3:35 下午 2021/11/27 | ||||
| func GetErrorStats(client *redis.Client) ([]Error, error) { | ||||
| 	var result map[string]string | ||||
| 	if err := infoToStruct(client, "errorstats", &result); nil != err { | ||||
| 		return make([]Error, 0), err | ||||
| 	} | ||||
|  | ||||
| 	errList := make([]Error, 0) | ||||
| 	for errType, item := range result { | ||||
| 		itemArr := strings.Split(item, "=") | ||||
| 		if len(itemArr) != 2 { | ||||
| 			continue | ||||
| 		} | ||||
| 		errInfo := Error{ | ||||
| 			Type:  strings.ReplaceAll(errType, "errorstat_", ""), | ||||
| 			Count: 0, | ||||
| 		} | ||||
| 		_ = convert.ConvertAssign(&errInfo.Count, itemArr[1]) | ||||
| 		errList = append(errList, errInfo) | ||||
| 	} | ||||
| 	return errList, nil | ||||
| } | ||||
|  | ||||
| // infoToStruct 读取到的数据,解析到结构体 | ||||
| // | ||||
| // Author : go_developer@163.com<白茶清欢> | ||||
| // | ||||
| // Date : 12:28 下午 2021/11/27 | ||||
| func infoToStruct(client *redis.Client, module string, receiver interface{}) error { | ||||
| 	data := client.Info(context.Background(), module).String() | ||||
| 	// yaml 文件规范要求, key: val , 注意 : 的后面有空格 | ||||
| 	data = strings.ReplaceAll(data, ":", ": ") | ||||
| 	return yml.Unmarshal([]byte(data), receiver) | ||||
| } | ||||
| @ -42,6 +42,12 @@ func TestCommandProxy(t *testing.T) { | ||||
| 		panic(err.Error()) | ||||
| 	} | ||||
| 	r, cmdErr := instance.CommandProxy(nil, "test_redis", "set", "command_proxy", "hello world") | ||||
| 	c, _ := instance.GetRedisClient("test_redis") | ||||
| 	fmt.Println(GetRedisServerInfo(c.Instance)) | ||||
| 	fmt.Println(GetCommandInfo(c.Instance)) | ||||
| 	fmt.Println(GetKeyspace(c.Instance)) | ||||
| 	fmt.Println(GetCluster(c.Instance)) | ||||
| 	fmt.Println(GetErrorStats(c.Instance)) | ||||
| 	assert.Nil(t, cmdErr, "命令执行成功") | ||||
| 	assert.Equal(t, "OK", fmt.Sprintf("%v", r)) | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user