Coverage Report

Created: 2026-08-03 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/brpc_client_cache.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <brpc/adaptive_connection_type.h>
21
#include <brpc/adaptive_protocol_type.h>
22
#include <brpc/channel.h>
23
#include <brpc/controller.h>
24
#include <butil/endpoint.h>
25
#include <fmt/format.h>
26
#include <gen_cpp/Types_types.h>
27
#include <gen_cpp/types.pb.h>
28
#include <glog/logging.h>
29
#include <google/protobuf/service.h>
30
#include <parallel_hashmap/phmap.h>
31
#include <stddef.h>
32
33
#include <functional>
34
#include <memory>
35
#include <mutex>
36
#include <ostream>
37
#include <string>
38
#include <utility>
39
#include <vector>
40
41
#include "common/compiler_util.h" // IWYU pragma: keep
42
#include "common/config.h"
43
#include "common/status.h"
44
#include "runtime/exec_env.h"
45
#include "service/backend_options.h"
46
#include "util/client_connection_provider.h"
47
#include "util/dns_cache.h"
48
#include "util/network_util.h"
49
50
namespace doris {
51
class PBackendService_Stub;
52
class PFunctionService_Stub;
53
} // namespace doris
54
55
// Entry that holds both resolved IP and stub, similar to Java's BackendServiceClientExtIp
56
template <typename T>
57
struct StubEntry {
58
    std::string real_ip;
59
    std::shared_ptr<T> stub;
60
};
61
62
template <typename T>
63
using StubMap = phmap::parallel_flat_hash_map<
64
        std::string, StubEntry<T>, std::hash<std::string>, std::equal_to<std::string>,
65
        std::allocator<std::pair<const std::string, StubEntry<T>>>, 8, std::mutex>;
