深入浅出Redis

一. 不一样的Redis

提到Redis,大家一定会想到的几个点是什么呢?
高并发、KV存储、内存数据库、丰富的数据结构、单线程(版本6之前)等。
那么,接下来,上面提到的这些,都会一一给大家解答,带大家系统剖析一下Redis的架构设计魅力!

1. 为什么会出现缓存?

一般情况下,数据都是在数据库中,应用系统直接操作数据库。当访问量上万,数据库压力增大,这个时候,怎么办呢?

有小伙伴会说,分库分表、读写分离。的确,这些确实是解决比较高的访问量的解决办法,但是,如果访问量更大,10万,100万呢?怎么分似乎都不解决问题吧,所以我们需要用到其他办法,来解决高并发带来的数据库压力。

这个时候,缓存出现了,顾名思义,就是先把数据缓存在内存中一份,当访问的时候,我们会先访问内存的数据,如果内存中的数据不存在,这个时候,我们再去读取数据库,之后把数据库中的数据再备份一份到内存中,这样下次读请求过来的时候,还是会直接先从内存中访问,访问到内存的数据了之后就直接返回了。这样做就完美的降低了数据库的压力,可能十万个请求进来,全部都访问了内存中备份的数据,而没有去访问数据库,或者说只有少量的请求访问到了数据库,这样真的是大大降低了数据库的压力,而且这样做也提高了系统响应,大家想一下,内存的读写速度是远远大于硬盘的读写速度的,一个请求进来读取的内存可以比读取硬盘快很多很多,用户的体验也会很高。

1.2 什么是缓存?

缓存原指CPU上的一种高速存储器,它先于内存与CPU交换数据,速度很快。
现在泛指存储在计算机上的原始数据的复制集,便于快速访问。
在互联网技术中,缓存是系统快速响应的关键技术之一。

1.3 缓存的三种读写模式

1.3.1 Cache Aside Pattern(常用)

Cache Aside Pattern(旁路缓存),是最经典的缓存+数据库读写模式。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。

更新的时候,先更新数据库,然后再删除缓存.

为什么是删除缓存,而不是更新缓存呢?

  • 缓存的值是一个结构,hash、list等更新数据需要遍历;

  • 懒加载,使用的时候才更新缓存,也可以采用异步的方式填充缓存。

高并发脏读的三种情况:

  • 先更新数据库,在更新缓存;

    update与commit之间,更新缓存,commit失败,则DB与缓存数据不一致。
  • 先删除缓存,再更新数据库

    update与commit之间,有新的读,缓存空,读DB数据到缓存,数据是旧的数据;
    commit后DB为新的数据;
    则DB与缓存数据不一致。
  • 先更新数据库,再删除缓存(推荐)

update与commit之间,有新的读,缓存空,读DB数据到缓存,数据是旧的数据;
commit后DB为新的数据;
则DB与缓存数据不一致;
采用延时双删策略。

1.3.2 Read/Write Through Pattern

应用程序只操作缓存,缓存操作数据库;
Read-Through(穿透读模式/直读模式):应用程序读缓存,缓存没有,由缓存回源到数据库,并写入缓存;
Write-Through(穿透写模式/直写模式):应用程序写缓存,缓存写数据库。该种模式需要提供数据库的handler,开发较为复杂。

1.3.3 Write Behind Caching Pattern

应用程序只更新缓存;
缓存通过异步的方式将数据批量或合并后更新到DB中,不能时时同步,甚至会丢数据。

1.4 Redis又是什么?

Redis是一个高性能的开源的,C语言写的NoSQL(非关系型数据库)也叫做缓存数据库,数据保存在内存中。Redis是以key-value形式存储,和传统的关系型数据库不一样。不一定遵循传统数据库的那些基本要求。比如,不遵循SQL标准、事务、表结构等。Redis有非常丰富的数据类型,比如String,list,set,zset,hash等。

1.5 Redis可以做什么?

1.5.1 减轻数据库压力,提高并发量,提高系统响应时间
1.5.2 做Session分离

传统的Session是由自己的tomcat进行维护和管理的,在集群和分布式情况下,不同的tomcat要管理不同的session,只能在各个tomcat之间,通过网络和IO进行session复制,极大的影响了系统的性能。
Redis解决了这一个问题,将登陆成功后的session信息,存放在Redis中,这样多个tomcat就可以共享Session信息。

