github.com/matrixorigin/matrixone@v1.2.0/pkg/cnservice/cnclient/client.go (about) 1 // Copyright 2021 - 2022 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cnclient 16 17 import ( 18 "fmt" 19 "sync" 20 "time" 21 22 "github.com/fagongzi/goetty/v2" 23 "github.com/matrixorigin/matrixone/pkg/common/moerr" 24 "github.com/matrixorigin/matrixone/pkg/common/morpc" 25 "github.com/matrixorigin/matrixone/pkg/logutil" 26 "github.com/matrixorigin/matrixone/pkg/pb/pipeline" 27 "github.com/matrixorigin/matrixone/pkg/txn/rpc" 28 ) 29 30 const ( 31 //@todo need to find out why rpc timeout, or move heartbeat check to another way. 32 defaultRPCTimeout = 120 * time.Second 33 ) 34 35 // client each node will hold only one client. 36 // It is responsible for sending messages to other nodes. and messages were received 37 // and handled by cn-server. 38 var client = &CNClient{ 39 ready: false, 40 requestPool: &sync.Pool{New: func() any { return &pipeline.Message{} }}, 41 } 42 43 func CloseCNClient() error { 44 return client.Close() 45 } 46 47 func GetStreamSender(backend string) (morpc.Stream, error) { 48 return client.NewStream(backend) 49 } 50 51 func AcquireMessage() *pipeline.Message { 52 return client.acquireMessage().(*pipeline.Message) 53 } 54 55 func IsCNClientReady() bool { 56 client.Lock() 57 defer client.Unlock() 58 59 return client.ready 60 } 61 62 type CNClient struct { 63 sync.Mutex 64 65 localServiceAddress string 66 ready bool 67 config *ClientConfig 68 client morpc.RPCClient 69 70 // pool for send message 71 requestPool *sync.Pool 72 } 73 74 func (c *CNClient) NewStream(backend string) (morpc.Stream, error) { 75 c.Lock() 76 defer c.Unlock() 77 if !c.ready { 78 return nil, moerr.NewInternalErrorNoCtx("cn client is not ready") 79 } 80 81 if backend == c.localServiceAddress { 82 return nil, moerr.NewInternalErrorNoCtx(fmt.Sprintf("remote run pipeline in local: %s", backend)) 83 } 84 return c.client.NewStream(backend, true) 85 } 86 87 func (c *CNClient) Close() error { 88 c.Lock() 89 defer c.Unlock() 90 91 c.ready = false 92 if c.client != nil { 93 return c.client.Close() 94 } 95 return nil 96 } 97 98 const ( 99 dfMaxSenderNumber = 100000 100 dfConnectTimeout = 5 * time.Second 101 dfClientReadBufferSize = 1 << 10 102 dfClientWriteBufferSize = 1 << 10 103 ) 104 105 // ClientConfig a config to init a CNClient 106 type ClientConfig struct { 107 // MaxSenderNumber is the max number of backends per host for compute node service. 108 MaxSenderNumber int 109 // TimeOutForEachConnect is the out time for each tcp connect. 110 TimeOutForEachConnect time.Duration 111 // related buffer size. 112 ReadBufferSize int 113 WriteBufferSize int 114 // RPC rpc config 115 RPC rpc.Config 116 } 117 118 // TODO: Here it needs to be refactored together with Runtime 119 func NewCNClient( 120 localServiceAddress string, 121 cfg *ClientConfig) error { 122 logger := logutil.GetGlobalLogger().Named("cn-backend") 123 124 var err error 125 cfg.Fill() 126 127 client.Lock() 128 defer client.Unlock() 129 if client.ready { 130 return nil 131 } 132 133 cli := client 134 cli.config = cfg 135 cli.localServiceAddress = localServiceAddress 136 cli.requestPool = &sync.Pool{New: func() any { return &pipeline.Message{} }} 137 138 codec := morpc.NewMessageCodec(cli.acquireMessage, 139 morpc.WithCodecMaxBodySize(int(cfg.RPC.MaxMessageSize))) 140 factory := morpc.NewGoettyBasedBackendFactory(codec, 141 morpc.WithBackendGoettyOptions( 142 goetty.WithSessionRWBUfferSize(cfg.ReadBufferSize, cfg.WriteBufferSize), 143 goetty.WithSessionReleaseMsgFunc(func(v any) { 144 m := v.(morpc.RPCMessage) 145 if !m.InternalMessage() { 146 cli.releaseMessage(m.Message.(*pipeline.Message)) 147 } 148 }), 149 ), 150 morpc.WithBackendReadTimeout(defaultRPCTimeout), 151 morpc.WithBackendConnectTimeout(cfg.TimeOutForEachConnect), 152 morpc.WithBackendLogger(logger), 153 ) 154 155 cli.client, err = morpc.NewClient( 156 "pipeline-client", 157 factory, 158 morpc.WithClientMaxBackendPerHost(cfg.MaxSenderNumber), 159 morpc.WithClientLogger(logger), 160 ) 161 cli.ready = err == nil 162 return nil 163 } 164 165 func (c *CNClient) acquireMessage() morpc.Message { 166 // TODO: pipeline.Message has many []byte fields, maybe can use PayloadMessage to avoid mem copy. 167 return c.requestPool.Get().(*pipeline.Message) 168 } 169 170 func (c *CNClient) releaseMessage(m *pipeline.Message) { 171 if c.requestPool != nil { 172 m.Reset() 173 c.requestPool.Put(m) 174 } 175 } 176 177 // Fill set some default value for client config. 178 func (cfg *ClientConfig) Fill() { 179 if cfg.MaxSenderNumber <= 0 { 180 cfg.MaxSenderNumber = dfMaxSenderNumber 181 } 182 if cfg.ReadBufferSize < 0 { 183 cfg.ReadBufferSize = dfClientReadBufferSize 184 } 185 if cfg.WriteBufferSize < 0 { 186 cfg.WriteBufferSize = dfClientWriteBufferSize 187 } 188 if cfg.TimeOutForEachConnect <= 0 { 189 cfg.TimeOutForEachConnect = dfConnectTimeout 190 } 191 } 192 193 func GetRPCClient() morpc.RPCClient { 194 client.Lock() 195 defer client.Unlock() 196 197 if client.ready { 198 return client.client 199 } 200 return nil 201 }