统计http请求最近5s内的请求成功率。

背景

  • 在请求量特别大的情况下,比如1s有几十万的请求,如何统计最近5s内请求的成功率?

整体思路

  • 1、使用redis的过期机制实现;
  • 2、统计学的思想。

实现

方案一:使用zset集合来实现

流程:

  • 可以使用Redis的zset有序集合来记录请求的时间戳和请求结果。每次请求,将当前的时间戳作为score,请求结果(如成功或失败)作为value,插入有序集合中。

  • 每秒钟统计最近5秒钟内的请求结果,根据统计结果计算出请求成功率。可以使用Redis的zrangebyscore命令获取五秒钟前到当前时间的时间戳列表,然后使用pipelining管道一次性获取所有时间戳对应的请求结果。然后分析请求结果,计算请求成功率。

伪代码示例

import time
import redis

redis_conn = redis.StrictRedis(host='localhost', port=6379, db=0)

def record_request_result(result):
    timestamp = int(time.time())
    redis_conn.zadd('request_result', {timestamp: result})

def calculate_success_rate():
    now = int(time.time())
    five_seconds_ago = now - 5

    request_results = redis_conn.zrangebyscore('request_result', five_seconds_ago, now)
    success_count = 0
    total_count = len(request_results)
    for result in request_results:
        if result == 'success':
            success_count += 1

    if total_count > 0:
        success_rate = success_count / total_count
    else:
        success_rate = 0.0

    return success_rate

在上述伪代码中,我们使用了Redis的zadd命令将每个请求的结果记录到zset有序集合中。在计算请求成功率时,使用了Redis的zrangebyscore命令一次性获取最近5秒钟内的时间戳列表,然后通过pipeline获取对应的请求结果。最后统计请求成功的数量和总数量,计算成功率。

这样,我们就可以在高并发的情况下实时统计请求成功率,对系统的性能和稳定性进行监控,及时追踪问题并进行优化。

产生的问题
1、集中过期。

  • 如果把几十万的请求都放进去会导致,每次都有几十万的数据集中过期,这会导致redis大部分时间都在处理过期的数据(比如redis会随机取100条数据,当过期率超过25%的时候,redis会一直重复此步骤处理数据,直到简直25%以下)

2、占用太多的内存

  • 如果把几十万的请求都放进去,会导致系统的内存使用量快速上升,如果严重则会导致系统崩溃。

问题优化
加一个随机值,只统计其中千分之一的请求情况。

def record_request_result(result):
    # 随机统计里面千分之一的请求情况
    if random.randint(1, 1000) == 1:
        timestamp = int(time.time())
        redis_conn.zadd('request_result', {timestamp: result})

方案二: 使用Bloom Filter等数据结构

思路

  • 使用Bloom Filter来存储请求结果,以达到占用更少的内存空间。Bloom Filter是一种高效的数据结构,用于判断某个元素是否可能存在于一个集合中。因为Bloom Filter的查询操作是常量复杂度的,因此可以实现高速的查询操作。
  • 将请求结果先进行哈希操作,然后存储到Bloom Filter中。
  • 在统计成功率时,先对当前时间向前推五秒钟内所有的时间段进行哈希操作,并查询每个哈希值在Bloom Filter中的存在情况。这样就可以得到五秒钟内请求成功的数量和请求总数,从而计算请求成功率。

伪代码示例:

import time
import redis
from pybloom_live import ScalableBloomFilter

redis_conn = redis.StrictRedis(host='localhost', port=6379, db=0)

bloom_filter = ScalableBloomFilter(mode=ScalableBloomFilter.SMALL_SET_GROWTH)

def record_request_result(result):
    timestamp = int(time.time())
    hashed_result = hash(result)
    bloom_filter.add(hashed_result)
    redis_conn.lpush(timestamp, result)
    redis_conn.expire(timestamp, 5)

def calculate_success_rate():
    now = int(time.time())
    five_seconds_ago = now - 5

    success_count = 0
    total_count = 0
    for timestamp in range(five_seconds_ago, now):
        results = redis_conn.lrange(timestamp, 0, -1)
        for result in results:
            hashed_result = hash(result)
            if hashed_result in bloom_filter:
                success_count += 1
            total_count += 1

    if total_count > 0:
        success_rate = success_count / total_count
    else:
        success_rate = 0.0

    return success_rate

在上述伪代码中,我们使用了Bloom Filter来对请求结果进行存储,以达到占用更少的内存。在计算请求成功率时,我们先对每个时间段内的请求结果进行哈希操作,然后查询哈希值在Bloom Filter中的存在情况,计算成功请求数和总请求数,从而计算成功率。

通过使用Bloom Filter等数据结构,我们可以在内存空间较小的情况下实现高效的数据存储和快速的查询操作,对于高并发的情况下请求成功率的实时统计有着一定的帮助。

存在的问题点

  • 这种方式bloom_filter中的数据会越来越多

问题优化

  • 当使用Bloom Filter进行请求结果存储时,Bloom Filter中的数据会不停累加,可能导致Bloom Filter所占用的内存空间逐渐变大。为了解决这个问题,我们可以采用布隆过滤器中的一种变种——Counting Bloom Filter。

  • Counting Bloom Filter可以使用数组代替位数组,并在其中计数每个哈希值的出现次数。与传统的Bloom Filter不同的是,Counting Bloom Filter在查询时需要使用一个计数器数组来记录哈希值在过去五秒内出现的次数。

下面是一个使用Counting Bloom Filter的示例代码:


import time
import redis
from pybloom_live import ScalableBloomFilter

redis_conn = redis.StrictRedis(host='localhost', port=6379, db=0)

bloom_filter = ScalableBloomFilter(mode=ScalableBloomFilter.SMALL_SET_GROWTH)

def record_request_result(result):
    timestamp = int(time.time())
    hashed_result = hash(result)
    bloom_filter.add(hashed_result)
    redis_conn.incr(hashed_result, amount=1)
    redis_conn.lpush(timestamp, result)
    redis_conn.expire(timestamp, 5)

def calculate_success_rate():
    now = int(time.time())
    five_seconds_ago = now - 5

    success_count = 0
    total_count = 0
    for timestamp in range(five_seconds_ago, now):
        results = redis_conn.lrange(timestamp, 0, -1)
        for result in results:
            hashed_result = hash(result)
            count = redis_conn.get(hashed_result)
            if count:
                success_count += int(count)
            total_count += 1

    if total_count > 0:
        success_rate = success_count / total_count
    else:
        success_rate = 0.0

    return success_rate

在上面的伪代码中,我们使用了Counting Bloom Filter来对请求结果进行存储。与Bloom Filter不同的是,我们使用Redis的incr命令来对每个哈希值进行计数,并将计数结果存储在Redis数据库中。

在计算请求成功率时,我们先对每个时间段内的请求结果进行哈希操作,然后查询哈希值在Redis数据库中的计数值,计算成功请求数和总请求数,从而计算成功率。

通过使用Counting Bloom Filter,我们可以在内存空间较小的情况下实现高效的数据存储和快速的查询操作,并且可以避免Bloom Filter中数据不断增加的问题。

总结

  • 正常来说我们使用方案一即可满足我们的需求,同时内存消耗也比较小,但是有一点需要注意的是,方案一如果请求量特别小,随机值特别大的话可能导致请求的结果不准确。

你可能感兴趣的:(python)