1.5.3 做分布式锁

一般Java中的锁都是多线程锁,是在一个进程中的,多个进程在并发的时候也会产生问题,也要控制时序性,这个时候Redis可以用来做分布式锁,使用Redis的setnx命令来实现。

1.5.4 电商购物车
  1. 以用户id为key
  2. 商品id为field
  3. 商品数量为value
    电商购物车操作:
  4. 添加商品:hset cart:1001 10088 1
  5. 增加数量:hincrby cart:1001 10088 1
  6. 商品总数:hlen cart:1001
  7. 删除商品:hdel cart:1001 10088
  8. 获取购物车所有商品:hgetall cart:1001
1.5.5 zset集合操作实现排行榜

1.5.5.1 点击新闻
ZINCRBY hotNews:20210416 1

1.5.5.2 展示当日排行前十
ZREVRANGE hotNews:20210416 0 9 WITHSCORES

1.5.5.3 七日搜索榜单计算
ZUNIONSTORE hotNews:20210416-20210516 7
hotNews:20210416 hotNews:20210417... hotNews:20210416

1.5.5.4 展示七日排行前十
ZREVRANGE hotNews:20210416-20210423 0 9 WITHSCORES

1.6 重点注意的问题

用Redis做缓存,有这么有多优点,那么,缺点是不是也会对应的有很多呢?**

1.6.1 缓存和数据库双写一致性问题

对于要求强一致性的,尽量不要使用缓存,只能采取合适的策略来降低缓存和数据库不一致的概率。

1.6.2 缓存雪崩问题

缓存在同一时间内大量的key过期的同时,又有大批的落入数据库中的。

解决方案:使用互斥锁。

这里要注意,分布式环境中要使用分布式锁,单机的话用普通的锁(synchronized、Lock)就够了。

1.6.3 缓存击穿问题

缓存击穿是用户恶意模拟请求很多缓存中不存在的数据,由于缓存中都没有,导致这些请求短时间内直接落在了数据库上,导致数据库异常。

解决方案:使用互斥锁。

1.6.4 缓存的并发竞争问题

多个redis的client同时set key引起的并发问题。其实redis自身就是单线程操作,多个client并发操作,先到的先执行,其余的阻塞。另外的解决方案是把redis.set操作放在队列中使其串行化,必须的一个一个执行。

1.7 Redis高性能设计

1.7.1 Redis是单线程的么?

Redis的单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化,异步删除,集群数据同步等,都是由额外的线程执行的。

1.7.2 Redis单线程为什么还能这么快?

这里我们在本地测试一下Redis支持的并发。

执行这条命令:

./redis-benchmark -h redis_server_ip get

结果:

====== get ======                                                   
  100000 requests completed in 1.68 seconds
  50 parallel clients
  13 bytes payload
  keep alive: 1
  host configuration "save": 
  host configuration "appendonly": no
  multi-thread: no

Latency by percentile distribution:
0.000% <= 0.239 milliseconds (cumulative count 45)
50.000% <= 0.543 milliseconds (cumulative count 50002)
75.000% <= 0.671 milliseconds (cumulative count 75158)
87.500% <= 0.799 milliseconds (cumulative count 87901)
93.750% <= 0.975 milliseconds (cumulative count 93898)
96.875% <= 1.175 milliseconds (cumulative count 96903)
98.438% <= 1.463 milliseconds (cumulative count 98439)
99.219% <= 1.871 milliseconds (cumulative count 99220)
99.609% <= 2.375 milliseconds (cumulative count 99611)
99.805% <= 3.863 milliseconds (cumulative count 99806)
99.902% <= 4.591 milliseconds (cumulative count 99903)
99.951% <= 4.903 milliseconds (cumulative count 99952)
99.976% <= 10.207 milliseconds (cumulative count 99976)
99.988% <= 10.303 milliseconds (cumulative count 99988)
99.994% <= 10.351 milliseconds (cumulative count 99994)
99.997% <= 10.375 milliseconds (cumulative count 99997)
99.998% <= 10.407 milliseconds (cumulative count 99999)
99.999% <= 10.431 milliseconds (cumulative count 100000)
100.000% <= 10.431 milliseconds (cumulative count 100000)

