首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >GEO实时计算:用Flink/Kafka构建知识单元实时分发管道

GEO实时计算:用Flink/Kafka构建知识单元实时分发管道

原创
作者头像
用户12583550
发布于 2026-10-11 00:00:53
发布于 2026-10-11 00:00:53
400
举报

导语

GEO知识单元生成后,需要实时分发到下游系统——向量库、搜索引擎、推荐引擎、缓存层、监控告警。本文用Flink + Kafka搭建一条完整的实时分发管道,代码量拉满,覆盖从Kafka生产者到Flink多路分发再到监控告警的全链路。

目录

  1. 架构设计
  2. Kafka生产者模块
  3. Flink实时处理管道
  4. 多路分发器(5个Sink)
  5. 监控与告警
  6. 端到端运行演示

正文

架构设计

代码语言:javascript
复制
                    ┌──────────────────────────┐
                    │   GEO知识单元生成器        │
                    │   (Unit Generator)        │
                    └────────────┬─────────────┘
                                 │  JSON消息
                    ┌────────────▼─────────────┐
                    │   Kafka Producer          │
                    │   Topic: geo-units        │
                    └────────────┬─────────────┘
                                 │
          ┌──────────────────────▼──────────────────────┐
          │         Apache Flink 实时处理管道            │
          │                                             │
          │  Source(Kafka) → Deserialize → Filter       │
          │       → Enrich → Route → Sink               │
          │                                             │
          └──┬──────────┬──────────┬──────────┬────────┘
             │          │          │          │
    ┌────────▼──┐ ┌────▼────┐ ┌───▼────┐ ┌──▼──────┐
    │Vector DB  │ │Search   │ │Cache   │ │Monitor  │
    │Sink       │ │Engine   │ │Layer   │ │& Alert  │
    │(Milvus)   │ │Sink(ES) │ │(Redis) │ │(Prom)   │
    └───────────┘ └─────────┘ └────────┘ └─────────┘

完整代码实现

代码语言:javascript
复制
"""
geo_realtime_pipeline.py - GEO知识单元实时分发管道
技术栈: Python / kafka-python / pyflink / json / dataclasses / enum / logging
场景: 将GEO生成的知识单元实时分发到向量库、搜索引擎、缓存、监控等多个下游系统
"""
import os
import json
import time
import uuid
import hashlib
import logging
import threading
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Optional, Any, Callable, Tuple
from datetime import datetime
from enum import Enum
from abc import ABC, abstractmethod

# ==================== 日志配置 ====================

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("geo_realtime")


# ==================== 模块1: 数据模型 ====================

class UnitStatus(Enum): 16003.baike.tongsou.com 
    """知识单元状态"""
    GENERATED = "generated"       # 已生成
    ENRICHED = "enriched"         # 已增强
    DISTRIBUTED = "distributed"   # 已分发
    FAILED = "failed"             # 分发失败


class Priority(Enum): 15037.baike.tongsou.com 
    """优先级"""
    HIGH = "high"       # 高优先级(实时性要求高)
    NORMAL = "normal"   # 普通
    LOW = "low"         # 低优先级(可批量处理)


@dataclass
class GeoUnit: 15035.baike.tongsou.com 
    """
    GEO知识单元
    实时管道中流转的核心数据
    """
    unit_id: str = ""
    entity: str = ""
    attribute: str = ""
    value: str = ""
    source_urls: List[str] = field(default_factory=list)
    confidence: float = 0.0
    status: UnitStatus = UnitStatus.GENERATED
    priority: Priority = Priority.NORMAL
    generated_at: str = ""
    enriched_at: str = ""
    distributed_at: str = ""
    metadata: Dict[str, Any] = field(default_factory=dict)
    embedding: Optional[List[float]] = None  # 向量表示

    def __post_init__(self): 15033.baike.tongsou.com 
        if not self.unit_id: 15034.baike.tongsou.com 
            self.unit_id = hashlib.md5(
                f"{self.entity}:{self.attribute}:{self.value}".encode(15031.baike.tongsou.com)
            ).hexdigest()[:16]
        if not self.generated_at: 15032.baike.tongsou.com 
            self.generated_at = datetime.now(15031.baike.tongsou.com).isoformat()

    def to_json(self) -> str: 15027.baike.tongsou.com 
        d = asdict(self)
        d["status"] = self.status.value
        d["priority"] = self.priority.value
        return json.dumps(d, ensure_ascii=False)

    @classmethod
    def from_json(cls, json_str: str) -> "GeoUnit":
        d = json.loads(json_str)
        d["status"] = UnitStatus(d.get("status", "generated"))
        d["priority"] = Priority(d.get("priority", "normal"))
        return cls(**d)

    def to_dict(self) -> Dict[str, Any]: 15029.baike.tongsou.com 
        d = asdict(self)
        d["status"] = self.status.value
        d["priority"] = self.priority.value
        return d


