nova api limit
nova的一天请求量大概在30w条,大量的并发操作经常会block到某一个点上,比如数据库读写。如何使得nova集群可以有条不紊的处理请求是保证服务稳定性的一个难点。对于解决上述问题,我们希望在nova拒绝掉处理不过来的请求,而保证用户可以及时获取响应,而不是等待600s后的timeout。
*ratelimit是一个用来限制api响应的wsgi中间件,只需要配置即可方便使用。本文着重分析其实现流程。
源码分析:
从代码结构来看,该功能主要由以下几个calss实现:
- 1 Limit
- 2 Limiter
- 3 LimitsController
- 4 RateLimitingMiddleware
1. class Limit
该类主要功能是从配置文件中读取limit信息并将其封装一个Limit对象。并且在其中判断响应是否接受。
配置示例如下:
limits =(GET, ““, ., 1, MINUTE);(POST, “*/servers”, ^/servers, 1, MINUTE);
该示例有两个limit,一个是针对GET请求,一个是针对POST方法下的*/server 请求。
针对 (GET, ““, ., 120, MINUTE) 其意思是,每分钟可以接受120个GET方法的请求
封装内容如下
self.verb = verb # GET self.uri = uri # * self.regex = regex # .* self.value = int(value) # 120 self.unit = unit # 60 self.unit_string = self.display_unit().lower() # minute self.remaining = int(value) # 120 if value <= 0: raise ValueError("Limit value must be > 0") self.last_request = None self.next_request = None self.water_level = 0 self.capacity = self.unit self.request_value = float(self.capacity) / float(self.value) # 0.5 即每0.5秒接受一个请求
2. class Limiter
该类主要功能持有Limit对象,并将其内容,状态缓存在内存中。
self.limits = copy.deepcopy(limits) self.levels = collections.defaultdict(lambda: copy.deepcopy(limits))
该init主要是持有Limit对象,冰球保存状态,其中self.levels生成一个类似dict的数据结构,可以针对每个user做不同的limit。
3.class LimitsController
该类主要功能是实现对Limit对象的增删改查,在H版本还未实现主要功能,仅仅实现了一个index功能。
4.class RateLimitingMiddleware
该类主要功能主要作为wsgi一个中间件,对外提供入口,处理每一个请求。
流程分析:
示例:
客户端输入: nova service-list
实际执行的请求是: GET http://127.0.0.1:8774/v2/13d216f5470b49328e78dd3a11164259/os-services
代码片段 (一)
/nova/api/openstack/compute/limits.py(289)call()
verb = req.method url = req.url context = req.environ.get("nova.context") if context: username = context.user_id else: username = None delay, error = self._limiter.check_for_delay(verb, url, username) if delay: msg = _("This request was rate-limited.") retry = time.time() + delay return wsgi.RateLimitFault(msg, error, retry) req.environ["nova.limits"] = self._limiter.get_limits(username) return self.application
首先分析请求属于什么http方法,以及url是什么
verb :get
url : http://127.0.0.1:8774/v2/13d216f5470b49328e78dd3a11164259/os-services
delay用于接受请求被判断后需要延迟多久,并将这个错误以code429形式返回给用户。
代码片段(二)
/nova/api/openstack/compute/limits.py(328)check_for_delay()
def check_for_delay(self, verb, url, username=None): delays = [] for limit in self.levels[username]: delay = limit(verb, url) if delay: delays.append((delay, limit.error_message)) if delays: delays.sort() return delays[0] return None, None
self.levels是nova预先从配置文件加载的所有limit的条件。
delays用来存limit判断需要延迟响应的请求,示例如下:
[(39.632054805755615, u’Only 1 GET request(s) can be made to “*” every minute.’)]
代码片段(三)
/nova/api/openstack/compute/limits.py(178)call()
*该方法主要判断当前请求是否在阈值内,即阈值算法*
if self.verb != verb or not re.match(self.regex, url): return now = self._get_time() if self.last_request is None: self.last_request = now leak_value = now - self.last_request self.water_level -= leak_value self.water_level = max(self.water_level, 0) self.water_level += self.request_value difference = self.water_level - self.capacity self.last_request = now if difference > 0: self.water_level -= self.request_value self.next_request = now + difference return difference cap = self.capacity water = self.water_level val = self.valu self.remaining = math.floor(((cap - water) / cap) * val) self.next_request = now
主要是self.water_level控制水位,来判断两次请求之间时间是否超过阈值,比如对于1分钟120次请求的limit。那么判断两次请求之间是否大于0.5秒,如果difference>0,则认为两次请求之间过短,把需要延迟的时间,补齐0.5s-两次请求间隔的时间返回。否则则认为请求可以接受。
总结:
对api限制请求响应,大致有两种做法,一种使用一个bucket,采用计数方式,每一个请求拿一个令牌,先到先得。另外一种是计算时间间隔。nova采用的后一种,看起来实现过程简单,并且粒度很细,可以针对某个http方法限制,也可以针对某个url限制,还可以针对某个用户限制。limit模块做一个wsgi中间件,与nova本身解耦,使用的时候,只需要在apt-paste.ini文件中添加,并且该模块可以很方便的移植到openstack其他项目中。真正的难点在于如何合理的限制api,限制哪些api,阈值是多少。这部分可以借鉴elk,通过比对14天nova各api响应数量,可以初步得到一个正常使用值。但该值并不是阈值,阈值需要借助qa性能组力量进行压力测试。