66
67
namespace doris {
68
class FailureDetectClosure : public ::google::protobuf::Closure {
69
public:
70
    FailureDetectClosure(std::shared_ptr<AtomicStatus>& channel_st,
71
                         ::google::protobuf::RpcController* controller,
72
                         ::google::protobuf::Closure* done)
73
1.00M
            : _channel_st(channel_st), _controller(controller), _done(done) {}
74
75
1.00M
    void Run() override {
76
1.00M
        Defer defer {[&]() { delete this; }};
77
        // All brpc related API will use brpc::Controller, so that it is safe
78
        // to do static cast here.
79
1.00M
        auto* cntl = static_cast<brpc::Controller*>(_controller);
80
1.00M
        if (cntl->Failed() && cntl->ErrorCode() == EHOSTDOWN) {
81
1
            Status error_st = Status::NetworkError(
82
1
                    "Failed to send brpc, error={}, error_text={}, client: {}, latency = {}",
83
1
                    berror(cntl->ErrorCode()), cntl->ErrorText(), BackendOptions::get_localhost(),
84
1
                    cntl->latency_us());
85
1
            LOG(WARNING) << error_st;
86
1
            _channel_st->update(error_st);
87
1
        }
88
        // Sometimes done == nullptr, for example hand_shake API.
89
1.00M
        if (_done != nullptr) {
90
1.00M
            _done->Run();
91
1.00M
        }
92
        // _done->Run may throw exception, so that move delete this to Defer.
93
        // delete this;
94
1.00M
    }
95
96
private:
97
    std::shared_ptr<AtomicStatus> _channel_st;
98
    ::google::protobuf::RpcController* _controller;
99
    ::google::protobuf::Closure* _done;
100
};
101
102
// This channel will use FailureDetectClosure to wrap the original closure
103
// If some non-recoverable rpc failure happens, it will save the error status in
104
// _channel_st.
105
// And brpc client cache will depend on it to detect if the client is health.
106
class FailureDetectChannel : public ::brpc::Channel {
107
public:
108
59
    FailureDetectChannel() : ::brpc::Channel() {
109
59
        _channel_st = std::make_shared<AtomicStatus>(); // default OK
110
59
    }
111
    void CallMethod(const google::protobuf::MethodDescriptor* method,
112
                    google::protobuf::RpcController* controller,
113
                    const google::protobuf::Message* request, google::protobuf::Message* response,
114
1.00M
                    google::protobuf::Closure* done) override {
115
1.00M
        FailureDetectClosure* failure_detect_closure = nullptr;
116
1.00M
        if (done != nullptr) {
117
            // If done == nullptr, then it means the call is sync call, so that should not
118
            // gen a failure detect closure for it. Or it will core.
119
999k
            failure_detect_closure = new FailureDetectClosure(_channel_st, controller, done);
120
999k
        }
121
1.00M
        ::brpc::Channel::CallMethod(method, controller, request, response, failure_detect_closure);
122
        // Done == nullptr, it is a sync call, should also deal with the bad channel.
123
1.00M
        if (done == nullptr) {
124
8.26k
            auto* cntl = static_cast<brpc::Controller*>(controller);
125
8.26k
            if (cntl->Failed() && cntl->ErrorCode() == EHOSTDOWN) {
126
2
                Status error_st = Status::NetworkError(
127
2
                        "Failed to send brpc, error={}, error_text={}, client: {}, latency = {}",
128
2
                        berror(cntl->ErrorCode()), cntl->ErrorText(),
129
2
                        BackendOptions::get_localhost(), cntl->latency_us());
130
2
                LOG(WARNING) << error_st;
131
2
                _channel_st->update(error_st);
132
2
            }
133
8.26k
        }
134
1.00M
    }
135
136
1.04M
    std::shared_ptr<AtomicStatus> channel_status() { return _channel_st; }
137
138
private:
139
    std::shared_ptr<AtomicStatus> _channel_st;
140
};
141
142
template <class T>
143
class BrpcClientCache {
144
public:
145
    BrpcClientCache(std::string protocol = "baidu_std", std::string connection_type = "",
146
                    std::string connection_group = "");
147
    virtual ~BrpcClientCache();
148
149
    std::shared_ptr<T> get_client(const butil::EndPoint& endpoint) {
150
        return get_client(butil::endpoint2str(endpoint).c_str());
151
    }
152
153
#ifdef BE_TEST
154
    virtual std::shared_ptr<T> get_client(const TNetworkAddress& taddr) {
155
        std::string host_port = fmt::format("{}:{}", taddr.hostname, taddr.port);
156
        return get_client(host_port);
157
    }
158
#else
159
20.7k
    std::shared_ptr<T> get_client(const TNetworkAddress& taddr) {
160
20.7k
        return get_client(taddr.hostname, taddr.port);
161
20.7k
    }
162
#endif
163
164
302
    std::shared_ptr<T> get_client(const PNetworkAddress& paddr) {
165
302
        return get_client(paddr.hostname(), paddr.port());
166
302
    }
167
168
1.03M
    std::shared_ptr<T> get_client(const std::string& host, int port) {
169
1.03M
        std::string realhost = host;
170
1.03M
        auto dns_cache = ExecEnv::GetInstance()->dns_cache();
171
1.03M
        if (dns_cache == nullptr) {
172
8
            LOG(WARNING) << "DNS cache is not initialized, skipping hostname resolve";
173
1.03M
        } else if (!is_valid_ip(host)) {
174
0
            Status status = dns_cache->get(host, &realhost);
175
0
            if (!status.ok()) {
176
0
                LOG(WARNING) << "failed to get ip from host:" << status.to_string();
177
                // The hostname is no longer resolvable, which normally means the backend
178
                // was dropped from the cluster. Returning early is not enough: any stub
179
                // cached under this host:port still holds a brpc Channel bound to the last
180
                // resolved (now dead) IP, and brpc keeps health-checking that socket
181
                // forever, which is the source of the endless
182
                // "Fail to wait EPOLLOUT ... Connection timed out" warnings. Drop it here
183
                // so the socket is closed along with the last reference to the stub.
184
0
                _stub_map.erase(fmt::format("{}:{}", host, port));
185
0
                return nullptr;
186
0
            }
187
0
        }
188
189
        // Use original host:port as key (like Java's TNetworkAddress address)
190
        // This allows us to detect IP changes when DNS resolution changes
191
1.03M
        std::string host_port = fmt::format("{}:{}", host, port);
192
193
1.03M
        std::shared_ptr<T> stub_ptr;
194
1.03M
        bool need_remove = false;
195
196
1.04M
        auto check_entry = [&](const auto& v) {
197
1.04M
            const StubEntry<T>& entry = v.second;
198
            // Check if cached IP matches current resolved IP
199
1.04M
            if (entry.real_ip != realhost) {
200
                // IP changed (DNS resolution changed)
201
0
                LOG(WARNING) << "Cached ip changed for " << host << ", before ip: " << entry.real_ip
202
0
                             << ", current ip: " << realhost;
203
0
                need_remove = true;
204
1.04M
            } else if (!static_cast<FailureDetectChannel*>(entry.stub->channel())
205
1.04M
                                ->channel_status()
206
1.04M
                                ->ok()) {
207
                // Client is not in normal state, need to recreate
208
                // At this point we cannot judge the progress of reconnecting the underlying channel.
209
                // In the worst case, it may take two minutes. But we can't stand the connection refused
210
                // for two minutes, so rebuild the channel directly.
211
2
                need_remove = true;
212
1.04M
            } else {
213
                // Cache hit: IP matches and client is healthy
214
1.04M
                stub_ptr = entry.stub;
215
1.04M
            }
216
1.04M
        };
Unexecuted instantiation: _ZZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiENKUlRKT_E_clISt4pairIS9_9StubEntryIS1_EEEEDaSD_
_ZZN5doris15BrpcClientCacheINS_20PBackendService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiENKUlRKT_E_clISt4pairIS9_9StubEntryIS1_EEEEDaSD_
Line
Count
Source
196
1.04M
        auto check_entry = [&](const auto& v) {
197
1.04M
            const StubEntry<T>& entry = v.second;
198
            // Check if cached IP matches current resolved IP
199
1.04M
            if (entry.real_ip != realhost) {
200
                // IP changed (DNS resolution changed)
201
0
                LOG(WARNING) << "Cached ip changed for " << host << ", before ip: " << entry.real_ip
202
0
                             << ", current ip: " << realhost;
203
0
                need_remove = true;
204
1.04M
            } else if (!static_cast<FailureDetectChannel*>(entry.stub->channel())
205
1.04M
                                ->channel_status()
206
1.04M
                                ->ok()) {
207
                // Client is not in normal state, need to recreate
208
                // At this point we cannot judge the progress of reconnecting the underlying channel.
209
                // In the worst case, it may take two minutes. But we can't stand the connection refused
210
                // for two minutes, so rebuild the channel directly.
211
2
                need_remove = true;
212
1.04M
            } else {
213
                // Cache hit: IP matches and client is healthy
214
1.04M
                stub_ptr = entry.stub;
215
1.04M
            }
216
1.04M
        };
217
218
1.04M
        if (LIKELY(_stub_map.if_contains(host_port, check_entry))) {
219
1.04M
            if (stub_ptr != nullptr) {
220
1.04M
                return stub_ptr;
221
1.04M
            }
222
            // IP changed or client unhealthy, need to remove old entry
223
35
            if (need_remove) {
224
2
                _stub_map.erase(host_port);
225
2
            }
226
35
        }
227
228
        // Create new stub using resolved IP for actual connection
229
18.4E
        std::string real_host_port = get_host_port(realhost, port);
230
18.4E
        auto stub = get_new_client_no_cache(real_host_port);
231
18.4E
        if (stub != nullptr) {
232
19
            StubEntry<T> entry {realhost, stub};
233
19
            _stub_map.try_emplace_l(
234
19
                    host_port, [&stub](const auto& v) { stub = v.second.stub; }, entry);
Unexecuted instantiation: _ZZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiENKUlRKT_E0_clISt4pairIS9_9StubEntryIS1_EEEEDaSD_
Unexecuted instantiation: _ZZN5doris15BrpcClientCacheINS_20PBackendService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiENKUlRKT_E0_clISt4pairIS9_9StubEntryIS1_EEEEDaSD_
235
19
        }
236
18.4E
        return stub;
237
1.03M
    }
Unexecuted instantiation: _ZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEi
_ZN5doris15BrpcClientCacheINS_20PBackendService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEi
Line
Count
Source
168
1.03M
    std::shared_ptr<T> get_client(const std::string& host, int port) {
169
1.03M
        std::string realhost = host;
170
1.03M
        auto dns_cache = ExecEnv::GetInstance()->dns_cache();
171
1.03M
        if (dns_cache == nullptr) {
172
8
            LOG(WARNING) << "DNS cache is not initialized, skipping hostname resolve";
173
1.03M
        } else if (!is_valid_ip(host)) {
174
0
            Status status = dns_cache->get(host, &realhost);
175
0
            if (!status.ok()) {
176
0
                LOG(WARNING) << "failed to get ip from host:" << status.to_string();
177
                // The hostname is no longer resolvable, which normally means the backend
178
                // was dropped from the cluster. Returning early is not enough: any stub
179
                // cached under this host:port still holds a brpc Channel bound to the last
180
                // resolved (now dead) IP, and brpc keeps health-checking that socket
181
                // forever, which is the source of the endless
182
                // "Fail to wait EPOLLOUT ... Connection timed out" warnings. Drop it here
183
                // so the socket is closed along with the last reference to the stub.
184
0
                _stub_map.erase(fmt::format("{}:{}", host, port));
185
0
                return nullptr;
186
0
            }
187
0
        }
188
189
        // Use original host:port as key (like Java's TNetworkAddress address)
190
        // This allows us to detect IP changes when DNS resolution changes
191
1.03M
        std::string host_port = fmt::format("{}:{}", host, port);
192
193
1.03M
        std::shared_ptr<T> stub_ptr;
194
1.03M
        bool need_remove = false;
195
196
1.03M
        auto check_entry = [&](const auto& v) {
197
1.03M
            const StubEntry<T>& entry = v.second;
198
            // Check if cached IP matches current resolved IP
199
1.03M
            if (entry.real_ip != realhost) {
200
                // IP changed (DNS resolution changed)
201
1.03M
                LOG(WARNING) << "Cached ip changed for " << host << ", before ip: " << entry.real_ip
202
1.03M
                             << ", current ip: " << realhost;
203
1.03M
                need_remove = true;
204
1.03M
            } else if (!static_cast<FailureDetectChannel*>(entry.stub->channel())
205
1.03M
                                ->channel_status()
206
1.03M
                                ->ok()) {
207
                // Client is not in normal state, need to recreate
208
                // At this point we cannot judge the progress of reconnecting the underlying channel.
209
                // In the worst case, it may take two minutes. But we can't stand the connection refused
210
                // for two minutes, so rebuild the channel directly.
211
1.03M
                need_remove = true;
212
1.03M
            } else {
213
                // Cache hit: IP matches and client is healthy
214
1.03M
                stub_ptr = entry.stub;
215
1.03M
            }
216
1.03M
        };
217
218
1.04M
        if (LIKELY(_stub_map.if_contains(host_port, check_entry))) {
219
1.04M
            if (stub_ptr != nullptr) {
220
1.04M
                return stub_ptr;
221
1.04M
            }
222
            // IP changed or client unhealthy, need to remove old entry
223
35
            if (need_remove) {
224
2
                _stub_map.erase(host_port);
225
2
            }
226
35
        }
227
228
        // Create new stub using resolved IP for actual connection
229
18.4E
        std::string real_host_port = get_host_port(realhost, port);
230
18.4E
        auto stub = get_new_client_no_cache(real_host_port);
231
18.4E
        if (stub != nullptr) {
232
19
            StubEntry<T> entry {realhost, stub};
233
19
            _stub_map.try_emplace_l(
234
19
                    host_port, [&stub](const auto& v) { stub = v.second.stub; }, entry);
235
19
        }
236
18.4E
        return stub;
237
1.03M
    }
238
239
0
    std::shared_ptr<T> get_client(const std::string& host_port) {
240
0
        const auto pos = host_port.rfind(':');
241
0
        std::string host = host_port.substr(0, pos);
242
0
        int port = 0;
243
0
        try {
244
0
            port = stoi(host_port.substr(pos + 1));
245
0
        } catch (const std::exception& err) {
246
0
            LOG(WARNING) << "failed to parse port from " << host_port << ": " << err.what();
247
0
            return nullptr;
248
0
        }
249
0
        return get_client(host, port);
250
0
    }
251
252
    std::shared_ptr<T> get_new_client_no_cache(const std::string& host_port,
253
                                               const std::string& protocol = "",
254
                                               const std::string& connection_type = "",
255
59
                                               const std::string& connection_group = "") {
256
59
        brpc::ChannelOptions options;
257
59
        Status status = doris::client::configure_brpc_channel_options(&options);
258
59
        if (!status.ok()) {
259
0
            throw status;
260
0
        }
261
59
        if (protocol != "") {
262
0
            options.protocol = protocol;
263
59
        } else if (_protocol != "") {
264
59
            options.protocol = _protocol;
265
59
        }
266
59
        if (connection_type != "") {
267
0
            options.connection_type = connection_type;
268
59
        } else if (_connection_type != "") {
269
4
            options.connection_type = _connection_type;
270
4
        }
271
59
        if (connection_group != "") {
272
0
            options.connection_group = connection_group;
273
59
        } else if (_connection_group != "") {
274
4
            options.connection_group = _connection_group;
275
4
        }
276
        // Add random connection id to connection_group to make sure use new socket
277
59
        options.connection_group += std::to_string(_connection_id.fetch_add(1));
278
59
        options.connect_timeout_ms = 2000;
279
59
        options.timeout_ms = 2000;
280
59
        options.max_retry = 10;
281
282
59
        std::unique_ptr<FailureDetectChannel> channel(new FailureDetectChannel());
283
59
        int ret_code = 0;
284
59
        if (host_port.find("://") == std::string::npos) {
285
59
            ret_code = channel->Init(host_port.c_str(), &options);
286
59
        } else {
287
0
            ret_code =
288
0
                    channel->Init(host_port.c_str(), config::rpc_load_balancer.c_str(), &options);
289
0
        }
290
59
        if (ret_code) {
291
1
            LOG(WARNING) << "Failed to initialize brpc Channel to " << host_port;
292
1
            return nullptr;
293
1
        }
294
58
        return std::make_shared<T>(channel.release(), google::protobuf::Service::STUB_OWNS_CHANNEL);
295
59
    }
_ZN5doris15BrpcClientCacheINS_20PBackendService_StubEE23get_new_client_no_cacheERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_SA_SA_
Line
Count
Source
255
59
                                               const std::string& connection_group = "") {
256
59
        brpc::ChannelOptions options;
257
59
        Status status = doris::client::configure_brpc_channel_options(&options);
258
59
        if (!status.ok()) {
259
0
            throw status;
260
0
        }
261
59
        if (protocol != "") {
262
0
            options.protocol = protocol;
263
59
        } else if (_protocol != "") {
264
59
            options.protocol = _protocol;
265
59
        }
266
59
        if (connection_type != "") {
267
0
            options.connection_type = connection_type;
268
59
        } else if (_connection_type != "") {
269
4
            options.connection_type = _connection_type;
270
4
        }
271
59
        if (connection_group != "") {
272
0
            options.connection_group = connection_group;
273
59
        } else if (_connection_group != "") {
274
4
            options.connection_group = _connection_group;
275
4
        }
276
        // Add random connection id to connection_group to make sure use new socket
277
59
        options.connection_group += std::to_string(_connection_id.fetch_add(1));
278
59
        options.connect_timeout_ms = 2000;
279
59
        options.timeout_ms = 2000;
280
59
        options.max_retry = 10;
281
282
59
        std::unique_ptr<FailureDetectChannel> channel(new FailureDetectChannel());
283
59
        int ret_code = 0;
284
59
        if (host_port.find("://") == std::string::npos) {
285
59
            ret_code = channel->Init(host_port.c_str(), &options);
286
59
        } else {
287
0
            ret_code =
288
0
                    channel->Init(host_port.c_str(), config::rpc_load_balancer.c_str(), &options);
289
0
        }
290
59
        if (ret_code) {
291
1
            LOG(WARNING) << "Failed to initialize brpc Channel to " << host_port;
292
1
            return nullptr;
293
1
        }
294
58
        return std::make_shared<T>(channel.release(), google::protobuf::Service::STUB_OWNS_CHANNEL);
295
59
    }
Unexecuted instantiation: _ZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE23get_new_client_no_cacheERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_SA_SA_
296
297
1
    size_t size() { return _stub_map.size(); }
298
299
0
    void clear() { _stub_map.clear(); }
300
301
0
    size_t erase(const std::string& host_port) { return _stub_map.erase(host_port); }
302
303
0
    size_t erase(const std::string& host, int port) {
304
0
        std::string host_port = fmt::format("{}:{}", host, port);
305
0
        return erase(host_port);
306
0
    }
307
308
0
    size_t erase(const butil::EndPoint& endpoint) {
309
0
        return _stub_map.erase(butil::endpoint2str(endpoint).c_str());
310
0
    }
311
312
0
    bool exist(const std::string& host_port) {
313
0
        return _stub_map.find(host_port) != _stub_map.end();
314
0
    }
315
316
0
    void get_all(std::vector<std::string>* endpoints) {
317
0
        for (auto it = _stub_map.begin(); it != _stub_map.end(); ++it) {
318
0
            endpoints->emplace_back(it->first.c_str());
319
0
        }
320
0
    }
321
322
    bool available(std::shared_ptr<T> stub, const butil::EndPoint& endpoint) {
323
        return available(stub, butil::endpoint2str(endpoint).c_str());
324
    }
325
326
1
    bool available(std::shared_ptr<T> stub, const std::string& host_port) {
327
1
        if (!stub) {
328
0
            LOG(WARNING) << "stub is null to: " << host_port;
329
0
            return false;
330
0
        }
331
1
        std::string message = "hello doris!";
332
1
        PHandShakeRequest request;
333
1
        request.set_hello(message);
334
1
        PHandShakeResponse response;
335
1
        brpc::Controller cntl;
336
1
        stub->hand_shake(&cntl, &request, &response, nullptr);
337
1
        if (cntl.Failed()) {
338
1
            LOG(WARNING) << "open brpc connection to " << host_port
339
1
                         << " failed: " << cntl.ErrorText();
340
1
            return false;
341
1
        } else if (response.has_status() && response.has_hello() && response.hello() == message &&
342
0
                   response.status().status_code() == 0) {
343
0
            return true;
344
0
        } else {
345
0
            LOG(WARNING) << "open brpc connection to " << host_port
346
0
                         << " failed: " << response.DebugString();
347
0
            return false;
348
0
        }
349
1
    }
350
351
1
    bool available(std::shared_ptr<T> stub, const std::string& host, int port) {
352
1
        std::string host_port = fmt::format("{}:{}", host, port);
353
1
        return available(stub, host_port);
354
1
    }
355
356
private:
357
    StubMap<T> _stub_map;
358
    const std::string _protocol;
359
    const std::string _connection_type;
360
    const std::string _connection_group;
361
    // use to generate unique connection id for each connection
362
    // to prevent the connection problem of brpc: https://github.com/apache/brpc/issues/2146
363
    std::atomic<int64_t> _connection_id {0};
364
};
365
366
using InternalServiceClientCache = BrpcClientCache<PBackendService_Stub>;
367
using FunctionServiceClientCache = BrpcClientCache<PFunctionService_Stub>;
368
} // namespace doris