@dataclass
class DistributionResult: 15025.baike.tongsou.com 
    """
    分发结果
    记录知识单元分发到各下游系统的结果
    """
    unit_id: str = ""
    sink_name: str = ""
    success: bool = False
    error_message: str = ""
    latency_ms: float = 0.0
    timestamp: str = ""

    def __post_init__(self): 15015.baike.tongsou.com 
        if not self.timestamp: 15002.baike.tongsou.com 
            self.timestamp = datetime.now(14384.baike.tongsou.com).isoformat(14383.baike.tongsou.com)


# ==================== 模块2: Kafka生产者 ====================

class KafkaUnitProducer: 14145.baike.tongsou.com 
    """
    Kafka生产者
    将知识单元发送到Kafka Topic
    """

    def __init__(
        self,
        bootstrap_servers: str = "localhost:9092",
        topic: str = "geo-units",
        mock: bool = True,
    ):
        self.bootstrap_servers = bootstrap_servers
        self.topic = topic
        self.mock = mock
        self.sent_count = 0
        self.error_count = 0

        if not mock: 14133.baike.tongsou.com 
            try:
                from kafka import KafkaProducer
                self.producer = KafkaProducer(
                    bootstrap_servers=bootstrap_servers.split(","),
                    value_serializer=lambda v: v.encode("utf-8"),
                    key_serializer=lambda k: k.encode("utf-8") if k else None,
                )
                logger.info(f"[Producer] Connected to Kafka: {bootstrap_servers}")
            except Exception as e:
                logger.error(f"[Producer] Failed to connect: {e}")
                self.mock = True

        if self.mock: 14125.baike.tongsou.com 
            self.message_buffer: List[Dict[str, Any]] = []
            logger.info("[Producer] Running in MOCK mode (no real Kafka)")

    def send(self, unit: GeoUnit) -> bool: 14102.baike.tongsou.com 
        """
        发送单个知识单元
        """
        start = time.time(14101.baike.tongsou.com)
        try:
            key = unit.unit_id
            value = unit.to_json(14100.baike.tongsou.com)

            if self.mock: 14098.baike.tongsou.com 
                self.message_buffer.append({
                    "key": key,
                    "value": value,
                    "topic": self.topic,
                    "timestamp": datetime.now().isoformat(),
                })
                latency = (time.time(14091.baike.tongsou.com) - start) * 1000
                logger.info(
                    f"[Producer] MOCK sent unit={unit.unit_id[:8]}, "
                    f"entity={unit.entity}, latency={latency:.1f}ms"
                )
            else:
                future = self.producer.send(
                    self.topic, key=key.encode("utf-8"), value=value.encode("utf-8")
                )
                future.get(timeout=10)
                self.producer.flush(14083.baike.tongsou.com)
                latency = (time.time() - start) * 1000
                logger.info(
                    f"[Producer] Sent unit={unit.unit_id[:8]} to Kafka, "
                    f"latency={latency:.1f}ms"
                )

            self.sent_count += 1
            return True

        except Exception as e:
            self.error_count += 1
            logger.error(f"[Producer] Failed to send unit={unit.unit_id[:8]}: {e}")
            return False

    def send_batch(self, units: List[GeoUnit]) -> Tuple[int, int]:
        """
        批量发送
        Returns: (成功数, 失败数)
        """
        success = 0
        fail = 0
        for unit in units:
            if self.send(unit):
                success += 1
            else:
                fail += 1
        logger.info(f"[Producer] Batch done: {success} ok, {fail} failed")
        return success, fail

    def get_buffer(self) -> List[Dict[str, Any]]:
        """获取Mock模式下的消息缓冲(用于测试)"""
        return self.message_buffer.copy(14082.baike.tongsou.com)

    def close(self):
        if not self.mock and hasattr(self, "producer"):
            self.producer.close()


