redis分布式(redis分布式锁实现原理)

本篇文章给大家谈谈redis分布式,以及redis分布式锁实现原理对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

细说分布式redis

IT培训数据库教程

细说分布式Redis架构设计和踩过的那些坑

作者:课课家教育2015-12-14 10:15:25

摘要:本文章主要分成五个步骤内容讲解

Redis、RedisCluster和Codis;

我们更爱一搜散御致性;

Codis在生产环境中的使用的经验和坑们;

掘禅对于分布式数据库和分布式架构的一些看法;

Q A环节。

Codis是一个分布式Redis解决方案,与官方的纯P2P的模式不同,Codis采用的是Proxy-based的方案。今天我们介绍一下Codis及下一个大版本RebornDB的设计,同时会介绍一些Codis在实际应用场景中的tips。最后抛砖引玉,会介绍一下我对分布式存储的一些观点和看法,望各位首席们雅正。

细说分布式Redis架构设计和踩过的那些坑_redis 分布式_ redis 分布式锁_分布式缓存redis

一、 Redis,RedisCluster和Codis

世岩Redis:想必大家的架构中,Redis已经是一个必不可少的部件,丰富的数据结构和超高的性能以及简单的协议,让Redis能够很好的作为数据库的上游缓存层。但是我们会比较担心Redis的单点问题,单点Redis容量大小总受限于内存,在业务对性能要求比较高的情况下,理想情况下我们希望所有的数据都能在内存里面,不要打到数据库上,所以很自然的就会寻求其他方案。 比如,SSD将内存换成了磁盘,以换取更大的容量。更自然的想法是将Redis变成一个可以水平扩展的分布式缓存服务,在Codis之前,业界只有Twemproxy,但是Twemproxy本身是一个静态的分布式Redis方案,进行扩容/缩容时候对运维要求非常高,而且很难做到平滑的扩缩容。Codis的目标其实就是尽量兼容Twemproxy的基础上,加上数据迁移的功能以实现扩容和缩容,最终替换Twemproxy。从豌豆荚最后上线的结果来看,最后完全替换了Twem,大概2T左右的内存集群。

Redis Cluster :与Codis同期发布正式版的官方cl

redis 分布式锁

1、一个tomcat是一个进程,其中有很多线程(与有多少个app无关) 

2、一个tomcat启动一个JVM,其中可以有很多APP 

3、一个tomcat中部署的多个app,虽然同处一个JVM里,但是由于散源无法相互调用,所以也可以认为是分布式的 

synchronized 只是本地锁啊,锁的也只是当前jvm下的对象,在分布式场景下,要用分布式锁。

redis 分布式锁应用场景: 程序不是在一台tomcat(不同jvm)或者一台 tomcat部署的多个由于无法相互调用,synchronized失效,此时操作共享变备掘桐量,例如库存,就要用分布式锁仿坦

简陋版:

解决key 失效时间小于业务执行时间问题

//放到启动类

redisson底层主要是lua脚本

原理图:

解决key 失效时间小于业务执行时间问题

使用lua后的效果:

redis 集群,主redis挂了,此时还没同步到从redis,怎么办?

可以使用zookeeper,它会等 其他的zookeeper同步加速成功再返回成功

redis没办法100%解决这个问题,可以容忍,redis性能远高于zookeeper

解决

1.可以使用redlock(不推荐,不完善):2.使用redission

高并发分布式锁实现:

将数据在redis里分段减库存

Redis分布式缓存搭建

花了两天时间整理了之前记录的Redis单体与哨兵模式的搭建与使用,又补齐了集群模式的使用和搭建经验,并对集群的一些个原理做了理解。

笔者安装中遇到的一些问题:

如果make报错,可能是没装gcc或者gcc++编辑器,安装之 yum -y install gcc gcc-c++ kernel-devel ,有可能还是提示一些个c文件编译不过,gcc -v查看下版本,如果不到5.3那么升级一下gcc:

在 /etc/profile 追加一行 source /opt/rh/devtoolset-9/enable

scl enable devtoolset-9 bash

重新make clean, make

这回编译通态纳哪过了,提示让你最好make test一下/

执行make test ,如果提示 You need tcl 8.5 or newer in order to run the Redis test

那就升级tcl, yum install tcl

重新make test,如果还有error就删了目录,重新tar包解压重新make , make test

\o/ All tests passed without errors! ,表示编译成功。

然后make install即可。

直接运行命令: ./redis-server /usr/redis-6.0.3/redis.conf

redis.conf 配置文件里帆码 bind 0.0.0.0 设置外部访问, requirepass xxxx 设置密码。

