Repository: matrixorigin/matrixone
Pull request #10916 (2023-07-29 04:15:30)

[refactor] clean up port configs.
For CN and DN, add new config port-base as the starting port for the port number used by the service. Means that it is not neccesary to config ports for ctl-service, lock-service and etc, port-base is enough. But for compatibility, the new policy is enabled only when port-base is configured. And if it not configured, the old configs for the address work.

For logservice, we do not use the port-base for two reasons. First, logservice will rarely have new services, and no new ports. Second, it is not suiteable to use port-base in logservice as hakeeper-client and gossip-seed-addresses needs to know the exact port numbers.

MO doc updated:

https://github.com/matrixorigin/matrixorigin.io.cn/pull/625

## What type of PR is this?

- [ ] API-change
- [ ] BUG
- [ ] Improvement
- [ ] Documentation
- [ ] Feature
- [ ] Test and CI
- [x] Code Refactoring

## Which issue(s) this PR fixes:

issue #10487 

## What this PR does / why we need it:

xzxiong: Curiously, why not all services share one `port-base`, like the default `data-dir` in the top level cfg ?
Then we may organize all in one place.

Patch:
diff --git a/cmd/mo-service/config.go b/cmd/mo-service/config.go
index 84c063bed..78f348214 100644
--- a/cmd/mo-service/config.go
+++ b/cmd/mo-service/config.go
@@ -118,7 +118,7 @@ func NewConfig() *Config {
 	return &Config{
 		HAKeeperClient: logservice.HAKeeperClientConfig{
 			DiscoveryAddress: "",
-			ServiceAddresses: []string{logservice.DefaultServiceAddress},
+			ServiceAddresses: []string{logservice.DefaultLogServiceServiceAddress},
 			AllocateIDBatch:  100,
 			EnableCompress:   false,
 		},
diff --git a/cmd/mo-service/main.go b/cmd/mo-service/main.go
index 8d8626f0c..4e6a8393f 100644
--- a/cmd/mo-service/main.go
+++ b/cmd/mo-service/main.go
@@ -214,13 +214,6 @@ func startCNService(
 		if err := s.Start(); err != nil {
 			panic(err)
 		}
-		// TODO: global client need to refactor
-		err = cnclient.NewCNClient(
-			c.ServiceAddress,
-			&cnclient.ClientConfig{RPC: cfg.getCNServiceConfig().RPC})
-		if err != nil {
-			panic(err)
-		}
 
 		<-ctx.Done()
 		if err := s.Close(); err != nil {
diff --git a/etc/launch-tae-CN-tae-DN/cn.toml b/etc/launch-tae-CN-tae-DN/cn.toml
index 73bd1f461..eb833e12a 100644
--- a/etc/launch-tae-CN-tae-DN/cn.toml
+++ b/etc/launch-tae-CN-tae-DN/cn.toml
@@ -35,6 +35,7 @@ disableMetric = false
 
 [cn]
 uuid = "dd1dccb4-4d3c-41f8-b482-5251dc7a41bf"
+port-base = 18000
 
 [cn.Engine]
 type = "distributed-tae"
diff --git a/etc/launch-tae-CN-tae-DN/dn.toml b/etc/launch-tae-CN-tae-DN/dn.toml
index b1bbd1677..f20f53be9 100644
--- a/etc/launch-tae-CN-tae-DN/dn.toml
+++ b/etc/launch-tae-CN-tae-DN/dn.toml
@@ -31,6 +31,7 @@ backend = "DISK-ETL"
 
 [dn]
 uuid = "dd4dccb4-4d3c-41f8-b482-5251dc7a41bf"
+port-base = 19000
 
 [dn.Txn.Storage]
 backend = "TAE"
@@ -46,8 +47,6 @@ incremental-interval = "180s"
 global-min-count = 60
 
 [dn.LogtailServer]
-listen-address = "0.0.0.0:32003"
-service-address = "127.0.0.1:32003"
 rpc-max-message-size = "16KiB"
 rpc-payload-copy-buffer-size = "16KiB"
 rpc-enable-checksum = true
diff --git a/etc/launch-tae-compose/config/cn-0.toml b/etc/launch-tae-compose/config/cn-0.toml
index ec9c628c6..3108ce0f1 100644
--- a/etc/launch-tae-compose/config/cn-0.toml
+++ b/etc/launch-tae-compose/config/cn-0.toml
@@ -44,22 +44,10 @@ service-addresses = ["logservice:32001"]
 
 [cn]
 uuid = "dd1dccb3-4d3c-41f9-b482-5251dc7a41bf"
-listen-address = "0.0.0.0:6002"
-service-address = "cn-0:6002"
+port-base = 18100
+service-host = "cn-0"
 sql-address = "cn-0:6001"
 
-[cn.lockservice]
-listen-address = "0.0.0.0:17001"
-service-address = "cn-0:17001"
-
-[cn.ctl.address]
-listen-address = "0.0.0.0:19958"
-service-address = "cn-0:19958"
-
-[cn.query-service.address]
-listen-address = "0.0.0.0:19998"
-service-address = "cn-0:19998"
-
 [cn.Engine]
 type = "distributed-tae"
 
diff --git a/etc/launch-tae-compose/config/cn-1.toml b/etc/launch-tae-compose/config/cn-1.toml
index 9ea321d87..b5996ff42 100644
--- a/etc/launch-tae-compose/config/cn-1.toml
+++ b/etc/launch-tae-compose/config/cn-1.toml
@@ -44,21 +44,9 @@ service-addresses = ["logservice:32001"]
 
 [cn]
 uuid = "dd1dccb4-4d3c-41f8-b482-5251dc7a41bf"
-listen-address = "0.0.0.0:6002"
-service-address = "cn-1:6002"
+port-base = 18200
 sql-address = "cn-1:6001"
-
-[cn.lockservice]
-listen-address = "0.0.0.0:17001"
-service-address = "cn-1:17001"
-
-[cn.ctl.address]
-listen-address = "0.0.0.0:19958"
-service-address = "cn-1:19958"
-
-[cn.query-service.address]
-listen-address = "0.0.0.0:19998"
-service-address = "cn-1:19998"
+service-host = "cn-1"
 
 [cn.Engine]
 type = "distributed-tae"
diff --git a/etc/launch-tae-compose/config/dn.toml b/etc/launch-tae-compose/config/dn.toml
index 018e2a366..0de37e5d5 100644
--- a/etc/launch-tae-compose/config/dn.toml
+++ b/etc/launch-tae-compose/config/dn.toml
@@ -40,12 +40,8 @@ service-addresses = ["logservice:32001"]
 
 [dn]
 uuid = "713F97B0-1E28-4F80-849D-FE0979AE734A"
-service-address = "dn:41010"
-listen-address = "0.0.0.0:41010"
-
-[dn.lockservice]
-listen-address = "0.0.0.0:17001"
-service-address = "dn:17001"
+port-base = 19000
+service-host = "dn"
 
 [dn.Txn]
 
@@ -62,8 +58,6 @@ incremental-interval = "180s"
 global-min-count = 60
 
 [dn.LogtailServer]
-listen-address = "0.0.0.0:32003"
-service-address = "dn:32003"
 rpc-max-message-size = "16KiB"
 rpc-payload-copy-buffer-size = "16KiB"
 rpc-enable-checksum = true
diff --git a/etc/launch-tae-multi-CN-tae-DN/cn1.toml b/etc/launch-tae-multi-CN-tae-DN/cn1.toml
index 4ae4fd39f..21a436d1c 100644
--- a/etc/launch-tae-multi-CN-tae-DN/cn1.toml
+++ b/etc/launch-tae-multi-CN-tae-DN/cn1.toml
@@ -37,8 +37,7 @@ disableMetric = true
 
 [cn]
 uuid = "dd1dccb4-4d3c-41f8-b482-5251dc7a41bf"
-listen-address = "0.0.0.0:7001"
-service-address = "127.0.0.1:7001"
+port-base = 18100
 
 [cn.Engine]
 type = "distributed-tae"
diff --git a/etc/launch-tae-multi-CN-tae-DN/cn2.toml b/etc/launch-tae-multi-CN-tae-DN/cn2.toml
index ad55e0408..6af94b481 100644
--- a/etc/launch-tae-multi-CN-tae-DN/cn2.toml
+++ b/etc/launch-tae-multi-CN-tae-DN/cn2.toml
@@ -37,8 +37,7 @@ disableMetric = true
 
 [cn]
 uuid = "dd1dccb5-4d3c-41f8-b482-5251dc7a41bf"
-listen-address = "0.0.0.0:7002"
-service-address = "127.0.0.1:7002"
+port-base = 18200
 
 [cn.Engine]
 type = "distributed-tae"
@@ -47,17 +46,5 @@ type = "distributed-tae"
 port = 16002
 unix-socket = "/tmp/mysql2.sock"
 
-[cn.lockservice]
-listen-address = "0.0.0.0:7003"
-service-address = "127.0.0.1:7003"
-
-[cn.ctl.address]
-listen-address = "0.0.0.0:9958"
-service-address = "127.0.0.1:9958"
-
-[cn.query-service.address]
-listen-address = "0.0.0.0:29998"
-service-address = "127.0.0.1:29998"
-
 [cn.txn]
 # mode = "Pessimistic"
diff --git a/etc/launch-tae-multi-CN-tae-DN/dn.toml b/etc/launch-tae-multi-CN-tae-DN/dn.toml
index 77b2fa18e..a74fcadc4 100644
--- a/etc/launch-tae-multi-CN-tae-DN/dn.toml
+++ b/etc/launch-tae-multi-CN-tae-DN/dn.toml
@@ -33,6 +33,7 @@ backend = "DISK-ETL"
 
 [dn]
 uuid = "dd4dccb4-4d3c-41f8-b482-5251dc7a41bf"
+port-base = 19000
 
 [dn.Txn]
 # mode = "Pessimistic"
@@ -49,8 +50,6 @@ incremental-interval = "180s"
 global-min-count = 60
 
 [dn.LogtailServer]
-listen-address = "0.0.0.0:32003"
-service-address = "127.0.0.1:32003"
 rpc-max-message-size = "16KiB"
 rpc-payload-copy-buffer-size = "16KiB"
 rpc-enable-checksum = true
diff --git a/etc/launch-with-proxy/cn1.toml b/etc/launch-with-proxy/cn1.toml
index cf8503927..3cb3a7b5e 100644
--- a/etc/launch-with-proxy/cn1.toml
+++ b/etc/launch-with-proxy/cn1.toml
@@ -35,8 +35,7 @@ disableMetric = true
 
 [cn]
 uuid = "dd1dccb4-4d3c-41f8-b482-5251dc7a41bf"
-listen-address = "0.0.0.0:7001"
-service-address = "127.0.0.1:7001"
+port-base = 18100
 
 [cn.Engine]
 type = "distributed-tae"
diff --git a/etc/launch-with-proxy/cn2.toml b/etc/launch-with-proxy/cn2.toml
index 2251a3e0c..3551e27fa 100644
--- a/etc/launch-with-proxy/cn2.toml
+++ b/etc/launch-with-proxy/cn2.toml
@@ -35,8 +35,7 @@ disableMetric = true
 
 [cn]
 uuid = "dd1dccb5-4d3c-41f8-b482-5251dc7a41bf"
-listen-address = "0.0.0.0:7002"
-service-address = "127.0.0.1:7002"
+port-base = 18200
 
 [cn.Engine]
 type = "distributed-tae"
@@ -45,15 +44,3 @@ type = "distributed-tae"
 port = 16002
 unix-socket = "/tmp/mysql2.sock"
 proxy-enabled = true
-
-[cn.lockservice]
-listen-address = "0.0.0.0:7003"
-service-address = "127.0.0.1:7003"
-
-[cn.ctl.address]
-listen-address = "0.0.0.0:9958"
-service-address = "127.0.0.1:9958"
-
-[cn.query-service.address]
-listen-address = "0.0.0.0:29998"
-service-address = "127.0.0.1:29998"
diff --git a/etc/launch-with-proxy/dn.toml b/etc/launch-with-proxy/dn.toml
index a906a8e41..d9c44da35 100644
--- a/etc/launch-with-proxy/dn.toml
+++ b/etc/launch-with-proxy/dn.toml
@@ -31,6 +31,7 @@ backend = "DISK-ETL"
 
 [dn]
 uuid = "dd4dccb4-4d3c-41f8-b482-5251dc7a41bf"
+port-base = 19000
 
 [dn.Txn]
 
@@ -46,8 +47,6 @@ incremental-interval = "180s"
 global-min-count = 60
 
 [dn.LogtailServer]
-listen-address = "0.0.0.0:32003"
-service-address = "127.0.0.1:32003"
 rpc-max-message-size = "16KiB"
 rpc-payload-copy-buffer-size = "16KiB"
 rpc-enable-checksum = true
diff --git a/pkg/cnservice/cnclient/client.go b/pkg/cnservice/cnclient/client.go
index 25b8bbc90..fbde2092f 100644
--- a/pkg/cnservice/cnclient/client.go
+++ b/pkg/cnservice/cnclient/client.go
@@ -15,18 +15,17 @@
 package cnclient
 
 import (
-	"github.com/matrixorigin/matrixone/pkg/common/moerr"
 	"sync"
 	"time"
 
-	"go.uber.org/zap"
-
 	"github.com/fagongzi/goetty/v2"
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
 	"github.com/matrixorigin/matrixone/pkg/common/morpc"
 	"github.com/matrixorigin/matrixone/pkg/common/runtime"
 	"github.com/matrixorigin/matrixone/pkg/logutil"
 	"github.com/matrixorigin/matrixone/pkg/pb/pipeline"
 	"github.com/matrixorigin/matrixone/pkg/txn/rpc"
+	"go.uber.org/zap"
 )
 
 // client each node will hold only one client.
diff --git a/pkg/cnservice/server.go b/pkg/cnservice/server.go
index 5bb8faf2a..db174a7be 100644
--- a/pkg/cnservice/server.go
+++ b/pkg/cnservice/server.go
@@ -24,6 +24,7 @@ import (
 	"github.com/fagongzi/goetty/v2"
 	"github.com/matrixorigin/matrixone/pkg/bootstrap"
 	"github.com/matrixorigin/matrixone/pkg/clusterservice"
+	"github.com/matrixorigin/matrixone/pkg/cnservice/cnclient"
 	"github.com/matrixorigin/matrixone/pkg/common/moerr"
 	"github.com/matrixorigin/matrixone/pkg/common/morpc"
 	"github.com/matrixorigin/matrixone/pkg/common/mpool"
@@ -45,6 +46,7 @@ import (
 	"github.com/matrixorigin/matrixone/pkg/txn/client"
 	"github.com/matrixorigin/matrixone/pkg/txn/rpc"
 	"github.com/matrixorigin/matrixone/pkg/txn/storage/memorystorage"
+	"github.com/matrixorigin/matrixone/pkg/util/address"
 	"github.com/matrixorigin/matrixone/pkg/util/executor"
 	"github.com/matrixorigin/matrixone/pkg/vm/engine"
 	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/blockio"
@@ -82,7 +84,9 @@ func NewService(
 		etlFS:       etlFS,
 		fileService: fileService,
 		sessionMgr:  queryservice.NewSessionManager(),
+		addressMgr:  address.NewAddressManager(cfg.ServiceHost, cfg.PortBase),
 	}
+	srv.registerServices()
 	if _, err = srv.getHAKeeperClient(); err != nil {
 		return nil, err
 	}
@@ -109,7 +113,7 @@ func NewService(
 		nil,
 		nil,
 		engine.Nodes{engine.Node{
-			Addr: cfg.ServiceAddress,
+			Addr: srv.pipelineServiceServiceAddr(),
 		}})
 	pu.HAKeeperClient = srv._hakeeperClient
 	cfg.Frontend.SetDefaultValues()
@@ -117,7 +121,11 @@ func NewService(
 	frontend.InitServerVersion(pu.SV.MoVersion)
 
 	// Init the autoIncrCacheManager after the default value is set before the init of moserver.
-	srv.aicm = &defines.AutoIncrCacheManager{AutoIncrCaches: make(map[string]defines.AutoIncrCache), Mu: &sync.Mutex{}, MaxSize: pu.SV.AutoIncrCacheSize}
+	srv.aicm = &defines.AutoIncrCacheManager{
+		AutoIncrCaches: make(map[string]defines.AutoIncrCache),
+		Mu:             &sync.Mutex{},
+		MaxSize:        pu.SV.AutoIncrCacheSize,
+	}
 
 	srv.pu = pu
 	srv.pu.LockService = srv.lockService
@@ -128,7 +136,7 @@ func NewService(
 		return nil, err
 	}
 
-	server, err := morpc.NewRPCServer("cn-server", cfg.ListenAddress,
+	server, err := morpc.NewRPCServer(PipelineService.String(), srv.pipelineServiceListenAddr(),
 		morpc.NewMessageCodec(srv.acquireMessage,
 			morpc.WithCodecMaxBodySize(int(cfg.RPC.MaxMessageSize))),
 		morpc.WithServerLogger(srv.logger),
@@ -168,6 +176,15 @@ func NewService(
 	}
 
 	srv.initCtlService()
+
+	// TODO: global client need to refactor
+	err = cnclient.NewCNClient(
+		srv.pipelineServiceServiceAddr(),
+		&cnclient.ClientConfig{RPC: cfg.RPC})
+	if err != nil {
+		panic(err)
+	}
+
 	return srv, nil
 }
 
@@ -308,7 +325,7 @@ func (s *service) handleRequest(
 	go func() {
 		defer value.Cancel()
 		s.requestHandler(ctx,
-			s.cfg.ServiceAddress,
+			s.pipelineServiceServiceAddr(),
 			req,
 			cs,
 			s.storeEngine,
@@ -548,7 +565,7 @@ func (s *service) getTxnClient() (c client.TxnClient, err error) {
 }
 
 func (s *service) initLockService() {
-	cfg := s.cfg.getLockServiceConfig()
+	cfg := s.getLockServiceConfig()
 	s.lockService = lockservice.NewLockService(cfg)
 	runtime.ProcessLevelRuntime().SetGlobalVariables(runtime.LockService, s.lockService)
 	lockservice.SetLockServiceByServiceID(cfg.ServiceID, s.lockService)
@@ -599,7 +616,7 @@ func handleAssemblePipeline(ctx context.Context, message morpc.Message, cs morpc
 
 func (s *service) initInternalSQlExecutor(mp *mpool.MPool) {
 	exec := compile.NewSQLExecutor(
-		s.cfg.ServiceAddress,
+		s.pipelineServiceServiceAddr(),
 		s.storeEngine,
 		mp,
 		s._txnClient,
diff --git a/pkg/cnservice/server_ctl.go b/pkg/cnservice/server_ctl.go
index 55fad33cf..4636d3f02 100644
--- a/pkg/cnservice/server_ctl.go
+++ b/pkg/cnservice/server_ctl.go
@@ -26,7 +26,7 @@ import (
 func (s *service) initCtlService() {
 	cs, err := ctlservice.NewCtlService(
 		s.cfg.UUID,
-		s.cfg.Ctl.Address.ListenAddress,
+		s.ctlServiceListenAddr(),
 		s.cfg.RPC)
 	if err != nil {
 		panic(err)
diff --git a/pkg/cnservice/server_heartbeat.go b/pkg/cnservice/server_heartbeat.go
index 133e292cf..fd9603357 100644
--- a/pkg/cnservice/server_heartbeat.go
+++ b/pkg/cnservice/server_heartbeat.go
@@ -66,13 +66,13 @@ func (s *service) heartbeat(ctx context.Context) {
 
 	hb := logservicepb.CNStoreHeartbeat{
 		UUID:               s.cfg.UUID,
-		ServiceAddress:     s.cfg.ServiceAddress,
+		ServiceAddress:     s.pipelineServiceServiceAddr(),
 		SQLAddress:         s.cfg.SQLAddress,
-		LockServiceAddress: s.cfg.LockService.ServiceAddress,
-		CtlAddress:         s.cfg.Ctl.Address.ServiceAddress,
+		LockServiceAddress: s.lockServiceServiceAddr(),
+		CtlAddress:         s.ctlServiceServiceAddr(),
 		Role:               s.metadata.Role,
 		TaskServiceCreated: s.GetTaskRunner() != nil,
-		QueryAddress:       s.cfg.QueryServiceConfig.Address.ServiceAddress,
+		QueryAddress:       s.queryServiceServiceAddr(),
 	}
 	cb, err := s._hakeeperClient.SendCNHeartbeat(ctx2, hb)
 	if err != nil {
diff --git a/pkg/cnservice/server_query.go b/pkg/cnservice/server_query.go
index ead3af9bb..69ec75857 100644
--- a/pkg/cnservice/server_query.go
+++ b/pkg/cnservice/server_query.go
@@ -20,7 +20,7 @@ import (
 
 func (s *service) initQueryService() {
 	svc, err := queryservice.NewQueryService(s.cfg.UUID,
-		s.cfg.QueryServiceConfig.Address.ListenAddress, s.cfg.RPC, s.sessionMgr)
+		s.queryServiceListenAddr(), s.cfg.RPC, s.sessionMgr)
 	if err != nil {
 		panic(err)
 	}
diff --git a/pkg/cnservice/service_ports.go b/pkg/cnservice/service_ports.go
new file mode 100644
index 000000000..058ae32ef
--- /dev/null
+++ b/pkg/cnservice/service_ports.go
@@ -0,0 +1,112 @@
+// Copyright 2021 - 2023 Matrix Origin
+//
+// Licensed 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 cnservice
+
+type PortSlot int
+
+// New service should add before the last one.
+const (
+	PipelineService PortSlot = iota
+	LockService
+	CtlService
+	QueryService
+	MaxService
+)
+
+// String implements the fmt.Stringer interface.
+func (s PortSlot) String() string {
+	switch s {
+	case PipelineService:
+		return "Pipeline service"
+	case LockService:
+		return "Lock service"
+	case CtlService:
+		return "Ctl service"
+	case QueryService:
+		return "Query service"
+	default:
+		return "Unknown service"
+	}
+}
+
+// newPortStrategy returns true only if the port-base is not configured.
+func (s *service) newPortStrategy() bool {
+	return s.cfg.PortBase != 0
+}
+
+func (s *service) registerServices() {
+	for slot := 0; slot < int(MaxService); slot++ {
+		s.addressMgr.Register(slot)
+	}
+}
+
+// The following methods mainly consider configuration compatibility.
+// If there are no compatibility issues anymore, the methods could
+// be removed.
+func (s *service) pipelineServiceServiceAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ServiceAddress(int(PipelineService))
+	}
+	return s.cfg.ServiceAddress
+}
+
+func (s *service) pipelineServiceListenAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ListenAddress(int(PipelineService))
+	}
+	return s.cfg.ListenAddress
+}
+
+func (s *service) lockServiceServiceAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ServiceAddress(int(LockService))
+	}
+	return s.cfg.LockService.ServiceAddress
+}
+
+func (s *service) lockServiceListenAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ListenAddress(int(LockService))
+	}
+	return s.cfg.LockService.ListenAddress
+}
+
+func (s *service) ctlServiceServiceAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ServiceAddress(int(CtlService))
+	}
+	return s.cfg.Ctl.Address.ServiceAddress
+}
+
+func (s *service) ctlServiceListenAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ListenAddress(int(CtlService))
+	}
+	return s.cfg.Ctl.Address.ListenAddress
+}
+
+func (s *service) queryServiceServiceAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ServiceAddress(int(QueryService))
+	}
+	return s.cfg.QueryServiceConfig.Address.ServiceAddress
+}
+
+func (s *service) queryServiceListenAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ListenAddress(int(QueryService))
+	}
+	return s.cfg.QueryServiceConfig.Address.ListenAddress
+}
diff --git a/pkg/cnservice/types.go b/pkg/cnservice/types.go
index 1722ab718..58a17f3a7 100644
--- a/pkg/cnservice/types.go
+++ b/pkg/cnservice/types.go
@@ -41,6 +41,7 @@ import (
 	"github.com/matrixorigin/matrixone/pkg/taskservice"
 	"github.com/matrixorigin/matrixone/pkg/txn/client"
 	"github.com/matrixorigin/matrixone/pkg/txn/rpc"
+	"github.com/matrixorigin/matrixone/pkg/util/address"
 	"github.com/matrixorigin/matrixone/pkg/util/toml"
 	"github.com/matrixorigin/matrixone/pkg/vm/engine"
 	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/options"
@@ -54,6 +55,9 @@ var (
 	// defaultTxnIsolation     = txn.TxnIsolation_SI
 	defaultTxnMode             = txn.TxnMode_Optimistic
 	maxForMaxPreparedStmtCount = 1000000
+
+	// Service ports related.
+	defaultServiceHost = "127.0.0.1"
 )
 
 type Service interface {
@@ -93,6 +97,17 @@ type Config struct {
 	// SQLAddress service address for receiving external sql client
 	SQLAddress string `toml:"sql-address"`
 
+	// PortBase is the base port for the service. We reserve reservedPorts for
+	// the service to start internal server inside it.
+	//
+	// TODO(volgariver6): The value of this field is also used to determine the version
+	// of MO. If it is not set, we use the old listen-address/service-address fields, and
+	// if it is set, we use the new policy to distribute the ports to all services.
+	PortBase int `toml:"port-base"`
+	// ServiceHost is the host name/IP for the service address of RPC request. There is
+	// no port value in it.
+	ServiceHost string `toml:"service-host"`
+
 	// FileService file service configuration
 
 	Engine struct {
@@ -349,16 +364,23 @@ func (c *Config) Validate() error {
 	}
 	c.QueryServiceConfig.Adjust(foundMachineHost, defaultQueryServiceListenAddress)
 
+	if c.PortBase != 0 {
+		if c.ServiceHost == "" {
+			c.ServiceHost = defaultServiceHost
+		}
+	}
+
 	// TODO: remove this if rc is stable
 	moruntime.ProcessLevelRuntime().SetGlobalVariables(moruntime.EnableCheckInvalidRCErrors,
 		c.Txn.EnableCheckRCInvalidError)
 	return nil
 }
 
-func (c *Config) getLockServiceConfig() lockservice.Config {
-	c.LockService.ServiceID = c.UUID
-	c.LockService.RPC = c.RPC
-	return c.LockService
+func (s *service) getLockServiceConfig() lockservice.Config {
+	s.cfg.LockService.ServiceID = s.cfg.UUID
+	s.cfg.LockService.RPC = s.cfg.RPC
+	s.cfg.LockService.ListenAddress = s.lockServiceListenAddr()
+	return s.cfg.LockService
 }
 
 type service struct {
@@ -408,4 +430,6 @@ type service struct {
 		runner         taskservice.TaskRunner
 		storageFactory taskservice.TaskStorageFactory
 	}
+
+	addressMgr address.AddressManager
 }
diff --git a/pkg/dnservice/cfg.go b/pkg/dnservice/cfg.go
index 9ef78ef28..b27585e74 100644
--- a/pkg/dnservice/cfg.go
+++ b/pkg/dnservice/cfg.go
@@ -56,6 +56,9 @@ var (
 
 	storageDir     = "storage"
 	defaultDataDir = "./mo-data"
+
+	// Service ports related.
+	defaultServiceHost = "127.0.0.1"
 )
 
 // Config dn store configuration
@@ -70,6 +73,17 @@ type Config struct {
 	// ListenAddress as the communication address.
 	ServiceAddress string `toml:"service-address"`
 
+	// PortBase is the base port for the service. We reserve reservedPorts for
+	// the service to start internal server inside it.
+	//
+	// TODO(volgariver6): The value of this field is also used to determine the version
+	// of MO. If it is not set, we use the old listen-address/service-address fields, and
+	// if it is set, we use the new policy to distribute the ports to all services.
+	PortBase int `toml:"port-base"`
+	// ServiceHost is the host name/IP for the service address of RPC request. There is
+	// no port value in it.
+	ServiceHost string `toml:"service-host"`
+
 	// HAKeeper configuration
 	HAKeeper struct {
 		// HeatbeatInterval heartbeat interval to send message to hakeeper. Default is 1s
@@ -248,5 +262,11 @@ func (c *Config) Validate() error {
 	c.Ctl.Adjust(foundMachineHost, defaultCtlListenAddress)
 	c.LockService.ServiceID = c.UUID
 	c.LockService.Validate()
+
+	if c.PortBase != 0 {
+		if c.ServiceHost == "" {
+			c.ServiceHost = defaultServiceHost
+		}
+	}
 	return nil
 }
diff --git a/pkg/dnservice/factory.go b/pkg/dnservice/factory.go
index a03ab2aff..46aafc24a 100644
--- a/pkg/dnservice/factory.go
+++ b/pkg/dnservice/factory.go
@@ -134,7 +134,7 @@ func (s *store) newTAEStorage(ctx context.Context, shard metadata.DNShard, facto
 		IncrementalInterval: s.cfg.Ckp.IncrementalInterval.Duration,
 		GlobalMinCount:      s.cfg.Ckp.GlobalMinCount,
 	}
-	logtailServerAddr := s.cfg.LogtailServer.ListenAddress
+	logtailServerAddr := s.logtailServiceListenAddr()
 	logtailServerCfg := &options.LogtailServerCfg{
 		RpcMaxMessageSize:      int64(s.cfg.LogtailServer.RpcMaxMessageSize),
 		RpcEnableChecksum:      s.cfg.LogtailServer.RpcEnableChecksum,
diff --git a/pkg/dnservice/service_ports.go b/pkg/dnservice/service_ports.go
new file mode 100644
index 000000000..5b6d42df9
--- /dev/null
+++ b/pkg/dnservice/service_ports.go
@@ -0,0 +1,112 @@
+// Copyright 2021 - 2023 Matrix Origin
+//
+// Licensed 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 dnservice
+
+type PortSlot int
+
+// New service should add before the last one.
+const (
+	TxnService PortSlot = iota
+	LogtailService
+	LockService
+	CtlService
+	MaxService
+)
+
+// String implements the fmt.Stringer interface.
+func (s PortSlot) String() string {
+	switch s {
+	case TxnService:
+		return "Txn service"
+	case LogtailService:
+		return "Logtail service"
+	case LockService:
+		return "Lock service"
+	case CtlService:
+		return "Ctl service"
+	default:
+		return "Unknown service"
+	}
+}
+
+// newPortStrategy returns true only if the port-base is not configured.
+func (s *store) newPortStrategy() bool {
+	return s.cfg.PortBase != 0
+}
+
+func (s *store) registerServices() {
+	for slot := 0; slot < int(MaxService); slot++ {
+		s.addressMgr.Register(slot)
+	}
+}
+
+// The following methods mainly consider configuration compatibility.
+// If there are no compatibility issues anymore, the methods could
+// be removed.
+func (s *store) txnServiceServiceAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ServiceAddress(int(TxnService))
+	}
+	return s.cfg.ServiceAddress
+}
+
+func (s *store) txnServiceListenAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ListenAddress(int(TxnService))
+	}
+	return s.cfg.ListenAddress
+}
+
+func (s *store) logtailServiceServiceAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ServiceAddress(int(LogtailService))
+	}
+	return s.cfg.LogtailServer.ServiceAddress
+}
+
+func (s *store) logtailServiceListenAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ListenAddress(int(LogtailService))
+	}
+	return s.cfg.LogtailServer.ListenAddress
+}
+
+func (s *store) lockServiceServiceAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ServiceAddress(int(LockService))
+	}
+	return s.cfg.LockService.ServiceAddress
+}
+
+func (s *store) lockServiceListenAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ListenAddress(int(LockService))
+	}
+	return s.cfg.LockService.ListenAddress
+}
+
+func (s *store) ctlServiceServiceAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ServiceAddress(int(CtlService))
+	}
+	return s.cfg.Ctl.Address.ServiceAddress
+}
+
+func (s *store) ctlServiceListenAddr() string {
+	if s.newPortStrategy() {
+		return s.addressMgr.ListenAddress(int(CtlService))
+	}
+	return s.cfg.Ctl.Address.ListenAddress
+}
diff --git a/pkg/dnservice/store.go b/pkg/dnservice/store.go
index f15365ddd..51545c411 100644
--- a/pkg/dnservice/store.go
+++ b/pkg/dnservice/store.go
@@ -20,8 +20,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/matrixorigin/matrixone/pkg/perfcounter"
-
 	"github.com/matrixorigin/matrixone/pkg/clusterservice"
 	"github.com/matrixorigin/matrixone/pkg/common/moerr"
 	"github.com/matrixorigin/matrixone/pkg/common/morpc"
