热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python中memcached的操作详解(图文)

通过缓存数据库查询结果,减少数据库访问次数,可以显著提高动态Web应用的速度和可扩展性。业界常用的有memcached,redis等,今天要讲的就是在python项目中如何使用memcached缓存服务。


前言

许多Web应用都将数据保存到MySQL这样的关系型数据库管理系统中,应用服务器从中读取数据并在浏览器中显示。 但随着数据量的增大、访问的集中,就会出现数据库的负担加重、数据库响应恶化、 网站显示延迟等不良影响。分布式缓存是优化网站性能的重要手段,大量站点都通过可伸缩的服务器集群提供大规模热点数据缓存服务。通过缓存数据库查询结果,减少数据库访问次数,可以显著提高动态Web应用的速度和可扩展性。业界常用的有redis、memcached等,今天要讲的就是在python项目中如何使用memcached缓存服务。

memcached简介

memcached是一款开源、高性能、分布式内存对象缓存系统,可应用各种需要缓存的场景,其主要目的是通过降低对Database的访问来加速web应用程序。
memcached本身其实不提供分布式解决方案。在服务端,memcached集群环境实际就是一个个memcached服务器的堆积,环境搭建较为简单;cache的分布式主要是在客户端实现,通过客户端的路由处理来达到分布式解决方案的目的。客户端做路由的原理非常简单,应用服务器在每次存取某key的value时,通过路由算法把key映射到某台memcached服务器nodeA上,因此这个key所有操作都在nodeA上进行。只要服务器还缓存着该数据,就能保证缓存命中。
但是,当memcached集群要扩容的时候,就会引发问题。例如:网站需要将3台缓存服务器扩容成4台。在更改服务器列表后,若仍使用余数hash,很容易就计算出,75%的请求不能命中缓存。随着服务器集群规模增大,不能命中的比率就越高。

1%3 = 1    
1%4 = 1
2%3 = 2    
2%4 = 2
3%3 = 0    
3%4 = 3
4%4 = 1    
4%4 = 0
#以此类推

这样扩容操作风险极大,可能给数据库带来很大的瞬时压力,甚至可能导致数据库崩溃。解决这个问题有2个方法:1、在访问低谷进行扩容,在扩容后预热数据;2、使用更优的路由算法。目前使用较多的是一致性Hash算法。

一致性哈希

memcached客户端可采用一致性hash算法作为路由策略,如图,相对于一般hash(如简单取模)的算法,一致性hash算法除了计算key的hash值外,还会计算每个server对应的hash值,然后将这些hash值映射到一个有限的值域上(比如0~2^32)。通过寻找hash值大于hash(key)的最小server作为存储该key数据的目标server。如果找不到,则直接把具有最小hash值的server作为目标server。同时,一定程度上,解决了扩容问题,增加或删除单个节点,对于整个集群来说,不会有大的影响。

memcached内存管理采取预分配、分组管理的方式,分组管理就是我们上面提到的slab class,按照chunk的大小slab被分为很多种类。内存预分配过程是怎样的呢?向memcached添加一个item时候,memcached首先会根据item的大小,来选择最合适的slab class:例如item的大小为190字节,默认情况下class 4的chunk大小为160字节显然不合适,class 5的chunk大小为200字节,大于190字节,因此该item将放在class 5中(显然这里会有10字节的浪费是不可避免的),计算好所要放入的chunk之后,memcached会去检查该类大小的chunk还有没有空闲的,如果没有,将会申请1M(1个slab)的空间并划分为该种类chunk。例如我们第一次向memcached中放入一个190字节的item时,memcached会产生一个slab class 2(也叫一个page),并会用去一个chunk,剩余5241个chunk供下次有适合大小item时使用,当我们用完这所有的5242个chunk之后,下次再有一个在160~200字节之间的item添加进来时,memcached会再次产生一个class 5的slab(这样就存在了2个pages)。

注意事项

  • chunk是在page里面划分的,而page固定为1m,所以chunk最大不能超过1m。

  • chunk实际占用内存要加48B,因为chunk数据结构本身需要占用48B。

  • 如果用户数据大于1m,则memcached会将其切割,放到多个chunk内。

  • 已分配出去的page不能回收。

  • -对于key-value信息,最好不要超过1m的大小;同时信息长度最好相对是比较均衡稳定的,这样能够保障最大限度的使用内存;同时,memcached采用的LRU清理策略,合理甚至过期时间,提高命中率。

使用场景

key-value能满足需求的前提下,使用memcached分布式集群是较好的选择,搭建与操作使用都比较简单;分布式集群在单点故障时,只影响小部分数据异常,目前还可以通过Magent缓存代理模式,做单点备份,提升高可用;整个缓存都是基于内存的,因此响应时间是很快,不需要额外的序列化、反序列化的程序,但同时由于基于内存,数据没有持久化,集群故障重启数据无法恢复。高版本的memcached已经支持CAS模式的原子操作,可以低成本的解决并发控制问题。

