兩階段提交實際項目V1

項目介紹

兩階段提交項目主要是實際用代碼演示覆現一下,兩階段提交的執行過程,僅供學習參考。本次主要分析的版本爲V1版本,主要實現的流程包括服務端的基礎架構編寫,客戶端的基礎架構編寫,完成事務提交的過程。

兩階段項目的用例模型

客戶端註冊
用例名稱 客戶端啓動註冊
主要參與者 客戶端
涉及關注點 客戶端:希望將自己註冊到服務器端,從而能夠向服務端提交事務數據或者接受其他客戶端的提交事務的數據
前置條件 服務器正常運行(後續可考慮設置固定的客戶端數量)
後置條件 註冊完成後,返回註冊成功信息
基本流程 1.客戶端啓動,客戶端啓動一個線程去連接服務器;2.服務端處理請求並返回正常
客戶端發起事務提交數據
用例名稱 客戶端發起事務提交數據
主要參與者 客戶端,服務端
涉及關注點 客戶端:客戶端先將待提交數據發送給服務端,服務端將數據提交給其他客戶端。
前置條件 服務器正常運行,並且沒有其他新加入的客戶端
後置條件 客戶端接受服務端返回的數據提交成功
基本流程 1.客戶端提交事務數據到服務端;2.服務端此時將數據發送給其他客戶端;3.此時客戶端進入等待狀態;4.服務端等待其他客戶端的反饋;5.如果其他所有客戶端都反饋成功,則向該客戶端發送數據提交成功消息,並讓通知其他所有客戶端提交該數據
客戶端發送終止事務
用例名稱 客戶端發送終止事務
主要參與者 客戶端,服務端
涉及關注點 客戶端:客戶端已經將事務發送給服務端,服務端等待其他客戶端的事務提交成功反饋。
前置條件 服務器正常運行,並且沒有其他新加入的客戶端
後置條件 接收到事務終止消息
基本流程 1.客戶端提交事務數據到服務端;2.服務端此時將數據發送給其他客戶端;3.此時客戶端進入等待狀態;4.服務端等待其他客戶端的反饋;5.其中有任意一個客戶端發送abort消息,則向該客戶端發送停止消息到其他客戶端,並讓其他客戶端終止該事務。

本文V1版本主要就是圍繞這三種基本用例模型來進行實現。包括客戶端註冊,事務的提交與客戶端發出的終止信號。

兩階段項目的流程圖

服務端的流程圖
如果沒有讀事件
有讀事件觸發
如果是接受請求事件回調函數
如果是接受數據回調函數
接受的數據不能夠解析一個數據格式
如果是客戶端連接斷開
接受的數據可以解析一個或者多個數據格式
判斷剩餘數據是否可以繼續解析
如果不能繼續解析
可以繼續解析
繼續監聽事件
服務端啓動
檢查配置文件
根據配置啓動並監聽
循環監聽事件
執行註冊的回調函數
接受請求初始化連接並註冊讀事件
接受遠端發送過來的數據
處理完成
響應處理
將處理結果返回客戶端
judge_data

服務端的流程圖,主要的實現思路參考IO複用事件驅動來進行數據的處理,並且實現思路也是通過單線程來實現。

客戶端的流程圖
如果收到數據
如果有一個或者多個可以解析的消息
客戶端啓動
檢查輸入參數連接服務端
生成註冊消息
發送數據到服務端
監聽是否有服務端返回數據
用戶輸入數據
解析消息
處理接受到的消息

客戶端的功能,主要就是獲取用戶輸入的指令或者數據,將該數據發送給服務端,然後客戶端需要監聽來自服務端的數據,是否有服務端的消息發送回來,如果有消息則處理該消息。

兩階段項目V1版本的時序圖

參與者0協調者參與者1參與者2參與者3註冊自己(註冊id)註冊成功註冊自己(註冊id)註冊成功註冊自己(註冊id)註冊成功註冊自己(註冊id)註冊成功發送需要提交的數據是否可以提交數據可以提交是否可以提交數據可以提交是否可以提交數據可以提交提交成功發送需要提交的數據是否可以提交數據不可以提交是否可以提交數據可以提交是否可以提交數據可以提交取消提交數據取消成功取消提交數據取消成功提交失敗參與者0協調者參與者1參與者2參與者3

兩階段項目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的主從複製的原理,而且在本版本中並沒有加入超時回調,在後續的版本中可能會考慮更多的細節情況,後續會努力去完善類圖的關係與設計。由於本人才疏學淺,如有錯誤請批評指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章