Redis是最流行的NoSQL数据库之一。目前广泛应用于缓存系统、分布式锁、计数器、消息队列系统、排行榜、社交网络等场景。在这篇文章中程哥将给大家带来redis的日常使用实践,通过代码实现redis的分布式锁。01Redis简介Redis是一个用ANSIC语言编写的开源,遵守BSD协议,支持网络,可以是基于内存或持久化的日志类型,key-value数据库,提供多种语言的API。Redis的出现很大程度上弥补了Memcache等key/value存储的不足。在某些场合下,关系型数据库(MySql、DB2等,关系型数据库可以通过外键关联建立表与表之间的关系。非关系型数据库通常是指数据以对象的形式存储在数据库中,表与表之间的关系对象是由每个对象本身的属性决定的)起到很好的补充作用。(比如可以减轻数据库访问的压力,对冷数据的处理不利)。02Redis实现特点(1)单线程Redis通过单线程实现,避免了多线程切换带来的性能损失。同时它所有的数据都在内存中,所有的操作都是内存级别的操作,所以即使是单线程的redis也可以这么快。但也正是因为是单线程使用,所以一定要谨慎使用Redis指令。对于那些耗时的指令(比如keys),一定要慎用。一不小心,Redis可能会卡死。(2)IO多路复用Redis通过IO多路复用解决单线程下客户端并发访问,redis使用epoll实现IO多路复用,将连接信息和事件放入队列中,依次放入文件事件Dispatcher中,事件调度器调度事件到事件处理程序。具体架构如下:03Redis集群方案比较(1)redis3.0之前版本的哨兵模式实现集群,一般使用哨兵工具来监控master节点的状态。如果master节点出现异常,会从master切换到slave,将某一个slave作为master,sentinel的配置略显复杂,性能和高可用一般,尤其是当master-slave切换发生在访问中断的瞬间,只有一个master节点以sentinel模式对外提供服务,没有不能支持高并发,单个master节点的内存不要设置太大,否则持久化文件会过大,影响数据恢复或主从同步的效率。(2)高可用集群模式Redis高可用集群是由多个主从节点组组成的分布式服务器组,具有复制、高可用、分片等特点。Redis集群可以在没有哨兵哨兵的情况下完成节点移除和故障转移功能。每个节点都需要设置为集群模式。这种集群模式没有中心节点,可以水平扩展。根据官方文档,可以线性扩展到数万个节点(官方建议不超过1000个节点)。redis集群的性能和高可用都比之前版本的sentinel模式要好,而且集群配置非常简单。04Redis集群部署常见问题在了解Redis集群部署常见问题之前,我们先了解一下Redis集群的实现原理。RedisCluster将所有数据划分到16384个槽(slots)中,每个节点负责一部分槽。插槽信息存储在每个节点中。当RedisCluster客户端连接到集群时,也会获取集群槽配置信息的副本,缓存在客户端本地。这样,当客户端要查找某个key时,可以直接定位到目标节点。同时,由于客户端和服务端的槽位信息可能不一致,因此需要一种修正机制来实现槽位信息的校验和调整。(1)跳转重定位问题当客户端向错误的节点发送命令时,节点会发现命令的key所在的slot不是自己管理的,然后会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端连接到这个节点获取数据。客户端收到指令后,不仅会跳转到正确的节点进行操作,还会同步更新修正本地的slot映射表缓存,后续所有的key都会使用新的slot映射表。(2)网络抖动问题在生产环境中,网络抖动问题是不可避免的。为了解决这个问题,RedisCluster提供了一个选项clusternodetimeout,即当一个节点超时不断失去连接时,只能将该节点识别为出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致频繁的主从切换(数据重新复制)。05基于Redis的分布式锁实现代码在多个进程/线程读写同一个共享资源的场景下,会因为资源争用而产生混乱,导致数据不一致。为了避免这个问题,我们可以在进程/线程操作共享资源之前获取一个token(也就是锁)。只有获得令牌的进程/线程才能操作资源,并在资源被操作后释放令牌。这实现了分布式锁。Redis的分布式锁是基于RedisSETNX命令实现的。在Redis中通过SETNX命令设置KeyValue时,有以下两种结果:1)返回1,表示为指定的key设置值成功,表示当前进程设置成功。获取到锁资源2)返回0,说明给指定的key设置值失败,因为key已经存在,说明其他进程已经获取到锁资源下面来看看如何通过python实现分布式锁(1)首先我们创建一个不使用分布式锁的演示,通过多线程将全局变量加1,看看结果如何。具体代码如下:代码运行结果如下,并不是我们期望的值(期望值应该是3+3=6)(2)然后我们创建一个带分布式锁的链表。我们来看看创建分布式锁的方法,如下:1.importtime2.importuuid3.fromredisimportStrictRedis,ConnectionPool4.importthreading5.6.classCollectRedis:7.#创建redis操作类8.def__init__(self):9.self.host="1.1.1.1"10.self.port=637911.self.db=512.13.@property14.defredis_session(self):15._session=getattr(self,"__redis_session",None)16.if_session:17.return_session18.redis_pool=ConnectionPool(host=self.host,port=self.port,db=self.db)19._session=StrictRedis(connection_pool=redis_pool)20.setattr(self,"__redis_session",_session)21.return_session22.23.#获取锁24.defget_lock(self,lock_key):25.returnsself.redis_session.get(lock_key)26.27.#设置锁28.defset_lock(self,lock_key,value,timeout=300):29.ssession=self.redis_session30.tag=session.setnx(lock_key,value)31.#如果能成功创建key,给key设置一个超时时间,相当于锁的有效时间32.#如果有是没有超时时间,会导致程序死锁33.iftag:34.session.expire(lock_key,timeout)35.returntag36.37.#deletelock是释放锁38.defdelete_lock(self,lock_key):39.returnself.redis_session.delete(lock_key)40.41.#获取锁资源方法42.defacquire_lock(lock_name,time_out=300):43.identifier=str(uuid.uuid4())44.end=time.time()+time_out+3045.redis_connect=收集Redis()46。#如果获取不到锁资源,线程会挂起,直到获取到锁资源或者超时47.whiletime.time()