redis高可用方案有两种:

常用搭建方案为1主1从或1主2从+3哨兵监控主节点, 以及3主3从6节点集群。

(1)sentinel哨兵

/usr/redis-6.0.3/src/redis-sentinel /usr/redis-6.0.3/sentinel2.conf

sentinel2.conf配置:

坑1:master节点也会在故障转移后成为从节点,也需要配置masterauth

当kill master进程之后,经过sentinel选举,slave成为了新的master,再次启动原master,提示如下错误:

原因是此时的master再次启动已经是slave了,需要向现在的新master输入密码,所以需要在master.conf

中配置:

坑2:哨兵配置文件要暴露客户端可以访问到的master地址

在 sentinel.conf 配置文件的 sentinel monitor mymaster 122.xx.xxx.xxx 6379 2 中,配置该哨兵对应的master名字、master地址和端口,以及达到多少个哨兵选举通过认为master挂掉。其中master地址要站在redis访问者(也就是客户端)的角度、配置茄稿访问者能访问的地址,例如sentinel与master在一台服务器(122.xx.xxx.xxx)上,那么相对sentinel其master在本机也就是127.0.0.1上,这样 sentinel monitor mymaster 127.0.0.1 6379 2 逻辑上没有问题,但是如果另外服务器上的springboot通过lettuce访问这个redis哨兵,则得到的master地址为127.0.0.1,也就是springboot所在服务器本机,这显然就有问题了。

附springboot2.1 redis哨兵配置:

坑3:要注意配置文件.conf会被哨兵修改

redis-cli -h localhost -p 26379 ,可以登到sentinel上用info命令查看一下哨兵的信息。

曾经遇到过这样一个问题,大致的信息如下

slaves莫名其妙多了一个,master的地址也明明改了真实对外的地址,这里又变成127.0.0.1 !

最后,把5个redis进程都停掉,逐个检查配置文件,发现redis的配置文件在主从哨兵模式会被修改,master的配置文件最后边莫名其妙多了一行replicaof 127.0.0.1 7001, 怀疑应该是之前配置错误的时候(见坑2)被哨兵动态加上去的! 总之,实践中一定要多注意配置文件的变化。

(2)集群

当数据量大到一定程度,比如几十上百G,哨兵模式不够用了需要做水平拆分,早些年是使用codis,twemproxy这些第三方中间件来做分片的,即 客户端 - 中间件 - Redis server 这样的模式,中间件使用一致性Hash算法来确定key在哪个分片上。后来Redis官方提供了方案,大家就都采用官方的Redis Cluster方案了。

Redis Cluster从逻辑上分16384个hash slot,分片算法是 CRC16(key) mod 16384 得到key应该对应哪个slot,据此判断这个slot属于哪个节点。

每个节点可以设置1或多个从节点,常用的是3主节点3从节点的方案。

reshard,重新分片,可以指定从哪几个节点移动一些hash槽到另一个节点去。重新分片的过程对客户端透明,不影响线上业务。

搭建Redis cluster

redis.conf文件关键的几个配置:

启动6个集群节点

[root@VM_0_11_centos redis-6.0.3]# ps -ef|grep redis

root 5508 1 0 21:25 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7001 [cluster]

root 6903 1 0 21:32 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7002 [cluster]

root 6939 1 0 21:33 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7003 [cluster]

root 6966 1 0 21:33 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7004 [cluster]

root 6993 1 0 21:33 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7005 [cluster]

root 7015 1 0 21:33 ? 00:00:00 /usr/redis-6.0.3/src/redis-server 0.0.0.0:7006 [cluster]

这时候这6个节点还是独立的,要把他们配置成集群:

说明: -a xxxx 是因为笔者在redis.conf中配置了requirepass xxxx密码,然后 --cluster-replicas 1 中的1表示每个master节点有1个从节点。

上述命令执行完以后会有一个询问: Can I set the above configuration? yes同意自动做好的分片即可。

最后 All 16384 slots covered. 表示集群中16384个slot中的每一个都有至少有1个master节点在处理,集群启动成功。

查看集群状态:

坑1:暴露给客户端的节点地址不对

使用lettuce连接发现连不上,查看日志 Connection refused: no further information: /127.0.0.1:7002 ,跟之前哨兵配置文件sentinel.conf里边配置master地址犯的错误一样,集群启动的时候带的地址应该是提供给客户端访问的地址。

我们要重建集群:先把6个redis进程停掉,然后删除 nodes-7001.conf 这些节点配置文件,删除持久化文件 dump.rdb 、 appendonly.aof ,重新启动6个进程,在重新建立集群:

然后,还是连不上,这次报错 connection timed out: /172.xx.0.xx:7004 ,发现连到企鹅云服务器的内网地址上了!

解决办法,修改每个节点的redis.conf配置文件,找到如下说明:

所以增加配置:

然后再重新构建集群,停进程、改配置、删除节点文件和持久化文件、启动进程、配置集群。。。再来一套(累死了)

重新使用Lettuce测试,这次终于连上了!

坑2:Lettuce客户端在master节点故障时没有自动切换到从节点

name这个key在7002上,kill这个进程模拟master下线,然后Lettuce一直重连。我们期望的是应该能自动切换到其slave 7006上去,如下图:

重新启动7002进程,

7006已成为新master,7002成为它的slave,然后Lettuce也能连接上了。

解决办法,修改Lettuce的配置:

笔者用的是springboot 2.1 spring-boot-starter-data-redis 默认的Lettuce客户端,当使用Redis cluster集群模式时,需要配置一下 RedisConnectionFactory 开启自适应刷新来做故障转移时的自动切换从节点进行连接。

重新测试:停掉master 7006,这次Lettuce可以正常切换连到7002slave上去了。(仍然会不断的在日志里报连接错误,因为需要一直尝试重连7006,但因为有7002从节点顶上了、所以应用是可以正常使用的)

Redis不保证数据的强一致性

Redis并不保证数据的强一致性,也就是取CAP定理中的AP

关于一致性Hash算法,可以参考 一致性Hash算法 - (jianshu.com)

Redis cluster使用的是hash slot算法,跟一致性Hash算法不太一样,固定16384个hash槽,然后计算key落在哪个slot里边(计算key的CRC16值再对16384取模),key找的是slot而不是节点,而slot与节点的对应关系可以通过reshard改变并通过gossip协议扩散到集群中的每一个节点、进而可以为客户端获知,这样key的节点寻址就跟具体的节点个数没关系了。也同样解决了普通hash取模算法当节点个数发生变化时,大量key对应的寻址都发生改动导致缓存失效的问题。

比如集群增加了1个节点,这时候如果不做任何操作,那么新增加的这个节点上是没有slot的,所有slot都在原来的节点上且对应关系不变、所以没有因为节点个数变动而缓存失效,当reshard一部分slot到新节点后,客户端获取到新迁移的这部分slot与新节点的对应关系、寻址到新节点,而没迁移的slot仍然寻址到原来的节点。

关于热迁移,猜想,内部应该是先做复制迁移,等迁移完了,再切换slot与节点的对应关系,复制没有完成之前仍按照原来的slot与节点对应关系去原节点访问。复制结束之后,再删除原节点上已经迁移的slot所对应的key。

与哨兵模式比较类似,当1个节点发现某个master节点故障了、会对这个故障节点进行pfail主观宕机,然后会通过gossip协议通知到集群中的其他节点、其他节点也执行判断pfail并gossip扩散广播这一过程,当超过半数节点pfail时那么故障节点就是fail客观宕机。接下来所有的master节点会在故障节点的从节点中选出一个新的主节点,此时所有的master节点中超过半数的都投票选举了故障节点的某个从节点,那么这个从节点当选新的master节点。

所有节点都持有元数据,节点之间通过gossip这种二进制协议进行通信、发送自己的元数据信息给其他节点、故障检测、集群配置更新、故障转移授权等等。

这种去中心化的分布式节点之间内部协调,包括故障识别、故障转移、选主等等,核心在于gossip扩散协议,能够支撑这样的广播协议在于所有的节点都持有一份完整的集群元数据,即所有的节点都知悉当前集群全局的情况。

Redis高可用方案 - (jianshu.com)

面试题:Redis 集群模式的工作原理能说一下么 - 云+社区 - 腾讯云 (tencent.com)

深度图解Redis Cluster原理 - detectiveHLH - 博客园 (cnblogs.com)

Redis学习笔记之集群重启和遇到的坑-阿里云开发者社区 (aliyun.com)

云服务器Redis集群部署及客户端通过公网IP连接问题

[img]

