Skip to content
Snippets Groups Projects
Unverified Commit 3b28d2a0 authored by Baster's avatar Baster Committed by GitHub
Browse files

Merge branch 'develop' into develop

parents 3a9e2304 0384e201
No related branches found
No related tags found
No related merge requests found
Showing
with 110 additions and 904 deletions
<!-- Thanks for sending a pull request! <!-- Thanks for sending a pull request!
Read https://github.com/apache/dubbo-go/blob/master/contributing.md before commit pull request.
--> -->
**What this PR does**: **What this PR does**:
......
...@@ -19,10 +19,6 @@ jobs: ...@@ -19,10 +19,6 @@ jobs:
os: os:
- ubuntu-latest - ubuntu-latest
env:
DING_TOKEN: "6374f1bf8d4f23cde81d4a4b8c1f0bc98cc92b5151ca938ab938d3d7f4230fc4"
DING_SIGN: "SECa98677289194bb0e5caec3051301d06515750ff1bd2f932a4704298afb2e0ae6"
steps: steps:
- name: Set up Go 1.x - name: Set up Go 1.x
...@@ -68,45 +64,4 @@ jobs: ...@@ -68,45 +64,4 @@ jobs:
chmod +x integrate_test.sh && ./integrate_test.sh chmod +x integrate_test.sh && ./integrate_test.sh
- name: Coverage - name: Coverage
run: bash <(curl -s https://codecov.io/bash) run: bash <(curl -s https://codecov.io/bash)
\ No newline at end of file
# Because the contexts of push and PR are different, there are two Notify.
# Notifications are triggered only in the apache/dubbo-go repository.
- name: DingTalk Message Notify only Push
uses: zcong1993/actions-ding@v3.0.1
# Whether job is successful or not, always () is always true.
if: |
always() &&
github.event_name == 'push' &&
github.repository == 'apache/dubbo-go'
with:
# DingDing bot token
dingToken: ${{ env.DING_TOKEN }}
secret: ${{ env.DING_SIGN }}
# Post Body to send
body: |
{
"msgtype": "markdown",
"markdown": {
"title": "Github Actions",
"text": "## Github Actions \n - name: CI \n - repository: ${{ github.repository }} \n - trigger: ${{ github.actor }} \n - event: ${{ github.event_name }} \n - ref: ${{ github.ref }} \n - status: [${{ job.status }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) \n - environment: ${{ runner.os }} \n - SHA: [${{ github.sha }}](${{ github.event.compare }})"
}
}
- name: DingTalk Message Notify only PR
uses: zcong1993/actions-ding@v3.0.1
if: |
always() &&
github.event_name == 'pull_request' &&
github.repository == 'apache/dubbo-go'
with:
dingToken: ${{ env.DING_TOKEN }}
secret: ${{ env.DING_SIGN }}
body: |
{
"msgtype": "markdown",
"markdown": {
"title": "Github Actions",
"text": "## Github Actions \n - name: CI \n - repository: ${{ github.repository }} \n - pr_title: ${{ github.event.pull_request.title }} \n - trigger: ${{ github.actor }} \n - event: ${{ github.event_name }} \n - ref: [${{ github.ref }}](${{ github.event.pull_request._links.html.href }}) \n - status: [${{ job.status }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) \n - environment: ${{ runner.os }} \n > SHA: [${{ github.sha }}](${{ github.event.pull_request._links.html.href }})"
}
}
...@@ -36,8 +36,8 @@ xcopy /f "%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/" ...@@ -36,8 +36,8 @@ xcopy /f "%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/"
md cluster\router\condition\zookeeper-4unittest\contrib\fatjar md cluster\router\condition\zookeeper-4unittest\contrib\fatjar
xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/" xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/"
mkdir -p cluster/router/tag/zookeeper-4unittest/contrib/fatjar md cluster/router/tag/zookeeper-4unittest/contrib/fatjar
cp ${zkJar} cluster/router/tag/zookeeper-4unittest/contrib/fatjar xcopy /f "%zkJar%" "cluster/router/tag/zookeeper-4unittest/contrib/fatjar/"
md metadata\report\zookeeper\zookeeper-4unittest\contrib\fatjar md metadata\report\zookeeper\zookeeper-4unittest\contrib\fatjar
xcopy /f "%zkJar%" "metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar/" xcopy /f "%zkJar%" "metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar/"
\ No newline at end of file
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
) )
import ( import (
gxnet "github.com/dubbogo/gost/net"
perrors "github.com/pkg/errors" perrors "github.com/pkg/errors"
"go.uber.org/atomic" "go.uber.org/atomic"
) )
...@@ -72,7 +71,7 @@ func (invoker *baseClusterInvoker) IsAvailable() bool { ...@@ -72,7 +71,7 @@ func (invoker *baseClusterInvoker) IsAvailable() bool {
//check invokers availables //check invokers availables
func (invoker *baseClusterInvoker) checkInvokers(invokers []protocol.Invoker, invocation protocol.Invocation) error { func (invoker *baseClusterInvoker) checkInvokers(invokers []protocol.Invoker, invocation protocol.Invocation) error {
if len(invokers) == 0 { if len(invokers) == 0 {
ip, _ := gxnet.GetLocalIP() ip := common.GetLocalIp()
return perrors.Errorf("Failed to invoke the method %v. No provider available for the service %v from "+ return perrors.Errorf("Failed to invoke the method %v. No provider available for the service %v from "+
"registry %v on the consumer %v using the dubbo version %v .Please check if the providers have been started and registered.", "registry %v on the consumer %v using the dubbo version %v .Please check if the providers have been started and registered.",
invocation.MethodName(), invoker.directory.GetUrl().SubURL.Key(), invoker.directory.GetUrl().String(), ip, constant.Version) invocation.MethodName(), invoker.directory.GetUrl().SubURL.Key(), invoker.directory.GetUrl().String(), ip, constant.Version)
...@@ -84,7 +83,7 @@ func (invoker *baseClusterInvoker) checkInvokers(invokers []protocol.Invoker, in ...@@ -84,7 +83,7 @@ func (invoker *baseClusterInvoker) checkInvokers(invokers []protocol.Invoker, in
//check cluster invoker is destroyed or not //check cluster invoker is destroyed or not
func (invoker *baseClusterInvoker) checkWhetherDestroyed() error { func (invoker *baseClusterInvoker) checkWhetherDestroyed() error {
if invoker.destroyed.Load() { if invoker.destroyed.Load() {
ip, _ := gxnet.GetLocalIP() ip := common.GetLocalIp()
return perrors.Errorf("Rpc cluster invoker for %v on consumer %v use dubbo version %v is now destroyed! can not invoke any more. ", return perrors.Errorf("Rpc cluster invoker for %v on consumer %v use dubbo version %v is now destroyed! can not invoke any more. ",
invoker.directory.GetUrl().Service(), ip, constant.Version) invoker.directory.GetUrl().Service(), ip, constant.Version)
} }
......
...@@ -24,12 +24,12 @@ import ( ...@@ -24,12 +24,12 @@ import (
) )
import ( import (
gxnet "github.com/dubbogo/gost/net"
perrors "github.com/pkg/errors" perrors "github.com/pkg/errors"
) )
import ( import (
"github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/cluster"
"github.com/apache/dubbo-go/common"
"github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/constant"
"github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/common/logger"
"github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol"
...@@ -89,7 +89,7 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr ...@@ -89,7 +89,7 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr
return result return result
} }
ip, _ := gxnet.GetLocalIP() ip := common.GetLocalIp()
invokerSvc := invoker.GetUrl().Service() invokerSvc := invoker.GetUrl().Service()
invokerUrl := invoker.directory.GetUrl() invokerUrl := invoker.directory.GetUrl()
return &protocol.RPCResult{ return &protocol.RPCResult{
......
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
) )
import ( import (
gxnet "github.com/dubbogo/gost/net"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
...@@ -55,7 +54,7 @@ func TestBuildRouterChain(t *testing.T) { ...@@ -55,7 +54,7 @@ func TestBuildRouterChain(t *testing.T) {
assert.NotNil(t, directory) assert.NotNil(t, directory)
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP))
routeURL := getRouteURL(rule, anyURL) routeURL := getRouteURL(rule, anyURL)
routeURL.AddParam(constant.INTERFACE_KEY, "mock-app") routeURL.AddParam(constant.INTERFACE_KEY, "mock-app")
...@@ -79,7 +78,7 @@ func TestIsProperRouter(t *testing.T) { ...@@ -79,7 +78,7 @@ func TestIsProperRouter(t *testing.T) {
regURL := url regURL := url
regURL.AddParam(constant.APPLICATION_KEY, "mock-app") regURL.AddParam(constant.APPLICATION_KEY, "mock-app")
d := NewBaseDirectory(&regURL) d := NewBaseDirectory(&regURL)
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP))
routeURL := getRouteURL(rule, anyURL) routeURL := getRouteURL(rule, anyURL)
routeURL.AddParam(constant.APPLICATION_KEY, "mock-app") routeURL.AddParam(constant.APPLICATION_KEY, "mock-app")
......
...@@ -26,7 +26,6 @@ import ( ...@@ -26,7 +26,6 @@ import (
) )
import ( import (
"github.com/dubbogo/gost/net"
perrors "github.com/pkg/errors" perrors "github.com/pkg/errors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
...@@ -164,7 +163,7 @@ func TestRoute_matchWhen(t *testing.T) { ...@@ -164,7 +163,7 @@ func TestRoute_matchWhen(t *testing.T) {
} }
func TestRoute_matchFilter(t *testing.T) { func TestRoute_matchFilter(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
t.Logf("The local ip is %s", localIP) t.Logf("The local ip is %s", localIP)
url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService?default.serialization=fastjson") url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService?default.serialization=fastjson")
url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
...@@ -223,7 +222,7 @@ func TestRoute_methodRoute(t *testing.T) { ...@@ -223,7 +222,7 @@ func TestRoute_methodRoute(t *testing.T) {
func TestRoute_ReturnFalse(t *testing.T) { func TestRoute_ReturnFalse(t *testing.T) {
url, _ := common.NewURL("") url, _ := common.NewURL("")
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)}
inv := &invocation.RPCInvocation{} inv := &invocation.RPCInvocation{}
rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false")) rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false"))
...@@ -234,7 +233,7 @@ func TestRoute_ReturnFalse(t *testing.T) { ...@@ -234,7 +233,7 @@ func TestRoute_ReturnFalse(t *testing.T) {
} }
func TestRoute_ReturnEmpty(t *testing.T) { func TestRoute_ReturnEmpty(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
url, _ := common.NewURL("") url, _ := common.NewURL("")
invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)}
inv := &invocation.RPCInvocation{} inv := &invocation.RPCInvocation{}
...@@ -246,7 +245,7 @@ func TestRoute_ReturnEmpty(t *testing.T) { ...@@ -246,7 +245,7 @@ func TestRoute_ReturnEmpty(t *testing.T) {
} }
func TestRoute_ReturnAll(t *testing.T) { func TestRoute_ReturnAll(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
urlString := "dubbo://" + localIP + "/com.foo.BarService" urlString := "dubbo://" + localIP + "/com.foo.BarService"
dubboURL, _ := common.NewURL(urlString) dubboURL, _ := common.NewURL(urlString)
mockInvoker1 := NewMockInvoker(dubboURL, 1) mockInvoker1 := NewMockInvoker(dubboURL, 1)
...@@ -262,7 +261,7 @@ func TestRoute_ReturnAll(t *testing.T) { ...@@ -262,7 +261,7 @@ func TestRoute_ReturnAll(t *testing.T) {
} }
func TestRoute_HostFilter(t *testing.T) { func TestRoute_HostFilter(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
url1, _ := common.NewURL(factory333URL) url1, _ := common.NewURL(factory333URL)
url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
...@@ -281,7 +280,7 @@ func TestRoute_HostFilter(t *testing.T) { ...@@ -281,7 +280,7 @@ func TestRoute_HostFilter(t *testing.T) {
} }
func TestRoute_Empty_HostFilter(t *testing.T) { func TestRoute_Empty_HostFilter(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
url1, _ := common.NewURL(factory333URL) url1, _ := common.NewURL(factory333URL)
url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
...@@ -300,7 +299,7 @@ func TestRoute_Empty_HostFilter(t *testing.T) { ...@@ -300,7 +299,7 @@ func TestRoute_Empty_HostFilter(t *testing.T) {
} }
func TestRoute_False_HostFilter(t *testing.T) { func TestRoute_False_HostFilter(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
url1, _ := common.NewURL(factory333URL) url1, _ := common.NewURL(factory333URL)
url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
...@@ -319,7 +318,7 @@ func TestRoute_False_HostFilter(t *testing.T) { ...@@ -319,7 +318,7 @@ func TestRoute_False_HostFilter(t *testing.T) {
} }
func TestRoute_Placeholder(t *testing.T) { func TestRoute_Placeholder(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
url1, _ := common.NewURL(factory333URL) url1, _ := common.NewURL(factory333URL)
url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
...@@ -338,7 +337,7 @@ func TestRoute_Placeholder(t *testing.T) { ...@@ -338,7 +337,7 @@ func TestRoute_Placeholder(t *testing.T) {
} }
func TestRoute_NoForce(t *testing.T) { func TestRoute_NoForce(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
url1, _ := common.NewURL(factory333URL) url1, _ := common.NewURL(factory333URL)
url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
...@@ -355,7 +354,7 @@ func TestRoute_NoForce(t *testing.T) { ...@@ -355,7 +354,7 @@ func TestRoute_NoForce(t *testing.T) {
} }
func TestRoute_Force(t *testing.T) { func TestRoute_Force(t *testing.T) {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
url1, _ := common.NewURL(factory333URL) url1, _ := common.NewURL(factory333URL)
url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
......
...@@ -25,7 +25,6 @@ import ( ...@@ -25,7 +25,6 @@ import (
import ( import (
"github.com/RoaringBitmap/roaring" "github.com/RoaringBitmap/roaring"
"github.com/dubbogo/gost/container/set" "github.com/dubbogo/gost/container/set"
"github.com/dubbogo/gost/net"
perrors "github.com/pkg/errors" perrors "github.com/pkg/errors"
) )
...@@ -184,7 +183,7 @@ func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache router.Cache, ur ...@@ -184,7 +183,7 @@ func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache router.Cache, ur
return result return result
} else if c.Force { } else if c.Force {
rule, _ := url.GetParamAndDecoded(constant.RULE_KEY) rule, _ := url.GetParamAndDecoded(constant.RULE_KEY)
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule) logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule)
return result return result
} }
......
...@@ -48,6 +48,10 @@ type DefaultHealthChecker struct { ...@@ -48,6 +48,10 @@ type DefaultHealthChecker struct {
// IsHealthy evaluates the healthy state on the given Invoker based on the number of successive bad request // IsHealthy evaluates the healthy state on the given Invoker based on the number of successive bad request
// and the current active request // and the current active request
func (c *DefaultHealthChecker) IsHealthy(invoker protocol.Invoker) bool { func (c *DefaultHealthChecker) IsHealthy(invoker protocol.Invoker) bool {
if !invoker.IsAvailable() {
return false
}
urlStatus := protocol.GetURLStatus(invoker.GetUrl()) urlStatus := protocol.GetURLStatus(invoker.GetUrl())
if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestCountLimit() { if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestCountLimit() {
logger.Debugf("Invoker [%s] is currently in circuitbreaker tripped state", invoker.GetUrl().Key()) logger.Debugf("Invoker [%s] is currently in circuitbreaker tripped state", invoker.GetUrl().Key())
......
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package common
import gxnet "github.com/dubbogo/gost/net"
var localIp string
func GetLocalIp() string {
if len(localIp) != 0 {
return localIp
}
localIp, _ = gxnet.GetLocalIP()
return localIp
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package common
import (
"testing"
)
import (
"github.com/stretchr/testify/assert"
)
func TestGetLocalIp(t *testing.T) {
assert.NotNil(t, GetLocalIp())
}
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
) )
import ( import (
"github.com/apache/dubbo-go/protocol/invocation"
perrors "github.com/pkg/errors" perrors "github.com/pkg/errors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
...@@ -34,6 +33,7 @@ import ( ...@@ -34,6 +33,7 @@ import (
"github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/constant"
"github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol"
"github.com/apache/dubbo-go/protocol/dubbo/hessian2" "github.com/apache/dubbo-go/protocol/dubbo/hessian2"
"github.com/apache/dubbo-go/protocol/invocation"
) )
type TestService struct { type TestService struct {
......
...@@ -26,6 +26,7 @@ import ( ...@@ -26,6 +26,7 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"sync"
) )
import ( import (
...@@ -77,10 +78,12 @@ func (t RoleType) Role() string { ...@@ -77,10 +78,12 @@ func (t RoleType) Role() string {
} }
type baseUrl struct { type baseUrl struct {
Protocol string Protocol string
Location string // ip+port Location string // ip+port
Ip string Ip string
Port string Port string
//url.Values is not safe map, add to avoid concurrent map read and map write error
paramsLock sync.RWMutex
params url.Values params url.Values
PrimitiveURL string PrimitiveURL string
} }
...@@ -393,16 +396,16 @@ func (c URL) Service() string { ...@@ -393,16 +396,16 @@ func (c URL) Service() string {
} }
// AddParam will add the key-value pair // AddParam will add the key-value pair
// Not thread-safe
// think twice before using it.
func (c *URL) AddParam(key string, value string) { func (c *URL) AddParam(key string, value string) {
c.paramsLock.Lock()
defer c.paramsLock.Unlock()
c.params.Add(key, value) c.params.Add(key, value)
} }
// AddParamAvoidNil will add key-value pair // AddParamAvoidNil will add key-value pair
// Not thread-safe
// think twice before using it.
func (c *URL) AddParamAvoidNil(key string, value string) { func (c *URL) AddParamAvoidNil(key string, value string) {
c.paramsLock.Lock()
defer c.paramsLock.Unlock()
if c.params == nil { if c.params == nil {
c.params = url.Values{} c.params = url.Values{}
} }
...@@ -411,16 +414,17 @@ func (c *URL) AddParamAvoidNil(key string, value string) { ...@@ -411,16 +414,17 @@ func (c *URL) AddParamAvoidNil(key string, value string) {
} }
// SetParam will put the key-value pair into url // SetParam will put the key-value pair into url
// it's not thread safe.
// think twice before you want to use this method
// usually it should only be invoked when you want to initialized an url // usually it should only be invoked when you want to initialized an url
func (c *URL) SetParam(key string, value string) { func (c *URL) SetParam(key string, value string) {
c.paramsLock.Lock()
defer c.paramsLock.Unlock()
c.params.Set(key, value) c.params.Set(key, value)
} }
// RangeParams will iterate the params // RangeParams will iterate the params
// it's not thread-safe
func (c *URL) RangeParams(f func(key, value string) bool) { func (c *URL) RangeParams(f func(key, value string) bool) {
c.paramsLock.RLock()
defer c.paramsLock.RUnlock()
for k, v := range c.params { for k, v := range c.params {
if !f(k, v[0]) { if !f(k, v[0]) {
break break
...@@ -430,6 +434,8 @@ func (c *URL) RangeParams(f func(key, value string) bool) { ...@@ -430,6 +434,8 @@ func (c *URL) RangeParams(f func(key, value string) bool) {
// GetParam gets value by key // GetParam gets value by key
func (c URL) GetParam(s string, d string) string { func (c URL) GetParam(s string, d string) string {
c.paramsLock.RLock()
defer c.paramsLock.RUnlock()
r := c.params.Get(s) r := c.params.Get(s)
if len(r) == 0 { if len(r) == 0 {
r = d r = d
......
...@@ -28,7 +28,6 @@ import ( ...@@ -28,7 +28,6 @@ import (
) )
import ( import (
gxnet "github.com/dubbogo/gost/net"
perrors "github.com/pkg/errors" perrors "github.com/pkg/errors"
) )
...@@ -250,7 +249,7 @@ func createInstance(url common.URL) (registry.ServiceInstance, error) { ...@@ -250,7 +249,7 @@ func createInstance(url common.URL) (registry.ServiceInstance, error) {
host := url.Ip host := url.Ip
if len(host) == 0 { if len(host) == 0 {
host, err = gxnet.GetLocalIP() host = common.GetLocalIp()
if err != nil { if err != nil {
return nil, perrors.WithMessage(err, "could not get the local Ip") return nil, perrors.WithMessage(err, "could not get the local Ip")
} }
......
...@@ -18,11 +18,11 @@ ...@@ -18,11 +18,11 @@
package config package config
import ( import (
"github.com/apache/dubbo-go/common"
"testing" "testing"
) )
import ( import (
gxnet "github.com/dubbogo/gost/net"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/atomic" "go.uber.org/atomic"
) )
...@@ -184,7 +184,7 @@ func TestExport(t *testing.T) { ...@@ -184,7 +184,7 @@ func TestExport(t *testing.T) {
func TestGetRandomPort(t *testing.T) { func TestGetRandomPort(t *testing.T) {
protocolConfigs := make([]*ProtocolConfig, 0, 3) protocolConfigs := make([]*ProtocolConfig, 0, 3)
ip, err := gxnet.GetLocalIP() ip := common.GetLocalIp()
protocolConfigs = append(protocolConfigs, &ProtocolConfig{ protocolConfigs = append(protocolConfigs, &ProtocolConfig{
Ip: ip, Ip: ip,
}) })
...@@ -194,7 +194,7 @@ func TestGetRandomPort(t *testing.T) { ...@@ -194,7 +194,7 @@ func TestGetRandomPort(t *testing.T) {
protocolConfigs = append(protocolConfigs, &ProtocolConfig{ protocolConfigs = append(protocolConfigs, &ProtocolConfig{
Ip: ip, Ip: ip,
}) })
assert.NoError(t, err) //assert.NoError(t, err)
ports := getRandomPort(protocolConfigs) ports := getRandomPort(protocolConfigs)
assert.Equal(t, ports.Len(), len(protocolConfigs)) assert.Equal(t, ports.Len(), len(protocolConfigs))
......
...@@ -23,7 +23,6 @@ import ( ...@@ -23,7 +23,6 @@ import (
import ( import (
gxset "github.com/dubbogo/gost/container/set" gxset "github.com/dubbogo/gost/container/set"
gxnet "github.com/dubbogo/gost/net"
) )
import ( import (
...@@ -61,7 +60,7 @@ func (c *overrideConfigurator) Configure(url *common.URL) { ...@@ -61,7 +60,7 @@ func (c *overrideConfigurator) Configure(url *common.URL) {
currentSide := url.GetParam(constant.SIDE_KEY, "") currentSide := url.GetParam(constant.SIDE_KEY, "")
configuratorSide := c.configuratorUrl.GetParam(constant.SIDE_KEY, "") configuratorSide := c.configuratorUrl.GetParam(constant.SIDE_KEY, "")
if currentSide == configuratorSide && common.DubboRole[common.CONSUMER] == currentSide && c.configuratorUrl.Port == "0" { if currentSide == configuratorSide && common.DubboRole[common.CONSUMER] == currentSide && c.configuratorUrl.Port == "0" {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
c.configureIfMatch(localIP, url) c.configureIfMatch(localIP, url)
} else if currentSide == configuratorSide && common.DubboRole[common.PROVIDER] == currentSide && c.configuratorUrl.Port == url.Port { } else if currentSide == configuratorSide && common.DubboRole[common.PROVIDER] == currentSide && c.configuratorUrl.Port == url.Port {
c.configureIfMatch(url.Ip, url) c.configureIfMatch(url.Ip, url)
...@@ -127,7 +126,7 @@ func (c *overrideConfigurator) configureDeprecated(url *common.URL) { ...@@ -127,7 +126,7 @@ func (c *overrideConfigurator) configureDeprecated(url *common.URL) {
// 1.If it is a consumer ip address, the intention is to control a specific consumer instance, it must takes effect at the consumer side, any provider received this override url should ignore; // 1.If it is a consumer ip address, the intention is to control a specific consumer instance, it must takes effect at the consumer side, any provider received this override url should ignore;
// 2.If the ip is 0.0.0.0, this override url can be used on consumer, and also can be used on provider // 2.If the ip is 0.0.0.0, this override url can be used on consumer, and also can be used on provider
if url.GetParam(constant.SIDE_KEY, "") == common.DubboRole[common.CONSUMER] { if url.GetParam(constant.SIDE_KEY, "") == common.DubboRole[common.CONSUMER] {
localIP, _ := gxnet.GetLocalIP() localIP := common.GetLocalIp()
c.configureIfMatch(localIP, url) c.configureIfMatch(localIP, url)
} else { } else {
c.configureIfMatch(constant.ANYHOST_VALUE, url) c.configureIfMatch(constant.ANYHOST_VALUE, url)
......
# [dubbo-go 的开发、设计与功能介绍](https://www.infoq.cn/article/7JIDIi7pfwDDk47EpaXZ)
## dubbo-go 的前世今生
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-a.png)
dubbo-go 是目前 Dubbo 多语言生态最火热的项目。dubbo-go 最早的版本应该要追溯到 2016 年,由社区于雨同学编写 dubbo-go 的初版。当时很多东西没有现成的轮子,如 Go 语言没有像 netty 一样的基于事件的网络处理引擎、 hessian2 协议没有 Go 语言版本实现,加上当时 Dubbo 也没有开始重新维护。所以从协议库到网络引擎,再到上层 dubbo-go ,其实都是从零开始写的。
在 2018 年,携程开始做 Go 语言的一些中间件以搭建内部的 Go 语言生态,需要有一个 Go 的服务框架可以与携程的现有 dubbo soa 生态互通。所以由我负责重构了 dubbo-go 并开源出这个版本。当时调研了很多开源的 Go 语言服务框架,当时能够支持 hessian2 协议的并跟 Dubbo 可以打通的仅找到了当时于雨写的 dubbo-go 早期版本。由于携程对社区版本的 Dubbo 做了挺多的扩展,源于对扩展性的需求我们 Go 语言版本需要一个更易于扩展的版本,加上当时这个版本本身的功能也比较简单,所以我们找到了作者合作重构了一个更好的版本。经过了大半年时间,在上图第三阶段 19 年 6 月的时候,基本上已经把 dubbo-go 重构了一遍,总体的思路是参考的 Dubbo 整体的代码架构,用 Go 语言完全重写了一个完整的具备服务端跟消费端的 Golang rpc/ 微服务框架。
后来我们将重构后的版本 dubbo-go 1.0 贡献给 Apache 基金会,到现在已经过去了两个多月的时间,近期社区发布了 1.1 版本。目前为止,已经有包括携程在内的公司已经在生产环境开始了试用和推广。
## Start dubbo-go
现在的 dubbo-go 已经能够跟 Java 版本做比较好的融合互通,同时 dubbo-go 自身也是一个完成的 Go 语言 rpc/ 微服务框架,它也可以脱离 java dubbo 来独立使用。
这边简单介绍一下用法,写一个 hello world 的例子。
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-b.png)
上图是一个简单的 java service ,注册为一个 Dubbo 服务,是一个简单的获取用户信息的例子。
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-c.png)
上图是 dubbo-go 的客户端,来订阅和调用这个 Java 的 Dubbo 服务。Go 语言客户端需要显式调用 SetConsumerService 来注册需要订阅的服务,然后通过调用 dubbo-go-hessian2 库的 registerPOJO 方法来注册 user 对象,做 Java 和 Go 语言之间的自定义 pojo 类型转换。具体的服务调用方法就是声明一个的 GetUser 闭包,便可直接调用。
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-d.png)
上图,同样的可以基于 dubbo-go 发布一个 GetUser 的服务端,使用方式类似,发布完后可以被 dubbo java 的客户端调用。
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-e.png)
如上图所示,现在已经做到了这样一个程度,同样一份 dubbo-go 客户端代码,可以去调用 dubbo-go 的服务端,也可以去调用 Dubbo Java 的服务端;同样一份 dubbo-go 的服务端代码,可以被 dubbo-go 客户端和 Java 客户端调用,所以基本上使用 Dubbo 作为 PPC 框架的 Go 语言应用跟 Java 应用是没有什么阻碍的,是完全的跨语言 RPC 调用。更重要的是 dubbo-go 继承了 Dubbo 的许多优点,如易于扩展、服务治理功能强大,大家在用 Go 语言开发应用的过程中,如果也遇到类似需要与 Dubbo Java 打通的需求,或者需要找一个服务治理功能完备的 Go 微服务框架,可以看下我们 dubbo-go 项目。
## dubbo-go 的组成项目
下面介绍一下 dubbo-go 的组成项目,为了方便可以被其他项目直接复用, dubbo-go 拆分成了多个项目,并全部以 Apache 协议开源。
**apache/dubbo-go**
dubbo-go 主项目, Dubbo 服务端、客户端完整 Go 语言实现。
**apache/dubbo-go-hession2**
目前应用最广泛,与 Java 版本兼容程度最高的 hessian2 协议 Go 语言实现,已经被多个 GolangRPC & Service Mesh 项目使用。
**dubbo-go/getty**
dubbo-go 异步网络 I/O 库,将网络处理层解耦。
**dubbo-go/gost**
基本类库,定义了 timeWheel、hashSet、taskPool 等。
**dubbo-go/dubbo-go-benchmark**
用于对 dubbo-go 进行简单的压力测试,性能测试。
**apache/dubbo-go-hessian2**
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-f.png)
先简单介绍一下 dubbo-go-hessian2 项目。该项目就是 hessian2 协议的 Go 语言实现,最基本的可以将 Java 的基本数据类型和复杂数据类型(如一些包装类和 list 接口实现类)与 golang 这边对应。
详情可以参考: [https://github.com/hessian-group/hessian-type-mapping](https://github.com/hessian-group/hessian-type-mapping)
另外 Dubbo Java 服务端可以不捕获异常,将异常类通过 hession2 协议序列化通过网络传输给消费端,消费端进行反序列化对该异常对象并进行捕获。我们经过一段时间的整理,目前已经支持在 Go 消费端定义对应 Java 的超过 40 种 exception 类,来实现对 Java 异常的捕获,即使用 dubbo-go 也可以做到直接捕获 Java 服务端抛出的异常。
另外对于 Java 端 BigDecimal 高精度计算类的支持。涉及到一些金融相关的计算会有类似的需求,所以也对这个类进行了支持。
其他的,还有映射 java 端的方法别名,主要的原因是 Go 这边语言的规约,需要被序列化的方法名必须是首字母大写。而 Java 这边没有这种规范,所以我们加了一个 hessian 标签的支持,可以允许用户手动映射 Java 端的方法名称。
基本上现在的 dubbo-go 已经满足绝大多数与 Java 的类型互通需求,我们近期也在实现对 Java 泛型的支持。
**dubbo-go/getty**
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-g.png)
Go 语言天生就是一个异步网络 I/O 模型,在 linux 上 Go 语言写的网络服务器也是采用的 epoll 作为最底层的数据收发驱动, 这跟 java 在 linux 的 nio 实现是一样的。所以 Go 语言的网络处理天生就是异步的。我们需要封装的其实是基于 Go 的异步网络读写以及之后的处理中间层。getty 将网络数据处理分为三层,入向方向分别经过对网络 i/o 封装的 streaming 层、根据不同协议对数据进行序列化反序列化的 codec 层,以及最后数据上升到需要上层消费的 handler 层。出向方向基本与入向经过的相反。每个链接的 IO 协程是成对出现的,比如读协程负责读取、 codec 逻辑然后数据到 listener 层,然后最后的事件由业务协程池来处理。
该项目目前是与 dubbo-go 解耦出来的,所以大家如果有类似需求可以直接拿来用,目前已经有对于 tcp/udp/websocket 的支持。
**Apache / dubbo-go**
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-h.png)
dubbo-go 主项目,我们重构的这一版主要是基于 Dubbo 的分层代码设计,上图是 dubbo-go 的代码分层。基本上与 Java 版本 Dubbo 现有的分层一致,所以 dubbo-go 也继承了 Dubbo 的一些优良特性,比如整洁的代码架构、易于扩展、完善的服务治理功能。
我们携程这边,使用的是自己的注册中心,可以在 dubbo-go 扩展机制的基础上灵活扩展而无需去改动 dubbo-go 的源代码。
## dubbo-go 的功能介绍
**dubbo-go 已实现功能**
目前 dubbo-go 已经实现了 Dubbo 的常用功能(如负责均衡、集群策略、服务多版本多实现、服务多注册中心多协议发布、泛化调用、服务降级熔断等),其中服务注册发现已经支持 zookeeper/etcd/consul/nacos 主流注册中心。这里不展开详细介绍,目前 dubbo-go 支持的功能可以查看项目 readme 中的 feature list ,详情参考: [https://github.com/apache/dubbo-go#feature-list](https://github.com/apache/dubbo-go#feature-list)
目前社区正在开发中的功能,主要是早期用户使用过程中提出的一些需求,也是生产落地一些必需的需求,如监控、调用链跟踪以及服务路由、动态配置中心等更高级的服务治理需求。
**dubbo-go 功能介绍之泛化调用**
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-i.png)
这里详细做几个重点功能的介绍。首先是泛化调用,如上图,这个也是社区同学提的需求。该同学公司内部有很多 Dubbo 服务,他们用 Go 做了一个 api gateway 网关,想要把 Dubbo 服务暴露成外网 http 接口。因为内部的 Dubbo 服务比较多,不可能每一个 Dubbo 服务都去做一个消费端接口去做适配,这样的话一旦服务端改动,客户端也要改。所以他这边的思路是做基于 dubbo-go 做泛化调用, api-gateway 解析出外网请求的地址,解析出想要调用的 Dubbo 服务的目标。基于 dubbo-go consumer 泛化调用指定 service、method ,以及调用参数。
具体的原理是, dubbo-go 这边作为消费端,实际会通过本地 genericService.invoke 方法做代理,参数里面包含了 service name,method name ,还包含被调用目标 service 需要的参数类型、值等数据,这些数据后面会通过 dubbo-go-hession2 做转换,会将内容转化成 map 类型,经过网络发送到对应的 Java 服务端,然后 Java 那边是接收的 map 类型的参数,会自动反序列化成自己的 pojo 类型。这样就实现了 dubbo-go 作为客户端,泛化调用 Dubbo 服务端的目的。
**dubbo-go 功能介绍之降级熔断**
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-j.png)
降级熔断这边是基于的是大家比较熟悉的 hystrix 的 Go 语言版本,基于 hystrix ,用户可以定义熔断规则和降级触发的代码段。降级熔断支持是作为一个独立的 dubbo-go filter ,可以灵活选择是否启用,如果不启用就可以在打包的时候不将依赖引入。Filter 层是 dubbo-go 中对于请求链路的一个责任链模式抽象,目前有许多功能都是基于动态扩展 filter 链来实现的,包括 trace、leastactive load balacne、log 等。降级熔断设计成一个服务调用端独立的 filter 可以灵活满足调用端视角对于微服务架构中“防雪崩“的服务治理需求。
**dubbo-go 功能介绍之动态配置**
关于动态配置中心, Dubbo 的 2.6 到 2.7 版本做了一个比较大的变化,从之前的 url 配置形式过渡到了支持配置中心 yaml 格式配置的形式,治理粒度也从单服务级别的配置支持到了应用级别的配置,不过在 2.7 版本中还是兼容 2.6 版本 url 形式进行服务配置的。dubbo-go 这边考虑到跟 Dubbo2.6 和 2.7 的互通性,同样支持 url 和配置文件方式的服务配置,同时兼容应用级别和服务级别的配置,跟 dubbo 保持一致,目前已经实现了 zookeeper 和 apollo 作为配置中心的支持。
## dubbo-go roadmap 2019-2020
![dubbo-go 的开发、设计与功能介绍](../../pic/arch/dubbo-go-design-implement-and-featrues-k.png)
最后是大家比较关注的,社区关于 dubbo-go 2019 年下半年的计划,目前来看主要还是现有功能的补齐和一些问题的修复,我们的目标就是首先做到 Java 和 Go 在运行时的兼容互通和功能的一致,其次是查漏补缺 dubbo-go 作为一个完整 Go 语言微服务框架在功能上的可以改进之处。
另外值得关注的一点是,预计今年年底, dubbo-go 会发布一个支持 kubernetes 作为注册中心的扩展,积极拥抱云原生生态。关于云原生的支持,社区前期做了积极的工作,包括讨论关于 dubbo-go 与 Service Mesh 的关系以及在其中的定位,可以肯定的是, dubbo-go 将会配合 Dubbo 社区在 Service Mesh 方向的规划并扮演重要角色,我们初步预计会在明年给出与 Service Mesh 开源社区项目集成的方案,请大家期待。
dubbo-go 社区目前属于快速健康成长状态,从捐赠给 Apache 后的不到 3 个月的时间里,吸引了大批量的活跃开发者和感兴趣的用户,欢迎各位同道在使用或者学习中遇到问题能够来社区讨论或者给予指正,也欢迎对 dubbo-go 有潜在需求或者对 dubbo-go 感兴趣的同道能加入到社区中。
**作者介绍**
何鑫铭,目前就职于携程,基础中台研发部技术专家,dubbo-go 共同发起人、主要作者,Apache Dubbo committer,关注互联网中台以及中间件领域。
\ No newline at end of file
This diff is collapsed.
# [dubbo-go 中如何实现远程配置管理?](https://blog.csdn.net/weixin_39860915/article/details/104548947)
2020-02-27 20:00:00
之前在 Apache/dubbo-go(以下简称 dubbo-go )社区中,有同学希望配置文件不仅可以放于本地,还可以放于配置管理中心里。那么,放在本地和配置管理中心究竟有哪些不一样呢?
放在本地,每次更新需要重启,配置文件管理困难,无法做到实时更新即刻生效。此外,本地文件还依赖人工版本控制,在微服务的场景下,大大的增加了运维的成本与难度。
而配置管理中心提供了统一的配置文件管理,支持文件更新、实时同步、统一版本控制、权限管理等功能。
# **目标**
* * *
基于以上几个背景,可以总结出以下**目标**
* 与 Dubbo 现有的配置中心内的配置文件兼容,降低新增语言栈的学习成本;
* 支持多种配置文件格式;
* 支持主流配置中心,适应不一样的使用场景,实现高扩展的配置下发;
# **配置中心**
* * *
配置中心在 dubbo-go 中主要承担以下场景的职责:
1、作为外部化配置中心,即存储 dubbo.properties 配置文件,此时,key 值通常为文件名如 dubbo.properties , value 则为配置文件内容。
2、存储单个配置项,如各种开关项、常量值等。
3、存储服务治理规则,此时 key 通常按照 “服务名 + 规则类型” 的格式来组织,而 value 则为具体的治理规则。
就目前而言,dubbo-go 首要支持的是 Dubbo 中支持的开源配置中心,包括:
1、Apollo :携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
2、ZooKeeper :一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
3、Nacos : Alibaba 开源的配置管理组件,提供了一组简单易用的特性集,帮助您实现动态服务发现、服务配置管理、服务及流量管理。
而考虑到某些公司内部有自身的研发的配置中心,又或者当前流行而 Dubbo 尚未支持的配置中心,如 etcd,我们的核心在于设计一套机制,允许我们,也包括用户,可以通过扩展接口新的实现,来快速接入不同的配置中心。
那在 dubbo-go 中究竟怎么实现呢?我们的答案是:**基于动态的插件机制在启动时按需加载配置中心的不同实现。**
实现该部分功能放置于一个独立的子项目中,见:
_https://github.com/apache/dubbo-go/tree/master/config\_center_
## **dubbo-go 设计**
原逻辑为:启动时读取本地配置文件,将其加载进内存,通过配置文件中的配置读取注册中心的信息获取服务提供者,注册服务消费者。
有些读者会有点困惑,不是说好了使用配置中心的,为什么现在又要读取本地配置呢?答案就是,读取的这部分信息分成两部分:
* 使用什么作为配置中心;
* 该配置中心的元数据,比如说使用 zookeeper 作为配置中心,那么 zookeeper 的链接信息就是元数据,毕竟我们只有在知道了链接信息之后才能连上 zookeeper;
在改造的时候,需要考虑以下的问题:
**1、如何实现支持多个配置中心?如何实现按需加载?**
通过抽象 DynamicConfiguration 让开发者可以快速支持多个配置中心。使用者导入指定的组件包后,在启动阶段将需要的组件加载进内存中,以便给程序按需调用,如下图绿色部分。
**2、配置中心的配置加载阶段在什么时候?**
应在读取配置文件阶段后,读取并解析本地配置文件中配置中心信息。初始化配置中心链接,读取 /dubbo/config/dubbo/dubbo.properties 与 /dubbo/config/dubbo/应用名/dubbo.properties ,并将其加载到内存之中覆盖原有配置,监听其变更,实时更新至内存,如下图蓝色部分:
![](../../pic/config-center/how-to-implement-remote-configuration-management-in-dubbo-go-a.jpg "configcenter-class.jpg")
### **ConfigCenterFactory**
使用者加载对应配置中心模块后,在初始化阶段加入各配置中心模块往其中注册其初始化类。
![](../../pic/config-center/how-to-implement-remote-configuration-management-in-dubbo-go-b.png "configCenterFactory.png")
### **DynamicConfigurationFactory**
整个动态配置中心的关键点就在 DynamicConfigurationFactory 上,其中通过解析内部自定义的 URL ,获取其协议类型,反射其参数,用于创建配置中心的链接。
![](../../pic/config-center/how-to-implement-remote-configuration-management-in-dubbo-go-c.png "configurationFactory.png")
如:
配置文件中配置:
```css
config_center:
protocol: zookeeper
address: 127.0.0.1:2181
namespace: test
```
dubbo-go 内部会解析为:
```javascript
zookeeper://127.0.0.1:2181?namespace=test
```
在内部传递,用于初始化配置中心链接。
**PS:**在 dubbo-go 中到处可见这种内部协议,透彻理解这个内部协议对阅读 dubbo-go 代码很有帮助。
### **DynamicConfiguration**
该接口规定了各个配置中心需要实现的功能:
* 配置数据反序列化方式:目前只有 Properties 转换器,参见:DefaultConfigurationParser 。
* 增加监听器:用于增加监听数据变化后增加特定逻辑(受限于配置中心 client 端实现)。
* 删除监听器:删除已有监听器(受限于配置中心 client 端实现,目前所知 nacos client 没有提供该方法)。
* 获取路由配置:获取路由表配置。
* 获取应用级配置:获取应用层级配置,如:协议类型配置等。
![](../../pic/config-center/how-to-implement-remote-configuration-management-in-dubbo-go-d.png "dynamicConfiguration.png")
## **实现**
* * *
![](../../pic/config-center/how-to-implement-remote-configuration-management-in-dubbo-go-e.png "design.jpg")
优先考虑与现有 Dubbo 设计兼容,从而降低使用者的学习成本,dubbo-admin 作为服务提供者实现应用级配置管理, dubbo-go 作为消费端实现配置下发管理功能。下面以 ZooKeeper 为例,对服务提供者与服务消费者进行整体流程分析。
### **如何存储配置管理**
dubbo-admin 配置管理中增加 global 配置,ZooKeeper 中会自动生成其对应配置节点,内容均为 dubbo-admin 中设置的配置。
1、/dubbo/config/dubbo/dubbo.properties 对应全局配置文件。
2、/dubbo/config/dubbo/ 应用名 /dubbo.properties 对应指定应用配置文件。
#### **节点路径**
#### ![](../../pic/config-center/how-to-implement-remote-configuration-management-in-dubbo-go-f.png "key-struct.jpg")
上图展示了 dubbo.properties 文件在 ZooKeeper 和 Apollo 中的存储结构:
**ZooKeeper**
* 命名空间 namespace 都为:Dubbo
* 分组 group :全局级别为 dubbo , 所有应用共享;应用级别为应用名 demo-provider ,只对该应用生效
* key : dubbo.properties
**Apollo**
* app\_id : 自由指定,默认:dubbo ,最好与 zookeeper namespace 一致
* cluster : 自由指定,最好与 zookeeper group 一致
* 命名空间 namespace : dubbo.properties
ZooKeeper 与 Apollo 最大的不一样就在于 dubbo.properties 所在的节点。
### **实现配置管理中心支持**
以 Apollo 为例,简单的介绍,如何实现支持一个新的配置管理中心。
#### **选择配置管理中心 Client / SDK**
本例中使用的 Apollo Go Client 为:https://github.com/zouyx/agollo 。
**PS:** 如没找到,自己实现也是可以的哦。
#### **节点路径**
因为每个配置管理中心的存储结构各有特点,导致 Dubbo 在使用外部配置管理中心时,存储配置节点的结构不一样。在 dubbo-configcenter 找到希望支持的配置管理中心,而本例中 Apollo 则在 ApolloDynamicConfiguration.java 。
注释中表明,Apollo namespace = governance (governance .properties) 用于治理规则,namespace = dubbo (dubbo.properties) 用于配置文件。
#### **实现 DynamicConfiguration**
新建创建客户端方法,最好客户端保持为单例。
![](../../pic/config-center/how-to-implement-remote-configuration-management-in-dubbo-go-g.png "image.png")
以下为必须实现的方法,以下方法用于获取配置中心配置。
* GetInternalProperty:在配置文件(Apollo 为 namespace)中,根据 key 获取对应 value;
* GetRule:获取治理配置文件(Apollo 为 namespace);
* GetProperties:获取整个配置文件(Apollo 为 namespace);
可选择实现的方法,如不实现,则不能动态更新 dubbo-go 中配置信息。
* RemoveListener
* AddListener
而 Parser & SetParser 使用默认实现即可,默认为 Properties 转换器。
更多信息,参考:dubbo-go-apollo ,详情参考:
_https://github.com/apache/dubbo-go/tree/master/config\_center/apollo_
## **使用方法**
从上面的设计里面,也能大概猜到怎么使用了:
![](../../pic/config-center/how-to-implement-remote-configuration-management-in-dubbo-go-h.png "zookeeper-usercase.png")
很显然,使用配置中心并不复杂,只需要把对应的依赖引入进来。在包初始化的时候,会创建出来对应的配置中心的实现。比如说加载 ZooKeeper 或者 Apollo 作为配置中心:
**ZooKeeper**
```nginx
_ "github.com/apache/dubbo-go/config_center/zookeeper"
```
**Apollo**
```nginx
_ "github.com/apache/dubbo-go/config_center/apollo"
```
当然仅仅加载还不够,比如说虽然我加载了 zookeeper,但是我还需要知道怎么连上这个配置中心,即前面提到的配置中心的元数据,这部分信息是需要在本地配置出来的。比如说:
**ZooKeeper**
```css
config_center:
protocol: "zookeeper"
address: "127.0.0.1:2181"
```
**Apollo**
如果需要使用 Apollo 作为配置中心,请提前创建 namespace: dubbo.properties,用于配置管理。
```properties
config_center:
protocol: "apollo"
address: "127.0.0.1:8070"
app_id: test_app
cluster: dev
```
# **总结**
* * *
更加具体的实现,我就不详细论述,大家可以去看源码,欢迎大家持续关注,或者贡献代码。
整个配置中心的功能,麻雀虽小,但五脏俱全。目前并不算是十分完善,但是整个框架层面上来说,是走在了正确的路上。从扩展性来说,是比较便利。目前支持的配置中心还不够丰富,只有 ZooKeeper 与 Apollo ,支持的配置文件格式也只有 properties ,虽然能满足基本使用场景,距离完善还有还长远的路。
**未来计划:**
* Nacos(等待发布 )
* etcd(正在开发)
* consul(未支持)
* 丰富的文件配置格式,如:yml , xml 等
**本文作者:**邹毅贤,Github ID @zouyx,开源爱好者,就职于 SheIn 供应链部门,负责供应链开放平台。
本文缩略图:icon by 用户7388492991
\ No newline at end of file
# [写在 Dubbo go 的第五年](https://my.oschina.net/u/3874284/blog/4577120)
09/16 14:33
![头图.png](../../pic/course/the-5th-years-of-dubbo-go-a.png)
作者 | 于雨
**阿里巴巴云原生公众号后台回复“915”即可查看 dubbogo v1.5.1 项目管理图清晰大图!**
# 引语
dubbogo 项目已进入第五个年头。
项目发展的前两年,我们把 hessian2 协议库、网络库和整体基础框架搭建一番。从 2018 年项目被 Dubbo 官方接纳开始,依托阿里平台,社区开始形成并快速发展。与社区同学们齐心合力之下,如今全面兼容 Dubbo v2.7.x 的 Dubbo-go v1.5.1 已经发布。
# 立项
一个项目整体必须提炼出核心目标,指明其存在的意义和价值。有了初心,项目发展过程中产生困惑时,才能明确答复 “我是谁?从哪里来?到哪里去”。
## 1\. dubbogo
dubbogo 项目有其自身的 milestone 要求,大致规划了每个阶段的关键里程碑,在项目发展初期仅仅是实现 Dubbo 的某个功能,但在发展过程中会不断结合当下的技术发展潮流,不断修正其未来发展方向。
其发版计划是通过“开发当前版本、规划新版本、根据反馈修正新版本”的模式定义当前版本的开发内容和下一个版本的发展方向。每次发版后会根据社区使用反馈对下一代的发展目标进行修正。
站在吃瓜人的角度,或许可以说出 “dubbogo 不就是 dubbo 的 Go 语言版本嘛,照着抄就是了” 之类的论调。而参与过 dubbogo 项目跟着社区一路走来的人,就知道 dubbogo 并不简单定位于 Dubbo 项目的 Go 语言版本。
dubbogo 初心不变,不同时间对自身定位均有升级。我认为当前 dubbogo 的定位是:
* 全面兼容 Dubbo;
* 一个 Go 语言应用通信框架,充分利用作为云原生时代第一语言---Go 语言的优势,扩展 dubbo 的能力。
## 2\. dubbo-go-proxy
dubbogo 项目初期目的是依靠 Dubbo 实现 "bridge the gap between Java and Go" ,目前 dubbogo 正与 Dubbo 齐头并进,已经达到项目立项的目标。有长期生命的通信框架,大概有 5 年的成长期和 5 年的稳定成熟期。目前的 dubbogo 处在成长期和稳定成熟期的过渡期,这意味着社区如果想保持发展态势,就必须开始走多元化道路,发展自己的生态了。
眼下 dubbogo 社区正在集中精力孵化一个新的项目---实现一个基于 dubbogo 的 [HTTP 网关](https://github.com/dubbogo/dubbo-go-proxy),项目的意义是:dubbogo 自身是一个流量控制中间件,在其上扩展项目,其方向很自然就是做一个 proxy/sidecar or gateway,且社区一直有网关这方面的需求。
项目目的如下:
* 做一个具有生产使用意义的网关;
* dubbo-go-proxy 验证 dubbogo 的能力,对 dubbogo 未来的进化指出新方向,共同进化;
* 优化 dubbogo 的稳定性和性能。
# 团队
项目立项完毕后,就进入招兵买马阶段了。
## 1\. 来源
dubbogo 社区发展初期,其关键成员都是通过提交 issue 或者 pr 的同学撩来的。通过这种方式撩来的同学因为志同道合,有极高的概率同社区一起走下来。dubbogo 社区的 core member 就是这样来的。
其次是与其他公司的合作。dubbogo 本身是一个有着极高生产环境需求的项目,在发展过程中依次与携程、涂鸦、斗鱼、虎牙、蚂蚁金服和阿里集团有过极深的合作,其间与携程的合作对 dubbogo 成型而言极为关键。
另一个途径是与其他社区合作。dubbogo 项目发展过程中,与以下社区合作过:
* 与 MOSN 社区合作实现 Dubbo Mesh;
* 与 sentinel 社区合作,在 Dubbo/Dubbo-go 完整接入 sentinel 的降级和限流方案;
* 与 Apollo 社区合作,在 Dubbo-go 中实现远程配置下发;
* 与 Nacos 社区合作,实现基于 Nacos 的服务发现。
与其他社区合作的好处是使得双方的项目都受益:扩展双方的能力和使用场景,其次是社区间人员的流动。在合作过程中,dubbogo 自身受益极大,目前有 4 个社区 committer 来自于其它社区。合作完成后并不意味着结束,而是一个新的双赢的开始:社区项目也是发展的,当一个项目有新特性时可以同时快速被另一个项目采用验证,对扩展开发者们的技术能力和人脉也是极为有利的,dubbogo 社区目前的好几个同学同时活跃在多个社区。
dubbogo 项目已经过了草莽阶段,形成了一个的 800 多人的社区群,所以 dubbogo-proxy 项目立项后,很快就在社区群内找到很多项目爱好者。
## 2\. 成员的 qualification
项目发展初期有很多同学会 Java 不懂 Dubbo 不会 Go,最后都通过参与项目提升了自我的能力。当然有些人会担心项目代码的质量,但只要秉持 "Community Over Code" 这个 "Apache Way",在发展过程中这些问题都不大。
2019 年时,参与 dubbogo 项目的成员中一部分同学平时的工作是进行业务开发,秉承着对中间件通信技术 “我是谁?我从哪里来?要到那里去” 的初心参与 dubbogo 的开发,无论是对 dubbogo 抑或是对其自身技术水平提升都产生了积极的影响。
dubbogo 社区对 dubbogo 发版时间有一定期限要求,所以对参与人员的时间投入也有一定的要求。每个版本的核心功能的 owner,需要保证在 deadline 期限内完成开发任务。
dubbogo 每个版本都有一个发版人,负责相应版本的任务拆分、发展跟踪、代码 Review 和最后的测试验收,这就要求发版人自身的技术水平和时间投入极高。目前 dubbogo 每个大版本的发版人都不是同一个人,每次 dubbogo 发版,都意味着每个发版人的体力和精力的极大付出。于某在此致敬历届发版人!
# 管理
项目立项后,就需要明确发展大方向、发展 milestone、版本规划、以及一段时间内的具体的开发规划。项目发展初期,Roadmap 可以不清晰,先摸着石头过河,在发展过程中逐步明确其内容。
## 1\. 需求收集
dubbogo 项目发展初期,其目标仅仅是实现 dubbo 某个版本的功能, 所以其需求收集并不用花费很久时间。随着 2019 年 8 月份发布 v1.0 后,dubbogo 越来越多地被多家生产厂商投入生产使用环境中,目前其需求方来源如下:
* 实现 dubbo 某个版本的功能;
* 实际使用方的生产需求;
* 为紧跟当下最近技术发展方向而进行的技术预演。
dubbogo 当前的 K8s 注册中心技术方案就是紧跟最新技术发展方向而进行预演的极好例证,其发展时间线如下:
* 2019 年 7 月,随着阿里集团和蚂蚁金服在云原生方向的推波助澜,阿里 dubbo 开发团队并未给出可靠的技术方向,dubbogo 社区 core members 也都没有云原生方向的技术积累和相应人才,决定先开发一个基于 kube-apiserver 的注册中心,以进行技术储备;
* 2019 年 8 月, 调研 dubbo 的 K8s 注册中心方案后,dubbogo 社区决定抛开它独立实现一个,争取以最低代价把 dubbo 应用迁移到 K8s 环境中运行起来,同时决定未来的发展方向是 dubbogo operator;
* 2019 年 11 月,社区的王翔同学给出了初步实现,在 2020 年 1 月随 dubbogo v1.2 版本发布;
* 2020 年 4 月,有用户要求在 K8s 环境中 consumer 可跨 namespace 访问 provider,相应实现在 2020 年 7 月随着 dubbogo v1.5 版本发布;
* 2020 年 5 月,dubbogo 社区和 mosn 社区合作实现了 dubbo mesh;
* 2020 年 6 月,社区意识到 kube-apiserver 是系统的运维态 IaaS 层的核心组件,不应该跨过 PaaS 层直接暴露给应用层,否则应用层使用不当或者框架自身的流量方面的 bug 把 kube-apiserver 打垮后将造成整个系统的 P0 级故障,dubbogo v1.6 应当给出 dubbogo operator 的具体实现;
* 2020 年 7 月,dubbogo v1.5 发布后,社区已经知道完全可以把目前的 kube-apiserver 注册中心的实现独立成为一个 dubbogo operator,未来的方向是结合 Istio 拓展其灰度发布、限流、故障注入和配置动态下发能力。
至于 dubbo-go-proxy ,dubbogo 社区并不打算借鉴其他项目,完全依靠社区同学贡献各自想法后,进行项目需求收集。目前 dubbogo 社区已经收集完毕 dubbo-go-proxy 的[项目需求方的意见](https://github.com/apache/dubbo-go/issues/540),社区已有 5 位同学参与项目一期开发,预计 10 月份发布初版。
## 2\. 项目管理
需求收集完毕,定义近期一段时间内的开发目标后,就进入了项目任务拆解和项目开发管理阶段。像 dubbogo 和 dubbo-go-proxy 这种后来者项目,处于追赶阶段,个人理解它们并没有所谓的后发优势,更没有特定的技术优势,能够做的就是快速迭代,缩短与竞品的差距。
dubbogo 要求接受开发任务的各个 feature owner 必须在某个 deadline 前完成相应的开发任务。当然,feature 的等级不同,非核心 feature 【如技术预演性的 feature】允许 delay,顺延发布也无不可。
我们在项目每个版本的需求收集阶段,会把爱好者统一拉入一个小群进行沟通交流。进入开发阶段时,由于项目时间 deadline 限定以及技术匹配度两项要求,每个版本就很自然能选出能够留下来参与项目开发的人。最终参与每个版本开发的人尽量不要超过 7 个人,否则沟通成本就会剧增。
下图是 dubbogo v1.5.1 的项目管理图\*\*(阿里巴巴云原生公众号后台回复“915”即可查看清晰大图)\*\*
![1.png](../../pic/course/the-5th-years-of-dubbo-go-b.png)
其有任务分解、技术风险以及风险预案。
![2.png](../../pic/course/the-5th-years-of-dubbo-go-c.png)
工具是生产力,目前以 dubbogo 项目开发进度跟踪工具使用 Github Projects。如上图,每个子任务进度,这个工具都会及时显示,相应的任务 owner 可以及时根据任务进度和 deadline ,调整开发计划。更进一步的好处是,没有人会对工具产生意见,摆脱“交通基本靠走,通讯基本靠吼”的沟通模式,减少版本发版人和 feature owner 之间的戾气。
## 3\. 代码质量
开源项目的开发任务不仅仅是开发代码,也不意味着因为各个 owner 仅仅是业余时间参与开源项目就可以降低对代码质量要求。
工具就是生产力,合适的工具能够减少人工工作量和提升代码质量。dubbogo 在项目开发过程中的各个阶段都用到了如下工具:
* auto-comment:contributor 提出 issue 或者 pr 后,可很方便地发出预先定制的评语;
* hound:一个 Go 语言项目静态代码检测工具,自动 Review 代码;
* travis:一个 Github 项目自动化测试工具,可自动执行代码单测和用户自定义的集成测试,并发出钉钉通知;
* 人工 Review:dubbogo 社区要求每个 pr 至少有三个 committer 级别的人 Review 通过;
* goreportcard:一个很好的 Github 项目静态代码检测工具;
* gitee:作为国内一个比较强大的代码托管网站,免费为项目提供了一些代码安全和静态代码质量检测工具,dubbogo 也经常使用,受益良多;
* 代码规范,社区内部有一份简单的代码规范,并随着项目发展不断完善。
# 展望
<br />dubbogo 项目每次发完版本,发版人都会首先发出一份 "What's New",除了总结最新版本的特性外,还会总结其近期进展并对未来发展进行规划,以帮助项目爱好者和使用者了解其实现思路和使用方法。
dubbogo 自身还有很多缺点,如:
* 网站建设和文档质量都有待改进;
* API 用户友好度不够;
* 配置文件过多且没有合理的文档说明导致入门门槛高;
* 整体性能改进,很多地方可添加调用链的缓存以减小锁竞争;
* 添加 prometheus 指标,继续提高 可观测性;
* 在协议层面支持其他微服务框架,实现原生通信,以继续提升其互联互通性;
* dubbo-samples 用例不够丰富,继续添加测试用例,以减小入门门槛;
希望借助社区之力,在 dubbogo 发展过程中消化并解决掉这些问题,dubbogo 社区【钉钉群号 23331795】与 dubbogo 同在。
### 作者简介
于雨,一个有十多年服务端基础架构研发一线工作经验的程序员,目前在蚂蚁金服可信原生部从事容器编排和 service mesh 工作。热爱开源,从 2015 年给 Redis 贡献代码开始,陆续改进过 Muduo/Pika/Dubbo/Dubbo-go 等知名项目。
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment