# -*- coding: utf-8 -*- # # Copyright 1999-2017 Tencent Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import time import threading STATE_CLOSED = 0 STATE_HALF_OPEN = 1 STATE_OPEN = 2 class Counter(object): def __init__(self): self.failures = 0 self.total = 0 self.consecutive_successes = 0 self.consecutive_failures = 0 def on_success(self): self.total += 1 self.consecutive_successes += 1 self.consecutive_failures = 0 def on_failure(self): self.total += 1 self.failures += 1 self.consecutive_failures += 1 self.consecutive_successes = 0 def clear(self): self.failures = 0 self.total = 0 self.consecutive_successes = 0 self.consecutive_failures = 0 def get_failure_rate(self): if self.total == 0: return 0.0 return float(self.failures) / self.total class CircuitBreaker(object): def __init__(self, breaker_setting): self.breaker_setting = breaker_setting self.lock = threading.Lock() self.counter = Counter() self.state = STATE_CLOSED self.expiry = time.time() + breaker_setting.window_interval self.generation = 0 def ready_to_open(self): return (self.counter.failures >= self.breaker_setting.max_fail_num and self.counter.get_failure_rate() >= self.breaker_setting.max_fail_percent) or \ self.counter.consecutive_failures >= 5 def current_state(self, now): if self.state == STATE_CLOSED: if self.expiry <= now: self.to_new_generation(now) elif self.state == STATE_OPEN: if self.expiry <= now: self.switch_state(STATE_HALF_OPEN, now) return self.state, self.generation def switch_state(self, new_state, now): if self.state == new_state: return self.state = new_state self.to_new_generation(now) def to_new_generation(self, now): self.generation = (self.generation + 1) % 10 self.counter.clear() if self.state == STATE_CLOSED: self.expiry = now + self.breaker_setting.window_interval elif self.state == STATE_OPEN: self.expiry = now + self.breaker_setting.timeout else: # STATE_HALF_OPEN self.expiry = time.time() # whether to use the backup region def before_requests(self): self.lock.acquire() now = time.time() state, generation = self.current_state(now) self.lock.release() if state == STATE_OPEN: return generation, True return generation, False def after_requests(self, before, success): self.lock.acquire() now = time.time() state, generation = self.current_state(now) self.lock.release() # the breaker has entered the next generation, the current results abandon. if generation != before: return if success: self.on_success(state, now) else: self.on_failure(state, now) def on_success(self, state, now): if state == STATE_CLOSED: self.counter.on_success() elif state == STATE_HALF_OPEN: self.counter.on_success() if self.counter.total - self.counter.failures >= self.breaker_setting.max_requests: self.switch_state(STATE_CLOSED, now) def on_failure(self, state, now): if state == STATE_CLOSED: self.counter.on_failure() if self.ready_to_open(): self.switch_state(STATE_OPEN, now) elif state == STATE_HALF_OPEN: self.counter.on_failure() self.switch_state(STATE_OPEN, now)
Memory