使用 Python 编写一个 Memcached 的 CLI

原文地址:

http://52sox.com/use-python-write-a-memcached-cli/

近期在项目中使用到了Memcached,相比redis较为齐全的工具,这个非关系型数据库客户端只能通过telnet与服务器端进行交互,于是有了为这个数据编写1个简便的客户端工具的想法。
如果你使用过redis提供的redis-cli,你会发现这个工具是多么的方便,比如某个命令你忘记了其使用的方式,你可以通过如下的方式来查看:

 127.0.0.1:6379> help get

  GET key
  summary: Get the value of a key
  since: 1.0.0
  group: string

目标

于是,打算参考redis-cli编写1个Memcached的CLI,在这个版本中,我们要实现:

  • 输入正确命令和参数后立即返回对应的结果

  • 1个提示帮助命令的功能

实现思路

为了实现这个命令行版本,我们需要考虑以下几个方面:

  • 支持修改连接服务器的监听地址和端口

  • 对传入的参数进行解析并调用对应的方法

  • 调用对应命令并传入参数后,如果参数不对能给出错误的提示

  • 支持帮助选项

对于第1个问题,我们可以通过参数的方式来解决。如果用户没有传入对应的参数,则使用默认的参数进行绑定。关于从命令行中解析参数的方式,Python提供了几种方式,这里我选用的是argparse模块来操作。
而对应后面3个问题,我们可以借助标准库中的cmd模块来实现。

选择客户端绑定

在Python的Memcached的客户端实现中,有python-memcachedpymemcache以及pylibmc等多种第3方库,这里我采用的是pymemcache来说明我们这个命令行的实现,主要原因在于它是纯python实现中最快和异常处理比较好的1个库。

实现

下面,我们正式开始实现这个命令行。由于我们一般不会直接实例化Cmd类。我们先定义1个MemcachedCLI类,这个类继承自cmd模块中的Cmd类。接着,我们会实例化1个Memcached类的实例。

from pymemcache.client.base import Client
from cmd import Cmd

class MemcachedCLI(Cmd):
    def __init__(self, host, port):
        self.client = Client((host, port))

之后,我们需要对用户输入的内容进行解析并调用其对应的方法。这方面,cmd模块已经帮助处理这方面的内容了,我们只需要在该类中实现1个do_*的方法,当我们在命令中输入的1个对应的命令时,比如hello,其将调用1个do_hello的方法。
我们知道,在Memcached中有多个命令,如果我们对这些命令1个个的实现,不是1件容易的事情。比如,在memcached中有1个get方法用于获取指定键名的数值,而在pymemcache的实现中,Client实例有1个get方法对应上述的这个指令。
因此,在这里,我们采用动态获取属性的方式来简化的工作量,即通过如下的方式来调用对应的get方法:

getattr(self.client,'get')

我们将这个属性的处理过程封装在1个get_action的方法中:

def get_action(self, client):
    for name in dir(client):
        if not name.startswith('_'):
            attr = getattr(client, name)
            if callback(attr):
                setattr(self.__class__, 'do_' + name, self._make_cmd(name))

在这里,我们遍历Client类实例的每个属性,如果对应的属性不以_字符开头,我们则获取该属性,如果该属性可以调用,我们再进行属性的设置,将其设置为该类的do_*属性,通过该类_make_cmd方法返回对应的数值。
在这里,我们将生成的cmd命令封装在_make_cmd中,在这里我们将通过name属性获取到用户在命令行中输入的第1个参数:

    def _make_cmd(self, name):
        def handler(self, line):
            parts = line.split()
            try:
                print(getattr(self.client, name)( *parts))
            except Exception as e:
                print('Error:{0}'.format(e))
        return handler

在这里我们需要对传入的字符串进行切分。比如,用户输入了set name 20,那么后面2个参数将以字符串name 20的形式传入。之后,我们尝试获取Client类的该属性,并传入解包后的参数进行调用。如果传入的参数不正确,将触发1个异常而被捕获,并直接输出。最后,我们返回1个handler函数。
这样,当我们输入如下的命令时:

set name 20

