consul 是一个服务注册、服务发现平台,可以接受api 或grpc 服务连接, 具有健康检查功能,consul的作用是将多个相同服务的服务器ip+端口收集为一个服务进行管理
consul 安装配置
使用docker 安装consul
docker pull consul
docker run -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600/udp
consul consul agent -dev -client=0.0.0.0
8500 端口是用于服务注册的端口,8600是用于api网关进行服务发现的端口,其余参数可自行百度
grpc服务注册consul
from dataclasses import dataclass, field
import aiohttp
import redis
import consul
import requests
from common.aiologer import logger
@dataclass()
class RegisterConsul:
host: str
port: int
connection: consul.Consul = field(init=False)
# 等到实例化对象的时候自动调用,类似于 __init__ 方法
def __post_init__(self):
self.connection = consul.Consul(host=self.host, port=self.port)
self.redis_register_key = ""
def register(self, name: str, service_id: str, address: str, port: int, tags: list, redis_config: dict) -> bool:
check = {
"GRPC": "{}:{}".format(address, port),
"GRPCUseTLS": False,
"Timeout": "5s",
"Interval": "5s",
"DeregisterCriticalServiceAfter": "15s"
}
redis_conn = redis.Redis(host=redis_config["host"], port=redis_config["port"],
password=redis_config["password"], db=redis_config["db"])
# 防止统一服务器上监听同一端口的服务重复注册
if self.prevent_re_register(address, port, redis_conn):
return True
logger.info("正在注册服务")
ret = self.connection.agent.service.register(name=name, service_id=service_id, address=address
, port=port, tags=tags, check=check)
redis_conn.delete(self.redis_register_key)
return ret
# 防止重复注册
def prevent_re_register(self, address, port, redis_conn) -> bool:
self.redis_register_key = "register_server_" + address + ":" + str(port)
# redis 分布式锁防止重复注册
register_lock_ret = redis_conn.set(self.redis_register_key, 1, nx=True, ex=60)
if register_lock_ret is None:
logger.info("redis_register_key: 服务已注册")
return True
filter_server = self.filter_server({"Address": address, "Port": port})
# 查询服务是否存在,防止重复注册
if filter_server:
for _, v in filter_server.items():
if v["Address"] == address and v["Port"] == port:
logger.info("filter_server: 服务已注册")
return True
return False
def del_server(self, server_id: str) -> None:
return self.connection.agent.service.deregister(server_id)
def get_all_server(self) -> dict:
return self.connection.agent.services()
def filter_server(self, filter: dict) -> dict:
url = f"http://{self.host}:{self.port}/v1/agent/services?"
params_list = []
for k, v in filter.items():
params_list.append(f"{k}={v}")
url += "&".join(params_list)
return requests.get(url).json()
# 通过srv name 获取服务的ip + 端口
async def get_host_port_by_srv_name(self, srv_name):
url = f"http://{self.host}:{self.port}/v1/catalog/service/{srv_name}"
# 异步获取srv name 的服务ip、端口 列表
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
data = await response.json()
return data
grpc 注册健康检查,
from common.grpc_health.v1 import health, health_pb2_grpc, _async
grpc_server = grpc.aio.server()
grpc_server.add_insecure_port("0.0.0.0:{}".format(port))
# 注册用户信息查询grpc 服务
user_info_pb2_grpc.add_UserServicer_to_server(user_info_server, grpc_server)
# 注册grpc 健康检查服务
health_pb2_grpc.add_HealthServicer_to_server(_async.HealthServicer(), grpc_server)
grpc 健康检查模块下载: grpc健康检查
api 注册consul
from dataclasses import dataclass, field
import aiohttp
import redis
import consul
import requests
from common.aiologger import logger
@dataclass()
class RegisterConsul:
host: str
port: int
connection: consul.Consul = field(init=False)
# 等到实例化对象的时候自动调用,类似于 __init__ 方法
def __post_init__(self):
self.connection = consul.Consul(host=self.host, port=self.port)
self.redis_register_key = ""
self.aiohttp_session = None
def register(self, name: str, service_id: str, address: str, port: int, tags: list, redis_config: dict) -> bool:
check = consul.Check().tcp(address, port, '5s', '5s', '15s')
redis_conn = redis.Redis(host=redis_config["host"], port=redis_config["port"],
password=redis_config["password"], db=redis_config["db"])
# 防止统一服务器上监听同一端口的服务重复注册
if self.prevent_re_register(address, port, redis_conn):
return True
logger.info("正在注册服务")
ret = self.connection.agent.service.register(name=name, service_id=service_id, address=address
, port=port, tags=tags, check=check)
redis_conn.delete(self.redis_register_key)
return ret
# 防止重复注册
def prevent_re_register(self, address, port, redis_conn) -> bool:
self.redis_register_key = "register_server_" + address + ":" + str(port)
# redis 分布式锁防止重复注册
register_lock_ret = redis_conn.set(self.redis_register_key, 1, nx=True, ex=60)
if register_lock_ret is None:
return True
filter_server = self.filter_server({"Address": address, "Port": port})
# 查询服务是否存在,防止重复注册
if filter_server:
for _, v in filter_server.items():
if v["Address"] == address and v["Port"] == port:
return True
return False
def del_server(self, server_id: str) -> None:
return self.connection.agent.service.deregister(server_id)
def get_all_server(self) -> dict:
return self.connection.agent.services()
def filter_server(self, filter: dict) -> dict:
url = f"http://{self.host}:{self.port}/v1/agent/services?"
params_list = []
for k, v in filter.items():
params_list.append(f"{k}={v}")
url += "&".join(params_list)
return requests.get(url).json()
# 通过srv name 获取服务的ip + 端口
async def get_host_port_by_srv_name(self, srv_name):
url = f"http://{self.host}:{self.port}/v1/catalog/service/{srv_name}"
# 异步获取srv name 的服务ip、端口 列表
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
data = await response.json()
return data
api就不需要健康检查模块了,consul会自动调用http接口
效果: