`
windytwang
  • 浏览: 50004 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

数据库快速统一地布署memcached

阅读更多

memcached是一款优秀的分布式key-value缓存框架。使用,集成简单方便。

但是对于如何优化数据库查询中,具体的使用方法网上相关的资源非常少。这里我给出自己摸索的经验方法与大家共享一下。

 

这里主要讲 如何给所有的表添加一种统一的使用缓存的办法,以及使用缓存使用中会为出现的问题,及统一的解决策略。

(以下是代码是基于python+Django,但其原理不限于此,但python语言的灵活强大在这里也可以得到很好的体现,试想要是在C++上实现这一切,就不是那么简单的一些事情)

 

首先我们假设创建了一个表User, id,name,password等荐,其中idprimary key,namekey.

 

1,基本的memcached使用方法

那么最简单的使用memcached的方法即为

cache.get('user_id_123')

cache.set('user_id_123', user )

 

2,何种查询使用memcached

这里只针对返回单条数据的查询做memcached,如果是返回集合的做cache,对于何时失效,如何失效的策略非常复杂,其开销也非常大,所以目前还没寻找到一个好的方法。所以这里只针对特定key的查询进行cache.

比如user这张表,可以定义按id进行查询,也可以按name进行查询。 又如有Item这张表,其构成为id,character_id,type,number. idprimary key, (character_id,type)key.那么可以按id进行查询,也可以按(character_id,type)进行查询。像(character_id,type)这样的,我们可以称做侯选key。这种查询更接近于我们数据库的使用情况,做缓存的意义更大一些,也更加复杂,我们主要就是对其进行讨论。

后面我再详细说如何以统一的接口来定义侯选key.

 

3,如何为查询生成key

 

def makeKey( classA,*args,**kwargs):

    endword = ""

    keys = kwargs.keys()

    keys.sort()

    for key in keys:

        endword += "_"+key

        endword += "%s"%(kwargs[key])

    return classA.__name__+endword

 

 

MakeKey( User,name='123')返回 User_name_123

MakeKey( Item,character_id=1,type=3)( Item,type=3,character_id=1)都返回Item_character_id_1_type_3

 

4,统一的get方法

 

def getFromDBWithId( className,*args, **kwargs ):

    key = makeKey(className,*args, **kwargs )

    value = cache.get(key )

    if( not value ):

        try:

            value = className.objects.get(*args,**kwargs )

        except:

            value = None

        cache.set( key, value )

    return value

 

 

5,何时添加缓存

get不命中时

insert

 

6,何时缓存失效

在任何对缓存进行操作时,缓存失效:delete, update

 

7,update的注意事项

首先这里会有一个前提假设,即数据库的主健id不会发生变化。

即不能user = User(id=123)

user.id = 124

user.save()

一般我们在使用数据库时,都不会修改主健ID。对于侯选健我们一般也不会这样做。

如果侯选健不发生变化,那么update时的操作非常简单:cache.set(key,newValue),重新赋值即可。

如果侯选健发生变化,那上面的办法就行不通了。

试想user = getFromDBWithId(User,name='xxx')

user.name ='ddd'

user.save()

如果用上面的方法,在查询 getFromDBWithId(User,name='xxx')时还会返回数据,而不是返回空

但即侯选健发生了变化,但主健id不会发生变化,我们也可以通过以下的方法来解决上面这个问题:

数据内容仅由idkey的来进行存储,侯选key存储的是与id的一个双向索引。

比如user = User( name='xxx'),其实在内存中有三部分构成:

1,cache.set('User_id_111',user)

2,cache.set('User_id_to_name_111','xxx')

3,cache.set('User_name_to_id_xxx',111)

这样我们再来看上面的情况

 

user = getFromDBWithId(User,name='xxx')

user.name ='ddd'

user.save()

 

save时,我们这样处理,就不会有问题了:

1,oldName = cache.get('User_id_to_name_%s'%user.id) # (oldName='xxx')

2,cache.invalide('User_name_to_id_%s'%oldName)

3,cache.set('User_id_to_name_%s'user.id,user.name)

4,cache.set('User_name_to_id_%s'%user.name,user.id)

5,cache.set('User_id_%s'%user.id,user)

 

8,统一加入缓存

上面我们给出了在User表上加入缓存的方法,当然我们不希望每个表都需要这样写一遍,高举DRY( do not reapeat yourself)的程序员肯定能想到更高明的办法。

切面编程?如果只需要写一个save()函数,然后注册的类,在更新表项时,都会被其改写,你只需要在外面配置哪些表需要加入cache,而完全不用从内部对其进行任务改写,这样会不会听上去很酷?

python切面编程的库有很多,我这里使用的aspects

1,这里我们需要每个表下定义一个CACHE_KEY(即我们的侯选key)

class User( models.Model ):

    name        = models.CharField( max_length=20 )

    password    = models.CharField( max_length=32 )

 

    CACHE_KEY = ["name"]

我们来定义我们想重用的delete

 

def newDelete(self):

    className = self.__class__.__name__

    key = className

    for subKey in self.__class__.CACHE_KEY :

       key += "_%s_"%subKey+str(getattr( self,subKey ))

    cache.delete(key)                                 #选移除缓存,再从数据库中移出

    retval = yield aspects.proceed(self)

    yield aspects.return_stop(retval)

 

定义新的Save(如果侯选key内容会变,可以参照第7点进行改动)

 

def newSave(self):

    etval = yield aspects.proceed(self)            #数据库执行update

    className = self.__class__.__name__

    key = className

    for subKey in self.__class__.CACHE_KEY :

       key += "_%s_"%subKey+str(getattr( self,subKey ))

    cache.set(key,self)

    yield aspects.return_stop(retval)

搜索所有的表项,添加上hook

import aspects

 

allClasses = dir()

def registerHook():

    global allClasses

    for elem in allClasses:

       if( globals().has_key(elem) ):

            if( "<class 'django.db.models.base.ModelBase'>" == str(type(globals()[elem]))):

                aspects.with_wrap(newSave, getattr(globals()[elem],"save"))

                aspects.with_wrap(newDelete, getattr(globals()[elem],"delete"))

 (当然你也可以手动定义好,给固定的类加上cache )

 

9,加起来不到100行代码,你已经给你所有的数据库表项添加上了缓存。现在你可以去敲你老板的门了:我刚给数据库讲了一个笑话,它现在冷静了很多

 


 

2
0
分享到:
评论
2 楼 simomo 2010-12-31  
感觉你说的aop和装饰器作用有点像~
1 楼 simomo 2010-12-06  
 
很受启发,受益匪浅~!

相关推荐

Global site tag (gtag.js) - Google Analytics