最近开始Swift源码分析,无非就是不想纸上谈兵,究竟是用什么算法,和方法实现的功能,所以决定开始写源码分析。
当你安装完swift,你需要做的第一件事情就是创建ring文件,而你用的第一个命令就是swift-ring-builder。swift-ring-builder文件位于源码的bin目录下,是swift最基本的命令,它与swift/common/ring/下的文件一起实现ring文件创建,添加,平衡,等操作。
对于初学者(比如说我)重写源码片段,可以更加深入的了解源码的原理,同时还能对python语言以及相关的库有更深的了解,swift-ring-builder中主要的功能实现就是在Commands类中,比如default(),create(),add(),rebalance()等, 然后main方法会根据你提供的相应参数,来提供执行相应的方法,然后其中的方法会调用/swift/common/ring/下的builder.py中相应的方法最终实现相应的操作,
当我们通过create创建account.builder文件的时候,commod == argv[2] 也就是create 然后执行create来创建account.builder。之后的操作,只要是存在account.builder文件,就会打开这个文件,生产builder实例,来进行相应的操作。其中的 default方法是显示当前的builder信息,可以用来在rebalance之前 检查add的device 。
其中reblance是最重要的方法,当中会涉及到/swift/common/ring下的ring.py builder.py utils.py文件,涉及到了一致性哈希算法和策略的实现,下个博客会具体分析。
代码片段:基本就是把源码抽出来,没写什么注释和异常处理,生成的builder文件,可以通过diff命令,比较是否跟使用swift-ring-builder命令创建的builder文件一样(答案当然是肯定的)。
001 | #! /usr/bin/env python |
002 |
003 | from sys import argv, exit, modules |
004 | import cPickle as pickle |
005 | from os.path import basename, dirname, exists, join as pathjoin |
006 | from itertools import islice, izip |
007 |
008 | from builder import RingBuilder |
009 |
010 |
011 | MAJOR_VERSION = 1 |
012 | MINOR_VERSION = 3 |
013 | EXIT_SUCCESS = 0 |
014 | EXIT_WARNING = 1 |
015 | EXIT_ERROR = 2 |
016 |
017 |
018 | class Commands: |
019 |
020 | def unknown(): |
021 | print 'Unknown command: %s' % argv[ 2 ] |
022 | exit(EXIT_ERROR) |
023 | |
024 | def create(): |
025 | """ |
026 | my-ring-builder <builder_file> create <part_power> <replicas> |
027 | <min_part_hours> |
028 | """ |
029 |
030 | if len (argv) < 6 : |
031 | print Commands.create.__doc__.strip() |
032 | exit(EXIT_ERROR) |
033 | builder = RingBuilder( int (argv[ 3 ]), int (argv[ 4 ]), int (argv[ 5 ])) |
034 | pickle.dump(builder.to_dict(), open (argv[ 1 ], 'wb' ), protocol = 2 ) |
035 | exit(EXIT_SUCCESS) |
036 |
037 | def default(): |
038 | """ |
039 | Shows information about the ring and the devices within. |
040 | """ |
041 | print '%s, build version %d ' % (argv[ 1 ], builder.version) |
042 | zones = 0 |
043 | balance = 0 |
044 | if builder.devs: |
045 | zones = len ( set (d[ 'zone' ] for d in builder.devs if d is not None )) |
046 | #blance = builder.get_balance() |
047 | print '%d partitions, %d replicas, %d zones, %d devices' \ |
048 | % (builder.parts, builder.replicas, zones, |
049 | len ([d for d in builder.devs if d])) |
050 | print 'The minimun number of hours before a partition can be' \ |
051 | 'reassigned is %s' % builder.min_part_hours |
052 | if builder.devs: |
053 | print 'Devices: id zone ip address port name' \ |
054 | 'weight partition' |
055 | weighted_parts = builder.parts * builder.replicas / \ |
056 | sum (d[ 'weight' ] for d in builder.devs if d is not None ) |
057 | for dev in builder.devs: |
058 | if dev is None : |
059 | continue |
060 | if not dev[ 'weight' ]: |
061 | if dev[ 'parts' ]: |
062 | blance = 999.99 |
063 | else : |
064 | blance = 0 |
065 | else : |
066 | blance = 100.0 * dev[ 'parts' ] / \ |
067 | (dev[ 'weight' ] * weighted_parts) - 100.0 |
068 | print ' %5d %5d %15s %5d %9s %6.02f %10s' % \ |
069 | (dev[ 'id' ], dev[ 'zone' ], dev[ 'ip' ], dev[ 'port' ], |
070 | dev[ 'device' ], dev[ 'weight' ], dev[ 'parts' ]) |
071 | exit(EXIT_SUCCESS) |
072 |
073 | def add(): |
074 | """ |
075 | ....... |
076 | """ |
077 |
078 | dev_and_weights = izip(islice(argv, 3 , len (argv), 2 ), |
079 | islice(argv, 4 , len (argv), 2 )) |
080 | for devstr, weightstr in dev_and_weights: |
081 | if not devstr.startswith( 'z' ): |
082 | print 'Invalid add value: %s' % devstr |
083 | exit(EXIT_ERROR) |
084 | i = 1 |
085 | while i < len (devstr) and devstr[i].isdigit(): |
086 | i + = 1 |
087 | zone = int (devstr[ 1 :i]) |
088 | rest = devstr[i:] |
089 |
090 | i = 1 |
091 | while i < len (rest) and rest[i] in '0123456789.' : |
092 | i + = 1 |
093 | ip = rest[ 1 :i] |
094 | rest = rest[i:] |
095 | print ip |
096 | i = 1 |
097 | while i < len (rest) and rest[i].isdigit(): |
098 | i + = 1 |
099 | port = int (rest[ 1 :i]) |
100 | rest = rest[i:] |
101 |
102 | i = 1 |
103 | while i < len (rest) and rest[i] ! = '_' : |
104 | i + = 1 |
105 | device_name = rest[ 1 :i] |
106 | rest = rest[i:] |
107 |
108 | meta = '' |
109 | if rest.startswith( '_' ): |
110 | meta = rest[ 1 :] |
111 | weight = float (weightstr) |
112 | for dev in builder.devs: |
113 | if dev is None : |
114 | continue |
115 | if dev[ 'ip' ] = = ip and dev[ 'port' ] = = port and \ |
116 | dev[ 'device' ] = = device_name: |
117 | print 'already uses %s' |
118 | exit(EXIT_ERROR) |
119 |
120 | next_dev_id = 0 |
121 | if builder.devs: |
122 | next_dev_id = max (d[ 'id' ] for d in builder.devs if d) + 1 |
123 | builder.add_dev({ 'id' : next_dev_id, 'zone' : zone, 'ip' : ip, |
124 | 'port' : port, 'device' : device_name, |
125 | 'weight' : weight, 'meta' : meta}) |
126 | print 'Device z%s-[%s]:%s/%s_"%s" with %s weight got id %s' % \ |
127 | (zone, ip, port, device_name, meta, weight, next_dev_id) |
128 | pickle.dump(builder.to_dict(), open (argv[ 1 ], 'wb' ), protocol = 2 ) |
129 | exit(EXIT_SUCCESS) |
130 |
131 | def rebalance(): |
132 | """ |
133 | Attempts to rebalance the ring by reassigning partitions |
134 | """ |
135 |
136 | devs_changed = builder.devs_changed |
137 | |
138 | last_balance = builder.get_balance() |
139 | parts, balance = builder.rebalance() |
140 | |
141 | builder.validate() |
142 | print 'Reassigned %d (%.02f%%) partitions. Balance is now %.02f.' % \ |
143 | (parts, 100.0 * parts / builder.parts, balance) |
144 | status = EXIT_SUCCESS |
145 | if balance > 5 : |
146 | print 'NOTE: Balance of %.02f indicates you should push the' % \ |
147 | balance |
148 | status = EXIT_WARNING |
149 | builder.get_ring().save(ring_file) |
150 | pickle.dump(builder.to_dict(), open (argv[ 1 ], 'wb' ), protocol = 2 ) |
151 | exit(status) |
152 | if __name__ = = '__main__' : |
153 | |
154 | if exists(argv[ 1 ]): |
155 | builder = pickle.load( open (argv[ 1 ], 'rb' )) |
156 | if not hasattr (builder, 'devs' ): |
157 | builder_dict = builder |
158 | builder = RingBuilder( 1 , 1 , 1 ) |
159 | builder.copy_from(builder_dict) |
160 | ring_file = argv[ 1 ] |
161 | if ring_file.endswith( '.builder' ): |
162 | ring_file = ring_file[: - len ( '.builder' )] |
163 | ring_file + = '.ring.gz' |
164 | if len (argv) = = 2 : |
165 | command = "default" |
166 | else : |
167 | command = argv[ 2 ] |
168 | Commands.__dict__.get(command, Commands.unknown)() |