这将调用Client实例的set方法,并将name和20以参数的形式传入,从而实现设置对应键名及其键值。
这样,我们就完成了我们前3个思路的工作。关于命令帮助的问题,在Python中存在1个docstring的东西,我们可以直接使用该类每个方法的__doc__属性来实现对其文档的获取。而在cmd中提供了help_*的方法来实现对某个命令帮助文档的调用。
而在pymemcache库中这方面已经帮助我们做好了,因此我们可以直接使用,我们只需要在之前的get_action方法中添加这么几行代码:

doc = (getattr(attr, '__doc__', '') or '').strip()
if doc:
    setattr(self.__class__, 'help_' + name, self._make_help(doc))

我们将其进行判断得到的对应文档是否为空字符串,如果不是才设置其对应的方法。我将帮助文档的内容封装在了1个_make_help的方法中:

    def _make_help(self, doc):
        def help(self):
            print(doc)
        return help

在这里,我们直接输出文档的内容即可。

总结

最后,我们来看实际的效果,首先是help列出所有可用的命令:

127.0.0.1:11211>help

Documented commands (type help <topic>):
========================================
EOF     check_key  delete_many   get        gets_many  quit      set_multi
add     close      delete_multi  get_many   help       replace   stats    
append  decr       exit          get_multi  incr       set       touch    
cas     delete     flush_all     gets       prepend    set_many  version 

然后是获取某个指令的说明:

127.0.0.1:11211>help get

The memcached "get" command, but only for one key, as a convenience.

        Args:
          key: str, see class docs for details.

        Returns:
          The value for the key, or None if the key wasn't found.

最后是获取和设置对应的键值:

127.0.0.1:11211>get name
None
127.0.0.1:11211>set name zhangsan
True
127.0.0.1:11211>get name
zhangsan

由于时间的限制,有一些细节的功能就不一一实现了。最后,可以通过如下的方式安装使用:

pip install memcached-cli

参考文章:

https://pymemcache.readthedocs.io/en/latest/apidoc/modules.html
https://tghw.com/blog/cheeky-python-a-redis-cli
https://docs.python.org/2/library/cmd.html#module-cmd


曾经的自己
记录开发中的点点滴滴,共同交流技术。

是1个执着、低调的人

1.9k 声望
161 粉丝
0 条评论
推荐阅读
C-如何快速生成Python的C扩展.md
真的好久没有分享技术文章了,主要是因为自己写的文章太过于小众,没想到自己竟然会分享这样一篇文章。这么一篇浓缩了自己多年来项目开发的实战经验。 不得不说,Python是一门很不错的编程语言。有时候,为了项目代码...

我勒个去阅读 1.9k

封面图
滚蛋吧,正则表达式!
你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」,首先想到的就是直接百度上搜索一个,然后采用 CV 大法神奇地接入到你的代码中?

良许4阅读 2.3k

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许5阅读 1.8k

FastAPI性能碾压Flask?
不止一次的听过,FastAPI性能碾压Flask,直追Golang,不过一直没有测试过,今天闲着没事测试一下看看结果。不知道是哪里出了问题,结果大跌眼镜。

二毛erma02阅读 10.2k评论 3

封面图
Python之如何优雅的重试
为了避免偶尔的网络连接失败,需要加上重试机制,那么最简单的形式就是在对应的代码片段加一个循环,循环体里使用异常捕获,连接成功时退出循环,否则就重复执行相关逻辑,此时修改之后的函数f如下

Harpsichord12073阅读 7.3k

Linux终端居然也可以做文件浏览器?
大家好,我是良许。在抖音上做直播已经整整 5 个月了,我很自豪我一路坚持到了现在【笑脸】最近我在做直播的时候,也开始学习鱼皮大佬,直播写代码。当然我不懂 Java 后端,因此就写写自己擅长的 Shell 脚本。但...

良许1阅读 2.1k

基于 EKS Fargate 搭建微服务性能分析系统
近期 Amazon Fargate 在中国区正式落地,因 Fargate 使用 Serverless 架构,更加适合对性能要求不敏感的服务使用,Pyroscope 是一款基于 Golang 开发的应用程序性能分析工具,Pyroscope 的服务端为无状态服务且性...

亚马逊云开发者阅读 7.8k

是1个执着、低调的人

1.9k 声望
161 粉丝
宣传栏