安装启动

$ sudo apt-get install memcached
$ memcached -m 32 -p 11211 -d
# memcached将会以守护程序的形式启动 memcached(-d),为其分配32M内存(-m 32),并指定监听 localhost的11211端口。
python操作memcached

在python中可通过memcache库来操作memcached,这个库使用很简单,声明一个client就可以读写memcached缓存了。

python访问memcached

#!/usr/bin/env pythonimport memcache

mc = memcache.Client(['127.0.0.1:12000'],debug=0)

mc.set("some_key", "Some value")
value = mc.get("some_key")

mc.set("another_key", 3)
mc.delete("another_key")

mc.set("key", "1")   # note that the key used for incr/decr must be a string.
mc.incr("key")
mc.decr("key")

然而,python-memcached默认的路由策略没有使用一致性哈希。

    def _get_server(self, key):
        if isinstance(key, tuple):
            serverhash, key = key        
            else:
            serverhash = serverHashFunction(key)        
            if not self.buckets:            
            return None, None

        for i in range(Client._SERVER_RETRIES):
            server = self.buckets[serverhash % len(self.buckets)]            
            if server.connect():                
            # print("(using server %s)" % server,)
                return server, key
            serverhash = serverHashFunction(str(serverhash) + str(i))        
            return None, None

从源码中可以看到:server = self.buckets[serverhash % len(self.buckets)],只是根据key进行了简单的取模。我们可以通过重写_get_server方法,让python-memcached支持一致性哈希。

import memcacheimport typesfrom hash_ring import HashRingclass MemcacheRing(memcache.Client):
    """Extends python-memcache so it uses consistent hashing to
    distribute the keys.
    """
    def init(self, servers, *k, **kw):
        self.hash_ring = HashRing(servers)
        memcache.Client.init(self, servers, *k, **kw)
        self.server_mapping = {}        
        for server_uri, server_obj in zip(servers, self.servers):
            self.server_mapping[server_uri] = server_obj    
            def _get_server(self, key):
        if type(key) == types.TupleType:            
        return memcache.Client._get_server(key)        
        for i in range(self._SERVER_RETRIES):
            iterator = self.hash_ring.iterate_nodes(key)            
            for server_uri in iterator:
                server_obj = self.server_mapping[server_uri]                
                if server_obj.connect():                    
                return server_obj, key        
                return None, None

torando项目中使用memcached

这里采用的策略是:1. 应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。2. 应用程序从cache中取数据,取到后返回。缓存更新是一个很复杂的问题,一般是先把数据存到数据库中,成功后,再让缓存失效。后面会再写文单独讨论memcached缓存更新的问题。

代码

# coding: utf-8import sysimport tornado.ioloopimport tornado.webimport loggingimport memcacheimport jsonimport urllib# 初始化memcache clientmc = memcache.Client(['127.0.0.1:11211'], debug=0)
mc_prefix = 'demo'class BaseHandler(tornado.web.RequestHandler):
    """ 把缓存处理抽象到BaseHandler基类 """
    USE_CACHE = False  # 控制是否使用缓存

    def format_args(self):
        arg_list = []        
        for a in self.request.arguments:            
        for value in self.request.arguments[a]:
                arg_list.append('%s=%s' % (a, urllib.quote(value.replace(' ', ''))))        
                # 根据请求的URL产生key
        arg_list.sort()
        key = '%s?%s' % (self.request.path, '&'.join(arg_list)) if arg_list else self.request.path
        key = '%s_%s' % (mc_prefix, key)        
        # key太长,不进行缓存处理
        if len(key) > 250:
            logging.error('key out of length: %s', key)            
            return None

        return key    def get(self, *args, **kwargs):
        if self.USE_CACHE:            
        try:                
        # 根据请求获取key
                self.key = self.format_args()                
                if self.key:
                    data = mc.get(self.key)  
                    # 若缓存命中,则直接返回数据
                    if data:
                        logging.info('get data from memecahce')
                        self.finish(data)                        
                        return
            except Exception, e:
                logging.exception(e)        
                # 若未命中缓存,调用do_get处理请求,获取数据
        data = self.do_get()
        data_str = json.dumps(data)        
        # 把成功获取到的数据,放入memcache缓存
        if self.USE_CACHE and data and data.get('result', -1) == 0 and self.key:            
        try:
                mc.set(self.key, data_str, 60)            
                except Exception, e:
                logging.exception(e)

        self.finish(data_str)    def do_get(self):
        return Noneclass DemoHandler(BaseHandler):
    USE_CACHE = True

    def do_get(self):
        a = self.get_argument('a', 'test')
        b = self.get_argument('b', 'test')        
        # 访问数据库获取数据,此处略去
        data = {'result': 0, 'a': a, 'b': b}        return datadef make_app():
    return tornado.web.Application([
        (r"/", DemoHandler),
    ])if name == "main":
    logging.basicConfig(stream=sys.stdout, level=logging.INFO,
                    format='%(asctime)s %(levelno)s %(message)s',
                    )

    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