Cumulative distribution of latencies:
0.000% <= 0.103 milliseconds (cumulative count 0)
5.629% <= 0.303 milliseconds (cumulative count 5629)
22.536% <= 0.407 milliseconds (cumulative count 22536)
41.650% <= 0.503 milliseconds (cumulative count 41650)
63.423% <= 0.607 milliseconds (cumulative count 63423)
79.776% <= 0.703 milliseconds (cumulative count 79776)
88.305% <= 0.807 milliseconds (cumulative count 88305)
91.948% <= 0.903 milliseconds (cumulative count 91948)
94.593% <= 1.007 milliseconds (cumulative count 94593)
96.131% <= 1.103 milliseconds (cumulative count 96131)
97.161% <= 1.207 milliseconds (cumulative count 97161)
97.760% <= 1.303 milliseconds (cumulative count 97760)
98.256% <= 1.407 milliseconds (cumulative count 98256)
98.562% <= 1.503 milliseconds (cumulative count 98562)
98.802% <= 1.607 milliseconds (cumulative count 98802)
98.987% <= 1.703 milliseconds (cumulative count 98987)
99.130% <= 1.807 milliseconds (cumulative count 99130)
99.261% <= 1.903 milliseconds (cumulative count 99261)
99.385% <= 2.007 milliseconds (cumulative count 99385)
99.469% <= 2.103 milliseconds (cumulative count 99469)
99.755% <= 3.103 milliseconds (cumulative count 99755)
99.829% <= 4.103 milliseconds (cumulative count 99829)
99.955% <= 5.103 milliseconds (cumulative count 99955)
99.963% <= 10.103 milliseconds (cumulative count 99963)
100.000% <= 11.103 milliseconds (cumulative count 100000)

Summary:
  throughput summary: 59453.03 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
        0.594     0.232     0.543     1.031     1.711    10.431

这里我们可以看到,每秒的话,差不多可以支持小6万的并发,这已经是一个很厉害的数据了。

因为它的所有数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能消耗问题。正因为Redis是单线程的,所以要小心使用Redis命令,对于那些耗时的指令(比如keys),一定要谨慎使用,一不小心就可能导致Redis卡顿。

Redis单线程如何处理那么多并发客户端连接?

Redis的IO多路复用:Redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,一次放到文件事件分派器,事件分派器将事件分发给事件处理器。

1.8 Redis核心设计原理

Redis作为key-value存储系统,数据结构如下:

一个Redis实例对应多个DB,一个DB对应多个key,key一般都是string的,后面的value叫做RedisObject,不是说value就是string,list,map这些,而是说这些所有的类型,都被Redis封装成了一个叫RedisObjcet,具体是哪个类型呢?这里是用指针的方式来指向具体是哪个类型。

为什么要这么做,主要是为了提高Redis的性能。

PS:这里插一句,为什么使用指针的方式要比使用对象本身的方式性能更好呢?
这里有两点:
第一点是动态分配;第二是指针一大特点在于你只需要在前面声明一下指针指向的类型(而如果要使用实际的对象,你还需要定义一下)。这样你就能降低你的编译单元之间的耦合性从而减少编译时间。

1.8.1 RedisDB结构

Redis没有表的概念,Redis实例所对应的DB以编号区分,DB本身就是key的命名空间
比如:user:1000作为key的值,表示在user这个命名空间下id为1000的元素,类似于user表的id=1000的行。

1.8.2 SDS字符串

众所周知,Redis是用C语言来实现的,在C语言中,String这个类型,其实就是一个char数组,比如char data[]="xxx\0",但是,客户端往Redis发送set命令,是可以发任意的字符串的,是没有校验的,所以假如我们发了一个字符串xx\0xx,那么\0后面的xx是不会读的,只会读前面的xx(C语言中用"\0"表示字符串结束,如果字符串本身就有"\0"字符,字符串就会被截断)。

所以Redis自实现了一个string叫sds,sds中记录了一个len和一个char buf[],len用来记录buf的长度,比如char buf[] = "xx\0xx",那么len就是5,sds中还有一个比较重要的属性就是free,表示还剩余多少。
free是通过改变len来计算,比如"xxx1234" 改成 "xxx123456",那么会按照(len+addlen)*2=18 来扩容,这个时候len变成了9,free就是18-9也变成了9。
例如:

