diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7f511c5f..ba52da95 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -19,7 +19,7 @@ jobs:
   testing:
     strategy:
       matrix:
-        go-version: [1.12.x,1.13.x,1.14.x,1.15.x,1.16.x,1.17.x,1.18.x,1.19.x,1.20.x,1.21.x]
+        go-version: [1.16.x,1.17.x,1.18.x,1.19.x,1.20.x,1.21.x]
         platform: [ubuntu-latest]
     runs-on: ${{ matrix.platform }}
     steps:
@@ -45,10 +45,10 @@ jobs:
     name: codecov
     runs-on: ubuntu-latest
     steps:
-      - name: Set up Go 1.15
+      - name: Set up Go 1.16
         uses: actions/setup-go@v3
         with:
-          go-version: 1.15.x
+          go-version: 1.16.x
         id: go
 
       - name: Checkout code
diff --git a/.gitignore b/.gitignore
index b3c37487..f203523c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,7 @@ zj*
 # gdb
 *.gdb_history
 __*
+*.log
 
 # motan-go
 *.pid
@@ -40,4 +41,4 @@ main/magent*
 log/log.test*
 go.sum
 agent_runtime
-test/
+test/
\ No newline at end of file
diff --git a/agent.go b/agent.go
index 9c9052ac..013f08f6 100644
--- a/agent.go
+++ b/agent.go
@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"github.com/weibocom/motan-go/endpoint"
 	vlog "github.com/weibocom/motan-go/log"
+	"github.com/weibocom/motan-go/meta"
+	"github.com/weibocom/motan-go/provider"
 	"gopkg.in/yaml.v2"
 	"io/ioutil"
 	"net"
@@ -193,6 +195,8 @@ func (a *Agent) StartMotanAgentFromConfig(config *cfg.Config) {
 		return
 	}
 	fmt.Println("init agent context success.")
+	// initialize meta package
+	meta.Initialize(a.Context)
 	a.initParam()
 	a.SetSanpshotConf()
 	a.initAgentURL()
