上一篇文章: Python--Redis实战:第五章:使用Redis构建支持程序:第3节:查找IP所属城市以及国家
随着咱们愈来愈多地使用Redis以及其余服务,如何存储各项服务的配置信息将变成一个棘手的问题:对于一个Redis服务器、一个数据库服务器以及一个Web服务器来讲,存储它们的配置信息并不困难;但若是咱们使用了一个拥有好几个从服务器的Redis主服务器,或者为不一样的应用程序设置了不一样的Redis服务器,甚至为数据库也设置了主服务器和从服务器的话,那么存储这些服务器的配置信息将变成一件让人头痛的事情。web
用于链接其余服务器以及服务器的配置信息通常都是以配置文件的形式存储在硬盘里面,每当机器下线、网络链接断开或者某些须要链接其余服务器的状况出现时,程序一般须要一次性地对不一样服务器中的多个配置文件进行更新。而这一节要介绍的就是如何将大部分配置信息从文件转移到Redis里面,使得应用程序能够本身完成绝大部分配置工做。redis
为了展现配置管理方面的难题是多么的常见,来看一个很是简单的配置例子:假设如今咱们要用一个标志flag来表示web服务器是否正在进行维护,若是服务器正在进行维护,那么它就不该该发送数据库请求,而是应该向访问们返回一条简短的【抱歉,咱们正在进行维护,请稍后重试】的信息;相反,若是服务器并无进行维护,那么它就应该按照既定的程序来运行。数据库
在一般状况下,即便只更新配置中的一个标志,也会致使更新后的配置文件被强制推送至全部Web服务器,收到更新的服务器可能须要从新载入配置、甚至可能还要重启应用程序服务器。json
与其尝试为不断增多的服务写入和维护配置文件,不如让咱们直接配置写入Redis里面。只要将配置信息存储在Redis里面,并编写应用程序来获取这些信息,咱们就不用再编写工具来向服务器推送配置信息了,服务器和程序也不用再经过载入配置文件的方式 来更新配置信息了。segmentfault
为了实现这个简单的功能,让咱们假设本身已经构建了一个中间层或者插件,这个中间层额做用在于:当is_under_maintenance()函数返回True时,它将向用户显示维护页面;与此相反,如何is_under_maintenance()函数返回False,它将如常地处理用户的访问请求。其中is_under_maintenance()函数经过检查一个名为is-under-maintenance的键来判断服务器是否正在进行维护:若是is-under-maintenance键非空,那么函数返回True;不然返回False,另外,由于访客在看见维护页面的时候一般都会不耐烦的频繁刷新页面,因此为了尽可能下降Redis在处理高访问量Web服务器时的负载,is_under_maintenance()函数最多只会每秒更新一次服务器维护信息。缓存
下面代码展现了is_under_maintenance()函数的具体定义:安全
import time LAST_CHECKED=None IS_UNDER_MAINTENANCE=False def is_under_maintenance(conn): #将连个变量设置为全局变量以便在以后对它们进行写入 global LAST_CHECKED,IS_UNDER_MAINTENANCE #距离上次检查是否以及超过1秒? if LAST_CHECKED<time.time()-1: #更新最后检查时间 LAST_CHECKED=time.time() #检查系统是否正在进行维护 IS_UNDER_MAINTENANCE=bool(conn.get('is-under-maintenance')) #返回一个布尔值,用于表示系统是否正在进行维护。 return IS_UNDER_MAINTENANCE
经过将is_under_maintenance()函数插入应用程序的正确位置上,咱们能够在1秒内改变数以千计Web服务器的行为。为了下降Redis在处理高访问量web服务器时的负载,is_under_maintenance()函数将服务器维护状态信息的更新频率限制为最多每秒1次,但若是有须要的话,咱们也能够加快信息的更新频率,甚至直接移除函数里面限制更新速度的那些代码。虽然is_under_maintenance()函数看上去彷佛并不实用,但它的确展现了将配置信息存储在一个普通可访问位置的威力。服务器
接下来咱们要考虑的是,怎样才能将更复杂的配置选项存储到Redis里面呢?cookie
在咱们愈来愈多地使用Redis的过程当中,无数的开发者已经发现,最终在某个时间点上,只使用一台Redis服务器将不能知足咱们的须要。由于咱们可能须要记录更多信息,可能须要更多用于缓存的空间,还可能会使用本书以后的章节会介绍的、使用Redis构建的高级服务。但无论何种缘由,咱们都须要用到更多Redis服务器。网络
为了平滑地从单台服务器过渡到多台服务器,用户最好仍是为应用程序中的每一个独立部分都分别运行一个Redis服务器,好比说,一个专门负责记录日志、一个专门负责记录统计数据、一个专门负责进行缓存、一个专门负责存储cookies等。别忘了,一台机器是能够运行多个Redis服务器的,只要这些服务器使用的端口号各不一样就能够了。除此以外,在一个Redis服务器里面使用多个【数据库】,也能够减小系统管理的工做量。以上提到的两种方法,都是经过将不一样数据划分至不一样键空间的方式,来或多或少的简化迁移至更大或更多服务器时所需的工做。但遗憾的是,随着Redis服务器的数量或者Redis数据库的数量不断增多,为全部Redis服务器管理和分发配置信息的工做将变得愈来愈烦琐和无趣。
在上一节中,咱们用了Redis来存储表示服务器是否正在进行维护的标志,并经过这个标志来决定是否须要向访客显示维护页面。而这一次,咱们一样可使用Redis来存储与其余Redis服务器有关的信息。说的更详细一点,咱们能够把一个已知的Redis服务器用做配置信息字典,而后经过这个字典存储的配置信息来链接为不一样应用或服务组件提供数据的其余Redis服务器。此外,这个字典还会在配置出现变动时,帮助客户端链接至正确的服务器。字典的具体实现比这个例子所要求的更为通用一些,由于我敢确定,当你开始使用这个字典来获取配置信息的时候,你很快就会把它应用到其余服务器以及其余服务上面,而不只仅用于获取Redis服务器的配置信息。
咱们将构建一个函数,该函数能够从一个键里面取出一个JSON编码的配置值,其中,存储配置值的键由服务的类型以及使用该服务的应用程序命名。举个例子,如何咱们想要获取链接存储统计数据的Redis服务器所需的信息,那么就须要获取config:redis:statistics键的值。下面函数展现了设置配置值的具体方法:
def set_config(conn,type,component,config): conn.set('config:%s:%s'%(type,component)) json.dumps(config)
经过这个函数,咱们能够为所欲为的设置任何JSON编码的配置信息。由于get_config()函数和前面介绍过的is_under__maintenance()函数具备类似的结构,因此咱们只要在语义上稍做修改,就可使用get_config()函数来替代is__under_maintenance()函数。下面代码列出了与set_config()相对应的get_config()函数,这个函数能够按照用户的须要,对配置信息进行0秒、1秒或者10秒的局部缓存。
import json import time CONFIGS={} CHECKED={} def get_config(conn,type,component,wait=1): key='config:%s:%s'%(type,component) #检查是否须要对这个组件的信息进行更新 if CHECKED.get(key)<time.time()-wait: #有须要对配置进行更新,记录最后一次检查这个链接的时间 CHECKED[key]=time.time() #取得Redis存储的组件配置 config=json.loads(conn.get(key) or '{}') #将潜在的Unicode关键字参数转换为字符串的关键字参数 config=dict((str(k),config[k]) for k in config) #取得组件正在使用的配置 old_cofig=CONFIGS.get(key) #若是两个配置并不相同 if config!=old_cofig: #那么对组件的配置进行更新 CONFIGS[key]=config return CONFIGS.get(key)
在拥有了配置信息和获取配置信息的两个函数以后,咱们还能够在此之上更近一步。咱们在前面一直考虑的都是怎样存储和获取配置信息以便链接各个不一样的Redis服务器,但直到目前为止,咱们编写的绝大多数函数和第一个参数都是一个链接参数。所以,为了避免再须要手动获取咱们正在使用的各项服务的链接,下面让咱们来构建一个可以帮助咱们自动链接这些服务的方法。
手动建立和传递Redis链接并非一件容易地事情,这不只是由于咱们须要重复查阅配置信息,还有一个缘由就是,即便使用了 上一节介绍的配置管理函数,咱们仍是须要获取配置、链接Redis,并在使用完链接以后关闭链接。为了简化链接的管理操做,咱们将编写一个装饰器,让它负责链接除配置服务器以外的全部其余Redis服务器。
装饰器Python提供了一种语法,用于将函数X传入另外一个函数Y的内部,其中函数Y就被成为装饰器。装饰器给用户提供了一个修改函数X行为的机会。有些装饰器能够用于校验参数,而有些装饰器则能够用于注册回调函数,甚至还有一些装饰器能够用于管理链接:就像咱们接下来要作的那样。
下面代码展现了咱们定义的装饰器,它接受一个指定的配置做为参数并生成一个包装器,这个包装器能够包裹一个函数,使得以后对被包裹函数的调用能够自动链接至正确的Redis服务器,而且链接Redis服务器所使用的那个链接会和用户以后提供的其余参数一同传递至包裹的函数:
REDIS_CONNECTIONS={} #将应用组件的名字传递给装饰器 def redis_connection(component,wait=1): #由于函数每次被调用都须要获取这个配置键,因此咱们干脆把它缓存起来 key='config:redis:'+component #包装器接受一个函数做为参数,并使用另外一个函数来包裹这个函数。 def wrapper(function): #将被包裹函数的一些有用的元数据复制给配置处理器。 @function.wraps(function) def call(*args,**kwargs):#建立负责管理链接信息的函数 #若是有就配置存在,那么获取它 old_config=CONFIGS.get(key,object()) #若是有新配置存在,那么获取它 _config=get_config(config_connection,'redis',component,wait) config={} #对配置进行处理并将其用于建立Redis链接 for k,v in _config.iteritems(): config[k.encode('utf-8')]=v #若是新旧配置并不相同,那么建立新的链接 if config!=old_config: REDIS_CONNECTIONS[key]=redis.Redis(**config) #将Redis链接以及其余匹配的参数传递给包裹函数,而后调用该函数并返回它的执行结果。 return function(REDIS_CONNECTIONS.get(key),*args,**kwargs) #返回被包裹的函数 return call #返回用于包裹Redis函数的包装器 return wrapper
同时使用*args和**kwargs在Python中,函数定义的args变量用于获取全部位置参数,而kwargs变量则用于获取全部命令出纳和素,这两种参数传递方式均可以将给定的参数传入被调用的函数里面。
上面战术的一系列嵌套函数初看上去可能会让人感动头昏目眩,但它们实际上并无想象中的那么复杂。redis_connection()装饰器接受一个应用组件的名字做为参数并返回一个包装器。这个包装器接受一个咱们想要将链接传递给它的函数为参数,而后对函数进行包裹并返回被包裹函数的调研器。这个调用器负责处理全部获取配置信息的工做,除此以外,它还负责链接Redis服务器并调用被包裹的函数。尽管redis_connecition()函数描述起来至关复杂,但实际使用起来倒是很是方便的,下面代码就展现了怎样将redis_connection()函数应用到之间介绍的log_recent()函数上面。
@redis_connection('logs') def log_recent(conn,app,message,severity=logging.INFO,pipe=None): # 尝试将日志的安全级别准还为简单的字符串 severity = str(SEVERITY.get(severity, severity)).lower() # 建立负责存储消息的键 destination = 'recent:%s:%s' % (name, severity) # 将当前时间添加到消息里面,用于记录消息的发送时间 message = time.asctime() + ' ' + message # 使用流水线来将通讯往返次数下降为一次 pipe = pipe or conn.pipeline() # 将消息添加到日志列表的最前面 pipe.lpush(destination, message) # 对日志列表进行修建,让它只包含最新的100条消息 pipe.ltrim(destination, 0, 99) # 执行两个命令 pipe.execute() log_recent('main','User 235 logged in')
如今你已经看到怎样使用redis_connection()来装饰log_recent()函数,这个装饰器仍是蛮有用的,不是吗?经过使用这个改良后的方法来处理连接和配置,咱们几乎能够把咱们要调用的全部函数的代码都删去好几行。
做为练习,请尝试使用redis_connection()去装饰以前介绍的access_time()上下文管理器,使得这个上下文管理器能够在没必要手动传递Redis服务器链接的状况下执行。
本章介绍的全部主题都直接或间接地用于对应用程序进行帮助和支持,这里展现的函数和装饰器都旨在帮助读者学会如何使用Redis来支撑应用程序的不用部分:日志、计数器以及统计数据能够帮助用户直观地了解应用程序的性能,而IP所属地查找程序则能够告诉你客户所在的地点。除此以外,存储服务的发现和配置信息能够帮助咱们减小大量须要手动处理链接的工做。
如今咱们已经知道了怎样使用Redis来对应用程序进行支持了,在接下来的第6章,咱们将学习如何使用Redis来构建应用程序组件。
上一篇文章: Python--Redis实战:第五章:使用Redis构建支持程序:第3节:查找IP所属城市以及国家