char buf[] = "xxx1234" 改成 "xxx123456" //这里的buf是柔性数组
free:12  变成free:10
len:8    变成len:10

Redis这样设计SDS有什么好处:
1、二进制安全的数据结构;
2、提供了内存预分配机制,避免了频繁的内存分配;
3、兼容C语言的函数库;
4、有单独的统计变量len和free,可以方便的得到字符串长度,这样就避免了读取不完整的风险;
5、内容存放在柔性数组buf中,SDS对上层暴露的指针不是指向结构体SDS的指针,而是直接指向柔性数组buf的指针。上层可像读取C字符串一样读取SDS的内容,兼容C语言处理字符串的各种函数。
这里解释一下什么叫柔型数组?
柔型数组即数组大小待定的数组,C语言中结构体的最后一个元素可以是大小未知的数组,也就是所谓的0长度,所以我们可以用结构体来创建柔性数组。柔性数组主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题(这也是Redis3.2之前所实现的)

1.9 优化建议

1.9.1 尽量使用短的key

当然在精简的同时,不要完了key的“见名知意”。对于value有些也可精简,比如性别使用0、1。

1.9.2 避免使用keys

keys *, 这个命令是阻塞的,即操作执行期间,其它任何命令在你的实例中都无法执行。当redis中key数据量小时到无所谓,数据量大就很糟糕了。所以我们应该避免去使用这个命令。可以去使用SCAN,来代替。

1.9.3 在存到Redis之前先把你的数据压缩下

redis为每种数据类型都提供了两种内部编码方式,在不同的情况下redis会自动调整合适的编码方式。

1.9.4 设置 key 有效期

我们应该尽可能的利用key有效期。比如一些临时数据(短信校验码),过了有效期Redis就会自动为你清除!

在Redis内,我们可以使用EXPIRE或EXPIREAT设置key的过期时间,Redis内存达到maxmemory限制后,Redis内存就会施行过期数据淘汰策略。过期删除策略通常有三种:定时删除、惰性删除、定期删除,目前Redis使用的过期键删除策略为惰性删除和定期删除,两种策略配合使用。

惰性删除,指当某个key值过期之后,该key值不会马上被删除,只有当读或者写一个已经过期的key时,才会触发惰性删除策略,此时该key完成删除。

定期删除,指每隔一段时间就会扫描一定数量数据库的expires字典内一定数量的key,并删除里面过期的key。

由于惰性删除无法保证过期数据被及时删除掉,所以Redis采用惰性删除,定期删除相结合的方法,可以帮助实现定期主动淘汰已过期的key,从而使cpu和内存资源达到最优的平衡效果。

1.9.5 选择回收策略(maxmemory-policy)

当 Redis 的实例空间被填满了之后,将会尝试回收一部分key。根据你的使用方式,强烈建议使用 volatile-lru(默认) 策略——前提是你对key已经设置了超时。但如果你运行的是一些类似于 cache 的东西,并且没有对 key 设置超时机制,可以考虑使用 allkeys-lru 回收机制,具体讲解查看 。maxmemory-samples 3 是说每次进行淘汰的时候 会随机抽取3个key 从里面淘汰最不经常使用的(默认选项)

maxmemory-policy 六种方式 :

  • volatile-lru:只对设置了过期时间的key进行LRU(默认值)
  • allkeys-lru : 是从所有key里 删除 不经常使用的key
  • volatile-random:随机删除即将过期key
  • allkeys-random:随机删除
  • volatile-ttl : 删除即将过期的
  • noeviction : 永不过期,返回错误
1.9.6 使用bit位级别操作和byte字节级别操作来减少不必要的内存使用
  • bit位级别操作:GETRANGE, SETRANGE, GETBIT and SETBIT
  • byte字节级别操作:GETRANGE and SETRANGE
1.9.7 尽可能地使用hashes哈希存储
1.9.8 当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能
1.9.9 想要一次添加多条数据的时候可以使用管道。
1.9.10 限制redis的内存大小

64位系统不限制内存,32位系统默认最多使用3GB内存

