比如说,以下展示的就是 Redis 服务器运行时输出的一些日志,这些日志记录了 Redis 开始运行的时间,载入数据库所耗费的时长,接收客户端连接所使用的端口号,以及进行数据持久化操作的时间点等信息:

    为了记录程序运行的状态,又或者为了对日志进行分析,我们有时候会需要把程序生成的日志储存起来。

    比如说,我们可以通过使用 命令,将日志的生成时间用作键、日志的内容用作值,把上面展示的日志储存到多个字符串键里面:

    1. redis> SET "06 Jul 17:40:49.611" "# Server started, Redis version 3.1.999"
    2. OK
    3.  
    4. redis> SET "06 Jul 17:40:49.627" "* DB loaded from disk: 0.016 seconds"
    5. OK
    6.  
    7. redis> SET "06 Jul 17:40:49.627" "* The server is now ready to accept connections on port 6379"
    8. OK
    9.  
    10. redis> SET "06 Jul 18:29:20.009" "* DB saved on disk"
    11. OK

    遗憾的是,这种日志储存方式并不理想,它的主要问题有两个:

    • 这种方法需要在数据库里面创建非常多的键。因为 Redis 每创建一个键就需要消耗一定的额外资源(overhead)来对键进行维护,所以键的数量越多,消耗的额外资源就会越多。

    • 这种方法将全部日志分散地储存在不同的键里面,当程序想要对特定的日志进行分析的时候,它就需要花费额外的时间和资源去查找指定的日志,这给分析操作带来了额外的麻烦和资源消耗。


    代码清单 2-5 使用字符串键实现高效的日志储存程序:/string/log.py

    1. LOG_SEPARATOR = "\n"
    2.  
    3.  
    4. def __init__(self, client, key):
    5. self.client = client
    6. self.key = key
    7.  
    8. def add(self, new_log):
    9. """
    10. 将给定的日志储存起来。
    11. """
    12. new_log += LOG_SEPARATOR
    13. self.client.append(self.key, new_log)
    14.  
    15. def get_all(self):
    16. """
    17. 以列表形式返回所有日志。
    18. all_logs = self.client.get(self.key)
    19. if all_logs is not None:
    20. log_list = all_logs.split(LOG_SEPARATOR)
    21. log_list.remove("")
    22. return log_list
    23. else:
    24. return []

    日志储存程序的 add() 方法负责将新日志储存起来。这个方法首先会将分隔符追加到新日志的末尾:

    1. new_log += LOG_SEPARATOR

    然后调用 APPEND 命令,将新日志追加到已有日志的末尾:

    1. self.client.append(self.key, new_log)

    举个例子,如果用户输入的日志是:

    那么 add() 方法首先会把分隔符 "\n" 追加到这行日志的末尾,使之变成:

    1. "this is log1\n"

    然后调用以下命令,将新日志追到已有日志的末尾:

    1. APPEND key "this is log1\n"
    1. all_logs = self.client.get(self.key)

    接着,程序会检查 这个值是否为空,如果为空则表示没有日志被储存,程序直接返回空列表 [] 作为 get_all() 方法的执行结果;另一方面,如果值不为空,那么程序将调用 Python 的 split() 方法对字符串值进行分割,并将分割结果储存到 log_list 列表里面:

    1. log_list = all_logs.split(LOG_SEPARATOR)

    因为 split() 方法会在结果中包含一个空字符串,而我们并不需要这个空字符串,所以程序还会调用 remove() 方法,将空字符串从分割结果中移除,使得 log_list 列表里面只保留被分割的日志:

    在此之后,程序只需要将包含了多条日志的 列表返回给调用者就可以了:

    1. return log_list

    举个例子,假设我们使用 add() 方法,在一个字符串键里面储存了 "this is log1"this is log2"this is log3" 这三条日志,那么 get_all() 方法在使用 GET 命令获取字符串键的值时,将得到以下结果:

    1. "this is log1\nthis is log2\nthis is log3"

    在使用 split(LOG_SEPARATOR) 方法对这个结果进行分割之后,程序将得到一个包含四个元素的列表,其中列表最后的元素为空字符串:

      在调用 方法移除列表中的空字符串之后,列表里面就只会包含被储存的日志:

      1. ["this is log1", "this is log2", "this is log3"]

      以下代码展示了这个日志储存程序的使用方法: