項目介紹
兩階段提交項目主要是實際用代碼演示覆現一下,兩階段提交的執行過程,僅供學習參考。本次主要分析的版本爲V1版本,主要實現的流程包括服務端的基礎架構編寫,客戶端的基礎架構編寫,完成事務提交的過程。
兩階段項目的用例模型
客戶端註冊
用例名稱 | 客戶端啓動註冊 |
---|---|
主要參與者 | 客戶端 |
涉及關注點 | 客戶端:希望將自己註冊到服務器端,從而能夠向服務端提交事務數據或者接受其他客戶端的提交事務的數據 |
前置條件 | 服務器正常運行(後續可考慮設置固定的客戶端數量) |
後置條件 | 註冊完成後,返回註冊成功信息 |
基本流程 | 1.客戶端啓動,客戶端啓動一個線程去連接服務器;2.服務端處理請求並返回正常 |
客戶端發起事務提交數據
用例名稱 | 客戶端發起事務提交數據 |
---|---|
主要參與者 | 客戶端,服務端 |
涉及關注點 | 客戶端:客戶端先將待提交數據發送給服務端,服務端將數據提交給其他客戶端。 |
前置條件 | 服務器正常運行,並且沒有其他新加入的客戶端 |
後置條件 | 客戶端接受服務端返回的數據提交成功 |
基本流程 | 1.客戶端提交事務數據到服務端;2.服務端此時將數據發送給其他客戶端;3.此時客戶端進入等待狀態;4.服務端等待其他客戶端的反饋;5.如果其他所有客戶端都反饋成功,則向該客戶端發送數據提交成功消息,並讓通知其他所有客戶端提交該數據 |
客戶端發送終止事務
用例名稱 | 客戶端發送終止事務 |
---|---|
主要參與者 | 客戶端,服務端 |
涉及關注點 | 客戶端:客戶端已經將事務發送給服務端,服務端等待其他客戶端的事務提交成功反饋。 |
前置條件 | 服務器正常運行,並且沒有其他新加入的客戶端 |
後置條件 | 接收到事務終止消息 |
基本流程 | 1.客戶端提交事務數據到服務端;2.服務端此時將數據發送給其他客戶端;3.此時客戶端進入等待狀態;4.服務端等待其他客戶端的反饋;5.其中有任意一個客戶端發送abort消息,則向該客戶端發送停止消息到其他客戶端,並讓其他客戶端終止該事務。 |
本文V1版本主要就是圍繞這三種基本用例模型來進行實現。包括客戶端註冊,事務的提交與客戶端發出的終止信號。
兩階段項目的流程圖
服務端的流程圖
服務端的流程圖,主要的實現思路參考IO複用事件驅動來進行數據的處理,並且實現思路也是通過單線程來實現。
客戶端的流程圖
客戶端的功能,主要就是獲取用戶輸入的指令或者數據,將該數據發送給服務端,然後客戶端需要監聽來自服務端的數據,是否有服務端的消息發送回來,如果有消息則處理該消息。
兩階段項目V1版本的時序圖
兩階段項目V1代碼
服務端代碼
import selectors
import socket
from uuid import uuid4
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(thread)d - %(lineno)d - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
HEAD_DELIMITER = "\r\n"
BODY_DELIMITER = "\n"
"""
code: msg
404 : 未找到相關的處理
500 : 處理異常出錯
401 :
200 : 處理成功
"""
class BaseHandler(object):
type = "base"
head_delimiter = HEAD_DELIMITER
body_delimiter = BODY_DELIMITER
body = "error"
def __init__(self, request_data=None, c=None):
self.request_data = request_data
self.protocl = c
def serilizer(self):
raise NotImplementedError
def serilizer_data(self, body):
if not body:
body = self.body
length = len(BODY_DELIMITER) + len(self.type) + len(body)
return "{0}{1}{2}{3}{4}".format(length, HEAD_DELIMITER, self.type, BODY_DELIMITER, body)
def finish(self, body):
content = self.serilizer_data(body)
self.protocl.write_result(content)
def deserialize(self, data):
return self.serilizer_data(data)
class ErrorMsg(BaseHandler):
type = "error"
def serilizer(self):
return self.finish("404")
class RegisterHandler(BaseHandler):
type = "register"
def serilizer(self):
name = self.request_data
dispatch.register_client(name, self.protocl.stream)
logger.info(dispatch.clients)
return self.finish("200")
def deserialize(self, data):
return self.serilizer_data(data)
class MultiHandler(BaseHandler):
body_parse = None
def parse_body(self):
raise NotImplementedError
class PreSubmit(MultiHandler):
type = "presubmit"
body_parse = "\t"
def write_body(self, xid, key, value):
content = "{0}{1}{2}{3}{4}".format(xid, self.body_parse, key, self.body_parse, value)
return self.serilizer_data(content)
def parse_body(self):
loc = self.request_data.find(self.body_parse)
if loc != -1:
client = self.request_data[:loc]
data = self.request_data[(loc + len(self.body_parse)):]
loc_v = data.find(self.body_parse)
if loc_v != -1:
key = data[:loc_v]
value = data[(loc_v + len(self.body_parse)):]
return client, key, value
return None, None, None
def serilizer(self):
# 檢查有什麼問題 沒什麼問題則 返回ok
xid, key, value = self.parse_body()
logger.info("client recv : xid : {0} key : {1} value : {2}".format(xid, key, value))
return xid
def deserialize(self, data):
return self.serilizer_data(data)
class ClientPostData(MultiHandler):
type = "client_start"
body_parse = "\t"
def write_body(self, name, key, value):
content = "{0}{1}{2}{3}{4}".format(name, self.body_parse, key, self.body_parse, value)
return self.serilizer_data(content)
def parser_body(self):
loc = self.request_data.find(self.body_parse)
if loc != -1:
client = self.request_data[:loc]
data = self.request_data[(loc+len(self.body_parse)):]
loc_v = data.find(self.body_parse)
if loc_v != -1:
key = data[:loc_v]
value = data[(loc_v+len(self.body_parse)):]
return client, key, value
return None, None, None
def serilizer(self):
# 開始將數據提交到各個參與者
# 生成一個事務ID
xid = str(uuid4())
client_id, key, value = self.parser_body()
logger.info("client_id : {0} key : {1} value : {2}".format(client_id, key, value))
transaction = Transaction(dispatch, client_id, xid)
transaction.presubmit(client_id, key, value, self)
def deserialize(self, data):
return self.serilizer_data(data)
class PreperPost(BaseHandler):
type = "preperpost"
class PreSubmitAck(MultiHandler):
"""
客戶端調用返回ack
"""
type = "presubmitack"
body_parse = "\t"
def write_body(self, xid, client_id):
content = "{0}{1}{2}".format(xid, self.body_parse, client_id)
return self.serilizer_data(content)
def parser_body(self):
loc = self.request_data.find(self.body_parse)
if loc != -1:
xid = self.request_data[:loc]
client_id = self.request_data[(loc + len(self.body_parse)):]
return xid, client_id
return None, None
def serilizer(self):
logger.info("request_data {0} dispath prepares {1}".format(self.request_data, dispatch.prepares))
xid, client_id = self.parser_body()
logger.info(" parser body xid : {0} client_id : {1}".format(xid, client_id))
if xid in dispatch.prepares:
tran = dispatch.prepares[xid]
tran.presubmit_ack()
logger.info("vote {0} dispath prepares {1}".format(tran, dispatch.prepares))
class SubmitAbort(BaseHandler):
"""
客戶端調用 終止提交
"""
type = "submitabort"
body_parse = "\t"
def write_body(self, xid, client_id):
content = "{0}{1}{2}".format(xid, self.body_parse, client_id)
return self.serilizer_data(content)
def parser_body(self):
loc = self.request_data.find(self.body_parse)
if loc != -1:
xid = self.request_data[:loc]
client_id = self.request_data[(loc + len(self.body_parse)):]
return xid, client_id
return None, None
def serilizer(self):
logger.info("request_data {0} dispath prepares {1}".format(self.request_data, dispatch.prepares))
xid, client_id = self.parser_body()
logger.info(" parser body xid : {0} client_id : {1}".format(xid, client_id))
if xid in dispatch.prepares:
tran = dispatch.prepares[xid]
logger.info("vote {0} dispath prepares {1}".format(tran, dispatch.prepares))
# 如果返回的事務id不存在,則證明本次的事務失敗需要向client發送終止
tran.tran_abort(client_id)
return self.finish("abort ok")
class Transaction(object):
"""
事務處理類
"""
def __init__(self, dispatch, client_id, xid):
self.dispatch = dispatch
self.originator = client_id
# 當前設計爲一次只能一個客戶端發起一個事務
self.participants_nums = len(dispatch.clients) - 1
self.participants_vote = 0
self.xid = xid
def finish(self):
if self.xid in self.dispatch.prepares:
self.dispatch.prepares.pop(self.xid)
def presubmit(self, client_id, key, value, handler):
p = PreSubmit()
if client_id is None:
handler.finish("client_id none")
return
for client in dispatch.clients:
stream = dispatch.clients[client]
logger.info(
"client : {0} clients : {1}".format(client, dispatch.clients))
if client != client_id:
result = p.write_body(self.xid, key, value)
logger.info(" stream result : {0}".format(result))
stream.write(result)
logger.info(" stream {0} ".format(stream))
self.dispatch.register_tran(self.xid, self)
def presubmit_ack(self):
self.participants_vote += 1
if self.participants_vote == self.participants_nums:
for client in dispatch.clients:
stream = dispatch.clients[client]
if self.originator == client:
# 此時證明可以向所有的參與者 發送提交指令
c = ClientPostData()
logger.info("stream {0}".format(stream))
stream.write(c.serilizer_data("finish"))
else:
p = PreperPost()
logger.info("stream {0}".format(stream))
stream.write(p.serilizer_data(self.xid))
self.finish()
logger.info("after {0} ".format(self.dispatch.prepares))
def tran_abort(self, client_id):
for client in dispatch.clients:
stream = dispatch.clients[client]
if not stream.check_client_id(client_id):
abort = SubmitAbort()
stream.write(abort.write_body(self.xid, client_id))
self.finish()
logger.info("after {0} ".format(self.dispatch.prepares))
class RegisterClient(object):
"""
客戶端註冊的客戶端
"""
def __init__(self, stream, client_id):
self.stream = stream
self.client_id = client_id
def write(self, data):
self.stream.write(data)
self.stream.update_io_state(selectors.EVENT_WRITE)
def update_io_state(self, mask):
self.stream.update_io_state(mask)
def check_stream(self, stream):
return self.stream == stream
def check_client_id(self, client_id):
return self.client_id == client_id
class Dispatch(object):
def __init__(self, mess):
self.mess = mess
self.messages = {}
self.clients = {}
self.prepares = {}
self.init()
def init(self):
for mes in self.mess:
self.register_message(mes)
def register_tran(self, xid, tran):
self.prepares[xid] = tran
def register_client(self, client_id, stream):
self.clients[client_id] = RegisterClient(stream, client_id)
def register_message(self, message):
if not issubclass(message, BaseHandler):
return
if hasattr(message, "type"):
mes_type = getattr(message, "type")
if mes_type:
self.messages[mes_type] = message
def execute(self, type, request_data, protocl):
if type in self.messages.keys():
self.messages[type](request_data, protocl).serilizer()
else:
ErrorMsg(request_data, protocl).serilizer()
def close(self, stream):
remove_client = None
for key in self.clients:
if self.clients[key].check_stream(stream):
logger.info("found error key: {0} straem: {1} ".format(key, stream))
remove_client = key
if remove_client:
self.clients.pop(remove_client)
messages = [RegisterHandler, ClientPostData, PreSubmitAck, SubmitAbort]
dispatch = Dispatch(messages)
logger.info(dispatch.messages)
class CooProtocl(object):
def __init__(self, stream):
self.stream = stream
self._read_delimiter = HEAD_DELIMITER
self._body_delimiter = BODY_DELIMITER
self.stream.read_util(self._read_delimiter, self.parse_length)
def parse_length(self, data):
body_length = data[:len(self._read_delimiter)]
if body_length.isdigit():
self.stream.read_nums_util(int(body_length), self.parse_body)
def write_result(self, data):
self.stream.write(data)
def parse_body(self, data):
loc = data.find(self._body_delimiter)
msg_type = data[:loc]
msg_content = data[(loc+len(self._body_delimiter)):]
logger.info("{0} {1}".format(msg_type, msg_content))
dispatch.execute(msg_type, msg_content, self)
self.stream.read_util(self._read_delimiter, self.parse_length)
class Stream(object):
def __init__(self, conn, addr, server):
self.conn = conn
self.addr = addr
self.conn.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024)
self.conn.setblocking(False)
self.recv_buffer = ""
self.write_buffer = b""
self._read_delimiter = None
self._read_callback = None
self._read_length = None
self._read_nums_callback = None
self.server = server
self.state = selectors.EVENT_READ
self.server.selector.register(self.conn, self.state, self.handle_event)
def _consume(self, loc):
data = self.recv_buffer[:loc]
self.recv_buffer = self.recv_buffer[loc:]
return data
def update_io_state(self, mask):
self.state = mask
self.server.modify(self.conn, mask, self.handle_event)
def read_util(self, delimiter, callback):
loc = self.recv_buffer.find(delimiter)
if loc != -1:
callback(self._consume(loc+len(delimiter)))
return
# 如果沒有解析對的格式則表示還需要等待數據輸入
self._read_delimiter = delimiter
self._read_callback = callback
self.update_io_state(selectors.EVENT_READ)
def read_nums_util(self, read_length, callback):
if len(self.recv_buffer) >= read_length:
callback(self._consume(read_length))
return
self._read_length = read_length
self._read_nums_callback = callback
self.update_io_state(selectors.EVENT_READ)
def close(self):
if self.state != -1:
self.state = -1
dispatch.close(self)
self.server.modify(self.conn, self.state)
def handle_read(self):
try:
data = self.conn.recv(1)
except socket.error as e:
if e.errno in (socket.EAGAIN, socket.EWOULDBLOCK):
logger.info("connectreset error : {0}".format(e))
return
else:
logger.info("handler read error : {0}".format(e))
self.close()
return
if not data:
self.close()
return
self.recv_buffer += data.decode("utf-8")
if self._read_delimiter:
loc = self.recv_buffer.find(self._read_delimiter)
if loc != -1:
callback = self._read_callback
data = self._consume(loc+len(self._read_delimiter))
self._read_delimiter = None
self._read_callback = None
callback(data)
elif self._read_length:
if len(self.recv_buffer) >= self._read_length:
callback = self._read_nums_callback
data = self._consume(self._read_length)
self._read_length = None
self._read_nums_callback = None
callback(data)
def write(self, data):
self.write_buffer += data.encode("utf-8")
def handle_write(self):
while self.write_buffer:
try:
size = self.conn.send(self.write_buffer)
except socket.error as e:
if e.errno in (socket.EWOULDBLOCK, socket.EAGAIN):
logger.error("send error : {0}".format(e))
break
else:
self.close()
logger.error("handle write close error : {0}".format(e))
return
else:
self.write_buffer = self.write_buffer[size:]
logger.info("left data : {0}".format(self.write_buffer))
def handle_event(self, conn, mask):
if mask == selectors.EVENT_READ:
self.handle_read()
elif mask == selectors.EVENT_WRITE:
self.handle_write()
if self.state == -1:
return
state = 0
if self._read_length or self._read_delimiter:
state = selectors.EVENT_READ | state
if self.write_buffer:
state = selectors.EVENT_WRITE | state
if state != self.state:
logger.info("290 {0}".format(state, self.state))
self.state = state
self.update_io_state(state)
class Server(object):
def __init__(self, ip=None, port=None):
self.ip = ip or "127.0.0.1"
self.port = port or 4545
self.sock = None
self.running = False
self.selector = selectors.DefaultSelector()
self.init()
def init(self):
self.sock = socket.socket()
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.ip, self.port))
self.sock.listen(100)
self.sock.setblocking(False)
self.selector.register(self.sock, selectors.EVENT_READ, self.accept)
def modify(self, conn, mask, callback=None):
logger.info("302 {0} {1} {2}".format(conn, mask, callback))
if mask != -1:
self.selector.modify(conn, mask, callback)
else:
self.selector.unregister(conn)
def accept(self, conn, mask):
conn, addr = conn.accept()
logger.info("recv conn {0}".format(conn))
s = Stream(conn, addr, self)
CooProtocl(s)
def start(self):
if not self.running:
self.running = True
while self.running:
events = self.selector.select(timeout=1)
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
def stop(self):
if self.running:
self.running = False
if __name__ == '__main__':
s = Server()
s.start()
客戶端代碼
import socket
import selectors
import threading
import click
import random
import time
from prompt_toolkit import prompt
from test_select import *
logger = logging.getLogger()
presubmit_flag = True
abort_flag = False
class Client(object):
def __init__(self, name, ip=None, port=None):
self.ip = ip or "127.0.0.1"
self.port = port or 4545
self.client_name = name
self.sock = None
self.running = False
self.recv_buffer = ""
self.write_buffer = b""
self.selector = selectors.DefaultSelector()
self.init()
def init(self):
self.sock = socket.socket()
self.sock.connect((self.ip, self.port))
self.sock.setblocking(False)
self.selector.register(self.sock, selectors.EVENT_READ, self.read)
def handle_execute(self, msg_type, msg_content):
if presubmit_flag and msg_type == "presubmit":
xid = PreSubmit(request_data=msg_content).serilizer()
if abort_flag:
abort = SubmitAbort()
self.write_result(abort.write_body(xid, self.client_name).encode("utf-8"))
else:
p_ack = PreSubmitAck()
self.write_result(p_ack.write_body(xid, self.client_name).encode("utf-8"))
def read(self, conn, mask):
data = conn.recv(1000)
if data:
logger.info("client recv data : {0}".format(repr(data)))
self.recv_buffer += data.decode("utf-8")
while True:
loc = self.recv_buffer.find(HEAD_DELIMITER)
logger.info("loc index : {0} recv buffer {1}".format(loc, self.recv_buffer))
if loc != -1:
length = int(self.recv_buffer[:loc])
self.recv_buffer = self.recv_buffer[(loc+len(HEAD_DELIMITER)):]
if length <= len(self.recv_buffer):
data = self.recv_buffer[:length]
body_loc = data.find(BODY_DELIMITER)
if body_loc != -1:
msg_type = data[:body_loc]
msg_content = data[(body_loc+len(BODY_DELIMITER)):]
logger.info("parse from server {0} {1} ".format(msg_type, msg_content))
self.handle_execute(msg_type, msg_content)
self.recv_buffer = self.recv_buffer[length:]
else:
break
else:
self.close()
def close(self):
logger.info("client close")
self.selector.unregister(self.sock)
self.sock.close()
if self.running:
time.sleep(1)
self.init()
def handle_send(self):
while self.write_buffer:
try:
size = self.sock.send(self.write_buffer)
except socket.error as e:
if e.errno in (socket.EWOULDBLOCK, socket.EAGAIN):
logger.error("send error : {0}".format(e))
break
else:
logger.error("handle write close error : {0}".format(e))
return
else:
self.write_buffer = self.write_buffer[size:]
def write_result(self, data):
if self.sock:
logger.info("send {0}".format(repr(data)))
while data:
try:
size = self.sock.send(data)
except socket.error as e:
if e.errno in (socket.EWOULDBLOCK, socket.EAGAIN):
logger.error("send error : {0}".format(e))
break
else:
logger.error("handle write close error : {0}".format(e))
return
else:
logger.info("send size {0}".format(size))
data = data[size:]
def stop(self):
if self.running:
self.running = False
print("close")
def start(self):
t = threading.Thread(target=self.worker)
t.setDaemon(False)
t.start()
def worker(self):
if not self.running:
self.running = True
print(self.running)
while self.running:
events = self.selector.select(timeout=1)
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
self.handle_send()
print("work over")
class Command(object):
def __init__(self, name):
self.name = name
def parse_command(self, args):
raise NotImplementedError
class SendCommand(Command):
def __init__(self, name, callback):
super().__init__(name)
self.callback = callback
def parse_command(self, args):
if len(args) != 3:
print(" {0} args must be 3 {1}".format(self.name, args))
return
key, value = args[1], args[2]
self.callback(key, value)
class AbortCommand(Command):
def parse_command(self, args):
if len(args) != 2:
print(" {0} args must be 2 ".format(self.name))
return
data = {
"true": True,
"false": False,
}
if args[1] not in data:
print(" {0} value not correct ".format(self.name))
return
global abort_flag
abort_flag = data[args[1]]
print("abort_flag : {0}".format(abort_flag))
class PreSubmitCommand(Command):
def parse_command(self, args):
if len(args) != 2:
print(" {0} args must be 2 ".format(self.name))
return
data = {
"true": True,
"false": False,
}
if args[1] not in data:
print(" {0} value not correct ".format(self.name))
return
global presubmit_flag
presubmit_flag = data[args[1]]
print(" presubmit_flag : {0}".format(presubmit_flag))
class CommandDispatch(object):
def __init__(self):
self.commands = {}
def register(self, command):
if isinstance(command, Command):
self.commands[command.name] = command
def execute(self, command_name, args):
if command_name not in self.commands:
print(" commands not found ")
return
self.commands[command_name].parse_command(args)
@click.command()
@click.option('--name', default=str(random.randint(1, 1000)), help="must unique client id")
def main(name):
c = Client(name=name)
c.start()
c.write_result(RegisterHandler().serilizer_data(name).encode("utf-8"))
def write(key, value):
c.write_result(ClientPostData().write_body(name, key, value).encode("utf-8"))
dis = CommandDispatch()
dis.register(AbortCommand("abort"))
dis.register(PreSubmitCommand("presubmit"))
dis.register(SendCommand("send", write))
while True:
answer = prompt('input> ').strip()
args = answer.split(" ")
counts = args.count("")
for i in range(counts):
args.remove("")
if len(args) >= 2:
command = args[0]
print("args : {0}".format(args))
dis.execute(command, args)
else:
print("args not correct")
if __name__ == '__main__':
main()
代碼聯調
首先啓動服務端,讓服務端等待請求
(venv) wuzideMacBook-Pro:2pc wuzi$ python 2pc_server.py
2019-11-01 21:35:56,379 - 4603864512 - 346 - INFO - {'register': <class '__main__.RegisterHandler'>, 'client_start': <class '__main__.ClientPostData'>, 'presubmitack': <class '__main__.PreSubmitAck'>, 'submitabort': <class '__main__.SubmitAbort'>}
接着我們就啓動三個客戶端來處理分別爲client1,client2,client3。
(venv) wuzideMacBook-Pro:2pc wuzi$ python 2pc_client.py --name=client1
2019-11-01 21:37:01,343 - 4439086528 - 437 - INFO - {'type1': <class 'test_select.MsgOne'>, 'type2': <class 'test_select.MsgTwo'>, 'register': <class 'test_select.RegisterHandler'>, 'client_start': <class 'test_select.ClientPostData'>, 'presubmitack': <class 'test_select.PreSubmitAck'>, 'submitabort': <class 'test_select.SubmitAbort'>}
True
2019-11-01 21:37:01,346 - 4439086528 - 96 - INFO - send b'16\r\nregister\nclient1'
2019-11-01 21:37:01,346 - 4439086528 - 108 - INFO - send size 20
2019-11-01 21:37:01,348 - 123145470545920 - 49 - INFO - client recv data : b'12\r\nregister\n200'
2019-11-01 21:37:01,348 - 123145470545920 - 53 - INFO - loc index : 2 recv buffer 12
register
200
2019-11-01 21:37:01,348 - 123145470545920 - 63 - INFO - parse from server register 200
2019-11-01 21:37:01,349 - 123145470545920 - 53 - INFO - loc index : -1 recv buffer
input>
(venv) wuzideMacBook-Pro:2pc wuzi$ python 2pc_client.py --name client2
2019-11-01 21:37:29,948 - 4399826368 - 437 - INFO - {'type1': <class 'test_select.MsgOne'>, 'type2': <class 'test_select.MsgTwo'>, 'register': <class 'test_select.RegisterHandler'>, 'client_start': <class 'test_select.ClientPostData'>, 'presubmitack': <class 'test_select.PreSubmitAck'>, 'submitabort': <class 'test_select.SubmitAbort'>}
True
2019-11-01 21:37:29,950 - 4399826368 - 96 - INFO - send b'16\r\nregister\nclient2'
2019-11-01 21:37:29,951 - 4399826368 - 108 - INFO - send size 20
2019-11-01 21:37:29,952 - 123145410183168 - 49 - INFO - client recv data : b'12\r\nregister\n200'
2019-11-01 21:37:29,952 - 123145410183168 - 53 - INFO - loc index : 2 recv buffer 12
register
200
2019-11-01 21:37:29,952 - 123145410183168 - 63 - INFO - parse from server register 200
2019-11-01 21:37:29,953 - 123145410183168 - 53 - INFO - loc index : -1 recv buffer
input>
(venv) wuzideMacBook-Pro:2pc wuzi$ python 2pc_client.py --name=client3
2019-11-01 21:38:17,543 - 4597429696 - 437 - INFO - {'type1': <class 'test_select.MsgOne'>, 'type2': <class 'test_select.MsgTwo'>, 'register': <class 'test_select.RegisterHandler'>, 'client_start': <class 'test_select.ClientPostData'>, 'presubmitack': <class 'test_select.PreSubmitAck'>, 'submitabort': <class 'test_select.SubmitAbort'>}
True
2019-11-01 21:38:17,546 - 4597429696 - 96 - INFO - send b'16\r\nregister\nclient3'
2019-11-01 21:38:17,547 - 4597429696 - 108 - INFO - send size 20
2019-11-01 21:38:17,549 - 123145484255232 - 49 - INFO - client recv data : b'12\r\nregister\n200'
2019-11-01 21:38:17,549 - 123145484255232 - 53 - INFO - loc index : 2 recv buffer 12
register
200
2019-11-01 21:38:17,549 - 123145484255232 - 63 - INFO - parse from server register 200
2019-11-01 21:38:17,549 - 123145484255232 - 53 - INFO - loc index : -1 recv buffer
input>
事務提交測試
讓client1在終端中輸入需要提交的數據;
input> send key data
args : ['send', 'key', 'data']
2019-11-01 21:39:14,687 - 4439086528 - 96 - INFO - send b'29\r\nclient_start\nclient1\tkey\tdata'
2019-11-01 21:39:14,687 - 4439086528 - 108 - INFO - send size 33
2019-11-01 21:39:14,717 - 123145470545920 - 49 - INFO - client recv data : b'19\r\nclient_start\nfinish'
input> 2019-11-01 21:39:14,719 - 123145470545920 - 53 - INFO - loc index : 2 recv buffer 19
client_start
finish
2019-11-01 21:39:14,719 - 123145470545920 - 63 - INFO - parse from server client_start finish
2019-11-01 21:39:14,719 - 123145470545920 - 53 - INFO - loc index : -1 recv buffer
此時client2與client3收到的數據如下;
2019-11-01 21:39:14,690 - 123145410183168 - 49 - INFO - client recv data : b'55\r\npresubmit\n5d677fb2-01dd-4962-b66b-24d3b572ca1d\tkey\tdata'
2019-11-01 21:39:14,691 - 123145410183168 - 53 - INFO - loc index : 2 recv buffer 55
presubmit
5d677fb2-01dd-4962-b66b-24d3b572ca1d key data
2019-11-01 21:39:14,691 - 123145410183168 - 63 - INFO - parse from server presubmit 5d677fb2-01dd-4962-b66b-24d3b572ca1d key data
2019-11-01 21:39:14,691 - 123145410183168 - 188 - INFO - client recv : xid : 5d677fb2-01dd-4962-b66b-24d3b572ca1d key : key value : data
2019-11-01 21:39:14,691 - 123145410183168 - 96 - INFO - send b'57\r\npresubmitack\n5d677fb2-01dd-4962-b66b-24d3b572ca1d\tclient2'
2019-11-01 21:39:14,691 - 123145410183168 - 108 - INFO - send size 61
2019-11-01 21:39:14,691 - 123145410183168 - 53 - INFO - loc index : -1 recv buffer
2019-11-01 21:39:14,709 - 123145410183168 - 49 - INFO - client recv data : b'47\r\npreperpost\n5d677fb2-01dd-4962-b66b-24d3b572ca1d'
2019-11-01 21:39:14,710 - 123145410183168 - 53 - INFO - loc index : 2 recv buffer 47
preperpost
5d677fb2-01dd-4962-b66b-24d3b572ca1d
2019-11-01 21:39:14,710 - 123145410183168 - 63 - INFO - parse from server preperpost 5d677fb2-01dd-4962-b66b-24d3b572ca1d
2019-11-01 21:39:14,710 - 123145410183168 - 53 - INFO - loc index : -1 recv buffer
2019-11-01 21:39:14,695 - 123145484255232 - 49 - INFO - client recv data : b'55\r\npresubmit\n5d677fb2-01dd-4962-b66b-24d3b572ca1d\tkey\tdata'
2019-11-01 21:39:14,695 - 123145484255232 - 53 - INFO - loc index : 2 recv buffer 55
presubmit
5d677fb2-01dd-4962-b66b-24d3b572ca1d key data
2019-11-01 21:39:14,695 - 123145484255232 - 63 - INFO - parse from server presubmit 5d677fb2-01dd-4962-b66b-24d3b572ca1d key data
2019-11-01 21:39:14,695 - 123145484255232 - 188 - INFO - client recv : xid : 5d677fb2-01dd-4962-b66b-24d3b572ca1d key : key value : data
2019-11-01 21:39:14,695 - 123145484255232 - 96 - INFO - send b'57\r\npresubmitack\n5d677fb2-01dd-4962-b66b-24d3b572ca1d\tclient3'
2019-11-01 21:39:14,696 - 123145484255232 - 108 - INFO - send size 61
2019-11-01 21:39:14,696 - 123145484255232 - 53 - INFO - loc index : -1 recv buffer
2019-11-01 21:39:14,711 - 123145484255232 - 49 - INFO - client recv data : b'47\r\npreperpost\n5d677fb2-01dd-4962-b66b-24d3b572ca1d'
2019-11-01 21:39:14,711 - 123145484255232 - 53 - INFO - loc index : 2 recv buffer 47
preperpost
5d677fb2-01dd-4962-b66b-24d3b572ca1d
2019-11-01 21:39:14,711 - 123145484255232 - 63 - INFO - parse from server preperpost 5d677fb2-01dd-4962-b66b-24d3b572ca1d
2019-11-01 21:39:14,711 - 123145484255232 - 53 - INFO - loc index : -1 recv buffer
從日誌中可以看出,數據以及提交到了本地,並且通過了提交。此時可以看出client1發出的事務提交,是得到了client2與client3的答覆之後,就確認提交成功了。基本的事務提交功能完成。
事務中止測試
假如還是由client1發送事務數據,此時讓client2在接受到是否可以進行事務提交的時候發揮abort信息,首先在client2中輸入如下數據更改本例中的測試標誌位;
input> abort true
args : ['abort', 'true']
abort_flag : True
input>
此時我們繼續在client1中輸入數據;
send key2 value2
args : ['send', 'key2', 'value2']
2019-11-01 21:47:36,660 - 4439086528 - 96 - INFO - send b'32\r\nclient_start\nclient1\tkey2\tvalue2'
2019-11-01 21:47:36,661 - 4439086528 - 108 - INFO - send size 36
2019-11-01 21:47:36,674 - 123145470545920 - 49 - INFO - client recv data : b'56\r\nsubmitabort\n85ccd0b5-d883-4550-9726-16da44abb084\tclient2'
2019-11-01 21:47:36,674 - 123145470545920 - 53 - INFO - loc index : 2 recv buffer 56
submitabort
85ccd0b5-d883-4550-9726-16da44abb084 client2
2019-11-01 21:47:36,674 - 123145470545920 - 63 - INFO - parse from server submitabort 85ccd0b5-d883-4550-9726-16da44abb084 client2
2019-11-01 21:47:36,674 - 123145470545920 - 53 - INFO - loc index : -1 recv buffer
此時client2的日誌如下;
2019-11-01 21:47:36,662 - 123145410183168 - 49 - INFO - client recv data : b'58\r\npresubmit\n85ccd0b5-d883-4550-9726-16da44abb084\tkey2\tvalue2'
2019-11-01 21:47:36,662 - 123145410183168 - 53 - INFO - loc index : 2 recv buffer 58
presubmit
85ccd0b5-d883-4550-9726-16da44abb084 key2 value2
2019-11-01 21:47:36,663 - 123145410183168 - 63 - INFO - parse from server presubmit 85ccd0b5-d883-4550-9726-16da44abb084 key2 value2
2019-11-01 21:47:36,663 - 123145410183168 - 188 - INFO - client recv : xid : 85ccd0b5-d883-4550-9726-16da44abb084 key : key2 value : value2
2019-11-01 21:47:36,663 - 123145410183168 - 96 - INFO - send b'56\r\nsubmitabort\n85ccd0b5-d883-4550-9726-16da44abb084\tclient2'
2019-11-01 21:47:36,663 - 123145410183168 - 108 - INFO - send size 60
2019-11-01 21:47:36,663 - 123145410183168 - 53 - INFO - loc index : -1 recv buffer
2019-11-01 21:47:36,666 - 123145410183168 - 49 - INFO - client recv data : b'20\r\nsubmitabort\nabort ok'
2019-11-01 21:47:36,666 - 123145410183168 - 53 - INFO - loc index : 2 recv buffer 20
submitabort
abort ok
2019-11-01 21:47:36,666 - 123145410183168 - 63 - INFO - parse from server submitabort abort ok
2019-11-01 21:47:36,666 - 123145410183168 - 53 - INFO - loc index : -1 recv buffer
從日誌中可以看出,在接受到presubmit消息的時候,此時client2返回的是abort的消息,並且服務器返回了一條abort成功的消息。繼續分析client3的消息。
2019-11-01 21:47:36,663 - 123145484255232 - 49 - INFO - client recv data : b'58\r\npresubmit\n85ccd0b5-d883-4550-9726-16da44abb084\tkey2\tvalue2'
2019-11-01 21:47:36,663 - 123145484255232 - 53 - INFO - loc index : 2 recv buffer 58
presubmit
85ccd0b5-d883-4550-9726-16da44abb084 key2 value2
2019-11-01 21:47:36,663 - 123145484255232 - 63 - INFO - parse from server presubmit 85ccd0b5-d883-4550-9726-16da44abb084 key2 value2
2019-11-01 21:47:36,663 - 123145484255232 - 188 - INFO - client recv : xid : 85ccd0b5-d883-4550-9726-16da44abb084 key : key2 value : value2
2019-11-01 21:47:36,663 - 123145484255232 - 96 - INFO - send b'57\r\npresubmitack\n85ccd0b5-d883-4550-9726-16da44abb084\tclient3'
2019-11-01 21:47:36,663 - 123145484255232 - 108 - INFO - send size 61
2019-11-01 21:47:36,663 - 123145484255232 - 53 - INFO - loc index : -1 recv buffer
2019-11-01 21:47:36,667 - 123145484255232 - 49 - INFO - client recv data : b'56\r\nsubmitabort\n85ccd0b5-d883-4550-9726-16da44abb084\tclient2'
2019-11-01 21:47:36,667 - 123145484255232 - 53 - INFO - loc index : 2 recv buffer 56
submitabort
85ccd0b5-d883-4550-9726-16da44abb084 client2
2019-11-01 21:47:36,667 - 123145484255232 - 63 - INFO - parse from server submitabort 85ccd0b5-d883-4550-9726-16da44abb084 client2
2019-11-01 21:47:36,667 - 123145484255232 - 53 - INFO - loc index : -1 recv buffer
從client3的日誌中可以看出,在接收到presubmit的消息的時候,已經給服務端返回了預提交成功的消息,但是在之後就立馬收到了服務端發送的submitabort的消息,從而得到事務中止的消息,至此事務中止的演示完成。
事務異常
當其中一個客戶端在消息預提交的時候,不回覆服務端的時候,此時相對服務端而言就得不到所有的客戶端的成功回覆,該事務就會一直處於等待狀態,在版本V1中並沒有對該情況進行其他處理,會導致該事務一直保存在服務端。
在client2中輸入;
input> presubmit false
args : ['presubmit', 'false']
presubmit_flag : False
此時繼續在client1中發送數據;
send key3 value3
args : ['send', 'key3', 'value3']
2019-11-01 22:02:10,690 - 4439086528 - 96 - INFO - send b'32\r\nclient_start\nclient1\tkey3\tvalue3'
2019-11-01 22:02:10,690 - 4439086528 - 108 - INFO - send size 36
此時client2的信息如下;
2019-11-01 22:02:10,698 - 123145410183168 - 49 - INFO - client recv data : b'58\r\npresubmit\n048df5a7-00e7-4344-91fa-1700c7b738ba\tkey3\tvalue3'
2019-11-01 22:02:10,699 - 123145410183168 - 53 - INFO - loc index : 2 recv buffer 58
presubmit
048df5a7-00e7-4344-91fa-1700c7b738ba key3 value3
2019-11-01 22:02:10,699 - 123145410183168 - 63 - INFO - parse from server presubmit 048df5a7-00e7-4344-91fa-1700c7b738ba key3 value3
2019-11-01 22:02:10,699 - 123145410183168 - 53 - INFO - loc index : -1 recv buffer
此時從信息中可以看見,client2收到了預提交數據,但是沒有回覆服務端ack,此時client3的消息如下;
2019-11-01 22:02:10,700 - 123145484255232 - 49 - INFO - client recv data : b'58\r\npresubmit\n048df5a7-00e7-4344-91fa-1700c7b738ba\tkey3\tvalue3'
2019-11-01 22:02:10,701 - 123145484255232 - 53 - INFO - loc index : 2 recv buffer 58
presubmit
048df5a7-00e7-4344-91fa-1700c7b738ba key3 value3
2019-11-01 22:02:10,702 - 123145484255232 - 63 - INFO - parse from server presubmit 048df5a7-00e7-4344-91fa-1700c7b738ba key3 value3
2019-11-01 22:02:10,702 - 123145484255232 - 188 - INFO - client recv : xid : 048df5a7-00e7-4344-91fa-1700c7b738ba key : key3 value : value3
2019-11-01 22:02:10,702 - 123145484255232 - 96 - INFO - send b'57\r\npresubmitack\n048df5a7-00e7-4344-91fa-1700c7b738ba\tclient3'
2019-11-01 22:02:10,702 - 123145484255232 - 108 - INFO - send size 61
2019-11-01 22:02:10,703 - 123145484255232 - 53 - INFO - loc index : -1 recv buffer
從消息中,可以看出client3收到了預提交數據並回復了可以提交,但是就是一直沒有收到可以提交數據的確認消息。此時就會一直在服務端保存該事務,該版本的實現中,因爲客戶端可以再動態的加入到服務端,此時如果當前正有事務在進行的話,如果加入了一個新的客戶端,此時就會導致該事務得不到新加入的回覆,導致該事務不會被提交,這個問題不在V1版本的考慮範圍。
總結
本文主要就是實現了兩階段提交的基本流程,並沒有考慮到其他異常的情況,主要完成了最基本的事務正常提交的流程,事務中止的流程等,並且在實現過程中可以通過加入相對的日誌來提高性能,但是在V1版本中並沒有實現,而且本文中的實例更像是類似與Zookeeper的主從複製的原理,而且在本版本中並沒有加入超時回調,在後續的版本中可能會考慮更多的細節情況,後續會努力去完善類圖的關係與設計。由於本人才疏學淺,如有錯誤請批評指正。