@@ -35,9 +33,11 @@ import (
 	logservicepb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
 	"github.com/matrixorigin/matrixone/pkg/pb/metadata"
 	"github.com/matrixorigin/matrixone/pkg/pb/txn"
+	"github.com/matrixorigin/matrixone/pkg/perfcounter"
 	"github.com/matrixorigin/matrixone/pkg/taskservice"
 	"github.com/matrixorigin/matrixone/pkg/txn/rpc"
 	"github.com/matrixorigin/matrixone/pkg/txn/service"
+	"github.com/matrixorigin/matrixone/pkg/util/address"
 	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/blockio"
 	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/common"
 	"go.uber.org/zap"
@@ -116,6 +116,8 @@ type store struct {
 		serviceHolder  taskservice.TaskServiceHolder
 		storageFactory taskservice.TaskStorageFactory
 	}
+
+	addressMgr address.AddressManager
 }
 
 // NewService create DN Service
@@ -149,10 +151,12 @@ func NewService(
 		fileService:         fileService,
 		metadataFileService: metadataFS,
 		shutdownC:           shutdownC,
+		addressMgr:          address.NewAddressManager(cfg.ServiceHost, cfg.PortBase),
 	}
 	for _, opt := range opts {
 		opt(s)
 	}
+	s.registerServices()
 	s.replicas = &sync.Map{}
 	s.stopper = stopper.NewStopper("dn-store",
 		stopper.WithLogger(s.rt.Logger().RawLogger()))