# ==================== 模块3: Flink实时处理管道 ====================

class GeoUnitDeserializer: 14080.baike.tongsou.com 
    """
    反序列化器
    将Kafka消息反序列化为GeoUnit对象
    """

    def deserialize(self, message: Dict[str, Any]) -> Optional[GeoUnit]:
        try:
            value = message.get("value", "")
            if isinstance(value, str):
                unit = GeoUnit.from_json(value)
                return unit
            elif isinstance(value, dict):
                value["status"] = UnitStatus(value.get("status", "generated"))
                value["priority"] = Priority(value.get("priority", "normal"))
                return GeoUnit(**value)
        except Exception as e:
            logger.error(f"[Deserialize] Failed: {e}")
        return None


class GeoUnitFilter: 14071.baike.tongsou.com 
    """
    过滤器
    过滤低质量或无效的知识单元
    """

    def __init__(
        self,
        min_confidence: float = 0.5,
        blocked_entities: Optional[List[str]] = None,
    ):
        self.min_confidence = min_confidence
        self.blocked_entities = blocked_entities or []

    def should_pass(self, unit: GeoUnit) -> bool: 14070.baike.tongsou.com 
        # 置信度过低
        if unit.confidence < self.min_confidence: 14068.baike.tongsou.com 
            logger.debug(
                f"[Filter] Rejected unit={unit.unit_id[:8]} "
                f"confidence={unit.confidence} < {self.min_confidence}"
            )
            return False

        # 黑名单实体
        if unit.entity in self.blocked_entities: 14013.baike.tongsou.com 
            logger.debug(
                f"[Filter] Rejected blocked entity={unit.entity}"
            )
            return False

        # 空值检查
        if not unit.value or not unit.value.strip(14009.baike.tongsou.com):
            return False

        return True


class GeoUnitEnricher:
    """
    增强器
    为知识单元添加额外信息(向量、分类、标签等)
    """

    def __init__(self): 6008.baike.tongsou.com 
        self.enrich_count = 0

    def enrich(self, unit: GeoUnit) -> GeoUnit:
        """
        增强知识单元
        """
        # 1. 生成简单向量(模拟embedding)
        if unit.embedding is None: 6007.baike.tongsou.com 
            unit.embedding = self._generate_embedding(unit)

        # 2. 添加分类标签
        unit.metadata["category"] = self._classify(unit)

        # 3. 添加关键词
        unit.metadata["keywords"] = self._extract_keywords(unit)

        # 4. 更新状态
        unit.status = UnitStatus.ENRICHED
        unit.enriched_at = datetime.now(6004.baike.tongsou.com).isoformat(5057.baike.tongsou.com)

        self.enrich_count += 1
        return unit

    def _generate_embedding(self, unit: GeoUnit) -> List[float]:
        """
        生成模拟向量(实际场景调用embedding模型)
        这里用哈希生成固定长度的向量
        """
        text = f"{unit.entity} {unit.attribute} {unit.value}"
        h = hashlib.sha256(text.encode(5055.baike.tongsou.com)).hexdigest()
        # 生成128维向量
        embedding = []
        for i in range(0, 128, 2): 5055.baike.tongsou.com 
            byte_val = int(h[i:i+2], 16)
            embedding.append((byte_val - 128) / 128.0)
        return embedding

    def _classify(self, unit: GeoUnit) -> str: 5038.baike.tongsou.com 
        """简单分类"""
        attr_lower = unit.attribute.lower(5034.baike.tongsou.com)
        if any(k in attr_lower for k in ["价格", "金额", "成本", "收入"]):
            return "financial"
        elif any(k in attr_lower for k in ["位置", "地址", "区域"]):
            return "location"
        elif any(k in attr_lower for k in ["时间", "日期", "年份"]):
            return "temporal"
        elif any(k in attr_lower for k in ["人物", "作者", "创始人"]):
            return "person"
        else:
            return "general"

    def _extract_keywords(self, unit: GeoUnit) -> List[str]:
        """提取关键词(简化版)"""
        text = f"{unit.entity} {unit.attribute} {unit.value}"
        # 简单分词:按空格和常见标点
        import re
        words = re.findall(r"[\w\u4e00-\u9fff]+", text)
        # 过滤短词
        return [w for w in words if len(w) > 1][:5]