数据量不可预估,并且内存也有限的话,尽量限制下redis使用的内存大小,这样可以避免redis使用swap分区或者出现OOM错误。(使用swap分区,性能较低,如果限制了内存,当到达指定内存之后就不能添加数据了,否则会报OOM错误。可以设置maxmemory-policy,内存不足时删除数据。)

1.9.11 SLOWLOG [get/reset/len]
  • slowlog-log-slower-than 它决定要对执行时间大于多少微秒(microsecond,1秒 = 1,000,000 微秒)的命令进行记录。
  • slowlog-max-len 它决定 slowlog 最多能保存多少条日志,当发现redis性能下降的时候可以查看下是哪些命令导致的。

二. Redis集群

Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
img

客户端与 redis 节点直连,不需要中间 proxy 层.客户端不需要连接集群所有节点连接集群中任何一个可用节点即可。

2.1 集群通信机制

Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)

2.1.1判断节点宕机

如果一个节点认为另外一个节点宕机,那么就是pfail,主观宕机,如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机。在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail

2.1.2 从节点过滤

对宕机的master node,从其所有的slave node中,选择一个切换成master node,检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master

2.1.3 从节点选举

每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master。从节点执行主备切换,从节点切换为主节点

2.2 分布存储机制-槽Slots

(1)redis-cluster 把所有的物理节点映射到[0-16383]slot 上,cluster 负责维护node<->slot<->value
(2)Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

例如三个节点:槽分布的值如下:

  • SERVER1: 0-5460
  • SERVER2: 5461-10922
  • SERVER3: 10923-16383

(3)如果一台机器接收到不是自己负责的slot的数据,就会把请求“转发”给该负责的机器。这个就叫转向 (Redirection)。

怎么确定新来的数据在哪台机器上写入呢?

Redis利用了Hash Table数据结构的基本原理,即通过一个Hash function把key映射为一个固定的整数number。通过number % 16384而得到一个固定的index整数值,根据这个index就能直到它所属的slot在哪个“负责人”位置了。

2.3 部署集群

IP 192.168.118.170 192.168.118.171 192.168.118.172
master端口 6379 6379 6379
slave端口 6380 6380 6380
2.3.1 安装相关依赖包
sudo yum install -y  --downloaddir=/home/tfd/setup/yum gcc gcc-c autoconf automake  rpm-build tcl libtool numactl   
2.3.2 解压安装
tar zxvf redis-6.2.1.tar.gz;
mv redis-6.2.1 /home/tfd/app/redis;
cd /home/tfd/app/redis;
make;
make test;
#如果测试的有报错,请清除编译的文件make clean
make PREFIX=/home/tfd/app/redis install;
2.3.3 增加环境变量
sudo vim /etc/profile
#Redis
export Redis=/home/tfd/app/redis
export PATH=PATH:Redis/bin

立即生效

source /etc/profile
2.3.4 创建数据目录
mkdir -p /home/tfd/app/redis_cluster/{6379,6380}/{data,log};
cp /home/tfd/app/redis/redis.conf /home/tfd/app/redis_cluster/6379;
cp /home/tfd/app/redis/redis.conf /home/tfd/app/redis_cluster/6380;
2.3.5 修改配置文件

6379端口:

vim /home/tfd/app/redis_cluster/6379/redis.conf
#绑定ip
bind 192.168.118.170
#服务端口
port 6379
#后台运行
daemonize yes
#pid文件
pidfile /var/run/redis_6379.pid
#指定日志文件
logfile "/home/tfd/app/redis_cluster/6379/log/redis-6379.log"
#指定数据文件路径
dir "/home/tfd/app/redis_cluster/6379/data"
#默认过期策略,只对设置龙过期时间的key进行LRU(默认值)
maxmemory-policy volatile-lru
#去掉注释,开启集群
cluster-enabled yes
#集群配置,对应端口
 cluster-config-file "/home/tfd/app/redis_cluster/6379/nodes-6379.conf"
 #请求超时,默认15秒,可自行设置
 cluster-node-timeout 15000
 #aof日志开启,如有需要可开启
appendonly no
save ""
save 3600 1
save 300 100
save 60 10000

6380端口:

vim /home/tfd/app/redis_cluster/6380/redis.conf
#绑定ip
bind 192.168.118.170
#服务端口
port 6380
#后台运行
daemonize yes
#pid文件
pidfile /var/run/redis_6380.pid
#指定日志文件
logfile "/home/tfd/app/redis_cluster/6380/log/redis-6380.log"
#指定数据文件路径
dir "/home/tfd/app/redis_cluster/6380/data"
#默认过期策略,只对设置了过期时间的key进行LRU(默认值)
maxmemory-policy volatile-lru
#去掉注释,开启集群
cluster-enabled yes
#集群配置,对应端口
 cluster-config-file "/home/tfd/app/redis_cluster/6380/nodes-6380.conf"
 #请求超时,默认15秒,可自行设置
 cluster-node-timeout 15000
 #aof日志开启,如有需要可开启
appendonly no
save ""

快速修改

sed -i 's/bind 127.0.0.1/bind 192.168.118.170/g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/bind 127.0.0.1/bind 192.168.118.170/g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's/port 6379/port 6380/g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's/daemonize no/daemonize yes/g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/daemonize no/daemonize yes/g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's/redis_6379.pid/redis_6380.pid/g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's#logfile ""#logfile "/home/tfd/app/redis_cluster/6379/log/redis-6379.log"#g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's#logfile ""#logfile "/home/tfd/app/redis_cluster/6380/log/redis-6380.log"#g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's#dir ./#dir "/home/tfd/app/redis_cluster/6379/data"#g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's#dir ./#dir "/home/tfd/app/redis_cluster/6380/data"#g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's/# maxmemory-policy noeviction/maxmemory-policy volatile-lru/g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/# maxmemory-policy noeviction/maxmemory-policy volatile-lru/g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's/# cluster-enabled yes/cluster-enabled yes/g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/# cluster-enabled yes/cluster-enabled yes/g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's%# cluster-config-file nodes-6379.conf% cluster-config-file "/home/tfd/app/redis_cluster/6379/nodes-6379.conf"%g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's%# cluster-config-file nodes-6379.conf% cluster-config-file "/home/tfd/app/redis_cluster/6380/nodes-6380.conf"%g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's/# cluster-node-timeout 15000/cluster-node-timeout 15000/g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/# cluster-node-timeout 15000/cluster-node-timeout 15000/g' /home/tfd/app/redis_cluster/6380/redis.conf;
sed -i 's/save \"\"/save \"\"/' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/# save ""/save ""/g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/# save ""/save ""/g' /home/tfd/app/redis_cluster/6380/redis.conf;
2.3.6 启动脚本

6379:

tee -a /home/tfd/app/redis_cluster/6379/6379.sh <<-'EOF'
#!/bin/sh
#端口
REDISPORT=6379
#执行命令
EXEC=/home/tfd/app/redis/bin/redis-server
CLIEXEC=/home/tfd/app/redis/bin/redis-cli
#pid和配置文件
PIDFILE=/var/run/redis_{REDISPORT}.pid
CONF="/home/tfd/app/redis_cluster/6379/redis.conf"

case "1" in
    start)
        if [ -f PIDFILE ]
        then
                echo "PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                EXECCONF
        fi
        ;;
    stop)
        if [ ! -f PIDFILE ]
        then
                echo "PIDFILE does not exist, process is not running"
        else
                PID=(catPIDFILE)
                echo "Stopping ..."
                CLIEXEC -pREDISPORT shutdown
                while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac
EOF

6380

tee -a /home/tfd/app/redis_cluster/6380/6380.sh <<-'EOF'
#!/bin/sh
REDISPORT=6380
EXEC=/home/tfd/app/redis/bin/redis-server
CLIEXEC=/home/tfd/app/redis/bin/redis-cli

PIDFILE=/var/run/redis_{REDISPORT}.pid
CONF="/home/tfd/app/redis_cluster/6380/redis.conf"

case "1" in
    start)
        if [ -f PIDFILE ]
        then
                echo "PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                EXECCONF
        fi
        ;;
    stop)
        if [ ! -f PIDFILE ]
        then
                echo "PIDFILE does not exist, process is not running"
        else
                PID=(catPIDFILE)
                echo "Stopping ..."
                CLIEXEC -pREDISPORT shutdown
                while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac
EOF

添加权限

chmod +x /home/tfd/app/redis_cluster/6379/6379.sh;
chmod +x /home/tfd/app/redis_cluster/6380/6380.sh;
2.3.7 拷贝到其他节点