@@ -340,7 +344,7 @@ func (s *store) initTxnSender() error {
 
 func (s *store) initTxnServer() error {
 	server, err := rpc.NewTxnServer(
-		s.cfg.ListenAddress,
+		s.txnServiceListenAddr(),
 		s.rt,
 		rpc.WithServerQueueBufferSize(s.cfg.RPC.ServerBufferQueueSize),
 		rpc.WithServerQueueWorkers(s.cfg.RPC.ServerWorkers),
@@ -363,7 +367,7 @@ func (s *store) initClocker() error {
 
 func (s *store) initLockTableAllocator() error {
 	s.lockTableAllocator = lockservice.NewLockTableAllocator(
-		s.cfg.LockService.ListenAddress,
+		s.lockServiceListenAddr(),
 		s.cfg.LockService.KeepBindTimeout.Duration,
 		s.cfg.RPC)
 	return nil
diff --git a/pkg/dnservice/store_ctl.go b/pkg/dnservice/store_ctl.go
index 8a63c0def..0180e6cb5 100644
--- a/pkg/dnservice/store_ctl.go
+++ b/pkg/dnservice/store_ctl.go
@@ -20,7 +20,7 @@ import (
 )
 
 func (s *store) initCtlService() error {
-	cs, err := ctlservice.NewCtlService(s.cfg.UUID, s.cfg.Ctl.Address.ListenAddress, s.cfg.RPC)
+	cs, err := ctlservice.NewCtlService(s.cfg.UUID, s.ctlServiceListenAddr(), s.cfg.RPC)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/dnservice/store_heartbeat.go b/pkg/dnservice/store_heartbeat.go
index d44509b99..28468fbec 100644
--- a/pkg/dnservice/store_heartbeat.go
+++ b/pkg/dnservice/store_heartbeat.go
@@ -56,12 +56,12 @@ func (s *store) heartbeat(ctx context.Context) {
 
 	hb := logservicepb.DNStoreHeartbeat{
 		UUID:                 s.cfg.UUID,
-		ServiceAddress:       s.cfg.ServiceAddress,
+		ServiceAddress:       s.txnServiceServiceAddr(),
 		Shards:               s.getDNShardInfo(),
 		TaskServiceCreated:   s.taskServiceCreated(),
-		LogtailServerAddress: s.cfg.LogtailServer.ServiceAddress,
-		LockServiceAddress:   s.cfg.LockService.ServiceAddress,
-		CtlAddress:           s.cfg.Ctl.Address.ServiceAddress,
+		LogtailServerAddress: s.logtailServiceServiceAddr(),
+		LockServiceAddress:   s.lockServiceServiceAddr(),
+		CtlAddress:           s.ctlServiceServiceAddr(),
 	}
 	cb, err := s.hakeeperClient.SendDNHeartbeat(ctx2, hb)
 	if err != nil {
diff --git a/pkg/logservice/config.go b/pkg/logservice/config.go
index 83b26f4f7..c761412e5 100644
--- a/pkg/logservice/config.go
+++ b/pkg/logservice/config.go
@@ -15,6 +15,7 @@
 package logservice
 
 import (
+	"fmt"
 	"strconv"
 	"strings"
 	"time"
@@ -22,7 +23,6 @@ import (
 	"github.com/google/uuid"
 	"github.com/lni/dragonboat/v4"
 	"github.com/lni/vfs"
-
 	"github.com/matrixorigin/matrixone/pkg/common/moerr"
 	"github.com/matrixorigin/matrixone/pkg/hakeeper"
 	"github.com/matrixorigin/matrixone/pkg/util/toml"
@@ -36,6 +36,8 @@ const (
 	defaultServiceAddress    = "0.0.0.0:32001"
 	defaultGossipAddress     = "0.0.0.0:32002"
 	defaultGossipSeedAddress = "127.0.0.1:32002"
+	defaultRaftPort          = 32000
+	defaultGossipPort        = 32002
 
 	defaultGossipProbeInterval = 5 * time.Second
 	defaultHeartbeatInterval   = time.Second
@@ -46,8 +48,14 @@ const (
 	// The default value for HAKeeper truncate interval.
 	defaultHAKeeperTruncateInterval = 24 * time.Hour
 
-	// DefaultServiceAddress is exported.
-	DefaultServiceAddress = defaultServiceAddress
+	DefaultListenHost     = "0.0.0.0"
+	DefaultServiceHost    = "127.0.0.1"
+	DefaultLogServicePort = 32001
+)
+
+var (
+	DefaultLogServiceServiceAddress = fmt.Sprintf("%s:%d", DefaultServiceHost, DefaultLogServicePort)
+	DefaultGossipServiceAddress     = fmt.Sprintf("%s:%d", DefaultServiceHost, defaultGossipPort)
 )
 
 // Config defines the Configurations supported by the Log Service.
@@ -73,16 +81,27 @@ type Config struct {
 	// already MaxExportedSnapshot exported snapshots, no exported snapshot will
 	// be generated.
 	MaxExportedSnapshot int `toml:"max-exported-snapshot"`
+	// ServiceHost is the host name/IP for the service address of RPC request. There is
+	// no port value in it.
+	ServiceHost string `toml:"service-host"`
 	// ServiceAddress is log service's service address that can be reached by
-	// other nodes such as DN nodes.
+	// other nodes such as DN nodes. It is deprecated and will be removed.
 	ServiceAddress string `toml:"logservice-address"`
 	// ServiceListenAddress is the local listen address of the ServiceAddress.
+	// It is deprecated and will be removed.
 	ServiceListenAddress string `toml:"logservice-listen-address"`
+	// ServicePort is log service's service address port that can be reached by
+	// other nodes such as DN nodes.
+	LogServicePort int `toml:"logservice-port"`
 	// RaftAddress is the address that can be reached by other log service nodes
-	// via their raft layer.
+	// via their raft layer. It is deprecated and will be removed.
 	RaftAddress string `toml:"raft-address"`
 	// RaftListenAddress is the local listen address of the RaftAddress.
+	// It is deprecated and will be removed.
 	RaftListenAddress string `toml:"raft-listen-address"`
+	// RaftPort is the address port that can be reached by other log service nodes
+	// via their raft layer.
+	RaftPort int `toml:"raft-port"`
 	// UseTeeLogDB enables the log service to use tee based LogDB which is backed
 	// by both a pebble and a tan based LogDB. This field should only be set to
 	// true during testing.
@@ -90,12 +109,16 @@ type Config struct {
 	// LogDBBufferSize is the size of the logdb buffer in bytes.
 	LogDBBufferSize uint64 `toml:"logdb-buffer-size"`
 	// GossipAddress is the address used for accepting gossip communication.
+	// It is deprecated and will be removed.
 	GossipAddress string `toml:"gossip-address"`
 	// GossipAddressV2 is the address used for accepting gossip communication.
-	// This is for domain name support.
+	// This is for domain name support. It is deprecated and will be removed.
 	GossipAddressV2 string `toml:"gossip-address-v2"`
 	// GossipListenAddress is the local listen address of the GossipAddress
+	// It is deprecated and will be removed.
 	GossipListenAddress string `toml:"gossip-listen-address"`
+	// GossipPort is the port address port used for accepting gossip communication.
+	GossipPort int `toml:"gossip-port"`
 	// GossipSeedAddresses is list of seed addresses that are used for
 	// introducing the local node into the gossip network.
 	GossipSeedAddresses []string `toml:"gossip-seed-addresses"`
@@ -269,17 +292,17 @@ func (c *Config) Validate() error {
 	}
 	// when *ListenAddress is not empty and *Address is empty, consider it as an
 	// error
-	if len(c.ServiceAddress) == 0 && len(c.ServiceListenAddress) != 0 {
-		return moerr.NewBadConfigNoCtx("ServiceAddress not set")
+	if len(c.ServiceAddress) == 0 && len(c.ServiceListenAddress) != 0 && c.LogServicePort == 0 {
+		return moerr.NewBadConfigNoCtx("ServiceAddress and LogServicePort not set")
 	}
-	if len(c.RaftAddress) == 0 && len(c.RaftListenAddress) != 0 {
-		return moerr.NewBadConfigNoCtx("RaftAddress not set")
+	if len(c.RaftAddress) == 0 && len(c.RaftListenAddress) != 0 && c.RaftPort == 0 {
+		return moerr.NewBadConfigNoCtx("RaftAddress and RaftPort not set")
 	}
 	if c.LogDBBufferSize == 0 {
 		return moerr.NewBadConfigNoCtx("LogDBBufferSize not set")
 	}
-	if len(c.GossipAddress) == 0 && len(c.GossipListenAddress) != 0 {
-		return moerr.NewBadConfigNoCtx("GossipAddress not set")
+	if len(c.GossipAddress) == 0 && len(c.GossipListenAddress) != 0 && c.GossipPort == 0 {
+		return moerr.NewBadConfigNoCtx("GossipAddress and GossipPort not set")
 	}
 	if len(c.GossipSeedAddresses) == 0 {
 		return moerr.NewBadConfigNoCtx("GossipSeedAddress not set")
@@ -346,10 +369,14 @@ func DefaultConfig() Config {
 		MaxExportedSnapshot:      defaultMaxExportedSnapshot,
 		ServiceAddress:           defaultServiceAddress,
 		RaftAddress:              defaultRaftAddress,
+		ServiceHost:              DefaultServiceHost,
+		LogServicePort:           DefaultLogServicePort,
+		RaftPort:                 defaultRaftPort,
 		UseTeeLogDB:              false,
 		LogDBBufferSize:          defaultLogDBBufferSize,
 		GossipAddress:            defaultGossipAddress,
-		GossipSeedAddresses:      []string{defaultGossipSeedAddress},
+		GossipPort:               defaultGossipPort,
+		GossipSeedAddresses:      []string{DefaultGossipServiceAddress},
 		GossipProbeInterval:      toml.Duration{Duration: defaultGossipProbeInterval},
 		GossipAllowSelfAsSeed:    true,
 		HeartbeatInterval:        toml.Duration{Duration: defaultHeartbeatInterval},
@@ -403,7 +430,7 @@ func DefaultConfig() Config {
 		}),
 		HAKeeperClientConfig: HAKeeperClientConfig{
 			DiscoveryAddress: "",
-			ServiceAddresses: []string{defaultServiceAddress},
+			ServiceAddresses: []string{DefaultLogServiceServiceAddress},
 			AllocateIDBatch:  100,
 			EnableCompress:   false,
 		},
@@ -419,7 +446,8 @@ func DefaultConfig() Config {
 	}
 }
 
-// Fill just fills the listen addresses.
+// Fill just fills the listen addresses. This function is deprecated and will be removed
+// as the configurations are all deprecated.
 func (c *Config) Fill() {
 	if len(c.ServiceAddress) == 0 {
 		c.ServiceAddress = defaultServiceAddress
@@ -465,7 +493,7 @@ type HAKeeperClientConfig struct {
 // Validate validates the HAKeeperClientConfig.
 func (c *HAKeeperClientConfig) Validate() error {
 	if len(c.DiscoveryAddress) == 0 && len(c.ServiceAddresses) == 0 {
-		c.ServiceAddresses = []string{defaultServiceAddress}
+		c.ServiceAddresses = []string{DefaultLogServiceServiceAddress}
 	}
 	if c.AllocateIDBatch == 0 {
 		c.AllocateIDBatch = 100
@@ -503,7 +531,7 @@ func (c *ClientConfig) Validate() error {
 		return moerr.NewBadConfigNoCtx("DNReplicaID value cannot be 0")
 	}
 	if len(c.DiscoveryAddress) == 0 && len(c.ServiceAddresses) == 0 {
-		c.ServiceAddresses = []string{defaultServiceAddress}
+		c.ServiceAddresses = []string{DefaultLogServiceServiceAddress}
 	}
 	return nil
 }
@@ -519,3 +547,45 @@ func splitAddresses(v string) []string {
 	}
 	return results
 }
+
+func (c *Config) LogServiceListenAddr() string {
+	if c.LogServicePort != 0 {
+		return fmt.Sprintf("%s:%d", DefaultListenHost, c.LogServicePort)
+	}
+	return c.ServiceListenAddress
+}
+
+func (c *Config) LogServiceServiceAddr() string {
+	if c.LogServicePort != 0 {
+		return fmt.Sprintf("%s:%d", c.ServiceHost, c.LogServicePort)
+	}
+	return c.ServiceAddress
+}
+
+func (c *Config) RaftListenAddr() string {
+	if c.RaftPort != 0 {
+		return fmt.Sprintf("%s:%d", DefaultListenHost, c.RaftPort)
+	}
+	return c.RaftListenAddress
+}
+
+func (c *Config) RaftServiceAddr() string {
+	if c.RaftPort != 0 {
+		return fmt.Sprintf("%s:%d", c.ServiceHost, c.RaftPort)
+	}
+	return c.RaftAddress
+}
+
+func (c *Config) GossipListenAddr() string {
+	if c.GossipPort != 0 {
+		return fmt.Sprintf("%s:%d", DefaultListenHost, c.GossipPort)
+	}
+	return c.GossipListenAddress
+}
+
+func (c *Config) GossipServiceAddr() string {
+	if c.GossipPort != 0 {
+		return fmt.Sprintf("%s:%d", c.ServiceHost, c.GossipPort)
+	}
+	return c.GossipAddress
+}
diff --git a/pkg/logservice/service.go b/pkg/logservice/service.go
index db38b5331..0e289a929 100644
--- a/pkg/logservice/service.go
+++ b/pkg/logservice/service.go
@@ -143,7 +143,7 @@ func NewService(
 
 	// TODO: check and fix all these magic numbers
 	codec := morpc.NewMessageCodec(mf, codecOpts...)
-	server, err := morpc.NewRPCServer(LogServiceRPCName, cfg.ServiceListenAddress, codec,
+	server, err := morpc.NewRPCServer(LogServiceRPCName, cfg.LogServiceListenAddr(), codec,
 		morpc.WithServerGoettyOptions(goetty.WithSessionReleaseMsgFunc(func(i interface{}) {
 			msg := i.(morpc.RPCMessage)
 			if !msg.InternalMessage() {
diff --git a/pkg/logservice/store.go b/pkg/logservice/store.go
index 3c619b61b..fc66b70d9 100644
--- a/pkg/logservice/store.go
+++ b/pkg/logservice/store.go
@@ -65,7 +65,7 @@ func isSetLeaseHolderUpdate(cmd []byte) bool {
 
 func getNodeHostConfig(cfg Config) config.NodeHostConfig {
 	meta := storeMeta{
-		serviceAddress: cfg.ServiceAddress,
+		serviceAddress: cfg.LogServiceServiceAddr(),
 	}
 	if cfg.GossipProbeInterval.Duration == 0 {
 		panic("cfg.GossipProbeInterval.Duration is 0")
@@ -84,8 +84,8 @@ func getNodeHostConfig(cfg Config) config.NodeHostConfig {
 		NodeHostDir:         cfg.DataDir,
 		RTTMillisecond:      cfg.RTTMillisecond,
 		AddressByNodeHostID: true,
-		RaftAddress:         cfg.RaftAddress,
-		ListenAddress:       cfg.RaftListenAddress,
+		RaftAddress:         cfg.RaftServiceAddr(),
+		ListenAddress:       cfg.RaftListenAddr(),
 		Expert: config.ExpertConfig{
 			FS:           cfg.FS,
 			LogDBFactory: logdbFactory,
@@ -95,8 +95,8 @@ func getNodeHostConfig(cfg Config) config.NodeHostConfig {
 			LogDB:                   logdb,
 		},
 		Gossip: config.GossipConfig{
-			BindAddress:      cfg.GossipListenAddress,
-			AdvertiseAddress: cfg.GossipAddress,
+			BindAddress:      cfg.GossipListenAddr(),
+			AdvertiseAddress: cfg.GossipServiceAddr(),
 			Seed:             cfg.GossipSeedAddresses,
 			Meta:             meta.marshal(),
 			CanUseSelfAsSeed: cfg.GossipAllowSelfAsSeed,
@@ -835,9 +835,9 @@ func (l *store) hakeeperTick() {
 func (l *store) getHeartbeatMessage() pb.LogStoreHeartbeat {
 	m := pb.LogStoreHeartbeat{
 		UUID:           l.id(),
-		RaftAddress:    l.cfg.RaftAddress,
-		ServiceAddress: l.cfg.ServiceAddress,
-		GossipAddress:  l.cfg.GossipAddress,
+		RaftAddress:    l.cfg.RaftServiceAddr(),
+		ServiceAddress: l.cfg.LogServiceServiceAddr(),
+		GossipAddress:  l.cfg.GossipServiceAddr(),
 		Replicas:       make([]pb.LogReplicaInfo, 0),
 	}
 	opts := dragonboat.NodeHostInfoOption{
diff --git a/pkg/util/address/address.go b/pkg/util/address/address.go
index fd9ceef33..a42862c1b 100644
--- a/pkg/util/address/address.go
+++ b/pkg/util/address/address.go
@@ -15,7 +15,14 @@
 package address
 
 import (
+	"fmt"
 	"strings"
+	"sync"
+)
+
+const (
+	defaultListenAddressHost = "0.0.0.0"
+	defaultReservedSlots     = 20
 )
 
 // Address is used to describe the address of a MO running service, divided into 2 addresses,
@@ -62,3 +69,71 @@ func replaceHost(
 	}
 	return strings.Replace(address, oldHost, newHost, 1)
 }
+
+// AddressManager manages all service names and ports. It uses unified
+// listen address and service address. The port of each service is generated
+// by port base and the port slot.
+type AddressManager interface {
+	// Register registers a service by its name and port slot.
+	Register(portSlot int)
+	// ListenAddress returns the service address of the service.
+	ListenAddress(slot int) string
+	// ServiceAddress returns the service address of the service.
+	ServiceAddress(slot int) string
+}
+
+type addressManager struct {
+	portBase      int
+	reservedSlots int
+	address       Address
+	mu            struct {
+		sync.Mutex
+		services map[int]struct{}
+	}
+}
+
+func NewAddressManager(serviceAddress string, portBase int) AddressManager {
+	am := &addressManager{
+		address: Address{
+			ListenAddress:  defaultListenAddressHost,
+			ServiceAddress: serviceAddress,
+		},
+		portBase:      portBase,
+		reservedSlots: defaultReservedSlots,
+	}
+	am.mu.services = make(map[int]struct{})
+	return am
+}
+
+// Register implements the AddressManager interface.
+func (m *addressManager) Register(portSlot int) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if portSlot > m.reservedSlots {
+		panic("the number of requested ports exceeds the limit")
+	}
+	if _, ok := m.mu.services[portSlot]; ok {
+		panic(fmt.Sprintf("slot %d has already been registered", portSlot))
+	}
+	m.mu.services[portSlot] = struct{}{}
+}
+
+// ListenAddress implements the AddressManager interface.
+func (m *addressManager) ListenAddress(slot int) string {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if _, ok := m.mu.services[slot]; !ok {
+		panic(fmt.Sprintf("slot %d has not been registered yet", slot))
+	}
+	return fmt.Sprintf("%s:%d", m.address.ListenAddress, m.portBase+slot)
+}
+
+// ServiceAddress implements the AddressManager interface.
+func (m *addressManager) ServiceAddress(slot int) string {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if _, ok := m.mu.services[slot]; !ok {
+		panic(fmt.Sprintf("slot %d has not been registered yet", slot))
+	}
+	return fmt.Sprintf("%s:%d", m.address.ServiceAddress, m.portBase+slot)
+}


Test patch:
diff --git a/pkg/cnservice/service_ports_test.go b/pkg/cnservice/service_ports_test.go
new file mode 100644
index 000000000..6fc2a2e01
--- /dev/null
+++ b/pkg/cnservice/service_ports_test.go
@@ -0,0 +1,84 @@
+// Copyright 2021 - 2023 Matrix Origin
+//
+// Licensed 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 cnservice
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/matrixorigin/matrixone/pkg/ctlservice"
+	"github.com/matrixorigin/matrixone/pkg/lockservice"
+	"github.com/matrixorigin/matrixone/pkg/queryservice"
+	"github.com/matrixorigin/matrixone/pkg/util/address"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestService_NewPortStrategy(t *testing.T) {
+	s := &service{cfg: &Config{}}
+	assert.False(t, s.newPortStrategy())
+	s.cfg.PortBase = 1000
+	assert.True(t, s.newPortStrategy())
+}
+
+func TestService_RegisterServices(t *testing.T) {
+	listenHost := "0.0.0.0"
+	serviceHost := "127.0.0.1"
+	port1 := 1000
+	port2 := 2000
+
+	s := &service{
+		cfg: &Config{
+			ListenAddress:  fmt.Sprintf("%s:%d", listenHost, port1),
+			ServiceAddress: fmt.Sprintf("%s:%d", serviceHost, port1),
+			LockService: lockservice.Config{
+				ListenAddress:  fmt.Sprintf("%s:%d", listenHost, port1+1),
+				ServiceAddress: fmt.Sprintf("%s:%d", serviceHost, port1+1),
+			},
+			Ctl: ctlservice.Config{
+				Address: address.Address{
+					ListenAddress:  fmt.Sprintf("%s:%d", listenHost, port1+2),
+					ServiceAddress: fmt.Sprintf("%s:%d", serviceHost, port1+2),
+				},
+			},
+			QueryServiceConfig: queryservice.Config{
+				Address: address.Address{
+					ListenAddress:  fmt.Sprintf("%s:%d", listenHost, port1+3),
+					ServiceAddress: fmt.Sprintf("%s:%d", serviceHost, port1+3),
+				},
+			},
+		},
+		addressMgr: address.NewAddressManager(serviceHost, port2),
+	}
+
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port1), s.pipelineServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port1), s.pipelineServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port1+1), s.lockServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port1+1), s.lockServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port1+2), s.ctlServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port1+2), s.ctlServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port1+3), s.queryServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port1+3), s.queryServiceListenAddr())
+
+	s.cfg.PortBase = port2
+	s.registerServices()
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port2), s.pipelineServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port2), s.pipelineServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port2+1), s.lockServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port2+1), s.lockServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port2+2), s.ctlServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port2+2), s.ctlServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port2+3), s.queryServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port2+3), s.queryServiceListenAddr())
+}
diff --git a/pkg/dnservice/service_ports_test.go b/pkg/dnservice/service_ports_test.go
new file mode 100644
index 000000000..db8a30954
--- /dev/null
+++ b/pkg/dnservice/service_ports_test.go
@@ -0,0 +1,96 @@
+// Copyright 2021 - 2023 Matrix Origin
+//
+// Licensed 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 dnservice
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/matrixorigin/matrixone/pkg/ctlservice"
+	"github.com/matrixorigin/matrixone/pkg/lockservice"
+	"github.com/matrixorigin/matrixone/pkg/util/address"
+	"github.com/matrixorigin/matrixone/pkg/util/toml"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestService_NewPortStrategy(t *testing.T) {
+	s := &store{cfg: &Config{}}
+	assert.False(t, s.newPortStrategy())
+	s.cfg.PortBase = 1000
+	assert.True(t, s.newPortStrategy())
+}
+
+func TestService_RegisterServices(t *testing.T) {
+	listenHost := "0.0.0.0"
+	serviceHost := "127.0.0.1"
+	port1 := 1000
+	port2 := 2000
+
+	s := &store{
+		cfg: &Config{
+			ListenAddress:  fmt.Sprintf("%s:%d", listenHost, port1),
+			ServiceAddress: fmt.Sprintf("%s:%d", serviceHost, port1),
+			LogtailServer: struct {
+				ListenAddress              string        `toml:"listen-address"`
+				ServiceAddress             string        `toml:"service-address"`
+				RpcMaxMessageSize          toml.ByteSize `toml:"rpc-max-message-size"`
+				RpcEnableChecksum          bool          `toml:"rpc-enable-checksum"`
+				LogtailCollectInterval     toml.Duration `toml:"logtail-collect-interval"`
+				LogtailResponseSendTimeout toml.Duration `toml:"logtail-response-send-timeout"`
+			}(struct {
+				ListenAddress              string
+				ServiceAddress             string
+				RpcMaxMessageSize          toml.ByteSize
+				RpcEnableChecksum          bool
+				LogtailCollectInterval     toml.Duration
+				LogtailResponseSendTimeout toml.Duration
+			}{
+				ListenAddress:  fmt.Sprintf("%s:%d", listenHost, port1+1),
+				ServiceAddress: fmt.Sprintf("%s:%d", serviceHost, port1+1),
+			}),
+			LockService: lockservice.Config{
+				ListenAddress:  fmt.Sprintf("%s:%d", listenHost, port1+2),
+				ServiceAddress: fmt.Sprintf("%s:%d", serviceHost, port1+2),
+			},
+			Ctl: ctlservice.Config{
+				Address: address.Address{
+					ListenAddress:  fmt.Sprintf("%s:%d", listenHost, port1+3),
+					ServiceAddress: fmt.Sprintf("%s:%d", serviceHost, port1+3),
+				},
+			},
+		},
+		addressMgr: address.NewAddressManager(serviceHost, port2),
+	}
+
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port1), s.txnServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port1), s.txnServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port1+1), s.logtailServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port1+1), s.logtailServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port1+2), s.lockServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port1+2), s.lockServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port1+3), s.ctlServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port1+3), s.ctlServiceListenAddr())
+
+	s.cfg.PortBase = port2
+	s.registerServices()
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port2), s.txnServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port2), s.txnServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port2+1), s.logtailServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port2+1), s.logtailServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port2+2), s.lockServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port2+2), s.lockServiceListenAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", serviceHost, port2+3), s.ctlServiceServiceAddr())
+	assert.Equal(t, fmt.Sprintf("%s:%d", listenHost, port2+3), s.ctlServiceListenAddr())
+}
diff --git a/pkg/logservice/config_test.go b/pkg/logservice/config_test.go
index 69eece40e..02e8a4228 100644
--- a/pkg/logservice/config_test.go
+++ b/pkg/logservice/config_test.go
@@ -17,11 +17,9 @@ package logservice
 import (
 	"testing"
 
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-
-	"github.com/matrixorigin/matrixone/pkg/common/moerr"
-	"github.com/matrixorigin/matrixone/pkg/hakeeper"
 )
 
 func getTestConfig() Config {
@@ -29,12 +27,9 @@ func getTestConfig() Config {
 	c.UUID = "uuid"
 	c.DeploymentID = 100
 	c.DataDir = "/mydata/dir"
-	c.ServiceAddress = "localhost:9000"
-	c.ServiceListenAddress = "localhost:9000"
-	c.RaftAddress = "localhost:9001"
-	c.RaftListenAddress = "localhost:9001"
-	c.GossipAddress = "localhost:9002"
-	c.GossipListenAddress = "localhost:9002"
+	c.LogServicePort = 9000
+	c.RaftPort = 9001
+	c.GossipPort = 9002
 	c.GossipSeedAddresses = []string{"localhost:9002"}
 	c.Fill()
 	return c
@@ -112,16 +107,19 @@ func TestConfigCanBeValidated(t *testing.T) {
 
 	c2 := c
 	c2.ServiceAddress = ""
+	c2.LogServicePort = 0
 	err = c2.Validate()
 	assert.True(t, moerr.IsMoErrCode(err, moerr.ErrBadConfig))
 
 	c3 := c
 	c3.RaftAddress = ""
+	c3.RaftPort = 0
 	err = c2.Validate()
 	assert.True(t, moerr.IsMoErrCode(err, moerr.ErrBadConfig))
 
 	c4 := c
 	c4.GossipAddress = ""
+	c4.GossipPort = 0
 	err = c4.Validate()
 	assert.True(t, moerr.IsMoErrCode(err, moerr.ErrBadConfig))
 
@@ -168,41 +166,6 @@ func TestBootstrapConfigCanBeValidated(t *testing.T) {
 	assert.True(t, moerr.IsMoErrCode(err, moerr.ErrBadConfig))
 }
 
-func TestFillConfig(t *testing.T) {
-	c := DefaultConfig()
-	c.Fill()
-	assert.Equal(t, uint64(1), c.DeploymentID)
-	assert.Equal(t, defaultDataDir, c.DataDir)
-	assert.Equal(t, defaultSnapshotExportDir, c.SnapshotExportDir)
-	assert.Equal(t, defaultMaxExportedSnapshot, c.MaxExportedSnapshot)
-	assert.Equal(t, defaultServiceAddress, c.ServiceAddress)
-	assert.Equal(t, defaultServiceAddress, c.ServiceListenAddress)
-	assert.Equal(t, defaultRaftAddress, c.RaftAddress)
-	assert.Equal(t, defaultRaftAddress, c.RaftListenAddress)
-	assert.Equal(t, defaultGossipAddress, c.GossipAddress)
-	assert.Equal(t, defaultGossipAddress, c.GossipListenAddress)
-	assert.Equal(t, 1, len(c.GossipSeedAddresses))
-	assert.Equal(t, hakeeper.DefaultTickPerSecond, c.HAKeeperConfig.TickPerSecond)
-	assert.Equal(t, hakeeper.DefaultLogStoreTimeout, c.HAKeeperConfig.LogStoreTimeout.Duration)
-	assert.Equal(t, hakeeper.DefaultDNStoreTimeout, c.HAKeeperConfig.DNStoreTimeout.Duration)
-}
-
-func TestListenAddressCanBeFilled(t *testing.T) {
-	cfg := Config{
-		DeploymentID:        1,
-		RTTMillisecond:      5,
-		DataDir:             "data-1",
-		ServiceAddress:      "127.0.0.1:9002",
-		RaftAddress:         "127.0.0.1:9000",
-		GossipAddress:       "127.0.0.1:9001",
-		GossipSeedAddresses: []string{"127.0.0.1:9011"},
-	}
-	cfg.Fill()
-	assert.Equal(t, cfg.ServiceAddress, cfg.ServiceListenAddress)
-	assert.Equal(t, cfg.RaftAddress, cfg.RaftListenAddress)
-	assert.Equal(t, cfg.GossipAddress, cfg.GossipListenAddress)
-}
-
 func TestClientConfigValidate(t *testing.T) {
 	tests := []struct {
 		cfg ClientConfig
@@ -272,12 +235,3 @@ func TestHAKeeperClientConfigValidate(t *testing.T) {
 		}
 	}
 }
-
-func TestGossipAddressV2IsUsed(t *testing.T) {
-	cfg := DefaultConfig()
-	cfg.GossipAddress = "127.0.0.1:9001"
-	cfg.GossipAddressV2 = "localhost:9002"
-	cfg.Fill()
-	assert.Equal(t, cfg.GossipAddress, cfg.GossipAddressV2)
-	assert.Equal(t, cfg.GossipAddress, cfg.GossipListenAddress)
-}
diff --git a/pkg/logservice/hakeeper_client_test.go b/pkg/logservice/hakeeper_client_test.go
index 0ab3e2e1d..41fdc3964 100644
--- a/pkg/logservice/hakeeper_client_test.go
+++ b/pkg/logservice/hakeeper_client_test.go
@@ -289,28 +289,22 @@ func testNotHAKeeperErrorIsHandled(t *testing.T, fn func(*testing.T, *managedHAK
 	cfg1.DeploymentID = 1
 	cfg1.RTTMillisecond = 5
 	cfg1.DataDir = "data-1"
-	cfg1.ServiceAddress = "127.0.0.1:9002"
-	cfg1.RaftAddress = "127.0.0.1:9000"
-	cfg1.GossipAddress = "127.0.0.1:9001"
+	cfg1.LogServicePort = 9002
+	cfg1.RaftPort = 9000
+	cfg1.GossipPort = 9001
 	cfg1.GossipSeedAddresses = []string{"127.0.0.1:9011"}
 	cfg1.DisableWorkers = true
-	cfg1.Fill()
 	cfg2 := DefaultConfig()
 	cfg2.UUID = uuid.New().String()
 	cfg2.FS = vfs.NewStrictMem()
 	cfg2.DeploymentID = 1
 	cfg2.RTTMillisecond = 5
 	cfg2.DataDir = "data-2"
-	cfg2.ServiceAddress = "127.0.0.1:9012"
-	cfg2.ServiceListenAddress = cfg2.ServiceAddress
-	cfg2.RaftAddress = "127.0.0.1:9010"
-	cfg2.RaftListenAddress = cfg2.RaftAddress
-	cfg2.GossipAddress = "127.0.0.1:9011"
-	cfg2.GossipAddressV2 = cfg2.GossipAddress
-	cfg2.GossipListenAddress = cfg2.GossipAddress
+	cfg2.LogServicePort = 9012
+	cfg2.RaftPort = 9010
+	cfg2.GossipPort = 9011
 	cfg2.GossipSeedAddresses = []string{"127.0.0.1:9001"}
 	cfg2.DisableWorkers = true
-	cfg2.Fill()
 	service1, err := NewService(cfg1,
 		newFS(),
 		nil,
@@ -347,7 +341,7 @@ func testNotHAKeeperErrorIsHandled(t *testing.T, fn func(*testing.T, *managedHAK
 		return &RPCResponse{pool: respPool}
 	}
 	cfg := HAKeeperClientConfig{
-		ServiceAddresses: []string{cfg1.ServiceAddress, cfg2.ServiceAddress},
+		ServiceAddresses: []string{cfg1.LogServiceServiceAddr(), cfg2.LogServiceServiceAddr()},
 	}
 	c := &hakeeperClient{
 		cfg:      cfg,
@@ -356,9 +350,9 @@ func testNotHAKeeperErrorIsHandled(t *testing.T, fn func(*testing.T, *managedHAK
 	}
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
 	defer cancel()
-	cc, err := getRPCClient(ctx, cfg1.ServiceAddress, c.respPool, defaultMaxMessageSize, false)
+	cc, err := getRPCClient(ctx, cfg1.LogServiceServiceAddr(), c.respPool, defaultMaxMessageSize, false)
 	require.NoError(t, err)
-	c.addr = cfg1.ServiceAddress
+	c.addr = cfg1.LogServiceServiceAddr()
 	c.client = cc
 	client := &managedHAKeeperClient{cfg: cfg}
 	client.mu.client = c
diff --git a/pkg/logservice/service_commands_test.go b/pkg/logservice/service_commands_test.go
index 81a696432..b9cb97732 100644
--- a/pkg/logservice/service_commands_test.go
+++ b/pkg/logservice/service_commands_test.go
@@ -40,15 +40,14 @@ func TestBackgroundTickAndHeartbeat(t *testing.T) {
 	cfg.DeploymentID = 1
 	cfg.RTTMillisecond = 5
 	cfg.DataDir = "data-1"
-	cfg.ServiceAddress = "127.0.0.1:9002"
-	cfg.RaftAddress = "127.0.0.1:9000"
-	cfg.GossipAddress = "127.0.0.1:9001"
+	cfg.LogServicePort = 9002
+	cfg.RaftPort = 9000
+	cfg.GossipPort = 9001
 	// below is an unreachable address intentionally set
 	cfg.GossipSeedAddresses = []string{"127.0.0.1:9010"}
 	cfg.HeartbeatInterval.Duration = 5 * time.Millisecond
 	cfg.HAKeeperTickInterval.Duration = 5 * time.Millisecond
 	cfg.HAKeeperClientConfig.ServiceAddresses = []string{"127.0.0.1:9002"}
-	cfg.Fill()
 	service, err := NewService(cfg,
 		newFS(),
 		nil,
diff --git a/pkg/logservice/service_test.go b/pkg/logservice/service_test.go
index 522614856..7619e7f31 100644
--- a/pkg/logservice/service_test.go
+++ b/pkg/logservice/service_test.go
@@ -36,6 +36,8 @@ import (
 )
 
 const (
+	testServicePort        = 9000
+	testGossipPort         = 9010
 	testServiceAddress     = "127.0.0.1:9000"
 	testGossipAddress      = "127.0.0.1:9010"
 	dummyGossipSeedAddress = "127.0.0.1:9100"
@@ -46,17 +48,14 @@ func getServiceTestConfig() Config {
 	c := DefaultConfig()
 	c.UUID = uuid.New().String()
 	c.RTTMillisecond = 10
-	c.GossipAddress = testGossipAddress
-	c.GossipListenAddress = testGossipAddress
+	c.GossipPort = testGossipPort
 	c.GossipSeedAddresses = []string{testGossipAddress, dummyGossipSeedAddress}
 	c.DeploymentID = 1
 	c.FS = vfs.NewStrictMem()
-	c.ServiceListenAddress = testServiceAddress
-	c.ServiceAddress = testServiceAddress
+	c.LogServicePort = testServicePort
 	c.DisableWorkers = true
 	c.UseTeeLogDB = true
 	c.RPC.MaxMessageSize = testServerMaxMsgSize
-	c.Fill()
 	return c
 }
 
@@ -521,24 +520,22 @@ func TestShardInfoCanBeQueried(t *testing.T) {
 	cfg1.DeploymentID = 1
 	cfg1.RTTMillisecond = 5
 	cfg1.DataDir = "data-1"
-	cfg1.ServiceAddress = "127.0.0.1:9002"
-	cfg1.RaftAddress = "127.0.0.1:9000"
-	cfg1.GossipAddress = "127.0.0.1:9001"
+	cfg1.LogServicePort = 9002
+	cfg1.RaftPort = 9000
+	cfg1.GossipPort = 9001
 	cfg1.GossipSeedAddresses = []string{"127.0.0.1:9011"}
 	cfg1.DisableWorkers = true
-	cfg1.Fill()
 	cfg2 := DefaultConfig()
 	cfg2.UUID = uuid.New().String()
 	cfg2.FS = vfs.NewStrictMem()
 	cfg2.DeploymentID = 1
 	cfg2.RTTMillisecond = 5
 	cfg2.DataDir = "data-2"
-	cfg2.ServiceAddress = "127.0.0.1:9012"
-	cfg2.RaftAddress = "127.0.0.1:9010"
-	cfg2.GossipAddress = "127.0.0.1:9011"
+	cfg2.LogServicePort = 9012
+	cfg2.RaftPort = 9010
+	cfg2.GossipPort = 9011
 	cfg2.GossipSeedAddresses = []string{"127.0.0.1:9001"}
 	cfg2.DisableWorkers = true
-	cfg1.Fill()
 	service1, err := NewService(cfg1,
 		newFS(),
 		nil,
@@ -589,7 +586,7 @@ func TestShardInfoCanBeQueried(t *testing.T) {
 		ri, ok := si1.Replicas[1]
 		assert.True(t, ok)
 		assert.Equal(t, nhID1, ri.UUID)
-		assert.Equal(t, cfg1.ServiceAddress, ri.ServiceAddress)
+		assert.Equal(t, cfg1.LogServiceServiceAddr(), ri.ServiceAddress)
 
 		si2, ok := service1.getShardInfo(2)
 		if !ok || si2.LeaderID != 1 {
@@ -602,7 +599,7 @@ func TestShardInfoCanBeQueried(t *testing.T) {
 		ri, ok = si2.Replicas[1]
 		assert.True(t, ok)
 		assert.Equal(t, nhID2, ri.UUID)
-		assert.Equal(t, cfg2.ServiceAddress, ri.ServiceAddress)
+		assert.Equal(t, cfg2.LogServiceServiceAddr(), ri.ServiceAddress)
 
 		si1, ok = service2.getShardInfo(1)
 		if !ok || si1.LeaderID != 1 {
@@ -615,7 +612,7 @@ func TestShardInfoCanBeQueried(t *testing.T) {
 		ri, ok = si1.Replicas[1]
 		assert.True(t, ok)
 		assert.Equal(t, nhID1, ri.UUID)
-		assert.Equal(t, cfg1.ServiceAddress, ri.ServiceAddress)
+		assert.Equal(t, cfg1.LogServiceServiceAddr(), ri.ServiceAddress)
 
 		si2, ok = service2.getShardInfo(2)
 		if !ok || si2.LeaderID != 1 {
@@ -628,7 +625,7 @@ func TestShardInfoCanBeQueried(t *testing.T) {
 		ri, ok = si2.Replicas[1]
 		assert.True(t, ok)
 		assert.Equal(t, nhID2, ri.UUID)
-		assert.Equal(t, cfg2.ServiceAddress, ri.ServiceAddress)
+		assert.Equal(t, cfg2.LogServiceServiceAddr(), ri.ServiceAddress)
 
 		done = true
 		break
@@ -651,9 +648,9 @@ func TestGossipInSimulatedCluster(t *testing.T) {
 		cfg.DeploymentID = 1
 		cfg.RTTMillisecond = 200
 		cfg.DataDir = fmt.Sprintf("data-%d", i)
-		cfg.ServiceAddress = fmt.Sprintf("127.0.0.1:%d", 26000+10*i)
-		cfg.RaftAddress = fmt.Sprintf("127.0.0.1:%d", 26000+10*i+1)
-		cfg.GossipAddress = fmt.Sprintf("127.0.0.1:%d", 26000+10*i+2)
+		cfg.LogServicePort = 26000 + 10*i
+		cfg.RaftPort = 26000 + 10*i + 1
+		cfg.GossipPort = 26000 + 10*i + 2
 		cfg.GossipSeedAddresses = []string{
 			"127.0.0.1:26002",
 			"127.0.0.1:26012",
diff --git a/pkg/logservice/store_hakeeper_check_test.go b/pkg/logservice/store_hakeeper_check_test.go
index b6e94461c..54c11cff1 100644
--- a/pkg/logservice/store_hakeeper_check_test.go
+++ b/pkg/logservice/store_hakeeper_check_test.go
@@ -164,64 +164,60 @@ func runHAKeeperClusterTest(t *testing.T, fn func(*testing.T, []*Service)) {
 	cfg1.DeploymentID = 1
 	cfg1.RTTMillisecond = 5
 	cfg1.DataDir = "data-1"
-	cfg1.ServiceAddress = "127.0.0.1:9002"
-	cfg1.RaftAddress = "127.0.0.1:9000"
-	cfg1.GossipAddress = "127.0.0.1:9001"
+	cfg1.LogServicePort = 9002
+	cfg1.RaftPort = 9000
+	cfg1.GossipPort = 9001
 	cfg1.GossipSeedAddresses = []string{"127.0.0.1:9011", "127.0.0.1:9021", "127.0.0.1:9031"}
 	cfg1.DisableWorkers = true
 	cfg1.HAKeeperConfig.TickPerSecond = 10
 	cfg1.HAKeeperConfig.LogStoreTimeout.Duration = 5 * time.Second
 	cfg1.HAKeeperConfig.DNStoreTimeout.Duration = 10 * time.Second
 	cfg1.HAKeeperConfig.CNStoreTimeout.Duration = 5 * time.Second
-	cfg1.Fill()
 	cfg2 := DefaultConfig()
 	cfg2.UUID = uuid.New().String()
 	cfg2.FS = vfs.NewStrictMem()
 	cfg2.DeploymentID = 1
 	cfg2.RTTMillisecond = 5
 	cfg2.DataDir = "data-2"
-	cfg2.ServiceAddress = "127.0.0.1:9012"
-	cfg2.RaftAddress = "127.0.0.1:9010"
-	cfg2.GossipAddress = "127.0.0.1:9011"
+	cfg2.LogServicePort = 9012
+	cfg2.RaftPort = 9010
+	cfg2.GossipPort = 9011
 	cfg2.GossipSeedAddresses = []string{"127.0.0.1:9001", "127.0.0.1:9021", "127.0.0.1:9031"}
 	cfg2.DisableWorkers = true
 	cfg2.HAKeeperConfig.TickPerSecond = 10
 	cfg2.HAKeeperConfig.LogStoreTimeout.Duration = 5 * time.Second
 	cfg2.HAKeeperConfig.DNStoreTimeout.Duration = 10 * time.Second
 	cfg2.HAKeeperConfig.CNStoreTimeout.Duration = 5 * time.Second
-	cfg2.Fill()
 	cfg3 := DefaultConfig()
 	cfg3.UUID = uuid.New().String()
 	cfg3.FS = vfs.NewStrictMem()
 	cfg3.DeploymentID = 1
 	cfg3.RTTMillisecond = 5
 	cfg3.DataDir = "data-3"
-	cfg3.ServiceAddress = "127.0.0.1:9022"
-	cfg3.RaftAddress = "127.0.0.1:9020"
-	cfg3.GossipAddress = "127.0.0.1:9021"
+	cfg3.LogServicePort = 9022
+	cfg3.RaftPort = 9020
+	cfg3.GossipPort = 9021
 	cfg3.GossipSeedAddresses = []string{"127.0.0.1:9001", "127.0.0.1:9011", "127.0.0.1:9031"}
 	cfg3.DisableWorkers = true
 	cfg3.HAKeeperConfig.TickPerSecond = 10
 	cfg3.HAKeeperConfig.LogStoreTimeout.Duration = 5 * time.Second
 	cfg3.HAKeeperConfig.DNStoreTimeout.Duration = 10 * time.Second
 	cfg3.HAKeeperConfig.CNStoreTimeout.Duration = 5 * time.Second
-	cfg3.Fill()
 	cfg4 := DefaultConfig()
 	cfg4.UUID = uuid.New().String()
 	cfg4.FS = vfs.NewStrictMem()
 	cfg4.DeploymentID = 1
 	cfg4.RTTMillisecond = 5
 	cfg4.DataDir = "data-4"
-	cfg4.ServiceAddress = "127.0.0.1:9032"
-	cfg4.RaftAddress = "127.0.0.1:9030"
-	cfg4.GossipAddress = "127.0.0.1:9031"
+	cfg4.LogServicePort = 9032
+	cfg4.RaftPort = 9030
+	cfg4.GossipPort = 9031
 	cfg4.GossipSeedAddresses = []string{"127.0.0.1:9001", "127.0.0.1:9011", "127.0.0.1:9021"}
 	cfg4.DisableWorkers = true
 	cfg4.HAKeeperConfig.TickPerSecond = 10
 	cfg4.HAKeeperConfig.LogStoreTimeout.Duration = 5 * time.Second
 	cfg4.HAKeeperConfig.DNStoreTimeout.Duration = 10 * time.Second
 	cfg4.HAKeeperConfig.CNStoreTimeout.Duration = 5 * time.Second
-	cfg4.Fill()
 	service1, err := NewService(cfg1,
 		newFS(),
 		nil,
diff --git a/pkg/logservice/store_test.go b/pkg/logservice/store_test.go
index 532ec48cf..819873254 100644
--- a/pkg/logservice/store_test.go
+++ b/pkg/logservice/store_test.go
@@ -54,7 +54,6 @@ var (
 
 func TestNodeHostConfig(t *testing.T) {
 	cfg := DefaultConfig()
-	cfg.Fill()
 	cfg.DeploymentID = 1234
 	cfg.DataDir = "lalala"
 	nhConfig := getNodeHostConfig(cfg)
@@ -73,14 +72,11 @@ func getStoreTestConfig() Config {
 	cfg := DefaultConfig()
 	cfg.UUID = uuid.New().String()
 	cfg.RTTMillisecond = 10
-	cfg.GossipAddress = testGossipAddress
-	cfg.GossipAddressV2 = testGossipAddress
-	cfg.GossipListenAddress = testGossipAddress
+	cfg.GossipPort = testGossipPort
 	cfg.GossipSeedAddresses = []string{testGossipAddress, dummyGossipSeedAddress}
 	cfg.DeploymentID = 1
 	cfg.FS = vfs.NewStrictMem()
 	cfg.UseTeeLogDB = true
-	cfg.Fill()
 	return cfg
 }
 
@@ -530,11 +526,10 @@ func getTestStores() (*store, *store, error) {
 	cfg1.DeploymentID = 1
 	cfg1.RTTMillisecond = 5
 	cfg1.DataDir = "data-1"
-	cfg1.ServiceAddress = "127.0.0.1:9001"
-	cfg1.RaftAddress = "127.0.0.1:9002"
-	cfg1.GossipAddress = "127.0.0.1:9011"
+	cfg1.LogServicePort = 9001
+	cfg1.RaftPort = 9002
+	cfg1.GossipPort = 9011
 	cfg1.GossipSeedAddresses = []string{"127.0.0.1:9011", "127.0.0.1:9012"}
-	cfg1.Fill()
 	store1, err := newLogStore(cfg1, nil, runtime.DefaultRuntime())
 	if err != nil {
 		return nil, nil, err
@@ -545,11 +540,10 @@ func getTestStores() (*store, *store, error) {
 	cfg2.DeploymentID = 1
 	cfg2.RTTMillisecond = 5
 	cfg2.DataDir = "data-1"
-	cfg2.ServiceAddress = "127.0.0.1:9006"
-	cfg2.RaftAddress = "127.0.0.1:9007"
-	cfg2.GossipAddress = "127.0.0.1:9012"
+	cfg2.LogServicePort = 9006
+	cfg2.RaftPort = 9007
+	cfg2.GossipPort = 9012
 	cfg2.GossipSeedAddresses = []string{"127.0.0.1:9011", "127.0.0.1:9012"}
-	cfg2.Fill()
 	store2, err := newLogStore(cfg2, nil, runtime.DefaultRuntime())
 	if err != nil {
 		return nil, nil, err
diff --git a/pkg/logservice/testclient.go b/pkg/logservice/testclient.go
index 2d52b8439..c7d4425d1 100644
--- a/pkg/logservice/testclient.go
+++ b/pkg/logservice/testclient.go
@@ -28,13 +28,11 @@ func NewTestService(fs vfs.FS) (*Service, ClientConfig, error) {
 	cfg := DefaultConfig()
 	cfg.UUID = uuid.New().String()
 	cfg.RTTMillisecond = 10
-	cfg.GossipSeedAddresses = []string{defaultGossipSeedAddress}
+	cfg.GossipSeedAddresses = []string{DefaultGossipServiceAddress}
 	cfg.DeploymentID = 1
 	cfg.FS = fs
-	cfg.ServiceListenAddress = addr[0]
-	cfg.ServiceAddress = addr[0]
+	cfg.LogServicePort = 9000
 	cfg.DisableWorkers = true
-	cfg.Fill()
 	service, err := NewService(cfg,
 		newFS(),
 		nil,
diff --git a/pkg/tests/service/logservice.go b/pkg/tests/service/logservice.go
index 20830817d..16c66114d 100644
--- a/pkg/tests/service/logservice.go
+++ b/pkg/tests/service/logservice.go
@@ -158,17 +158,16 @@ func newLogService(
 func buildLogConfig(
 	index int, opt Options, address serviceAddresses,
 ) logservice.Config {
-	cfg := logservice.Config{
-		UUID:                  uuid.New().String(),
-		FS:                    vfs.NewStrictMem(),
-		DeploymentID:          defaultDeploymentID,
-		RTTMillisecond:        defaultRTTMillisecond,
-		ServiceAddress:        address.getLogListenAddress(index), // hakeeper client use this address
-		RaftAddress:           address.getLogRaftAddress(index),
-		GossipAddress:         address.getLogGossipAddress(index),
-		GossipSeedAddresses:   address.getLogGossipSeedAddresses(),
-		GossipAllowSelfAsSeed: opt.initial.logReplicaNum == 1,
-	}
+	cfg := logservice.DefaultConfig()
+	cfg.UUID = uuid.New().String()
+	cfg.FS = vfs.NewStrictMem()
+	cfg.DeploymentID = defaultDeploymentID
+	cfg.RTTMillisecond = defaultRTTMillisecond
+	cfg.LogServicePort = getPort(address.getLogListenAddress(index)) // hakeeper client use this address
+	cfg.RaftPort = getPort(address.getLogRaftAddress(index))
+	cfg.GossipPort = getPort(address.getLogGossipAddress(index))
+	cfg.GossipSeedAddresses = address.getLogGossipSeedAddresses()
+	cfg.GossipAllowSelfAsSeed = opt.initial.logReplicaNum == 1
 	cfg.DataDir = filepath.Join(opt.rootDataDir, cfg.UUID)
 	cfg.HeartbeatInterval.Duration = opt.heartbeat.log
 	cfg.HAKeeperCheckInterval.Duration = opt.hakeeper.checkInterval
@@ -179,9 +178,6 @@ func buildLogConfig(
 	cfg.HAKeeperConfig.DNStoreTimeout.Duration = opt.hakeeper.dnStoreTimeout
 	cfg.HAKeeperConfig.CNStoreTimeout.Duration = opt.hakeeper.cnStoreTimeout
 
-	// we must invoke Fill in order to set default configuration value.
-	cfg.Fill()
-
 	return cfg
 }
 
diff --git a/pkg/tests/service/network.go b/pkg/tests/service/network.go
index 7901e721b..572a45a59 100644
--- a/pkg/tests/service/network.go
+++ b/pkg/tests/service/network.go
@@ -15,6 +15,8 @@
 package service
 
 import (
+	"strconv"
+	"strings"
 	"testing"
 
 	"github.com/RoaringBitmap/roaring"
@@ -423,3 +425,15 @@ func (p NetworkPartition) ListCNServiceIndex() []uint32 {
 	}
 	return indexes
 }
+
+func getPort(addr string) int {
+	ss := strings.Split(addr, ":")
+	if len(ss) != 2 {
+		panic("bad address: " + addr)
+	}
+	p, err := strconv.Atoi(ss[1])
+	if err != nil {
+		panic("bad address: " + addr)
+	}
+	return p
+}
diff --git a/pkg/tests/service/service.go b/pkg/tests/service/service.go
index cc58a9888..38267a727 100644
--- a/pkg/tests/service/service.go
+++ b/pkg/tests/service/service.go
@@ -1293,7 +1293,7 @@ func (c *testCluster) buildLogConfigs(
 		cfg := buildLogConfig(i, c.opt, address)
 		cfgs = append(cfgs, cfg)
 
-		localAddr := cfg.ServiceAddress
+		localAddr := cfg.LogServiceServiceAddr()
 		opt := buildLogOptions(cfg, c.backendFilterFactory(localAddr))
 		opts = append(opts, opt)
 	}