本文目录导读:

Django 服务发现在不同的架构场景下有不同的集成方式,以下是几种主流方案的详细指南:
使用 Consul(最常用方案)
安装依赖
pip install python-consul django-consul
Django 配置 (settings.py)
# Consul 配置 CONSUL_HOST = '127.0.0.1' CONSUL_PORT = 8500 # 服务注册配置 SERVICE_NAME = 'my-django-app' SERVICE_PORT = 8000 SERVICE_TAGS = ['django', 'api', 'v1'] # 健康检查配置 HEALTH_CHECK_URL = f'/health/' HEALTH_INTERVAL = '10s' HEALTH_TIMEOUT = '5s'
服务注册与发现模块 (services/consul.py)
import consul
from django.conf import settings
class ConsulService:
def __init__(self):
self.client = consul.Consul(
host=settings.CONSUL_HOST,
port=settings.CONSUL_PORT
)
def register_service(self):
"""注册服务"""
service_id = f"{settings.SERVICE_NAME}-{socket.gethostname()}-{settings.SERVICE_PORT}"
self.client.agent.service.register(
name=settings.SERVICE_NAME,
service_id=service_id,
address=socket.gethostbyname(socket.gethostname()),
port=settings.SERVICE_PORT,
tags=settings.SERVICE_TAGS,
check=consul.Check.http(
f'http://localhost:{settings.SERVICE_PORT}{settings.HEALTH_CHECK_URL}',
interval=settings.HEALTH_INTERVAL,
timeout=settings.HEALTH_TIMEOUT
)
)
return service_id
def deregister_service(self, service_id):
"""注销服务"""
self.client.agent.service.deregister(service_id)
def discover_service(self, service_name=None):
"""发现服务"""
services = self.client.agent.services()
if service_name:
return {k: v for k, v in services.items()
if v['Service'] == (service_name or settings.SERVICE_NAME)}
return services
Django App 初始化 (apps.py)
from django.apps import AppConfig
import atexit
import socket
class MyAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
def ready(self):
if not hasattr(self, '_service_registered'):
from .services.consul import ConsulService
consul_service = ConsulService()
service_id = consul_service.register_service()
# 程序退出时取消注册
atexit.register(consul_service.deregister_service, service_id)
self._service_registered = True
健康检查视图 (views.py)
from django.http import JsonResponse
from django.views.decorators.http import require_GET
from django.db import connection
@require_GET
def health_check(request):
"""健康检查接口"""
status = 'healthy'
try:
# 检查数据库连接
connection.ensure_connection()
except Exception as e:
status = 'unhealthy'
return JsonResponse({
'status': status,
'service': settings.SERVICE_NAME
})
使用 Etcd
安装依赖
pip install python-etcd3
Django 配置 (settings.py)
ETCD_HOST = '127.0.0.1' ETCD_PORT = 2379 SERVICE_TTL = 30 # 秒
服务注册模块 (services/etcd.py)
import etcd3
import json
from django.conf import settings
class EtcdService:
def __init__(self):
self.client = etcd3.client(
host=settings.ETCD_HOST,
port=settings.ETCD_PORT
)
def register_service(self):
"""注册服务并设置租约"""
lease = self.client.lease(settings.SERVICE_TTL)
service_info = {
'name': settings.SERVICE_NAME,
'address': socket.gethostbyname(socket.gethostname()),
'port': settings.SERVICE_PORT,
'tags': settings.SERVICE_TAGS
}
key = f"/services/{settings.SERVICE_NAME}/{socket.gethostname()}:{settings.SERVICE_PORT}"
self.client.put(key, json.dumps(service_info), lease=lease)
# 启动自动续约
lease.refresh()
return lease
def discover_service(self, service_name=None):
"""发现服务"""
prefix = f"/services/{service_name or settings.SERVICE_NAME}"
services = []
for value, metadata in self.client.get_prefix(prefix):
service = json.loads(value)
services.append(service)
return services
使用 Kubernetes API(原生方案)
安装依赖
pip install kubernetes
服务发现配置 (services/k8s.py)
from kubernetes import client, config
import os
class K8sServiceDiscovery:
def __init__(self):
# 在K8s集群内运行
config.load_incluster_config()
self.api = client.CoreV1Api()
def discover_service(self, service_name, namespace='default'):
"""通过K8s API发现服务"""
services = self.api.list_namespaced_service(
namespace=namespace,
field_selector=f'metadata.name={service_name}'
)
endpoints = []
for svc in services.items:
# 获取Endpoints
endpoints_obj = self.api.read_namespaced_endpoints(
svc.metadata.name,
namespace
)
if endpoints_obj.subsets:
for subset in endpoints_obj.subsets:
for address in subset.addresses:
endpoints.append({
'host': address.ip,
'port': subset.ports[0].port
})
return endpoints
def get_service_url(self, service_name, namespace='default'):
"""获取服务URL"""
endpoints = self.discover_service(service_name, namespace)
if endpoints:
# 负载均衡选择
import random
endpoint = random.choice(endpoints)
return f"http://{endpoint['host']}:{endpoint['port']}"
return None
环境变量方式(简化版)
# Django settings.py
import os
# Kubernetes会自动注入环境变量
# 格式: {SERVICE_NAME}_SERVICE_HOST 和 {SERVICE_NAME}_SERVICE_PORT
def get_service_url(service_name):
"""通过环境变量获取服务地址"""
host = os.environ.get(f'{service_name.upper()}_SERVICE_HOST')
port = os.environ.get(f'{service_name.upper()}_SERVICE_PORT')
if host and port:
return f"http://{host}:{port}"
return None
# 使用示例
USER_SERVICE_URL = get_service_url('user-service')
使用 Eureka(Spring Cloud 生态)
安装依赖
pip install py_eureka_client
Django 配置 (settings.py)
EUREKA_SERVER = 'http://eureka-server:8761/eureka/' EUREKA_APP_NAME = 'django-app' EUREKA_INSTANCE_HOST = 'localhost' EUREKA_INSTANCE_PORT = 8000
服务注册模块 (services/eureka.py)
import py_eureka_client.eureka_client as eureka_client
from django.conf import settings
class EurekaService:
def __init__(self):
self.client = None
def init_eureka(self):
"""初始化Eureka客户端"""
eureka_client.init(
eureka_server=settings.EUREKA_SERVER,
app_name=settings.EUREKA_APP_NAME,
instance_port=settings.EUREKA_INSTANCE_PORT,
instance_host=settings.EUREKA_INSTANCE_HOST
)
def discover_service(self, app_name):
"""发现服务"""
instances = eureka_client.get_instances(app_name)
return instances
客户端负载均衡
使用 Round Robin 算法
import itertools
from typing import Dict, List
class RoundRobinBalancer:
def __init__(self):
self._services: Dict[str, List] = {}
self._iterators = {}
def update_services(self, service_name: str, endpoints: List):
"""更新服务列表"""
self._services[service_name] = endpoints
if service_name not in self._iterators:
self._iterators[service_name] = itertools.cycle(endpoints)
def get_next(self, service_name: str):
"""获取下一个可用服务"""
if service_name not in self._services:
return None
endpoints = self._services[service_name]
if not endpoints:
return None
return next(self._iterators[service_name])
集成到 Django 中间件
# middleware/service_discovery.py
from django.http import JsonResponse
from .services.consul import ConsulService
class ServiceDiscoveryMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.consul = ConsulService()
def __call__(self, request):
# 在请求处理前更新服务列表
if hasattr(request, 'service_discovery'):
services = self.consul.discover_service()
request.service_list = services
response = self.get_response(request)
return response
生产环境最佳实践
Docker Compose 示例
version: '3.8'
services:
consul:
image: consul:1.15
ports:
- "8500:8500"
command: agent -dev -client=0.0.0.0
django-app:
build: .
ports:
- "8000:8000"
environment:
- CONSUL_HOST=consul
- DJANGO_SETTINGS_MODULE=myproject.settings.production
depends_on:
- consul
command: >
sh -c "python manage.py migrate &&
python manage.py runserver 0.0.0.0:8000"
监控与告警
# monitoring/health_check.py
from prometheus_client import start_http_server, Gauge
import threading
class ServiceMonitor:
def __init__(self):
self.service_status = Gauge(
'django_service_status',
'Service registration status',
['service_name']
)
def start(self, port=8001):
"""启动监控HTTP服务"""
threading.Thread(
target=start_http_server,
args=(port,),
daemon=True
).start()
def update_metrics(self, service_name, status):
"""更新服务指标"""
self.service_status.labels(
service_name=service_name
).set(1 if status else 0)
推荐方案
- 小规模部署:使用 Consul,配置简单,功能完整
- Kubernetes 环境:使用 K8s 内置服务发现
- Spring Cloud 生态:使用 Eureka
- 需要强一致性:使用 Etcd
选择方案时需要考虑团队技术栈、运维能力和系统复杂度。