测试结果

在浏览器访问http://127.0.0.1:8888/?a=1&b=3,终端打印的log如下:

2017-02-21 22:45:05,987 20 304 GET /?a=1&b=2 (127.0.0.1) 3.11ms
2017-02-21 22:45:07,427 20 get data from memecahce
2017-02-21 22:45:07,427 20 304 GET /?a=1&b=2 (127.0.0.1) 0.71ms
2017-02-21 22:45:10,350 20 200 GET /?a=1&b=3 (127.0.0.1) 0.82ms
2017-02-21 22:45:13,586 20 get data from memecahce

从日志可以看到,缓存命中的情况。

小结

本文介绍了memcached的路由算法、内存管理、使用场景等基本概念,然后举例说明了在python项目中如何使用memcached缓存。缓存更新的问题还需要进一步分析讨论。

以上就是Python中memcached的操作详解(图文)的详细内容,更多请关注 第一PHP社区 其它相关文章!


推荐阅读
  • 成为一名高效的Java架构师不仅需要掌握高级Java编程技巧,还需深入理解JVM的工作原理及其优化方法。此外,对池技术(包括对象池、连接池和线程池)的应用、多线程处理、集合对象的内部机制、以及常用的数据结构和算法的精通也是必不可少的。同时,熟悉Linux操作系统、TCP/IP协议栈、HTTP协议等基础知识,对于构建高效稳定的系统同样重要。 ... [详细]
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • 本文介绍了数据库体系的基础知识,涵盖关系型数据库(如MySQL)和非关系型数据库(如MongoDB)的基本操作及高级功能。通过三个阶段的学习路径——基础、优化和部署,帮助读者全面掌握数据库的使用和管理。 ... [详细]
  • 远程过程调用(RPC)是一种允许客户端通过网络请求服务器执行特定功能的技术。它简化了分布式系统的交互,使开发者可以像调用本地函数一样调用远程服务,并获得返回结果。本文将深入探讨RPC的工作原理、发展历程及其在现代技术中的应用。 ... [详细]
  • 前言无论是对于刚入行工作还是已经工作几年的java开发者来说,面试求职始终是你需要直面的一件事情。首先梳理自己的知识体系,针对性准备,会有事半功倍的效果。我们往往会把重点放在技术上 ... [详细]
  • MySQL缓存机制深度解析
    本文详细探讨了MySQL的缓存机制,包括主从复制、读写分离以及缓存同步策略等内容。通过理解这些概念和技术,读者可以更好地优化数据库性能。 ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
  • 字节跳动夏季招聘面试经验分享
    本文详细记录了字节跳动夏季招聘的面试经历,涵盖了一、二、三轮面试的技术问题及项目讨论,旨在为准备类似面试的求职者提供参考。 ... [详细]
  • 在分布式系统中,当多个服务器共同提供服务时,如何高效地将请求路由到正确的服务器是一个关键问题。传统的方法如简单哈希取模在服务器数量变化时会导致大量数据迁移。本文探讨了一致性哈希算法如何有效解决这一问题,确保系统的稳定性和高效性。 ... [详细]
  • 本文详细介绍了在 Windows 7 上安装和配置 PHP 5.4 的 Memcached 分布式缓存系统的方法,旨在减少数据库的频繁访问,提高应用程序的响应速度。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
  • 本文探讨了MariaDB在当前数据库市场中的地位和挑战,分析其可能面临的困境,并提出了对未来发展的几点看法。 ... [详细]
  • 本文探讨了如何在日常工作中通过优化效率和深入研究核心技术,将技术和知识转化为实际收益。文章结合个人经验,分享了提高工作效率、掌握高价值技能以及选择合适工作环境的方法,帮助读者更好地实现技术变现。 ... [详细]
  • 深入理解一致性哈希算法及其应用
    本文详细介绍了分布式系统中的一致性哈希算法,探讨其原理、优势及应用场景,帮助读者全面掌握这一关键技术。 ... [详细]
  • Spring Cloud学习指南:深入理解微服务架构
    本文介绍了微服务架构的基本概念及其在Spring Cloud中的实现。讨论了微服务架构的主要优势,如简化开发和维护、快速启动、灵活的技术栈选择以及按需扩展的能力。同时,也探讨了微服务架构面临的挑战,包括较高的运维要求、分布式系统的复杂性、接口调整的成本等问题。最后,文章提出了实施微服务时应遵循的设计原则。 ... [详细]
author-avatar
无心少年丶的诱惑
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有