改redis.conf中的bind ip,改成本机的

scp -r /home/tfd/app/ tfd@dc-did-server-2:/home/tfd;
sed -i 's/bind 192.168.118.170/bind 192.168.118.171/g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/bind 192.168.118.170/bind 192.168.118.171/g' /home/tfd/app/redis_cluster/6380/redis.conf;
scp -r /home/tfd/app/ tfd@dc-did-server-3:/home/tfd;
sed -i 's/bind 192.168.118.170/bind 192.168.118.172/g' /home/tfd/app/redis_cluster/6379/redis.conf;
sed -i 's/bind 192.168.118.170/bind 192.168.118.172/g' /home/tfd/app/redis_cluster/6380/redis.conf;
2.3.8 启动服务
/home/tfd/app/redis_cluster/6379/6379.sh start;
/home/tfd/app/redis_cluster/6380/6380.sh start;

查看redis启动情况

ps -efl | grep redis
2.3.9 创建集群

2.3.9.1建立三个master

redis-cli --cluster create 192.168.118.170:6379 192.168.118.171:6379 192.168.118.172:6379 --cluster-replicas 0

2.3.9.2 slave挂靠master

redis-cli --cluster add-node 192.168.118.171:6380 192.168.118.170:6379 --cluster-slave --cluster-master-id 53fed7f1682ffb9a2e507f282149a25c0563e224;
redis-cli --cluster add-node 192.168.118.172:6380 192.168.118.171:6379 --cluster-slave --cluster-master-id 6ca24bfba979ef3ec93d222320cd3c565249744a;
redis-cli --cluster add-node 192.168.118.170:6380 192.168.118.172:6379 --cluster-slave --cluster-master-id 4827391da498f9d659493d0853d6fe0f8025f139;
2.3.10 集群验证
redis-cli -c -h 192.168.118.170 -p 6379 cluster nodes
redis-cli -c -h 192.168.118.170 -p 6380
192.168.118.170:6380> set hello world

三. Redis 常用命令

3.1 集群命令

集群

cluster info :打印集群的信息
cluster nodes :列出集群当前已知的所有节点( node),以及这些节点的相关信息。
节点
cluster meet :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
cluster forget :从集群中移除 node_id 指定的节点。
cluster replicate :将当前节点设置为 node_id 指定的节点的从节点。
cluster saveconfig :将节点的配置文件保存到硬盘里面。
槽(slot)
cluster addslots [slot ...] :将一个或多个槽( slot)指派( assign)给当前节点。
cluster delslots [slot ...] :移除一个或多个槽对当前节点的指派。
cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
cluster setslot node :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给
另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
cluster setslot migrating :将本节点的槽 slot 迁移到 node_id 指定的节点中。
cluster setslot importing :从 node_id 指定的节点中导入槽 slot 到本节点。
cluster setslot stable :取消对槽 slot 的导入( import)或者迁移( migrate)。

cluster keyslot :计算键 key 应该被放置在哪个槽上。
cluster countkeysinslot :返回槽 slot 目前包含的键值对数量。
cluster getkeysinslot :返回 count 个 slot 槽中的键

3.2 连接操作命令

  • quit:关闭连接(connection)
  • auth:简单密码认证
  • help cmd: 查看 cmd 帮助,例如:help quit

3.3 持久化

  • save:将数据同步保存到磁盘
  • bgsave:将数据异步保存到磁盘
  • lastsave:返回上次成功将数据保存到磁盘的 Unix 时戳
  • shutdown:将数据同步保存到磁盘,然后关闭服务

3.4 远程服务控制

  • info:提供服务器的信息和统计
  • monitor:实时转储收到的请求
  • slaveof:改变复制策略设置
  • config:在运行时配置 Redis 服务器

3.5 对 key 操作的命令

  • exists(key):确认一个 key 是否存在
  • del(key):删除一个 key
  • type(key):返回值的类型
  • keys(pattern):返回满足给定 pattern 的所有 key
  • randomkey:随机返回 key 空间的一个
  • keyrename(oldname, newname):重命名 key
  • dbsize:返回当前数据库中 key 的数目
  • expire:设定一个 key 的活动时间(s)
  • ttl:获得一个 key 的活动时间
  • select(index):按索引查询
  • move(key, dbindex):移动当前数据库中的 key 到 dbindex 数据库
  • flushdb:删除当前选择数据库中的所有 key
  • flushall:删除所有数据库中的所有 key

