polaris-python 0.0.1.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polaris/__init__.py +74 -0
- polaris/_version.py +16 -0
- polaris/api/__init__.py +55 -0
- polaris/api/_log_file.py +232 -0
- polaris/api/config.py +145 -0
- polaris/api/consumer.py +240 -0
- polaris/api/log.py +414 -0
- polaris/api/lossless.py +73 -0
- polaris/api/provider.py +152 -0
- polaris/api/skill.py +162 -0
- polaris/pkg/__init__.py +15 -0
- polaris/pkg/common/__init__.py +15 -0
- polaris/pkg/common/plugin.py +33 -0
- polaris/pkg/config/__init__.py +15 -0
- polaris/pkg/config/api.py +1205 -0
- polaris/pkg/config/default.py +92 -0
- polaris/pkg/config/event.py +76 -0
- polaris/pkg/config/loader.py +392 -0
- polaris/pkg/config/stat_reporter.py +100 -0
- polaris/pkg/flow/__init__.py +15 -0
- polaris/pkg/flow/admin_server.py +104 -0
- polaris/pkg/flow/async_flow.py +55 -0
- polaris/pkg/flow/base_flow.py +111 -0
- polaris/pkg/flow/impl.py +1277 -0
- polaris/pkg/flow/lossless.py +605 -0
- polaris/pkg/flow/notify.py +46 -0
- polaris/pkg/flow/register.py +210 -0
- polaris/pkg/flow/skill.py +110 -0
- polaris/pkg/flow/sync_flow.py +20 -0
- polaris/pkg/model/__init__.py +15 -0
- polaris/pkg/model/context.py +53 -0
- polaris/pkg/model/contract.py +89 -0
- polaris/pkg/model/core.py +51 -0
- polaris/pkg/model/engine.py +83 -0
- polaris/pkg/model/error.py +94 -0
- polaris/pkg/model/event.py +159 -0
- polaris/pkg/model/lossless.py +102 -0
- polaris/pkg/model/rule.py +152 -0
- polaris/pkg/model/server.py +288 -0
- polaris/pkg/model/service.py +820 -0
- polaris/pkg/model/skill.py +179 -0
- polaris/pkg/model/stat.py +168 -0
- polaris/pkg/model/util.py +58 -0
- polaris/pkg/version/__init__.py +18 -0
- polaris/plugin/__init__.py +32 -0
- polaris/plugin/circuit_breaker/__init__.py +19 -0
- polaris/plugin/circuit_breaker/base.py +54 -0
- polaris/plugin/circuit_breaker/composite.py +265 -0
- polaris/plugin/circuit_breaker/config.py +36 -0
- polaris/plugin/event/__init__.py +21 -0
- polaris/plugin/event/pushgateway.py +202 -0
- polaris/plugin/load_balancer/__init__.py +24 -0
- polaris/plugin/load_balancer/base.py +30 -0
- polaris/plugin/load_balancer/random.py +39 -0
- polaris/plugin/load_balancer/weighted_random.py +50 -0
- polaris/plugin/local_registry/__init__.py +18 -0
- polaris/plugin/local_registry/base.py +47 -0
- polaris/plugin/local_registry/file_cache.py +335 -0
- polaris/plugin/local_registry/local_cache.py +433 -0
- polaris/plugin/server_connector/__init__.py +20 -0
- polaris/plugin/server_connector/base.py +104 -0
- polaris/plugin/server_connector/convert.py +149 -0
- polaris/plugin/server_connector/grpc_connector/__init__.py +21 -0
- polaris/plugin/server_connector/grpc_connector/connection.py +401 -0
- polaris/plugin/server_connector/grpc_connector/grpc_client.py +384 -0
- polaris/plugin/server_connector/grpc_connector/tls.py +45 -0
- polaris/plugin/service_router/__init__.py +28 -0
- polaris/plugin/service_router/base.py +105 -0
- polaris/plugin/service_router/isolated.py +35 -0
- polaris/plugin/service_router/metadata.py +84 -0
- polaris/plugin/service_router/recover.py +62 -0
- polaris/plugin/service_router/util.py +54 -0
- polaris/plugin/skill/__init__.py +16 -0
- polaris/plugin/skill/connector.py +127 -0
- polaris/plugin/skill/persistence.py +94 -0
- polaris/plugin/stat_reporter/__init__.py +16 -0
- polaris/plugin/stat_reporter/base.py +34 -0
- polaris/plugin/stat_reporter/prometheus.py +375 -0
- polaris/plugin_manager.py +146 -0
- polaris_python-0.0.1.dev3.dist-info/METADATA +213 -0
- polaris_python-0.0.1.dev3.dist-info/RECORD +84 -0
- polaris_python-0.0.1.dev3.dist-info/WHEEL +5 -0
- polaris_python-0.0.1.dev3.dist-info/licenses/LICENSE +18 -0
- polaris_python-0.0.1.dev3.dist-info/top_level.txt +1 -0
polaris/pkg/flow/impl.py
ADDED
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import copy
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
import traceback
|
|
21
|
+
from collections.abc import Coroutine
|
|
22
|
+
from typing import Any, TypeVar
|
|
23
|
+
|
|
24
|
+
from polarismesh_specification.api.v1.service_manage import response_pb2
|
|
25
|
+
|
|
26
|
+
import polaris.api.log as logger
|
|
27
|
+
from polaris.pkg.config.api import ClusterService, get_server_services
|
|
28
|
+
from polaris.pkg.flow.base_flow import CacheFilter
|
|
29
|
+
from polaris.pkg.flow.base_flow import CacheResult
|
|
30
|
+
from polaris.pkg.flow.base_flow import RealTimeLimitTask
|
|
31
|
+
from polaris.pkg.flow.base_flow import get_load_cache_values
|
|
32
|
+
from polaris.pkg.flow.admin_server import AdminHttpServer
|
|
33
|
+
from polaris.pkg.flow.lossless import LosslessManager, lossless_http_handlers
|
|
34
|
+
from polaris.pkg.flow.register import RegisterStateManager
|
|
35
|
+
from polaris.pkg.model.context import *
|
|
36
|
+
from polaris.pkg.model.core import *
|
|
37
|
+
from polaris.pkg.model.event import SDKEventName, SDKEventType, SDKEventStatus, now_event
|
|
38
|
+
from polaris.pkg.model.lossless import LosslessInstance
|
|
39
|
+
from polaris.pkg.model.engine import BaseEngine
|
|
40
|
+
from polaris.pkg.model.rule import *
|
|
41
|
+
from polaris.pkg.model.server import *
|
|
42
|
+
from polaris.pkg.model.service import *
|
|
43
|
+
from polaris.pkg.model import util
|
|
44
|
+
from polaris.pkg.version import version
|
|
45
|
+
from polaris.plugin.circuit_breaker.composite import CircuitBreakResult
|
|
46
|
+
from polaris.plugin.local_registry.file_cache import LocalFileCache
|
|
47
|
+
from polaris.plugin.local_registry.local_cache import InstanceFilter
|
|
48
|
+
from polaris.plugin.local_registry.local_cache import InstanceProperties
|
|
49
|
+
from polaris.plugin.local_registry.local_cache import ServiceUpdateRequest
|
|
50
|
+
from polaris.plugin.circuit_breaker.base import BaseCircuitBreaker
|
|
51
|
+
from polaris.plugin.load_balancer.base import BaseLoadBalancer
|
|
52
|
+
from polaris.plugin.local_registry.base import BaseLocalRegistry
|
|
53
|
+
from polaris.plugin.server_connector.base import BaseServerConnector
|
|
54
|
+
from polaris.plugin.service_router.base import BaseServiceRouter
|
|
55
|
+
from polaris.plugin.service_router.base import NearbyKeyAreaWithCampus
|
|
56
|
+
from polaris.plugin.service_router.base import NearbyKeyAreaWithRegion
|
|
57
|
+
from polaris.plugin.service_router.base import NearbyKeyAreaWithZone
|
|
58
|
+
from polaris.plugin.service_router.base import RouteInfo
|
|
59
|
+
from polaris.plugin_manager import Configuration
|
|
60
|
+
from polaris.plugin_manager import InitContext
|
|
61
|
+
from polaris.plugin_manager import PluginManager
|
|
62
|
+
from polaris.plugin_manager import PluginType
|
|
63
|
+
from polaris.plugin_manager import setup
|
|
64
|
+
from .notify import *
|
|
65
|
+
from polaris.pkg.model.error import ErrorCode, SDKError, ServerCode
|
|
66
|
+
|
|
67
|
+
# 定时任务时间10s
|
|
68
|
+
UPDATE_INTERVAL = 10
|
|
69
|
+
T = TypeVar("T")
|
|
70
|
+
network_logger = logger.get_logger("network")
|
|
71
|
+
cache_logger = logger.get_logger("cache")
|
|
72
|
+
circuitbreaker_logger = logger.get_logger("circuitbreaker")
|
|
73
|
+
route_logger = logger.get_logger("route")
|
|
74
|
+
stat_report_logger = logger.get_logger("statReport")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# 保存服务上一次的检测周期的map
|
|
78
|
+
keyLastCircuitBreakCheckTimes = "lastCircuitBreakCheckTimes"
|
|
79
|
+
# 保存健康探测上一次周期的map
|
|
80
|
+
keyLastClientReportTimes = "keyLastClientReportTimes"
|
|
81
|
+
# 保存上一次获取discover服务的时间
|
|
82
|
+
keyLastDiscoverServiceCheckTimes = "lastDiscoverServiceCheckTimes"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ServiceUpdateTask:
|
|
86
|
+
def __init__(self, service_key: ServiceEventKey, cache_obj, update_interval: int, last_update_time: int):
|
|
87
|
+
self.service_key = service_key
|
|
88
|
+
self.update_interval = update_interval
|
|
89
|
+
self.last_update_time = last_update_time
|
|
90
|
+
self.cache_obj = cache_obj
|
|
91
|
+
self.runner: asyncio.Task | None = None
|
|
92
|
+
|
|
93
|
+
def need_update(self) -> bool:
|
|
94
|
+
cur_time = int(time.time() * 1000)
|
|
95
|
+
return cur_time >= self.last_update_time + self.update_interval
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class FutureRequest:
|
|
99
|
+
def __init__(self, request: GetOneInstanceRequest, hash_value: int, do_load_balance: bool):
|
|
100
|
+
self.request = request
|
|
101
|
+
self.hash_value = hash_value
|
|
102
|
+
self.do_load_balance = do_load_balance
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AsyncFlowFuture:
|
|
106
|
+
def __init__(self, request: FutureRequest, filter_request: CacheFilter = None, redirect_times: int = 0,
|
|
107
|
+
engine: BaseEngine = None):
|
|
108
|
+
self.request = request
|
|
109
|
+
self.filter_request = filter_request
|
|
110
|
+
self.redirect_times = redirect_times
|
|
111
|
+
self.engine = engine
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class CheckPeriods:
|
|
115
|
+
def __init__(self, circuit_breaker: int = 0, client_report: int = 0,
|
|
116
|
+
discover_service: int = 0):
|
|
117
|
+
self.circuit_breaker = circuit_breaker
|
|
118
|
+
self.client_report = client_report
|
|
119
|
+
self.discover_service = discover_service
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Engine(BaseEngine):
|
|
123
|
+
def __init__(self, config: Configuration = None, connector: BaseServerConnector = None,
|
|
124
|
+
registry: BaseLocalRegistry = None,
|
|
125
|
+
filter_only_router: BaseServiceRouter = None,
|
|
126
|
+
circuit_breaker_chain: Dict[str, BaseCircuitBreaker] = None, load_balancer: BaseLoadBalancer = None,
|
|
127
|
+
max_retry_times: int = 0,
|
|
128
|
+
router_chain: List[BaseServiceRouter] = None, check_periods: CheckPeriods = None,
|
|
129
|
+
before_router_chain: List[BaseServiceRouter] = None,
|
|
130
|
+
after_router_chain: List[BaseServiceRouter] = None,
|
|
131
|
+
default_api_timeout: int = 0,
|
|
132
|
+
svc_load_balancer_chain: Dict[str, BaseLoadBalancer] = None,
|
|
133
|
+
server_service: List[ClusterService] = None,
|
|
134
|
+
svc_to_router_chain: Dict[str, List[BaseServiceRouter]] = None, global_ctx: ValueContext = None,
|
|
135
|
+
file_cache: LocalFileCache = None):
|
|
136
|
+
self.thread: threading.Thread | None = None
|
|
137
|
+
self.loop: asyncio.AbstractEventLoop | None = None
|
|
138
|
+
self._loop_ready = threading.Event()
|
|
139
|
+
self._destroyed = False
|
|
140
|
+
self._destroy_callbacks = []
|
|
141
|
+
self._resources_released = False
|
|
142
|
+
provider_config = config.get_provider() if config is not None else None
|
|
143
|
+
min_register_interval = provider_config.get_min_register_interval() if provider_config else 30000
|
|
144
|
+
self.register_states = RegisterStateManager(
|
|
145
|
+
self._do_register, self._async_heartbeat, min_register_interval,
|
|
146
|
+
)
|
|
147
|
+
self.lossless_manager = LosslessManager(config, None) if config is not None else None
|
|
148
|
+
self._lossless_owned_providers = {}
|
|
149
|
+
self.admin_server = AdminHttpServer(
|
|
150
|
+
config.get_global().get_admin()) if config is not None else None
|
|
151
|
+
# 全局配置
|
|
152
|
+
self.configuration = config
|
|
153
|
+
# 服务端连接器
|
|
154
|
+
self.svc_connector = connector
|
|
155
|
+
# 服务本地缓存
|
|
156
|
+
self.registry = registry
|
|
157
|
+
# 只做过滤的服务器路由插件
|
|
158
|
+
self.filter_only_router = filter_only_router
|
|
159
|
+
# list
|
|
160
|
+
if circuit_breaker_chain is None:
|
|
161
|
+
self.circuit_breaker_chain = {}
|
|
162
|
+
else:
|
|
163
|
+
self.circuit_breaker_chain = circuit_breaker_chain
|
|
164
|
+
# 负载均衡器
|
|
165
|
+
self.load_balancer = load_balancer
|
|
166
|
+
# 最大重试次数
|
|
167
|
+
self.max_retry_times = max_retry_times
|
|
168
|
+
# 定期检查
|
|
169
|
+
if not check_periods:
|
|
170
|
+
self.check_periods = CheckPeriods()
|
|
171
|
+
else:
|
|
172
|
+
self.check_periods = check_periods
|
|
173
|
+
# 默认API超时时间
|
|
174
|
+
self.default_api_timeout = default_api_timeout
|
|
175
|
+
# 服务器级的均衡器
|
|
176
|
+
if svc_load_balancer_chain is None:
|
|
177
|
+
self.svc_load_balancer_chain = {}
|
|
178
|
+
else:
|
|
179
|
+
self.svc_load_balancer_chain = svc_load_balancer_chain
|
|
180
|
+
# 系统服务列表
|
|
181
|
+
if server_service is None:
|
|
182
|
+
self.server_services = []
|
|
183
|
+
else:
|
|
184
|
+
self.server_services = server_service
|
|
185
|
+
# 服务级的路由责任链
|
|
186
|
+
if svc_to_router_chain is None:
|
|
187
|
+
self.svc_to_router_chain = {}
|
|
188
|
+
else:
|
|
189
|
+
self.svc_to_router_chain = svc_to_router_chain
|
|
190
|
+
# 服务路由责任链
|
|
191
|
+
if router_chain is None:
|
|
192
|
+
self.router_chain = []
|
|
193
|
+
else:
|
|
194
|
+
self.router_chain = router_chain
|
|
195
|
+
# Java 对齐的三段路由链
|
|
196
|
+
self.before_router_chain = [] if before_router_chain is None else before_router_chain
|
|
197
|
+
self.after_router_chain = [] if after_router_chain is None else after_router_chain
|
|
198
|
+
self.global_ctx = global_ctx if global_ctx is not None else ValueContext()
|
|
199
|
+
self.file_cache = file_cache
|
|
200
|
+
|
|
201
|
+
# 全局更新任务集
|
|
202
|
+
self.update_task_set = {}
|
|
203
|
+
self._circuit_breaker_timers = {}
|
|
204
|
+
self.skill_flow = None
|
|
205
|
+
self.skill_connector = None
|
|
206
|
+
self.stat_reporter = None
|
|
207
|
+
self.event_reporters = []
|
|
208
|
+
|
|
209
|
+
def set_default_value_for_instances_request(self, req: GetInstancesRequest):
|
|
210
|
+
if req.timeout == 0:
|
|
211
|
+
req.set_timeout(self.default_api_timeout)
|
|
212
|
+
if req.retry_count == 0:
|
|
213
|
+
req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
|
|
214
|
+
return req
|
|
215
|
+
|
|
216
|
+
def set_deafult_value_for_reqisgter_request(self, req: InstanceRegisterRequest):
|
|
217
|
+
if req.timeout == 0:
|
|
218
|
+
req.set_timeout(self.default_api_timeout)
|
|
219
|
+
if req.retry_count == 0:
|
|
220
|
+
req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
|
|
221
|
+
return req
|
|
222
|
+
|
|
223
|
+
def set_default_value_for_deregister_request(self, req: InstanceDeRegisterRequest):
|
|
224
|
+
if req.timeout == 0:
|
|
225
|
+
req.set_timeout(self.default_api_timeout)
|
|
226
|
+
if req.retry_count == 0:
|
|
227
|
+
req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
|
|
228
|
+
return req
|
|
229
|
+
|
|
230
|
+
def set_default_value_for_heartbeat_request(self, req: InstanceHeartbeatRequest):
|
|
231
|
+
if req.timeout == 0:
|
|
232
|
+
req.set_timeout(self.default_api_timeout)
|
|
233
|
+
if req.retry_count == 0:
|
|
234
|
+
req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
|
|
235
|
+
return req
|
|
236
|
+
|
|
237
|
+
def set_default_value_for_rule_request(self, req: GetServiceRuleRequest):
|
|
238
|
+
if req.timeout == 0:
|
|
239
|
+
req.set_timeout(self.default_api_timeout)
|
|
240
|
+
if req.retry_count == 0:
|
|
241
|
+
req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
|
|
242
|
+
return req
|
|
243
|
+
|
|
244
|
+
def get_router_chain(self, svc: ServiceKey):
|
|
245
|
+
chain = self.svc_to_router_chain.get(svc, None)
|
|
246
|
+
if chain:
|
|
247
|
+
return chain
|
|
248
|
+
else:
|
|
249
|
+
return self.router_chain
|
|
250
|
+
|
|
251
|
+
def get_full_router_chain(self, svc: ServiceKey):
|
|
252
|
+
return self.before_router_chain + self.get_router_chain(svc) + self.after_router_chain
|
|
253
|
+
|
|
254
|
+
# 获取负载均衡器
|
|
255
|
+
def get_load_balancer(self, svc: ServiceKey):
|
|
256
|
+
return self.svc_load_balancer_chain.get(svc, self.load_balancer)
|
|
257
|
+
|
|
258
|
+
# 初始化服务端插件
|
|
259
|
+
def init_server_service_plugins(self, plugins: PluginManager):
|
|
260
|
+
router_list = list()
|
|
261
|
+
for router_name in DefaultConf.DefaultServerServiceRouterChain:
|
|
262
|
+
router = plugins.get_instance(PluginType.SERVICE_ROUTER, router_name)
|
|
263
|
+
router_list.append(router)
|
|
264
|
+
|
|
265
|
+
for svc in self.server_services:
|
|
266
|
+
self.svc_to_router_chain[svc.service_key] = router_list
|
|
267
|
+
load_balance = plugins.get_instance(PluginType.LOAD_BALANCE,
|
|
268
|
+
DefaultConf.DefaultServerServiceToLoadBalancer[svc.cluster_type])
|
|
269
|
+
self.svc_load_balancer_chain[svc.service_key] = load_balance
|
|
270
|
+
return True
|
|
271
|
+
|
|
272
|
+
def instance_request_to_cache_filter(self, req: GetInstancesRequest, redirect_service: ServiceMetadata):
|
|
273
|
+
filters = CacheFilter(namespace=req.get_namespace(), service=req.get_service(),
|
|
274
|
+
dest_instance_fileter=InstanceFilter(service=req.get_service(),
|
|
275
|
+
namespace=req.get_namespace(),
|
|
276
|
+
is_internal_request=False))
|
|
277
|
+
if redirect_service:
|
|
278
|
+
filters = build_redirected_filter(filters, redirect_service)
|
|
279
|
+
return filters
|
|
280
|
+
|
|
281
|
+
def create_async_flow_future(self, req: FutureRequest):
|
|
282
|
+
future = AsyncFlowFuture(request=req)
|
|
283
|
+
future.engine = Engine
|
|
284
|
+
return future
|
|
285
|
+
|
|
286
|
+
def start_job_thread(self):
|
|
287
|
+
"""Start the single event loop used by gRPC and background jobs."""
|
|
288
|
+
if self.thread is not None and self.thread.is_alive():
|
|
289
|
+
logger.warning("sdk has already had a alive thread")
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
startup_errors = []
|
|
293
|
+
|
|
294
|
+
def run_jobs():
|
|
295
|
+
loop = asyncio.new_event_loop()
|
|
296
|
+
asyncio.set_event_loop(loop)
|
|
297
|
+
self.loop = loop
|
|
298
|
+
try:
|
|
299
|
+
loop.run_until_complete(self.init_self_ip())
|
|
300
|
+
if self.stat_reporter is not None:
|
|
301
|
+
loop.run_until_complete(self.stat_reporter.start(self))
|
|
302
|
+
if self.admin_server is not None:
|
|
303
|
+
loop.run_until_complete(self.admin_server.start())
|
|
304
|
+
for event_reporter in self.event_reporters:
|
|
305
|
+
loop.run_until_complete(event_reporter.start(self))
|
|
306
|
+
logger.info("start timing update job")
|
|
307
|
+
loop.create_task(self.timing_update_server_task())
|
|
308
|
+
loop.create_task(self.timing_expire_cache_task())
|
|
309
|
+
if self.circuit_breaker_chain:
|
|
310
|
+
logger.info("start circuit break job")
|
|
311
|
+
loop.create_task(self.timing_check_circuit_break_task())
|
|
312
|
+
logger.info("start job of client reporting")
|
|
313
|
+
loop.create_task(self.add_client_report_task())
|
|
314
|
+
if hasattr(self.svc_connector, "start_address_switching"):
|
|
315
|
+
self.svc_connector.start_address_switching()
|
|
316
|
+
self._loop_ready.set()
|
|
317
|
+
loop.run_forever()
|
|
318
|
+
except Exception as error:
|
|
319
|
+
startup_errors.append(error)
|
|
320
|
+
finally:
|
|
321
|
+
if startup_errors:
|
|
322
|
+
self._destroyed = True
|
|
323
|
+
loop.run_until_complete(self._shutdown())
|
|
324
|
+
pending = [task for task in asyncio.all_tasks(loop) if not task.done()]
|
|
325
|
+
for task in pending:
|
|
326
|
+
task.cancel()
|
|
327
|
+
if pending:
|
|
328
|
+
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
|
329
|
+
loop.close()
|
|
330
|
+
self.loop = None
|
|
331
|
+
self._finish_destroy()
|
|
332
|
+
self._loop_ready.set()
|
|
333
|
+
|
|
334
|
+
def async_job_threading():
|
|
335
|
+
with logger.use_context_logger(self.global_ctx.logger):
|
|
336
|
+
run_jobs()
|
|
337
|
+
|
|
338
|
+
self.thread = threading.Thread(target=async_job_threading, daemon=True)
|
|
339
|
+
self.thread.start()
|
|
340
|
+
connect_timeout = self.configuration.get_global().get_server_connector().get_connect_timeout() / 1000
|
|
341
|
+
if not self._loop_ready.wait(timeout=max(5, connect_timeout + 5)):
|
|
342
|
+
raise SDKError(ErrorCode.ErrCodeInvalidStateError, "failed to start SDK event loop")
|
|
343
|
+
if startup_errors:
|
|
344
|
+
raise startup_errors[0]
|
|
345
|
+
|
|
346
|
+
async def init_self_ip(self):
|
|
347
|
+
"""Resolve this context's SDK IP before reporting without changing instance hosts."""
|
|
348
|
+
global_config = self.configuration.get_global()
|
|
349
|
+
api_config = global_config.get_api()
|
|
350
|
+
if api_config.bind_ip or api_config.get_bind_intf():
|
|
351
|
+
return
|
|
352
|
+
api_config.bind_ip_value = ""
|
|
353
|
+
connector = global_config.get_server_connector()
|
|
354
|
+
address = connector.get_addresses()[0]
|
|
355
|
+
try:
|
|
356
|
+
api_config.bind_ip_value = await util.get_local_ip(address, connector.get_connect_timeout())
|
|
357
|
+
except (OSError, ValueError) as error:
|
|
358
|
+
network_logger.warning("failed to resolve SDK bindIP via %s; keeping it empty: %s", address, error)
|
|
359
|
+
|
|
360
|
+
def add_destroy_callback(self, callback):
|
|
361
|
+
"""Release context-owned resources only after this engine has stopped."""
|
|
362
|
+
self._destroy_callbacks.append(callback)
|
|
363
|
+
|
|
364
|
+
def _finish_destroy(self):
|
|
365
|
+
if self._resources_released:
|
|
366
|
+
return
|
|
367
|
+
self._resources_released = True
|
|
368
|
+
if self.skill_flow is not None:
|
|
369
|
+
try:
|
|
370
|
+
self.skill_flow.destroy()
|
|
371
|
+
except Exception:
|
|
372
|
+
cache_logger.error("failed to release Skill backups: %s", traceback.format_exc())
|
|
373
|
+
self.update_task_set.clear()
|
|
374
|
+
for callback in self._destroy_callbacks:
|
|
375
|
+
try:
|
|
376
|
+
callback()
|
|
377
|
+
except Exception:
|
|
378
|
+
with logger.use_context_logger(self.global_ctx.logger):
|
|
379
|
+
logger.error("failed to release context resources: %s", traceback.format_exc())
|
|
380
|
+
self._destroy_callbacks.clear()
|
|
381
|
+
|
|
382
|
+
def _submit(self, coroutine, loop):
|
|
383
|
+
# Bind the task at submission without wrapping an unstarted coroutine.
|
|
384
|
+
with logger.use_context_logger(self.global_ctx.logger):
|
|
385
|
+
return asyncio.run_coroutine_threadsafe(coroutine, loop)
|
|
386
|
+
|
|
387
|
+
async def _run_async(self, coroutine: Coroutine[Any, Any, T]) -> T:
|
|
388
|
+
"""Run a coroutine on the SDK loop from any caller loop."""
|
|
389
|
+
loop = self.loop
|
|
390
|
+
if self._destroyed or loop is None:
|
|
391
|
+
coroutine.close()
|
|
392
|
+
raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK engine has been destroyed")
|
|
393
|
+
try:
|
|
394
|
+
running_loop = asyncio.get_running_loop()
|
|
395
|
+
except RuntimeError:
|
|
396
|
+
running_loop = None
|
|
397
|
+
if running_loop is loop:
|
|
398
|
+
with logger.use_context_logger(self.global_ctx.logger):
|
|
399
|
+
return await coroutine
|
|
400
|
+
try:
|
|
401
|
+
future = self._submit(coroutine, loop)
|
|
402
|
+
except RuntimeError as error:
|
|
403
|
+
coroutine.close()
|
|
404
|
+
raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK event loop is unavailable") from error
|
|
405
|
+
return await asyncio.wrap_future(future)
|
|
406
|
+
|
|
407
|
+
def _run_sync(self, coroutine: Coroutine[Any, Any, T]) -> T:
|
|
408
|
+
"""Block on a coroutine running on the SDK loop."""
|
|
409
|
+
loop = self.loop
|
|
410
|
+
if self._destroyed or loop is None:
|
|
411
|
+
coroutine.close()
|
|
412
|
+
raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK engine has been destroyed")
|
|
413
|
+
if self.thread is not None and threading.current_thread() is self.thread:
|
|
414
|
+
coroutine.close()
|
|
415
|
+
raise SDKError(ErrorCode.ErrCodeInvalidStateError,
|
|
416
|
+
"sync API cannot run on the SDK event loop; use the async_ API")
|
|
417
|
+
try:
|
|
418
|
+
asyncio.get_running_loop()
|
|
419
|
+
except RuntimeError:
|
|
420
|
+
pass
|
|
421
|
+
else:
|
|
422
|
+
coroutine.close()
|
|
423
|
+
raise SDKError(ErrorCode.ErrCodeInvalidStateError,
|
|
424
|
+
"sync API cannot run in an active event loop; use the async_ API")
|
|
425
|
+
try:
|
|
426
|
+
future = self._submit(coroutine, loop)
|
|
427
|
+
except RuntimeError as error:
|
|
428
|
+
coroutine.close()
|
|
429
|
+
raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK event loop is unavailable") from error
|
|
430
|
+
return future.result()
|
|
431
|
+
|
|
432
|
+
async def _shutdown(self):
|
|
433
|
+
current = asyncio.current_task()
|
|
434
|
+
if self.lossless_manager is not None:
|
|
435
|
+
await self.lossless_manager.close()
|
|
436
|
+
for event_reporter in self.event_reporters:
|
|
437
|
+
await event_reporter.close()
|
|
438
|
+
self.event_reporters = []
|
|
439
|
+
pending = [task for task in asyncio.all_tasks() if task is not current and not task.done()]
|
|
440
|
+
for task in pending:
|
|
441
|
+
task.cancel()
|
|
442
|
+
if pending:
|
|
443
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
444
|
+
for handle, _, _ in self._circuit_breaker_timers.values():
|
|
445
|
+
handle.cancel()
|
|
446
|
+
self._circuit_breaker_timers.clear()
|
|
447
|
+
await self.register_states.close()
|
|
448
|
+
if self.stat_reporter is not None:
|
|
449
|
+
await self.stat_reporter.close()
|
|
450
|
+
if self.admin_server is not None:
|
|
451
|
+
await self.admin_server.close()
|
|
452
|
+
close = getattr(self.svc_connector, "close", None)
|
|
453
|
+
if close is not None:
|
|
454
|
+
await close()
|
|
455
|
+
if self.skill_connector is not None:
|
|
456
|
+
await self.skill_connector.close()
|
|
457
|
+
|
|
458
|
+
async def _shutdown_on_loop(self, loop):
|
|
459
|
+
try:
|
|
460
|
+
await self._shutdown()
|
|
461
|
+
finally:
|
|
462
|
+
loop.stop()
|
|
463
|
+
|
|
464
|
+
def destroy(self):
|
|
465
|
+
"""Stop jobs and close the selected server connector."""
|
|
466
|
+
if self._destroyed:
|
|
467
|
+
return
|
|
468
|
+
self._destroyed = True
|
|
469
|
+
loop = self.loop
|
|
470
|
+
if loop is not None and loop.is_running():
|
|
471
|
+
if threading.current_thread() is self.thread:
|
|
472
|
+
loop.create_task(self._shutdown_on_loop(loop))
|
|
473
|
+
return
|
|
474
|
+
try:
|
|
475
|
+
self._submit(self._shutdown(), loop).result()
|
|
476
|
+
except Exception:
|
|
477
|
+
with logger.use_context_logger(self.global_ctx.logger):
|
|
478
|
+
network_logger.error("failed to close server connector: %s", traceback.format_exc())
|
|
479
|
+
finally:
|
|
480
|
+
if not loop.is_closed():
|
|
481
|
+
loop.call_soon_threadsafe(loop.stop)
|
|
482
|
+
if self.thread is not None and self.thread is not threading.current_thread():
|
|
483
|
+
self.thread.join()
|
|
484
|
+
if self.thread is None or not self.thread.is_alive():
|
|
485
|
+
self.loop = None
|
|
486
|
+
self._finish_destroy()
|
|
487
|
+
|
|
488
|
+
def sync_get_instances(self, req: GetInstancesRequest, do_balance: bool = True, load: bool = True):
|
|
489
|
+
"""Return candidates through the SDK event loop."""
|
|
490
|
+
return self._run_sync(self._query_instances(req, "candidates", load))
|
|
491
|
+
|
|
492
|
+
async def async_get_instances(self, req: GetInstancesRequest, do_balance: bool = True,
|
|
493
|
+
load: bool = True) -> CacheResult:
|
|
494
|
+
"""Asynchronously load all instances through the SDK event loop."""
|
|
495
|
+
return await self._run_async(self._query_instances(req, "candidates", load))
|
|
496
|
+
|
|
497
|
+
def sync_get_all_instances(self, req):
|
|
498
|
+
return self._run_sync(self._query_instances(req, "all"))
|
|
499
|
+
|
|
500
|
+
async def async_get_all_instances(self, req):
|
|
501
|
+
return await self._run_async(self._query_instances(req, "all"))
|
|
502
|
+
|
|
503
|
+
def sync_get_healthy_instances(self, req):
|
|
504
|
+
return self._run_sync(self._query_instances(req, "healthy"))
|
|
505
|
+
|
|
506
|
+
async def async_get_healthy_instances(self, req):
|
|
507
|
+
return await self._run_async(self._query_instances(req, "healthy"))
|
|
508
|
+
|
|
509
|
+
def sync_get_services(self):
|
|
510
|
+
"""Return service resources currently held by this context."""
|
|
511
|
+
return self._run_sync(self._async_get_services())
|
|
512
|
+
|
|
513
|
+
async def async_get_services(self):
|
|
514
|
+
"""Return service resources currently held by this context."""
|
|
515
|
+
return await self._run_async(self._async_get_services())
|
|
516
|
+
|
|
517
|
+
async def _async_get_services(self):
|
|
518
|
+
return tuple(self.registry.get_services())
|
|
519
|
+
|
|
520
|
+
async def _query_instances(self, req, mode, load=True):
|
|
521
|
+
from polaris.pkg.model.service import InstancesResponse
|
|
522
|
+
result = await self._async_get_instances(req, load=load)
|
|
523
|
+
if result is None or result.dest_instances is None:
|
|
524
|
+
return None
|
|
525
|
+
snapshot = result.dest_instances
|
|
526
|
+
if mode != "all":
|
|
527
|
+
snapshot = self.filter_instances(req, snapshot, healthy_only=mode == "healthy")
|
|
528
|
+
return InstancesResponse(snapshot, req.flow_id)
|
|
529
|
+
|
|
530
|
+
async def _async_get_instances(self, req: GetInstancesRequest, do_balance: bool = True,
|
|
531
|
+
load: bool = True) -> CacheResult:
|
|
532
|
+
req = self.set_default_value_for_instances_request(req)
|
|
533
|
+
if self._warmup_enabled():
|
|
534
|
+
self.lossless_manager.touch_rules(req.get_namespace(), req.get_service())
|
|
535
|
+
request = self.instance_request_to_cache_filter(req=req, redirect_service=None)
|
|
536
|
+
result = await get_load_cache_values(self.registry, request, load, req.use_discover_cache, req.timeout)
|
|
537
|
+
return result
|
|
538
|
+
|
|
539
|
+
def sync_get_one_instance(self, req: GetOneInstanceRequest) -> Instance:
|
|
540
|
+
"""Load one instance through the SDK event loop."""
|
|
541
|
+
return self._run_sync(self._async_get_one_instance(req))
|
|
542
|
+
|
|
543
|
+
async def async_get_one_instance(self, req: GetOneInstanceRequest) -> Instance:
|
|
544
|
+
"""Asynchronously load one instance through the SDK event loop."""
|
|
545
|
+
return await self._run_async(self._async_get_one_instance(req))
|
|
546
|
+
|
|
547
|
+
async def _async_get_one_instance(self, req: GetOneInstanceRequest) -> Instance:
|
|
548
|
+
instance_request = GetInstancesRequest(flow_id=req.flow_id, service=req.service, namespace=req.namespace,
|
|
549
|
+
timeout=req.timeout, use_discover_cache=req.use_discover_cache,
|
|
550
|
+
exclude_offline_service=req.exclude_offline_service)
|
|
551
|
+
|
|
552
|
+
cache_result = await self._async_get_instances(instance_request)
|
|
553
|
+
|
|
554
|
+
if cache_result is None:
|
|
555
|
+
return None
|
|
556
|
+
|
|
557
|
+
return self.choose_instance(req, cache_result.dest_instances,
|
|
558
|
+
cache_result.source_route_rule, cache_result.dest_route_rule)
|
|
559
|
+
|
|
560
|
+
def choose_instance(self, req, instances, source_route_rule=None, dest_route_rule=None):
|
|
561
|
+
"""Apply the configured router chain and load balancer to an instance snapshot."""
|
|
562
|
+
|
|
563
|
+
service_instances = self.filter_instances(req, instances)
|
|
564
|
+
if self._warmup_enabled():
|
|
565
|
+
service_instances = self.lossless_manager.apply_warmup_weights(service_instances)
|
|
566
|
+
result = self.load_balancer.choose_instance(service_instances)
|
|
567
|
+
if result is not None and self.circuit_breaker_chain:
|
|
568
|
+
status = result.get_circuit_breaker_status()
|
|
569
|
+
if status.get_status() == CBStatusEnum.HalfOpen and not status.try_allocate_request():
|
|
570
|
+
candidates = [node for node in service_instances.instances
|
|
571
|
+
if node is not result and node.is_healthy() and
|
|
572
|
+
node.get_circuit_breaker_status().is_available()]
|
|
573
|
+
while candidates:
|
|
574
|
+
service_instances.instances = candidates
|
|
575
|
+
selected = self.load_balancer.choose_instance(service_instances)
|
|
576
|
+
if selected is None:
|
|
577
|
+
break
|
|
578
|
+
if selected.get_circuit_breaker_status().try_allocate_request():
|
|
579
|
+
return selected
|
|
580
|
+
candidates = [node for node in candidates if node is not selected]
|
|
581
|
+
route_logger.warning("all instances unavailable; returning %s:%d without half-open permit",
|
|
582
|
+
result.get_host(), result.get_port())
|
|
583
|
+
if result is not None:
|
|
584
|
+
route_logger.debug(
|
|
585
|
+
"load balancer selected %s:%d namespace=%s service=%s",
|
|
586
|
+
result.get_host(), result.get_port(), req.namespace, req.service,
|
|
587
|
+
)
|
|
588
|
+
return result
|
|
589
|
+
|
|
590
|
+
def _warmup_enabled(self) -> bool:
|
|
591
|
+
if self.lossless_manager is None:
|
|
592
|
+
return False
|
|
593
|
+
weight_adjust = self.configuration.get_consumer().get_weight_adjust()
|
|
594
|
+
return weight_adjust.is_enable() and "warmup" in weight_adjust.get_chain()
|
|
595
|
+
|
|
596
|
+
def filter_instances(self, req, instances, healthy_only=False):
|
|
597
|
+
"""Filter a copied list without balancing or allocating half-open permits."""
|
|
598
|
+
|
|
599
|
+
service_instances = copy.copy(instances)
|
|
600
|
+
if getattr(req, "exclude_offline_service", False):
|
|
601
|
+
if service_instances.get_service_status() == ServiceStatus.OFFLINE:
|
|
602
|
+
service_instances.instances = []
|
|
603
|
+
service_instances.total_weight = 0
|
|
604
|
+
return service_instances
|
|
605
|
+
|
|
606
|
+
routeInfo = RouteInfo(source_service=req.source_service,
|
|
607
|
+
dest_service=req,
|
|
608
|
+
include_unhealthy_instances=getattr(req, "include_unhealthy_instance", False),
|
|
609
|
+
include_circuitbreak_instances=getattr(req, "include_circuit_break_instances", False),
|
|
610
|
+
canary=req.canary,
|
|
611
|
+
metadata=req.get_metadata(),
|
|
612
|
+
metadata_failover_type=req.get_metadata_failover_type())
|
|
613
|
+
for item in self.get_full_router_chain(ServiceKey(req.namespace, req.service)):
|
|
614
|
+
if (healthy_only or getattr(req, "skip_route_filter", False)) and item in self.router_chain:
|
|
615
|
+
continue
|
|
616
|
+
if not item.enable(routeInfo, service_instances):
|
|
617
|
+
route_logger.debug(
|
|
618
|
+
"router %s skipped namespace=%s service=%s",
|
|
619
|
+
type(item).__name__, req.namespace, req.service,
|
|
620
|
+
)
|
|
621
|
+
continue
|
|
622
|
+
before = len(service_instances.instances)
|
|
623
|
+
filter_result = item.get_filtered_instances(route_info=routeInfo, instances=service_instances)
|
|
624
|
+
if filter_result is None:
|
|
625
|
+
return None
|
|
626
|
+
service_instances.instances = filter_result.instances
|
|
627
|
+
route_logger.debug(
|
|
628
|
+
"router %s namespace=%s service=%s instances=%d->%d",
|
|
629
|
+
type(item).__name__, req.namespace, req.service,
|
|
630
|
+
before, len(service_instances.instances),
|
|
631
|
+
)
|
|
632
|
+
if not service_instances.instances:
|
|
633
|
+
break
|
|
634
|
+
return service_instances
|
|
635
|
+
|
|
636
|
+
async def _skill_call(self, operation, request):
|
|
637
|
+
if self.skill_flow is None:
|
|
638
|
+
from polaris.pkg.flow.skill import SkillFlow
|
|
639
|
+
self.skill_flow = SkillFlow(
|
|
640
|
+
self.skill_connector, self.configuration.get_ai().get_skill().get_server_connector(),
|
|
641
|
+
self.global_ctx.logger.get_logger("cache"),
|
|
642
|
+
)
|
|
643
|
+
return await getattr(self.skill_flow, operation)(request)
|
|
644
|
+
|
|
645
|
+
def sync_skill_call(self, operation, request):
|
|
646
|
+
"""Run a blocking Skill operation on the context's owner loop."""
|
|
647
|
+
return self._run_sync(self._skill_call(operation, request))
|
|
648
|
+
|
|
649
|
+
async def async_skill_call(self, operation, request):
|
|
650
|
+
"""Run a Skill operation on the context's owner loop."""
|
|
651
|
+
return await self._run_async(self._skill_call(operation, request))
|
|
652
|
+
|
|
653
|
+
def sync_contract_call(self, operation, request):
|
|
654
|
+
"""Run a blocking service-contract operation on the context's owner loop."""
|
|
655
|
+
return self._run_sync(getattr(self.svc_connector, operation)(request))
|
|
656
|
+
|
|
657
|
+
async def async_contract_call(self, operation, request):
|
|
658
|
+
"""Run a service-contract operation on the context's owner loop."""
|
|
659
|
+
return await self._run_async(getattr(self.svc_connector, operation)(request))
|
|
660
|
+
|
|
661
|
+
def sync_register(self, req: InstanceRegisterRequest) -> InstanceRegisterResponse:
|
|
662
|
+
"""Register an instance on the SDK event loop."""
|
|
663
|
+
return self._run_sync(self._async_register(req))
|
|
664
|
+
|
|
665
|
+
async def async_register(self, req: InstanceRegisterRequest) -> InstanceRegisterResponse:
|
|
666
|
+
"""Asynchronously register through the same instance lifecycle flow."""
|
|
667
|
+
return await self._run_async(self._async_register(req))
|
|
668
|
+
|
|
669
|
+
async def _do_register(self, req: InstanceRegisterRequest) -> InstanceRegisterResponse:
|
|
670
|
+
return await self.svc_connector.register_instance(req)
|
|
671
|
+
|
|
672
|
+
async def _async_register(self, req: InstanceRegisterRequest) -> InstanceRegisterResponse:
|
|
673
|
+
if req.auto_heartbeat and req.ttl is None:
|
|
674
|
+
req.ttl = DefaultHeartbeatTTL
|
|
675
|
+
req.validate()
|
|
676
|
+
snapshot = copy.deepcopy(req)
|
|
677
|
+
async with self.register_states.operation_lock(snapshot):
|
|
678
|
+
response = await self._do_register(snapshot)
|
|
679
|
+
if self._destroyed:
|
|
680
|
+
raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK engine has been destroyed")
|
|
681
|
+
req.instance_id = snapshot.instance_id = response.instance_id
|
|
682
|
+
if snapshot.auto_heartbeat:
|
|
683
|
+
await self.register_states.put(snapshot)
|
|
684
|
+
return response
|
|
685
|
+
|
|
686
|
+
def sync_deregister(self, req: InstanceDeRegisterRequest) -> None:
|
|
687
|
+
"""Deregister an instance on the SDK event loop."""
|
|
688
|
+
return self._run_sync(self._async_deregister(req))
|
|
689
|
+
|
|
690
|
+
async def async_deregister(self, req: InstanceDeRegisterRequest) -> None:
|
|
691
|
+
"""Asynchronously stop instance tasks and deregister through the SDK loop."""
|
|
692
|
+
return await self._run_async(self._async_deregister(req))
|
|
693
|
+
|
|
694
|
+
async def _async_deregister(self, req: InstanceDeRegisterRequest) -> None:
|
|
695
|
+
async with self.register_states.operation_lock(req):
|
|
696
|
+
state_request = await self.register_states.remove(req)
|
|
697
|
+
outgoing = copy.deepcopy(req)
|
|
698
|
+
if state_request is not None:
|
|
699
|
+
outgoing.instance_id = state_request.instance_id
|
|
700
|
+
await self.svc_connector.deregister_instance(outgoing)
|
|
701
|
+
event_center = self.global_ctx.core_map.get(ContextKeyEventCenter)
|
|
702
|
+
if event_center is not None:
|
|
703
|
+
event_center.publish(now_event(
|
|
704
|
+
self.global_ctx.get_client_id(), SDKEventType.INSTANCE, SDKEventName.INSTANCE_THREAD_END,
|
|
705
|
+
client_ip=self.configuration.get_global().get_api().get_bind_ip(),
|
|
706
|
+
namespace=outgoing.namespace, service=outgoing.service,
|
|
707
|
+
instance_id=outgoing.instance_id, host=outgoing.host, port=outgoing.port,
|
|
708
|
+
))
|
|
709
|
+
|
|
710
|
+
def sync_lossless_register(self, req: InstanceRegisterRequest) -> None:
|
|
711
|
+
"""Run lossless online for one instance on the SDK event loop."""
|
|
712
|
+
return self._run_sync(self._async_lossless_register(req))
|
|
713
|
+
|
|
714
|
+
async def async_lossless_register(self, req: InstanceRegisterRequest) -> None:
|
|
715
|
+
"""Asynchronously run lossless online through the SDK loop."""
|
|
716
|
+
return await self._run_async(self._async_lossless_register(req))
|
|
717
|
+
|
|
718
|
+
async def _async_lossless_register(self, req: InstanceRegisterRequest) -> None:
|
|
719
|
+
instance = LosslessInstance.from_register_request(req)
|
|
720
|
+
self._ensure_lossless_provider(req, instance)
|
|
721
|
+
await self.lossless_manager.lossless_register(instance)
|
|
722
|
+
|
|
723
|
+
def sync_lossless_deregister(self, req) -> None:
|
|
724
|
+
"""Run lossless offline for one instance on the SDK event loop."""
|
|
725
|
+
return self._run_sync(self._async_lossless_deregister(req))
|
|
726
|
+
|
|
727
|
+
async def async_lossless_deregister(self, req) -> None:
|
|
728
|
+
"""Asynchronously run lossless offline through the SDK loop."""
|
|
729
|
+
return await self._run_async(self._async_lossless_deregister(req))
|
|
730
|
+
|
|
731
|
+
async def _async_lossless_deregister(self, req) -> None:
|
|
732
|
+
instance = self._lossless_instance_for(req)
|
|
733
|
+
if self.lossless_manager.get_action_provider(instance) is None:
|
|
734
|
+
self._ensure_lossless_provider(req, instance)
|
|
735
|
+
await self.lossless_manager.lossless_deregister(instance)
|
|
736
|
+
|
|
737
|
+
@staticmethod
|
|
738
|
+
def _lossless_instance_for(req) -> LosslessInstance:
|
|
739
|
+
metadata = dict(getattr(req, "metadata", {}) or {})
|
|
740
|
+
return LosslessInstance(
|
|
741
|
+
namespace=req.namespace, service=req.service,
|
|
742
|
+
host=req.host, port=req.port, metadata=metadata,
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
def _ensure_lossless_provider(self, req: InstanceRegisterRequest, instance: LosslessInstance) -> None:
|
|
746
|
+
from polaris.pkg.flow.lossless import HttpLosslessActionProvider
|
|
747
|
+
|
|
748
|
+
async def register_action(_properties):
|
|
749
|
+
await self._async_register(req)
|
|
750
|
+
|
|
751
|
+
async def deregister_action():
|
|
752
|
+
deregister = InstanceDeRegisterRequest(
|
|
753
|
+
service=req.service, service_token=req.service_token, namespace=req.namespace,
|
|
754
|
+
host=req.host, port=req.port, instance_id=getattr(req, "instance_id", ""),
|
|
755
|
+
timeout=self.default_api_timeout,
|
|
756
|
+
retry_count=self.configuration.get_global().get_api().get_max_retry_times(),
|
|
757
|
+
)
|
|
758
|
+
await self._async_deregister(deregister)
|
|
759
|
+
|
|
760
|
+
manager = self.lossless_manager
|
|
761
|
+
existing = manager.get_action_provider(instance)
|
|
762
|
+
if existing is not None and self._lossless_owned_providers.get(instance) is not existing:
|
|
763
|
+
return
|
|
764
|
+
provider = HttpLosslessActionProvider(
|
|
765
|
+
instance, register_action, deregister_action,
|
|
766
|
+
strategy_resolver=lambda: manager.cached_options(instance).strategy,
|
|
767
|
+
health_check_path_resolver=lambda: manager.cached_options(instance).health_check_path,
|
|
768
|
+
)
|
|
769
|
+
manager.set_action_provider(instance, provider)
|
|
770
|
+
self._lossless_owned_providers[instance] = provider
|
|
771
|
+
|
|
772
|
+
def sync_heartbeat(self, req: InstanceHeartbeatRequest) -> None:
|
|
773
|
+
"""Send an instance heartbeat on the SDK event loop."""
|
|
774
|
+
return self._run_sync(self._async_heartbeat(req))
|
|
775
|
+
|
|
776
|
+
async def async_heartbeat(self, req: InstanceHeartbeatRequest) -> None:
|
|
777
|
+
"""Asynchronously send an explicit heartbeat through the SDK loop."""
|
|
778
|
+
return await self._run_async(self._async_heartbeat(req))
|
|
779
|
+
|
|
780
|
+
async def _async_heartbeat(self, req: InstanceHeartbeatRequest) -> None:
|
|
781
|
+
return await self.svc_connector.heartbeat(req)
|
|
782
|
+
|
|
783
|
+
def update_location(self, region: str, zone: str, campus: str):
|
|
784
|
+
self.global_ctx.set_value(NearbyKeyAreaWithCampus, campus)
|
|
785
|
+
self.global_ctx.set_value(NearbyKeyAreaWithRegion, region)
|
|
786
|
+
self.global_ctx.set_value(NearbyKeyAreaWithZone, zone)
|
|
787
|
+
|
|
788
|
+
def do_circuit_break_for_service(self, svc: ServiceKey, svc_instances: ServiceInstances, cb_name: str = ""):
|
|
789
|
+
"""
|
|
790
|
+
对服务进行熔断判断操作
|
|
791
|
+
:param cb_name:
|
|
792
|
+
:param svc:
|
|
793
|
+
:param svc_instances:
|
|
794
|
+
:return:
|
|
795
|
+
"""
|
|
796
|
+
|
|
797
|
+
request = ServiceUpdateRequest()
|
|
798
|
+
request.namespace = svc_instances.get_namespace()
|
|
799
|
+
request.service = svc_instances.get_service()
|
|
800
|
+
checked = []
|
|
801
|
+
transitions = []
|
|
802
|
+
for name, cb in self.circuit_breaker_chain.items():
|
|
803
|
+
if cb_name and name != cb_name:
|
|
804
|
+
continue
|
|
805
|
+
result = cb.timing_circuit_break(svc_instances.instances)
|
|
806
|
+
for target, nodes in ((CBStatusEnum.Open, result.instance_2_open),
|
|
807
|
+
(CBStatusEnum.Closed, result.instance_2_close),
|
|
808
|
+
(CBStatusEnum.HalfOpen, result.instance_2_halfopen)):
|
|
809
|
+
for node in nodes:
|
|
810
|
+
previous_status = node.get_circuit_breaker_status().get_status()
|
|
811
|
+
properties = InstanceProperties()
|
|
812
|
+
properties.service = svc
|
|
813
|
+
properties.ID = node.get_id()
|
|
814
|
+
allowance = cb.get_half_open_request_count() if target == CBStatusEnum.HalfOpen else 0
|
|
815
|
+
properties.properties = {"CircuitBreakerStatus": CircuitBreakerStatus(
|
|
816
|
+
status=target, ts=result.now, allowed_requests_after_half_open=allowance)}
|
|
817
|
+
request.properties.append(properties)
|
|
818
|
+
transitions.append((node, target, previous_status))
|
|
819
|
+
cb.on_state_change(node)
|
|
820
|
+
checked.append((name, cb))
|
|
821
|
+
if request.properties:
|
|
822
|
+
self.registry.update_instances(request)
|
|
823
|
+
event_center = self.global_ctx.core_map.get(ContextKeyEventCenter)
|
|
824
|
+
if event_center is not None:
|
|
825
|
+
status_names = {
|
|
826
|
+
CBStatusEnum.Open: SDKEventStatus.OPEN,
|
|
827
|
+
CBStatusEnum.HalfOpen: SDKEventStatus.HALF_OPEN,
|
|
828
|
+
CBStatusEnum.Closed: SDKEventStatus.CLOSE,
|
|
829
|
+
}
|
|
830
|
+
event_names = {
|
|
831
|
+
CBStatusEnum.Open: SDKEventName.CIRCUIT_BREAKER_OPEN,
|
|
832
|
+
CBStatusEnum.HalfOpen: SDKEventName.CIRCUIT_BREAKER_HALF_OPEN,
|
|
833
|
+
CBStatusEnum.Closed: SDKEventName.CIRCUIT_BREAKER_CLOSE,
|
|
834
|
+
}
|
|
835
|
+
for node, target, previous_status in transitions:
|
|
836
|
+
host = node.get_host()
|
|
837
|
+
port = node.get_port()
|
|
838
|
+
event_center.publish(now_event(
|
|
839
|
+
self.global_ctx.get_client_id(), SDKEventType.CIRCUIT_BREAKING, event_names[target],
|
|
840
|
+
client_ip=self.configuration.get_global().get_api().get_bind_ip(),
|
|
841
|
+
namespace=node.get_namespace(), service=node.get_service(),
|
|
842
|
+
instance_id=node.get_id(), host=host, port=port,
|
|
843
|
+
previous_status=status_names.get(previous_status, ""), current_status=status_names[target],
|
|
844
|
+
rule_name="", additional={"isolation_object": f"{host}:{port}"},
|
|
845
|
+
))
|
|
846
|
+
for name, cb in checked:
|
|
847
|
+
for node in svc_instances.instances:
|
|
848
|
+
self._schedule_circuit_break_check(node, name, cb)
|
|
849
|
+
|
|
850
|
+
def _schedule_circuit_break_check(self, instance, cb_name, cb):
|
|
851
|
+
if self.loop is None or self._destroyed:
|
|
852
|
+
return
|
|
853
|
+
key = (instance.get_namespace(), instance.get_service(), instance.get_id(), cb_name)
|
|
854
|
+
due = cb.next_check_time(instance)
|
|
855
|
+
existing = self._circuit_breaker_timers.get(key)
|
|
856
|
+
if existing is not None:
|
|
857
|
+
handle, local_value, scheduled_due = existing
|
|
858
|
+
if (due is not None and due == scheduled_due and local_value is instance.get_local_value()
|
|
859
|
+
and not handle.cancelled()):
|
|
860
|
+
return
|
|
861
|
+
handle.cancel()
|
|
862
|
+
self._circuit_breaker_timers.pop(key, None)
|
|
863
|
+
if due is None:
|
|
864
|
+
return
|
|
865
|
+
local_value = instance.get_local_value()
|
|
866
|
+
|
|
867
|
+
def check():
|
|
868
|
+
self._circuit_breaker_timers.pop(key, None)
|
|
869
|
+
if self._destroyed:
|
|
870
|
+
return
|
|
871
|
+
try:
|
|
872
|
+
cached = self.registry.get_instances(InstanceFilter(
|
|
873
|
+
namespace=key[0], service=key[1], is_internal_request=True))
|
|
874
|
+
if cached is None or cached.get_instance_local_value(key[2]) is not local_value:
|
|
875
|
+
return
|
|
876
|
+
self.do_circuit_break_for_service(ServiceKey(key[0], key[1]), cached, cb_name)
|
|
877
|
+
except Exception:
|
|
878
|
+
circuitbreaker_logger.error("failed to check instance circuit breaker: %s", traceback.format_exc())
|
|
879
|
+
|
|
880
|
+
handle = self.loop.call_later(max(0, due - cb.now()) / 1000, check)
|
|
881
|
+
self._circuit_breaker_timers[key] = (handle, local_value, due)
|
|
882
|
+
|
|
883
|
+
def _prune_circuit_breaker_timers(self, service_key, instances=None):
|
|
884
|
+
for key, (handle, local_value, _) in list(self._circuit_breaker_timers.items()):
|
|
885
|
+
if key[:2] != (service_key.namespace, service_key.service):
|
|
886
|
+
continue
|
|
887
|
+
if instances is not None and instances.get_instance_local_value(key[2]) is local_value:
|
|
888
|
+
continue
|
|
889
|
+
handle.cancel()
|
|
890
|
+
self._circuit_breaker_timers.pop(key, None)
|
|
891
|
+
|
|
892
|
+
async def process_real_time_task(self, task_data: RealTimeLimitTask):
|
|
893
|
+
service_key = task_data.get_service_key()
|
|
894
|
+
svr_instances = self.registry.get_instances(
|
|
895
|
+
InstanceFilter(service=service_key.get_service(), namespace=service_key.get_namespace(),
|
|
896
|
+
is_internal_request=True))
|
|
897
|
+
if svr_instances is None or not svr_instances.is_initialized():
|
|
898
|
+
return None
|
|
899
|
+
self.do_circuit_break_for_service(task_data.get_service_key(), svr_instances, task_data.get_cb_name())
|
|
900
|
+
# await self.do_period_task(task_data.get_service_key(), svr_instances)
|
|
901
|
+
return None
|
|
902
|
+
|
|
903
|
+
@staticmethod
|
|
904
|
+
def get_now_ts():
|
|
905
|
+
return int(time.time() * 1000)
|
|
906
|
+
|
|
907
|
+
# 获取服务规则
|
|
908
|
+
async def sync_get_service_rule(self, event_type: int, req: GetServiceRuleRequest) -> ServiceRuleResponse:
|
|
909
|
+
"""Compatibility entry used by the asynchronous public API."""
|
|
910
|
+
return await self._run_async(self._get_service_rule(event_type, req))
|
|
911
|
+
|
|
912
|
+
async def _get_service_rule(self, event_type: int, req: GetServiceRuleRequest) -> ServiceRuleResponse:
|
|
913
|
+
"""Reject non-instance resources until those caches are implemented."""
|
|
914
|
+
del event_type, req
|
|
915
|
+
raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument, "only instance resources are cached")
|
|
916
|
+
|
|
917
|
+
def sync_update_service_call_result(self, req: ServiceCallResult):
|
|
918
|
+
return self._run_sync(self._update_service_call_result(req))
|
|
919
|
+
|
|
920
|
+
async def async_update_service_call_result(self, req: ServiceCallResult):
|
|
921
|
+
return await self._run_async(self._update_service_call_result(req))
|
|
922
|
+
|
|
923
|
+
async def _update_service_call_result(self, req: ServiceCallResult):
|
|
924
|
+
if self.stat_reporter is not None:
|
|
925
|
+
self.stat_reporter.report(req)
|
|
926
|
+
if not self.circuit_breaker_chain:
|
|
927
|
+
return None
|
|
928
|
+
if req.called_instance is None or req.ret_code is None:
|
|
929
|
+
return None
|
|
930
|
+
svc = ServiceKey(req.get_namespace(), req.get_service())
|
|
931
|
+
cached = self.registry.get_instances(InstanceFilter(
|
|
932
|
+
namespace=svc.namespace, service=svc.service, is_internal_request=True))
|
|
933
|
+
if cached is None or cached.get_instance_local_value(req.get_id()) is not req.called_instance.get_local_value():
|
|
934
|
+
return None
|
|
935
|
+
instance = cached.instances_map[req.get_id()]
|
|
936
|
+
for cb_name, cb in self.circuit_breaker_chain.items():
|
|
937
|
+
ret = cb.realtime_circuit_break(instance, req.get_ret_code(), req.get_delay())
|
|
938
|
+
if ret:
|
|
939
|
+
self.do_circuit_break_for_service(svc, cached, cb_name)
|
|
940
|
+
else:
|
|
941
|
+
self._schedule_circuit_break_check(instance, cb_name, cb)
|
|
942
|
+
|
|
943
|
+
async def timing_update_server_task(self):
|
|
944
|
+
while True:
|
|
945
|
+
try:
|
|
946
|
+
network_logger.debug("begin to update server info")
|
|
947
|
+
if not self.update_task_set:
|
|
948
|
+
continue
|
|
949
|
+
local_update_task_set = self.update_task_set.copy()
|
|
950
|
+
for service_key, update_task in local_update_task_set.items():
|
|
951
|
+
if update_task.cache_obj.loading or not update_task.need_update():
|
|
952
|
+
continue
|
|
953
|
+
if update_task.runner is None or update_task.runner.done():
|
|
954
|
+
update_task.runner = asyncio.create_task(self._run_service_update_task(update_task))
|
|
955
|
+
except Exception as exception:
|
|
956
|
+
network_logger.error("receive exception when doing periodic server info updating %s", traceback.format_exc())
|
|
957
|
+
finally:
|
|
958
|
+
await asyncio.sleep(self.configuration.get_consumer().local_cache.service_refresh_interval / 1000)
|
|
959
|
+
|
|
960
|
+
async def _run_service_update_task(self, task):
|
|
961
|
+
try:
|
|
962
|
+
await self.process_update_task(task)
|
|
963
|
+
except Exception:
|
|
964
|
+
cache_logger.error("failed to refresh resource %s: %s", task.service_key.__dict__, traceback.format_exc())
|
|
965
|
+
|
|
966
|
+
async def timing_expire_cache_task(self):
|
|
967
|
+
"""Periodically reclaim idle resources independently of pending discovery reads."""
|
|
968
|
+
expire_time = self.configuration.get_consumer().get_local_cache().get_service_expire_time()
|
|
969
|
+
check_interval = min(expire_time / 2, 60 * 60 * 1000) / 1000
|
|
970
|
+
while True:
|
|
971
|
+
await asyncio.sleep(check_interval)
|
|
972
|
+
try:
|
|
973
|
+
await self.registry.expire_services()
|
|
974
|
+
except Exception:
|
|
975
|
+
cache_logger.error("failed to evict idle cache resources: %s", traceback.format_exc())
|
|
976
|
+
|
|
977
|
+
async def stop_service_update(self, service_key, cache_obj):
|
|
978
|
+
"""Stop the old resource generation before closing its discovery stream."""
|
|
979
|
+
if service_key.event_type == EventType.Instance:
|
|
980
|
+
self._prune_circuit_breaker_timers(service_key)
|
|
981
|
+
task = self.update_task_set.get(service_key)
|
|
982
|
+
if task is not None and task.cache_obj is cache_obj:
|
|
983
|
+
self.update_task_set.pop(service_key)
|
|
984
|
+
if task.runner is not None and task.runner is not asyncio.current_task() and not task.runner.done():
|
|
985
|
+
task.runner.cancel()
|
|
986
|
+
await asyncio.gather(task.runner, return_exceptions=True)
|
|
987
|
+
request = InstanceDiscoverRequest(1, service_key.service, service_key.namespace)
|
|
988
|
+
try:
|
|
989
|
+
await self.svc_connector.stop_discover(request)
|
|
990
|
+
finally:
|
|
991
|
+
await self.file_cache.delete(request)
|
|
992
|
+
|
|
993
|
+
def _service_update_active(self, task):
|
|
994
|
+
return self.update_task_set.get(task.service_key) is task and not task.cache_obj.invalid
|
|
995
|
+
|
|
996
|
+
async def timing_check_circuit_break_task(self):
|
|
997
|
+
while True:
|
|
998
|
+
try:
|
|
999
|
+
circuitbreaker_logger.debug("begin to do circuit break task")
|
|
1000
|
+
services = self.registry.get_services()
|
|
1001
|
+
for service_key, v in services.items():
|
|
1002
|
+
if service_key.event_type != EventType.Instance:
|
|
1003
|
+
continue
|
|
1004
|
+
svr_instances = self.registry.get_instances(
|
|
1005
|
+
InstanceFilter(service=service_key.get_service(), namespace=service_key.get_namespace(),
|
|
1006
|
+
is_internal_request=True))
|
|
1007
|
+
if svr_instances is None or not svr_instances.is_initialized():
|
|
1008
|
+
continue
|
|
1009
|
+
self.do_circuit_break_for_service(service_key, svr_instances)
|
|
1010
|
+
except Exception as e:
|
|
1011
|
+
circuitbreaker_logger.error("receive exception when doing circuit break checking %s", traceback.format_exc())
|
|
1012
|
+
finally:
|
|
1013
|
+
await asyncio.sleep(self.configuration.get_consumer().circuit_breaker.check_period / 1000)
|
|
1014
|
+
|
|
1015
|
+
async def process_update_task(self, task: ServiceUpdateTask):
|
|
1016
|
+
if not self._service_update_active(task) or task.cache_obj.loading or not task.need_update():
|
|
1017
|
+
return
|
|
1018
|
+
|
|
1019
|
+
revision = self.registry.get_service_revision(task.service_key.service, task.service_key.namespace,
|
|
1020
|
+
task.service_key.event_type)
|
|
1021
|
+
if task.service_key.event_type != EventType.Instance:
|
|
1022
|
+
raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
|
|
1023
|
+
"only instance resources are cached")
|
|
1024
|
+
request = InstanceDiscoverRequest(type=1, name=task.service_key.service,
|
|
1025
|
+
namespace=task.service_key.namespace, revision=revision)
|
|
1026
|
+
|
|
1027
|
+
client = self.svc_connector
|
|
1028
|
+
|
|
1029
|
+
def get_local_value_func(ins):
|
|
1030
|
+
cached = task.cache_obj.value
|
|
1031
|
+
local_value = cached.get_instance_local_value(ins.get_id()) if cached is not None else None
|
|
1032
|
+
if local_value is None:
|
|
1033
|
+
return DefaultInstanceLocalValue()
|
|
1034
|
+
else:
|
|
1035
|
+
return local_value
|
|
1036
|
+
|
|
1037
|
+
for _ in range(self.configuration.get_global().get_api().max_retry_times):
|
|
1038
|
+
if not self._service_update_active(task):
|
|
1039
|
+
return
|
|
1040
|
+
try:
|
|
1041
|
+
ret = await client.discover(request)
|
|
1042
|
+
if not self._service_update_active(task):
|
|
1043
|
+
return
|
|
1044
|
+
|
|
1045
|
+
service_ins = ServiceInstances.new_from_discover_rsp(ret, get_local_value_func)
|
|
1046
|
+
if (task.cache_obj.value is not None and not task.cache_obj.value.loaded_from_file
|
|
1047
|
+
and task.cache_obj.value.revision == service_ins.revision):
|
|
1048
|
+
task.last_update_time = int(time.time() * 1000)
|
|
1049
|
+
break
|
|
1050
|
+
self._prune_circuit_breaker_timers(task.service_key, service_ins)
|
|
1051
|
+
cache_logger.debug("get instance update %s %s %s", task.service_key.__dict__,
|
|
1052
|
+
len(service_ins.instances),
|
|
1053
|
+
service_ins.filtered_instances.__dict__)
|
|
1054
|
+
|
|
1055
|
+
# for k, v in self.registry.service_map.items():
|
|
1056
|
+
# print(k.service, v.value.instances)
|
|
1057
|
+
update_ret = self.registry.update_service(task.service_key, service_ins)
|
|
1058
|
+
if update_ret is False:
|
|
1059
|
+
cache_logger.error("Failed to update service %s", task.service_key.__dict__)
|
|
1060
|
+
break
|
|
1061
|
+
self.file_cache.save(request, ret)
|
|
1062
|
+
task.last_update_time = int(time.time() * 1000)
|
|
1063
|
+
|
|
1064
|
+
except SDKError as err:
|
|
1065
|
+
if not self._service_update_active(task):
|
|
1066
|
+
return
|
|
1067
|
+
if err.server_code == ServerCode.NotFoundService:
|
|
1068
|
+
async with task.cache_obj.load_lock:
|
|
1069
|
+
await self.registry.remove_service(task.service_key, task.cache_obj)
|
|
1070
|
+
return
|
|
1071
|
+
if err.err_code == ErrorCode.ErrCodeDataNotChange:
|
|
1072
|
+
task.cache_obj.remote_updated = True
|
|
1073
|
+
task.last_update_time = int(time.time() * 1000)
|
|
1074
|
+
cache_logger.debug("Data not change for updating %s", task.service_key.__dict__)
|
|
1075
|
+
break
|
|
1076
|
+
cache_logger.error("Failed to discover %s in periodic updating task because %s", task.service_key.__dict__,
|
|
1077
|
+
repr(err))
|
|
1078
|
+
continue
|
|
1079
|
+
else:
|
|
1080
|
+
cache_logger.info(
|
|
1081
|
+
"cache snapshot updated namespace=%s service=%s revision=%s instances=%d",
|
|
1082
|
+
task.service_key.namespace, task.service_key.service,
|
|
1083
|
+
service_ins.revision, len(service_ins.instances),
|
|
1084
|
+
)
|
|
1085
|
+
break
|
|
1086
|
+
return
|
|
1087
|
+
|
|
1088
|
+
def load_local_client_report_result(self):
|
|
1089
|
+
resp = response_pb2.Response()
|
|
1090
|
+
# TODO load from local cache file
|
|
1091
|
+
|
|
1092
|
+
async def add_client_report_task(self):
|
|
1093
|
+
values = ValueContext()
|
|
1094
|
+
while True:
|
|
1095
|
+
try:
|
|
1096
|
+
await self.do_period_client_report(values)
|
|
1097
|
+
except Exception as exception:
|
|
1098
|
+
stat_report_logger.error("receive exception when add client report task %s", traceback.format_exc())
|
|
1099
|
+
finally:
|
|
1100
|
+
await asyncio.sleep(UPDATE_INTERVAL)
|
|
1101
|
+
|
|
1102
|
+
async def do_period_client_report(self, ctx_values: ValueContext):
|
|
1103
|
+
now = time.time()
|
|
1104
|
+
if keyLastClientReportTimes in ctx_values.core_map:
|
|
1105
|
+
last_report_time_value = ctx_values.get_value(keyLastClientReportTimes)
|
|
1106
|
+
if (now - last_report_time_value) * 1000 < self.check_periods.client_report:
|
|
1107
|
+
return
|
|
1108
|
+
ctx_values.set_value(keyLastClientReportTimes, now)
|
|
1109
|
+
api_config = self.configuration.get_global().get_api()
|
|
1110
|
+
client_host = api_config.get_bind_ip()
|
|
1111
|
+
|
|
1112
|
+
report_client_req = ReportClientRequest(
|
|
1113
|
+
version=version,
|
|
1114
|
+
client_id=self.global_ctx.get_client_id(),
|
|
1115
|
+
timeout=self.default_api_timeout,
|
|
1116
|
+
# TODO
|
|
1117
|
+
persist_handler=None
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
if len(client_host) > 0:
|
|
1121
|
+
report_client_req.host = client_host
|
|
1122
|
+
|
|
1123
|
+
if self.stat_reporter is not None:
|
|
1124
|
+
info = self.stat_reporter.meta_info()
|
|
1125
|
+
if info is not None:
|
|
1126
|
+
report_client_req.reporter_meta_infos = [info]
|
|
1127
|
+
|
|
1128
|
+
report_client_resp = await self.svc_connector.report_client(report_client_req)
|
|
1129
|
+
|
|
1130
|
+
self.update_location(
|
|
1131
|
+
region=report_client_resp.region,
|
|
1132
|
+
zone=report_client_resp.zone,
|
|
1133
|
+
campus=report_client_resp.campus
|
|
1134
|
+
)
|
|
1135
|
+
|
|
1136
|
+
def build_instance_property(now, allowed_request: int, request: ServiceUpdateRequest, cb_name: str):
|
|
1137
|
+
property = InstanceProperties()
|
|
1138
|
+
property.set_id()
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
def build_redirected_filter(filters: CacheFilter, redirect_service: ServiceMetadata):
|
|
1142
|
+
filters.service = redirect_service.get_service()
|
|
1143
|
+
filters.namespace = redirect_service.get_namespace()
|
|
1144
|
+
filters.desc_instances_filter.service = redirect_service.get_service()
|
|
1145
|
+
filters.desc_instances_filter.namespace = redirect_service.get_namespace()
|
|
1146
|
+
return filters
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
def build_service_update_request(svc: ServiceKey, results: Dict[str, CircuitBreakResult]):
|
|
1150
|
+
request = ServiceUpdateRequest(namespace=svc.get_namespace(), service=svc.get_service())
|
|
1151
|
+
for cb_name, result in results.items():
|
|
1152
|
+
build_instance_property(result.instance_2_close, result)
|
|
1153
|
+
|
|
1154
|
+
return None
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
def create_flow_engine(config: Configuration, plugins: PluginManager, global_ctx: ValueContext) -> Engine:
|
|
1158
|
+
if global_ctx is None:
|
|
1159
|
+
global_ctx = ValueContext()
|
|
1160
|
+
|
|
1161
|
+
logger.info("polaris engine initializing clientId=%s version=%s",
|
|
1162
|
+
global_ctx.get_client_id(), version)
|
|
1163
|
+
setup(InitContext(value_ctx=global_ctx, config=config, plugins=plugins))
|
|
1164
|
+
|
|
1165
|
+
flow_engine = Engine(config=config, default_api_timeout=config.get_global().get_api().get_timeout(),
|
|
1166
|
+
global_ctx=global_ctx, file_cache=plugins.get_instance(PluginType.LOCAL_CACHE, "file"),
|
|
1167
|
+
server_service=list(get_server_services(config).values()))
|
|
1168
|
+
try:
|
|
1169
|
+
return _initialize_flow_engine(flow_engine, config, plugins, global_ctx)
|
|
1170
|
+
except Exception:
|
|
1171
|
+
flow_engine.destroy()
|
|
1172
|
+
raise
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
def _initialize_flow_engine(flow_engine, config, plugins, global_ctx):
|
|
1176
|
+
protocol = config.get_global().get_server_connector().get_protocol()
|
|
1177
|
+
|
|
1178
|
+
# 加载服务端连接器
|
|
1179
|
+
target_plugin = plugins.get_instance(PluginType.SERVER_CONNECTOR, protocol)
|
|
1180
|
+
flow_engine.svc_connector = target_plugin
|
|
1181
|
+
if flow_engine.lossless_manager is not None:
|
|
1182
|
+
flow_engine.lossless_manager._connector = target_plugin
|
|
1183
|
+
lossless_config = config.get_provider().get_lossless()
|
|
1184
|
+
admin_config = config.get_global().get_admin()
|
|
1185
|
+
if lossless_config.enable and admin_config.port is not None and admin_config.port >= 0:
|
|
1186
|
+
for method, path, handler in lossless_http_handlers(flow_engine.lossless_manager):
|
|
1187
|
+
flow_engine.admin_server.add_route(method, path, handler)
|
|
1188
|
+
flow_engine.skill_connector = plugins.get_instance(PluginType.SKILL_CONNECTOR, "polaris")
|
|
1189
|
+
stat_chain = config.get_global().get_stat_reporter().get_chain()
|
|
1190
|
+
stat_reporter_name = stat_chain[0] if stat_chain else "prometheus"
|
|
1191
|
+
if stat_reporter_name == "pushgateway":
|
|
1192
|
+
stat_reporter_name = "prometheus"
|
|
1193
|
+
flow_engine.stat_reporter = plugins.get_instance(PluginType.STAT_REPORTER, stat_reporter_name)
|
|
1194
|
+
event_reporter_config = config.get_global().get_event_reporter()
|
|
1195
|
+
if event_reporter_config.is_enable():
|
|
1196
|
+
for reporter_name in event_reporter_config.get_chain():
|
|
1197
|
+
flow_engine.event_reporters.append(
|
|
1198
|
+
plugins.get_instance(PluginType.EVENT_REPORTER, reporter_name))
|
|
1199
|
+
if hasattr(flow_engine.svc_connector, "connection"):
|
|
1200
|
+
flow_engine.svc_connector.connection.stat_reporter = flow_engine.stat_reporter
|
|
1201
|
+
flow_engine.svc_connector.engine = flow_engine
|
|
1202
|
+
|
|
1203
|
+
# 加载本地缓存
|
|
1204
|
+
local_cache_type = config.get_consumer().get_local_cache().get_type()
|
|
1205
|
+
target_plugin = plugins.get_instance(PluginType.LOCAL_CACHE, local_cache_type)
|
|
1206
|
+
flow_engine.registry = target_plugin
|
|
1207
|
+
|
|
1208
|
+
# 加载注册中心服务地址依赖插件
|
|
1209
|
+
ret = flow_engine.init_server_service_plugins(plugins)
|
|
1210
|
+
if not ret:
|
|
1211
|
+
# TODO
|
|
1212
|
+
# raise Error
|
|
1213
|
+
return None
|
|
1214
|
+
|
|
1215
|
+
# 加载 Java 对齐的 before / core / after 三段服务路由链
|
|
1216
|
+
router_config = config.get_consumer().get_service_router()
|
|
1217
|
+
router_names = {}
|
|
1218
|
+
for config_field, engine_field in (
|
|
1219
|
+
("before_chain", "before_router_chain"),
|
|
1220
|
+
("chain", "router_chain"),
|
|
1221
|
+
("after_chain", "after_router_chain")):
|
|
1222
|
+
names = list(getattr(router_config, "get_%s" % config_field)() or ())
|
|
1223
|
+
routers = [plugins.get_instance(PluginType.SERVICE_ROUTER, router_name)
|
|
1224
|
+
for router_name in names]
|
|
1225
|
+
setattr(flow_engine, engine_field, routers)
|
|
1226
|
+
router_names[config_field] = names
|
|
1227
|
+
route_logger.info(
|
|
1228
|
+
"router chains initialized before=%s core=%s after=%s",
|
|
1229
|
+
router_names["before_chain"], router_names["chain"], router_names["after_chain"],
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
# 加载负载均衡插件
|
|
1233
|
+
load_balance_type = config.get_consumer().get_load_balancer().get_type()
|
|
1234
|
+
target_plugin = plugins.get_instance(PluginType.LOAD_BALANCE, load_balance_type)
|
|
1235
|
+
flow_engine.load_balancer = target_plugin
|
|
1236
|
+
|
|
1237
|
+
# 加载全局上下文
|
|
1238
|
+
flow_engine.global_ctx = global_ctx
|
|
1239
|
+
|
|
1240
|
+
# 加载客户端本地信息
|
|
1241
|
+
flow_engine.load_local_client_report_result()
|
|
1242
|
+
# 添加客户端定期上报任务
|
|
1243
|
+
flow_engine.check_periods.client_report = config.get_global().get_api().get_report_interval()
|
|
1244
|
+
|
|
1245
|
+
# 加载熔断器组件
|
|
1246
|
+
cb_config = config.get_consumer().get_circuit_breaker()
|
|
1247
|
+
enable_cb = cb_config.is_enable() and cb_config.is_default_rule_enable()
|
|
1248
|
+
if enable_cb:
|
|
1249
|
+
cb_chain = config.get_consumer().get_circuit_breaker().get_chain()
|
|
1250
|
+
if len(cb_chain) > 0:
|
|
1251
|
+
for cb_name in cb_chain:
|
|
1252
|
+
target_plugin = plugins.get_instance(PluginType.CIRCUIT_BREAKER, cb_name)
|
|
1253
|
+
# print("polaris CIRCUIT_BREAKER plugin", target_plugin)
|
|
1254
|
+
flow_engine.circuit_breaker_chain[cb_name] = target_plugin
|
|
1255
|
+
circuitbreaker_logger.info(
|
|
1256
|
+
"circuit breaker chain initialized enabled=%s plugins=%s",
|
|
1257
|
+
enable_cb, list(flow_engine.circuit_breaker_chain),
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
global_ctx.set_value(ContextKeyEngine, flow_engine)
|
|
1261
|
+
logger.info(
|
|
1262
|
+
"polaris engine initialized clientId=%s version=%s protocol=%s addresses=%s "
|
|
1263
|
+
"localCache=%s loadBalancer=%s routers=%s circuitBreakers=%s lossless=%s "
|
|
1264
|
+
"eventReporters=%s statReporter=%s admin=%s:%s reportInterval=%dms",
|
|
1265
|
+
global_ctx.get_client_id(), version, protocol,
|
|
1266
|
+
list(config.get_global().get_server_connector().get_addresses()),
|
|
1267
|
+
local_cache_type, load_balance_type, router_names,
|
|
1268
|
+
list(flow_engine.circuit_breaker_chain),
|
|
1269
|
+
flow_engine.lossless_manager.enabled,
|
|
1270
|
+
[type(reporter).__name__ for reporter in flow_engine.event_reporters],
|
|
1271
|
+
type(flow_engine.stat_reporter).__name__ if flow_engine.stat_reporter is not None else None,
|
|
1272
|
+
config.get_global().get_admin().host, config.get_global().get_admin().port,
|
|
1273
|
+
flow_engine.check_periods.client_report,
|
|
1274
|
+
)
|
|
1275
|
+
logger.info("success to start polaris engine")
|
|
1276
|
+
flow_engine.start_job_thread()
|
|
1277
|
+
return flow_engine
|