├── .gitignore
├── MyPaxos_py
├── .idea
│ ├── MyPaxos_py.iml
│ ├── misc.xml
│ ├── modules.xml
│ └── workspace.xml
├── AdversarialMessagePump.py
├── InstanceRecord.py
├── Message.py
├── MessagePump.py
├── PaxoAcceptor.py
├── PaxoAcceptorProtocol.py
├── PaxoProposer.py
├── PaxoProposerProtocol.py
├── paox_testMain.py
└── test.py
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
--------------------------------------------------------------------------------
/MyPaxos_py/.idea/MyPaxos_py.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/MyPaxos_py/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/MyPaxos_py/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MyPaxos_py/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
116 |
117 |
118 |
119 | u额外协议
120 | garbageCollect
121 | notify
122 | findAndFillGaps
123 | protocolCount
124 | copyAsReply
125 | notifyProposer
126 | MSG_ACCEPTOR_AGREE
127 | recvMsg
128 | self.failed
129 | fail
130 | 达成协议的法定人数
131 | setPrimary
132 | 协议被客户端接受
133 | newProposal
134 | propose
135 | propose(
136 | MSG_HEARTBEAT
137 | doTranition
138 | self.instances
139 | start---
140 | newHB
141 | sendMsg
142 |
143 |
144 |
145 |
146 |
147 | $PROJECT_DIR$
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | true
170 | DEFINITION_ORDER
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 | 1523668641375
349 |
350 |
351 | 1523668641375
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 | file://$PROJECT_DIR$/PaxoProposerProtocol.py
422 | 104
423 |
424 |
425 |
426 | file://$PROJECT_DIR$/PaxoAcceptor.py
427 | 65
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
933 |
934 |
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 |
944 |
945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
--------------------------------------------------------------------------------
/MyPaxos_py/AdversarialMessagePump.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: AdversarialMessagePump.py
5 |
6 | @time: 2018/04/14 10:21
7 |
8 | @desc:#对抗消息传输,延迟并任意顺序传输,模拟网络延迟
9 | """
10 |
11 | import random #随机数
12 |
13 | from MessagePump import MessagePump #导入MessagePump类
14 |
15 | class AdversarialMessagePump(MessagePump):
16 | def __init__(self, owner, port, timeout=3):
17 | MessagePump.__init__(owner, port, timeout) #初始化父类
18 | self.msg = set() #避免消息重复
19 |
20 | #等待消息
21 | def waitForMsg(self):
22 | try:
23 | msg = self.queue.get(True, 0.1) # 从队列中取消息,最多等0.1s
24 | self.msg.add(msg)
25 | except Exception as e:
26 | print(e)
27 |
28 | #随机处理消息,为的是模拟网络延时
29 | if len(self.msg) > 0 and random.random() < 0.95:
30 | msg = random.choice(list(self.msg)) #随机取出一个消息
31 | self.msg.remove(msg) #删除取出的消息
32 |
33 | else:
34 | msg = None
35 |
36 | return msg
--------------------------------------------------------------------------------
/MyPaxos_py/InstanceRecord.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: InstanceRecord.py
5 |
6 | @time: 2018/04/14 10:31
7 |
8 | @desc: 本地记录类,记录决策者,提议者之间协议
9 | """
10 |
11 | import threading, socket, pickle, queue,random
12 | # InstanceRecord本地记录类,决策者,提议者之间协议
13 | from PaxoProposerProtocol import PaxoProposerProtocol
14 |
15 | class InstanceRecord():
16 | def __init__(self):
17 | self.protocols = {} #协议字典
18 | self.highestID = (-1, -1) #最高版本(提议版本,端口号)
19 | self.value = None #提议值
20 |
21 | #增加协议
22 | def addProtocol(self, protocol):
23 | self.protocols[protocol.proposalID] = protocol
24 | #取得版本最高的协议 假设端口较大的Proposer为领导,优先承诺 端口相同时取版本号较大的
25 | if protocol.proposalID[1] > self.highestID[1] or \
26 | (protocol.proposalID[1] == self.highestID[1] \
27 | and protocol.proposalID[0] > self.highestID[0]):
28 | self.highestID = protocol.proposalID
29 |
30 | #抓取协议
31 | def getProtocol(self, protocolID):
32 |
33 | return self.protocols[protocolID]
34 |
35 | #清理协议
36 | def cleanProtocols(self):
37 | keys = self.protocols.keys() #取得所有可以
38 | #遍历删除协议
39 | for key in keys:
40 | protocol = self.protocols[key]
41 | if protocol.state == PaxoProposerProtocol.STATE_ACCEPTED:
42 | print("Deleting protocol")
43 | del self.protocols[key] #删除协议
44 |
45 |
--------------------------------------------------------------------------------
/MyPaxos_py/Message.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: Message.py
5 |
6 | @time: 2018/04/14 09:31
7 |
8 | @desc: 消息传递类
9 | """
10 |
11 | class Message:
12 | #常量
13 | MSG_ACCEPTOR_AGREE = 0 #Acceptor对提议请求的承诺
14 | MSG_ACCEPTOR_ACCEPT = 1 #Acceptor对Accept请求的接受
15 | MSG_ACCEPTOR_REJECT = 2 #Acceptor对提议请求的拒绝
16 | MSG_ACCEPTOR_UNACCEPT = 3 #Acceptor对Accept请求的不接受
17 | MSG_ACCEPT = 4 #Proposer发出的Accept请求 或
18 | MSG_PROPOSE = 5 #Proposer发出的提议请求
19 | MSG_EXT_PROPOSE = 6 #外部(Client)发给Proposer的提议
20 | MSG_HEARTBEAT = 7 #定时的心跳信息,用来同步提议
21 |
22 | def __init__(self, cmd=None): #消息初始化有个命令
23 | self.cmd = cmd
24 |
25 | #对某个消息的回复消息
26 | def copyAsReply(self, msg):
27 | self.proposalID = msg.proposalID #提议id
28 | self.instanceID = msg.instanceID #当前id
29 | self.to = msg.source #提议发给谁
30 | self.source = msg.to #提议源自谁
31 | self.value = msg.value #提议信息
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/MyPaxos_py/MessagePump.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: MessagePump.py
5 |
6 | @time: 2018/04/14 09:46
7 |
8 | @desc: 基于Socket传递消息,封装网络类传递消息
9 | """
10 |
11 | import threading #线程
12 | import pickle #对象序列化
13 | import socket #网络信息传输
14 | import queue #队列
15 |
16 | class MessagePump(threading.Thread):
17 | # 传递消息的辅助类
18 | class MPHelper(threading.Thread):
19 | def __init__(self, owner):
20 | self.owner = owner #传递消息的对象的所有者
21 |
22 | threading.Thread.__init__(self) # 父类初始化
23 |
24 | def run(self): #运行
25 | while not self.owner.abort: #只要所有者线程未结束
26 | try:
27 | #返回二进制数据,地址
28 | (bytes, addr) = self.owner.socket.recvfrom(2048) #收取消息
29 | msg = pickle.loads(bytes) #读取二进制转化为消息
30 | msg.source = addr[1] #取出返回的地址
31 | self.owner.queue.put(msg) #消息存入队列
32 |
33 | except Exception as e: #异常
34 | print(e)
35 |
36 | def __init__(self, owner, port, timeout=2):
37 | #基本参数初始化
38 | self.owner = owner #所有者
39 | self.timeout = 2 #超时时间
40 | self.port = port #网络接口
41 |
42 | #网络通信初始化
43 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #UDP通信
44 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 200000) #通信参数
45 | self.socket.bind(("localhost", port)) #socket绑定
46 | self.socket.settimeout(timeout) #设置超时
47 |
48 | self.queue = queue.Queue() #队列
49 | self.helper = MessagePump.MPHelper(self) #接收消息的工具类
50 |
51 | threading.Thread.__init__(self) #父类初始化
52 | self.abort = False #默认不终止状态
53 |
54 | #运行主线程
55 | def run(self):
56 | self.helper.start() #开启收消息的线程
57 | while not self.abort: #只要不是终止状态
58 | msg = self.waitForMsg() #阻塞等待消息
59 | self.owner.recvMsg(msg) #收取消息
60 |
61 | #等待消息
62 | def waitForMsg(self):
63 | try:
64 | msg = self.queue.get(True, 3) #从队列中取消息,最多等3s
65 |
66 | return msg
67 | except Exception as e:
68 | print(e)
69 |
70 | return None
71 |
72 | #发送消息
73 | def sendMsg(self, msg):
74 | bytes = pickle.dumps(msg) #把消息转成二进制
75 | addr = ("localhost", msg.to)
76 | self.socket.sendto(bytes, addr) #发送消息到地址
77 |
78 | return True
79 |
80 | #设置状态为放弃
81 | def doAbort(self):
82 | self.abort = True
--------------------------------------------------------------------------------
/MyPaxos_py/PaxoAcceptor.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: PaxoAcceptor.py
5 |
6 | @time: 2018/04/14 10:50
7 |
8 | @desc: 决策者
9 | """
10 |
11 | from Message import Message
12 | from MessagePump import MessagePump
13 | from InstanceRecord import InstanceRecord
14 | from PaxoAcceptorProtocol import PaxoAcceptorProtocol
15 |
16 | class PaxoAcceptor:
17 | def __init__(self, port, proposers):
18 | self.port = port #端口
19 | self.proposers = proposers #提议者
20 | self.instances = {} # 接口列表
21 | self.msgPump = MessagePump(self, self.port) #消息传送器
22 | self.failed = False #没有失败
23 |
24 | #开始
25 | def start(self):
26 | self.msgPump.start()
27 |
28 | #停止
29 | def stop(self):
30 | self.msgPump.doAbort()
31 |
32 | #失败
33 | def fail(self):
34 | self.failed = True
35 |
36 | #恢复
37 | def recover(self):
38 | self.failed = False
39 |
40 | #发送消息
41 | def sendMsg(self, msg):
42 | self.msgPump.sendMsg(msg)
43 |
44 | #接收消息
45 | def recvMsg(self, msg):
46 | if msg == None: #消息为空
47 | return
48 |
49 | if self.failed: #失败状态不接收消息
50 | return
51 |
52 | if msg.cmd == Message.MSG_PROPOSE: #消息为提议
53 | if msg.instanceID not in self.instances: #消息未加入
54 | record = InstanceRecord() #记录器
55 | # record.value = msg.value
56 | self.instances[msg.instanceID] = record #将消息记录下来
57 |
58 | protocol = PaxoAcceptorProtocol(self) #创建决策者协议
59 |
60 | #[start---1.2]Acceptor收到一个消息,消息类型为提议。然后借助AcceptorProtocol实例处理提议
61 | protocol.recvProposal(msg) #借助决策者处理协议
62 | self.instances[msg.instanceID].addProtocol(protocol) #记录协议
63 |
64 | else:
65 | #[start---2.1]Acceptor收到一个消息,类型为来自Proposer的Accept请求。借助AcceptorPropotal处理该消息
66 | self.instances[msg.instanceID].getProtocol(msg.proposalID).doTranition(msg) #抓取协议记录
67 |
68 |
69 | #通知客户端
70 | def notifyClient(self, protocol, msg):
71 | if protocol.state == PaxoAcceptorProtocol.STATE_PROPOSAL_ACCEPTED: #接受协议
72 | self.instances[protocol.instanceID].value = msg.value #存储信息
73 | print("协议被客户端接受", msg.value)
74 |
75 |
76 | #获取本地记录数据
77 | def getInstanceValue(self, instanceID):
78 | return self.instances[instanceID].value
79 |
80 | #获取最高同意建议
81 | def getHighestProposal(self, instanceID):
82 | return self.instances[instanceID].highestID
--------------------------------------------------------------------------------
/MyPaxos_py/PaxoAcceptorProtocol.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: PaxoAcceptorProtocol.py
5 |
6 | @time: 2018/04/14 10:50
7 |
8 | @desc: 决策者协议
9 | """
10 |
11 | from Message import Message #协议依赖消息
12 |
13 | class PaxoAcceptorProtocol:
14 | #常量
15 | STATE_UNDEFIND = -1 #协议未定义
16 | STATE_PROPOSAL_RECEIVED = 0 #收到消息
17 | STATE_PROPOSAL_REJECTED = 1 #拒绝链接,网络不通可能
18 | STATE_PROPOSAL_AGREED = 2 #承诺将接受该提议 针对Proposer的PROPOSED请求
19 | STATE_PROPOSAL_ACCEPTED = 3 #接受该协议 针对Proposer的Accept请求
20 | STATE_PROPOSAL_UNACCEPTED = 4 #拒绝请求
21 |
22 | def __init__(self, client):
23 | self.client = client
24 | self.state = PaxoAcceptorProtocol.STATE_UNDEFIND #默认初始未定义
25 |
26 | #收到提议
27 | def recvProposal(self, msg):
28 | if msg.cmd == Message.MSG_PROPOSE: #处理提议
29 | self.proposalID = msg.proposalID #协议编号
30 | self.instanceID = msg.instanceID #记录编号
31 | (port, count) = self.client.getHighestProposal(msg.instanceID) #获取端口,协议最高编号
32 |
33 | #[start---1.3] AcceptorProtocol收到一个提议,判断提议版本回复承诺接受消息或拒绝消息
34 | #判断协议是否是最高版本,版本相同时优先接收端口号高的提议消息
35 | if count < self.proposalID[0] \
36 | or (count == self.proposalID[0] and port < self.proposalID[1]):
37 | self.state = PaxoAcceptorProtocol.STATE_PROPOSAL_AGREED #Acceptor当前已经承诺给某Proposer会接受请求
38 | print("承诺会接受该提议:%s, %s " % (msg.instanceID, msg.value))
39 | value = self.client.getInstanceValue(msg.instanceID) #抓取记录的数据值
40 |
41 | msg_agree = Message(Message.MSG_ACCEPTOR_AGREE) #创建一个消息作为对Proposer的承诺
42 | msg_agree.copyAsReply(msg) #拷贝并回复
43 | msg_agree.value = value #保存值
44 | msg_agree.sequence = (port, count) #保存端口数据
45 | self.client.sendMsg(msg_agree) #给Proposer回复承诺消息
46 |
47 | else:
48 | #提议版本过低,我已经承诺给别的Proposer 所以拒绝
49 | self.state = PaxoAcceptorProtocol.STATE_PROPOSAL_REJECTED
50 |
51 | return self.proposalID
52 |
53 | else:
54 | pass
55 |
56 |
57 | #过渡
58 | def doTranition(self, msg):
59 | # [start---2.2_1] AcceptorPropotal收到Proposer发出的Accept请求。按Paxos算法思想,这里需要判断请求版本号,当且仅当Acceptor之前承诺过的提议版本号最大值小于Accept请求版本号才会接受该Proposer提议。这里我们借助“先入为主”的思想简化问题,只要这时候协议状态为STATE_PROPOSAL_AGREED,就给所有Proposer广播消息表示自己确认接受该Proposer提议
60 | if self.state == PaxoAcceptorProtocol.STATE_PROPOSAL_AGREED \
61 | and msg.cmd == Message.MSG_ACCEPT: #同意链接 并消息被接受
62 | self.state = PaxoAcceptorProtocol.STATE_PROPOSAL_ACCEPTED #接受协议
63 | msg_accept = Message(Message.MSG_ACCEPTOR_ACCEPT) #创造消息,用来表示该Acceptor最终接受了某个Proposer的提议
64 | msg_accept.copyAsReply(msg)
65 |
66 | #广播消息给所有提议者,告知自己最终接受了拿个Proposer的提议
67 | for proposer in self.client.proposers:
68 | msg_accept.to = proposer
69 | self.client.sendMsg(msg_accept)
70 |
71 | #[start - --2.2_1]AcceptorPropotal通知Acceptor更新InstanceRecord的值,到此时已有一个提议被一个Acceptor最终接受。
72 | self.notifyClient(msg)
73 |
74 | return True
75 |
76 | raise Exception(u"并非预期状态与命令")
77 |
78 | #通知客户端
79 | def notifyClient(self, msg):
80 | self.client.notifyClient(self, msg)
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/MyPaxos_py/PaxoProposer.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: PaxoProposer.py
5 |
6 | @time: 2018/04/14 10:50
7 |
8 | @desc: 提议者
9 | """
10 |
11 | import threading
12 | import queue
13 | import time
14 |
15 | from Message import Message
16 | from MessagePump import MessagePump
17 | from InstanceRecord import InstanceRecord
18 | from PaxoProposerProtocol import PaxoProposerProtocol
19 |
20 | class PaxoProposer:
21 |
22 | #心跳监听类
23 | class HeartbeatListener(threading.Thread):
24 | def __init__(self, proposer):
25 | self.proposer = proposer #提议者
26 | self.queue = queue.Queue() #消息队列
27 | self.abort = False
28 |
29 | threading.Thread.__init__(self) #父类初始化
30 |
31 | #从队列取出消息
32 | def newHB(self, msg):
33 | self.queue.put(msg)
34 |
35 | #开始执行,读取消息
36 | def run(self):
37 | # elapsed = 0 #时间计数器
38 | while not self.abort: #非取消状态
39 | # stime = time.time()
40 | try:
41 | hb = self.queue.get(True, 2) #抓取消息, 最多抓取2s
42 | #假设谁的端口号比较高谁是领导
43 | if hb.source > self.proposer.port:
44 | self.proposer.setPrimary(False)
45 | except:
46 | self.proposer.setPrimary(True)
47 |
48 | #取消
49 | def doAbort(self):
50 | self.abort = True
51 |
52 | #定时发送类
53 | class HeartbeatSender(threading.Thread):
54 | def __init__(self, proposer):
55 | self.proposer = proposer
56 | self.abort = False
57 |
58 | threading.Thread.__init__(self)
59 |
60 | #
61 | def run(self):
62 | while not self.abort:
63 | time.sleep(1) #每1s一次
64 | if self.proposer.isPrimary: #提议者为领导
65 | msg = Message(Message.MSG_HEARTBEAT) #心跳信息
66 | msg.source = self.proposer.port #设置消息源端口
67 | for proposer in self.proposer.proposers: #遍历提议者
68 | msg.to = proposer
69 | self.proposer.sendMsg(msg) #发送消息
70 |
71 | #
72 | def doAbort(self):
73 | self.abort = True
74 |
75 | #初始化
76 | def __init__(self, port, proposers=None, acceptors=None):
77 | self.port = port
78 |
79 | #初始化提议者
80 | if proposers == None:
81 | self.proposers = []
82 | else:
83 | self.proposers = proposers
84 |
85 | if acceptors == None:
86 | self.acceptors = []
87 | else:
88 | self.acceptors = acceptors
89 |
90 | self.group = self.proposers+self.acceptors #组
91 | self.isPrimary = False #是否是领导,被大多说决策者选出来的提议者是领导
92 | self.proposalCount = 0 #提议数量
93 | self.msgPump = MessagePump(self, port) #消息传送器
94 | self.instances = {} #记录数据
95 | self.hbListener = PaxoProposer.HeartbeatListener(self) #监听工具实例
96 | self.hbSender = PaxoProposer.HeartbeatSender(self) #发送工具实例
97 | self.highestInstance = -1 #最高协议版本
98 | self.stopped = True #是否在运行
99 | self.lastTime = time.time() #最后一次时间
100 |
101 | #开始
102 | def start(self):
103 | self.hbSender.start() # 发送器
104 | self.hbListener.start() #监听器
105 | self.msgPump.start() #消息发送
106 | self.stopped = False
107 |
108 | #停止
109 | def stop(self):
110 | self.hbSender.doAbort() # 发送器
111 | self.hbListener.doAbort() # 监听器
112 | self.msgPump.doAbort() # 消息发送
113 | self.stopped = True
114 |
115 | #设置是否为领导者
116 | def setPrimary(self, isPrimary):
117 | if self.isPrimary != isPrimary:
118 | if isPrimary:
119 | print(u"%s is leader" % self.port)
120 | else:
121 | print(u"%s is not leader" % self.port)
122 |
123 | self.isPrimary = isPrimary
124 |
125 | #获取支持所有提议者的决策者
126 | def getGroup(self):
127 | return self.group
128 |
129 | #获取所有提议者
130 | def getProposers(self):
131 | return self.proposers
132 |
133 | #获取所有决策者
134 | def getAcceptors(self):
135 | return self.acceptors
136 |
137 | #提议被承诺接受或最终接受的条件必须满足:获得1/2以上的Acceptor支持
138 | def getQuorumCount(self):
139 | return len(self.getAcceptors())/2
140 |
141 | #获取本地记录数据
142 | def getInstanceValue(self, instanceID):
143 | if instanceID in self.instances.keys():
144 | return self.instances[instanceID].value
145 |
146 | return None
147 |
148 | #获取历史记录
149 | def getHistory(self):
150 | return [self.getInstanceValue(i) for i in range(0, self.highestInstance+1)]
151 | # mylist = []
152 | # for i in range(0, self.highestInstance + 1):
153 | # last = self.getInstanceValue(i)
154 | # mylist.append(last)
155 | #
156 | # return mylist
157 |
158 | #获取提议同意的数量
159 | def getNumAccepted(self):
160 | return len([v for v in self.getHistory() if v != None])
161 | # mylist = []
162 | # list = self.getHistory()
163 | # for i in list:
164 | # if i != None:
165 | # mylist.append(i)
166 | #
167 | # return len(mylist)
168 |
169 |
170 | #通知其他提议者
171 | def notifyProposer(self, protocol, msg):
172 | #提议被同意
173 | #[start---2.5] Proposer更新InstanceRecord记录,如果协议最终被大多数Acceptor拒绝则尝试重新提议
174 | if protocol.state == PaxoProposerProtocol.STATE_ACCEPTED:
175 | print(u"协议%s被%s同意" % (msg.instanceID, msg.value))
176 | self.instances[msg.instanceID].accepted = True
177 | self.instances[msg.instanceID].value = msg.value
178 | self.highestInstance = max(msg.instanceID, self.highestInstance)
179 |
180 | return True
181 |
182 | #如果协议最终被大多数Acceptor拒绝则尝试重新提议
183 | if protocol.state == PaxoProposerProtocol.STATE_REJECTED:
184 | self.proposalCount = max(self.proposalCount, msg.highestPID[1])
185 | self.newProposal(msg.value) #重试提议
186 |
187 | return True
188 |
189 |
190 | #新的提议
191 | def newProposal(self, value, instance=None):
192 | protocol = PaxoProposerProtocol(self) #创建提议者协议实例
193 |
194 | #创建协议标号
195 | if instance == None:
196 | self.highestInstance += 1
197 | instanceID = self.highestInstance
198 | else:
199 | instanceID = instance
200 |
201 | self.proposalCount += 1 #提议数
202 | id = (self.port, self.proposalCount) #保存端口,协议数
203 |
204 | #协议记录
205 | if instanceID in self.instances:
206 | record = self.instances[instanceID]
207 | else:
208 | record = InstanceRecord()
209 | self.instances[instanceID] = record
210 |
211 | #[start - --1.1]Proposer借助自身的PaxoProposerProtocol实例发起一个提议请求
212 | protocol.propose(value, id, instanceID) #发起提议
213 | record.addProtocol(protocol) #追加协议
214 |
215 | # 发送消息
216 | def sendMsg(self, msg):
217 | self.msgPump.sendMsg(msg)
218 |
219 | # 接收消息
220 | def recvMsg(self, msg):
221 | if self.stopped:
222 | return
223 |
224 | if msg == None:
225 | if self.isPrimary and time.time() - self.lastTime > 15.0:
226 | self.findAndFillGaps()
227 | self.garbageCollect() #处理
228 |
229 | return
230 |
231 | if msg.cmd == Message.MSG_HEARTBEAT: #心跳消息
232 | self.hbListener.newHB(msg)
233 |
234 | return True
235 |
236 | #[start---1.0]Proposer收到一个消息,消息类型为一个提议。首先判断自身是否为领导者,如果是创建协议
237 | if msg.cmd == Message.MSG_EXT_PROPOSE: #额外协议
238 | print("u外部传来的提议 %s %s" % (self.port, self.highestInstance))
239 | if self.isPrimary:
240 | self.newProposal(msg.value) #新的协议
241 |
242 | return True
243 |
244 | #[start---1.4]Proposer收到一个消息,类型为Acceptor的承诺(MSG_ACCEPTOR_AGREE)。既然不是Proposer最终要的接受提议的结果,转给ProposerPropotocal状态机处理
245 | if self.isPrimary and msg.cmd != Message.MSG_ACCEPTOR_ACCEPT: #如果消息不是最终同意消息
246 | self.instances[msg.instanceID].getProtocol(msg.proposalID).doTranition(msg)
247 |
248 | if msg.cmd == Message.MSG_ACCEPTOR_ACCEPT:
249 | #[start---2.3]Proposer收到一个消息,类型为Acceptor确认接受提议(MSG_ACCEPTOR_ACCEPT),根据该消息更新Proposer的InstanceRecord(新建record,追加协议等)。并将消息交给ProposerProtocol状态机处理
250 | if msg.instanceID not in self.instances:
251 | record = InstanceRecord()
252 | # record.value = msg.value
253 | self.instances[msg.instanceID] = record
254 |
255 | record = self.instances[msg.instanceID] #记录
256 | if msg.proposalID not in record.protocols:
257 | #创建协议
258 | protocol = PaxoProposerProtocol(self) #提议者协议
259 | protocol.state = PaxoProposerProtocol.STATE_AGREED #同意链接
260 | protocol.proposalID = msg.proposalID
261 | protocol.instanceID = msg.instanceID
262 | protocol.value = msg.value
263 | record.addProtocol(protocol) #追加协议
264 | else:
265 | protocol = record.getProtocol(msg.proposalID)
266 |
267 | protocol.doTranition(msg) #过渡处理
268 | # print(self.getInstanceValue(0))
269 |
270 | return True
271 |
272 | #无用信息处理
273 | def garbageCollect(self):
274 | for i in self.instances:
275 | self.instances[i].cleanProtocols()
276 |
277 | #获取空白时间处理下事务
278 | def findAndFillGaps(self):
279 | for i in range(1, self.highestInstance):
280 | if self.getInstanceValue(i) == None:
281 | print(u"填充空白", i)
282 | self.newProposal(0, i)
283 |
284 | self.lastTime = time.time()
285 |
286 |
--------------------------------------------------------------------------------
/MyPaxos_py/PaxoProposerProtocol.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: PaxoProposerProtocol.py
5 |
6 | @time: 2018/04/14 10:50
7 |
8 | @desc: 提议者协议
9 | """
10 |
11 | from Message import Message #协议依赖消息
12 |
13 | class PaxoProposerProtocol:
14 | #常量
15 | STATE_UNDEFIND = -1 #提议协议未定义
16 | STATE_PROPOSED = 0 #提议类型
17 | STATE_REJECTED = 1 #拒绝状态 提议被拒绝
18 | STATE_AGREED = 2 #提议被承诺接受 Prepare阶段获取大多数Acceptor承诺后的协议状态
19 | STATE_ACCEPTED = 3 #提议被接受
20 | STATE_UNACCEPTED = 4 #提议未被拒绝
21 |
22 | def __init__(self, proposer):
23 | self.proposer = proposer
24 |
25 | self.state = PaxoProposerProtocol.STATE_UNDEFIND
26 | self.proprsalID = (-1, -1)
27 | # self.agreeCount = (0, 0)
28 | # self.acceptCount = (0, 0)
29 | # self.rejectCount = (0, 0)
30 | # self.unacceptCount = (0, 0)
31 |
32 | self.agreeCount = 0
33 | self.acceptCount = 0
34 | self.rejectCount = 0
35 | self.unacceptCount = 0
36 |
37 | self.instanceID = -1
38 | self.highestseen = (0, 0) #最高协议
39 |
40 |
41 | #提议
42 | def propose(self, value, pID, instanceID):
43 | self.proposalID = pID
44 | self.instanceID = instanceID
45 | self.value = value
46 |
47 | # 创建一个提议类型的消息
48 | msg = Message(Message.MSG_PROPOSE)
49 | msg.proposalID = pID
50 | msg.instanceID = instanceID
51 | msg.value = value
52 |
53 | #将提议请求发送给每一位决策者Acceptor
54 | for server in self.proposer.getAcceptors():
55 | msg.to = server
56 | self.proposer.sendMsg(msg)
57 |
58 | #当前协议状态为已提议状态
59 | self.state = PaxoProposerProtocol.STATE_PROPOSED #提议类型
60 |
61 | return self.proprsalID
62 |
63 | #过渡 根据状态机运行
64 | def doTranition(self, msg):
65 | #[start---1.5]ProposerPropotocal状态机函数收到一个MSG_ACCEPTOR_AGREE消息,此时表示有一个Acceptor承诺会接受我的请求。
66 | if self.state == PaxoProposerProtocol.STATE_PROPOSED: #当前协议为提议状态提议
67 | if msg.cmd == Message.MSG_ACCEPTOR_AGREE:
68 | #许诺接受提议的计数器
69 | self.agreeCount += 1
70 | #[start---2.0_1]该条件下的代码会不断执行,直到许诺Proposer的数量超过半数,表示Prepare阶段基本结束。此时的Proposer向Acceptor集合发送Accept请求,请求Acceptor确认他们的许诺。
71 | if self.agreeCount > self.proposer.getQuorumCount(): #选举
72 | print(u"达成协议的法定人数%d,最后的价值回答是:%s" % (self.agreeCount, msg.value))
73 | if msg.value != None:
74 | #判断版本号,根据版本高德更新记录的值
75 | if msg.sequence[0] > self.highestseen[0] \
76 | or (msg.sequence[0] == self.highestseen[0] \
77 | and msg.sequence[1] > self.highestseen[1]):
78 | self.value = msg.value #数据同步
79 | self.highestseen = msg.sequence
80 |
81 | self.state == PaxoProposerProtocol.STATE_AGREED #提议取得超半数Acceptor承诺,对应协议状态变为协议被许诺将接受
82 |
83 | #[start---2.0_1]向Acceptor发送Accept请求,请求Acceptor确认许诺
84 | msg_accept = Message(Message.MSG_ACCEPT)
85 | msg_accept.copyAsReply(msg)
86 | msg_accept.value = self.value
87 | msg_accept.proposerID = msg.to
88 | for server in self.proposer.getAcceptors(): #广播Accept请求
89 | msg_accept.to = server
90 | self.proposer.sendMsg(msg_accept)
91 |
92 | #[start---2.0_2]通知其他提议者我的提议已经被半数Accept许诺将会被接受,使得其他Proposer知道Prepare阶段哪个提议获得的承诺最多。这样,在Commit阶段,他们可能通过改变提议来使系统尽快达到一致性。
93 | self.proposer.notifyProposer(self, msg)
94 |
95 | return True
96 | if msg.cmd == Message.MSG_ACCEPTOR_REJECT: #被拒绝
97 | self.rejectCount += 1
98 | if self.rejectCount > self.proposer.getQuorumCount(): #决策者拒绝数超过半数
99 | self.state = PaxoProposerProtocol.STATE_REJECTED
100 | self.proposer.notifyProposer(self, msg)
101 |
102 | return True
103 |
104 |
105 | if self.state == PaxoProposerProtocol.STATE_AGREED: #同意状态
106 | #[start---2.4_1]ProposerProtocol收到一个Acceptor的最终确认消息(MSG_ACCEPTOR_ACCEPT),此时表示新增一个Acceptor最终接受了我的提议。但此时的协议状态仍然是STATE_AGREED状态,因为一个提议最终被系统接受必须先被超半数的Acceptor节点确认接受。
107 | if msg.cmd == Message.MSG_ACCEPTOR_ACCEPT: #确认接受协议
108 | #确认接受当前协议的Acceptor计数器
109 | self.acceptCount += 1
110 | if self.acceptCount > self.proposer.getQuorumCount(): #提议被超半数Acceptor最终确认接受
111 |
112 | #[start---2.4_2] STATE_AGREED条件下的代码会不断执行,直到最终接受提议的Acceptor超过半数。这时,协议状态由STATE_AGREED状态更新为最终被系统确认状态(STATE_ACCEPTED)。
113 | self.state = PaxoProposerProtocol.STATE_ACCEPTED #接受
114 | #最后,最后,当前Proposerg更新自己的InstanceRecord记录
115 | self.proposer.notifyProposer(self, msg)
116 |
117 | #当然,这里也有一种可能是被超过半数节点不接受,那么同样其他Proposer节点必有一个节点提议被接受。
118 | #该Proposer的提议最终不被Acceptor确认接受
119 | if msg.cmd == Message.MSG_ACCEPTOR_UNACCEPT: #不同意协议
120 | self.unacceptCount += 1
121 | if self.unacceptCount > self.proposer.getQuorumCount():
122 | self.state = PaxoProposerProtocol.STATE_UNACCEPTED
123 | self.proposer.notifyProposer(self, msg)
124 |
125 | pass
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/MyPaxos_py/paox_testMain.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: paxo_testMain.py
5 |
6 | @time: 2018/04/14 17:50
7 |
8 | @desc: Paxos算法测试用例
9 | """
10 |
11 | import threading, socket, pickle, queue,random
12 | import time
13 |
14 | from MessagePump import MessagePump
15 | from Message import Message
16 | from InstanceRecord import InstanceRecord
17 | from PaxoProposer import PaxoProposer
18 | from PaxoProposerProtocol import PaxoProposerProtocol
19 | from PaxoAcceptorProtocol import PaxoAcceptorProtocol
20 | from PaxoAcceptor import PaxoAcceptor
21 |
22 | if __name__ == '__main__':
23 | #Acceptor数量
24 | numclients = 5
25 | #实例化决策者数组,决策者节点端口号为65520-65525
26 | acceptors = [PaxoAcceptor(port, [56321, 56322]) for port in range(65520, 65520 + numclients)]
27 |
28 | #实例化提议者,端口号分别56321,56322 对应的决策者为acceptors
29 | proposer1 = PaxoProposer(56321, [56321, 56322], [acceptor.port for acceptor in acceptors])
30 | proposer2 = PaxoProposer(56322, [56321, 56322], [acceptor.port for acceptor in acceptors])
31 |
32 | #启动提议者提议程序
33 | proposer1.start()
34 | proposer1.setPrimary(True)
35 | proposer2.setPrimary(True)
36 | proposer2.start()
37 |
38 | #启动决策者决策程序
39 | for acceptor in acceptors:
40 | acceptor.start()
41 |
42 | #模拟网络中两个节点宕机
43 | acceptors[0].fail()
44 | acceptors[1].fail()
45 |
46 | #利用Socket机制发送提议给决策者
47 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
48 | start = time.time()
49 |
50 | #将1-1000作为每次提议的值
51 | for i in range(1000):
52 | m = Message(Message.MSG_EXT_PROPOSE)
53 | m.value = 0 + i
54 | m.to = 56322
55 | bytes = pickle.dumps(m)
56 | s.sendto(bytes, ("localhost", m.to))
57 |
58 | # if i == 2 or i == 30:
59 | # print(leader2.getInstanceValue(1))
60 |
61 | #当提议被999个决策者接受时结束整个提议程序
62 | while proposer1.getNumAccepted() < 999:
63 | print(u"休眠1秒--被接受: %d" % proposer1.getNumAccepted())
64 | time.sleep(1)
65 | end = time.time()
66 |
67 | print(u"休眠10秒")
68 | time.sleep(10)
69 | print(u"结束领导者")
70 | proposer1.stop()
71 | proposer2.stop()
72 | print(u"结束客户端")
73 | for acceptor in acceptors:
74 | acceptor.stop()
75 |
76 | print(u"领导者1 历史记录: %s" % proposer1.getHistory())
77 | print(u"领导者 2 历史记录: %s " % proposer2.getHistory())
78 | print(u"一共用了%d 秒" % (end - start))
79 |
--------------------------------------------------------------------------------
/MyPaxos_py/test.py:
--------------------------------------------------------------------------------
1 | """
2 | @author: chaors
3 |
4 | @file: Message.py
5 |
6 | @time: 2018/04/14 09:31
7 |
8 | @desc: 消息传递类
9 | """
10 |
11 | class Message:
12 | #常量
13 | MSG_ACCEPTOR_AGREE = 0 #Acceptor对提议请求的承诺
14 | MSG_ACCEPTOR_ACCEPT = 1 #Acceptor对Accept请求的接受
15 | MSG_ACCEPTOR_REJECT = 2 #Acceptor对提议请求的拒绝
16 | MSG_ACCEPTOR_UNACCEPT = 3 #Acceptor对Accept请求的不接受
17 | MSG_ACCEPT = 4 #Proposer发出的Accept请求
18 | MSG_PROPOSE = 5 #Proposer发出的提议请求
19 | MSG_EXT_PROPOSE = 6 #外部(Client)发给Proposer的提议
20 | MSG_HEARTBEAT = 7 #定时的心跳信息,用来同步提议
21 |
22 | def __init__(self, cmd=None): #消息初始化有个状态
23 | pass
24 |
25 | #对某个消息的回复消息
26 | def copyAsReply(self, msg):
27 | pass
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PaxosDemo_py
2 | # 基于Python的区块链共识算法Paxos实战
3 |
4 | # 0.前言
5 |
6 | 本文记录笔者学习和理解区块链共识算法Paxos的点滴,文章比较长,需要耐心来细细琢磨,笔者也是苦战了一个周末才对此有那么一点初步了解,有问题的地方请不吝斧正!
7 |
8 | 1.初始是阅读本文后续内容的基础,概念性的东西叙述不多,干货干货干货在后面的代码实战。但有提供我认为优秀的帖子以供参考理解。前面这些Paxos基本的理解是2.代码设计和3.实战流程的基础!
9 |
10 | # 1.初识
11 |
12 | ### 相关概念
13 |
14 | Paxos 问题是指分布式的系统中存在故障(fault),但不存在恶意(corrupt)节点场景(即 可能消息丢失或重复,但无错误消息)下的共识达成(Consensus)问题。
15 |
16 | Paxos最早是 Leslie Lamport 用 Paxon 岛的故事模型来进行描述而命名。故事背景是古希腊 Paxon 岛上的多个法官在一个大厅内对一个议案进行表决,如何达成统一 的结果。他们之间通过服务人员来传递纸条,但法官可能离开或进入大厅,服务人员可能偷 懒去睡觉。
17 |
18 | Paxos 是第一个被证明的共识算法,其原理基于两阶段提交并进行扩展。 作为现在共识算法设计的鼻祖,以最初论文的难懂(算法本身并不复杂)出名。算法中将节点分为三种类型:
19 |
20 | - proposer:提出一个提案,等待大家批准为结案。往往是客户端担任该角色;
21 |
22 | - acceptor:负责对提案进行投票。往往是服务端担任该角色;一般需要至少3个且节点个数为奇数,因为Paxos算法最终要产生一个大多数决策者都同意的提议。
23 |
24 | - learner:被告知结案结果,并与之统一,不参与投票过程。可能为客户端或服务端。
25 |
26 | ### paxos算法的两阶段
27 | - prepare 阶段:
28 | 1. Proposer 选择一个提案编号 n,然后向acceptor的某个超过半数的子成员发送编号为n的 prepare 请求;
29 | 2. Acceptor 收到 prepare 消息后,如果提案的编号n大于该acceptor已经回复的所有 prepare 请求的编号,则 Acceptor 将自己上次已经批准的最大编号提案回复给 Proposer,并承诺不再回复小于 n 的提案;
30 |
31 | - commit阶段:
32 | 1. 当一个 Proposer 收到了半数以上的 Acceptors 对 prepare 的回复后,就进入批准阶段。它要向回复 prepare 请求的 Acceptors 发送 accept 请求,包括编号 n 和根据 prepare阶段 决定的 value。这里的value是所有响应中编号最大的提案的value(如果根据 prepare 没有已经接受的 value,那么它可以自由决定 value)。
33 | 2. 在不违背自己向其他 Proposer 的承诺的前提下,Acceptor 收到 accept 请求后即接受这个请求。即如果acceptor收到这个针对n提案的accept请求,只要该acceptor尚未对编号大于n的prepare请求做出过响应,它就可以通过这个提案。
34 |
35 | ### 他山之石
36 |
37 | Paxos算法初次接触听上去确实有点晦涩难懂,这里有一篇贴子我觉得不错。贴出来可以参考:
38 |
39 | - [通过现实世界描述Paxos算法](http://www.cnblogs.com/endsock/p/3480093.html)
40 | - [通过实例来理解paxos算法](https://blog.csdn.net/xiaqunfeng123/article/details/51712983)
41 | - [Paxos算法原理与推导](http://www.cnblogs.com/linbingdong/p/6253479.html)
42 |
43 | 另外,wiki对[Paxos](https://en.wikipedia.org/wiki/Paxos_(computer_science))的描述也是比较不错和权威的参考资料。
44 |
45 | 有了以上对Paxos算法的理解,我们才能进行下一步:自己编程实现Paxos算法。
46 |
47 | # 2.代码实战
48 |
49 | ### 流程理解
50 |
51 | Paxos算法核心的两个角色便是Proposer(提议者)和Acceptor(决策者),因此也必须围绕这两个对象进行算法架构的设计。
52 |
53 | 
54 |
55 | ###### Proposer行为分析
56 | - 1.0 向所有Acceptor发出一个提议(proposal);
57 |
58 | - 2.0 如果收到一个拒绝信息(reject),尝试重新发送被拒绝的提议;
59 |
60 | - 2.1 如果收到一个Acceptor的承诺回应(agree),用一个标志(agreeCount)来计数给了自己承诺的Acceptor个数。当agreeCount超过Acceptor总数的一半时,表示有大多数Acceptor承诺将接受这个提议,需要将自己的提议状态置为承诺接受状态(agreed)。同时,还要通知其他Proposer我这个提议已经得到大多数Acceptor承诺会接受。
61 |
62 | - 3.0 提议为承诺接受状态(agreed)时,Proposer需要再向Acceptor集合发送一个接受提议的确认请求,我们称该请求为Accept请求。
63 |
64 | - 3.1 发出Accept请求后会收到Acceptor的回复,如果收到接受信息(Accept),用一个标志(acceptCount)来计数接受自己提议的Acceptor个数。同样当acceptCount超过半数时,表示大多数Acceptor接受了这个提议,需要将提议状态由承诺接受状态(agreed)置为接受状态(acceptd)。
65 | 同时,还要通知其他Proposer我这个提议已经得到大多数Acceptor接受。
66 |
67 | - 以上1,2属于Paxos算法的Prepare阶段,3属于Accept阶段。
68 |
69 | ###### Acceptor行为分析
70 |
71 | - 1.0 当Acceptor收到一个提议后,判断提议版本号是否大于自身保存的提议版本。
72 |
73 | - 1.0 如果小于自身表示曾经已经给过别的Proposer承诺,发送一个拒绝消息(reject),表示自己拒绝给当前Proposer任何承诺。
74 |
75 | - 1.1 反之,则替换自身保存的提议版本号并给当前Proposer发送一个承诺回应(agree),表示将承诺接受他的提议。同时,将自身状态置为已经给了某个Proposer承诺(agree)。
76 |
77 | - 2.0 Acceptor收到一个Proposer的编号为N的Accept请求,只要该Acceptor之前不曾承诺编号M(M>N)的其他Proposer提议,那么他就接受该提案。同时,将自身状态置为已接受某个Proposer提议,并通知所有Proposer这个消息。
78 |
79 | - 以上1属于Paxos算法的Prepare阶段,2属于Accept阶段。
80 |
81 | ___以上行为分析针对本次Paxos算法编程实战!!!___
82 |
83 | ### 类的设计
84 |
85 | Paxos算法解决的是分布式系统一致性的问题,我们通过端口号在一台计算机上模拟多个节点。
86 |
87 | 毋庸置疑,我们分别需要一个Proposer类和Acceptor类。
88 |
89 | ###### PaxoProposer 提议者类
90 |
91 | - Proposer的作用是提出一个提议并发送给Acceptor,所以他本身必须知道所有的Acceptor,同时有些时候要跟其他Proposer通讯,所以也需要知道所有的Proposer(见init方法)。
92 |
93 | - 基本的开始结束接口(start, stop)
94 |
95 | - 在判断提议是否被大多数Acceptor承诺接受或最终接受,我们需要设定一个判定条件(getQuorumCount)
96 |
97 | - 当提议被承诺接受或最终接受时需要通知其他Proposer(notifyProposer)
98 |
99 | - 发送消息(提议或Accept请求)给Acceptor(sendMsg);接收来自Acceptor的消息(recvMsg)
100 |
101 | - 为了方便调试,我们可能需要知道整个过程请求提议的历史记录(getHistory)
102 |
103 | - 自己的提议最终被Acceptor接受的个数(getNumAccepted)
104 |
105 | - 清楚Paxos算法流程后,我们发现假设有两个Proposer依次提出编号递增的提议,最终会陷入死循环使得Paxos算法无法保证活性。所以,一般的做法是选取一个主Proposer作为领导,只有领导才能提出提议(setPrimary)。
106 |
107 | - Proposer类的一个难点在于提议发出后的各种状态转变与对应数据的处理。从提议发出到提议被接受整个过程,提议的状态是在不断地变化,但最终总会到达一个终止态。对于这种情况的处理,__状态机__注定是一个不错的选择。由于这里有点复杂我们将提议功能单独拿出来抽象为一个Proposer的协议类__PaxoProposerProtocol__。
108 |
109 | -由于各个节点收发消息是并行的,这里对消息的检测需要用到线程。这里HeartbeatListener来监听消息,HeartbeatSender用来发送消息。
110 |
111 |
112 | ```
113 | """
114 | @author: chaors
115 |
116 | @file: PaxoProposer.py
117 |
118 | @time: 2018/04/14 10:50
119 |
120 | @desc: 提议者
121 | """
122 |
123 | class PaxoProposer:
124 |
125 | #心跳监听类
126 | class HeartbeatListener(threading.Thread):
127 | pass
128 | #定时发送类
129 | class HeartbeatSender(threading.Thread):
130 | pass
131 |
132 |
133 | #初始化
134 | def __init__(self, port, proposers=None, acceptors=None):
135 | pass
136 | #开始
137 | def start(self):
138 | pass
139 |
140 | #停止
141 | def stop(self):
142 | pass
143 |
144 | #设置是否为领导者
145 | def setPrimary(self, isPrimary):
146 | pass
147 |
148 | #获取支持所有提议者的决策者
149 | def getGroup(self):
150 | pass
151 |
152 | #获取所有提议者
153 | def getProposers(self):
154 | pass
155 |
156 | #获取所有决策者
157 | def getAcceptors(self):
158 | pass
159 |
160 | #提议被承诺接受或最终接受的条件必须满足:获得1/2以上的Acceptor支持
161 | def getQuorumCount(self):
162 | pass
163 |
164 | #获取本地记录数据
165 | def getInstanceValue(self, instanceID):
166 | pass
167 |
168 | #获取历史记录
169 | def getHistory(self):
170 | pass
171 |
172 | #获取提议同意的数量
173 | def getNumAccepted(self):
174 | pass
175 |
176 |
177 | #通知其他提议者
178 | def notifyProposer(self, protocol, msg):
179 | pass
180 |
181 |
182 | #新的提议
183 | def newProposal(self, value, instance=None):
184 | pass
185 |
186 | # 发送消息
187 | def sendMsg(self, msg):
188 | pass
189 |
190 | # 接收消息
191 | def recvMsg(self, msg):
192 | pass
193 | ```
194 |
195 | ##### PaxoProposerProtocol类
196 |
197 | 用来提交一个提议,并用于提交提议后各种状态的处理。
198 |
199 | - 定义一些状态来表示当前Proposoer提议的各种状态
200 |
201 | - 发起提议(propose)
202 |
203 | - 状态机处理(doTranition)
204 |
205 | ```
206 | """
207 | @author: chaors
208 |
209 | @file: PaxoProposerProtocol.py
210 |
211 | @time: 2018/04/14 10:50
212 |
213 | @desc: 提议者协议
214 | """
215 |
216 | class PaxoProposerProtocol:
217 | #常量
218 | STATE_UNDEFIND = -1 #提议协议未定义
219 | STATE_PROPOSED = 0 #提议类型
220 | STATE_REJECTED = 1 #拒绝状态 提议被拒绝
221 | STATE_AGREED = 2 #提议被承诺接受 Prepare阶段获取大多数Acceptor承诺后的协议状态
222 | STATE_ACCEPTED = 3 #提议被接受
223 | STATE_UNACCEPTED = 4 #提议未被拒绝
224 |
225 | def __init__(self, proposer):
226 | pass
227 |
228 | #发起提议
229 | def propose(self, value, pID, instanceID):
230 | pass
231 |
232 | #状态过渡 根据状态机运行
233 | def doTranition(self, msg):
234 | pass
235 | ```
236 |
237 | ##### PaxoAcceptor类
238 |
239 | 决策者,对Proposer提出的提议和Accept请求做出回应。
240 |
241 | - 和Proposer类似的接口不再赘述。
242 |
243 | - 需要对比Proposer发来的提议版本(getHighestProposal)
244 |
245 | ```
246 | """
247 | @author: chaors
248 |
249 | @file: PaxoAcceptor.py
250 |
251 | @time: 2018/04/14 10:50
252 |
253 | @desc: 决策者
254 | """
255 |
256 | class PaxoAcceptor:
257 | def __init__(self, port, proposers):
258 | pass
259 |
260 | #开始
261 | def start(self):
262 | pass
263 |
264 | #停止
265 | def stop(self):
266 | pass
267 |
268 | #失败
269 | def fail(self):
270 | pass
271 |
272 | #恢复
273 | def recover(self):
274 | pass
275 |
276 | #发送消息
277 | def sendMsg(self, msg):
278 | pass
279 |
280 | #接收消息
281 | def recvMsg(self, msg):
282 | pass
283 |
284 |
285 | #通知客户端
286 | def notifyClient(self, protocol, msg):
287 | pass
288 |
289 |
290 | #获取本地记录数据
291 | def getInstanceValue(self, instanceID):
292 | pass
293 |
294 | #获取最高同意建议
295 | def getHighestProposal(self, instanceID):
296 | pass
297 | ```
298 |
299 | ##### PaxoAcceptorProtocol类
300 |
301 | 决策者协议,用来处理Proposer提出的提议,并同样使用状态机来处理自身各种状态。
302 |
303 | ```
304 | """
305 | @author: chaors
306 |
307 | @file: PaxoAcceptorProtocol.py
308 |
309 | @time: 2018/04/14 10:50
310 |
311 | @desc: 决策者协议
312 | """
313 |
314 | from Message import Message #协议依赖消息
315 |
316 | class PaxoAcceptorProtocol:
317 | #常量
318 | STATE_UNDEFIND = -1 #协议未定义
319 | STATE_PROPOSAL_RECEIVED = 0 #收到消息
320 | STATE_PROPOSAL_REJECTED = 1 #拒绝链接,网络不通可能
321 | STATE_PROPOSAL_AGREED = 2 #承诺将接受该提议 针对Proposer的PROPOSED请求
322 | STATE_PROPOSAL_ACCEPTED = 3 #接受该协议 针对Proposer的Accept请求
323 | STATE_PROPOSAL_UNACCEPTED = 4 #拒绝请求
324 |
325 | def __init__(self, client):
326 | pass
327 |
328 | #收到提议
329 | def recvProposal(self, msg):
330 | pass
331 |
332 |
333 | #过渡
334 | def doTranition(self, msg):
335 | pass
336 |
337 | #通知客户端
338 | def notifyClient(self, msg):
339 | pass
340 | ```
341 |
342 | ##### Message类
343 |
344 | Proposer和Acceptor的角色都有了,还差一个他们之间传递的消息类。这个消息有以下几种:
345 |
346 | - Proposer发出的提议请求
347 | - Proposer发出的Accept请求
348 | - Acceptor对提议请求的拒绝
349 | - Acceptor对提议请求的承诺
350 | - Acceptor对Accept请求的接受
351 | - Acceptor对Accept请求的不接受
352 | - 外部(Client)发给Proposer的提议
353 | - 作为对消息的回复消息
354 | - 定时的心跳信息,用来同步提议
355 |
356 | ```
357 | """
358 | @author: chaors
359 |
360 | @file: Message.py
361 |
362 | @time: 2018/04/14 09:31
363 |
364 | @desc: 消息传递类
365 | """
366 |
367 | class Message:
368 | #常量
369 | MSG_ACCEPTOR_AGREE = 0 #Acceptor对提议请求的承诺
370 | MSG_ACCEPTOR_ACCEPT = 1 #Acceptor对Accept请求的接受
371 | MSG_ACCEPTOR_REJECT = 2 #Acceptor对提议请求的拒绝
372 | MSG_ACCEPTOR_UNACCEPT = 3 #Acceptor对Accept请求的不接受
373 | MSG_ACCEPT = 4 #Proposer发出的Accept请求
374 | MSG_PROPOSE = 5 #Proposer发出的提议请求
375 | MSG_EXT_PROPOSE = 6 #外部(Client)发给Proposer的提议
376 | MSG_HEARTBEAT = 7 #定时的心跳信息,用来同步提议
377 |
378 | def __init__(self, cmd=None): #消息初始化有个状态
379 | pass
380 |
381 | #对某个消息的回复消息
382 | def copyAsReply(self, msg):
383 | pass
384 | ```
385 |
386 | ##### InstanceRecord类
387 |
388 | 提议被抽象在协议里,在系统达到一致性之前,Proposer可能尝试提交多次协议信息(包含提议)。在Proposer和Acceptor之间都需要保存所有的提议记录,所以两者都有一个InstanceRecord实例数组。
389 |
390 | 对于Proposer,InstanceRecord实例数组保存的是提交过的所有提议记录,并且会随着提议状态的改变更新记录状态(包括协议和记录的值)的值。
391 |
392 | 对于Acceptor,InstanceRecord实例数组保存的是Acceptor接收的Proposer提议请求,并随着提议版本的改变而更新。Acceptor给出承诺(agree)的条件是提议版本大于当前InstanceRecord里的协议版本;Acceptor接受提议(accept)的条件是当前Accept请求版本号比之前给出承诺的的提议版本号大。
393 |
394 | - 协议,包含了每次请求的协议信息(protocols)
395 | - 最高版本,当前所有提交的请求的最高版本(highestID)
396 | - 记录值,该次请求的值
397 |
398 | ```
399 | """
400 | @author: chaors
401 |
402 | @file: InstanceRecord.py
403 |
404 | @time: 2018/04/14 10:31
405 |
406 | @desc: 本地记录类,记录决策者,提议者之间协议
407 | """
408 |
409 | import threading, socket, pickle, queue,random
410 | # InstanceRecord本地记录类,决策者,提议者之间协议
411 | from PaxoProposerProtocol import PaxoProposerProtocol
412 |
413 | class InstanceRecord():
414 | def __init__(self):
415 | self.protocols = {} #协议字典
416 | self.highestID = (-1, -1) #最高版本(提议版本,端口号)
417 | self.value = None #提议值
418 |
419 | #增加协议
420 | def addProtocol(self, protocol):
421 | self.protocols[protocol.proposalID] = protocol
422 | #取得版本最高的协议 假设端口较大的Proposer为领导,优先承诺 端口相同时取版本号较大的
423 | if protocol.proposalID[1] > self.highestID[1] or \
424 | (protocol.proposalID[1] == self.highestID[1] \
425 | and protocol.proposalID[0] > self.highestID[0]):
426 | self.highestID = protocol.proposalID
427 |
428 | #抓取协议
429 | def getProtocol(self, protocolID):
430 |
431 | return self.protocols[protocolID]
432 |
433 | #清理协议
434 | def cleanProtocols(self):
435 | keys = self.protocols.keys() #取得所有可以
436 | #遍历删除协议
437 | for key in keys:
438 | protocol = self.protocols[key]
439 | if protocol.state == PaxoProposerProtocol.STATE_ACCEPTED:
440 | print("Deleting protocol")
441 | del self.protocols[key] #删除协议
442 | ```
443 |
444 | ##### MessagePump类
445 |
446 | 消息的结构是有了,但是它是怎么在节点(Proposer和Acceptor)之间传递的呢。这里我们封装一个基于Socket传递消息的网络类。这里接收消息需要借助一个线程,我们在构造一个接收消息的辅助类。
447 |
448 | 这里的只是不属于Paxos算法重点,就不赘述了。直接上代码。
449 |
450 | ```
451 | """
452 | @author: chaors
453 |
454 | @file: MessagePump.py
455 |
456 | @time: 2018/04/14 09:46
457 |
458 | @desc: 基于Socket传递消息,封装网络类传递消息
459 | """
460 |
461 | import threading #线程
462 | import pickle #对象序列化
463 | import socket #网络信息传输
464 | import queue #队列
465 |
466 | class MessagePump(threading.Thread):
467 | # 传递消息的辅助类
468 | class MPHelper(threading.Thread):
469 | def __init__(self, owner):
470 | self.owner = owner #传递消息的对象的所有者
471 |
472 | threading.Thread.__init__(self) # 父类初始化
473 |
474 | def run(self): #运行
475 | while not self.owner.abort: #只要所有者线程未结束
476 | try:
477 | #返回二进制数据,地址
478 | (bytes, addr) = self.owner.socket.recvfrom(2048) #收取消息
479 | msg = pickle.loads(bytes) #读取二进制转化为消息
480 | msg.source = addr[1] #取出返回的地址
481 | self.owner.queue.put(msg) #消息存入队列
482 |
483 | except Exception as e: #异常
484 | print(e)
485 |
486 | def __init__(self, owner, port, timeout=2):
487 | #基本参数初始化
488 | self.owner = owner #所有者
489 | self.timeout = 2 #超时时间
490 | self.port = port #网络接口
491 |
492 | #网络通信初始化
493 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #UDP通信
494 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 200000) #通信参数
495 | self.socket.bind(("localhost", port)) #socket绑定
496 | self.socket.settimeout(timeout) #设置超时
497 |
498 | self.queue = queue.Queue() #队列
499 | self.helper = MessagePump.MPHelper(self) #接收消息的工具类
500 |
501 | threading.Thread.__init__(self) #父类初始化
502 | self.abort = False #默认不终止状态
503 |
504 | #运行主线程
505 | def run(self):
506 | self.helper.start() #开启收消息的线程
507 | while not self.abort: #只要不是终止状态
508 | msg = self.waitForMsg() #阻塞等待消息
509 | self.owner.recvMsg(msg) #收取消息
510 |
511 | #等待消息
512 | def waitForMsg(self):
513 | try:
514 | msg = self.queue.get(True, 3) #从队列中取消息,最多等3s
515 |
516 | return msg
517 | except Exception as e:
518 | print(e)
519 |
520 | return None
521 |
522 | #发送消息
523 | def sendMsg(self, msg):
524 | bytes = pickle.dumps(msg) #把消息转成二进制
525 | addr = ("localhost", msg.to)
526 | self.socket.sendto(bytes, addr) #发送消息到地址
527 |
528 | return True
529 |
530 | #设置状态为放弃
531 | def doAbort(self):
532 | self.abort = True
533 | ```
534 |
535 | ##### Paxos_MainTest Paxos算法测试
536 |
537 | ```
538 | """
539 | @author: chaors
540 |
541 | @file: paxo_testMain.py
542 |
543 | @time: 2018/04/14 17:50
544 |
545 | @desc: Paxos算法测试用例
546 | """
547 |
548 | import threading, socket, pickle, queue,random
549 | import time
550 |
551 | from MessagePump import MessagePump
552 | from Message import Message
553 | from InstanceRecord import InstanceRecord
554 | from PaxoProposer import PaxoProposer
555 | from PaxoProposerProtocol import PaxoProposerProtocol
556 | from PaxoAcceptorProtocol import PaxoAcceptorProtocol
557 | from PaxoAcceptor import PaxoAcceptor
558 |
559 | if __name__ == '__main__':
560 | #Acceptor数量
561 | numclients = 5
562 | #实例化决策者数组,决策者节点端口号为65520-65525
563 | acceptors = [PaxoAcceptor(port, [56321, 56322]) for port in range(65520, 65520 + numclients)]
564 |
565 | #实例化提议者,端口号分别56321,56322 对应的决策者为acceptors
566 | proposer1 = PaxoProposer(56321, [56321, 56322], [acceptor.port for acceptor in acceptors])
567 | proposer2 = PaxoProposer(56322, [56321, 56322], [acceptor.port for acceptor in acceptors])
568 |
569 | #启动提议者提议程序
570 | proposer1.start()
571 | proposer1.setPrimary(True)
572 | proposer2.setPrimary(True)
573 | proposer2.start()
574 |
575 | #启动决策者决策程序
576 | for acceptor in acceptors:
577 | acceptor.start()
578 |
579 | #模拟网络中两个节点宕机
580 | acceptors[0].fail()
581 | acceptors[1].fail()
582 |
583 | #利用Socket机制发送提议给决策者
584 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
585 | start = time.time()
586 | for i in range(1000):
587 | m = Message(Message.MSG_EXT_PROPOSE)
588 | m.value = 0 + i
589 | m.to = 56322
590 | bytes = pickle.dumps(m)
591 | s.sendto(bytes, ("localhost", m.to))
592 |
593 | # if i == 2 or i == 30:
594 | # print(leader2.getInstanceValue(1))
595 |
596 | #当提议被999个决策者接受时结束整个提议程序
597 | while proposer1.getNumAccepted() < 999:
598 | print(u"休眠1秒--被接受: %d" % proposer1.getNumAccepted())
599 | time.sleep(1)
600 | end = time.time()
601 |
602 | print(u"休眠10秒")
603 | time.sleep(10)
604 | print(u"结束领导者")
605 | proposer1.stop()
606 | proposer2.stop()
607 | print(u"结束客户端")
608 | for acceptor in acceptors:
609 | acceptor.stop()
610 |
611 | print(u"领导者1 历史记录: %s" % proposer1.getHistory())
612 | print(u"领导者 2 历史记录: %s " % proposer2.getHistory())
613 | print(u"一共用了%d 秒" % (end - start))
614 | ```
615 |
616 | # 3.通过代码进一步了解Paox算法处理逻辑
617 |
618 | 上面已经完成了基本代码的架构,详细源码稍后我会上传到github。
619 | 接下来,我们通过一个简单的测试用例来再一次更深入地从代码层面理解Paxos算法的处理逻辑。
620 |
621 | 我们运行paxo_testMain代码,事先我在关键步骤处都打了断点。这样就可以完整地从代码角度看一次Paxos算法两个阶段的运行,也能直观地观察到各个步骤的代码处理逻辑。
622 |
623 | ## __!!!阅读说明:__
624 |
625 | ##### 1.x 对应Paxos算法Prepare阶段
626 | ##### 2.x 对应Paxos算法Commit阶段
627 |
628 | ### 废话少说上代码
629 |
630 | - ##### 1.0 [start---1.0]Proposer收到一个消息,消息类型为一个提议。首先判断自身是否为领导者,如果是创建协议
631 |
632 | 
633 |
634 | - ##### 1.1 [start---1.1]Proposer借助自身的PaxoProposerProtocol实例发起一个提议请求
635 |
636 | 
637 |
638 | 
639 |
640 | - ##### 1.2 [start---1.2]Acceptor收到一个消息,消息类型为提议。然后借助AcceptorProtocol实例处理提议。
641 |
642 | 
643 |
644 | - ##### 1.3[start---1.3] AcceptorProtocol收到一个提议,判断提议版本回复Proposer承诺接受消息或拒绝消息
645 |
646 | 
647 |
648 | - ##### 1.4[start---1.4]Proposer收到一个消息,类型为Acceptor的承诺(MSG_ACCEPTOR_AGREE)。既然不是Proposer最终要的接受提议的结果,转给ProposerPropotocal(当前消息的记录ID(instanceID)对应的协议)状态机处理。
649 |
650 | 
651 |
652 | - ##### [start---1.5] and [start---2.0]
653 | ###### --[start---1.5] ProposerPropotocal状态机函数收到一个MSG_ACCEPTOR_AGREE消息,此时表示新增加一个Acceptor承诺会接受我的请求。
654 |
655 | ###### -- [start---2.0_1]该条件下的代码会不断执行,直到许诺Proposer的数量超过半数,表示Prepare阶段基本结束。此时协议状态更新为协议被承诺接受(STATE_AGREED)。此时的Proposer向Acceptor集合发送Accept请求,请求Acceptor确认他们的许诺。
656 |
657 | ###### -- [start---2.0_2]同时,向其他Proposer广播该消息,使得其他Proposer知道Prepare阶段哪个提议获得的承诺最多。这样,在Commit阶段,他们可能通过改变提议来使系统尽快达到一致性。
658 |
659 | 
660 |
661 | - ##### 2.1[start---2.1]Acceptor收到一个消息,类型为来自Proposer的Accept请求。借助AcceptorPropotal处理该消息。
662 |
663 | 
664 |
665 | - ##### 2.2[start---2.2]
666 | ###### -- [start---2.2_1] AcceptorPropotal收到Proposer发出的Accept请求。按Paxos算法思想,这里需要判断请求版本号,当且仅当Acceptor之前承诺过的提议版本号最大值小于Accept请求版本号才会接受该Proposer提议。这里我们借助“先入为主”的思想简化问题,只要这时候协议状态为STATE_PROPOSAL_AGREED,就给所有Proposer广播消息表示自己确认接受该Proposer提议。
667 |
668 | ###### -- [start---2.2_2] AcceptorPropotal通知Acceptor更新InstanceRecord的值,到此时已有一个提议被一个Acceptor最终接受。
669 |
670 | 
671 |
672 | - ##### 2.3[start---2.3]Proposer收到一个消息,类型为Acceptor确认接受提议(MSG_ACCEPTOR_ACCEPT),根据该消息更新Proposer的InstanceRecord(新建record,追加协议等)。并将消息交给ProposerProtocol状态机处理。
673 |
674 | 
675 |
676 | - ##### 2.4[start---2.4]
677 | ###### [start---2.4_1] -- ProposerProtocol收到一个Acceptor的最终确认消息(MSG_ACCEPTOR_ACCEPT),此时表示新增一个Acceptor最终接受了我的提议。但此时的协议状态仍然是STATE_AGREED状态,因为一个提议最终被系统接受必须先被超半数的Acceptor节点确认接受。
678 |
679 | ###### [start---2.4_2] STATE_AGREED条件下的代码会不断执行,直到最终接受提议的Acceptor超过半数。这时,协议状态由STATE_AGREED状态更新为最终被系统确认状态(STATE_ACCEPTED)。最后,当前Proposer更新自己的InstanceRecord记录。当然,这里也有一种可能是被超过半数节点不接受,那么同样其他Proposer节点必有一个节点提议被接受。
680 |
681 | 
682 |
683 | - ##### 2.5[start---2.5] Proposer更新InstanceRecord记录,如果协议最终被大多数Acceptor拒绝则尝试重新提议(步骤回到1.1)。
684 |
685 | 
686 |
687 | # 总结
688 | 以上,我们就从代码层面对PAxos算法有一个更深入的了解,我想根据代码再反过来理解PAxos算法,势必会有一个更深刻的印象。
689 |
690 | 刚开始听说Paxos也是好几脸懵逼,也是鏖战一个周末才有这么点体悟。还在学习区块链的小小白起步中,写这篇帖子也是记录下自己学习的过程。勉之。
691 |
692 |
693 |
694 |
--------------------------------------------------------------------------------