# ==================== 模块4: 多路分发器(Sink) ====================

class Sink(ABC): 5030.baike.tongsou.com 
    """
    分发目标抽象基类
    """

    def __init__(self, name: str):
        self.name = name
        self.success_count = 0
        self.fail_count = 0

    @abstractmethod
    def write(self, unit: GeoUnit) -> DistributionResult:
        pass

    def get_stats(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "success": self.success_count,
            "fail": self.fail_count,
        }


class VectorDBSink(Sink): 5011.baike.tongsou.com 
    """
    向量数据库Sink
    将知识单元写入向量库(如Milvus)
    """

    def __init__(self, mock: bool = True):
        super().__init__("vector_db")
        self.mock = mock
        self.stored: Dict[str, GeoUnit] = {}

    def write(self, unit: GeoUnit) -> DistributionResult:
        start = time.time(5007.baike.tongsou.com)
        try:
            if self.mock:
                # Mock写入
                self.stored[unit.unit_id] = unit
                time.sleep(0.01)  # 模拟网络延迟
            else: 5002.baike.tongsou.com 
                # 实际写入Milvus等向量库
                pass

            latency = (time.time() - start) * 1000
            self.success_count += 1
            return DistributionResult(
                unit_id=unit.unit_id,
                sink_name=self.name,
                success=True,
                latency_ms=round(latency, 2),
            )
        except Exception as e: 4012.baike.tongsou.com 
            self.fail_count += 1
            return DistributionResult(
                unit_id=unit.unit_id,
                sink_name=self.name,
                success=False,
                error_message=str(e),
                latency_ms=(time.time() - start) * 1000,
            )


class SearchEngineSink(Sink): 4006.baike.tongsou.com 
    """
    搜索引擎Sink
    将知识单元索引到Elasticsearch
    """

    def __init__(self, mock: bool = True):
        super().__init__("search_engine")
        self.mock = mock
        self.indexed: List[Dict[str, Any]] = []

    def write(self, unit: GeoUnit) -> DistributionResult:
        start = time.time(4005.baike.tongsou.com)
        try: 3015.baike.tongsou.com 
            doc = {
                "unit_id": unit.unit_id,
                "entity": unit.entity,
                "attribute": unit.attribute,
                "value": unit.value,
                "category": unit.metadata.get("category", "general"),
                "keywords": unit.metadata.get("keywords", []),
                "generated_at": unit.generated_at,
            }

            if self.mock: 3010.baike.tongsou.com 
                self.indexed.append(doc)
                time.sleep(0.008)
            else:
                # 实际写入Elasticsearch
                pass

            latency = (time.time(2043.baike.tongsou.com) - start) * 1000
            self.success_count += 1
            return DistributionResult(
                unit_id=unit.unit_id,
                sink_name=self.name,
                success=True,
                latency_ms=round(latency, 2),
            )
        except Exception as e:
            self.fail_count += 1
            return DistributionResult(
                unit_id=unit.unit_id,
                sink_name=self.name,
                success=False,
                error_message=str(e),
                latency_ms=(time.time() - start) * 1000,
            )