@@ -225,7 +229,7 @@ func (a *Agent) StartMotanAgentFromConfig(config *cfg.Config) {
 
 func (a *Agent) startRegistryFailback() {
 	vlog.Infoln("start agent failback")
-	ticker := time.NewTicker(registry.DefaultFailbackInterval * time.Millisecond)
+	ticker := time.NewTicker(time.Duration(registry.GetFailbackInterval()) * time.Millisecond)
 	defer ticker.Stop()
 	for range ticker.C {
 		a.registryLock.Lock()
@@ -265,22 +269,8 @@ func (a *Agent) GetRegistryStatus() []map[string]*motan.RegistryStatus {
 }
 
 func (a *Agent) registerStatusSampler() {
-	metrics.RegisterStatusSampleFunc("memory", func() int64 {
-		p, _ := process.NewProcess(int32(os.Getpid()))
-		memInfo, err := p.MemoryInfo()
-		if err != nil {
-			return 0
-		}
-		return int64(memInfo.RSS >> 20)
-	})
-	metrics.RegisterStatusSampleFunc("cpu", func() int64 {
-		p, _ := process.NewProcess(int32(os.Getpid()))
-		cpuPercent, err := p.CPUPercent()
-		if err != nil {
-			return 0
-		}
-		return int64(cpuPercent)
-	})
+	metrics.RegisterStatusSampleFunc("memory", GetRssMemory)
+	metrics.RegisterStatusSampleFunc("cpu", GetCpuPercent)
 	metrics.RegisterStatusSampleFunc("goroutine_count", func() int64 {
 		return int64(runtime.NumGoroutine())
 	})
@@ -289,6 +279,24 @@ func (a *Agent) registerStatusSampler() {
 	})
 }
 
+func GetRssMemory() int64 {
+	p, _ := process.NewProcess(int32(os.Getpid()))
+	memInfo, err := p.MemoryInfo()
+	if err != nil {
+		return 0
+	}
+	return int64(memInfo.RSS >> 20)
+}
+
+func GetCpuPercent() int64 {
+	p, _ := process.NewProcess(int32(os.Getpid()))
+	cpuPercent, err := p.CPUPercent()
+	if err != nil {
+		return 0
+	}
+	return int64(cpuPercent)
+}
+
 func (a *Agent) initStatus() {
 	if a.recover {
 		a.recoverStatus()
@@ -371,7 +379,7 @@ func (a *Agent) initParam() {
 		port = defaultPort
 	}
 
-	mPort := *motan.Mport
+	mPort := motan.GetMport()
 	if mPort == 0 {
 		if envMPort, ok := os.LookupEnv("mport"); ok {
 			if envMPortInt, err := strconv.Atoi(envMPort); err == nil {
@@ -629,6 +637,7 @@ func (a *Agent) initAgentURL() {
 	} else {
 		agentURL.Parameters[motan.ApplicationKey] = agentURL.Group
 	}
+	motan.SetApplication(agentURL.Parameters[motan.ApplicationKey])
 	if agentURL.Group == "" {
 		agentURL.Group = defaultAgentGroup
 		agentURL.Parameters[motan.ApplicationKey] = defaultAgentGroup
@@ -652,8 +661,9 @@ func (a *Agent) initAgentURL() {
 func (a *Agent) startAgent() {
 	url := a.agentURL.Copy()
 	url.Port = a.port
+	url.Protocol = mserver.Motan2
 	handler := &agentMessageHandler{agent: a}
-	server := &mserver.MotanServer{URL: url}
+	server := defaultExtFactory.GetServer(url)
 	server.SetMessageHandler(handler)
 	vlog.Infof("Motan agent is started. port:%d", a.port)
 	fmt.Println("Motan agent start.")
@@ -673,7 +683,7 @@ func (a *Agent) registerAgent() {
 		if agentURL.Host == "" {
 			agentURL.Host = motan.GetLocalIP()
 		}
-		if registryURL, regexit := a.Context.RegistryURLs[reg]; regexit {
+		if registryURL, regExist := a.Context.RegistryURLs[reg]; regExist {
 			registry := a.extFactory.GetRegistry(registryURL)
 			if registry != nil {
 				vlog.Infof("agent register in registry:%s, agent url:%s", registry.GetURL().GetIdentity(), agentURL.GetIdentity())
@@ -697,6 +707,16 @@ type agentMessageHandler struct {
 	agent *Agent
 }
 
+func (a *agentMessageHandler) GetName() string {
+	return "agentMessageHandler"
+}
+
+func (a *agentMessageHandler) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	info[motan.RuntimeMessageHandlerTypeKey] = a.GetName()
+	return info
+}
+
 func (a *agentMessageHandler) clusterCall(request motan.Request, ck string, motanCluster *cluster.MotanCluster) (res motan.Response) {
 	// fill default request info
 	fillDefaultReqInfo(request, motanCluster.GetURL())
@@ -747,8 +767,8 @@ func (a *agentMessageHandler) httpCall(request motan.Request, ck string, httpClu
 	}
 	httpRequest := fasthttp.AcquireRequest()
 	httpResponse := fasthttp.AcquireResponse()
+	// do not release http response
 	defer fasthttp.ReleaseRequest(httpRequest)
-	defer fasthttp.ReleaseResponse(httpResponse)
 	httpRequest.Header.Del("Host")
 	httpRequest.SetHost(originalService)
 	httpRequest.URI().SetPath(request.GetMethod())
@@ -886,6 +906,9 @@ func (a *Agent) unavailableAllServices() {
 func (a *Agent) doExportService(url *motan.URL) {
 	a.svcLock.Lock()
 	defer a.svcLock.Unlock()
+	if _, ok := a.serviceExporters.Load(url.GetIdentityWithRegistry()); ok {
+		return
+	}
 
 	globalContext := a.Context
 	exporter := &mserver.DefaultExporter{}
@@ -932,11 +955,38 @@ func (a *Agent) doExportService(url *motan.URL) {
 }
 
 type serverAgentMessageHandler struct {
-	providers *motan.CopyOnWriteMap
+	providers          *motan.CopyOnWriteMap
+	frameworkProviders *motan.CopyOnWriteMap
+}
+
+func (sa *serverAgentMessageHandler) GetName() string {
+	return "serverAgentMessageHandler"
+}
+
+func (sa *serverAgentMessageHandler) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	info[motan.RuntimeMessageHandlerTypeKey] = sa.GetName()
+	providersInfo := map[string]interface{}{}
+	sa.providers.Range(func(k, v interface{}) bool {
+		provider, ok := v.(motan.Provider)
+		if !ok {
+			return true
+		}
+		providersInfo[k.(string)] = provider.GetRuntimeInfo()
+		return true
+	})
+	info[motan.RuntimeProvidersKey] = providersInfo
+	return info
 }
 
 func (sa *serverAgentMessageHandler) Initialize() {
 	sa.providers = motan.NewCopyOnWriteMap()
+	sa.frameworkProviders = motan.NewCopyOnWriteMap()
+	sa.initFrameworkServiceProvider()
+}
+
+func (sa *serverAgentMessageHandler) initFrameworkServiceProvider() {
+	sa.frameworkProviders.Store(meta.MetaServiceName, &provider.MetaProvider{})
 }
 
 func getServiceKey(group, path string) string {
@@ -954,15 +1004,22 @@ func (sa *serverAgentMessageHandler) Call(request motan.Request) (res motan.Resp
 		group = request.GetAttachment(motan.GroupKey)
 	}
 	serviceKey := getServiceKey(group, request.GetServiceName())
+	if mfs := request.GetAttachment(mpro.MFrameworkService); mfs != "" {
+		if fp, ok := sa.frameworkProviders.Load(request.GetServiceName()); ok {
+			return fp.(motan.Provider).Call(request)
+		}
+		//throw specific exception to avoid triggering forced fusing on the client side。
+		return motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{ErrCode: 501, ErrMsg: motan.ServiceNotSupport, ErrType: motan.ServiceException})
+	}
 	if p := sa.providers.LoadOrNil(serviceKey); p != nil {
 		p := p.(motan.Provider)
 		res = p.Call(request)
 		res.GetRPCContext(true).GzipSize = int(p.GetURL().GetIntValue(motan.GzipSizeKey, 0))
 		return res
 	}
-	vlog.Errorf("not found provider for %s", motan.GetReqInfo(request))
+	vlog.Errorf("%s%s, %s", motan.ProviderNotExistPrefix, serviceKey, motan.GetReqInfo(request))
 	atomic.AddInt64(&notFoundProviderCount, 1)
-	return motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{ErrCode: 500, ErrMsg: "not found provider for " + serviceKey, ErrType: motan.ServiceException})
+	return motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{ErrCode: motan.EProviderNotExist, ErrMsg: motan.ProviderNotExistPrefix + serviceKey, ErrType: motan.ServiceException})
 }
 
 func (sa *serverAgentMessageHandler) AddProvider(p motan.Provider) error {
@@ -1119,13 +1176,12 @@ func (a *Agent) startMServer() {
 			for kk, vv := range v {
 				handlers[kk] = vv
 			}
-
 		}
 	}
 	for k, v := range handlers {
 		a.mhandle(k, v)
 	}
-
+	var mPort int
 	var managementListener net.Listener
 	if managementUnixSockAddr := a.agentURL.GetParam(motan.ManagementUnixSockKey, ""); managementUnixSockAddr != "" {
 		listener, err := motan.ListenUnixSock(managementUnixSockAddr)
@@ -1159,19 +1215,21 @@ func (a *Agent) startMServer() {
 			managementListener = motan.TCPKeepAliveListener{TCPListener: listener.(*net.TCPListener)}
 			break
 		}
+		mPort = a.mport
 		if managementListener == nil {
 			vlog.Warningf("start management server failed for port range %s", startAndPortStr)
 			return
 		}
 	} else {
 		listener, err := net.Listen("tcp", ":"+strconv.Itoa(a.mport))
+		mPort = a.mport
 		if err != nil {
 			vlog.Infof("listen manage port %d failed:%s", a.mport, err.Error())
 			return
 		}
 		managementListener = motan.TCPKeepAliveListener{TCPListener: listener.(*net.TCPListener)}
 	}
-
+	motan.SetMport(mPort)
 	vlog.Infof("start listen manage for address: %s", managementListener.Addr().String())
 	err := http.Serve(managementListener, nil)
 	if err != nil {
@@ -1191,8 +1249,10 @@ func (a *Agent) mhandle(k string, h http.Handler) {
 		setAgentLock.Unlock()
 	}
 	http.HandleFunc(k, func(w http.ResponseWriter, r *http.Request) {
+		vlog.Infof("mport request: %s, address: %s", r.URL.Path, r.RemoteAddr)
 		if !PermissionCheck(r) {
 			w.Write([]byte("need permission!"))
+			vlog.Warningf("mport request no permission: %s, address: %s", r.URL.Path, r.RemoteAddr)
 			return
 		}
 		defer func() {
diff --git a/agent_test.go b/agent_test.go
index d21166e7..1546a367 100644
--- a/agent_test.go
+++ b/agent_test.go
@@ -48,13 +48,13 @@ var (
 )
 
 func Test_unixClientCall1(t *testing.T) {
-	t.Parallel()
+	//t.Parallel()
 	startServer(t, "helloService", 22991)
 	time.Sleep(time.Second * 3)
 	// start client mesh
 	ext := GetDefaultExtFactory()
 	os.Remove("agent.sock")
-	config, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
+	clientAgentConfig, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
   mport: 12500
   port: 13821
@@ -79,9 +79,10 @@ motan-refer:
       registry: direct
       asyncInitConnection: false
       serialization: breeze`)))
-	agent := NewAgent(ext)
-	go agent.StartMotanAgentFromConfig(config)
+	targetAgent := NewAgent(ext)
+	go targetAgent.StartMotanAgentFromConfig(clientAgentConfig)
 	time.Sleep(time.Second * 3)
+	core.SetMport(0)
 	c1 := NewMeshClient()
 	c1.SetAddress("unix://./agent.sock")
 	c1.Initialize()
@@ -93,7 +94,7 @@ motan-refer:
 }
 
 func Test_envHandler(t *testing.T) {
-	t.Parallel()
+	//t.Parallel()
 	time.Sleep(time.Second * 3)
 	// start client mesh
 	ext := GetDefaultExtFactory()
@@ -118,13 +119,14 @@ motan-refer:
       registry: direct
       asyncInitConnection: false
       serialization: breeze`)))
-	agent := NewAgent(ext)
-	agent.RegisterEnvHandlers("testHandler", map[string]http.Handler{
+	clientAgent := NewAgent(ext)
+	clientAgent.RegisterEnvHandlers("testHandler", map[string]http.Handler{
 		"/test/test": testHandler(),
 	})
 	os.Setenv(core.HandlerEnvironmentName, "testHandler")
-	go agent.StartMotanAgentFromConfig(config)
+	go clientAgent.StartMotanAgentFromConfig(config)
 	time.Sleep(time.Second * 3)
+	core.SetMport(0)
 	client := http.Client{
 		Timeout: time.Second,
 	}
@@ -144,13 +146,13 @@ func testHandler() http.HandlerFunc {
 }
 
 func Test_unixClientCall2(t *testing.T) {
-	t.Parallel()
+	//t.Parallel()
 	startServer(t, "helloService", 22992)
 	time.Sleep(time.Second * 3)
 	// start client mesh
 	ext := GetDefaultExtFactory()
 	os.Remove("agent2.sock")
-	config1, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
+	clientAgentconfig1, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
   mport: 12501
   port: 12921
@@ -171,17 +173,18 @@ motan-refer:
     test-refer:
       group: hello
       path: helloService
-      protocol: motanV1Compatible
+      protocol: motan2
       registry: direct
       serialization: breeze
       asyncInitConnection: false
 `)))
-	agent := NewAgent(ext)
-	go agent.StartMotanAgentFromConfig(config1)
+	clientAgent := NewAgent(ext)
+	go clientAgent.StartMotanAgentFromConfig(clientAgentconfig1)
 	time.Sleep(time.Second * 3)
+	core.SetMport(0)
 
 	ext1 := GetDefaultExtFactory()
-	cfg, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
+	clientConfig, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-client:
   log_dir: stdout
   application: client-test
@@ -193,13 +196,13 @@ motan-refer:
   test-refer:
     registry: local
     serialization: breeze
-    protocol: motanV1Compatible
+    protocol: motan2
     group: hello
     path: helloService
     requestTimeout: 3000
     asyncInitConnection: false
 `)))
-	mccontext := NewClientContextFromConfig(cfg)
+	mccontext := NewClientContextFromConfig(clientConfig)
 	mccontext.Start(ext1)
 	mclient := mccontext.GetClient("test-refer")
 	var reply string
@@ -207,6 +210,7 @@ motan-refer:
 	assert.Nil(t, err)
 	assert.Equal(t, "Hello jack from motan server", reply)
 }
+
 func TestMain(m *testing.M) {
 	core.RegistLocalProvider("LocalTestService", &LocalTestServiceProvider{})
 	cfgFile := filepath.Join("testdata", "agent.yaml")
@@ -224,6 +228,8 @@ func TestMain(m *testing.M) {
 		agent.ConfigFile = cfgFile
 		agent.StartMotanAgent()
 	}()
+	time.Sleep(time.Second * 3)
+	core.SetMport(0)
 	proxyURL, _ := url.Parse("http://localhost:9983")
 	proxyClient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
 	time.Sleep(1 * time.Second)
@@ -248,8 +254,8 @@ func Test_initParam(t *testing.T) {
 	assert := assert.New(t)
 	conf, err := config.NewConfigFromFile(filepath.Join("testdata", "agent.yaml"))
 	assert.Nil(err)
-	a := NewAgent(nil)
-	a.Context = &core.Context{Config: conf}
+	testAgent := NewAgent(nil)
+	testAgent.Context = &core.Context{Config: conf}
 	logFilterCallerFalseConfig, err := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
   log_filter_caller: false
@@ -258,7 +264,7 @@ motan-agent:
 	section, err := conf.GetSection("motan-agent")
 	assert.Nil(err)
 	assert.Equal(false, section["log_filter_caller"].(bool))
-	a.initParam()
+	testAgent.initParam()
 
 	logFilterCallerTrueConfig, err := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
@@ -269,7 +275,7 @@ motan-agent:
 	section, err = conf.GetSection("motan-agent")
 	assert.Nil(err)
 	assert.Equal(true, section["log_filter_caller"].(bool))
-	a.initParam()
+	testAgent.initParam()
 
 	logDirConfig, err := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
@@ -279,12 +285,12 @@ motan-agent:
 	conf.Merge(logDirConfig)
 	section, err = conf.GetSection("motan-agent")
 	assert.Nil(err)
-	a.initParam()
-	assert.Equal(a.logdir, "./test/abcd")
+	testAgent.initParam()
+	assert.Equal(testAgent.logdir, "./test/abcd")
 	os.Args = append(os.Args, "-log_dir", "./test/cdef")
 	_ = flag.Set("log_dir", "./test/cdef")
-	a.initParam()
-	assert.Equal(a.logdir, "./test/cdef")
+	testAgent.initParam()
+	assert.Equal(testAgent.logdir, "./test/cdef")
 	// test export log dir
 	assert.Equal(vlog.GetLogDir(), "./test/cdef")
 
@@ -296,8 +302,8 @@ motan-agent:
 	conf.Merge(mportConfig)
 	section, err = conf.GetSection("motan-agent")
 	assert.Nil(err)
-	a.initParam()
-	assert.Equal(a.mport, 8003)
+	testAgent.initParam()
+	assert.Equal(testAgent.mport, 8003)
 
 	mportConfigENV, err := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
@@ -308,8 +314,8 @@ motan-agent:
 	section, err = conf.GetSection("motan-agent")
 	assert.Nil(err)
 	err = os.Setenv("mport", "8006")
-	a.initParam()
-	assert.Equal(a.mport, 8006)
+	testAgent.initParam()
+	assert.Equal(testAgent.mport, 8006)
 
 	mportConfigENVParam, err := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
@@ -321,8 +327,8 @@ motan-agent:
 	assert.Nil(err)
 	//err = os.Setenv("mport", "8006")
 	//_ = flag.Set("mport", "8007")
-	//a.initParam()
-	//assert.Equal(a.mport, 8007)
+	//testAgent.initParam()
+	//assert.Equal(testAgent.mport, 8007)
 	os.Unsetenv("mport")
 }
 
@@ -403,7 +409,6 @@ func TestRpcToHTTPProxy(t *testing.T) {
 	}
 	close(requests)
 	wg.Wait()
-
 }
 
 func TestLocalEndpoint(t *testing.T) {
@@ -418,13 +423,13 @@ func TestAgent_RuntimeDir(t *testing.T) {
 
 func TestAgent_InitCall(t *testing.T) {
 	//init
-	agent := NewAgent(nil)
-	agent.agentURL = &core.URL{Parameters: make(map[string]string)}
+	targetAgent := NewAgent(nil)
+	targetAgent.agentURL = &core.URL{Parameters: make(map[string]string)}
 	urlTest := &core.URL{Parameters: make(map[string]string)}
 	urlTest.Group = "test1"
 	urlTest.Parameters[core.AsyncInitConnection] = "false"
-	agent.initCluster(urlTest)
-	agentHandler := &agentMessageHandler{agent: agent}
+	targetAgent.initCluster(urlTest)
+	agentHandler := &agentMessageHandler{agent: targetAgent}
 
 	for _, v := range []*core.URL{
 		{Parameters: map[string]string{core.VersionKey: ""}, Path: "test3", Group: "", Protocol: ""},
@@ -438,11 +443,11 @@ func TestAgent_InitCall(t *testing.T) {
 		{Parameters: map[string]string{core.VersionKey: "1.3"}, Path: "test", Group: "g1", Protocol: "http"},
 		{Parameters: map[string]string{core.VersionKey: "1.3"}, Path: "test0", Group: "g0", Protocol: "http"},
 	} {
-		agent.initCluster(v)
+		targetAgent.initCluster(v)
 	}
 
 	//test init cluster with one path and one groups in clusterMap
-	temp := agent.clusterMap.LoadOrNil(getClusterKey("test1", "1.0", "", ""))
+	temp := targetAgent.clusterMap.LoadOrNil(getClusterKey("test1", "1.0", "", ""))
 	assert.NotNil(t, temp, "init cluster with one path and two groups in clusterMap fail")
 
 	//test agentHandler call with group
@@ -502,8 +507,8 @@ func TestAgent_InitCall(t *testing.T) {
 	// wait ha
 	time.Sleep(time.Second * 1)
 
-	agent.reloadClusters(ctx)
-	assert.Equal(t, agent.serviceMap.Len(), 1, "hot-load serviceMap helloService2 length error")
+	targetAgent.reloadClusters(ctx)
+	assert.Equal(t, targetAgent.serviceMap.Len(), 1, "hot-load serviceMap helloService2 length error")
 
 	request = newRequest("helloService2", "hello", "Ray")
 	motanResponse := agentHandler.Call(request)
@@ -521,14 +526,14 @@ func TestAgent_InitCall(t *testing.T) {
 	dynamicURLs := map[string]*core.URL{
 		"test6": {Parameters: map[string]string{core.VersionKey: ""}, Path: "test6", Group: "g1", Protocol: ""},
 	}
-	agent.serviceMap.Store("test6", []serviceMapItem{
+	targetAgent.serviceMap.Store("test6", []serviceMapItem{
 		{url: dynamicURLs["test6"], cluster: nil},
 	})
-	agent.configurer = NewDynamicConfigurer(agent)
-	agent.configurer.subscribeNodes = dynamicURLs
+	targetAgent.configurer = NewDynamicConfigurer(targetAgent)
+	targetAgent.configurer.subscribeNodes = dynamicURLs
 	ctx.RefersURLs = reloadUrls
-	agent.reloadClusters(ctx)
-	assert.Equal(t, agent.serviceMap.Len(), 3, "hot-load serviceMap except length error")
+	targetAgent.reloadClusters(ctx)
+	assert.Equal(t, targetAgent.serviceMap.Len(), 3, "hot-load serviceMap except length error")
 
 	for _, v := range []struct {
 		service  string
@@ -554,10 +559,10 @@ func TestNotFoundProvider(t *testing.T) {
 	notFoundService := "notFoundService"
 	request := meshClient.BuildRequest(notFoundService, "test", []interface{}{})
 	epUrl := &core.URL{
-		Protocol: "motanV1Compatible",
+		Protocol: endpoint.Motan2,
 		Host:     "127.0.0.1",
 		Port:     9982,
-		Path:     "notFoundService",
+		Path:     notFoundService,
 		Group:    "test",
 		Parameters: map[string]string{
 			core.TimeOutKey:              "3000",
@@ -575,7 +580,7 @@ func TestNotFoundProvider(t *testing.T) {
 	ep.SetProxy(true)
 	resp := ep.Call(request)
 	assert.NotNil(t, resp.GetException())
-	assert.Contains(t, "not found provider for test_notFoundService", resp.GetException().ErrMsg)
+	assert.Contains(t, core.ProviderNotExistPrefix+"test_notFoundService", resp.GetException().ErrMsg)
 	assert.Equal(t, int64(1), atomic.LoadInt64(&notFoundProviderCount))
 	ep.Destroy()
 }
@@ -595,6 +600,10 @@ type LocalTestServiceProvider struct {
 	url *core.URL
 }
 
+func (l *LocalTestServiceProvider) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{}
+}
+
 func (l *LocalTestServiceProvider) SetService(s interface{}) {
 }
 
@@ -638,7 +647,7 @@ func (l *LocalTestServiceProvider) GetPath() string {
 }
 
 func Test_unixHTTPClientCall(t *testing.T) {
-	t.Parallel()
+	//t.Parallel()
 	go func() {
 		http.HandleFunc("/unixclient", func(w http.ResponseWriter, r *http.Request) {
 			w.Write([]byte("okay"))
@@ -656,7 +665,7 @@ func Test_unixHTTPClientCall(t *testing.T) {
 	}()
 	// start unix server mesh
 	ext := GetDefaultExtFactory()
-	config1, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
+	serverAgentConfig1, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
   port: 12822
   mport: 12504
@@ -682,9 +691,11 @@ motan-service:
       enableRewrite: false
       export: motan2:23282
 `)))
-	agent := NewAgent(ext)
-	go agent.StartMotanAgentFromConfig(config1)
+	serverAgent := NewAgent(ext)
+	go serverAgent.StartMotanAgentFromConfig(serverAgentConfig1)
 	time.Sleep(time.Second * 3)
+	core.SetMport(0)
+
 	c1 := NewMeshClient()
 	c1.SetAddress("127.0.0.1:23282")
 	c1.Initialize()
@@ -695,15 +706,16 @@ motan-service:
 	assert.Nil(t, resp.GetException())
 	assert.Equal(t, "okay", string(reply))
 }
+
 func Test_unixRPCClientCall(t *testing.T) {
-	t.Parallel()
+	//t.Parallel()
 	os.Remove("server.sock")
 	startServer(t, "helloService", 0, "server.sock")
 	time.Sleep(time.Second * 3)
 	// start client mesh
 	// start unix server mesh
 	ext := GetDefaultExtFactory()
-	config1, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
+	serverMeshConfig, _ := config.NewConfigFromReader(bytes.NewReader([]byte(`
 motan-agent:
   port: 12821
   mport: 12503
@@ -729,9 +741,11 @@ motan-service:
       proxy.host: unix://./server.sock
       export: motan2:12281
 `)))
-	agent := NewAgent(ext)
-	go agent.StartMotanAgentFromConfig(config1)
+	serverAgent := NewAgent(ext)
+	go serverAgent.StartMotanAgentFromConfig(serverMeshConfig)
 	time.Sleep(time.Second * 3)
+	core.SetMport(0)
+
 	c1 := NewMeshClient()
 	c1.SetAddress("127.0.0.1:12281")
 	c1.Initialize()
@@ -740,10 +754,16 @@ motan-service:
 	resp := c1.BaseCall(req, &reply)
 	assert.Nil(t, resp.GetException())
 	assert.Equal(t, "Hello jack from motan server", string(reply))
+
+	// test not found provider endpoint circuit breaker and runtime info
+	reqNotFound := c1.BuildRequestWithGroup("helloServiceNotFound", "Hello", []interface{}{"jack"}, "helloNotFound")
+	respNotFound := c1.BaseCall(reqNotFound, &reply)
+	assert.NotNil(t, respNotFound.GetException())
+	assert.Equal(t, core.ProviderNotExistPrefix+"helloNotFound_helloServiceNotFound", respNotFound.GetException().ErrMsg)
 }
 
 func Test_changeDefaultMotanEpAsyncInit(t *testing.T) {
-	template := `
+	serviceAgentConfigTemplate := `
 motan-agent:
   port: 12821
   mport: 12503
@@ -770,23 +790,27 @@ motan-service:
       export: motan2:12281
 `
 	ext := GetDefaultExtFactory()
-	config1, _ := config.NewConfigFromReader(bytes.NewReader([]byte(fmt.Sprintf(template, "false"))))
-	a := NewAgent(ext)
-	a.Context = &core.Context{Config: config1}
-	a.Context.Initialize()
-	a.initParam()
+	config1, _ := config.NewConfigFromReader(bytes.NewReader([]byte(fmt.Sprintf(serviceAgentConfigTemplate, "false"))))
+	serverAgent := NewAgent(ext)
+	serverAgent.Context = &core.Context{Config: config1}
+	serverAgent.Context.Initialize()
+	serverAgent.initParam()
 	assert.Equal(t, endpoint.GetDefaultMotanEPAsynInit(), false)
 
-	config2, _ := config.NewConfigFromReader(bytes.NewReader([]byte(fmt.Sprintf(template, "true"))))
-	a = NewAgent(ext)
-	a.Context = &core.Context{Config: config2}
-	a.Context.Initialize()
-	a.initParam()
+	config2, _ := config.NewConfigFromReader(bytes.NewReader([]byte(fmt.Sprintf(serviceAgentConfigTemplate, "true"))))
+	serverAgent = NewAgent(ext)
+	serverAgent.Context = &core.Context{Config: config2}
+	serverAgent.Context.Initialize()
+	serverAgent.initParam()
 	assert.Equal(t, endpoint.GetDefaultMotanEPAsynInit(), true)
 }
 
 func Test_agentRegistryFailback(t *testing.T) {
-	template := `
+	currentFailbackInterval := registry.GetFailbackInterval()
+	newInterval := uint32(5000)
+	registry.SetFailbackInterval(newInterval)
+
+	serverAgentTemplate := `
 motan-agent:
   port: 12829
   mport: 12604
@@ -814,14 +838,15 @@ motan-service:
 	extFactory.RegistExtRegistry("test-registry", func(url *core.URL) core.Registry {
 		return &testRegistry{url: url}
 	})
-	config1, err := config.NewConfigFromReader(bytes.NewReader([]byte(template)))
+	config1, err := config.NewConfigFromReader(bytes.NewReader([]byte(serverAgentTemplate)))
 	assert.Nil(t, err)
-	agent := NewAgent(extFactory)
-	go agent.StartMotanAgentFromConfig(config1)
-	time.Sleep(time.Second * 10)
+	serverAgent := NewAgent(extFactory)
+	go serverAgent.StartMotanAgentFromConfig(config1)
+	time.Sleep(time.Second * 3)
+	core.SetMport(0)
 
 	setRegistryFailSwitcher(true)
-	m := agent.GetRegistryStatus()
+	m := serverAgent.GetRegistryStatus()
 	assert.Equal(t, len(m), 1)
 	for _, mm := range m[0] {
 		if mm.Service.Path == "helloService" {
@@ -830,8 +855,8 @@ motan-service:
 	}
 	agentStatus := getCurAgentStatus(12604)
 	assert.Equal(t, agentStatus, core.NotRegister)
-	agent.SetAllServicesAvailable()
-	m = agent.GetRegistryStatus()
+	serverAgent.SetAllServicesAvailable()
+	m = serverAgent.GetRegistryStatus()
 	for _, mm := range m[0] {
 		if mm.Service.Path == "helloService" {
 			assert.Equal(t, mm.Status, core.RegisterFailed)
@@ -840,8 +865,8 @@ motan-service:
 	agentStatus = getCurAgentStatus(12604)
 	assert.Equal(t, agentStatus, core.RegisterFailed)
 	setRegistryFailSwitcher(false)
-	time.Sleep(registry.DefaultFailbackInterval * time.Millisecond)
-	m = agent.GetRegistryStatus()
+	time.Sleep(time.Duration(newInterval) * time.Millisecond)
+	m = serverAgent.GetRegistryStatus()
 	assert.Equal(t, len(m), 1)
 	for _, mm := range m[0] {
 		if mm.Service.Path == "helloService" {
@@ -851,8 +876,8 @@ motan-service:
 	agentStatus = getCurAgentStatus(12604)
 	assert.Equal(t, agentStatus, core.RegisterSuccess)
 	setRegistryFailSwitcher(true)
-	agent.SetAllServicesUnavailable()
-	m = agent.GetRegistryStatus()
+	serverAgent.SetAllServicesUnavailable()
+	m = serverAgent.GetRegistryStatus()
 	assert.Equal(t, len(m), 1)
 	for _, mm := range m[0] {
 		if mm.Service.Path == "helloService" {
@@ -862,8 +887,8 @@ motan-service:
 	agentStatus = getCurAgentStatus(12604)
 	assert.Equal(t, agentStatus, core.UnregisterFailed)
 	setRegistryFailSwitcher(false)
-	time.Sleep(registry.DefaultFailbackInterval * time.Millisecond)
-	m = agent.GetRegistryStatus()
+	time.Sleep(time.Duration(newInterval) * time.Millisecond)
+	m = serverAgent.GetRegistryStatus()
 	assert.Equal(t, len(m), 1)
 	for _, mm := range m[0] {
 		if mm.Service.Path == "helloService" {
@@ -872,6 +897,7 @@ motan-service:
 	}
 	agentStatus = getCurAgentStatus(12604)
 	assert.Equal(t, agentStatus, core.UnregisterSuccess)
+	registry.SetFailbackInterval(currentFailbackInterval)
 }
 
 type testRegistry struct {
@@ -880,6 +906,10 @@ type testRegistry struct {
 	registeredServices  map[string]*core.URL
 }
 
+func (t *testRegistry) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{}
+}
+
 func (t *testRegistry) Initialize() {
 	t.registeredServices = make(map[string]*core.URL)
 	t.namingServiceStatus = core.NewCopyOnWriteMap()
@@ -915,7 +945,6 @@ func (t *testRegistry) Register(serverURL *core.URL) {
 		Registry: t,
 		IsCheck:  registry.IsCheck(serverURL),
 	})
-
 }
 
 func (t *testRegistry) UnRegister(serverURL *core.URL) {
@@ -991,7 +1020,6 @@ func setRegistryFailSwitcher(b bool) {
 	} else {
 		atomic.StoreInt64(&testRegistryFailSwitcher, 0)
 	}
-
 }
 
 func getRegistryFailSwitcher() bool {
@@ -1024,3 +1052,30 @@ func getCurAgentStatus(port int64) string {
 	}
 	return res.Status
 }
+
+func TestRuntimeHandler(t *testing.T) {
+	resp, err := http.Get("http://127.0.0.1:8002/runtime/info")
+	assert.Nil(t, err)
+	var runtimeInfo map[string]interface{}
+	bodyBytes, err := ioutil.ReadAll(resp.Body)
+	assert.Nil(t, err)
+	err = resp.Body.Close()
+	assert.Nil(t, err)
+	err = json.Unmarshal(bodyBytes, &runtimeInfo)
+	assert.Nil(t, err)
+
+	for _, s := range []string{
+		core.RuntimeInstanceTypeKey,
+		core.RuntimeExportersKey,
+		core.RuntimeClustersKey,
+		core.RuntimeHttpClustersKey,
+		core.RuntimeExtensionFactoryKey,
+		core.RuntimeServersKey,
+		core.RuntimeBasicKey,
+	} {
+		info, ok := runtimeInfo[s]
+		assert.True(t, ok)
+		assert.NotNil(t, info)
+		t.Logf("key: %s", s)
+	}
+}
diff --git a/client.go b/client.go
index b00a3c59..e2000de9 100644
--- a/client.go
+++ b/client.go
@@ -4,13 +4,12 @@ import (
 	"errors"
 	"flag"
 	"fmt"
-	"github.com/weibocom/motan-go/config"
-	vlog "github.com/weibocom/motan-go/log"
-	"sync"
-
 	"github.com/weibocom/motan-go/cluster"
+	"github.com/weibocom/motan-go/config"
 	motan "github.com/weibocom/motan-go/core"
+	vlog "github.com/weibocom/motan-go/log"
 	mpro "github.com/weibocom/motan-go/protocol"
+	"sync"
 )
 
 var (
@@ -72,13 +71,14 @@ func (c *Client) BaseGo(req motan.Request, reply interface{}, done chan *motan.A
 	rc := req.GetRPCContext(true)
 	rc.ExtFactory = c.extFactory
 	rc.Result = result
-	rc.AsyncCall = true
-	rc.Result.Reply = reply
-	res := c.cluster.Call(req)
-	if res.GetException() != nil {
-		result.Error = errors.New(res.GetException().ErrMsg)
+	rc.Reply = reply
+	go func() {
+		res := c.cluster.Call(req)
+		if res.GetException() != nil {
+			result.Error = errors.New(res.GetException().ErrMsg)
+		}
 		result.Done <- result
-	}
+	}()
 	return result
 }
 
@@ -173,3 +173,7 @@ func (m *MCContext) GetRefer(service string) interface{} {
 	// TODO 对client的封装,可以根据idl自动生成代码时支持
 	return nil
 }
+
+func (m *MCContext) GetContext() *motan.Context {
+	return m.context
+}
diff --git a/client_test.go b/client_test.go
index eff481e0..d114fa77 100644
--- a/client_test.go
+++ b/client_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	assert2 "github.com/stretchr/testify/assert"
 	"github.com/weibocom/motan-go/config"
+	motan "github.com/weibocom/motan-go/core"
 	"testing"
 	"time"
 )
@@ -78,6 +79,13 @@ motan-client:
 	err = mclient.BaseCall(req, &reply) // sync call
 	assert.Nil(err)
 	assert.Equal("Hello Ray", reply)
+
+	// test async call
+	var asyncReply string
+	asyncResult := mclient.Go("hello", []interface{}{"Ray"}, &asyncReply, make(chan *motan.AsyncResult, 1))
+	<-asyncResult.Done
+	assert.Nil(asyncResult.Error)
+	assert.Equal("Hello Ray", asyncReply)
 }
 
 func StartServer() {
diff --git a/cluster/command.go b/cluster/command.go
index f80405f7..cbcefae5 100644
--- a/cluster/command.go
+++ b/cluster/command.go
@@ -29,6 +29,10 @@ const (
 	RuleProtocol = "rule"
 )
 
+var (
+	recordInfoSize = 10
+)
+
 var oldSwitcherMap = make(map[string]bool) //Save the default value before the switcher last called
 
 // CommandRegistryWrapper wrapper registry for every cluster
@@ -45,6 +49,24 @@ type CommandRegistryWrapper struct {
 	tcCommand          *ClientCommand //effective traffic control command
 	degradeCommand     *ClientCommand //effective degrade command
 	switcherCommand    *ClientCommand
+	weights            map[string]string
+	commandRecorder    *motan.CircularRecorder
+	notifyRecorder     *motan.CircularRecorder
+}
+
+func (c *CommandRegistryWrapper) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{
+		motan.RuntimeNameKey: c.GetName(),
+	}
+	info[motan.RuntimeWeightKey] = c.weights
+	if c.staticTcCommand != nil {
+		info[motan.RuntimeStaticCommandKey] = c.staticTcCommand
+	}
+	info[motan.RuntimeAgentCommandKey] = c.agentCommandInfo
+	info[motan.RuntimeServiceCommandKey] = c.serviceCommandInfo
+	info[motan.RuntimeCommandHistoryKey] = c.commandRecorder.GetRecords()
+	info[motan.RuntimeNotifyHistoryKey] = c.notifyRecorder.GetRecords()
+	return info
 }
 
 type ClientCommand struct {
@@ -144,7 +166,7 @@ func ParseCommand(commandInfo string) *Command {
 }
 
 func GetCommandRegistryWrapper(cluster *MotanCluster, registry motan.Registry) motan.Registry {
-	cmdRegistry := &CommandRegistryWrapper{cluster: cluster, registry: registry}
+	cmdRegistry := &CommandRegistryWrapper{cluster: cluster, registry: registry, weights: map[string]string{}, commandRecorder: motan.NewCircularRecorder(recordInfoSize), notifyRecorder: motan.NewCircularRecorder(recordInfoSize)}
 	cmdRegistry.ownGroupURLs = make([]*motan.URL, 0)
 	cmdRegistry.otherGroupListener = make(map[string]*serviceListener)
 	cmdRegistry.cluster = cluster
@@ -243,6 +265,9 @@ func (c *CommandRegistryWrapper) clear() {
 	for _, l := range c.otherGroupListener {
 		l.unSubscribe(c.registry)
 	}
+	c.weights = make(map[string]string)
+	c.notifyRecorder = motan.NewCircularRecorder(recordInfoSize)
+	c.commandRecorder = motan.NewCircularRecorder(recordInfoSize)
 	c.otherGroupListener = make(map[string]*serviceListener)
 }
 
@@ -305,6 +330,7 @@ func (c *CommandRegistryWrapper) getResultWithCommand(needNotify bool) []*motan.
 	}
 	if needNotify {
 		c.notifyListener.Notify(c.registry.GetURL(), result)
+		c.notifyRecorder.AddRecord(result)
 	}
 	vlog.Infof("%s get result with command. tcCommand: %t, degradeCommand:%t,  result size %d, will notify:%t", c.cluster.GetURL().GetIdentity(), currentCommand != nil, c.degradeCommand != nil, len(result), needNotify)
 	return result
@@ -365,6 +391,8 @@ func buildRuleURL(weight string) *motan.URL {
 }
 
 func (c *CommandRegistryWrapper) processCommand(commandType int, commandInfo string) bool {
+	c.commandRecorder.AddRecord(commandInfo)
+
 	c.mux.Lock()
 	defer c.mux.Unlock()
 	needNotify := false
@@ -426,6 +454,7 @@ func (c *CommandRegistryWrapper) processTcCommand(newTcCommand *ClientCommand) {
 	if newTcCommand == nil && c.staticTcCommand == nil {
 		vlog.Infof("%s process command result : no tc command. ", c.cluster.GetURL().GetIdentity())
 	} else {
+		tempWeight := map[string]string{}
 		var groups []string
 		if newTcCommand != nil {
 			vlog.Infof("%s process command result : has dynamic tc command. tc command will enable.command : %+v", c.cluster.GetURL().GetIdentity(), newTcCommand)
@@ -435,6 +464,9 @@ func (c *CommandRegistryWrapper) processTcCommand(newTcCommand *ClientCommand) {
 		}
 		for _, group := range groups {
 			g := strings.Split(group, ":")
+			if len(g) >= 2 {
+				tempWeight[g[0]] = g[1]
+			}
 			if c.cluster.GetURL().Group == g[0] { // own group already subscribe
 				continue
 			}
@@ -446,6 +478,7 @@ func (c *CommandRegistryWrapper) processTcCommand(newTcCommand *ClientCommand) {
 				newListeners[g[0]] = newSubscribe(c, g[0])
 			}
 		}
+		c.weights = tempWeight
 	}
 
 	c.otherGroupListener = newListeners
diff --git a/cluster/command_test.go b/cluster/command_test.go
index 4ed10ba4..8565f076 100644
--- a/cluster/command_test.go
+++ b/cluster/command_test.go
@@ -3,6 +3,7 @@ package cluster
 import (
 	"bytes"
 	"fmt"
+	"github.com/stretchr/testify/assert"
 	"strconv"
 	"strings"
 	"testing"
@@ -137,6 +138,8 @@ func TestGetResultWithCommand(t *testing.T) {
 	if !hasRule {
 		t.Errorf("notify urls not has rule url! urls:%+v\n", urls)
 	}
+	// check runtime info
+	checkDefaultCommandWrapperRuntimeInfo(crw, t)
 
 	// has static command
 	crw = getDefaultCommandWrapper(true)
@@ -152,6 +155,9 @@ func TestGetResultWithCommand(t *testing.T) {
 
 	crw.processCommand(ServiceCmd, "")
 	validateResult(crw, []string{"test-group1", "test-group2", "test-group3"}, t)
+
+	// check runtime info
+	checkDefaultCommandWrapperRuntimeInfo(crw, t)
 }
 
 func validateResult(crw *CommandRegistryWrapper, notifyGroups []string, t *testing.T) {
@@ -214,6 +220,9 @@ func TestProcessCommand(t *testing.T) {
 	}
 	fmt.Printf("notify:%t, crw:%+v\n", notify, crw)
 
+	// check runtime info
+	checkDefaultCommandWrapperRuntimeInfo(crw, t)
+
 	// has static command
 	crw = getDefaultCommandWrapper(true)
 	processServiceCmd(crw, cl, t)
@@ -233,6 +242,8 @@ func TestProcessCommand(t *testing.T) {
 		}
 	}
 
+	// check runtime info
+	checkDefaultCommandWrapperRuntimeInfo(crw, t)
 }
 
 func processServiceCmd(crw *CommandRegistryWrapper, cl string, t *testing.T) {
@@ -364,6 +375,44 @@ func checkHost(urls []*motan.URL, f func(host string) bool, t *testing.T) {
 	}
 }
 
+func checkDefaultCommandWrapperRuntimeInfo(crw *CommandRegistryWrapper, t *testing.T) {
+	info := crw.GetRuntimeInfo()
+	assert.NotNil(t, info)
+
+	name, ok := info[motan.RuntimeNameKey]
+	assert.True(t, ok)
+	assert.Equal(t, crw.GetName(), name)
+
+	weightsInfo, ok := info[motan.RuntimeWeightKey]
+	assert.True(t, ok)
+	assert.NotNil(t, weightsInfo)
+
+	staticCommand, ok := info[motan.RuntimeStaticCommandKey]
+	if crw.staticTcCommand == nil {
+		assert.False(t, ok)
+		assert.Nil(t, staticCommand)
+	} else {
+		assert.True(t, ok)
+		assert.NotNil(t, staticCommand)
+	}
+
+	agentCommandInfo, ok := info[motan.RuntimeAgentCommandKey]
+	assert.True(t, ok)
+	assert.NotNil(t, agentCommandInfo)
+
+	serviceCommandInfo, ok := info[motan.RuntimeServiceCommandKey]
+	assert.True(t, ok)
+	assert.NotNil(t, serviceCommandInfo)
+
+	commandHistory, ok := info[motan.RuntimeCommandHistoryKey]
+	assert.True(t, ok)
+	assert.NotNil(t, commandHistory)
+
+	notifyHistory, ok := info[motan.RuntimeNotifyHistoryKey]
+	assert.True(t, ok)
+	assert.NotNil(t, notifyHistory)
+}
+
 func getDefaultCommandWrapper(withStaticCommand bool) *CommandRegistryWrapper {
 	cluster := initCluster()
 	if withStaticCommand {
diff --git a/cluster/httpCluster.go b/cluster/httpCluster.go
index 02ae87c5..fa62fdea 100644
--- a/cluster/httpCluster.go
+++ b/cluster/httpCluster.go
@@ -107,6 +107,16 @@ func NewHTTPCluster(url *core.URL, proxy bool, context *core.Context, extFactory
 	return c
 }
 
+func (c *HTTPCluster) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{
+		core.RuntimeNameKey: c.GetName(),
+	}
+	if c.url != nil {
+		info[core.RuntimeUrlKey] = c.url.ToExtInfo()
+	}
+	return info
+}
+
 func (c *HTTPCluster) CanServe(uri string) (string, bool) {
 	service := c.uriConverter.URIToServiceName(uri, nil)
 	if service == "" {
diff --git a/cluster/httpCluster_test.go b/cluster/httpCluster_test.go
index 014820b5..43f4f139 100644
--- a/cluster/httpCluster_test.go
+++ b/cluster/httpCluster_test.go
@@ -38,5 +38,18 @@ func TestHTTPCluster_Call(t *testing.T) {
 	request.Method = uri
 	response := httpCluster.Call(request)
 	assert.True(t, response != nil)
+
+	// runtime info
+	info := httpCluster.GetRuntimeInfo()
+	assert.NotNil(t, info)
+
+	urlInfo, ok := info[core.RuntimeUrlKey]
+	assert.True(t, ok)
+	assert.Equal(t, httpCluster.url.ToExtInfo(), urlInfo)
+
+	name, ok := info[core.RuntimeNameKey]
+	assert.True(t, ok)
+	assert.Equal(t, httpCluster.GetName(), name)
+
 	httpCluster.Destroy()
 }
diff --git a/cluster/motanCluster.go b/cluster/motanCluster.go
index 4bae0690..98b17779 100644
--- a/cluster/motanCluster.go
+++ b/cluster/motanCluster.go
@@ -268,6 +268,9 @@ func (m *MotanCluster) Destroy() {
 			vlog.Infof("destroy endpoint %s .", e.GetURL().GetIdentity())
 			e.Destroy()
 		}
+		if d, ok := m.LoadBalance.(motan.Destroyable); ok {
+			d.Destroy()
+		}
 		m.closed = true
 	}
 }
@@ -347,6 +350,61 @@ func (m *MotanCluster) NotifyAgentCommand(commandInfo string) {
 	}
 }
 
+func (m *MotanCluster) GetRuntimeInfo() map[string]interface{} {
+	m.notifyLock.Lock()
+	defer m.notifyLock.Unlock()
+
+	referSize := len(m.Refers)
+	info := map[string]interface{}{
+		motan.RuntimeNameKey: m.GetName(),
+	}
+	info[motan.RuntimeRefererSizeKey] = referSize
+	if m.url != nil {
+		info[motan.RuntimeUrlKey] = m.url.ToExtInfo()
+	}
+
+	unavailableRefers := make(map[string]interface{})
+	availableRefers := make(map[string]interface{}, referSize)
+	i := 0
+	for _, endpoint := range m.Refers {
+		ep := endpoint.(*motan.FilterEndPoint)
+		if ep.IsAvailable() {
+			availableRefers[fmt.Sprintf("%d-%s", i, ep.URL.GetIdentity())] = ep.Caller.GetRuntimeInfo()
+		} else {
+			unavailableRefers[fmt.Sprintf("%d-%s", i, ep.URL.GetIdentity())] = ep.Caller.GetRuntimeInfo()
+		}
+		i++
+	}
+	info[motan.RuntimeReferersKey] = map[string]interface{}{
+		motan.RuntimeAvailableKey:   availableRefers,
+		motan.RuntimeUnavailableKey: unavailableRefers,
+	}
+	registriesInfo := make(map[string]interface{}, len(m.Registries))
+	for _, registry := range m.Registries {
+		registriesInfo[registry.GetURL().GetIdentity()] = registry.GetRuntimeInfo()
+	}
+	info[motan.RuntimeRegistriesKey] = registriesInfo
+
+	var filtersInfo []interface{}
+	for _, filter := range m.Filters {
+		filtersInfo = append(filtersInfo, filter.GetRuntimeInfo())
+	}
+	if len(filtersInfo) > 0 {
+		info[motan.RuntimeFiltersKey] = filtersInfo
+	}
+
+	var clusterFilters []interface{}
+	clusterFilter := m.clusterFilter
+	for clusterFilter != nil {
+		clusterFilters = append(clusterFilters, clusterFilter.GetRuntimeInfo())
+		clusterFilter = clusterFilter.GetNext()
+	}
+	if len(clusterFilters) > 0 {
+		info[motan.RuntimeClusterFiltersKey] = clusterFilters
+	}
+	return info
+}
+
 const (
 	clusterIdcPlaceHolder = "${idc}"
 )
diff --git a/cluster/motanCluster_test.go b/cluster/motanCluster_test.go
index 20702af1..5c80b5e8 100644
--- a/cluster/motanCluster_test.go
+++ b/cluster/motanCluster_test.go
@@ -23,6 +23,15 @@ func TestInitFilter(t *testing.T) {
 	cluster.initFilters()
 	checkClusterFilter(cluster.clusterFilter, 4, t)
 	checkEndpointFilter(cluster.Filters, 3, t)
+	// check runtime info filters
+	info := cluster.GetRuntimeInfo()
+	filters, ok := info[motan.RuntimeFiltersKey]
+	assert.True(t, ok)
+	assert.Equal(t, 3, len(filters.([]interface{})))
+
+	clusterFilters, ok := info[motan.RuntimeClusterFiltersKey]
+	assert.True(t, ok)
+	assert.Equal(t, 4, len(clusterFilters.([]interface{})))
 }
 
 func checkClusterFilter(filter motan.ClusterFilter, expectDeep int, t *testing.T) {
@@ -94,6 +103,38 @@ func TestNotify(t *testing.T) {
 	if destroyEndpoint.IsAvailable() {
 		t.Fatalf("cluster endpoint should not be available")
 	}
+
+	// check runtime info
+	info := cluster.GetRuntimeInfo()
+	assert.NotNil(t, info)
+
+	name, ok := info[motan.RuntimeNameKey]
+	assert.True(t, ok)
+	assert.Equal(t, cluster.GetName(), name)
+
+	refersSize, ok := info[motan.RuntimeRefererSizeKey]
+	assert.True(t, ok)
+	assert.Equal(t, len(cluster.Refers), refersSize)
+
+	urlInfo, ok := info[motan.RuntimeUrlKey]
+	assert.True(t, ok)
+	assert.NotNil(t, urlInfo)
+
+	refers, ok := info[motan.RuntimeReferersKey]
+	assert.True(t, ok)
+	assert.NotNil(t, refers)
+
+	available, ok := refers.(map[string]interface{})[motan.RuntimeAvailableKey]
+	assert.True(t, ok)
+	assert.NotNil(t, available)
+
+	unavailable, ok := refers.(map[string]interface{})[motan.RuntimeUnavailableKey]
+	assert.True(t, ok)
+	assert.NotNil(t, unavailable)
+
+	registries, ok := info[motan.RuntimeRegistriesKey]
+	assert.True(t, ok)
+	assert.NotNil(t, registries)
 }
 
 func TestCall(t *testing.T) {
diff --git a/config/compatible.yaml b/config/compatible.yaml
new file mode 100644
index 00000000..90c549bf
--- /dev/null
+++ b/config/compatible.yaml
@@ -0,0 +1,57 @@
+#only for unit test!!
+motan-agent:
+  port: 9981
+  mport: 8002
+  #log_dir: "/data1/logs/agentlog/"
+  log_dir: "./logs"
+  registry: "vintage"
+  application: "pc-yf-test"
+
+motan-server:
+  testkey:testv
+
+motan-client:
+  testkey:testv
+
+#config of registries
+motan-registry:
+  vintage:
+    protocol: vintage
+    host: 10.**.**.**
+    port: 8090
+    registryRetryPeriod: 30000
+    registrySessionTimeout: 10000
+    requestTimeout: 5000
+  consul:
+    protocol: consul
+    host: 10.**.**.**
+    port: 8090
+  direct:
+    protocol: direct
+    host: 10.**.**.**
+    port: 8013
+
+#conf of basic refers
+motan-basicRefer:
+  mybasicRefer:
+    group: basic-group
+    registry: "vintage"
+    requestTimeout: 1000
+    haStrategy: failover
+    loadbalance: random
+    filter: "accessLog,metrics"
+    maxClientConnection: 10
+    minClientConnection: 1
+    retries: 0
+    application: pc
+
+#conf of refers
+motan-refer:
+  status-rpc-json:
+    path: com.weibo.api.test.service.TestRpc
+    group: test-group
+    registry: vintage
+    serialization: simple
+    protocol: motanV1Compatible
+    version: 0.1
+    basicRefer: mybasicRefer
diff --git a/config/config.go b/config/config.go
index d7b0675b..b9789ef5 100644
--- a/config/config.go
+++ b/config/config.go
@@ -127,12 +127,17 @@ func (c *Config) Int64(key string) (int64, error) {
 
 // String returns the string value for a given key.
 func (c *Config) String(key string) string {
+	return c.GetStringWithDefault(key, "")
+}
+
+// String returns the string value for a given key.
+func (c *Config) GetStringWithDefault(key string, def string) string {
 	if value, err := c.getData(key); err != nil {
-		return ""
+		return def
 	} else if vv, ok := value.(string); ok {
 		return vv
 	}
-	return ""
+	return def
 }
 
 // GetSection returns map for the given key
diff --git a/core/constants.go b/core/constants.go
index 4224b485..5815bc9e 100644
--- a/core/constants.go
+++ b/core/constants.go
@@ -133,12 +133,117 @@ const (
 
 // errorCodes
 const (
-	ENoEndpoints = 1001
-	ENoChannel   = 1002
-	EUnkonwnMsg  = 1003
-	EConvertMsg  = 1004
+	ENoEndpoints      = 1001
+	ENoChannel        = 1002
+	EUnkonwnMsg       = 1003
+	EConvertMsg       = 1004
+	EProviderNotExist = 404
+)
+
+// ProviderNotExistPrefix errorMessage
+const (
+	ProviderNotExistPrefix = "provider not exist serviceKey="
 )
 
 const (
 	DefaultReferVersion = "1.0"
 )
+
+// meta info
+const (
+	DefaultMetaPrefix            = "META_"
+	EnvMetaPrefixKey             = "envMetaPrefix"
+	URLRegisterMeta              = "registerMeta"
+	DefaultRegisterMeta          = false
+	MetaCacheExpireSecondKey     = "metaCacheExpireSecond"
+	DynamicMetaKey               = "dynamicMeta"
+	DefaultDynamicMeta           = true
+	WeightRefreshPeriodSecondKey = "weightRefreshPeriodSecond"
+	WeightMetaSuffixKey          = "WEIGHT"
+	ServiceNotSupport            = "service not support"
+)
+
+//----------- runtime -------------
+
+const (
+	// -----------top level keys-------------
+
+	RuntimeInstanceTypeKey     = "instanceType"
+	RuntimeExportersKey        = "exporters"
+	RuntimeClustersKey         = "clusters"
+	RuntimeHttpClustersKey     = "httpClusters"
+	RuntimeExtensionFactoryKey = "extensionFactory"
+	RuntimeServersKey          = "servers"
+	RuntimeBasicKey            = "basic"
+
+	//-----------common keys-------------
+
+	RuntimeUrlKey            = "url"
+	RuntimeIsAvailableKey    = "isAvailable"
+	RuntimeProxyKey          = "proxy"
+	RuntimeAvailableKey      = "available"
+	RuntimeEndpointKey       = "endpoint"
+	RuntimeFiltersKey        = "filters"
+	RuntimeClusterFiltersKey = "clusterFilters"
+	RuntimeNameKey           = "name"
+	RuntimeIndexKey          = "index"
+	RuntimeTypeKey           = "type"
+
+	// -----------exporter keys-------------
+
+	RuntimeProviderKey = "provider"
+
+	// -----------server keys-------------
+
+	RuntimeAgentServerKey        = "agentServer"
+	RuntimeAgentPortServerKey    = "agentPortServer"
+	RuntimeMaxContentLengthKey   = "maxContentLength"
+	RuntimeHeartbeatEnabledKey   = "heartbeatEnabled"
+	RuntimeMessageHandlerKey     = "messageHandler"
+	RuntimeProvidersKey          = "providers"
+	RuntimeMessageHandlerTypeKey = "messageHandlerType"
+
+	// -----------http proxy server-------------
+
+	RuntimeHttpProxyServerKey = "httpProxyServer"
+	RuntimeDenyKey            = "deny"
+	RuntimeKeepaliveKey       = "keepalive"
+	RuntimeDefaultDomainKey   = "defaultDomain"
+
+	// -----------cluster keys-------------
+
+	RuntimeReferersKey    = "referers"
+	RuntimeRefererSizeKey = "refererSize"
+	RuntimeUnavailableKey = "unavailable"
+
+	// -----------endpoint keys-------------
+
+	RuntimeErrorCountKey       = "errorCount"
+	RuntimeKeepaliveRunningKey = "keepaliveRunning"
+	RuntimeKeepaliveTypeKey    = "keepaliveType"
+
+	// -----------extensionFactory keys-------------
+
+	RuntimeRegistriesKey = "registries"
+
+	// -----------registry keys-------------
+
+	RuntimeRegisteredServiceUrlsKey = "registeredServiceUrls"
+	RuntimeSubscribedServiceUrlsKey = "subscribedServiceUrls"
+	RuntimeFailedRegisterUrls       = "failedRegisterUrls"
+	RuntimeFailedUnregisterUrls     = "failedUnregisterUrls"
+	RuntimeFailedSubscribeUrls      = "failedSubscribeUrls"
+	RuntimeFailedUnsubScribeUrls    = "failedUnsubscribeUrls"
+	RuntimeSubscribeInfoKey         = "subscribeInfo"
+	RuntimeAgentCommandKey          = "agentCommand"
+	RuntimeServiceCommandKey        = "serviceCommand"
+	RuntimeStaticCommandKey         = "staticCommand"
+	RuntimeWeightKey                = "weight"
+	RuntimeCommandHistoryKey        = "commandHistory"
+	RuntimeNotifyHistoryKey         = "notifyHistory"
+
+	// -----------basic keys-------------
+
+	RuntimeCpuPercentKey = "cpuPercent"
+	RuntimeRssMemoryKey  = "rssMemory"
+)
diff --git a/core/globalContext.go b/core/globalContext.go
index 29d974e4..ec4d4d1f 100644
--- a/core/globalContext.go
+++ b/core/globalContext.go
@@ -9,6 +9,8 @@ import (
 	"os"
 	"reflect"
 	"strings"
+	"sync/atomic"
+	"unsafe"
 )
 
 const (
@@ -87,6 +89,22 @@ var (
 	Recover     = flag.Bool("recover", false, "recover from accidental exit")
 )
 
+func GetMport() int {
+	return *(*int)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&Mport))))
+}
+
+func SetMport(v int) {
+	atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&Mport)), unsafe.Pointer(&v))
+}
+
+func GetApplication() string {
+	return *(*string)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&Application))))
+}
+
+func SetApplication(v string) {
+	atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&Application)), unsafe.Pointer(&v))
+}
+
 func AddRelevantFilter(filterStr string) {
 	k := strings.TrimSpace(filterStr)
 	if k != "" {
@@ -192,7 +210,7 @@ func (c *Context) Initialize() {
 		c.pool = *Pool
 	}
 	if c.application == "" {
-		c.application = *Application
+		c.application = GetApplication()
 	}
 
 	c.RegistryURLs = make(map[string]*URL)
@@ -420,7 +438,6 @@ func (c *Context) basicConfToURLs(section string) map[string]*URL {
 		if len(finalFilters) > 0 {
 			newURL.PutParam(FilterKey, c.FilterSetToStr(finalFilters))
 		}
-
 		newURLs[key] = newURL
 	}
 	return newURLs
@@ -536,21 +553,11 @@ func (c *Context) parseRegGroupSuffix(urlMap map[string]*URL) {
 	if regGroupSuffix == "" {
 		return
 	}
-	filterMap := make(map[string]struct{}, len(urlMap))
 	for _, url := range urlMap {
-		filterMap[url.GetIdentityWithRegistry()] = struct{}{}
-	}
-	for k, url := range urlMap {
 		if strings.HasSuffix(url.Group, regGroupSuffix) {
 			continue
 		}
-		newUrl := url.Copy()
-		newUrl.Group += regGroupSuffix
-		if _, ok := filterMap[newUrl.GetIdentityWithRegistry()]; ok {
-			continue
-		}
-		filterMap[newUrl.GetIdentityWithRegistry()] = struct{}{}
-		urlMap[k] = newUrl
+		url.Group += regGroupSuffix
 	}
 }
 
@@ -581,6 +588,12 @@ func (c *Context) parseSubGroupSuffix(urlMap map[string]*URL) {
 func (c *Context) parseRefers() {
 	referUrls := c.basicConfToURLs(refersSection)
 	c.parseSubGroupSuffix(referUrls)
+	//TODO: to compatible with removed protocol: motanV1Compatible
+	for k := range referUrls {
+		if referUrls[k].Protocol == "motanV1Compatible" {
+			referUrls[k].Protocol = "motan"
+		}
+	}
 	c.RefersURLs = referUrls
 }
 
diff --git a/core/globalContext_test.go b/core/globalContext_test.go
index 770bec34..b80943fd 100644
--- a/core/globalContext_test.go
+++ b/core/globalContext_test.go
@@ -30,6 +30,15 @@ func TestGetContext(t *testing.T) {
 	assert.Equal(t, "motan-demo-rpc", rs.ServiceURLs["mytest-motan2"].Group, "parse serivce key fail")
 }
 
+func TestCompatible(t *testing.T) {
+	rs := &Context{ConfigFile: "../config/compatible.yaml"}
+	rs.Initialize()
+	assert.NotNil(t, rs.RefersURLs)
+	for _, j := range rs.RefersURLs {
+		assert.Equal(t, j.Protocol, "motan")
+	}
+}
+
 func TestNewContext(t *testing.T) {
 	configFile := filepath.Join("testdata", "app.yaml")
 	pool1Context := NewContext(configFile, "app", "app-idc1")
@@ -259,11 +268,11 @@ func TestContext_parseRegGroupSuffix(t *testing.T) {
 			},
 			AssertFunc: func(t *testing.T, urlMap map[string]*URL) {
 				groupMap := countGroup(urlMap)
-				assert.Equal(t, 1, groupMap["group1"])
-				assert.Equal(t, 1, groupMap["group1"+regGroupSuffix])
+				assert.Equal(t, 0, groupMap["group1"])
+				assert.Equal(t, 2, groupMap["group1"+regGroupSuffix])
 				assert.Equal(t, 1, groupMap["group2"+regGroupSuffix])
-				assert.Equal(t, 1, groupMap["group3"])
-				assert.Equal(t, 1, groupMap["group3"+regGroupSuffix])
+				assert.Equal(t, 0, groupMap["group3"])
+				assert.Equal(t, 2, groupMap["group3"+regGroupSuffix])
 				assert.Equal(t, 2, groupMap["group4"+regGroupSuffix])
 				assert.Equal(t, 2, groupMap["group5"+regGroupSuffix])
 			},
@@ -271,6 +280,7 @@ func TestContext_parseRegGroupSuffix(t *testing.T) {
 	}
 	os.Setenv(RegGroupSuffix, regGroupSuffix)
 	ctx := &Context{}
+	// replace all groups with regGroupSuffix
 	for _, s := range cases {
 		ctx.parseRegGroupSuffix(s.UrlMap)
 		s.AssertFunc(t, s.UrlMap)
diff --git a/core/motan.go b/core/motan.go
index ec84c8b3..22936c9c 100644
--- a/core/motan.go
+++ b/core/motan.go
@@ -1,6 +1,7 @@
 package core
 
 import (
+	"container/ring"
 	"errors"
 	"net"
 	"strconv"
@@ -88,6 +89,7 @@ type Cloneable interface {
 
 // Caller : can process a motan request. the call maybe process from remote by endpoint, maybe process by some kinds of provider
 type Caller interface {
+	RuntimeInfo
 	WithURL
 	Status
 	Call(request Request) Response
@@ -160,6 +162,12 @@ type LoadBalance interface {
 	SetWeight(weight string)
 }
 
+// WeightLoadBalance : weight loadBalance for cluster
+type WeightLoadBalance interface {
+	LoadBalance
+	NotifyWeightChange()
+}
+
 // DiscoverService : discover service for cluster
 type DiscoverService interface {
 	Subscribe(url *URL, listener NotifyListener)
@@ -202,6 +210,7 @@ type SnapshotService interface {
 
 // Registry : can subscribe or register service
 type Registry interface {
+	RuntimeInfo
 	Name
 	WithURL
 	DiscoverService
@@ -235,6 +244,7 @@ type CommandNotifyListener interface {
 
 // Filter : filter request or response in a call processing
 type Filter interface {
+	RuntimeInfo
 	Name
 	// filter must be prototype
 	NewFilter(url *URL) Filter
@@ -266,6 +276,7 @@ type Server interface {
 	WithURL
 	Name
 	Destroyable
+	RuntimeInfo
 	SetMessageHandler(mh MessageHandler)
 	GetMessageHandler() MessageHandler
 	Open(block bool, proxy bool, handler MessageHandler, extFactory ExtensionFactory) error
@@ -274,6 +285,7 @@ type Server interface {
 
 // Exporter : export and manage a service. one exporter bind with a service
 type Exporter interface {
+	RuntimeInfo
 	Export(server Server, extFactory ExtensionFactory, context *Context) error
 	Unexport() error
 	SetProvider(provider Provider)
@@ -293,6 +305,8 @@ type Provider interface {
 
 // MessageHandler : handler message(request) for Server
 type MessageHandler interface {
+	Name
+	RuntimeInfo
 	Call(request Request) (res Response)
 	AddProvider(p Provider) error
 	RmProvider(p Provider)
@@ -310,6 +324,7 @@ type Serialization interface {
 
 // ExtensionFactory : can regiser and get all kinds of extension implements.
 type ExtensionFactory interface {
+	RuntimeInfo
 	GetHa(url *URL) HaStrategy
 	GetLB(url *URL) LoadBalance
 	GetFilter(name string) Filter
@@ -413,7 +428,6 @@ func (c *RPCContext) Reset() {
 	c.BodySize = 0
 	c.SerializeNum = 0
 	c.Serialized = false
-	c.AsyncCall = false
 	c.Result = nil
 	c.Reply = nil
 	c.FinishHandlers = c.FinishHandlers[:0]
@@ -434,10 +448,8 @@ func (c *RPCContext) OnFinish() {
 
 // AsyncResult : async call result
 type AsyncResult struct {
-	StartTime int64
-	Done      chan *AsyncResult
-	Reply     interface{}
-	Error     error
+	Done  chan *AsyncResult
+	Error error
 }
 
 // DeserializableValue : for lazy deserialize
@@ -578,7 +590,6 @@ func (req *MotanRequest) Clone() interface{} {
 			GzipSize:            req.RPCContext.GzipSize,
 			SerializeNum:        req.RPCContext.SerializeNum,
 			Serialized:          req.RPCContext.Serialized,
-			AsyncCall:           req.RPCContext.AsyncCall,
 			Result:              req.RPCContext.Result,
 			Reply:               req.RPCContext.Reply,
 			RequestSendTime:     req.RPCContext.RequestSendTime,
@@ -748,6 +759,20 @@ type DefaultExtensionFactory struct {
 	newRegistryLock sync.Mutex
 }
 
+func (d *DefaultExtensionFactory) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	// registries runtime info
+	d.newRegistryLock.Lock()
+	defer d.newRegistryLock.Unlock()
+	registriesInfo := map[string]interface{}{}
+	for s, registry := range d.registries {
+		registriesInfo[s] = registry.GetRuntimeInfo()
+	}
+	info[RuntimeRegistriesKey] = registriesInfo
+
+	return info
+}
+
 func (d *DefaultExtensionFactory) GetHa(url *URL) HaStrategy {
 	haName := url.GetParam(Hakey, "failover")
 	if newHa, ok := d.haFactories[haName]; ok {
@@ -920,6 +945,15 @@ func GetLastClusterFilter() ClusterFilter {
 
 type lastEndPointFilter struct{}
 
+func (l *lastEndPointFilter) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{
+		RuntimeNameKey:  l.GetName(),
+		RuntimeIndexKey: l.GetIndex(),
+		RuntimeTypeKey:  l.GetType(),
+	}
+	return info
+}
+
 func (l *lastEndPointFilter) GetName() string {
 	return "lastEndPointFilter"
 }
@@ -961,6 +995,14 @@ func (l *lastEndPointFilter) GetType() int32 {
 
 type lastClusterFilter struct{}
 
+func (l *lastClusterFilter) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		RuntimeNameKey:  l.GetName(),
+		RuntimeIndexKey: l.GetIndex(),
+		RuntimeTypeKey:  l.GetType(),
+	}
+}
+
 func (l *lastClusterFilter) GetName() string {
 	return "lastClusterFilter"
 }
@@ -1009,6 +1051,13 @@ type FilterEndPoint struct {
 	Caller        Caller
 }
 
+func (f *FilterEndPoint) GetRuntimeInfo() map[string]interface{} {
+	if f.Caller != nil {
+		return f.Caller.GetRuntimeInfo()
+	}
+	return map[string]interface{}{}
+}
+
 func (f *FilterEndPoint) Call(request Request) Response {
 	if request.GetRPCContext(true).Tc != nil {
 		request.GetRPCContext(true).Tc.PutReqSpan(&Span{Name: EpFilterStart, Addr: f.GetURL().GetAddressStr(), Time: time.Now()})
@@ -1197,3 +1246,76 @@ func GetLocalProvider(service string) Provider {
 	}
 	return nil
 }
+
+//-----------RuntimeInfo interface-------------
+
+// RuntimeInfo : output runtime information
+type RuntimeInfo interface {
+	GetRuntimeInfo() map[string]interface{}
+}
+
+// GetRuntimeInfo : call s.GetRuntimeInfo
+func GetRuntimeInfo(s interface{}) map[string]interface{} {
+	if sc, ok := s.(RuntimeInfo); ok {
+		return sc.GetRuntimeInfo()
+	}
+	return map[string]interface{}{}
+}
+
+// -----------CircularRecorder-------------
+type circularRecorderItem struct {
+	timestamp int64
+	value     interface{}
+}
+
+type CircularRecorder struct {
+	ring   *ring.Ring
+	lock   sync.RWMutex
+	keyBuf []byte
+}
+
+func NewCircularRecorder(size int) *CircularRecorder {
+	return &CircularRecorder{
+		ring:   ring.New(size),
+		lock:   sync.RWMutex{},
+		keyBuf: make([]byte, 0, 18),
+	}
+}
+
+func (c *CircularRecorder) AddRecord(item interface{}) {
+	if item == nil {
+		return
+	}
+	c.lock.Lock()
+	defer c.lock.Unlock()
+	c.ring.Value = &circularRecorderItem{
+		timestamp: time.Now().UnixNano() / 1e6,
+		value:     item,
+	}
+	c.ring = c.ring.Next()
+}
+
+func (c *CircularRecorder) GetRecords() map[string]interface{} {
+	c.lock.RLock()
+	defer c.lock.RUnlock()
+	records := make(map[string]interface{})
+	idx := int64(0)
+	c.ring.Do(func(i interface{}) {
+		if i != nil {
+			item, ok := i.(*circularRecorderItem)
+			if ok {
+				records[c.generateRecordKey(idx, item.timestamp)] = item.value
+				idx++
+			}
+		}
+	})
+	return records
+}
+
+func (c *CircularRecorder) generateRecordKey(idx, timestamp int64) string {
+	c.keyBuf = c.keyBuf[:0]
+	c.keyBuf = strconv.AppendInt(c.keyBuf, idx, 10)
+	c.keyBuf = append(c.keyBuf, ':')
+	c.keyBuf = strconv.AppendInt(c.keyBuf, timestamp, 10)
+	return string(c.keyBuf)
+}
diff --git a/core/motan_test.go b/core/motan_test.go
index 388c11c3..7fce15b0 100644
--- a/core/motan_test.go
+++ b/core/motan_test.go
@@ -2,6 +2,7 @@ package core
 
 import (
 	"strconv"
+	"strings"
 	"sync"
 	"testing"
 
@@ -190,3 +191,23 @@ func newDiscoverErrorRegistry() *TestRegistry {
 	registry.DiscoverError = true
 	return registry
 }
+
+func TestCircularRecorder(t *testing.T) {
+	size := 10
+	r := NewCircularRecorder(size)
+	assert.NotNil(t, r)
+	assert.Equal(t, size, r.ring.Len())
+
+	for i := 0; i < size*2; i++ {
+		r.AddRecord(i)
+	}
+	records := r.GetRecords()
+	assert.Equal(t, size, r.ring.Len())
+	assert.Equal(t, size, len(records))
+
+	for k, v := range records {
+		keyArr := strings.Split(k, ":")
+		idx, _ := strconv.Atoi(keyArr[0])
+		assert.Equal(t, idx+size, v.(int))
+	}
+}
diff --git a/core/test.go b/core/test.go
index 047683a8..0bf04269 100644
--- a/core/test.go
+++ b/core/test.go
@@ -15,6 +15,10 @@ type TestFilter struct {
 	next  ClusterFilter
 }
 
+func (t *TestFilter) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{}
+}
+
 func (t *TestFilter) GetName() string {
 	return "TestFilter"
 }
@@ -57,6 +61,10 @@ type TestEndPointFilter struct {
 	next  EndPointFilter
 }
 
+func (t *TestEndPointFilter) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{}
+}
+
 func (t *TestEndPointFilter) GetName() string {
 	return "TestEndPointFilter"
 }
@@ -99,6 +107,10 @@ type TestProvider struct {
 	URL *URL
 }
 
+func (t *TestProvider) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{}
+}
+
 func (t *TestProvider) SetService(s interface{}) {
 }
 
@@ -131,6 +143,10 @@ type TestEndPoint struct {
 	available   atomic.Value
 }
 
+func (t *TestEndPoint) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{}
+}
+
 func (t *TestEndPoint) GetURL() *URL {
 	return t.URL
 }
@@ -233,6 +249,12 @@ type TestRegistry struct {
 	DiscoverError bool
 }
 
+func (t *TestRegistry) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		RuntimeNameKey: t.GetName(),
+	}
+}
+
 func (t *TestRegistry) GetName() string {
 	return "testRegistry"
 }
diff --git a/core/url.go b/core/url.go
index 11ac3be6..89f9e0f4 100644
--- a/core/url.go
+++ b/core/url.go
@@ -64,7 +64,7 @@ func (u *URL) IsMatch(service, group, protocol, version string) bool {
 	}
 	// for motan v1 request, parameter protocol should be empty
 	if protocol != "" {
-		if u.Protocol == "motanV1Compatible" {
+		if u.Protocol == "motan2" {
 			if protocol != "motan2" && protocol != "motan" {
 				return false
 			}
@@ -328,7 +328,7 @@ func (u *URL) MergeParams(params map[string]string) {
 }
 
 func (u *URL) CanServe(other *URL) bool {
-	if u.Protocol != other.Protocol && u.Protocol != ProtocolLocal {
+	if !u.CanServeProtocol(other) {
 		vlog.Errorf("can not serve protocol, err : p1:%s, p2:%s", u.Protocol, other.Protocol)
 		return false
 	}
@@ -336,9 +336,11 @@ func (u *URL) CanServe(other *URL) bool {
 		vlog.Errorf("can not serve path, err : p1:%s, p2:%s", u.Path, other.Path)
 		return false
 	}
-	if !IsSame(u.Parameters, other.Parameters, SerializationKey, "") {
-		vlog.Errorf("can not serve serialization, err : s1:%s, s2:%s", u.Parameters[SerializationKey], other.Parameters[SerializationKey])
-		return false
+	if u.Protocol != "motan2" {
+		if !IsSame(u.Parameters, other.Parameters, SerializationKey, "") {
+			vlog.Errorf("can not serve serialization, err : s1:%s, s2:%s", u.Parameters[SerializationKey], other.Parameters[SerializationKey])
+			return false
+		}
 	}
 	// compatible with old version: 0.1
 	if !(IsSame(u.Parameters, other.Parameters, VersionKey, "0.1") || IsSame(u.Parameters, other.Parameters, VersionKey, DefaultReferVersion)) {
@@ -348,6 +350,14 @@ func (u *URL) CanServe(other *URL) bool {
 	return true
 }
 
+func (u *URL) CanServeProtocol(other *URL) bool {
+	// motan2 is compatible with motan
+	if other.Protocol == "motan" && u.Protocol == "motan2" {
+		return true
+	}
+	return u.Protocol == other.Protocol || u.Protocol == ProtocolLocal
+}
+
 func IsSame(m1 map[string]string, m2 map[string]string, key string, defaultValue string) bool {
 	if m1 == nil && m2 == nil {
 		return true
diff --git a/core/url_test.go b/core/url_test.go
index 73ca3a73..fa15f774 100644
--- a/core/url_test.go
+++ b/core/url_test.go
@@ -103,7 +103,7 @@ func TestCopyAndMerge(t *testing.T) {
 	}
 }
 
-func TestCanServer(t *testing.T) {
+func TestCanServe(t *testing.T) {
 	params1 := make(map[string]string)
 	params2 := make(map[string]string)
 	url1 := &URL{Protocol: "motan", Path: "test/path", Parameters: params1}
@@ -146,6 +146,33 @@ func TestCanServer(t *testing.T) {
 		t.Fatalf("url CanServe testFail url1: %+v, url2: %+v\n", url1, url2)
 	}
 	fmt.Printf("url1:%+v, url2:%+v\n", url1, url2)
+	url1.Path = ""
+	url2.Path = ""
+	url1.Protocol = "motan2"
+	url2.Protocol = "motan"
+	if !url1.CanServe(url2) {
+		t.Fatalf("url CanServe testFail url1: %+v, url2: %+v\n", url1, url2)
+	}
+	url1.Protocol = "motan"
+	url2.Protocol = "motan2"
+	if url1.CanServe(url2) {
+		t.Fatalf("url CanServe testFail url1: %+v, url2: %+v\n", url1, url2)
+	}
+	url1.Protocol = "local"
+	url2.Protocol = "motan2"
+	if !url1.CanServe(url2) {
+		t.Fatalf("url CanServe testFail url1: %+v, url2: %+v\n", url1, url2)
+	}
+	url1.Protocol = "abc"
+	url2.Protocol = "motan2"
+	if url1.CanServe(url2) {
+		t.Fatalf("url CanServe testFail url1: %+v, url2: %+v\n", url1, url2)
+	}
+	url1.Protocol = "motan"
+	url2.Protocol = "motan2"
+	if url1.CanServe(url2) {
+		t.Fatalf("url CanServe testFail url1: %+v, url2: %+v\n", url1, url2)
+	}
 }
 
 func TestGetPositiveIntValue(t *testing.T) {
diff --git a/core/util.go b/core/util.go
index 0438ca69..0ba6f4c3 100644
--- a/core/util.go
+++ b/core/util.go
@@ -100,6 +100,24 @@ func SliceShuffle(slice []string) []string {
 	return slice
 }
 
+func EndpointShuffle(slice []EndPoint) []EndPoint {
+	for i := 0; i < len(slice); i++ {
+		a := rand.Intn(len(slice))
+		b := rand.Intn(len(slice))
+		slice[a], slice[b] = slice[b], slice[a]
+	}
+	return slice
+}
+
+func ByteSliceShuffle(slice []byte) []byte {
+	for i := 0; i < len(slice); i++ {
+		a := rand.Intn(len(slice))
+		b := rand.Intn(len(slice))
+		slice[a], slice[b] = slice[b], slice[a]
+	}
+	return slice
+}
+
 func FirstUpper(s string) string {
 	r := []rune(s)
 
@@ -286,3 +304,10 @@ func ClearDirectEnvRegistry() {
 	directRpc = nil
 	initDirectEnv = sync.Once{}
 }
+
+func GetNonNegative(originValue int64) int64 {
+	if originValue > 0 {
+		return originValue
+	}
+	return 0x7fffffffffffffff & originValue
+}
diff --git a/default.go b/default.go
index 52e41cd7..ef77faf7 100644
--- a/default.go
+++ b/default.go
@@ -82,8 +82,17 @@ func GetDefaultManageHandlers() map[string]http.Handler {
 		defaultManageHandlers["/registry/list"] = dynamicConfigurer
 		defaultManageHandlers["/registry/info"] = dynamicConfigurer
 
+		metaInfo := &MetaInfo{}
+		defaultManageHandlers["/meta/update"] = metaInfo
+		defaultManageHandlers["/meta/delete"] = metaInfo
+		defaultManageHandlers["/meta/get"] = metaInfo
+		defaultManageHandlers["/meta/getAll"] = metaInfo
+
 		hotReload := &HotReload{}
 		defaultManageHandlers["/reload/clusters"] = hotReload
+
+		runtimeHandler := &RuntimeHandler{}
+		defaultManageHandlers["/runtime/info"] = runtimeHandler
 	})
 	return defaultManageHandlers
 }
diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go
index 4ea84c15..45ce1c23 100644
--- a/endpoint/endpoint.go
+++ b/endpoint/endpoint.go
@@ -10,11 +10,12 @@ import (
 
 // ext name
 const (
-	Grpc              = "grpc"
-	Motan2            = "motan2"
-	Local             = "local"
-	Mock              = "mockEndpoint"
-	MotanV1Compatible = "motanV1Compatible"
+	Grpc   = "grpc"
+	Motan2 = "motan2"
+	// Motan1 endpoint is to support dynamic configuration. Golang cannot build motan1 request
+	Motan1 = "motan"
+	Local  = "local"
+	Mock   = "mockEndpoint"
 )
 
 const (
@@ -26,7 +27,7 @@ var idOffset uint64 // id generator offset
 
 func RegistDefaultEndpoint(extFactory motan.ExtensionFactory) {
 	extFactory.RegistExtEndpoint(Motan2, func(url *motan.URL) motan.EndPoint {
-		return &MotanEndpoint{url: url}
+		return &MotanCommonEndpoint{url: url}
 	})
 
 	extFactory.RegistExtEndpoint(Grpc, func(url *motan.URL) motan.EndPoint {
@@ -41,7 +42,7 @@ func RegistDefaultEndpoint(extFactory motan.ExtensionFactory) {
 		return &MockEndpoint{URL: url}
 	})
 
-	extFactory.RegistExtEndpoint(MotanV1Compatible, func(url *motan.URL) motan.EndPoint {
+	extFactory.RegistExtEndpoint(Motan1, func(url *motan.URL) motan.EndPoint {
 		return &MotanCommonEndpoint{url: url}
 	})
 }
@@ -65,6 +66,12 @@ type MockEndpoint struct {
 	MockResponse motan.Response
 }
 
+func (m *MockEndpoint) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: m.GetName(),
+	}
+}
+
 func (m *MockEndpoint) GetName() string {
 	return "mockEndpoint"
 }
diff --git a/endpoint/grpcEndpoint.go b/endpoint/grpcEndpoint.go
index 997ea7be..258a18b0 100644
--- a/endpoint/grpcEndpoint.go
+++ b/endpoint/grpcEndpoint.go
@@ -7,8 +7,12 @@ import (
 	motan "github.com/weibocom/motan-go/core"
 	"github.com/weibocom/motan-go/log"
 	"golang.org/x/net/context"
-	grpc "google.golang.org/grpc"
-	metadata "google.golang.org/grpc/metadata"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/metadata"
+)
+
+const (
+	GRPCSerialNum = 1
 )
 
 type GrpcEndPoint struct {
@@ -17,9 +21,11 @@ type GrpcEndPoint struct {
 	proxy    bool
 }
 
-const (
-	GRPCSerialNum = 1
-)
+func (g *GrpcEndPoint) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: g.GetName(),
+	}
+}
 
 func (g *GrpcEndPoint) Initialize() {
 	grpcconn, err := grpc.Dial((g.url.Host + ":" + strconv.Itoa((int)(g.url.Port))), grpc.WithInsecure(), grpc.WithCodec(&agentCodec{}))
diff --git a/endpoint/localEndpoint.go b/endpoint/localEndpoint.go
index 4ed863a7..e2b624d8 100644
--- a/endpoint/localEndpoint.go
+++ b/endpoint/localEndpoint.go
@@ -10,6 +10,12 @@ type LocalEndpoint struct {
 	provider motan.Provider
 }
 
+func (l *LocalEndpoint) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: l.GetName(),
+	}
+}
+
 func (l *LocalEndpoint) GetName() string {
 	return "localEndpoint"
 }
diff --git a/endpoint/mockDynamicEndpoint.go b/endpoint/mockDynamicEndpoint.go
new file mode 100644
index 00000000..4361f450
--- /dev/null
+++ b/endpoint/mockDynamicEndpoint.go
@@ -0,0 +1,91 @@
+package endpoint
+
+import (
+	motan "github.com/weibocom/motan-go/core"
+	mpro "github.com/weibocom/motan-go/protocol"
+	"strconv"
+	"sync"
+	"sync/atomic"
+)
+
+type MockDynamicEndpoint struct {
+	URL           *motan.URL
+	available     bool
+	DynamicWeight int64
+	StaticWeight  int64
+	Count         int64
+	dynamicMeta   sync.Map
+}
+
+func (m *MockDynamicEndpoint) GetName() string {
+	return "mockEndpoint"
+}
+
+func (m *MockDynamicEndpoint) GetURL() *motan.URL {
+	return m.URL
+}
+
+func (m *MockDynamicEndpoint) SetURL(url *motan.URL) {
+	m.URL = url
+}
+
+func (m *MockDynamicEndpoint) IsAvailable() bool {
+	return m.available
+}
+
+func (m *MockDynamicEndpoint) SetAvailable(a bool) {
+	m.available = a
+}
+
+func (m *MockDynamicEndpoint) SetProxy(proxy bool) {}
+
+func (m *MockDynamicEndpoint) SetSerialization(s motan.Serialization) {}
+
+func (m *MockDynamicEndpoint) Call(request motan.Request) motan.Response {
+	if isMetaServiceRequest(request) {
+		resMap := make(map[string]string)
+		m.dynamicMeta.Range(func(key, value interface{}) bool {
+			resMap[key.(string)] = value.(string)
+			return true
+		})
+		atomic.AddInt64(&m.Count, 1)
+		return &motan.MotanResponse{ProcessTime: 1, Value: resMap}
+	}
+	atomic.AddInt64(&m.Count, 1)
+	return &motan.MotanResponse{ProcessTime: 1, Value: "ok"}
+}
+
+func (m *MockDynamicEndpoint) Destroy() {}
+
+func (m *MockDynamicEndpoint) GetRuntimeInfo() map[string]interface{} {
+	return make(map[string]interface{})
+}
+
+func (m *MockDynamicEndpoint) SetWeight(isDynamic bool, weight int64) {
+	if isDynamic {
+		m.DynamicWeight = weight
+		m.dynamicMeta.Store(motan.DefaultMetaPrefix+motan.WeightMetaSuffixKey, strconv.Itoa(int(weight)))
+	} else {
+		m.StaticWeight = weight
+		m.URL.PutParam(motan.DefaultMetaPrefix+motan.WeightMetaSuffixKey, strconv.Itoa(int(weight)))
+	}
+}
+
+func NewMockDynamicEndpoint(url *motan.URL) *MockDynamicEndpoint {
+	return &MockDynamicEndpoint{
+		URL:       url,
+		available: true,
+	}
+}
+
+func NewMockDynamicEndpointWithWeight(url *motan.URL, staticWeight int64) *MockDynamicEndpoint {
+	res := NewMockDynamicEndpoint(url)
+	res.StaticWeight = staticWeight
+	res.URL.PutParam(motan.DefaultMetaPrefix+motan.WeightMetaSuffixKey, strconv.Itoa(int(staticWeight)))
+	return res
+}
+
+func isMetaServiceRequest(request motan.Request) bool {
+	return request != nil && "com.weibo.api.motan.runtime.meta.MetaService" == request.GetServiceName() &&
+		"getDynamicMeta" == request.GetMethod() && "y" == request.GetAttachment(mpro.MFrameworkService)
+}
diff --git a/endpoint/motanCommonEndpoint.go b/endpoint/motanCommonEndpoint.go
index 36c4a659..538967ec 100644
--- a/endpoint/motanCommonEndpoint.go
+++ b/endpoint/motanCommonEndpoint.go
@@ -6,6 +6,7 @@ import (
 	"github.com/panjf2000/ants/v2"
 	motan "github.com/weibocom/motan-go/core"
 	vlog "github.com/weibocom/motan-go/log"
+	"github.com/weibocom/motan-go/metrics"
 	mpro "github.com/weibocom/motan-go/protocol"
 	"net"
 	"strconv"
@@ -15,6 +16,11 @@ import (
 	"time"
 )
 
+const (
+	KeepaliveHeartbeat uint32 = 1
+	KeepaliveProfile   uint32 = 2
+)
+
 var (
 	streamPool = sync.Pool{New: func() interface{} {
 		return &Stream{
@@ -31,7 +37,7 @@ type MotanCommonEndpoint struct {
 	channels                     *ChannelPool
 	destroyed                    bool
 	destroyCh                    chan struct{}
-	available                    bool
+	available                    atomic.Value
 	errorCount                   uint32
 	proxy                        bool
 	errorCountThreshold          int64
@@ -45,14 +51,24 @@ type MotanCommonEndpoint struct {
 	heartbeatVersion             int
 	gzipSize                     int
 
-	keepaliveRunning bool
+	keepaliveRunning atomic.Value
+	keepaliveType    uint32
 	serialization    motan.Serialization
 
 	DefaultVersion int // default encode version
 }
 
+func (m *MotanCommonEndpoint) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey:             m.GetName(),
+		motan.RuntimeErrorCountKey:       atomic.LoadUint32(&m.errorCount),
+		motan.RuntimeKeepaliveRunningKey: m.keepaliveRunning.Load(),
+		motan.RuntimeKeepaliveTypeKey:    atomic.LoadUint32(&m.keepaliveType),
+	}
+}
+
 func (m *MotanCommonEndpoint) setAvailable(available bool) {
-	m.available = available
+	m.available.Store(available)
 }
 
 func (m *MotanCommonEndpoint) SetSerialization(s motan.Serialization) {
@@ -77,6 +93,7 @@ func (m *MotanCommonEndpoint) Initialize() {
 	m.lazyInit = m.url.GetBoolValue(motan.LazyInit, defaultLazyInit)
 	asyncInitConnection := m.url.GetBoolValue(motan.AsyncInitConnection, GetDefaultMotanEPAsynInit())
 	m.heartbeatVersion = -1
+	m.keepaliveRunning.Store(false)
 	m.DefaultVersion = mpro.Version2
 	m.gzipSize = int(m.url.GetIntValue(motan.GzipSizeKey, 0))
 	factory := func() (net.Conn, error) {
@@ -136,23 +153,21 @@ func (m *MotanCommonEndpoint) Call(request motan.Request) motan.Response {
 
 	if m.channels == nil {
 		vlog.Errorf("motanEndpoint %s error: channels is null", m.url.GetAddressStr())
-		m.recordErrAndKeepalive()
-		return m.defaultErrMotanResponse(request, "motanEndpoint error: channels is null")
-	}
-	startTime := time.Now().UnixNano()
-	if rc.AsyncCall {
-		rc.Result.StartTime = startTime
+		resp := m.defaultErrMotanResponse(request, "motanEndpoint error: channels is null")
+		m.recordErrAndKeepalive(resp.GetException())
+		return resp
 	}
 	// get a channel
 	channel, err := m.channels.Get()
 	if err != nil {
 		vlog.Errorf("motanEndpoint %s error: can not get a channel, msg: %s", m.url.GetAddressStr(), err.Error())
-		m.recordErrAndKeepalive()
-		return motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{
+		resp := motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{
 			ErrCode: motan.ENoChannel,
 			ErrMsg:  "can not get a channel",
 			ErrType: motan.ServiceException,
 		})
+		m.recordErrAndKeepalive(resp.GetException())
+		return resp
 	}
 	deadline := m.GetRequestTimeout(request)
 	// do call
@@ -160,15 +175,13 @@ func (m *MotanCommonEndpoint) Call(request motan.Request) motan.Response {
 	response, err := channel.Call(request, deadline, rc)
 	if err != nil {
 		vlog.Errorf("motanEndpoint call fail. ep:%s, req:%s, error: %s", m.url.GetAddressStr(), motan.GetReqInfo(request), err.Error())
-		m.recordErrAndKeepalive()
-		return m.defaultErrMotanResponse(request, "channel call error:"+err.Error())
-	}
-	if rc.AsyncCall {
-		return defaultAsyncResponse
+		resp := m.defaultErrMotanResponse(request, "channel call error:"+err.Error())
+		m.recordErrAndKeepalive(resp.GetException())
+		return resp
 	}
 	excep := response.GetException()
 	if excep != nil && excep.ErrType != motan.BizException {
-		m.recordErrAndKeepalive()
+		m.recordErrAndKeepalive(excep)
 	} else {
 		// reset errorCount
 		m.resetErr()
@@ -217,7 +230,7 @@ func (m *MotanCommonEndpoint) initChannelPoolWithRetry(factory ConnFactory, conf
 			for {
 				select {
 				case <-ticker.C:
-					channels, err := NewChannelPool(m.clientConnection, factory, config, m.serialization, m.lazyInit)
+					channels, err = NewChannelPool(m.clientConnection, factory, config, m.serialization, m.lazyInit)
 					if err == nil {
 						m.channels = channels
 						m.setAvailable(true)
@@ -236,19 +249,29 @@ func (m *MotanCommonEndpoint) initChannelPoolWithRetry(factory ConnFactory, conf
 	}
 }
 
-func (m *MotanCommonEndpoint) recordErrAndKeepalive() {
+func (m *MotanCommonEndpoint) recordErrAndKeepalive(exception *motan.Exception) {
 	// errorCountThreshold <= 0 means not trigger keepalive
 	if m.errorCountThreshold > 0 {
 		errCount := atomic.AddUint32(&m.errorCount, 1)
 		// ensure trigger keepalive
 		if errCount >= uint32(m.errorCountThreshold) {
 			m.setAvailable(false)
+			m.setKeepaliveType(exception)
 			vlog.Infoln("Referer disable:" + m.url.GetIdentity())
 			go m.keepalive()
 		}
 	}
 }
 
+func (m *MotanCommonEndpoint) setKeepaliveType(exception *motan.Exception) {
+	if exception != nil && exception.ErrCode == motan.EProviderNotExist &&
+		(strings.Contains(exception.ErrMsg, motan.ProviderNotExistPrefix)) {
+		atomic.StoreUint32(&m.keepaliveType, KeepaliveProfile)
+	} else {
+		atomic.StoreUint32(&m.keepaliveType, KeepaliveHeartbeat)
+	}
+}
+
 func (m *MotanCommonEndpoint) resetErr() {
 	atomic.StoreUint32(&m.errorCount, 0)
 }
@@ -261,16 +284,16 @@ func (m *MotanCommonEndpoint) keepalive() {
 		return
 	}
 	// ensure only one keepalive handler
-	if m.keepaliveRunning {
+	if v, ok := m.keepaliveRunning.Load().(bool); ok && v {
 		m.lock.Unlock()
 		return
 	}
-	m.keepaliveRunning = true
+	m.keepaliveRunning.Store(true)
 	m.lock.Unlock()
 
 	defer func() {
 		m.lock.Lock()
-		m.keepaliveRunning = false
+		m.keepaliveRunning.Store(false)
 		m.lock.Unlock()
 	}()
 
@@ -280,17 +303,12 @@ func (m *MotanCommonEndpoint) keepalive() {
 	for {
 		select {
 		case <-ticker.C:
-			if channel, err := m.channels.Get(); err != nil {
-				vlog.Infof("[keepalive] failed. url:%s, err:%s", m.url.GetIdentity(), err.Error())
+			if atomic.LoadUint32(&m.keepaliveType) == KeepaliveProfile {
+				m.profile()
 			} else {
-				_, err = channel.HeartBeat(m.heartbeatVersion)
-				if err == nil {
-					m.setAvailable(true)
-					m.resetErr()
-					vlog.Infof("[keepalive] heartbeat success. url: %s", m.url.GetIdentity())
+				if m.heartbeat() {
 					return
 				}
-				vlog.Infof("[keepalive] heartbeat failed. url:%s, err:%s", m.url.GetIdentity(), err.Error())
 			}
 		case <-m.destroyCh:
 			return
@@ -298,6 +316,29 @@ func (m *MotanCommonEndpoint) keepalive() {
 	}
 }
 
+func (m *MotanCommonEndpoint) heartbeat() bool {
+	if channel, err := m.channels.Get(); err != nil {
+		vlog.Infof("[keepalive] failed. url:%s, err:%s", m.url.GetIdentity(), err.Error())
+	} else {
+		_, err = channel.HeartBeat(m.heartbeatVersion)
+		if err == nil {
+			m.setAvailable(true)
+			m.resetErr()
+			vlog.Infof("[keepalive] heartbeat success. url: %s", m.url.GetIdentity())
+			return true
+		}
+		vlog.Infof("[keepalive] heartbeat failed. url:%s, err:%s", m.url.GetIdentity(), err.Error())
+	}
+	return false
+}
+
+func (m *MotanCommonEndpoint) profile() {
+	metrics.AddGaugeWithKeys(metrics.DefaultRuntimeCircuitBreakerGroup+"-"+motan.GetApplication(), "",
+		metrics.DefaultRuntimeCircuitBreakerService,
+		[]string{metrics.DefaultStatRole, metrics.DefaultRuntimeErrorApplication, strconv.Itoa(motan.GetMport()) + "-" + m.url.Path},
+		"", 1)
+}
+
 func (m *MotanCommonEndpoint) defaultErrMotanResponse(request motan.Request, errMsg string) motan.Response {
 	return motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{
 		ErrCode: 400,
@@ -319,7 +360,10 @@ func (m *MotanCommonEndpoint) SetURL(url *motan.URL) {
 }
 
 func (m *MotanCommonEndpoint) IsAvailable() bool {
-	return m.available
+	if m.available.Load() == nil {
+		return false
+	}
+	return m.available.Load().(bool)
 }
 
 type Channel struct {
@@ -430,10 +474,6 @@ func (s *Stream) Send() (err error) {
 			if s.rc.Tc != nil {
 				s.rc.Tc.PutReqSpan(&motan.Span{Name: motan.Send, Addr: s.channel.address, Time: sendTime})
 			}
-			if s.rc.AsyncCall {
-				// only return send success, it can release in Stream.notify
-				s.canRelease.Store(true)
-			}
 		}
 		return nil
 	case <-s.timer.C:
@@ -522,24 +562,6 @@ func (s *Stream) notify(msg interface{}, t time.Time) {
 			s.rc.Tc.PutResSpan(&motan.Span{Name: motan.Decode, Addr: s.channel.address, Time: decodeTime})
 			s.rc.Tc.PutResSpan(&motan.Span{Name: motan.Convert, Addr: s.channel.address, Time: time.Now()})
 		}
-		if s.rc.AsyncCall {
-			defer func() {
-				s.RemoveFromChannel()
-				releaseStream(s)
-			}()
-			result := s.rc.Result
-			if err != nil {
-				result.Error = err
-				result.Done <- result
-				return
-			}
-			if err = res.ProcessDeserializable(result.Reply); err != nil {
-				result.Error = err
-			}
-			res.SetProcessTime((time.Now().UnixNano() - result.StartTime) / 1000000)
-			result.Done <- result
-			return
-		}
 	}
 	s.res = res
 	s.recvNotifyCh <- struct{}{}
@@ -560,11 +582,7 @@ func (c *Channel) newStream(req motan.Request, rc *motan.RPCContext, deadline ti
 	s.req = req
 	s.deadline = time.Now().Add(deadline)
 	s.rc = rc
-	if rc != nil && rc.AsyncCall { // release by Stream.Notify
-		s.canRelease.Store(false)
-	} else { // release by Channel self
-		s.canRelease.Store(true)
-	}
+	s.canRelease.Store(true)
 	c.streamLock.Lock()
 	c.streams[s.streamId] = s
 	c.streamLock.Unlock()
@@ -608,24 +626,17 @@ func (s *Stream) RemoveFromChannel() bool {
 
 // Call send request to the server.
 //
-//	about return: exception in response will record error count, err will not.
+//	about return: exception in response will record error Count, err will not.
 func (c *Channel) Call(req motan.Request, deadline time.Duration, rc *motan.RPCContext) (motan.Response, error) {
 	stream, err := c.newStream(req, rc, deadline)
 	if err != nil {
 		return nil, err
 	}
-	defer func() {
-		if rc == nil || !rc.AsyncCall {
-			releaseStream(stream)
-		}
-	}()
+	defer releaseStream(stream)
 
 	if err = stream.Send(); err != nil {
 		return nil, err
 	}
-	if rc != nil && rc.AsyncCall {
-		return nil, nil
-	}
 	return stream.Recv()
 }
 
diff --git a/endpoint/motanCommonEndpoint_test.go b/endpoint/motanCommonEndpoint_test.go
index 75c460c5..2957c717 100644
--- a/endpoint/motanCommonEndpoint_test.go
+++ b/endpoint/motanCommonEndpoint_test.go
@@ -8,12 +8,31 @@ import (
 	"github.com/weibocom/motan-go/serialize"
 	"net"
 	"runtime"
+	"strconv"
+	"sync/atomic"
 	"testing"
 	"time"
 )
 
+func TestNotInitCommonEndpoint(t *testing.T) {
+	url := &motan.URL{Port: 8989, Protocol: "motan"}
+	url.PutParam(motan.TimeOutKey, "100")
+	url.PutParam(motan.AsyncInitConnection, "false")
+	ep := &MotanCommonEndpoint{}
+	ep.SetURL(url)
+
+	ep.SetProxy(true)
+	ep.SetSerialization(&serialize.SimpleSerialization{})
+	assert.Equal(t, false, ep.IsAvailable())
+	assert.Nil(t, ep.keepaliveRunning.Load())
+
+	ep.Initialize()
+	assert.Equal(t, true, ep.IsAvailable())
+	assert.Equal(t, false, ep.keepaliveRunning.Load())
+}
+
 func TestGetV1Name(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible"}
+	url := &motan.URL{Port: 8989, Protocol: "motan"}
 	url.PutParam(motan.TimeOutKey, "100")
 	url.PutParam(motan.AsyncInitConnection, "false")
 	ep := &MotanCommonEndpoint{}
@@ -32,7 +51,7 @@ func TestGetV1Name(t *testing.T) {
 }
 
 func TestV1RecordErrEmptyThreshold(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible"}
+	url := &motan.URL{Port: 8989, Protocol: "motan"}
 	url.PutParam(motan.TimeOutKey, "100")
 	url.PutParam(motan.ClientConnectionKey, "1")
 	url.PutParam(motan.AsyncInitConnection, "false")
@@ -54,9 +73,10 @@ func TestV1RecordErrEmptyThreshold(t *testing.T) {
 }
 
 func TestV1RecordErrWithErrThreshold(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible"}
-	url.PutParam(motan.TimeOutKey, "100")
-	url.PutParam(motan.ErrorCountThresholdKey, "5")
+	errorCountThreshold := 5
+	url := &motan.URL{Port: 8989, Protocol: "motan"}
+	url.PutParam(motan.TimeOutKey, "1100")
+	url.PutParam(motan.ErrorCountThresholdKey, strconv.Itoa(errorCountThreshold))
 	url.PutParam(motan.ClientConnectionKey, "1")
 	url.PutParam(motan.AsyncInitConnection, "false")
 	ep := &MotanCommonEndpoint{}
@@ -65,14 +85,25 @@ func TestV1RecordErrWithErrThreshold(t *testing.T) {
 	ep.SetSerialization(&serialize.SimpleSerialization{})
 	ep.Initialize()
 	assert.Equal(t, 1, ep.clientConnection)
-	for j := 0; j < 10; j++ {
+	for j := 0; j < errorCountThreshold; j++ {
 		request := &motan.MotanRequest{ServiceName: "test", Method: "test"}
 		request.Attachment = motan.NewStringMap(0)
-		ep.Call(request)
-		if j < 4 {
+		request.Attachment.Store("exception", "other_exception")
+		request.Attachment.Store("sleep_time", "200")
+		resp := ep.Call(request)
+		assert.NotNil(t, resp)
+		assert.NotNil(t, resp.GetException())
+		assert.Equal(t, motan.EUnkonwnMsg, resp.GetException().ErrCode)
+		if j < errorCountThreshold-1 {
 			assert.True(t, ep.IsAvailable())
 		} else {
-			assert.False(t, ep.IsAvailable())
+			assert.Equal(t, KeepaliveHeartbeat, atomic.LoadUint32(&ep.keepaliveType))
+			// wait keepalive goroutine set keepaliveRunning status
+			time.Sleep(time.Millisecond * 100)
+			assert.True(t, ep.keepaliveRunning.Load().(bool))
+			time.Sleep(ep.keepaliveInterval * 2)
+			assert.True(t, ep.IsAvailable())
+			assert.False(t, ep.keepaliveRunning.Load().(bool))
 		}
 	}
 	<-ep.channels.channels
@@ -83,35 +114,57 @@ func TestV1RecordErrWithErrThreshold(t *testing.T) {
 	time.Sleep(time.Second * 2)
 
 	assertChanelStreamEmpty(ep, t)
-	//assert.True(t, ep.IsAvailable())
 	ep.Destroy()
 }
 
-func TestMotanCommonEndpoint_SuccessCall(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible"}
-	url.PutParam(motan.TimeOutKey, "2000")
-	url.PutParam(motan.ErrorCountThresholdKey, "1")
-	url.PutParam(motan.ClientConnectionKey, "1")
+func TestNotFoundProviderCircuitBreaker(t *testing.T) {
+	url := &motan.URL{Port: 8989, Protocol: "motan"}
+	url.PutParam(motan.TimeOutKey, "1200")
+	url.PutParam(motan.ErrorCountThresholdKey, "5")
+	url.PutParam(motan.ClientConnectionKey, "10")
 	url.PutParam(motan.AsyncInitConnection, "false")
 	ep := &MotanCommonEndpoint{}
 	ep.SetURL(url)
 	ep.SetSerialization(&serialize.SimpleSerialization{})
 	ep.Initialize()
-	assert.Equal(t, 1, ep.clientConnection)
-	request := &motan.MotanRequest{ServiceName: "test", Method: "test"}
-	request.Attachment = motan.NewStringMap(0)
-	res := ep.Call(request)
-	assert.Nil(t, res.GetException())
-	v := res.GetValue()
-	s, ok := v.(string)
+	assert.Equal(t, 10, ep.clientConnection)
+	for j := 0; j < 10; j++ {
+		request := &motan.MotanRequest{ServiceName: "test", Method: "test"}
+		request.Attachment = motan.NewStringMap(0)
+		request.Attachment.Store("exception", "not_found_provider")
+		res := ep.Call(request)
+		assert.Equal(t, motan.ProviderNotExistPrefix, res.GetException().ErrMsg)
+		if j < 4 {
+			assert.True(t, ep.IsAvailable())
+		} else {
+			assert.False(t, ep.IsAvailable())
+			assert.Equal(t, KeepaliveProfile, atomic.LoadUint32(&ep.keepaliveType))
+		}
+	}
+
+	// runtime info
+	info := ep.GetRuntimeInfo()
+	name, ok := info[motan.RuntimeNameKey]
 	assert.True(t, ok)
-	assert.Equal(t, s, "hello")
+	assert.Equal(t, ep.GetName(), name)
+
+	errorCount, ok := info[motan.RuntimeErrorCountKey]
+	assert.True(t, ok)
+	assert.Equal(t, uint32(10), errorCount.(uint32))
+
+	keepaliveRunning, ok := info[motan.RuntimeKeepaliveRunningKey]
+	assert.True(t, ok)
+	assert.Equal(t, ep.keepaliveRunning.Load(), keepaliveRunning)
+
+	keepaliveType, ok := info[motan.RuntimeKeepaliveTypeKey]
+	assert.Equal(t, ep.keepaliveType, keepaliveType)
 
 	assertChanelStreamEmpty(ep, t)
+	ep.Destroy()
 }
 
-func TestMotanCommonEndpoint_AsyncCall(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible"}
+func TestMotanCommonEndpoint_SuccessCall(t *testing.T) {
+	url := &motan.URL{Port: 8989, Protocol: "motan"}
 	url.PutParam(motan.TimeOutKey, "2000")
 	url.PutParam(motan.ErrorCountThresholdKey, "1")
 	url.PutParam(motan.ClientConnectionKey, "1")
@@ -121,20 +174,20 @@ func TestMotanCommonEndpoint_AsyncCall(t *testing.T) {
 	ep.SetSerialization(&serialize.SimpleSerialization{})
 	ep.Initialize()
 	assert.Equal(t, 1, ep.clientConnection)
-	var resStr string
-	request := &motan.MotanRequest{ServiceName: "test", Method: "test", RPCContext: &motan.RPCContext{AsyncCall: true, Result: &motan.AsyncResult{Reply: &resStr, Done: make(chan *motan.AsyncResult, 5)}}}
+	request := &motan.MotanRequest{ServiceName: "test", Method: "test"}
 	request.Attachment = motan.NewStringMap(0)
 	res := ep.Call(request)
 	assert.Nil(t, res.GetException())
-	resp := <-request.GetRPCContext(false).Result.Done
-	assert.Nil(t, resp.Error)
-	assert.Equal(t, resStr, "hello")
+	v := res.GetValue()
+	s, ok := v.(string)
+	assert.True(t, ok)
+	assert.Equal(t, s, "hello")
 
 	assertChanelStreamEmpty(ep, t)
 }
 
 func TestMotanCommonEndpoint_ErrorCall(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible"}
+	url := &motan.URL{Port: 8989, Protocol: "motan"}
 	url.PutParam(motan.TimeOutKey, "100")
 	url.PutParam(motan.ErrorCountThresholdKey, "1")
 	url.PutParam(motan.ClientConnectionKey, "1")
@@ -150,6 +203,8 @@ func TestMotanCommonEndpoint_ErrorCall(t *testing.T) {
 	res := ep.Call(request)
 	fmt.Println(res.GetException().ErrMsg)
 	assert.False(t, ep.IsAvailable())
+	assert.Equal(t, KeepaliveHeartbeat, atomic.LoadUint32(&ep.keepaliveType))
+
 	time.Sleep(1 * time.Millisecond)
 	beforeNGoroutine := runtime.NumGoroutine()
 	ep.Call(request)
@@ -161,7 +216,7 @@ func TestMotanCommonEndpoint_ErrorCall(t *testing.T) {
 }
 
 func TestMotanCommonEndpoint_RequestTimeout(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible"}
+	url := &motan.URL{Port: 8989, Protocol: "motan"}
 	url.PutParam(motan.TimeOutKey, "100")
 	url.PutParam(motan.ErrorCountThresholdKey, "1")
 	url.PutParam(motan.ClientConnectionKey, "1")
@@ -189,7 +244,7 @@ func TestMotanCommonEndpoint_RequestTimeout(t *testing.T) {
 }
 
 func TestV1LazyInit(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible", Parameters: map[string]string{"lazyInit": "true"}}
+	url := &motan.URL{Port: 8989, Protocol: "motan", Parameters: map[string]string{"lazyInit": "true"}}
 	url.PutParam(motan.TimeOutKey, "100")
 	url.PutParam(motan.ErrorCountThresholdKey, "1")
 	url.PutParam(motan.ClientConnectionKey, "1")
@@ -216,7 +271,7 @@ func TestV1LazyInit(t *testing.T) {
 }
 
 func TestV1AsyncInit(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible", Parameters: map[string]string{"asyncInitConnection": "true"}}
+	url := &motan.URL{Port: 8989, Protocol: "motan", Parameters: map[string]string{"asyncInitConnection": "true"}}
 	url.PutParam(motan.TimeOutKey, "100")
 	url.PutParam(motan.ErrorCountThresholdKey, "1")
 	url.PutParam(motan.ClientConnectionKey, "1")
@@ -228,42 +283,6 @@ func TestV1AsyncInit(t *testing.T) {
 	time.Sleep(time.Second * 5)
 }
 
-// TestMotanCommonEndpoint_AsyncCallNoResponse verify V2Channel streams memory leak when server not reply response
-// TODO::  bugs to be fixed
-func TestMotanCommonEndpoint_AsyncCallNoResponse(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motanV1Compatible"}
-	url.PutParam(motan.TimeOutKey, "2000")
-	url.PutParam(motan.ErrorCountThresholdKey, "1")
-	url.PutParam(motan.ClientConnectionKey, "1")
-	url.PutParam(motan.AsyncInitConnection, "false")
-	ep := &MotanCommonEndpoint{}
-	ep.SetURL(url)
-	ep.SetSerialization(&serialize.SimpleSerialization{})
-	ep.Initialize()
-	assert.Equal(t, 1, ep.clientConnection)
-	var resStr string
-	request := &motan.MotanRequest{ServiceName: "test", Method: "test", RPCContext: &motan.RPCContext{AsyncCall: true, Result: &motan.AsyncResult{Reply: &resStr, Done: make(chan *motan.AsyncResult, 5)}}}
-	request.Attachment = motan.NewStringMap(0)
-	// server not reply
-	request.SetAttachment("no_response", "true")
-
-	res := ep.Call(request)
-	assert.Nil(t, res.GetException())
-	timeoutTimer := time.NewTimer(time.Second * 3)
-	defer timeoutTimer.Stop()
-	select {
-	case <-request.GetRPCContext(false).Result.Done:
-		t.Errorf("unexpect condition, recv response singnal")
-	case <-timeoutTimer.C:
-		t.Logf("expect condition, not recv response singnal")
-	}
-
-	// Channel.streams can`t release stream
-	c := <-ep.channels.getChannels()
-	// it will be zero if server not reply response, bug to be fixed
-	assert.Equal(t, 1, len(c.streams))
-}
-
 func TestStreamPool(t *testing.T) {
 	var oldStream *Stream
 	// consume stream poll until call New func
@@ -315,9 +334,11 @@ func assertChanelStreamEmpty(ep *MotanCommonEndpoint, t *testing.T) {
 				assert.Equal(t, 0, len(c.streams))
 				c.streamLock.Unlock()
 
-				c.heartbeatLock.Lock()
-				assert.Equal(t, 0, len(c.heartbeats))
-				c.heartbeatLock.Unlock()
+				if v, ok := ep.keepaliveRunning.Load().(bool); ok && !v {
+					c.heartbeatLock.Lock()
+					assert.Equal(t, 0, len(c.heartbeats))
+					c.heartbeatLock.Unlock()
+				}
 			}
 		default:
 			return
diff --git a/endpoint/motanEndpoint.go b/endpoint/motanEndpoint.go
index 69fb0cf3..d1494645 100644
--- a/endpoint/motanEndpoint.go
+++ b/endpoint/motanEndpoint.go
@@ -29,8 +29,6 @@ var (
 	ErrRecvRequestTimeout       = fmt.Errorf("timeout err: receive request timeout")
 	ErrUnsupportedMessage       = fmt.Errorf("stream notify : Unsupported message")
 
-	defaultAsyncResponse = &motan.MotanResponse{Attachment: motan.NewStringMap(motan.DefaultAttachmentSize), RPCContext: &motan.RPCContext{AsyncCall: true}}
-
 	errPanic     = errors.New("panic error")
 	v2StreamPool = sync.Pool{New: func() interface{} {
 		return &V2Stream{
@@ -39,6 +37,7 @@ var (
 	}}
 )
 
+// Deprecated: Use MotanCommonEndpoint instead.
 type MotanEndpoint struct {
 	url                          *motan.URL
 	lock                         sync.Mutex
@@ -63,6 +62,12 @@ type MotanEndpoint struct {
 	serialization    motan.Serialization
 }
 
+func (m *MotanEndpoint) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: m.GetName(),
+	}
+}
+
 func (m *MotanEndpoint) setAvailable(available bool) {
 	m.available.Store(available)
 }
@@ -146,9 +151,6 @@ func (m *MotanEndpoint) Call(request motan.Request) motan.Response {
 		m.recordErrAndKeepalive()
 		return m.defaultErrMotanResponse(request, "motanEndpoint error: channels is null")
 	}
-	if rc.AsyncCall {
-		rc.Result.StartTime = time.Now().UnixNano()
-	}
 	// get a channel
 	channel, err := m.channels.Get()
 	if err != nil {
@@ -183,9 +185,6 @@ func (m *MotanEndpoint) Call(request motan.Request) motan.Response {
 		m.recordErrAndKeepalive()
 		return m.defaultErrMotanResponse(request, "channel call error:"+err.Error())
 	}
-	if rc.AsyncCall {
-		return defaultAsyncResponse
-	}
 	recvMsg.Header.SetProxy(m.proxy)
 	recvMsg.Header.RequestID = request.GetRequestID()
 	response, err := mpro.ConvertToResponse(recvMsg, m.serialization)
@@ -225,7 +224,7 @@ func (m *MotanEndpoint) initChannelPoolWithRetry(factory ConnFactory, config *Ch
 			for {
 				select {
 				case <-ticker.C:
-					channels, err := NewV2ChannelPool(m.clientConnection, factory, config, m.serialization, m.lazyInit)
+					channels, err = NewV2ChannelPool(m.clientConnection, factory, config, m.serialization, m.lazyInit)
 					if err == nil {
 						m.channels = channels
 						m.setAvailable(true)
@@ -419,17 +418,12 @@ func (s *V2Stream) Send() error {
 	ready := sendReady{message: s.sendMsg}
 	select {
 	case s.channel.sendCh <- ready:
-		// read/write data race
 		if s.rc != nil {
 			sendTime := time.Now()
 			s.rc.RequestSendTime = sendTime
 			if s.rc.Tc != nil {
 				s.rc.Tc.PutReqSpan(&motan.Span{Name: motan.Send, Addr: s.channel.address, Time: sendTime})
 			}
-			if s.rc.AsyncCall {
-				// only return send success, it can release in V2Stream.notify
-				s.canRelease.Store(true)
-			}
 		}
 		return nil
 	case <-s.timer.C:
@@ -479,30 +473,6 @@ func (s *V2Stream) notify(msg *mpro.Message, t time.Time) {
 			s.rc.Tc.PutResSpan(&motan.Span{Name: motan.Receive, Addr: s.channel.address, Time: t})
 			s.rc.Tc.PutResSpan(&motan.Span{Name: motan.Decode, Time: time.Now()})
 		}
-		if s.rc.AsyncCall {
-			defer func() {
-				s.RemoveFromChannel()
-				releaseV2Stream(s)
-			}()
-			msg.Header.SetProxy(s.rc.Proxy)
-			result := s.rc.Result
-			response, err := mpro.ConvertToResponse(msg, s.channel.serialization)
-			if err != nil {
-				vlog.Errorf("convert to response fail. ep: %s, requestid:%d, err:%s", s.channel.address, msg.Header.RequestID, err.Error())
-				result.Error = err
-				result.Done <- result
-				return
-			}
-			if err = response.ProcessDeserializable(result.Reply); err != nil {
-				result.Error = err
-			}
-			response.SetProcessTime((time.Now().UnixNano() - result.StartTime) / 1000000)
-			if s.rc.Tc != nil {
-				s.rc.Tc.PutResSpan(&motan.Span{Name: motan.Convert, Addr: s.channel.address, Time: time.Now()})
-			}
-			result.Done <- result
-			return
-		}
 	}
 
 	s.recvMsg = msg
@@ -543,11 +513,7 @@ func (c *V2Channel) newStream(msg *mpro.Message, rc *motan.RPCContext) (*V2Strea
 		c.streamLock.Unlock()
 		s.isHeartBeat = false
 	}
-	if rc != nil && rc.AsyncCall { // release by Stream.Notify
-		s.canRelease.Store(false)
-	} else { // release by Channel self
-		s.canRelease.Store(true)
-	}
+	s.canRelease.Store(true)
 	return s, nil
 }
 
@@ -579,19 +545,12 @@ func (c *V2Channel) Call(msg *mpro.Message, deadline time.Duration, rc *motan.RP
 	if err != nil {
 		return nil, err
 	}
-	defer func() {
-		if rc == nil || !rc.AsyncCall {
-			releaseV2Stream(stream)
-		}
-	}()
+	defer releaseV2Stream(stream)
 
 	stream.SetDeadline(deadline)
 	if err = stream.Send(); err != nil {
 		return nil, err
 	}
-	if rc != nil && rc.AsyncCall {
-		return nil, nil
-	}
 	return stream.Recv()
 }
 
diff --git a/endpoint/motanEndpoint_test.go b/endpoint/motanEndpoint_test.go
index acb98912..545a0d88 100644
--- a/endpoint/motanEndpoint_test.go
+++ b/endpoint/motanEndpoint_test.go
@@ -120,30 +120,6 @@ func TestMotanEndpoint_SuccessCall(t *testing.T) {
 	ep.Destroy()
 }
 
-func TestMotanEndpoint_AsyncCall(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motan2"}
-	url.PutParam(motan.TimeOutKey, "2000")
-	url.PutParam(motan.ErrorCountThresholdKey, "1")
-	url.PutParam(motan.ClientConnectionKey, "1")
-	url.PutParam(motan.AsyncInitConnection, "false")
-	ep := &MotanEndpoint{}
-	ep.SetURL(url)
-	ep.SetSerialization(&serialize.SimpleSerialization{})
-	ep.Initialize()
-	assert.Equal(t, 1, ep.clientConnection)
-	var resStr string
-	request := &motan.MotanRequest{ServiceName: "test", Method: "test", RPCContext: &motan.RPCContext{AsyncCall: true, Result: &motan.AsyncResult{Reply: &resStr, Done: make(chan *motan.AsyncResult, 5)}}}
-	request.Attachment = motan.NewStringMap(0)
-	res := ep.Call(request)
-	assert.Nil(t, res.GetException())
-	resp := <-request.GetRPCContext(false).Result.Done
-	assert.Nil(t, resp.Error)
-	assert.Equal(t, resStr, "hello")
-
-	assertV2ChanelStreamEmpty(ep, t)
-	ep.Destroy()
-}
-
 func TestMotanEndpoint_ErrorCall(t *testing.T) {
 	url := &motan.URL{Port: 8989, Protocol: "motan2"}
 	url.PutParam(motan.TimeOutKey, "100")
@@ -241,43 +217,6 @@ func TestAsyncInit(t *testing.T) {
 	time.Sleep(time.Second * 5)
 }
 
-// TestMotanEndpoint_AsyncCallNoResponse verify V2Channel streams memory leak when server not reply response
-// bugs to be fixed
-func TestMotanEndpoint_AsyncCallNoResponse(t *testing.T) {
-	url := &motan.URL{Port: 8989, Protocol: "motan2"}
-	url.PutParam(motan.TimeOutKey, "2000")
-	url.PutParam(motan.ErrorCountThresholdKey, "1")
-	url.PutParam(motan.ClientConnectionKey, "1")
-	url.PutParam(motan.AsyncInitConnection, "false")
-	ep := &MotanEndpoint{}
-	ep.SetURL(url)
-	ep.SetSerialization(&serialize.SimpleSerialization{})
-	ep.Initialize()
-	assert.Equal(t, 1, ep.clientConnection)
-	var resStr string
-	request := &motan.MotanRequest{ServiceName: "test", Method: "test", RPCContext: &motan.RPCContext{AsyncCall: true, Result: &motan.AsyncResult{Reply: &resStr, Done: make(chan *motan.AsyncResult, 5)}}}
-	request.Attachment = motan.NewStringMap(0)
-
-	// server not reply
-	request.SetAttachment("no_response", "true")
-
-	res := ep.Call(request)
-	assert.Nil(t, res.GetException())
-	timeoutTimer := time.NewTimer(time.Second * 3)
-	defer timeoutTimer.Stop()
-	select {
-	case <-request.GetRPCContext(false).Result.Done:
-		t.Errorf("unexpect condition, recv response singnal")
-	case <-timeoutTimer.C:
-		t.Logf("expect condition, not recv response singnal")
-	}
-
-	// Channel.streams cant`t release stream
-	c := <-ep.channels.getChannels()
-	// it will be zero if server not reply response, bug to be fixed
-	assert.Equal(t, 1, len(c.streams))
-}
-
 func TestV2StreamPool(t *testing.T) {
 	var oldStream *V2Stream
 	// consume v2stream poll until call New func
@@ -380,15 +319,17 @@ func handle(netListen net.Listener) {
 }
 
 func handleConnection(conn net.Conn, timeout int) {
-	reader := bufio.NewReader(conn)
-	decodeBuf := make([]byte, 100)
-	msg, err := protocol.Decode(reader, &decodeBuf)
-	if err != nil {
-		time.Sleep(time.Millisecond * 1000)
-		conn.Close()
-		return
+	for {
+		reader := bufio.NewReader(conn)
+		decodeBuf := make([]byte, 100)
+		msg, err := protocol.Decode(reader, &decodeBuf)
+		if err != nil {
+			time.Sleep(time.Millisecond * 1000)
+			conn.Close()
+			return
+		}
+		processMsg(msg, conn)
 	}
-	processMsg(msg, conn)
 }
 
 func processMsg(msg *protocol.Message, conn net.Conn) {
@@ -403,16 +344,40 @@ func processMsg(msg *protocol.Message, conn net.Conn) {
 	if msg.Header.IsHeartbeat() {
 		res = protocol.BuildHeartbeat(msg.Header.RequestID, protocol.Res)
 	} else {
-		time.Sleep(time.Millisecond * 1000)
-		serialization := &serialize.SimpleSerialization{}
-		resp := &motan.MotanResponse{
-			RequestID:   lastRequestID,
-			Value:       "hello",
-			ProcessTime: 1000,
+		sleepTimeStr, _ := msg.Metadata.Load("sleep_time")
+		sleepTimeValue, _ := strconv.Atoi(sleepTimeStr)
+		if sleepTimeValue <= 0 {
+			sleepTimeValue = 1000
 		}
+		time.Sleep(time.Millisecond * time.Duration(sleepTimeValue))
+		var resp *motan.MotanResponse
+		e, _ := msg.Metadata.Load("exception")
+		switch e {
+		case "not_found_provider":
+			resp = motan.BuildExceptionResponse(lastRequestID,
+				&motan.Exception{
+					ErrCode: motan.EProviderNotExist,
+					ErrMsg:  motan.ProviderNotExistPrefix,
+					ErrType: motan.ServiceException})
+		case "other_exception":
+			resp = motan.BuildExceptionResponse(lastRequestID,
+				&motan.Exception{
+					ErrCode: motan.EUnkonwnMsg,
+					ErrMsg:  "exception",
+					ErrType: motan.ServiceException})
+		default:
+			resp = &motan.MotanResponse{
+				RequestID:   lastRequestID,
+				Value:       "hello",
+				ProcessTime: 1000,
+			}
+		}
+		serialization := &serialize.SimpleSerialization{}
+
 		res, err = protocol.ConvertToResMessage(resp, serialization)
 		if err != nil {
 			conn.Close()
+			return
 		}
 	}
 	res.Header.RequestID = lastRequestID
diff --git a/filter/accessLog.go b/filter/accessLog.go
index ee980087..2b9cc0c6 100644
--- a/filter/accessLog.go
+++ b/filter/accessLog.go
@@ -18,6 +18,10 @@ type AccessLogFilter struct {
 	next motan.EndPointFilter
 }
 
+func (t *AccessLogFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(t)
+}
+
 func (t *AccessLogFilter) GetIndex() int {
 	return 1
 }
diff --git a/filter/circuitBreaker.go b/filter/circuitBreaker.go
index 0c986de1..9a379a4d 100644
--- a/filter/circuitBreaker.go
+++ b/filter/circuitBreaker.go
@@ -24,6 +24,10 @@ type CircuitBreakerFilter struct {
 	includeBizException bool
 }
 
+func (c *CircuitBreakerFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(c)
+}
+
 func (c *CircuitBreakerFilter) GetIndex() int {
 	return 20
 }
diff --git a/filter/circuitBreaker_test.go b/filter/circuitBreaker_test.go
index 687d2a3f..2b929882 100644
--- a/filter/circuitBreaker_test.go
+++ b/filter/circuitBreaker_test.go
@@ -189,6 +189,10 @@ func TestMockException(t *testing.T) {
 
 type mockEndPointFilter struct{}
 
+func (m *mockEndPointFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(m)
+}
+
 func (m *mockEndPointFilter) GetName() string {
 	return "mockEndPointFilter"
 }
@@ -226,6 +230,10 @@ func (m *mockEndPointFilter) GetType() int32 {
 
 type mockExceptionEPFilter struct{}
 
+func (m *mockExceptionEPFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(m)
+}
+
 func (m *mockExceptionEPFilter) GetName() string {
 	return "mockEndPointFilter"
 }
diff --git a/filter/clusterAccessLog.go b/filter/clusterAccessLog.go
index b4f359d5..af0328c3 100644
--- a/filter/clusterAccessLog.go
+++ b/filter/clusterAccessLog.go
@@ -10,6 +10,10 @@ type ClusterAccessLogFilter struct {
 	next motan.ClusterFilter
 }
 
+func (t *ClusterAccessLogFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(t)
+}
+
 func (t *ClusterAccessLogFilter) GetIndex() int {
 	return 1
 }
diff --git a/filter/clusterCircuitBreaker.go b/filter/clusterCircuitBreaker.go
index 78fe169c..b42c78fd 100644
--- a/filter/clusterCircuitBreaker.go
+++ b/filter/clusterCircuitBreaker.go
@@ -11,6 +11,10 @@ type ClusterCircuitBreakerFilter struct {
 	includeBizException bool
 }
 
+func (c *ClusterCircuitBreakerFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(c)
+}
+
 func (c *ClusterCircuitBreakerFilter) GetIndex() int {
 	return 20
 }
diff --git a/filter/clusterCircuitBreaker_test.go b/filter/clusterCircuitBreaker_test.go
index 6f370018..ce18cf44 100644
--- a/filter/clusterCircuitBreaker_test.go
+++ b/filter/clusterCircuitBreaker_test.go
@@ -81,6 +81,10 @@ func TestClusterCircuitBreakerOther(t *testing.T) {
 
 type mockClusterFilter struct{}
 
+func (c *mockClusterFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(c)
+}
+
 func (c *mockClusterFilter) GetIndex() int {
 	return 101
 }
diff --git a/filter/clusterMetrics.go b/filter/clusterMetrics.go
index 6aa2d840..52f9a35e 100644
--- a/filter/clusterMetrics.go
+++ b/filter/clusterMetrics.go
@@ -11,6 +11,10 @@ type ClusterMetricsFilter struct {
 	next motan.ClusterFilter
 }
 
+func (c *ClusterMetricsFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(c)
+}
+
 func (c *ClusterMetricsFilter) GetIndex() int {
 	return 5
 }
diff --git a/filter/failfast.go b/filter/failfast.go
index 7e03ecfe..e0713074 100644
--- a/filter/failfast.go
+++ b/filter/failfast.go
@@ -20,6 +20,10 @@ type FailfastFilter struct {
 	eps     *endpointStatus
 }
 
+func (e *FailfastFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(e)
+}
+
 type endpointStatus struct {
 	available                 bool
 	availMutex                sync.RWMutex
diff --git a/filter/filter.go b/filter/filter.go
index 38552ac8..6d9340b1 100644
--- a/filter/filter.go
+++ b/filter/filter.go
@@ -71,3 +71,11 @@ func getFilterStartTime(caller motan.Caller, request motan.Request) time.Time {
 		return time.Now()
 	}
 }
+
+func GetFilterRuntimeInfo(filter motan.Filter) map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey:  filter.GetName(),
+		motan.RuntimeIndexKey: filter.GetIndex(),
+		motan.RuntimeTypeKey:  filter.GetType(),
+	}
+}
diff --git a/filter/filter_test.go b/filter/filter_test.go
new file mode 100644
index 00000000..d3544c09
--- /dev/null
+++ b/filter/filter_test.go
@@ -0,0 +1,42 @@
+package filter
+
+import (
+	"github.com/stretchr/testify/assert"
+	"github.com/weibocom/motan-go/core"
+	"testing"
+)
+
+func TestGetFilterRuntimeInfo(t *testing.T) {
+	ext := &core.DefaultExtensionFactory{}
+	ext.Initialize()
+	RegistDefaultFilters(ext)
+
+	for _, name := range []string{
+		AccessLog,
+		Metrics,
+		CircuitBreaker,
+		FailFast,
+		Trace,
+		RateLimit,
+		ClusterAccessLog,
+		ClusterMetrics,
+		ClusterCircuitBreaker,
+	} {
+		filter := ext.GetFilter(name)
+		assert.NotNil(t, filter)
+		info := filter.GetRuntimeInfo()
+		assert.NotNil(t, info)
+
+		filterName, ok := info[core.RuntimeNameKey]
+		assert.True(t, ok)
+		assert.Equal(t, name, filterName)
+
+		index, ok := info[core.RuntimeIndexKey]
+		assert.True(t, ok)
+		assert.Equal(t, filter.GetIndex(), index)
+
+		filterType, ok := info[core.RuntimeTypeKey]
+		assert.True(t, ok)
+		assert.Equal(t, filter.GetType(), filterType)
+	}
+}
diff --git a/filter/metrics.go b/filter/metrics.go
index da34c3f4..0770d88a 100644
--- a/filter/metrics.go
+++ b/filter/metrics.go
@@ -26,6 +26,10 @@ type MetricsFilter struct {
 	next motan.EndPointFilter
 }
 
+func (m *MetricsFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(m)
+}
+
 func (m *MetricsFilter) GetIndex() int {
 	return 2
 }
diff --git a/filter/rateLimit.go b/filter/rateLimit.go
index e98692b1..4651e3bc 100644
--- a/filter/rateLimit.go
+++ b/filter/rateLimit.go
@@ -29,6 +29,10 @@ type RateLimitFilter struct {
 	url                *core.URL
 }
 
+func (r *RateLimitFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(r)
+}
+
 func (r *RateLimitFilter) NewFilter(url *core.URL) core.Filter {
 	ret := &RateLimitFilter{}
 	//init bucket
diff --git a/filter/tracing.go b/filter/tracing.go
index aa855087..a9014f77 100644
--- a/filter/tracing.go
+++ b/filter/tracing.go
@@ -119,6 +119,10 @@ type TracingFilter struct {
 	next core.EndPointFilter
 }
 
+func (t *TracingFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(t)
+}
+
 func (t *TracingFilter) SetNext(nextFilter core.EndPointFilter) {
 	t.next = nextFilter
 }
diff --git a/filter/tracing_test.go b/filter/tracing_test.go
index 1091dc7b..bb3b6a4d 100644
--- a/filter/tracing_test.go
+++ b/filter/tracing_test.go
@@ -380,6 +380,10 @@ type MockFilter struct {
 	filter func(caller core.Caller, request core.Request) core.Response
 }
 
+func (f *MockFilter) GetRuntimeInfo() map[string]interface{} {
+	return GetFilterRuntimeInfo(f)
+}
+
 func (*MockFilter) SetNext(nextFilter core.EndPointFilter) {
 	panic("implement me")
 }
@@ -421,6 +425,10 @@ type Provider struct {
 	url       *core.URL
 }
 
+func (p *Provider) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{}
+}
+
 func (p *Provider) SetService(s interface{}) {
 	if f, ok := s.(func(request core.Request) core.Response); ok {
 		p.handler = f
@@ -459,6 +467,10 @@ type Referrer struct {
 	call func(caller core.Caller, request core.Request) core.Response
 }
 
+func (r *Referrer) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{}
+}
+
 func (r *Referrer) GetName() string {
 	return r.name
 }
diff --git a/go.mod b/go.mod
index e7623234..c2ab501c 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/weibocom/motan-go
 
-go 1.11
+go 1.16
 
 require (
 	github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
@@ -16,15 +16,16 @@ require (
 	github.com/mitchellh/mapstructure v1.1.2
 	github.com/opentracing/opentracing-go v1.0.2
 	github.com/panjf2000/ants/v2 v2.9.0
+	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a
 	github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec
 	github.com/shirou/gopsutil/v3 v3.21.9
 	github.com/smartystreets/goconvey v1.6.4 // indirect
-	github.com/stretchr/testify v1.8.2
+	github.com/stretchr/testify v1.8.4
 	github.com/valyala/fasthttp v1.2.0
 	github.com/weibreeze/breeze-go v0.1.1
-	go.uber.org/atomic v1.4.0 // indirect
+	go.uber.org/atomic v1.4.0
 	go.uber.org/multierr v1.1.0 // indirect
 	go.uber.org/zap v1.10.0
 	golang.org/x/net v0.0.0-20201224014010-6772e930b67b
diff --git a/ha/backupRequestHA.go b/ha/backupRequestHA.go
index 8ee949d9..d95fe9dd 100644
--- a/ha/backupRequestHA.go
+++ b/ha/backupRequestHA.go
@@ -97,10 +97,9 @@ func (br *BackupRequestHA) Call(request motan.Request, loadBalance motan.LoadBal
 			break
 		}
 		// log & clone backup request
-		pr := request
+		pr := request.Clone().(motan.Request)
 		if i > 0 {
 			vlog.Infof("[backup request ha] delay %d request id: %d, service: %s, method: %s", delay, request.GetRequestID(), request.GetServiceName(), request.GetMethod())
-			pr = request.Clone().(motan.Request)
 		}
 		lastErrorCh = make(chan motan.Response, 1)
 		go func(postRequest motan.Request, endpoint motan.EndPoint, errorCh chan motan.Response) {
diff --git a/http/httpProxy.go b/http/httpProxy.go
index 33738633..c95cde60 100644
--- a/http/httpProxy.go
+++ b/http/httpProxy.go
@@ -552,6 +552,7 @@ func MotanRequestToFasthttpRequest(motanRequest core.Request, fasthttpRequest *f
 }
 
 // FasthttpResponseToMotanResponse convert a http response to a motan response
+// Notice:: Do not release HttpResponse early using this method
 // For http mesh server side, the httpServer response to the server agent but client need a motan response
 // Contrast to request convert, we put all headers to meta, an body maybe just use it with type []byte
 func FasthttpResponseToMotanResponse(motanResponse core.Response, fasthttpResponse *fasthttp.Response) {
diff --git a/http/httpRpc_test.go b/http/httpRpc_test.go
index bf33aaf2..4c99b6b2 100644
--- a/http/httpRpc_test.go
+++ b/http/httpRpc_test.go
@@ -57,6 +57,7 @@ func (s *APITestSuite) SetupTest() {
 	serverConfig, _ := config.NewConfigFromReader(bytes.NewReader([]byte(serverConf)))
 	go serverAgent.StartMotanAgentFromConfig(serverConfig)
 	time.Sleep(time.Second * 3)
+	motancore.SetMport(0)
 }
 
 func TestAPITestSuite(t *testing.T) {
diff --git a/lb/lb.go b/lb/lb.go
index 062c5333..48a59161 100644
--- a/lb/lb.go
+++ b/lb/lb.go
@@ -16,6 +16,7 @@ const (
 	Random            = "random"
 	Roundrobin        = "roundrobin"
 	ConsistentHashKey = "consistentHashKey"
+	WeightRoundRobin  = "wrr"
 )
 
 const (
@@ -39,6 +40,9 @@ func RegistDefaultLb(extFactory motan.ExtensionFactory) {
 	extFactory.RegistExtLb(ConsistentHashKey, NewWeightLbFunc(func(url *motan.URL) motan.LoadBalance {
 		return &ConsistentHashLB{url: url}
 	}))
+	extFactory.RegistExtLb(WeightRoundRobin, NewWeightLbFunc(func(url *motan.URL) motan.LoadBalance {
+		return NewWeightRondRobinLb(url)
+	}))
 }
 
 // WeightedLbWrapper support multi group weighted LB
@@ -55,6 +59,25 @@ func NewWeightLbFunc(newLb motan.NewLbFunc) motan.NewLbFunc {
 	}
 }
 
+func (w *WeightedLbWrapper) Destroy() {
+	destroyInnerRefers(w.refers)
+}
+
+func destroyInnerRefers(refers innerRefers) {
+	if v, ok := refers.(*singleGroupRefers); ok {
+		if vlb, ok := v.lb.(motan.Destroyable); ok {
+			vlb.Destroy()
+		}
+	}
+	if v, ok := refers.(*weightedRefers); ok {
+		for _, lb := range v.groupLb {
+			if vlb, ok := lb.(motan.Destroyable); ok {
+				vlb.Destroy()
+			}
+		}
+	}
+}
+
 func (w *WeightedLbWrapper) OnRefresh(endpoints []motan.EndPoint) {
 	if w.weightString == "" { //not weighted lb
 		vlog.Infof("WeightedLbWrapper: %s - OnRefresh:not have weight", w.url.GetIdentity())
@@ -130,7 +153,9 @@ func (w *WeightedLbWrapper) OnRefresh(endpoints []motan.EndPoint) {
 	}
 	wr.weightRing = motan.SliceShuffle(ring)
 	wr.ringSize = len(wr.weightRing)
+	oldRefers := w.refers
 	w.refers = wr
+	destroyInnerRefers(oldRefers)
 	vlog.Infof("WeightedLbWrapper: %s - OnRefresh: weight:%s", w.url.GetIdentity(), w.weightString)
 }
 
@@ -140,7 +165,9 @@ func (w *WeightedLbWrapper) onRefreshSingleGroup(endpoints []motan.EndPoint) {
 	} else {
 		lb := w.newLb(w.url)
 		lb.OnRefresh(endpoints)
+		oldRefers := w.refers
 		w.refers = &singleGroupRefers{lb: lb}
+		destroyInnerRefers(oldRefers)
 	}
 }
 
diff --git a/lb/weightRoundRobinLb.go b/lb/weightRoundRobinLb.go
new file mode 100644
index 00000000..9558344c
--- /dev/null
+++ b/lb/weightRoundRobinLb.go
@@ -0,0 +1,308 @@
+package lb
+
+import (
+	motan "github.com/weibocom/motan-go/core"
+	vlog "github.com/weibocom/motan-go/log"
+	"math/rand"
+	"sync"
+	"sync/atomic"
+)
+
+type WeightRoundRobinLB struct {
+	selector  Selector
+	mutex     sync.RWMutex
+	refresher *WeightedEpRefresher
+}
+
+func NewWeightRondRobinLb(url *motan.URL) *WeightRoundRobinLB {
+	lb := &WeightRoundRobinLB{}
+	lb.refresher = NewWeightEpRefresher(url, lb)
+	return lb
+}
+
+func (r *WeightRoundRobinLB) OnRefresh(endpoints []motan.EndPoint) {
+	r.refresher.RefreshWeightedHolders(endpoints)
+}
+
+func (r *WeightRoundRobinLB) Select(request motan.Request) motan.EndPoint {
+	selector := r.getSelector()
+	if selector == nil {
+		return nil
+	}
+	return selector.(Selector).DoSelect(request)
+}
+
+func (r *WeightRoundRobinLB) SelectArray(request motan.Request) []motan.EndPoint {
+	// cannot use select array, return nil
+	return nil
+}
+
+func (r *WeightRoundRobinLB) SetWeight(weight string) {}
+
+func (r *WeightRoundRobinLB) Destroy() {
+	r.refresher.Destroy()
+}
+
+func (r *WeightRoundRobinLB) getSelector() Selector {
+	r.mutex.RLock()
+	defer r.mutex.RUnlock()
+	return r.selector
+}
+
+func (r *WeightRoundRobinLB) setSelector(s Selector) {
+	r.mutex.Lock()
+	defer r.mutex.Unlock()
+	r.selector = s
+}
+
+func (r *WeightRoundRobinLB) NotifyWeightChange() {
+	var tempHolders []*WeightedEpHolder
+	h := r.refresher.weightedEpHolders.Load()
+	if h == nil {
+		// fast stop
+		return
+	}
+	tempHolders = h.([]*WeightedEpHolder)
+	weights := make([]int, len(tempHolders))
+	haveSameWeight := true
+	totalWeight := 0
+	for i := 0; i < len(tempHolders); i++ {
+		weights[i] = int(tempHolders[i].getWeight())
+		totalWeight += weights[i]
+		if weights[i] != weights[0] {
+			haveSameWeight = false
+		}
+	}
+	// if all eps have the same weight, then use RoundRobinSelector
+	if haveSameWeight { // use RoundRobinLoadBalance
+		selector := r.getSelector()
+		if selector != nil {
+			if v, ok := selector.(*roundRobinSelector); ok { // reuse the RoundRobinSelector
+				v.refresh(tempHolders)
+				return
+			}
+		}
+		// new RoundRobinLoadBalance
+		r.setSelector(newRoundRobinSelector(tempHolders))
+		vlog.Infoln("WeightRoundRobinLoadBalance use RoundRobinSelector. url:" + r.getURLLogInfo())
+		return
+	}
+	// find the GCD and divide the weights
+	gcd := findGcd(weights)
+	if gcd > 1 {
+		totalWeight = 0 // recalculate totalWeight
+		for i := 0; i < len(weights); i++ {
+			weights[i] /= gcd
+			totalWeight += weights[i]
+		}
+	}
+	// Check whether it is suitable to use WeightedRingSelector
+	if len(weights) <= wrMaxEpSize && totalWeight <= wrMaxTotalWeight {
+		r.setSelector(newWeightedRingSelector(tempHolders, totalWeight, weights))
+		vlog.Infoln("WeightRoundRobinLoadBalance use WeightedRingSelector. url:" + r.getURLLogInfo())
+		return
+	}
+	r.setSelector(newSlidingWindowWeightedRoundRobinSelector(tempHolders, weights))
+	vlog.Infoln("WeightRoundRobinLoadBalance use SlidingWindowWeightedRoundRobinSelector. url:" + r.getURLLogInfo())
+}
+
+func (r *WeightRoundRobinLB) getURLLogInfo() string {
+	url := r.refresher.url
+	if url == nil {
+		holders := r.refresher.weightedEpHolders.Load()
+		if v, ok := holders.([]*WeightedEpHolder); ok {
+			if len(v) > 0 {
+				url = v[0].ep.GetURL()
+			}
+		}
+	}
+	if url == nil {
+		return ""
+	}
+	return url.GetIdentity()
+}
+
+type Selector interface {
+	DoSelect(request motan.Request) motan.EndPoint
+}
+
+type roundRobinSelector struct {
+	weightHolders atomic.Value
+	idx           int64
+}
+
+func newRoundRobinSelector(holders []*WeightedEpHolder) *roundRobinSelector {
+	res := &roundRobinSelector{}
+	res.weightHolders.Store(holders)
+	return res
+}
+
+func (r *roundRobinSelector) DoSelect(request motan.Request) motan.EndPoint {
+	temp := r.weightHolders.Load()
+	if temp == nil {
+		return nil
+	}
+	tempHolders := temp.([]*WeightedEpHolder)
+	ep := tempHolders[int(motan.GetNonNegative(atomic.AddInt64(&r.idx, 1)))%len(tempHolders)].ep
+	if ep.IsAvailable() {
+		return ep
+	}
+	// if the ep is not available, loop section from random position
+	start := rand.Intn(len(tempHolders))
+	for i := 0; i < len(tempHolders); i++ {
+		ep = tempHolders[(i+start)%len(tempHolders)].ep
+		if ep.IsAvailable() {
+			return ep
+		}
+	}
+	return nil
+}
+
+func (r *roundRobinSelector) refresh(holders []*WeightedEpHolder) {
+	r.weightHolders.Store(holders)
+}
+
+const (
+	wrMaxEpSize      = 256
+	wrMaxTotalWeight = 256 * 20
+)
+
+type weightedRingSelector struct {
+	weightHolders []*WeightedEpHolder
+	idx           int64
+	weights       []int
+	weightRing    []byte
+}
+
+func newWeightedRingSelector(holders []*WeightedEpHolder, totalWeight int, weights []int) *weightedRingSelector {
+	wrs := &weightedRingSelector{
+		weightHolders: holders,
+		weightRing:    make([]byte, totalWeight),
+		weights:       weights,
+	}
+	wrs.initWeightRing()
+	return wrs
+}
+
+func (r *weightedRingSelector) initWeightRing() {
+	ringIndex := 0
+	for i := 0; i < len(r.weights); i++ {
+		for j := 0; j < r.weights[i]; j++ {
+			r.weightRing[ringIndex] = byte(i)
+			ringIndex++
+		}
+	}
+	if ringIndex != len(r.weightRing) {
+		vlog.Warningf("WeightedRingSelector initWeightRing with wrong totalWeight. expect:%d, actual: %d", len(r.weightRing), ringIndex)
+	}
+	r.weightRing = motan.ByteSliceShuffle(r.weightRing)
+
+}
+
+func (r *weightedRingSelector) DoSelect(request motan.Request) motan.EndPoint {
+	ep := r.weightHolders[r.getHolderIndex(int(motan.GetNonNegative(atomic.AddInt64(&r.idx, 1))))].ep
+	if ep.IsAvailable() {
+		return ep
+	}
+	// If the ep is not available, loop selection from random position
+	start := rand.Intn(len(r.weightRing))
+	for i := 0; i < len(r.weightRing); i++ {
+		// byte could indicate 0~255
+		ep = r.weightHolders[r.getHolderIndex(start+i)].getEp()
+		if ep.IsAvailable() {
+			return ep
+		}
+	}
+	return nil
+}
+
+func (r *weightedRingSelector) getHolderIndex(ringIndex int) int {
+	holderIndex := int(r.weightRing[ringIndex%len(r.weightRing)])
+	return holderIndex
+}
+
+const (
+	swwrDefaultWindowSize = 50
+)
+
+type slidingWindowWeightedRoundRobinSelector struct {
+	idx        int64
+	windowSize int
+	items      []*selectorItem
+}
+
+func newSlidingWindowWeightedRoundRobinSelector(holders []*WeightedEpHolder, weights []int) *slidingWindowWeightedRoundRobinSelector {
+	windowSize := len(weights)
+	if windowSize > swwrDefaultWindowSize {
+		windowSize = swwrDefaultWindowSize
+		// The window size cannot be divided by the number of referers, which ensures that the starting position
+		// of the window will gradually change during sliding
+		for len(weights)%windowSize == 0 {
+			windowSize--
+		}
+	}
+	items := make([]*selectorItem, 0, len(holders))
+	for i := 0; i < len(weights); i++ {
+		items = append(items, newSelectorItem(holders[i].getEp(), weights[i]))
+	}
+	return &slidingWindowWeightedRoundRobinSelector{
+		items:      items,
+		windowSize: windowSize,
+	}
+}
+
+func (r *slidingWindowWeightedRoundRobinSelector) DoSelect(request motan.Request) motan.EndPoint {
+	windowStartIndex := motan.GetNonNegative(atomic.AddInt64(&r.idx, int64(r.windowSize)))
+	totalWeight := 0
+	var sMaxWeight int64 = 0
+	maxWeightIndex := 0
+	// Use SWRR(https://github.com/nginx/nginx/commit/52327e0627f49dbda1e8db695e63a4b0af4448b1) to select referer from the current window.
+	// In order to reduce costs, do not limit concurrency in the entire selection process,
+	// and only use atomic updates for the current weight.
+	// Since concurrent threads will execute Select in different windows,
+	// the problem of instantaneous requests increase on one node due to concurrency will not be serious.
+	// And because the referers used on different client sides are shuffled,
+	// the impact of high instantaneous concurrent selection on the server side will be further reduced.
+	for i := 0; i < r.windowSize; i++ {
+		idx := (int(windowStartIndex) + i) % len(r.items)
+		item := r.items[idx]
+		if item.ep.IsAvailable() {
+			currentWeight := atomic.AddInt64(&item.currentWeight, int64(item.weight))
+			totalWeight += item.weight
+			if currentWeight > sMaxWeight {
+				sMaxWeight = currentWeight
+				maxWeightIndex = idx
+			}
+		}
+	}
+	if sMaxWeight > 0 {
+		item := r.items[maxWeightIndex]
+		atomic.AddInt64(&item.currentWeight, int64(-totalWeight))
+		if item.ep.IsAvailable() {
+			return item.ep
+		}
+	}
+	// If no suitable node is selected or the node is unavailable,
+	// then select an available referer from a random index
+	var idx = int(windowStartIndex) + rand.Intn(r.windowSize)
+	for i := 1; i < len(r.items); i++ {
+		item := r.items[(idx+i)%len(r.items)]
+		if item.ep.IsAvailable() {
+			return item.ep
+		}
+	}
+	return nil
+}
+
+type selectorItem struct {
+	ep            motan.EndPoint
+	weight        int
+	currentWeight int64
+}
+
+func newSelectorItem(ep motan.EndPoint, weight int) *selectorItem {
+	return &selectorItem{
+		weight: weight,
+		ep:     ep,
+	}
+}
diff --git a/lb/weightRoundRobinLb_test.go b/lb/weightRoundRobinLb_test.go
new file mode 100644
index 00000000..64093acb
--- /dev/null
+++ b/lb/weightRoundRobinLb_test.go
@@ -0,0 +1,358 @@
+package lb
+
+import (
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	"github.com/weibocom/motan-go/core"
+	"github.com/weibocom/motan-go/endpoint"
+	"github.com/weibocom/motan-go/meta"
+	"math"
+	"math/rand"
+	"sync/atomic"
+	"testing"
+	"time"
+)
+
+func TestDynamicStaticWeight(t *testing.T) {
+	url := &core.URL{
+		Protocol: "motan2",
+		Host:     "127.0.0.1",
+		Port:     8080,
+		Path:     "mockService",
+	}
+	url.PutParam(core.DynamicMetaKey, "false")
+	// test dynamic weight config
+	lb := NewWeightRondRobinLb(url)
+	assert.False(t, lb.refresher.supportDynamicWeight)
+	lb.Destroy()
+
+	url.PutParam(core.DynamicMetaKey, "true")
+	lb = NewWeightRondRobinLb(url)
+	assert.True(t, lb.refresher.supportDynamicWeight)
+
+	meta.ClearMetaCache()
+	var staticWeight int64 = 9
+	eps := buildTestDynamicEps(10, true, staticWeight, url)
+	eps = core.EndpointShuffle(eps)
+	lb.OnRefresh(eps)
+	_, ok := lb.getSelector().(*roundRobinSelector)
+	assert.True(t, ok)
+	for _, j := range lb.refresher.weightedEpHolders.Load().([]*WeightedEpHolder) {
+		// test static weight
+		assert.Equal(t, j.staticWeight, staticWeight)
+		assert.Equal(t, j.dynamicWeight, int64(0))
+		assert.Equal(t, j.getWeight(), staticWeight)
+	}
+
+	// test dynamic wight change
+	lb.refresher.weightedEpHolders.Load().([]*WeightedEpHolder)[3].ep.(*endpoint.MockDynamicEndpoint).SetWeight(true, 22)
+	meta.ClearMetaCache()
+	time.Sleep(time.Second * 5)
+	//assert.Equal(t, int64(22), lb.refresher.weightedEpHolders.Load().([]*WeightedEpHolder)[3].dynamicWeight)
+	//assert.Equal(t, int64(22), lb.refresher.weightedEpHolders.Load().([]*WeightedEpHolder)[3].getWeight())
+	_, ok = lb.getSelector().(*weightedRingSelector)
+	assert.True(t, ok)
+	// test close refresh task
+	lb.Destroy()
+	time.Sleep(time.Second * 5)
+	assert.True(t, lb.refresher.isDestroyed.Load())
+}
+
+func TestGetEpWeight(t *testing.T) {
+	url := &core.URL{
+		Protocol: "motan2",
+		Host:     "127.0.0.1",
+		Port:     8080,
+		Path:     "mockService",
+	}
+	ep := endpoint.NewMockDynamicEndpoint(url)
+	type test struct {
+		expectWeight     int64
+		fromDynamic      bool
+		defaultWeight    int64
+		setWeight        bool
+		setWeightDynamic bool
+		setWeightValue   int64
+	}
+	testSet := []test{
+		// test static weight
+		{expectWeight: 11, fromDynamic: false, defaultWeight: 11, setWeight: false},
+		{expectWeight: 5, fromDynamic: false, defaultWeight: 5, setWeight: false},
+		{expectWeight: 8, fromDynamic: false, defaultWeight: 11, setWeight: true, setWeightDynamic: false, setWeightValue: 8},
+		// test dynamic weight
+		{expectWeight: 0, fromDynamic: true, defaultWeight: 0, setWeight: false},
+		{expectWeight: 5, fromDynamic: true, defaultWeight: 5, setWeight: false},
+		{expectWeight: 15, fromDynamic: true, defaultWeight: 11, setWeight: true, setWeightDynamic: true, setWeightValue: 15},
+		// test abnormal weight
+		{expectWeight: MinEpWeight, fromDynamic: false, defaultWeight: 11, setWeight: true, setWeightDynamic: false, setWeightValue: -8},
+		{expectWeight: MaxEpWeight, fromDynamic: false, defaultWeight: 11, setWeight: true, setWeightDynamic: false, setWeightValue: 501},
+		{expectWeight: MinEpWeight, fromDynamic: true, defaultWeight: 11, setWeight: true, setWeightDynamic: true, setWeightValue: -1},
+		{expectWeight: MaxEpWeight, fromDynamic: true, defaultWeight: 11, setWeight: true, setWeightDynamic: true, setWeightValue: 666},
+	}
+
+	for _, j := range testSet {
+		if j.setWeight {
+			ep.SetWeight(j.setWeightDynamic, j.setWeightValue)
+		}
+		w, e := getEpWeight(ep, j.fromDynamic, j.defaultWeight)
+		assert.Nil(t, e)
+		assert.Equal(t, j.expectWeight, w)
+		meta.ClearMetaCache()
+	}
+}
+
+func TestNotifyWeightChange(t *testing.T) {
+	type test struct {
+		size       int
+		sameWeight bool
+		maxWeight  int
+		selector   string
+	}
+	testSet := []test{
+		// test RR
+		{size: 20, sameWeight: true, maxWeight: 8, selector: "roundRobinSelector"},
+		{size: 20, sameWeight: true, maxWeight: 0, selector: "roundRobinSelector"},
+		{size: 20, sameWeight: true, maxWeight: 101, selector: "roundRobinSelector"},
+		{size: 20, sameWeight: true, maxWeight: -10, selector: "roundRobinSelector"}, //abnormal weight
+		//// test WR
+		{size: 20, sameWeight: false, maxWeight: 8, selector: "weightedRingSelector"},
+		{size: wrMaxEpSize, sameWeight: false, maxWeight: 8, selector: "weightedRingSelector"},
+		{size: wrMaxEpSize, sameWeight: false, maxWeight: wrMaxTotalWeight / wrMaxEpSize, selector: "weightedRingSelector"},
+		// test SWWRR
+		{size: wrMaxEpSize + 1, sameWeight: false, maxWeight: 6, selector: "slidingWindowWeightedRoundRobinSelector"},
+		{size: 40, sameWeight: false, maxWeight: 800, selector: "slidingWindowWeightedRoundRobinSelector"},
+	}
+	url := &core.URL{
+		Protocol: "motan2",
+		Host:     "127.0.0.1",
+		Port:     8080,
+		Path:     "mockService",
+	}
+	url.PutParam(core.DynamicMetaKey, "false")
+	lb := NewWeightRondRobinLb(url)
+	for _, j := range testSet {
+		eps := buildTestDynamicEps(j.size, j.sameWeight, int64(j.maxWeight), url)
+		eps = core.EndpointShuffle(eps)
+		lb.OnRefresh(eps)
+		var ok bool
+		switch j.selector {
+		case "roundRobinSelector":
+			_, ok = lb.getSelector().(*roundRobinSelector)
+		case "weightedRingSelector":
+			_, ok = lb.getSelector().(*weightedRingSelector)
+		case "slidingWindowWeightedRoundRobinSelector":
+			_, ok = lb.getSelector().(*slidingWindowWeightedRoundRobinSelector)
+		}
+		assert.True(t, ok)
+		meta.ClearMetaCache()
+	}
+	lb.Destroy()
+}
+
+func TestRoundRobinSelector(t *testing.T) {
+	url := &core.URL{
+		Protocol: "motan2",
+		Host:     "127.0.0.1",
+		Port:     8080,
+		Path:     "mockService",
+	}
+	url.PutParam(core.DynamicMetaKey, "false")
+	lb := NewWeightRondRobinLb(url)
+	round := 100
+	// small size
+	checkRR(t, lb, 20, 8, round, 1, 1, 0, url)
+	// large size
+	checkRR(t, lb, 500, 8, round, 1, 1, 0, url)
+	// some nodes are unavailable
+	maxRatio := 0.4
+	avgRatio := 0.1
+	round = 200
+	checkRR(t, lb, 20, 8, round, float64(round)*maxRatio, float64(round)*avgRatio, 2, url)
+	checkRR(t, lb, 100, 8, round, float64(round)*maxRatio, float64(round)*avgRatio, 10, url)
+
+	maxRatio = 0.7
+	checkRR(t, lb, 300, 8, round, float64(round)*maxRatio, float64(round)*avgRatio, 50, url)
+	lb.Destroy()
+}
+
+func checkRR(t *testing.T, lb *WeightRoundRobinLB, size int, initialMaxWeight int64,
+	round int, expectMaxDelta float64, expectAvgDelta float64, unavailableSize int, url *core.URL) {
+	eps := buildTestDynamicEpsWithUnavailable(size, true, initialMaxWeight, true, unavailableSize, url)
+	rand.Seed(time.Now().UnixNano())
+	eps = core.EndpointShuffle(eps)
+	lb.OnRefresh(eps)
+	_, ok := lb.getSelector().(*roundRobinSelector)
+	assert.True(t, ok)
+	processCheck(t, lb, "RR", eps, round, expectMaxDelta, expectAvgDelta, unavailableSize)
+}
+
+func TestWeightRingSelector(t *testing.T) {
+	url := &core.URL{
+		Protocol: "motan2",
+		Host:     "127.0.0.1",
+		Port:     8080,
+		Path:     "mockService",
+	}
+	url.PutParam(core.DynamicMetaKey, "false")
+	lb := NewWeightRondRobinLb(url)
+	round := 100
+	// small size
+	checkKWR(t, lb, 51, 49, round, 1, 1, 0, url)
+	// max node size of WR
+	checkKWR(t, lb, 256, 15, round, 1, 1, 0, url)
+
+	// same nodes are unavailable
+	maxRatio := 0.4
+	avgRatio := 0.1
+	checkKWR(t, lb, 46, 75, round, float64(round)*maxRatio, float64(round)*avgRatio, 5, url)
+	checkKWR(t, lb, 231, 31, round, float64(round)*maxRatio, float64(round)*avgRatio, 35, url)
+	maxRatio = 0.6
+	//checkKWR(t, lb, 211, 31, round, float64(round)*maxRatio, float64(round)*avgRatio, 45, url)
+	lb.Destroy()
+}
+
+func checkKWR(t *testing.T, lb *WeightRoundRobinLB, size int, initialMaxWeight int64,
+	round int, expectMaxDelta float64, expectAvgDelta float64, unavailableSize int, url *core.URL) {
+	eps := buildTestDynamicEpsWithUnavailable(size, false, initialMaxWeight, true, unavailableSize, url)
+	rand.Seed(time.Now().UnixNano())
+	eps = core.EndpointShuffle(eps)
+	lb.OnRefresh(eps)
+	_, ok := lb.getSelector().(*weightedRingSelector)
+	assert.True(t, ok)
+	processCheck(t, lb, "WR", eps, round, expectMaxDelta, expectAvgDelta, unavailableSize)
+}
+
+func TestSlidingWindowWeightedRoundRobinSelector(t *testing.T) {
+	url := &core.URL{
+		Protocol: "motan2",
+		Host:     "127.0.0.1",
+		Port:     8080,
+		Path:     "mockService",
+	}
+	url.PutParam(core.DynamicMetaKey, "false")
+	lb := NewWeightRondRobinLb(url)
+	round := 100
+	// equals default window size, the accuracy is higher than sliding window
+	size := swwrDefaultWindowSize
+	checkSWWRR(t, lb, size, int64(wrMaxTotalWeight*3/size), round, 2, 1, 0, url)
+	// less than default window size
+	size = swwrDefaultWindowSize - 9
+	checkSWWRR(t, lb, size, int64(wrMaxTotalWeight*3/size), round, 2, 1, 0, url)
+
+	// greater than default window size
+	// sliding windows will reduce the accuracy of WRR, so the threshold should be appropriately increased
+	maxRatio := 0.5
+	avgRatio := 0.1
+	round = 200
+	size = 270
+	checkSWWRR(t, lb, size, 45, round, float64(round)*maxRatio, float64(round)*avgRatio, 0, url)
+
+	// some nodes are unavailable
+	size = 260
+	checkSWWRR(t, lb, size, int64(wrMaxTotalWeight*3/size), round, float64(round)*maxRatio, float64(round)*avgRatio, 10, url)
+	size = 399
+	checkSWWRR(t, lb, size, 67, round, float64(round)*maxRatio, float64(round)*avgRatio, 40, url)
+	lb.Destroy()
+}
+
+func checkSWWRR(t *testing.T, lb *WeightRoundRobinLB, size int, initialMaxWeight int64,
+	round int, expectMaxDelta float64, expectAvgDelta float64, unavailableSize int, url *core.URL) {
+	eps := buildTestDynamicEpsWithUnavailable(size, false, initialMaxWeight, true, unavailableSize, url)
+	rand.Seed(time.Now().UnixNano())
+	eps = core.EndpointShuffle(eps)
+	lb.OnRefresh(eps)
+	_, ok := lb.getSelector().(*slidingWindowWeightedRoundRobinSelector)
+	assert.True(t, ok)
+	processCheck(t, lb, "SWWRR", eps, round, expectMaxDelta, expectAvgDelta, unavailableSize)
+}
+
+func processCheck(t *testing.T, lb *WeightRoundRobinLB, typ string, eps []core.EndPoint, round int,
+	expectMaxDelta float64, expectAvgDelta float64, unavailableSize int) {
+	var totalWeight int64 = 0
+	for _, ep := range eps {
+		if !ep.IsAvailable() {
+			continue
+		}
+		totalWeight += ep.(*endpoint.MockDynamicEndpoint).StaticWeight
+	}
+	for i := 0; i < int(totalWeight)*round; i++ {
+		lb.Select(nil).Call(nil)
+	}
+	var maxDelta float64 = 0.0
+	var totalDelta float64 = 0.0
+	unavailableCount := 0
+	for _, ep := range eps {
+		if !ep.IsAvailable() {
+			unavailableCount++
+		} else {
+			mep := ep.(*endpoint.MockDynamicEndpoint)
+			ratio := float64(atomic.LoadInt64(&mep.Count)) / float64(mep.StaticWeight)
+			delta := math.Abs(ratio - float64(round))
+			if delta > maxDelta {
+				maxDelta = delta
+			}
+			totalDelta += delta
+			if delta > expectMaxDelta {
+				fmt.Printf("%s: count=%d, staticWeight=%d, ratio=%.2f, delta=%.2f\n", typ, atomic.LoadInt64(&mep.Count), mep.StaticWeight, ratio, delta)
+			}
+			assert.True(t, delta <= expectMaxDelta) // check max delta
+		}
+	}
+	// avg delta
+	avgDelta := totalDelta / float64(len(eps)-unavailableSize)
+	assert.True(t, avgDelta-float64(round) < expectAvgDelta)
+	fmt.Printf("%s: avgDeltaPercent=%.2f%%, maxDeltaPercent=%.2f%%, avgDelta=%.2f, maxDelta=%.2f\n", typ, avgDelta*float64(100)/float64(round), maxDelta*float64(100)/float64(round), avgDelta, maxDelta)
+	if unavailableSize > 0 {
+		assert.Equal(t, unavailableSize, unavailableCount)
+	}
+}
+
+func buildTestDynamicEps(size int, sameStaticWeight bool, maxWeight int64, url *core.URL) []core.EndPoint {
+	return buildTestDynamicEpsWithUnavailable(size, sameStaticWeight, maxWeight, false, 0, url)
+}
+
+func buildTestDynamicEpsWithUnavailable(size int, sameStaticWeight bool, maxWeight int64, adjust bool, unavailableSize int, url *core.URL) []core.EndPoint {
+	return buildTestEps(size, sameStaticWeight, maxWeight, adjust, unavailableSize, "", url)
+}
+
+func buildTestEps(size int, sameStaticWeight bool, maxWeight int64, adjust bool, unavailableSize int, group string, url *core.URL) []core.EndPoint {
+	rand.Seed(time.Now().UnixNano())
+	var res []core.EndPoint
+	for i := 0; i < size; i++ {
+		weight := maxWeight
+		if !sameStaticWeight {
+			weight = int64(rand.Float64() * float64(maxWeight))
+		}
+		if adjust {
+			weight = doAdjust(weight)
+		}
+		curUrl := &core.URL{
+			Protocol: "motan2",
+			Host:     "127.0.0.1",
+			Port:     8080 + i,
+			Path:     "mockService",
+		}
+		if url.GetParam(core.DynamicMetaKey, "") == "false" {
+			curUrl.PutParam(core.DynamicMetaKey, "false")
+		}
+		ep := endpoint.NewMockDynamicEndpointWithWeight(curUrl, weight)
+		if i < unavailableSize {
+			ep.SetAvailable(false)
+		}
+		if group != "" {
+			ep.URL.PutParam(core.GroupKey, group)
+		}
+		res = append(res, ep)
+	}
+	return res
+}
+
+func doAdjust(w int64) int64 {
+	if w < MinEpWeight {
+		return MinEpWeight
+	} else if w > MaxEpWeight {
+		return MaxEpWeight
+	} else {
+		return w
+	}
+}
diff --git a/lb/weightedEpRefresher.go b/lb/weightedEpRefresher.go
new file mode 100644
index 00000000..157eb746
--- /dev/null
+++ b/lb/weightedEpRefresher.go
@@ -0,0 +1,229 @@
+package lb
+
+import (
+	"errors"
+	motan "github.com/weibocom/motan-go/core"
+	vlog "github.com/weibocom/motan-go/log"
+	"github.com/weibocom/motan-go/meta"
+	"go.uber.org/atomic"
+	"golang.org/x/net/context"
+	"strconv"
+	"sync"
+	"time"
+)
+
+const (
+	defaultEpWeight                  = 10
+	MinEpWeight                      = 1
+	MaxEpWeight                      = 500 //protective restriction
+	defaultWeightRefreshPeriodSecond = 3
+)
+
+// WeightedEpRefresher is held by load balancer who needs dynamic endpoint weights
+type WeightedEpRefresher struct {
+	url                  *motan.URL
+	refreshCanceler      context.CancelFunc
+	supportDynamicWeight bool
+	weightedEpHolders    atomic.Value
+	weightLB             motan.WeightLoadBalance
+	isDestroyed          atomic.Bool
+	mutex                sync.Mutex
+}
+
+func NewWeightEpRefresher(url *motan.URL, lb motan.WeightLoadBalance) *WeightedEpRefresher {
+	refreshPeriod := url.GetIntValue(motan.WeightRefreshPeriodSecondKey, defaultWeightRefreshPeriodSecond)
+	refreshCtx, refreshCancel := context.WithCancel(context.Background())
+	wer := &WeightedEpRefresher{
+		url:                  url,
+		supportDynamicWeight: url.GetBoolValue(motan.DynamicMetaKey, motan.DefaultDynamicMeta),
+		refreshCanceler:      refreshCancel,
+		weightLB:             lb,
+	}
+	go wer.doRefresh(refreshCtx, refreshPeriod)
+	return wer
+}
+
+func (w *WeightedEpRefresher) Destroy() {
+	w.refreshCanceler()
+}
+
+func (w *WeightedEpRefresher) notifyWeightChange() {
+	w.mutex.Lock()
+	defer w.mutex.Unlock()
+	vlog.Infoln("weight has changed, url: " + w.url.GetIdentity())
+	w.weightLB.NotifyWeightChange()
+}
+
+func (w *WeightedEpRefresher) RefreshWeightedHolders(eps []motan.EndPoint) {
+	var newHolder []*WeightedEpHolder
+	var oldHolder []*WeightedEpHolder
+	if t := w.weightedEpHolders.Load(); t != nil {
+		oldHolder = t.([]*WeightedEpHolder)
+	}
+	allHolder := make([]*WeightedEpHolder, 0, len(eps))
+	for _, ep := range eps {
+		var holder *WeightedEpHolder
+		if oldHolder != nil {
+			// Check whether referer can be reused
+			for _, tempHolder := range oldHolder {
+				if ep == tempHolder.getEp() {
+					holder = tempHolder
+					break
+				}
+			}
+		}
+		if holder == nil {
+			staticWeight, _ := getEpWeight(ep, false, defaultEpWeight)
+			holder = BuildWeightedEpHolder(ep, staticWeight)
+			newHolder = append(newHolder, holder)
+		}
+		allHolder = append(allHolder, holder)
+	}
+	// only refresh new holders' dynamic weight
+	if len(newHolder) != 0 {
+		refreshDynamicWeight(newHolder, 15*1000)
+	}
+	w.weightedEpHolders.Store(allHolder)
+	w.notifyWeightChange()
+}
+
+func refreshDynamicWeight(holders []*WeightedEpHolder, taskTimeout int64) bool {
+	needNotify := atomic.NewBool(false)
+	wg := sync.WaitGroup{}
+	wg.Add(len(holders))
+	for _, h := range holders {
+		if h.supportDynamicWeight {
+			go func(holder *WeightedEpHolder) {
+				defer wg.Done()
+				oldWeight := holder.dynamicWeight
+				var err error
+				dw, err := getEpWeight(holder.getEp(), true, 0)
+				if err != nil {
+					if errors.Is(err, meta.ServiceNotSupportError) {
+						holder.supportDynamicWeight = false
+					} else {
+						vlog.Warningf("refresh dynamic weight fail! url:%s, error: %s\n", holder.getEp().GetURL().GetIdentity(), err.Error())
+					}
+					return
+				}
+				holder.dynamicWeight = dw
+				if oldWeight != holder.dynamicWeight {
+					needNotify.Store(true)
+				}
+			}(h)
+		} else {
+			wg.Done()
+		}
+	}
+	// just wait certain amount of time
+	timer := time.NewTimer(time.Millisecond * time.Duration(taskTimeout))
+	finishChan := make(chan struct{})
+	defer close(finishChan)
+	go func() {
+		wg.Wait()
+		// the chan might be closed
+		select {
+		case _, ok := <-finishChan:
+			if !ok {
+				// chan has been closed
+				return
+			}
+		default:
+			finishChan <- struct{}{}
+			return
+		}
+	}()
+	select {
+	case <-timer.C:
+	case <-finishChan:
+		timer.Stop()
+	}
+	return needNotify.Load()
+}
+
+func getEpWeight(ep motan.EndPoint, fromDynamicMeta bool, defaultWeight int64) (int64, error) {
+	var metaMap map[string]string
+	var err error
+	if fromDynamicMeta {
+		metaMap, err = meta.GetEpDynamicMeta(ep)
+	} else {
+		metaMap = meta.GetEpStaticMeta(ep)
+	}
+	if err != nil {
+		return defaultWeight, err
+	}
+	weightStr := meta.GetMetaValue(metaMap, motan.WeightMetaSuffixKey)
+	return adjustWeight(ep, weightStr, defaultWeight), nil
+}
+
+func adjustWeight(ep motan.EndPoint, weight string, defaultWeight int64) int64 {
+	res := defaultWeight
+	if weight != "" {
+		temp, err := strconv.ParseInt(weight, 10, 64)
+		if err != nil {
+			vlog.Warningf("WeightedRefererHolder parse weight fail. %s, use default weight %d, org weight: %s, err: %s\n", ep.GetURL().GetIdentity(), defaultWeight, weight, err.Error())
+			return defaultWeight
+		}
+		if temp < MinEpWeight {
+			temp = MinEpWeight
+		} else if temp > MaxEpWeight {
+			temp = MaxEpWeight
+		}
+		res = temp
+	}
+	return res
+}
+
+func (w *WeightedEpRefresher) doRefresh(ctx context.Context, refreshPeriod int64) {
+	ticker := time.NewTicker(time.Second * time.Duration(refreshPeriod))
+	defer ticker.Stop()
+	for {
+		select {
+		case <-ctx.Done():
+			w.isDestroyed.Store(true)
+			return
+		case <-ticker.C:
+			if w.supportDynamicWeight {
+				w.refreshHolderDynamicWeightTask()
+			}
+		}
+	}
+}
+
+func (w *WeightedEpRefresher) refreshHolderDynamicWeightTask() {
+	// The reference of holders might be changed during the loop
+	// Only refresh historical holders
+	var tempLoaders []*WeightedEpHolder
+	if t := w.weightedEpHolders.Load(); t != nil {
+		tempLoaders = t.([]*WeightedEpHolder)
+	}
+	if refreshDynamicWeight(tempLoaders, 30*1000) {
+		w.notifyWeightChange()
+	}
+}
+
+type WeightedEpHolder struct {
+	ep                   motan.EndPoint
+	staticWeight         int64
+	supportDynamicWeight bool
+	dynamicWeight        int64
+}
+
+func BuildWeightedEpHolder(ep motan.EndPoint, staticWeight int64) *WeightedEpHolder {
+	return &WeightedEpHolder{
+		ep:                   ep,
+		staticWeight:         staticWeight,
+		supportDynamicWeight: ep.GetURL().GetBoolValue(motan.DynamicMetaKey, motan.DefaultDynamicMeta),
+	}
+}
+
+func (w *WeightedEpHolder) getWeight() int64 {
+	if w.dynamicWeight > 0 {
+		return w.dynamicWeight
+	}
+	return w.staticWeight
+}
+
+func (w *WeightedEpHolder) getEp() motan.EndPoint {
+	return w.ep
+}
diff --git a/log/log.go b/log/log.go
index a3b71c2e..34dd8670 100644
--- a/log/log.go
+++ b/log/log.go
@@ -373,6 +373,12 @@ func (d *defaultLogger) AccessLog(logObject *AccessLogEntity) {
 }
 
 func (d *defaultLogger) doAccessLog(logObject *AccessLogEntity) {
+	defer func() {
+		if err := recover(); err != nil {
+			log.Printf("doAccessLog failed. err:%v, logObject: %+v", err, logObject)
+			debug.PrintStack()
+		}
+	}()
 	if d.accessStructured {
 		d.accessLogger.Info("",
 			zap.String("filterName", logObject.FilterName),
diff --git a/main/agentdemo.go b/main/agentdemo.go
index eef08171..17297fc8 100644
--- a/main/agentdemo.go
+++ b/main/agentdemo.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+	"github.com/weibocom/motan-go/filter"
 	"net/http"
 	"net/http/httputil"
 
@@ -48,6 +49,10 @@ type MyEndPointFilter struct {
 	next motancore.EndPointFilter
 }
 
+func (m *MyEndPointFilter) GetRuntimeInfo() map[string]interface{} {
+	return filter.GetFilterRuntimeInfo(m)
+}
+
 func (m *MyEndPointFilter) GetIndex() int {
 	return 20
 }
diff --git a/manageHandler.go b/manageHandler.go
index f8e79085..8cd84f0c 100644
--- a/manageHandler.go
+++ b/manageHandler.go
@@ -5,6 +5,8 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"github.com/weibocom/motan-go/meta"
+	mserver "github.com/weibocom/motan-go/server"
 	"html/template"
 	"io"
 	"log"
@@ -741,6 +743,36 @@ func setLogStatus(jsonEncoder *json.Encoder, logType, available string) {
 	}
 }
 
+type MetaInfo struct {
+	agent *Agent
+}
+
+func (m *MetaInfo) SetAgent(agent *Agent) {
+	m.agent = agent
+}
+
+func (m *MetaInfo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	switch r.URL.Path {
+	case "/meta/update":
+		if r.Form == nil {
+			r.ParseMultipartForm(32 << 20)
+		}
+		for k, v := range r.Form {
+			if len(v) > 0 {
+				meta.PutDynamicMeta(k, v[0])
+			}
+		}
+		JSONSuccess(w, "ok")
+	case "/meta/delete":
+		meta.RemoveDynamicMeta(r.FormValue("key"))
+		JSONSuccess(w, "ok")
+	case "/meta/get":
+		JSONSuccess(w, map[string]string{r.FormValue("key"): meta.GetDynamicMeta()[r.FormValue("key")]})
+	case "/meta/getAll":
+		JSONSuccess(w, map[string]map[string]string{"meta": meta.GetDynamicMeta()})
+	}
+}
+
 type HotReload struct {
 	agent *Agent
 }
@@ -767,6 +799,143 @@ func (h *HotReload) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+type RuntimeHandler struct {
+	agent *Agent
+}
+
+func (h *RuntimeHandler) SetAgent(agent *Agent) {
+	h.agent = agent
+}
+
+func (h *RuntimeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	switch r.URL.Path {
+	case "/runtime/info":
+		result := map[string]interface{}{
+			"result":                     "ok",
+			motan.RuntimeInstanceTypeKey: "motan-agent",
+		}
+		// cluster info
+		result[motan.RuntimeClustersKey] = h.getClusterInfo()
+
+		// http cluster info
+		result[motan.RuntimeHttpClustersKey] = h.getHttpClusterInfo()
+
+		// exporter info
+		result[motan.RuntimeExportersKey] = h.getExporterInfo()
+
+		// extensionFactory info
+		result[motan.RuntimeExtensionFactoryKey] = GetDefaultExtFactory().GetRuntimeInfo()
+
+		// servers info
+		result[motan.RuntimeServersKey] = h.getServersInfo()
+
+		// basic info
+		result[motan.RuntimeBasicKey] = h.getBasicInfo()
+
+		res, _ := json.Marshal(result)
+		w.Write(res)
+	}
+}
+
+func (h *RuntimeHandler) getClusterInfo() map[string]interface{} {
+	info := make(map[string]interface{}, h.agent.clusterMap.Len())
+	h.agent.clusterMap.Range(func(k, v interface{}) bool {
+		cls, ok := v.(*cluster.MotanCluster)
+		if !ok {
+			return true
+		}
+		info[k.(string)] = cls.GetRuntimeInfo()
+		return true
+	})
+	return info
+}
+
+func (h *RuntimeHandler) getHttpClusterInfo() map[string]interface{} {
+	info := make(map[string]interface{}, h.agent.httpClusterMap.Len())
+	h.agent.httpClusterMap.Range(func(k, v interface{}) bool {
+		cls, ok := v.(*cluster.HTTPCluster)
+		if !ok {
+			return true
+		}
+		h.addInfos(cls.GetRuntimeInfo(), k.(string), info)
+		return true
+	})
+	return info
+}
+
+func (h *RuntimeHandler) getExporterInfo() map[string]interface{} {
+	info := make(map[string]interface{}, h.agent.serviceExporters.Len())
+	h.agent.serviceExporters.Range(func(k, v interface{}) bool {
+		exporter, ok := v.(*mserver.DefaultExporter)
+		if !ok {
+			return true
+		}
+		h.addInfos(exporter.GetRuntimeInfo(), k.(string), info)
+		return true
+	})
+	return info
+}
+
+func (h *RuntimeHandler) getServersInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	h.addInfos(h.agent.agentServer.GetRuntimeInfo(), motan.RuntimeAgentServerKey, info)
+	agentPortServerInfo := map[string]interface{}{}
+	for port, server := range h.agent.agentPortServer {
+		agentPortServerInfo[strconv.Itoa(port)] = server.GetRuntimeInfo()
+	}
+	h.addInfos(agentPortServerInfo, motan.RuntimeAgentPortServerKey, info)
+	h.addInfos(h.agent.httpProxyServer.GetRuntimeInfo(), motan.RuntimeHttpProxyServerKey, info)
+	return info
+}
+
+func (h *RuntimeHandler) getBasicInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	info[motan.RuntimeCpuPercentKey] = GetCpuPercent()
+	info[motan.RuntimeRssMemoryKey] = GetRssMemory()
+	return info
+}
+
+func (h *RuntimeHandler) addInfos(info map[string]interface{}, key string, result map[string]interface{}) {
+	if info != nil && len(info) > 0 {
+		result[key] = info
+	}
+}
+
+// JSONSuccess return success JSON data
+func JSONSuccess(w http.ResponseWriter, data interface{}) {
+	JSON(w, "", "ok", data)
+}
+
+// JSONError return Error JSON data
+func JSONError(w http.ResponseWriter, msg string) {
+	JSON(w, msg, "fail", nil)
+}
+
+func JSON(w http.ResponseWriter, errMsg string, result string, data interface{}) {
+	if errMsg != "" {
+		d := struct {
+			Result string `json:"result"`
+			Error  string `json:"error"`
+		}{
+			Result: result,
+			Error:  errMsg,
+		}
+		bs, _ := json.Marshal(d)
+		w.Write(bs)
+	} else {
+		d := struct {
+			Result string      `json:"result"`
+			Data   interface{} `json:"data"`
+		}{
+			Result: result,
+			Data:   data,
+		}
+		bs, _ := json.Marshal(d)
+		w.Write(bs)
+	}
+
+}
+
 //------------ below code is copied from net/http/pprof -------------
 
 // Cmdline responds with the running program's
diff --git a/meshClient.go b/meshClient.go
index 88e93a46..147d0043 100644
--- a/meshClient.go
+++ b/meshClient.go
@@ -119,8 +119,8 @@ func (c *MeshClient) BaseCall(request core.Request, reply interface{}) core.Resp
 	}
 	httpRequest := fasthttp.AcquireRequest()
 	httpResponse := fasthttp.AcquireResponse()
+	// do not release http response
 	defer fasthttp.ReleaseRequest(httpRequest)
-	defer fasthttp.ReleaseResponse(httpResponse)
 	httpRequest.Header.Del("Host")
 	httpRequest.SetHost(request.GetServiceName())
 	httpRequest.URI().SetPath(request.GetMethod())
diff --git a/meta/meta.go b/meta/meta.go
new file mode 100644
index 00000000..175cc640
--- /dev/null
+++ b/meta/meta.go
@@ -0,0 +1,193 @@
+package meta
+
+import (
+	"errors"
+	"github.com/patrickmn/go-cache"
+	"github.com/weibocom/motan-go/core"
+	"github.com/weibocom/motan-go/endpoint"
+	vlog "github.com/weibocom/motan-go/log"
+	mpro "github.com/weibocom/motan-go/protocol"
+	"os"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+const (
+	defaultCacheExpireSecond    = 3
+	notSupportCacheExpireSecond = 30
+)
+
+var (
+	dynamicMeta            = core.NewStringMap(30)
+	envMeta                = make(map[string]string)
+	envPrefix              = core.DefaultMetaPrefix
+	metaEmptyMap           = make(map[string]string)
+	metaCache              = cache.New(time.Second*time.Duration(defaultCacheExpireSecond), 30*time.Second)
+	notSupportCache        = cache.New(time.Second*time.Duration(notSupportCacheExpireSecond), 30*time.Second)
+	ServiceNotSupportError = errors.New(core.ServiceNotSupport)
+	notSupportSerializer   = map[string]bool{
+		"protobuf":     true,
+		"grpc-pb":      true,
+		"grpc-pb-json": true,
+	}
+	supportProtocols = map[string]bool{
+		"motan":  true,
+		"motan2": true,
+	}
+	once = sync.Once{}
+)
+
+func Initialize(ctx *core.Context) {
+	once.Do(func() {
+		expireSecond := defaultCacheExpireSecond
+		if ctx != nil && ctx.Config != nil {
+			envPrefix = ctx.Config.GetStringWithDefault(core.EnvMetaPrefixKey, core.DefaultMetaPrefix)
+			expireSecondStr := ctx.Config.GetStringWithDefault(core.MetaCacheExpireSecondKey, "")
+			if expireSecondStr != "" {
+				tempCacheExpireSecond, err := strconv.Atoi(expireSecondStr)
+				if err == nil && tempCacheExpireSecond > 0 {
+					expireSecond = tempCacheExpireSecond
+				}
+			}
+		}
+		vlog.Infof("meta cache expire time : %d(s)\n", expireSecond)
+		metaCache = cache.New(time.Second*time.Duration(expireSecond), 30*time.Second)
+		vlog.Infof("using meta prefix : %s\n", envPrefix)
+		// load meta info from env variable
+		for _, env := range os.Environ() {
+			if strings.HasPrefix(env, envPrefix) {
+				kv := strings.Split(env, "=")
+				envMeta[kv[0]] = kv[1]
+			}
+		}
+	})
+}
+
+func GetEnvMeta() map[string]string {
+	return envMeta
+}
+
+func PutDynamicMeta(key, value string) {
+	dynamicMeta.Store(key, value)
+}
+
+func RemoveDynamicMeta(key string) {
+	dynamicMeta.Delete(key)
+}
+
+func GetDynamicMeta() map[string]string {
+	return dynamicMeta.RawMap()
+}
+
+func GetMergedMeta() map[string]string {
+	mergedMap := make(map[string]string)
+	for k, v := range envMeta {
+		mergedMap[k] = v
+	}
+	for k, v := range dynamicMeta.RawMap() {
+		mergedMap[k] = v
+	}
+	return mergedMap
+}
+
+func GetMetaValue(meta map[string]string, keySuffix string) string {
+	var res string
+	if meta != nil {
+		if v, ok := meta[envPrefix+keySuffix]; ok {
+			res = v
+		} else {
+			res = meta[core.DefaultMetaPrefix+keySuffix]
+		}
+	}
+	return res
+}
+
+func GetEpDynamicMeta(endpoint core.EndPoint) (map[string]string, error) {
+	cacheKey := getCacheKey(endpoint.GetURL())
+	if v, ok := metaCache.Get(cacheKey); ok {
+		return v.(map[string]string), nil
+	}
+	res, err := getRemoteDynamicMeta(cacheKey, endpoint)
+	if err != nil {
+		return nil, err
+	}
+	metaCache.Set(cacheKey, res, cache.DefaultExpiration)
+	return res, nil
+}
+
+// GetEpStaticMeta get remote static meta information from referer url attachments.
+// the static meta is init at server start from env.
+func GetEpStaticMeta(endpoint core.EndPoint) map[string]string {
+	res := make(map[string]string)
+	url := endpoint.GetURL()
+	if url != nil {
+		for k, v := range url.Parameters {
+			if strings.HasPrefix(k, core.DefaultMetaPrefix) || strings.HasPrefix(k, envPrefix) {
+				res[k] = v
+			}
+		}
+	}
+	return res
+}
+
+func getRemoteDynamicMeta(cacheKey string, endpoint core.EndPoint) (map[string]string, error) {
+	if _, ok := notSupportCache.Get(cacheKey); ok || !isSupport(cacheKey, endpoint.GetURL()) {
+		return nil, ServiceNotSupportError
+	}
+	if !endpoint.IsAvailable() {
+		return nil, errors.New("endpoint unavailable")
+	}
+	resp := endpoint.Call(getMetaServiceRequest())
+	if resp.GetException() != nil {
+		if resp.GetException().ErrMsg == core.ServiceNotSupport {
+			notSupportCache.Set(cacheKey, true, cache.DefaultExpiration)
+			return nil, ServiceNotSupportError
+		}
+		return nil, errors.New(resp.GetException().ErrMsg)
+	}
+	reply := make(map[string]string)
+	err := resp.ProcessDeserializable(&reply)
+	if err != nil {
+		return nil, err
+	}
+	// multiple serialization might encode empty map into interface{}, not map[string]string
+	// in this case, return a public empty string map
+	if res, ok := resp.GetValue().(map[string]string); ok && res != nil {
+		return res, nil
+	}
+	return metaEmptyMap, nil
+}
+
+func getMetaServiceRequest() core.Request {
+	req := &core.MotanRequest{
+		RequestID:   endpoint.GenerateRequestID(),
+		ServiceName: MetaServiceName,
+		Method:      MetaMethodName,
+		Attachment:  core.NewStringMap(core.DefaultAttachmentSize),
+		Arguments:   []interface{}{},
+	}
+	req.SetAttachment(mpro.MFrameworkService, "y")
+	return req
+}
+
+func getCacheKey(url *core.URL) string {
+	return url.Host + ":" + url.GetPortStr()
+}
+
+func isSupport(cacheKey string, url *core.URL) bool {
+	// check dynamicMeta config, protocol and serializer
+	if url.GetBoolValue(core.DynamicMetaKey, core.DefaultDynamicMeta) &&
+		!notSupportSerializer[url.GetStringParamsWithDefault(core.SerializationKey, "")] &&
+		supportProtocols[url.Protocol] {
+		return true
+	}
+	notSupportCache.Set(cacheKey, true, cache.DefaultExpiration)
+	return false
+}
+
+func ClearMetaCache() {
+	metaCache.Flush()
+	notSupportCache.Flush()
+}
diff --git a/meta/service.go b/meta/service.go
new file mode 100644
index 00000000..326a7843
--- /dev/null
+++ b/meta/service.go
@@ -0,0 +1,13 @@
+package meta
+
+const (
+	MetaServiceName = "com.weibo.api.motan.runtime.meta.MetaService"
+	MetaMethodName  = "getDynamicMeta"
+)
+
+type MetaService struct {
+}
+
+func (s *MetaService) getDynamicMeta() map[string]string {
+	return GetDynamicMeta()
+}
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 9180ef35..d2f6e560 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -39,6 +39,10 @@ const (
 	DefaultStatService     = "status"
 	DefaultStatRole        = "motan-agent"
 	DefaultStatApplication = "unknown"
+
+	DefaultRuntimeErrorApplication      = "runtime-error"
+	DefaultRuntimeCircuitBreakerGroup   = "circuit-breaker"
+	DefaultRuntimeCircuitBreakerService = "endpoint"
 )
 
 var (
diff --git a/protocol/motanProtocol.go b/protocol/motanProtocol.go
index ea9321ec..789db413 100644
--- a/protocol/motanProtocol.go
+++ b/protocol/motanProtocol.go
@@ -41,18 +41,19 @@ const (
 )
 
 const (
-	MPath          = "M_p"
-	MMethod        = "M_m"
-	MException     = "M_e"
-	MProcessTime   = "M_pt"
-	MMethodDesc    = "M_md"
-	MGroup         = "M_g"
-	MProxyProtocol = "M_pp"
-	MVersion       = "M_v"
-	MModule        = "M_mdu"
-	MSource        = "M_s"
-	MRequestID     = "M_rid"
-	MTimeout       = "M_tmo"
+	MPath             = "M_p"
+	MMethod           = "M_m"
+	MException        = "M_e"
+	MProcessTime      = "M_pt"
+	MMethodDesc       = "M_md"
+	MGroup            = "M_g"
+	MProxyProtocol    = "M_pp"
+	MVersion          = "M_v"
+	MModule           = "M_mdu"
+	MSource           = "M_s"
+	MRequestID        = "M_rid"
+	MTimeout          = "M_tmo"
+	MFrameworkService = "M_fws"
 )
 
 type Header struct {
diff --git a/provider/cgiProvider.go b/provider/cgiProvider.go
index e3ce4416..0e5cd243 100644
--- a/provider/cgiProvider.go
+++ b/provider/cgiProvider.go
@@ -30,6 +30,13 @@ type CgiProvider struct {
 	url *motan.URL
 }
 
+func (c *CgiProvider) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{
+		motan.RuntimeNameKey: c.GetName(),
+	}
+	return info
+}
+
 func (c *CgiProvider) Initialize() {
 }
 
diff --git a/provider/httpProvider.go b/provider/httpProvider.go
index a5b47ec7..f4175238 100644
--- a/provider/httpProvider.go
+++ b/provider/httpProvider.go
@@ -43,6 +43,16 @@ type HTTPProvider struct {
 	enableHttpException bool
 }
 
+func (h *HTTPProvider) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{
+		motan.RuntimeNameKey: h.GetName(),
+	}
+	if h.url != nil {
+		info[motan.RuntimeUrlKey] = h.url.ToExtInfo()
+	}
+	return info
+}
+
 const (
 	// DefaultMotanMethodConfKey for default motan method conf, when make a http call without a specific motan method
 	DefaultMotanMethodConfKey = "http_default_motan_method"
@@ -261,7 +271,7 @@ func (h *HTTPProvider) DoTransparentProxy(request motan.Request, t int64, ip str
 		fillHttpException(resp, http.StatusNotFound, t, err.Error())
 		return resp
 	}
-	// sets rewrite
+	// set rewrite
 	httpReq.URI().SetScheme(h.proxySchema)
 	httpReq.URI().SetPath(rewritePath)
 	request.GetAttachments().Range(func(k, v string) bool {
diff --git a/provider/httpProvider_test.go b/provider/httpProvider_test.go
index cf587864..af843262 100644
--- a/provider/httpProvider_test.go
+++ b/provider/httpProvider_test.go
@@ -68,6 +68,14 @@ func TestHTTPProvider_Call(t *testing.T) {
 	body, _ := serialization.SerializeMulti(req.Arguments)
 	req.Arguments = []interface{}{&core.DeserializableValue{Serialization: serialization, Body: body}}
 	assert.Equal(t, "/2/p1/test?a=b", string(provider.Call(req).GetValue().([]interface{})[1].([]byte)))
+
+	// runtime info
+	info := provider.GetRuntimeInfo()
+	assert.NotNil(t, info)
+
+	urlInfo, ok := info[core.RuntimeUrlKey]
+	assert.True(t, ok)
+	assert.Equal(t, providerURL.ToExtInfo(), urlInfo)
 }
 
 func TestHTTPProvider_Http_Exception(t *testing.T) {
diff --git a/provider/httpxProvider.go b/provider/httpxProvider.go
index dee3a26d..6d67ba19 100644
--- a/provider/httpxProvider.go
+++ b/provider/httpxProvider.go
@@ -32,6 +32,14 @@ type HTTPXProvider struct {
 	mixVars    []string
 }
 
+func (h *HTTPXProvider) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	if h.url != nil {
+		info[motan.RuntimeUrlKey] = h.url.ToExtInfo()
+	}
+	return info
+}
+
 // Initialize http provider
 func (h *HTTPXProvider) Initialize() {
 	h.httpClient = &fasthttp.Client{
diff --git a/provider/metaProvider.go b/provider/metaProvider.go
new file mode 100644
index 00000000..bde124b8
--- /dev/null
+++ b/provider/metaProvider.go
@@ -0,0 +1,50 @@
+package provider
+
+import (
+	motan "github.com/weibocom/motan-go/core"
+	"github.com/weibocom/motan-go/meta"
+)
+
+type MetaProvider struct{}
+
+func (m *MetaProvider) Initialize() {}
+
+func (m *MetaProvider) Call(request motan.Request) motan.Response {
+	resp := &motan.MotanResponse{
+		RequestID: request.GetRequestID(),
+		Value:     meta.GetDynamicMeta(),
+	}
+	return resp
+}
+
+func (m *MetaProvider) GetPath() string {
+	return "com.weibo.api.motan.runtime.meta.MetaService"
+}
+
+func (m *MetaProvider) SetService(s interface{}) {}
+
+func (m *MetaProvider) SetContext(context *motan.Context) {}
+
+func (m *MetaProvider) GetName() string {
+	return "metaProvider"
+}
+
+func (m *MetaProvider) GetURL() *motan.URL {
+	return nil
+}
+
+func (m *MetaProvider) SetURL(url *motan.URL) {}
+
+func (m *MetaProvider) SetSerialization(s motan.Serialization) {}
+
+func (m *MetaProvider) SetProxy(proxy bool) {}
+
+func (m *MetaProvider) Destroy() {}
+
+func (m *MetaProvider) IsAvailable() bool {
+	return true
+}
+
+func (m *MetaProvider) GetRuntimeInfo() map[string]interface{} {
+	return make(map[string]interface{})
+}
diff --git a/provider/motanProvider.go b/provider/motanProvider.go
index b2c5f860..b1fb5bec 100644
--- a/provider/motanProvider.go
+++ b/provider/motanProvider.go
@@ -15,6 +15,16 @@ type MotanProvider struct {
 	extFactory motan.ExtensionFactory
 }
 
+func (m *MotanProvider) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	if m.url != nil {
+		info[motan.RuntimeUrlKey] = m.url.ToExtInfo()
+	}
+	info[motan.RuntimeAvailableKey] = m.available
+	info[motan.RuntimeEndpointKey] = m.ep.GetRuntimeInfo()
+	return info
+}
+
 const (
 	ProxyHostKey = "proxy.host"
 	DefaultHost  = "127.0.0.1"
@@ -29,9 +39,6 @@ func (m *MotanProvider) Initialize() {
 		vlog.Errorln("reverse proxy service port config error!")
 		return
 	}
-	if protocol == "motan2" || protocol == "motan" { // TODO temp compatible with motan1. remove if MotanCommonEndpoint as default Motan endpoint
-		protocol = "motanV1Compatible"
-	}
 	host := m.url.GetParam(ProxyHostKey, DefaultHost)
 	endpointURL := m.url.Copy()
 	endpointURL.Protocol = protocol
diff --git a/provider/motanProvider_test.go b/provider/motanProvider_test.go
index 2a086518..eab09009 100644
--- a/provider/motanProvider_test.go
+++ b/provider/motanProvider_test.go
@@ -75,3 +75,39 @@ func TestXForwardedFor(t *testing.T) {
 	providerCorr.Call(request)
 	assert.NotEqual(t, request.GetAttachment(motan.XForwardedForLower), "test")
 }
+
+func TestMotanProvider_GetRuntimeInfo(t *testing.T) {
+	//init factory
+	factory := &motan.DefaultExtensionFactory{}
+	factory.Initialize()
+	endpoint.RegistDefaultEndpoint(factory)
+	RegistDefaultProvider(factory)
+
+	//init motanProvider
+	mContext := motan.Context{}
+	mContext.ConfigFile = confFilePath
+	mContext.Initialize()
+	request := &motan.MotanRequest{}
+	request.SetAttachment(motan.XForwardedForLower, "test")
+	url := mContext.ServiceURLs[serviceName]
+	//call correct
+	providerCorr := MotanProvider{url: url, extFactory: factory}
+	providerCorr.Initialize()
+
+	// runtime info
+	info := providerCorr.GetRuntimeInfo()
+	assert.NotNil(t, info)
+
+	urlInfo, ok := info[motan.RuntimeUrlKey]
+	assert.True(t, ok)
+	assert.Equal(t, url.ToExtInfo(), urlInfo)
+
+	available, ok := info[motan.RuntimeAvailableKey]
+	assert.True(t, ok)
+	assert.Equal(t, providerCorr.available, available)
+
+	endpointInfo, ok := info[motan.RuntimeEndpointKey]
+	assert.True(t, ok)
+	assert.Equal(t, providerCorr.ep.GetRuntimeInfo(), endpointInfo)
+	assert.NotNil(t, endpointInfo)
+}
diff --git a/provider/provider.go b/provider/provider.go
index 37862de8..8b70cdd8 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -55,6 +55,14 @@ type DefaultProvider struct {
 	url     *motan.URL
 }
 
+func (d *DefaultProvider) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	if d.url != nil {
+		info[motan.RuntimeUrlKey] = d.url.ToExtInfo()
+	}
+	return info
+}
+
 func (d *DefaultProvider) Initialize() {
 	d.methods = make(map[string]reflect.Value, 32)
 	if d.service != nil && d.url != nil {
@@ -133,6 +141,12 @@ type MockProvider struct {
 	service      interface{}
 }
 
+func (m *MockProvider) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: m.GetName(),
+	}
+}
+
 func (m *MockProvider) GetName() string {
 	return "mockProvider"
 }
diff --git a/registry/consulRegistry.go b/registry/consulRegistry.go
index ae1c1168..56e3c3cd 100644
--- a/registry/consulRegistry.go
+++ b/registry/consulRegistry.go
@@ -13,6 +13,12 @@ type ConsulRegistry struct {
 	heartInterval time.Duration
 }
 
+func (v *ConsulRegistry) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: v.GetName(),
+	}
+}
+
 func (v *ConsulRegistry) GetURL() *motan.URL {
 	return v.url
 }
diff --git a/registry/directRegistry.go b/registry/directRegistry.go
index 9bc46783..6743a04d 100644
--- a/registry/directRegistry.go
+++ b/registry/directRegistry.go
@@ -13,6 +13,12 @@ type DirectRegistry struct {
 	urls []*motan.URL
 }
 
+func (d *DirectRegistry) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: d.GetName(),
+	}
+}
+
 func (d *DirectRegistry) GetURL() *motan.URL {
 	return d.url
 }
diff --git a/registry/localRegistry.go b/registry/localRegistry.go
index cda744b9..eff8c4da 100644
--- a/registry/localRegistry.go
+++ b/registry/localRegistry.go
@@ -8,6 +8,12 @@ type LocalRegistry struct {
 	url *motan.URL
 }
 
+func (d *LocalRegistry) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: d.GetName(),
+	}
+}
+
 func (d *LocalRegistry) GetURL() *motan.URL {
 	return d.url
 }
diff --git a/registry/meshRegistry.go b/registry/meshRegistry.go
index fa72c438..d362f46f 100644
--- a/registry/meshRegistry.go
+++ b/registry/meshRegistry.go
@@ -32,6 +32,12 @@ type MeshRegistry struct {
 	subscribeLock     sync.Mutex
 }
 
+func (r *MeshRegistry) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: r.GetName(),
+	}
+}
+
 func (r *MeshRegistry) Initialize() {
 	r.registeredService = make(map[string]*motan.URL)
 	r.subscribedService = make(map[string]*motan.URL)
diff --git a/registry/registry.go b/registry/registry.go
index 0dca1d32..b7e716c5 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -7,6 +7,7 @@ import (
 	"path/filepath"
 	"strconv"
 	"sync"
+	"sync/atomic"
 	"time"
 	"unsafe"
 
@@ -19,7 +20,6 @@ const (
 	DefaultHeartbeatInterval = 10 * 1000 //ms
 	DefaultTimeout           = 3 * 1000  //ms
 	DefaultSnapshotDir       = "./snapshot"
-	DefaultFailbackInterval  = 30 * 1000 //ms
 )
 
 // ext name
@@ -32,9 +32,18 @@ const (
 )
 
 var (
-	setSnapshotConfLock sync.Mutex
+	setSnapshotConfLock     sync.Mutex
+	defaultFailbackInterval uint32 = 30000 // ms
 )
 
+func GetFailbackInterval() uint32 {
+	return atomic.LoadUint32(&defaultFailbackInterval)
+}
+
+func SetFailbackInterval(interval uint32) {
+	atomic.StoreUint32(&defaultFailbackInterval, interval)
+}
+
 type SnapshotNodeInfo struct {
 	ExtInfo string `json:"extInfo"`
 	Addr    string `json:"address"`
diff --git a/registry/zkRegistry.go b/registry/zkRegistry.go
index 7fd239cf..6b0f39f7 100644
--- a/registry/zkRegistry.go
+++ b/registry/zkRegistry.go
@@ -47,6 +47,12 @@ type ZkRegistry struct {
 	subscribedCommandMap map[string]map[motan.CommandNotifyListener]*motan.URL // save all subscribed commands with listeners
 }
 
+func (z *ZkRegistry) GetRuntimeInfo() map[string]interface{} {
+	return map[string]interface{}{
+		motan.RuntimeNameKey: z.GetName(),
+	}
+}
+
 // Initialize initializes all structure members and handles new session.
 func (z *ZkRegistry) Initialize() {
 	z.sessionTimeout = time.Duration(
diff --git a/server.go b/server.go
index 63672ac2..8baa135a 100644
--- a/server.go
+++ b/server.go
@@ -207,6 +207,10 @@ func (m *MSContext) Initialize() {
 	}
 }
 
+func (m *MSContext) GetContext() *motan.Context {
+	return m.context
+}
+
 // RegisterService register service with serviceId for config ref.
 // the type.string will used as serviceId if sid is not set. e.g. 'packageName.structName'
 func (m *MSContext) RegisterService(s interface{}, sid string) error {
@@ -271,7 +275,7 @@ func (m *MSContext) GetRegistryStatus() []map[string]*motan.RegistryStatus {
 
 func (m *MSContext) startRegistryFailback() {
 	vlog.Infoln("start MSContext registry failback")
-	ticker := time.NewTicker(registry.DefaultFailbackInterval * time.Millisecond)
+	ticker := time.NewTicker(time.Duration(registry.GetFailbackInterval()) * time.Millisecond)
 	defer ticker.Stop()
 	for range ticker.C {
 		m.registryLock.Lock()
diff --git a/server/httpProxyServer.go b/server/httpProxyServer.go
index 70fa2a6b..494cd6eb 100644
--- a/server/httpProxyServer.go
+++ b/server/httpProxyServer.go
@@ -55,6 +55,17 @@ type HTTPProxyServer struct {
 	defaultDomain  string
 }
 
+func (s *HTTPProxyServer) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	if s.url != nil {
+		info[core.RuntimeUrlKey] = s.url.ToExtInfo()
+	}
+	info[core.RuntimeDenyKey] = s.deny
+	info[core.RuntimeKeepaliveKey] = s.keepalive
+	info[core.RuntimeDefaultDomainKey] = s.defaultDomain
+	return info
+}
+
 func NewHTTPProxyServer(url *core.URL) *HTTPProxyServer {
 	return &HTTPProxyServer{url: url}
 }
diff --git a/server/motanserver.go b/server/motanserver.go
index 7fdd21fb..2f73e645 100644
--- a/server/motanserver.go
+++ b/server/motanserver.go
@@ -55,6 +55,18 @@ type MotanServer struct {
 	heartbeatEnabled bool
 }
 
+func (m *MotanServer) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	info[motan.RuntimeProxyKey] = m.proxy
+	info[motan.RuntimeMaxContentLengthKey] = m.maxContextLength
+	info[motan.RuntimeHeartbeatEnabledKey] = m.heartbeatEnabled
+	if m.URL != nil {
+		info[motan.RuntimeUrlKey] = m.URL.ToExtInfo()
+	}
+	info[motan.RuntimeMessageHandlerKey] = m.handler.GetRuntimeInfo()
+	return info
+}
+
 func (m *MotanServer) Open(block bool, proxy bool, handler motan.MessageHandler, extFactory motan.ExtensionFactory) error {
 	m.isDestroyed = make(chan bool, 1)
 	m.heartbeatEnabled = true
diff --git a/server/server.go b/server/server.go
index 78f60db8..cae13134 100644
--- a/server/server.go
+++ b/server/server.go
@@ -3,6 +3,9 @@ package server
 import (
 	"errors"
 	"fmt"
+	"github.com/weibocom/motan-go/meta"
+	mpro "github.com/weibocom/motan-go/protocol"
+	"github.com/weibocom/motan-go/provider"
 	"sync"
 
 	motan "github.com/weibocom/motan-go/core"
@@ -50,6 +53,16 @@ type DefaultExporter struct {
 	// 服务管理单位,负责服务注册、心跳、导出和销毁,内部包含provider,与provider是一对一关系
 }
 
+func (d *DefaultExporter) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	if d.url != nil {
+		info[motan.RuntimeUrlKey] = d.url.ToExtInfo()
+	}
+	info[motan.RuntimeProviderKey] = d.provider.GetRuntimeInfo()
+	info[motan.RuntimeIsAvailableKey] = d.available
+	return info
+}
+
 func (d *DefaultExporter) Export(server motan.Server, extFactory motan.ExtensionFactory, context *motan.Context) (err error) {
 	d.lock.Lock()
 	defer d.lock.Unlock()
@@ -64,6 +77,12 @@ func (d *DefaultExporter) Export(server motan.Server, extFactory motan.Extension
 	d.extFactory = extFactory
 	d.server = server
 	d.url = d.provider.GetURL()
+	// add server side meta info to the url, so these meta info can be passed to the client side through the registration mechanism.
+	if d.url.GetBoolValue(motan.URLRegisterMeta, motan.DefaultRegisterMeta) {
+		for k, v := range meta.GetEnvMeta() {
+			d.url.PutParam(k, v)
+		}
+	}
 	d.url.PutParam(motan.NodeTypeKey, motan.NodeTypeService) // node type must be service in export
 	regs, ok := d.url.Parameters[motan.RegistryKey]
 	if !ok {
@@ -142,11 +161,33 @@ func (d *DefaultExporter) SetURL(url *motan.URL) {
 }
 
 type DefaultMessageHandler struct {
-	providers map[string]motan.Provider
+	providers          map[string]motan.Provider
+	frameworkProviders map[string]motan.Provider
+}
+
+func (d *DefaultMessageHandler) GetName() string {
+	return "defaultMessageHandler"
+}
+
+func (d *DefaultMessageHandler) GetRuntimeInfo() map[string]interface{} {
+	info := map[string]interface{}{}
+	info[motan.RuntimeMessageHandlerTypeKey] = d.GetName()
+	providersInfo := map[string]interface{}{}
+	for s, provider := range d.providers {
+		providersInfo[s] = provider.GetRuntimeInfo()
+	}
+	info[motan.RuntimeProvidersKey] = providersInfo
+	return info
 }
 
 func (d *DefaultMessageHandler) Initialize() {
 	d.providers = make(map[string]motan.Provider)
+	d.frameworkProviders = make(map[string]motan.Provider)
+	d.initFrameworkServiceProvider()
+}
+
+func (d *DefaultMessageHandler) initFrameworkServiceProvider() {
+	d.frameworkProviders[meta.MetaServiceName] = &provider.MetaProvider{}
 }
 
 func (d *DefaultMessageHandler) AddProvider(p motan.Provider) error {
@@ -170,14 +211,21 @@ func (d *DefaultMessageHandler) Call(request motan.Request) (res motan.Response)
 		res = motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{ErrCode: 500, ErrMsg: "provider call panic", ErrType: motan.ServiceException})
 		vlog.Errorf("provider call panic. req:%s", motan.GetReqInfo(request))
 	})
+	if mfs := request.GetAttachment(mpro.MFrameworkService); mfs != "" {
+		if fp, ok := d.frameworkProviders[request.GetServiceName()]; ok {
+			return fp.(motan.Provider).Call(request)
+		}
+		//throw specific exception to avoid triggering forced fusing on the client side。
+		return motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{ErrCode: 501, ErrMsg: motan.ServiceNotSupport, ErrType: motan.ServiceException})
+	}
 	p := d.providers[request.GetServiceName()]
 	if p != nil {
 		res = p.Call(request)
 		res.GetRPCContext(true).GzipSize = int(p.GetURL().GetIntValue(motan.GzipSizeKey, 0))
 		return res
 	}
-	vlog.Errorf("not found provider for %s", motan.GetReqInfo(request))
-	return motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{ErrCode: 500, ErrMsg: "not found provider for " + request.GetServiceName(), ErrType: motan.ServiceException})
+	vlog.Errorf("%s%s%s", motan.ProviderNotExistPrefix, request.GetServiceName(), motan.GetReqInfo(request))
+	return motan.BuildExceptionResponse(request.GetRequestID(), &motan.Exception{ErrCode: motan.EProviderNotExist, ErrMsg: motan.ProviderNotExistPrefix + request.GetServiceName(), ErrType: motan.ServiceException})
 }
 
 type FilterProviderWrapper struct {
@@ -185,6 +233,20 @@ type FilterProviderWrapper struct {
 	filter   motan.EndPointFilter
 }
 
+func (f *FilterProviderWrapper) GetRuntimeInfo() map[string]interface{} {
+	info := f.provider.GetRuntimeInfo()
+	var filterInfo []interface{}
+	filter := f.filter
+	for filter != nil {
+		filterInfo = append(filterInfo, filter.GetRuntimeInfo())
+		filter = filter.GetNext()
+	}
+	if len(filterInfo) > 0 {
+		info[motan.RuntimeFiltersKey] = filterInfo
+	}
+	return info
+}
+
 func (f *FilterProviderWrapper) SetService(s interface{}) {
 	f.provider.SetService(s)
 }
diff --git a/server/server_test.go b/server/server_test.go
new file mode 100644
index 00000000..fc41bfc7
--- /dev/null
+++ b/server/server_test.go
@@ -0,0 +1,110 @@
+package server
+
+import (
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/suite"
+	"github.com/weibocom/motan-go/cluster"
+	"github.com/weibocom/motan-go/core"
+	"sync"
+	"testing"
+)
+
+type APITestSuite struct {
+	suite.Suite
+	motanServer               *MotanServer
+	motanServerUrl            *core.URL
+	motanServerMessageHandler core.MessageHandler
+	httpProxyServerUrl        *core.URL
+	httpProxyServer           *HTTPProxyServer
+}
+
+func (s *APITestSuite) SetupTest() {
+	s.motanServerUrl = &core.URL{
+		Protocol: "motan2",
+		Host:     "127.0.0.1",
+		Port:     20243,
+		Path:     "testpath",
+		Group:    "testgroup",
+	}
+	s.motanServerMessageHandler = &DefaultMessageHandler{}
+	server := &MotanServer{URL: s.motanServerUrl}
+	server.Open(false, false, s.motanServerMessageHandler, nil)
+	s.motanServer = server
+
+	s.httpProxyServerUrl = &core.URL{
+		Host:  "127.0.0.1",
+		Port:  20241,
+		Path:  "test.path",
+		Group: "test.group",
+	}
+	s.httpProxyServer = NewHTTPProxyServer(s.httpProxyServerUrl)
+	s.httpProxyServer.Open(false, false, &testHttpClusterGetter{}, nil)
+}
+
+func (s *APITestSuite) TearDownTest() {
+	s.motanServer.Destroy()
+	s.httpProxyServer.Destroy()
+}
+
+func TestAPITestSuite(t *testing.T) {
+	suite.Run(t, new(APITestSuite))
+}
+
+func (s *APITestSuite) TestMotanServerRuntimeInfo() {
+	info := s.motanServer.GetRuntimeInfo()
+	assert.NotNil(s.T(), info)
+	urlInfo, ok := info[core.RuntimeUrlKey]
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), s.motanServerUrl.ToExtInfo(), urlInfo.(string))
+
+	heartbeatEnabled, ok := info[core.RuntimeHeartbeatEnabledKey]
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), s.motanServer.heartbeatEnabled, heartbeatEnabled)
+
+	proxy, ok := info[core.RuntimeProxyKey]
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), false, proxy)
+
+	maxContentLength, ok := info[core.RuntimeMaxContentLengthKey]
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), core.DefaultMaxContentLength, maxContentLength)
+
+	messageHandlerInfo, ok := info[core.RuntimeMessageHandlerKey]
+	assert.True(s.T(), ok)
+
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), s.motanServerMessageHandler.GetRuntimeInfo(), messageHandlerInfo)
+}
+
+func (s *APITestSuite) TestHttpProxyServerRuntimeInfo() {
+	info := s.httpProxyServer.GetRuntimeInfo()
+	assert.NotNil(s.T(), info)
+
+	urlInfo, ok := info[core.RuntimeUrlKey]
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), s.httpProxyServerUrl.ToExtInfo(), urlInfo.(string))
+
+	deny, ok := info[core.RuntimeDenyKey]
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), s.httpProxyServer.deny, deny)
+
+	keepalive, ok := info[core.RuntimeKeepaliveKey]
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), s.httpProxyServer.keepalive, keepalive)
+
+	domain, ok := info[core.RuntimeDefaultDomainKey]
+	assert.True(s.T(), ok)
+	assert.Equal(s.T(), s.httpProxyServer.defaultDomain, domain)
+}
+
+type testHttpClusterGetter struct {
+	cluster *cluster.HTTPCluster
+	once    sync.Once
+}
+
+func (t *testHttpClusterGetter) GetHTTPCluster(host string) *cluster.HTTPCluster {
+	t.once.Do(func() {
+		t.cluster = cluster.NewHTTPCluster(&core.URL{}, false, nil, nil)
+	})
+	return t.cluster
+}
diff --git a/server_test.go b/server_test.go
index 3ee7b110..850ce27c 100644
--- a/server_test.go
+++ b/server_test.go
@@ -95,6 +95,9 @@ motan-server:
 }
 
 func TestServerRegisterFailBack(t *testing.T) {
+	currentFailbackInterval := registry.GetFailbackInterval()
+	newInterval := uint32(1000 * 5)
+	registry.SetFailbackInterval(newInterval)
 	assert := assert2.New(t)
 	cfgText := `
 motan-server:
@@ -148,7 +151,7 @@ motan-service:
 		}
 	}
 	setRegistryFailSwitcher(false)
-	time.Sleep(registry.DefaultFailbackInterval * time.Millisecond)
+	time.Sleep(time.Duration(newInterval) * time.Millisecond)
 	m = mscontext.GetRegistryStatus()
 	assert.Equal(len(m), 1)
 	for _, mm := range m[0] {
@@ -166,7 +169,7 @@ motan-service:
 		}
 	}
 	setRegistryFailSwitcher(false)
-	time.Sleep(registry.DefaultFailbackInterval * time.Millisecond)
+	time.Sleep(time.Duration(newInterval) * time.Millisecond)
 	m = mscontext.GetRegistryStatus()
 	assert.Equal(len(m), 1)
 	for _, mm := range m[0] {
@@ -174,6 +177,7 @@ motan-service:
 			assert.Equal(mm.Status, motan.UnregisterSuccess)
 		}
 	}
+	registry.SetFailbackInterval(currentFailbackInterval)
 }
 
 func TestNewMotanServerContextFromConfig(t *testing.T) {
diff --git a/version.go b/version.go
index 3b5f329f..d37ef2f5 100644
--- a/version.go
+++ b/version.go
@@ -1,5 +1,5 @@
 package motan
 
 const (
-	Version = "1.0.0"
+	Version = "1.2.0"
 )