OLC 扩展开发指南
本文档指导开发者如何为 OLC 添加新的扩展。OLC 提供了四个主要扩展点,每个扩展点遵循相同的模式:定义抽象 → 实现类 → 注册到 Manager/Factory。
扩展点总览
| 扩展点 | 抽象基类 | 注册位置 | 适用场景 |
|---|---|---|---|
| 框架适配器 | BaseHTTPMiddleware |
app.add_middleware() |
支持新的 Web 框架 |
| 限流器 | Limit |
OlcRateLimiterFactory |
新的限流算法或策略 |
| 算法 | FlowAbstractAlgorithm |
OlcAlgManager |
新的自适应算法 |
| 配置源 | Stub |
OlcConfigPluginManager |
新的配置获取方式 |
选择决策树:
- 需要拦截 HTTP 请求?→ 框架适配器扩展
- 需要新的限流策略?→ 限流器扩展
- 需要动态调整限流阈值?→ 算法扩展
- 需要从新来源获取规则?→ 配置源扩展
框架适配器扩展
适配器负责:拦截请求 → 提取标签 → 调用 OLC.process() → 处理 block → 调用 OLC.clean()
步骤
- 创建
olc/adapters/<framework>/目录 - 实现
adapter.py:参考OlcFastAPIAdapter - 实现
adapter_config.py:定义配置类 - 在
pyproject.toml添加 optional-dependencies - 编写测试
示例:Flask 适配器
olc/adapters/flask/adapter_config.py:
from typing import Callable, Dict, Optional, Union, Awaitable
from flask import Request, Response
TagExtractor = Callable[[Request], Union[Dict[str, str], Awaitable[Dict[str, str]]]]
BlockResponseBuilder = Callable[[Request, str], Union[Response, Awaitable[Response]]]
class OlcFlaskAdapterConfig:
def __init__(
self,
tag_extractor: Optional[TagExtractor] = None,
block_msg: str = "Request blocked by rate limiter",
block_response_builder: Optional[BlockResponseBuilder] = None,
token_number: int = 1,
):
self.tag_extractor = tag_extractor
self.block_msg = block_msg
self.block_response_builder = block_response_builder
self.token_number = token_number
olc/adapters/flask/adapter.py:
import logging
from typing import List, Optional
from flask import Request, Response, jsonify
from olc.adapters.flask.adapter_config import OlcFlaskAdapterConfig
from olc.bean.dimension import Method, URL
from olc.bean.olc_control_request import OlcControlRequest
from olc.bean.tag_group import TagGroup
from olc.config.properties_loader import OlcConfigManager
from olc.control.context.context import OlcContext
from olc.olc import OLC
logger = logging.getLogger(__name__)
class OlcFlaskAdapter:
def __init__(
self,
app,
config: Optional[OlcFlaskAdapterConfig] = None,
groups: Optional[List[TagGroup]] = None,
):
self.app = app
self.config = config or OlcFlaskAdapterConfig()
self.groups = groups
app.before_request(self._before_request)
app.after_request(self._after_request)
def _extract_tags(self, request: Request) -> dict:
if self.config.tag_extractor is None:
return {
f"{URL}": request.path,
f"{Method}": request.method,
}
return self.config.tag_extractor(request)
def _before_request(self):
from flask import request as flask_request
if not OlcConfigManager.get_instance().get_sdk_config().switch:
return None
try:
tags = self._extract_tags(flask_request)
olc_request = OlcControlRequest(
tags=tags,
token_number=self.config.token_number,
)
context = OLC.get_instance().process(olc_request, self.groups)
flask_request.olc_context = context
if context.result.block:
block_msg = context.result.block_msg or self.config.block_msg
if self.config.block_response_builder is not None:
return self.config.block_response_builder(flask_request, block_msg)
return jsonify({
"error": "Too Many Requests",
"message": block_msg,
"path": flask_request.path,
}), 429
except Exception as e:
logger.error(f"OLC process error: {e}", exc_info=True)
return None
def _after_request(self, response: Response):
from flask import request as flask_request
context = getattr(flask_request, "olc_context", None)
if context is not None:
try:
OLC.get_instance().clean(context)
except Exception as e:
logger.error(f"OLC cleanup error: {e}", exc_info=True)
return response
pyproject.toml 新增:
[project.optional-dependencies]
flask = [
"flask>=2.0.0",
]
限流器扩展
限流器负责:实现 Limit 抽象接口的 5 个方法。
步骤
- 在
olc/limit/node/或olc/limit/cluster/下创建新限流器类 - 继承
Limit抽象类,实现所有抽象方法 - 如需新的
FlowControlMode,在FlowControlMode枚举中添加 - 在
OlcRateLimiterFactory对应方法中注册新分支 - 编写测试
示例:滑动窗口限流器
olc/limit/node/static/sliding_window_limiter.py:
import threading
import time
from collections import deque
from olc.bean.match_wrapper import MatchWrapper
from olc.bean.olc_config_rule import FlowPolicy
from olc.limit.limiter import Limit
class SlidingWindowLimiter(Limit):
def __init__(self, wrapper: MatchWrapper[FlowPolicy]):
self._wrapper = wrapper
self._window_seconds = wrapper.policy.time_unit_to_seconds()
self._rate_limit = wrapper.policy.rate_limit
self._lock = threading.Lock()
self._timestamps: deque = deque()
def get_match_wrapper(self) -> MatchWrapper[FlowPolicy]:
return self._wrapper
def _cleanup(self, now: float):
cutoff = now - self._window_seconds
while self._timestamps and self._timestamps[0] <= cutoff:
self._timestamps.popleft()
def pre_check(self, token_number: int) -> bool:
now = time.time()
with self._lock:
self._cleanup(now)
return len(self._timestamps) + token_number <= self._rate_limit
def try_acquire(self, token_number: int) -> bool:
now = time.time()
with self._lock:
self._cleanup(now)
if len(self._timestamps) + token_number <= self._rate_limit:
for _ in range(token_number):
self._timestamps.append(now)
return True
return False
def roll_back(self, token_number: int):
with self._lock:
for _ in range(min(token_number, len(self._timestamps))):
self._timestamps.pop()
def dec_token(self, token_number: int):
pass
注册到工厂 — 修改 OlcRateLimiterFactory._get_static_limiter:
def _get_static_limiter(self, wrapper: MatchWrapper[FlowPolicy]) -> Optional[Limit]:
policy = wrapper.policy
mode = policy.flow_control_mode
limiter = None
if StrUtils.equals_ignore_case(mode, FlowControlMode.QPS.value):
limiter = QpsLimiter(wrapper)
elif StrUtils.equals_ignore_case(
mode, FlowControlMode.QUOTA.value
) or StrUtils.equals_ignore_case(mode, FlowControlMode.RESPONSE_QUOTA.value):
limiter = QuotaLimiter(wrapper)
elif StrUtils.equals_ignore_case(mode, FlowControlMode.CONCURRENT.value):
limiter = ConcurrentLimiter(wrapper)
elif StrUtils.equals_ignore_case(mode, FlowControlMode.SLIDING_WINDOW.value):
limiter = SlidingWindowLimiter(wrapper)
else:
logger.warning(f"unknown flow control mode {mode} for {policy.name}")
return limiter
添加 FlowControlMode 枚举值 — 修改 olc/bean/olc_config_rule.py:
class FlowControlMode(Enum):
QPS = "qps"
QUOTA = "quota"
RESPONSE_QUOTA = "response_quota"
CONCURRENT = "concurrent"
SLIDING_WINDOW = "sliding_window"
算法扩展
算法负责:实现 FlowAbstractAlgorithm.make_decision 方法,根据资源使用率动态调整限流阈值。
步骤
- 在
olc/alg/flow/下创建新算法类 - 继承
FlowAbstractAlgorithm,定义参数(ParaName),实现make_decision - 在
OlcAlgManager.__init__中注册 - 编写测试
示例:AIMD 算法
AIMD (Additive Increase Multiplicative Decrease) 是一种经典的拥塞控制算法:正常时线性增加限流阈值,过载时乘性减少。
olc/alg/flow/aimd_algorithm.py:
import logging
from typing import Any
from olc.alg.alg_context import AlgContext
from olc.alg.algorithm import T, ParaName
from olc.alg.flow.flow_alg import (
FlowAbstractAlgorithm,
MIN_WAL,
MAX_WAL,
DELTA,
CURRENT_USAGE,
)
from olc.event.sny_event import Event
logger = logging.getLogger(__name__)
ADDITIVE_STEP = "additiveStep"
MULTIPLICATIVE_FACTOR = "multiplicativeFactor"
OVERLOAD_THRESHOLD = "overloadThreshold"
class AimdAlgorithm(FlowAbstractAlgorithm):
NAME = "AIMD"
def __init__(self, auto: bool = False):
super().__init__(auto)
self.para_names[MIN_WAL] = ParaName(MIN_WAL, 0.5)
self.para_names[MAX_WAL] = ParaName(MAX_WAL, 2.0)
self.para_names[DELTA] = ParaName(DELTA, 5)
self.para_names[ADDITIVE_STEP] = ParaName(ADDITIVE_STEP, 10)
self.para_names[MULTIPLICATIVE_FACTOR] = ParaName(
MULTIPLICATIVE_FACTOR, 0.5
)
self.para_names[OVERLOAD_THRESHOLD] = ParaName(
OVERLOAD_THRESHOLD, 0.8
)
self.para_names[CURRENT_USAGE] = ParaName(
CURRENT_USAGE, "localresource.memory", True
)
self._states = {}
Event.get_instance().OLC_CLEAN_RULE.connect(self._clean_states)
def make_decision(self, alg_context: AlgContext[T]) -> T:
previous = alg_context.previous
init_result = alg_context.init_result
group_name = alg_context.group.name if alg_context.group else "default"
current_usage = alg_context.get_float_value_by_dynamic(CURRENT_USAGE)
current_usage = max(0.0, min(1.0, current_usage))
additive_step = alg_context.get_float_value_by_param(
ADDITIVE_STEP, self.para_names[ADDITIVE_STEP].default_value
)
multiplicative_factor = alg_context.get_float_value_by_param(
MULTIPLICATIVE_FACTOR,
self.para_names[MULTIPLICATIVE_FACTOR].default_value,
)
overload_threshold = alg_context.get_float_value_by_param(
OVERLOAD_THRESHOLD,
self.para_names[OVERLOAD_THRESHOLD].default_value,
)
min_wal = alg_context.get_float_value_by_param(
MIN_WAL, self.para_names[MIN_WAL].default_value
)
max_wal = alg_context.get_float_value_by_param(
MAX_WAL, self.para_names[MAX_WAL].default_value
)
if current_usage > overload_threshold:
new_threshold = int(previous * multiplicative_factor)
else:
new_threshold = previous + int(additive_step)
new_threshold = max(
int(init_result * min_wal),
min(int(init_result * max_wal), new_threshold),
)
return new_threshold
def check(self, params: dict[str, Any]) -> bool:
if not super().check(params):
return False
if not self.valid_min_max_param(MIN_WAL, MAX_WAL, params):
return False
factor = params.get(MULTIPLICATIVE_FACTOR)
if factor is not None and (float(factor) <= 0 or float(factor) >= 1):
logger.warning(
f"multiplicativeFactor must be in (0, 1), got {factor}"
)
return False
return True
def _clean_states(self, sender, **kwargs):
self._states.clear()
注册到算法管理器 — 修改 OlcAlgManager.__init__:
def __init__(self):
self._flow_alg_map: Dict[str, Optional[FlowAbstractAlgorithm]] = {}
self.add_flow_alg(PidAlgorithm.NAME, PidAlgorithm())
self.add_flow_alg(AimdAlgorithm.NAME, AimdAlgorithm())
配置源扩展
配置源负责:实现 Stub 抽象类的 get_olc_config_rules 方法,返回 JSON 字符串。
步骤
- 在
olc/stub/下创建新配置源类 - 继承
Stub,实现get_olc_config_rules - 在
OlcConfigPluginManager中注册 - 在
overload-config.properties中配置olc.sdk.config.stub=<name> - 编写测试
示例:HTTP 远程配置源
olc/stub/http_config_stub.py:
import logging
from typing import Optional
import requests
from olc.stub.config_stub_manager import Stub
from olc.utils.str_utils import StrUtils
logger = logging.getLogger(__name__)
class HttpConfigStub(Stub):
NAME = "http"
def __init__(self):
from olc.config.properties_loader import OlcConfigManager
sdk_config = OlcConfigManager.get_instance().get_sdk_config()
self._url = getattr(sdk_config, "config_http_url", "")
def closeStub(self):
pass
def get_stub_name(self) -> str:
return HttpConfigStub.NAME
def get_olc_config_rules(self) -> Optional[str]:
if StrUtils.is_empty(self._url):
logger.error("HTTP config URL is empty")
return None
try:
response = requests.get(self._url, timeout=5)
response.raise_for_status()
return response.text
except requests.RequestException as e:
logger.error(f"Failed to fetch config from {self._url}: {e}")
return None
注册到插件管理器 — 修改 OlcConfigPluginManager.get_instance:
@classmethod
def get_instance(cls) -> "OlcConfigPluginManager":
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = OlcConfigPluginManager()
cls._instance.register_stub(JsonFileConfigStub.NAME, JsonFileConfigStub)
cls._instance.register_stub(HttpConfigStub.NAME, HttpConfigStub)
return cls._instance
配置使用 — 在 overload-config.properties 中:
olc.sdk.config.stub=http
olc.sdk.config.http.url=http://config-server:8080/api/olc/rules