3.6 redis-benchmark

redis性能测试工具,测试redis在当前系统下的读写性能
- -h 检测主机ip地址
- -p 检测主机端口
- -s服务器套接字
- -c 并发连接数
- -n 请求数
- -d 测试使用的数据集的大小/字节(默认3字节)
- -k 1:表示保持连接(默认值) 0:重新连接
- -r SET/GET/INCR方法使用随机数插入数值,设置10则插入值为rand:000000000000 - rand:000000000009
- -P 默认为1(无管道),当网络延迟过长时,使用管道方式通信(请求和响应打包发送接收)
- -q 简约信息模式,只显示查询和秒值等基本信息
- --csv 以csv格式输出信息
- -l 无线循环插入测试数据,ctrl+c停止
- -t 只运行测试逗号分隔的列表命令,如 -t ping , set , get
- -I 空闲模式,立即打开50个空闲连接和等待

redis-benchmark -t set -n 1000000 -r 100000000
redis-benchmark -t ping,set,get -n 100000 –csv
redis-benchmark -r 10000 -n 10000 lpush mylist ele:rand:000000000000
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

附.Redis的数据类型

Redis支持保存五种基本数据结构类型。string(字符串),hash(哈希),list(列表),set(集合)和zset(sorted set有序集合)。

1.string

它是redis的最基本的数据类型,一个键对应一个值,需要注意是一个键值最大存储512MB。一般用于一些复杂的计数功能的缓存;

2.hash

redis hash是一个键值对的集合,是一个string类型的field和value的映射表,这里的value存放的是结构化的对象,操作其中某个字段十分方便,适合用于存储对象;

3.list

是Redis类相对简单的字符串列表,通过list可以做简单的消息列队功能。list对应的是一个双向列表,按照插入顺序排序;

4.set

是string类型的无序集合,集合中的数据不能重复出现,所以可以用来做全局的去重功能;

5.zset

是string类型的有序集合,同set一样不可重复。有序集合中排序因子为每个元素附带一个double型的分数,根据分数对元素进行升序排序。如果多个元素有相同的分数,就会通过字典序完成升序排序,因此sorted set十分适合做排行榜应用。sorted set也可用来做延时任务。

6.redis.conf 配置文件说明
- daemonize 如需后台运行改为yes
- pdifile 把pid文件放在/var/run/redis.pid,可以配置到其他地址
- port 监听端口,默认为6379
- timeout 连接时的超时时间,单位为秒
- loglevel 四个级别:debug、verbose、notice、warning
- Debug 记录很多信息,用于开发和测试;
- Varbose 有用的信息,不像debug会记录那么多;
- Notice 普通的verbose,常用于生产环境;
- Warning 只有非常重要或者严重的信息会记录到日志; 默认是notice级别。
- logfile 日志路径,默认使用标准输出,即打印在命令行终端的端口上
- database 可用数据库数,默认值为16,默认使用的数据库是0
- save 保存快照的频率,第一个表示多长时间,第二个表示执行多少次写操作。在一定时间内执行一定数量的写操作时,自动保存快照。可设置多个条件。比如save 300 10 表示:300秒内至少有300个key被改变时,触发保存
- rdbcompression 在进行镜像备份时,是否进行压缩
- dbfilename 镜像备份文件的文件名
- dir 镜像备份的文件放置的路径
- slaveof 设置该数据库为其他数据库的从数据库
- masterauth 当主数据库连接需要密码验证时,在这里设定
- maxclients 限制同时连接的客户端数量
- appendonly 开启的话每次写操作会记一条log,这会提高数据抗风险能力,但影响效率
- appendfsync 设置aof的同步频率,有三种选择always、everysec、no,
- always每次写都强制调用fsync,everysec每秒启用一次fsync,
- no不调用fsync等待系统自己同步。默认是everysec表示每秒同步一次。
- vm_enabled 是否开启虚拟内存支持
- vm_swap_file 虚拟内存的交换文件的路径
- vm_max_momery 开启虚拟内存后,redis将使用的最大物理内存的大小,默认为0

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