redis分布式锁常见问题及解决方案

        1.1 锁需要具备唯一性

        1.2 锁需要有超时时间,防止死锁

        1.3 锁的创建和设置锁超时时间需要具备原子性

        1.4 锁的超时的续期问题

       虚尘 1.5 B的锁被A给释放了的问题

        1.6 锁的可重入问题

        1.7 集群下分布式锁的问题

        问题讲解:

        首先分布式锁要解决的问题就是毁枯分布式环境下同一资源被多个进程进行访问和操作的问题,既然是同一资源,那么肯定要考虑数据安全问题.其实和单进程下加锁解锁的原理是一样的,单进程下需要考虑多线程对同一变量进行访问和修改问题,为了保证同一变量不被多个线程同时访问,按照顺序对变量进行修改,就要在访问变量时进行加锁,这个加锁可以是重量级锁,也可以是基于cas的乐观锁.

        解决方案:

        使用redis命令setnx(set if not exist),即只能被一个客户端占坑,如果redis实例存在唯一键(key),如果再想在该键(key)上设置值,就会被拒绝.

        问题讲解:

        redis释放锁需要客户端的操作,如果此时客户端突然挂了,就没有释放锁的操作了,也意味着其他客户端想要重新加锁,却加不了的问题.

        解决方案:

        所以,为了避免客户端挂掉或者说是客户端不能正常释放锁的问题,需要在加锁的同时,给锁加上超时时间.

        即,加锁和给锁加上超时时间的操作如下操作:

setnx lockkey true    #加锁操作

ok

expire lockkey 5    #给锁加上超时时间

... do something critical ...

del lockkey    #释放锁

(integer) 1

        问题讲解:

        通过2.3加锁和超时时间的设置可以看到,setnx和expire需要两个命令来完成操作,也就是需要两次RTT操作,如果在setnx和expire两次命令之间,客户端突然挂掉,这时又无法释放锁,且又回到了死锁的问题.

        解决方案:

        使用set扩展命令

        如下:

set lockkey true ex 5 nx   #加锁,过期时间5s

ok

... do something critical ...

del lockkey

        以上的set lockkey true ex 5 nx命令可以一次性完成setnx和expire两个操作,也就是解决了原子性问题.

        问题讲解:

        redis分布式锁过期,而业务逻辑没执行完的场景,不过,这里换一种思路想问题,把redis锁的过期时间再弄长点不就解决了吗?那还是有问题,我们可以在加锁的时候,手动调长redis锁的过期时间,可这个时间多长合适?业务逻辑的执行时间是不可控的,调的过长又会影响操作性能。

        解决方案:

        使用redis客户端redisson,redisson很好的解决了redis在分布式环境下的一些棘手问题,它的宗旨就是让使用者减少对Redis的关注,将更多精力用在处理业务逻辑上。redisson对分布式锁做了很好封装,只需调用API即可。RLock lock = redissonClient.getLock("stockLock");

        redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。这个机制也被叫做:“看门狗”

        问题讲解:

        A、B两个线程来尝试给key myLock加锁,A线程先拿到锁(假如锁3秒后过期),B线程就在等待尝试获取锁,到这一点毛病没有。那如果此时业务逻辑比较耗时,执行时间已经超过redis锁过期时间,这时A线程的锁自动释放(删除key),B线程检测到myLock这个key不存在,执行 SETNX命令也拿到了锁。但是,此时A线程执行完业务逻辑之后,还是会去释放锁(删除key),这就导差余禅致B线程的锁被A线程给释放了。

      解决方案:

      一般我们在每个线程加锁时要带上自己独有的value值来标识,只释放指定value的key,否则就会出现释放锁混乱的场景一般我们可以设置value为业务前缀_当前线程ID或者uuid,只有当前value相同的才可以释放锁

        问题讲解:

        上面我们讲了,为了保证锁具有唯一性,需要使用setnx,后来为了与超时时间一起设置,我们选用了set命令。 在我们想要在加锁期间,拥有锁的客户端想要再次获得锁,也就是锁重入

        解决方案:

       给锁设置hash结构的加锁次数,每次加锁就+1

        问题讲解:

        这一问题是在redis集群方案时会出现的.事实上,现在为了保证redis的高可用和访问性能,都会设置redis的主节点和从节点,主节点负责写操作,从节点负责读操作,也就意味着,我们所有的锁都要写在主redis服务器实例中,如果主redis服务器宕机,资源释放(在没有加持久化时候,如果加了持久化,这一问题会更加复杂),此时redis主节点的数据并没有复制到从服务器,此时,其他客户端就会趁机获取锁,而之前拥有锁的客户端可能还在对资源进行操作,此时又会出现多客户端对同一资源进行访问和操作的问题.

        解决方案:

        使用redlock,原理与zookeeper分布式锁原理相同.多台主机超过半数设置成功则获取锁成功,要注意下主机个数必须是奇数,不过这有效率问题

关于redis分布式和redis分布式锁实现原理的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

标签列表