kubernetes apiserver源码:如何实现委托模式

179 阅读3分钟

apiserver是如何实现委托模式的(delegation pattern)?

首先需要明白http.Handler是什么? http.Handler就是一个非常简单的接口,处理一个请求,返回一个响应。

// A Handler responds to an HTTP request.
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

pathHandler

pathHandler是一个小而精简的实现类,很容易分析:

// pathHandler is an http.Handler that will satisfy requests first by exact match, then by prefix,
// then by notFoundHandler
type pathHandler struct {
	// muxName is used for logging so you can trace requests through
	muxName string
        
        // 处理精准匹配,比如/apis/get/pods
	// pathToHandler is a map of exactly matching request to its handler
	pathToHandler map[string]http.Handler

        // 用于前缀匹配,在精准匹配没有匹配到,使用前缀匹配
	// this has to be sorted by most slashes then by length
	prefixHandlers []prefixHandler

        // 如果精准匹配和前缀匹配都失败,则使用这个http.Handler进行处理
	// notFoundHandler is the handler to use for satisfying requests with no other match
	notFoundHandler http.Handler
}
// prefixHandler holds the prefix it should match and the handler to use
type prefixHandler struct {
	// prefix is the prefix to test for a request match
	prefix string
	// handler is used to satisfy matching requests
	handler http.Handler
}

代理模式就是这样实现的, 比如pathToHandlerprefixHandlers是属于api-server的,假如匹配不到,就会转发到notFoundHandler,而notFoundHandler属于apiextensions-server.

// ServeHTTP makes it an http.Handler
func (h *pathHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        // 处理精准匹配,比如/apis/get/pods
	if exactHandler, ok := h.pathToHandler[r.URL.Path]; ok {
		klog.V(5).Infof("%v: %q satisfied by exact match", h.muxName, r.URL.Path)
		exactHandler.ServeHTTP(w, r)
		return
	}
        // 用于前缀匹配,在精准匹配没有匹配到,使用前缀匹配
	for _, prefixHandler := range h.prefixHandlers {
		if strings.HasPrefix(r.URL.Path, prefixHandler.prefix) {
			klog.V(5).Infof("%v: %q satisfied by prefix %v", h.muxName, r.URL.Path, prefixHandler.prefix)
			prefixHandler.handler.ServeHTTP(w, r)
			return
		}
	}

	klog.V(5).Infof("%v: %q satisfied by NotFoundHandler", h.muxName, r.URL.Path)
         // 如果精准匹配和前缀匹配都失败,则使用委托模式中的下一级的被代理者的http.Handler进行处理
	h.notFoundHandler.ServeHTTP(w, r)
}

PathRecorderMux

PathRecorderMux相当于并发安全版的pathHandler.


// PathRecorderMux wraps a mux object and records the registered exposedPaths.
type PathRecorderMux struct {
	// name is used for logging so you can trace requests through
	name string

	lock            sync.Mutex
	notFoundHandler http.Handler
	pathToHandler   map[string]http.Handler
	prefixToHandler map[string]http.Handler

	// mux stores a pathHandler and is used to handle the actual serving.
	// Turns out, we want to accept trailing slashes, BUT we don't care about handling
	// everything under them.  This does exactly matches only unless its explicitly requested to
	// do something different
	mux atomic.Value

	// exposedPaths is the list of paths that should be shown at /
	exposedPaths []string

	// pathStacks holds the stacks of all registered paths.  This allows us to show a more helpful message
	// before the "http: multiple registrations for %s" panic.
	pathStacks map[string]string
}

可以发现PathRecorderMuxpathHandler的字段大致是相同的,只是内部有一个互斥锁sync.Mutex和存储pathHandler的字段mux atomic.Value

实际上这两个对象相同字段的数据是一样的。

比如删除一个path:

// Unregister removes a path from the mux.
func (m *PathRecorderMux) Unregister(path string) {
        // 加锁
	m.lock.Lock()
        // 退出时,解锁
	defer m.lock.Unlock()
        
        // PathRecorderMux删除自身相关path
	delete(m.pathToHandler, path)
	delete(m.prefixToHandler, path)
	delete(m.pathStacks, path)
	for i := range m.exposedPaths {
		if m.exposedPaths[i] == path {
			m.exposedPaths = append(m.exposedPaths[:i], m.exposedPaths[i+1:]...)
			break
		}
	}
        // 同步内部的对象pathHandler
	m.refreshMuxLocked()
}

可以发现m.refreshMuxLocked() 只不过是创建一个pathHandler,将外部的数据拷贝到内部。

// refreshMuxLocked creates a new mux and must be called while locked.  Otherwise the view of handlers may
// not be consistent
func (m *PathRecorderMux) refreshMuxLocked() {
	newMux := &pathHandler{
		muxName:         m.name,
		pathToHandler:   map[string]http.Handler{},
		prefixHandlers:  []prefixHandler{},
		notFoundHandler: http.NotFoundHandler(),
	}
	if m.notFoundHandler != nil {
		newMux.notFoundHandler = m.notFoundHandler
	}
	for path, handler := range m.pathToHandler {
		newMux.pathToHandler[path] = handler
	}

	keys := sets.StringKeySet(m.prefixToHandler).List()
	sort.Sort(sort.Reverse(byPrefixPriority(keys)))
	for _, prefix := range keys {
		newMux.prefixHandlers = append(newMux.prefixHandlers, prefixHandler{
			prefix:  prefix,
			handler: m.prefixToHandler[prefix],
		})
	}

	m.mux.Store(newMux)
}

但是如果处理http请求的话,还是使用内部的pathHandler:

// ServeHTTP makes it an http.Handler
func (m *PathRecorderMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	m.mux.Load().(*pathHandler).ServeHTTP(w, r)
}

那为什么还要维持内外结构体维持两份相同的数据呢? 这个问题搞不懂。