consul提供服务注册与健康检查功能,服务使用方可监视对应服务的情况(要启动专用线程),但是没找到对应的使用方式。
只找到一个建康检查的接口(示例:http://localhost:8500/v1/health/service/test_server?passing),通过定时调用该接口可获取健康的服务列表,与之前的相比较,然后做相应工作。
etcd注册服务时可设定ttl,然后服务要专门启动一个线程定时刷新该ttl,相当保持心跳,通过这种机制注册服务及实现健康检查功能。服务使用方可调用监视接口,监视对应服务是否有变化。
服务使用方根据获取的服务列表,每台机器启动一个连接池(这个连接池可使用第三方的实现),并管理该机器组及对应连接池(这种方式一般是使用了第三方框架,比如go的http, gin,beego或是python的flask,django等,上层使用方是多线程的且不可控)。客户端做负载均衡,策略可根据实际情况实现,比如轮询的方式。在使用时如果发现连接不可用,可根据错误情况做相应工作,如果是断开连接就要 剔除该机器。服务重连交给服务监视线程来做。读写连接机器时用读写锁做同步,可允许两个不同线程拿同一机器的连接。机器的连接池用普通锁同步,防止两个不同线程同时拿同一个连接。
如果使用方的并发线程是固定的(这种方式一般整个框架代码都是自己实现,没用第三方的框架),则对每个线程启动对每台机器的一个连接,根据负载均衡策略线程内做负载均衡。这种方式最高效,建立的连接少,拿连接时不用锁。检查线程发现有更新时通知工作线程做连接管理的更新。检查线程与工作线程发消息可用环形队列实现的不加锁的线程安全队列来做。检查线程是生产者,工作线程是消费者,工作线程发现有更新时做更新操作,全程无锁实现。这种模型是最高效的。
下面的例子是基于上篇python的grpc示例,使用consul做服务的注册与发现:
1.下载consul
开发形式启动:
./consul agent -dev
2.服务端server.py(启动)
#coding=utf-8
from concurrent import futures
import time
import grpc
import grpchello_pb2
import grpchello_pb2_grpc
import consul
def register(server_name, ip, port):
c = consul.Consul() # 连接consul 服务器,默认是127.0.0.1,可用host参数指定host
print("开始注册服务{}".format(server_name))
check = consul.Check.tcp(ip, port, "10s") # 健康检查的ip,端口,检查时间
c.agent.service.register(server_name, "{}-{}-{}".format(server_name, ip, port), address=ip, port=port, check=check) # 注册服务部分
print("注册服务{}成功".format(server_name))
def unregister(server_name, ip, port):
c = consul.Consul()
print("开始退出服务{}".format(server_name))
c.agent.service.deregister('{}-{}-{}'.format(server_name, ip, port))
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
#继承接口基类,并实现基类中的方法
class MyServer(grpchello_pb2_grpc.gRPCServicer):
def SayHello(self, request, context):
print(request.name)
message = "hello,this is my test result:" + request.name
return grpchello_pb2.HelloReply(message = message)
def Calc(selr, request, context):
ret = 0
if request.para3 == "+":
ret = request.para1 + request.para2
elif request.para3 == "-":
ret = request.para1 - request.para2
elif request.para3 == "*":
ret = request.para1 * request.para2
elif request.para3 == "/":
ret = request.para1 / request.para2
else:
ret = 0
print(str(request.para1) + str(request.para3) + str(request.para2) + "=" + str(ret))
return grpchello_pb2.CalcReply(ret = ret)
def server():
#设置服务线程数
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
#注册服务
grpchello_pb2_grpc.add_gRPCServicer_to_server(MyServer(), server)
#设置服务ip和port
server.add_insecure_port('[::]:50051')
register("test_server", "127.0.0.1", 50051)
print("sever is opening ,waiting for message...")
#非阻塞启动
server.start()
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
server.stop(0)
if __name__ == '__main__':
server()
3.服务端server2.py(启动)
#coding=utf-8
from concurrent import futures
import time
import grpc
import grpchello_pb2
import grpchello_pb2_grpc
import consul
def register(server_name, ip, port):
c = consul.Consul() # 连接consul 服务器,默认是127.0.0.1,可用host参数指定host
print("开始注册服务{}".format(server_name))
check = consul.Check.tcp(ip, port, "10s") # 健康检查的ip,端口,检查时间
c.agent.service.register(server_name, "{}-{}-{}".format(server_name, ip, port), address=ip, port=port, check=check) # 注册服务部分
print("注册服务{}成功".format(server_name))
def unregister(server_name, ip, port):
c = consul.Consul()
print("开始退出服务{}".format(server_name))
c.agent.service.deregister('{}-{}-{}'.format(server_name, ip, port))
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
#继承接口基类,并实现基类中的方法
class MyServer(grpchello_pb2_grpc.gRPCServicer):
def SayHello(self, request, context):
print(request.name)
message = "hello,this is my test result:" + request.name
return grpchello_pb2.HelloReply(message = message)
def Calc(selr, request, context):
print("req.para1:" + str(request.para1))
print("req.para2:" + str(request.para2))
print("req.para3:" + str(request.para3))
ret = 0
if request.para3 == "+":
ret = request.para1 + request.para2
elif request.para3 == "-":
ret = request.para1 - request.para2
elif request.para3 == "*":
ret = request.para1 * request.para2
elif request.para3 == "/":
ret = request.para1 / request.para2
else:
ret = 0
print(str(request.para1) + str(request.para3) + str(request.para2) + "=" + str(ret))
return grpchello_pb2.CalcReply(ret = ret)
def serve():
#设置服务线程数
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
#注册服务
grpchello_pb2_grpc.add_gRPCServicer_to_server(MyServer(), server)
#设置服务ip和port
server.add_insecure_port('[::]:50052')
register("test_server", "127.0.0.1", 50052)
print("sever is opening ,waiting for message...")
#非阻塞启动
server.start()
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
server.stop(0)
if __name__ == '__main__':
serve()
3.客户端client.py(启动)
#coding:utf-8
from __future__ import print_function
import grpc
import time
import grpchello_pb2
import grpchello_pb2_grpc
from server_group import *
#通道池的设置
#监视服务:发现有变化时,要看和之前的比较,如果是增加,就新建一组该服务的通道,如果是减少,就删除该服务的对应组的通道
def run():
#这样创建的通道是线程安全的通道
channel = global_server_group.get_channel()
print("channel:", channel)
stub = grpchello_pb2_grpc.gRPCStub(channel)
print("stub:", stub)
response = stub.Calc(grpchello_pb2.CalcRequest(para1 = 2, para2 = 1, para3 = "+"))
print(response)
count = 0
while True:
count += 1
channel = global_server_group.get_channel()
try:
print("channel:", channel)
stub = grpchello_pb2_grpc.gRPCStub(channel)
stub.Calc(grpchello_pb2.CalcRequest(para1 = count, para2 = 1, para3 = "+"))
time.sleep(1)
except Exception as err:
global_server_group.del_dead_channel(channel)
print(err)
continue
if __name__ == '__main__':
start_server_check_thread()
time.sleep(3)
run()
server_group.py(健康服务的定时获取,更新等服务管理)
#coding:utf-8
import consul
import requests
import json
import gevent
import time
import grpc
from gevent import monkey
#import Queue
monkey.patch_all()
class server_group(object):
def __init__(self, address):
self.channel = []
self.address = address#参数是一个集合类型
self.address_channel = {}
self.channel_address = {}
for add in self.address:
#这个是线程安全通道
_channel = grpc.insecure_channel(add)
self.channel.append(_channel)
self.address_channel[add] = _channel
self.channel_address[_channel] = add
self.index = 0
self.len = len(self.channel)
self.RWLock = RWLock()
def get_channel(self):
self.RWLock.read_acquire()
_channel = self.channel[self.index]
self.index += 1
if self.index >= self.len:
self.index = 0
print("len:" + str(self.len) + "\tindex:" + str(self.index))
self.RWLock.read_release()
return _channel
def del_dead_channel(self, _channel):
self.RWLock.write_acquire()
add = self.channel_address.get(_channel)
if add and _channel:
del self.address_channel[add]
del self.channel_address[_channel]
self.channel.remove(_channel)
self.address.remove(add)
self.index = 0
self.len = len(self.channel)
self.RWLock.write_release()
return True
#要增加的服务,要删除的服务,交集,老的减交集为要删除的,新的减交集为要增加的
#加读写锁同步,读时允计多个线程同时拿同一个index
def update_server_group(self, address):
if self.address == address:
print("没有更新")
return False
for add in self.address:
print("老的add:" + add)
for add in address:
print("新的add:" + add)
print("进入更新")
self.RWLock.write_acquire()
#交集
com = (self.address).intersection(address)
del_address = self.address - com
add_address = address - com
self.address = com.union(add_address)
for add in del_address:
_channel = self.address_channel.get(add)
if _channel:
del self.address_channel[add]
del self.channel_address[_channel]
self.channel.remove(_channel)
for add in add_address:
_channel = grpc.insecure_channel(add)
self.channel.append(_channel)
self.address_channel[add] = _channel
self.channel_address[_channel] = add
self.index = 0
self.len = len(self.channel)
self.RWLock.write_release()
return True
import threading
class RWLock(object):
def __init__(self):
self.rlock = threading.Lock()
self.wlock = threading.Lock()
self.reader = 0
#写加锁
def write_acquire(self):
self.wlock.acquire()
def write_release(self):
self.wlock.release()
#读加锁
def read_acquire(self):
self.rlock.acquire()
self.reader += 1
if self.reader == 1:
self.wlock.acquire()
self.rlock.release()
def read_release(self):
self.rlock.acquire()
self.reader -= 1
if self.reader == 0:
self.wlock.release()
self.rlock.release()
def get_health_server():
address = set()
#通过如下方式可以获取服务端健康的服务
url = "http://localhost:8500/v1/health/service/test_server?passing"
resp = requests.get(url)
#print(resp)
#print(resp.text)
my_dict = json.loads(resp.text)
for value in my_dict:
service = value["Service"]
ip_port = str(service["Address"]) + ":" + str(service["Port"])
#print(ip_port)
address.add(ip_port)
return address
def server_check():
while True:
address = get_health_server()
for add in address:
print(add)
global_server_group.update_server_group(address)
time.sleep(1)
global_server_group = server_group(set())
def start_server_check_gevent():
gevent.spawn(server_check)
def start_server_check_thread():
t = threading.Thread(target = server_check)
t.start()