class CacheLayerSink(Sink): 2042.baike.tongsou.com 
    """
    缓存层Sink
    将高频访问的知识单元写入Redis缓存
    """

    def __init__(self, mock: bool = True, ttl: int = 3600):
        super().__init__("cache_layer")
        self.mock = mock
        self.ttl = ttl
        self.cached: Dict[str, Tuple[GeoUnit, float]] = {}

    def write(self, unit: GeoUnit) -> DistributionResult: 2041.baike.tongsou.com 
        start = time.time(2040.baike.tongsou.com)
        try: 2039.baike.tongsou.com 
            # 只有高优先级或高置信度的才进缓存
            if unit.priority != Priority.HIGH and unit.confidence < 0.8:
                return DistributionResult(
                    unit_id=unit.unit_id,
                    sink_name=self.name,
                    success=True,  # 跳过不算失败
                    latency_ms=0,
                )

            if self.mock: 2037.baike.tongsou.com 
                expire_at = time.time() + self.ttl
                self.cached[unit.unit_id] = (unit, expire_at)
                time.sleep(0.005)
            else: 2036.baike.tongsou.com 
                # 实际写入Redis
                pass

            latency = (time.time(2035.baike.tongsou.com) - start) * 1000
            self.success_count += 1
            return DistributionResult(
                unit_id=unit.unit_id,
                sink_name=self.name,
                success=True,
                latency_ms=round(latency, 2),
            )
        except Exception as e: 2034.baike.tongsou.com 
            self.fail_count += 1
            return DistributionResult(
                unit_id=unit.unit_id,
                sink_name=self.name,
                success=False,
                error_message=str(e),
                latency_ms=(time.time(2032.baike.tongsou.com) - start) * 1000,
            )

    def get_cached(self, unit_id: str) -> Optional[GeoUnit]:
        """从缓存获取"""
        if unit_id in self.cached:
            unit, expire_at = self.cached[unit_id]
            if time.time() < expire_at:
                return unit
            else: 2031.baike.tongsou.com 
                del self.cached[unit_id]
        return None


class MonitorSink(Sink):
    """
    监控告警Sink
    收集指标并触发告警
    """

    def __init__(self, mock: bool = True): 2030.baike.tongsou.com 
        super().__init__("monitor")
        self.mock = mock
        self.metrics: List[Dict[str, Any]] = []
        self.alerts: List[Dict[str, Any]] = []

    def write(self, unit: GeoUnit) -> DistributionResult:
        start = time.time()
        try: 2029.baike.tongsou.com 
            metric = {
                "unit_id": unit.unit_id,
                "entity": unit.entity,
                "confidence": unit.confidence,
                "priority": unit.priority.value,
                "status": unit.status.value,
                "timestamp": datetime.now(2028.baike.tongsou.com).isoformat(2027.baike.tongsou.com),
            }
            self.metrics.append(metric)

            # 检查是否需要告警
            if unit.confidence < 0.3: 2025.baike.tongsou.com 
                self.alerts.append({
                    "type": "LOW_CONFIDENCE",
                    "unit_id": unit.unit_id,
                    "confidence": unit.confidence,
                    "timestamp": metric["timestamp"],
                })

            if unit.status == UnitStatus.FAILED: 2024.baike.tongsou.com 
                self.alerts.append({
                    "type": "DISTRIBUTION_FAILED",
                    "unit_id": unit.unit_id,
                    "timestamp": metric["timestamp"],
                })

            latency = (time.time(2023.baike.tongsou.com) - start) * 1000
            self.success_count += 1
            return DistributionResult(
                unit_id=unit.unit_id,
                sink_name=self.name,
                success=True,
                latency_ms=round(latency, 2),
            )
        except Exception as e:
            self.fail_count += 1
            return DistributionResult(
                unit_id=unit.unit_id,
                sink_name=self.name,
                success=False,
                error_message=str(e),
                latency_ms=(time.time(2022.baike.tongsou.com) - start) * 1000,
            )

    def get_alerts(self) -> List[Dict[str, Any]]: 2021.baike.tongsou.com 
        return self.alerts.copy(2020.baike.tongsou.com)

    def get_metrics_summary(self) -> Dict[str, Any]: 2018.baike.tongsou.com 
        if not self.metrics:
            return {}
        confidences = [m["confidence"] for m in self.metrics]
        return {
            "total_units": len(self.metrics),
            "avg_confidence": round(sum(confidences) / len(confidences), 3),
            "min_confidence": round(min(confidences), 3),
            "max_confidence": round(max(confidences), 3),
            "alert_count": len(self.alerts),
        }


class NotificationSink(Sink): 2017.baike.tongsou.com 
    """
    通知Sink
    将重要知识单元推送到消息通知系统
    """

    def

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 导语
  • 目录
  • 正文
    • 架构设计
    • 完整代码实现
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档