NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。
NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。
基本都是内存数据库
,查询速度极快
特点
Redis概述
string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)
且这些操作都是原子性的。
数据都是缓存在内存中。
但是Redis可以将数据持久化
master-slave(主从)同步
使用场景
。。。
Redis的安装
参考word文档笔记
相关知识即小总结
Redis默认端口 6379
同一个库中不能有同名的key,会进行覆盖,不同库之间可以存在同名key
Redis也是内存数据库 但是可以持久化
默认有16个数据库,初始默认使用0号库,所有库密码相同
select n 切换数据库
Redis是单线程+多路IO复用
常用命令
1、Docker进入容器中使用客户端连接redis服务器
redis-cli
2、Linux下启动redis服务
redis-server /xx/redis.conf 以指定目录下的配置文件启动redis
3、客户端连接
redis-cli -p xxx 指定连接哪个端口的redis服务
如果有密码 使用 auth 密码
4、启动哨兵服务
redis-sentienl /xx/sentinel.conf 启动哨兵服务以指定的配置文件
dbsize 查看当前数据库的key数量
flushdb 清空当前库
flushall 清空所有库
keys * 查看当前库的所有key
del keyName 删除key
unlink keyName 根据value选择非阻塞删除 仅将key从keyspace中元数据中删除,真正的删除会在后续异步操作
type keyName 查看key对应的value类型
exists keyName 判断指定key是否存在
expire keyName n 为指定的key设置一个过期时间 单位:秒 (不设置默认永不过期)
ttl keyName 查看这个key还有多少秒过期 -1表示永不过期 -2表示已经过期
可以将Redis看做就是一个Map,key是String,value就是下面的5中数据类型
value类似Java中的String
所以说这个Map就可以看做是Map
512M
1、添加或者修改
set key value 添加一个值
mset k1 v1 k2 v2 ... 添加多个k-v
2、添加时直接设置过期时间
setex key timen value
3、以新换旧
设置新值的同时返回原始值
getset key newvalue
4、查询
get key 获取指定key的value值
mget k1 k2 ... 获取多个key的value值
5、范围查询
getrange key start end 查询key的value指定长度值
6、范围覆盖(覆盖指定索引后面的value) 索引从0开始 左闭右闭 [ ]
setrange key startn value
7、追加
append key abc 在指定key的value值后面添加abc字符串
8、获取value长度
strlen key 获取key的value长度
9、尝试添加
setnx key value 添加一个值 如果不存在添加 存在就不添加 (用于分布式锁机制)
返回0代表设置失败 返回1代表设置成功
msetnx k13 v1 k2 v2 添加多个值,如果不存在则添加 存在就不添加 只有所有的都添加成功才 成功(原子性 有一个失败就全部失败)
返回0代表添加失败 返回1代表添加成功
10、原子增减
----- 下面这些操作都是原子性的 (单线程操作)
incr key 将指定key的value值加1 前提:value必须是数字
decr key 将指定key的value值减1 前提:value必须是数字
incrby key n 将指定key的value值加指定的值 前提:value必须是数字
dncrby key n 将指定key的value值减指定的值 前提:value必须是数字
当字符串长度小于1M时,扩容都是加倍现有的空间
,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M
。1、从左边或者右边插入数据,先进来的数据在最前面
lpush key v1 v2 v3 ...
rpush key v1 v2 v3 ...
2、从列表的左边或者右边取出一个值,如果此列表中值全被取出了,那么这个列表就不存在了
lpop key
rpop key
3、按照索引下标范围遍历元素从左到右 (跟lpop不同 这个只是查询功能,不会将数据取出)
如果写 lrange key 0 -1 则会取出所有的数据
lrange key start end
4、获取列表长度
llen key
5、获取指定索引位置n上的元素(索引从0开始)
lindex key n
6、从k1列表取出一个元素插入到k2列表的左边
rpoplpush k1 k2
7、在指定的元素oldvalue左边插入一个元素newvalue
linsert key before oldvalue newvalue
8、从左边开始删除n个指定value的元素
lrem key n value
9、替换列表中索引为n的值
lset key n newvalue
无序且不可重复
无序
不重复
集合,底层就是一个Hash表,所以添加删除查找复杂度都是O(1)
Map>
1、添加元素(已存在的元素不会添加 不重复型) 返回值代表插入成功的数据的个数
sadd key v1 v2 v3 ...
2、查询出指定key的集合中的所有元素
smembers key
3、判断指定集合中是否包含某一个元素(v1)
sismember key v1
4、返回集合中的元素个数
scard key
5、删除集合中的指定元素
srem key v1 v2 ...
6、随机从集合中取出n个元素并返回 (相当于从集合中随机删除并返回删除的值)
spop key n
7、随机从集合中获取n个元素 (不会将元素从集合中删除)
srandmember key n
8、将k1集合中的一个元素v1移动到k2集合中
smove k1 k2 v1
9、返回两个集合的交集
sinter k1 k2
10、返回两个集合的并集元素。
sunion k1 k2
11、返回两个集合的差集元素 (即找出key1中有的k2没有的元素)
sdiff k1 k2
Map>
整个数据可以看做是- Map
,
将最外层的key取名叫key
内部的map的key取名叫innerkey
1、添加数据(可以添加多个数据)
hset key innerkey1 v1 innerkey2 v2 ....
2、取出一个数据
hget key innerkey
3、判断Hash中是否存在某个字段
hexists key innerkey
4、获取指定key的Hash中所有的key
hkeys key
5、获取指定key的Hash中的所有value
hvals key
6、将指定key的Hash中的指定key的value值加n (前提:此value必须是数字类型)
hincrby key innerkey n
7、为指定key的innerkey的value赋值 (innerkey不存在时赋值 存在不赋值) 成功返回1 失败返回0
hsetnx key innerkey value
Map>
1、添加数据
zadd key score1 innerkey1 score2 innerkey2 ...
2、返回根据socre排序完成之后的指定索引范围的元素
> 写0 -1返回所有数据
> 带withscores score也会一起显示
zrange key index_start index_end withscores
3、返回score在指定范围的元素 (从小到大)
zrangebyscore key score_min score_max [withscores] [limit]分页
4、返回score在指定范围的元素 (从大到小)
zrevrangebyscore key score_max score_min [withscores] [limit]分页
5、为指定的innerkey加上n
zincrby key n innerkey
6、 删除一个Key中的innerkey
zrem key innerkey
7、统计一个Key中指定score范围的innerkey的个数
zcount key score_min score_max
8、返回一个innerkey在一个key中的排名(从0开始 从小到大)
zrank key innerkey
参考word文档
https://www.yuque.com/u21639638/kv9qg4/19986153
https://www.yuque.com/u21639638/kv9qg4/19986153
https://www.yuque.com/u21639638/kv9qg4/19986153
https://www.yuque.com/u21639638/kv9qg4/19986153
各个数据类型应用场景:
类型 | 简介 | 特性 | 场景 | Java表示 |
---|---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M | — | Map |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 | Map |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的API | 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 | Map |
Set(集合) | 哈希表实现,元素不重复 | 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 | **1、**共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 | Map |
Sorted ZSet(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 |
1、排行榜 2、带权重的消息队列 | Map |
参考Word文档
https://www.yuque.com/u21639638/kv9qg4/19986153#ST3hw
auth 密码
进行验证类似MQ
参考Word文档
https://www.yuque.com/u21639638/kv9qg4/19986153#ST3hw
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.1</version>
</dependency>
@Test
public void test(){
//Redis有密码的话使用带密码的构造器
Jedis jedis = new Jedis("ip",6379);
//测试是否连接成功 返回pong代表连接成功
String isConn = jedis.ping();
System.out.println(isConn);
}
//删除key
jedis.del("username");
//切换数据库
jedis.select(9);
Long size = jedis.dbSize();
Set<String> keys = jedis.keys("*");
//查看value类型
String type = jedis.type("username");
Boolean username = jedis.exists("username");
//设置过期时间
Long username1 = jedis.expire("username", 2000);
//查看过期时间
Long ttl = jedis.ttl("username");
//关闭
jedis.close();
方法和redis的命令基本一致
jedis.set("username", "张三");
String username = jedis.get("username");
//添加并设置过期时间
jedis.setex("password", 100, "123456");
jedis.mset("age","2","gender","男");
List<String> info = jedis.mget("username", "password", "age", "gender");
//获取原值并设置新值
String username1 = jedis.getSet("username", "李四");
System.out.println(username1);
//查看value的指定长度
String password = jedis.getrange("password", 0L, 1L);
System.out.println(password);
//key的value长度
Long strlen = jedis.strlen("age");
System.out.println(strlen);
//有不添加 没有才添加
Long setnx = jedis.setnx("username", "王五");
System.out.println(setnx);
//年龄+1
Long age = jedis.incr("age");
System.out.println(jedis.get("age"));
//从左边放数据
Long lpush = jedis.lpush("username", "zhangsna", "李四", "jack");
String username = jedis.lpop("username");
//取出数据
List<String> names = jedis.lrange("username", 0L, -1L);
for (String name : names) {
System.out.println(name);
}
//获取长度
Long llen = jedis.llen("username");
System.out.println(llen);
//获取指定位置上的值
String lindex = jedis.lindex("username", 2);
System.out.println(lindex);
//取出数据
String name = jedis.lpop("key");
multi
命令开启一个事务,此时是组队阶段,即所有的命令不会执行,可以使用exec
命令进入执行阶段,此时所有的操作按照顺序依次执行
,discard
命令将组队阶段的所有操作全部撤销
,即放弃事务,进入执行阶段的事务不能被撤销
事务中某些操作出现错误
开启一个事务,难免有些操作会出现错误,有两种情况
所有的命令都不会执行
。单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
watch key
在开启一个事务之前,可以使用watch [key1] [key2] [key3],可以监视一个或多个key,如果在事务执行之前这些key被改变了,value或者key的名字被改变,那么这个事务将被打断(被取消)
unwatch key
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。
官方文档说明
setnx
博客一
博客二
word文档
因为Redis中的数据都在内存中,我们想要将这些内存中的数据保存到硬盘中,就得使用持久化技术
Redis的持久化方式
保存某一时刻的数据状态
记录所有的写(增删改)命令到日志文件中
dump.rdb
BGSAVE
运行流程
: 客户端可以使用BGSAVE命令来创建一个快照 (dump.rdb) ,当接收到客户端的BGSAVE命令时,redis会调用fork
来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求名词解释
: fork 当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化: 在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务
SAVE
SAVE命令会阻塞主进程的操作,在SAVE执行完毕之前主进程无法执行操作,一般不使用
SAVE
命令,阻塞所有的客户端,不在执行客户端发送的任何命令,并且在save命令执行完毕之后关闭服务器BGSAVE
命令,如果设置多个save配置选项,只要任意一个save配置选项满足,redis也会触发一次BGSAVE
命令。可以自定义配置
默认的触发条件
save 900 1 ---->900秒有一个key发生变化就生成一次dump.rdb文件
save 300 10 ---->300秒有10个key发生变化就生成一次dump.rdb文件
save 60 1000 ---->60秒内有1000个key发生变化就生成一次dump.rdb文件
生成快照文件的名称和位置
修改生成快照名称
dbfilename dump.rdb
修改生成位置
dir ./
可能会丢失数据,当某一时刻满足生成rdb文件的条件时会生成一次,然后这时又执行了一些写操作,但是不满足生成rdb文件的条件,这时突然Redis宕机(非正常的shutdown),那么这些数据就不会被生成快照,就造成了数据的丢失。
将Redis执行的写操作都记录到日志文件AOF中,以此来记录数据发生的变化,所以在Redis服务器启动时只需执行一遍AOF文件即可将数据恢复
Redis默认没有开启AOF持久化,需要在Redis.conf中手动开启
# 1.开启AOF持久化
- a.修改 appendonly yes 开启持久化
- b.修改 appendfilename "appendonly.aof" 指定生成文件名称(AOF文件)
默认的位置跟RDB配置的dir位置相同
默认是everysec
相当于批量写入
# 1.always 【谨慎使用】
- 说明: 每个redis写命令都要同步写入硬盘,严重降低redis速度
- 解释: 如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
- 注意: 转盘式硬盘在这种频率下200左右个命令/s ; 固态硬盘(SSD) 几百万个命令/s;
- 警告: 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的写入放大问题,导致将固态硬盘的寿命从原来的几年降低为几个月。
# 2.everysec 【推荐】
- 说明: 每秒执行一次同步显式的将多个写命令同步到磁盘
- 解释: 为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据。
# 3.no 【不推荐】
- 说明: 由操作系统决定何时同步
- 解释:最后使用no选项,将完全有操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时,redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。
Redis.conf中修改
# 1.修改日志同步频率
- 修改appendfsync everysec|always|no 指定
作用:用来在一定程度上减少AOF文件的体积
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写(ReWriter)机制。
1、客户端重写
- 执行BGREWRITEAOF
命令 不会阻塞redis的服务
2、服务器配置自动触发
可以设置两个参数
auto-aof-rewrite-percentage
代表触发重写时当前文件大小是否比上一次文件大小的大了百分之xx
auto-aof-rewrite-min-size
代表触发重写的当前文件的大小
比如默认的两个参数的值分别是100和64,也就是说当AOF文件大小到大64MB时,会触发一次重写,当比如重写后可能变成了30MB,然后运行一段时间后到了60MB,这时满足了第一个条件,即60比30大了百分之百,但是不满足重写的最小文件大小64M,则不会重写,当AOF文件到达了64MB时才会重写,以次类推 (重写使用的也是BGREWRITEAOF操作)
在Redis.conf中找
重写aof文件的操作,并没有修改旧的AOF文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件,这点和快照有点类似。
# 重写流程
- 1. redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令(即当前Redis中的数据的生成命令 set xx xx等)
- 2. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
- 3. 当子进程把快照内容写入以命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
- 4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
如果RDB和AOF同时开启,Redis启动时默认会使用AOF
,AOF保存数据更加完整安全
来源Redis.conf官方的解释
官方推荐两个都启用。如果对数据不敏感,可以选单独用RDB。不建议单独用 AOF,因为可能会出现Bug。如果只是做纯内存缓存,可以都不用。
两种持久化方案既可以同时使用(aof),又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定。
无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)
主节点无需额外进行配置
# 开启对外连接
bind 0.0.0.0
# 指定端口
port 6379
在REPLICATION
块中配置,可以配置多个从节点
# redis 5之后是replicaof 主节点ip 主节点Redis服务器的端口
replicaof 192.168.80.37 6379
# 注意:如果主节点的Redis服务器配置了密码 那么从节点这里一定要配置 如果主节点没有配置密码,那么这里注释即可
masterauth 主节点密码
经过测试,当主节点添加数据时,从节点自动的也有数据
默认从节点只能读,不能进行写操作
,可以通过修改从节点配置文件中的replica-read-only修改,不过基本不会修改,从节点只是用来当做同步主节点的数据,从节点不进行写操作 replica-read-only yes/no
哨兵可以设置多个,哨兵刚开始监控主节点,向主节点发送心跳检测,检测主节点是否还在运行,一旦半数以上的哨兵判断主节点已经挂掉了,那么就会从从节点中选举出一个节点作为主节点对外服务
哨兵的运行是基于主从复制的架构,所以要先搭建好主从复制的架构,参考上一节的配置
哨兵的配置
创建一个sentinel.conf(必须以此命名)
配置文件,首次就编写下面的内容即可,当整个架构运行起来后,后续的内容会自动填充
# 编写下面一行配置 前两个是固定写法,第三个是给此架构起一个名字,一般起mymaster
# ip是主节点的ip 端口是主节点的端口 1是至少有多少个哨兵同意进行选举的个数
sentinel monitor mymaster 192.168.80.37 7002 1
# 如果主节点有密码 必须配置密码
sentinel auth-pass mymaster redis
启动主从节点以及哨兵
注意:默认哨兵服务的端口号是26379
# 启动主从节点
进入bin目录下 运行redis-server命令
redis-server 主节点的配置文件目录/redis.conf
redis-server 从节点的配置文件目录/redis.conf
redis-server 从节点的配置文件目录/redis.conf
# 运行哨兵
默认bin目录下没有redis.sentinel脚本,在redis目录下的src下有,拷贝一份到bin目录下,然后启动
redis-sentinel /哨兵的配置文件目录/sentinel.conf
运行起来后,再看sentinel.conf,自动配置了一些关键的配置,比如端口号,从节点的ip端口等
sentinel myid 7e298931454708c4a4a6b3ac5977c279f6c684c0
# Generated by CONFIG REWRITE
port 26379
dir "/usr/local/redis/bin"
protected-mode no
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 192.168.80.37 7002 1
sentinel auth-pass mymaster redis
sentinel config-epoch mymaster 4
daemonize no
sentinel leader-epoch mymaster 9
sentinel known-replica mymaster 192.168.80.37 7001
sentinel known-replica mymaster 192.168.80.37 7000
sentinel current-epoch 9
测试
当所有的服务都启动完成后,将主节点手动shutdown掉,看到哨兵服务的日志打印,切换了一个从节点作为了主节点,测试了新的主节点是可以进行写操作的,将挂掉的主节点启动,发现哨兵的日志打印原主节点已经成为了新主节点的一个从节点(说明哨兵+主从复制架构搭建成功)
按照下面的三种顺序判断选举哪个从节点
选数据最全的
选runid小的
不在配置Redis的服务器ip和端口,只需配置哨兵的ip和端口即可
spring:
redis:
sentinel:
# sentinel.conf中配置的自定义集群名称
master: mymaster
# 是一个集合,可配置多个哨兵
nodes: [192.168.80.37:8123]
# Redis密码
password: redis
#Redis的密码
password: redis
总结
存在的问题
虽然哨兵解决了主从复制架构中的主节点挂掉后从节点无法对外服务的缺陷,但是仍然存在着整个结构只有一台服务进行工作的问题,在高并发情况下显然是会崩掉的,而且由于做持久化,久而久之AOF文件会越来越大,服务器迟早会崩掉,所有我们要做成集群的模式,多台Redis对外进行服务,实现高可用。
下一节,我们就来实现Redis的集群模式
为了解决及哨兵模式的不足,即无法进行数据的压力分担,即只有一台服务器对外服务,Redis自3.0开始引入了集群(Cluster)模式,支持主从+哨兵,支持数据分片,将数据分片在多台服务器上,实现了压力分担。
但是,redis的集群模式,跟普通的集群模式是不同的
1、 redis的集群通过客户端可以直接连接redis服务器,不用使用中间代理,比如我们在做Tomcat集群时必须使用Nginx进行负载均衡,将请求分发到Tomcat集群中,
2、 redis的集群可以直接每一台服务器(主节点)存储的数据是不同的,有分片的概念,将不同的数据根据规则存储到不同的redis服务器上,
3、 集群中的各个节点之间会进行ping-pong机制(内部使用了二进制协议优化了传输速度)
的心跳检测,如果超过一半的节点认为一个节点挂了,那么这个节点就是挂了,
所以建议搭建集群时,节点的个数最好是奇数个
上面也说了Redis的集群中的节点存储的数据时不同的,就是数据的分片,如何分片呢?,如何进行数据的寻找呢?
(这里说的节点个数不包括从节点,只包括对外提供服务的主节点)
,对这个槽位进行平均分配,比如下面如果我们有设置了三个节点,那么node1可能的槽位就是0-5461,node2可能是5642-10922,node3可能是10923-16383,然后客户端进行操作数据时先会对key进行CRC16的计算然后%16383
,计算出这个key所在槽位的节点,然后重定向到指定节点进行操作前提: 安装
ruby
环境,并使用redis-xxx.gem
yum源安装的ruby版本太老,建议使用安装包的形式,进行编译安装,建议使用ruby2.3.x
安装ruby出现问题
上传一个redis-xx.gem.tar.gz,解压并使用gem命令
gem install redis-xxx.gem
1、复制redis.conf分别到/root/redis_cluster/700n下,然后分别修改配置
这里是模拟集群,所以都在一台机器上创建,这里我们创建6个redis服务,三主三从
# 4.修改不同目录配置文件 这里为了简单不设置密码 注意修改一些文件输出文件的名字
- port 7000 ... //修改端口
- bind 0.0.0.0 //开启远程连接
- cluster-enabled yes //开启集群模式
- cluster-config-file nodes-port.conf //集群节点配置文件
- cluster-node-timeout 5000 //集群节点超时时间
- appendonly yes //开启AOF持久化
2、启动所有的redis服务
进入redis的bin目录
redis-server /xxx/redis.conf
.....
使用 ps -aux | grep redis 查看进程
发现每个redis后面都是cluster,即以集群模式启动
3、拷贝redis目录下的src目录下的redis-trib.rb脚本到bin目录
(4.x使用,5.x直接使用redis-cli即可)
cp /usr/local/redis/srcredis-trib.rb ../bin
4、执行脚本,将启动的所有redis服务都放到一个集群里
命令详解
参考
因为这里我用的是redis5.x ,官方推荐使用redis-cli命令
1、创建集群命令
redis-cli --cluster create
192.168.80.101:7000 192.168.80.101:7001 192.168.80.101:7002 192.168.80.101:7003 192.168.80.101:7004 192.168.80.101:7005
--cluster-replicas 1
# 1代表每个主节点配备几个从节点 然后会进行自动计算主节点及从节点
2、查看集群状态 (输入任意一个节点ip端口即可)
redis-cli --cluster check 192.168.80.101:7001
执行完之后,集群会创建完毕,主从节点都会自动标识,如果不满意,可以输入no,然后重新执行命令再次生成,输入yes即可使用集群
- 主节点
主节点存在hash slots,且主节点的hash slots 没有交叉,即都是顺序分配(xx-xxx)
主节点不能删除
一个主节点可以有多个从节点
主节点宕机时多个副本之间自动选举主节点,如主节点恢复则变为从节点
- 从节点
从节点没有hash slots
可以删除一个从节点
从节点不负责数据的写,只负责数据的同步
[新加节点] [原集群中的任意一个节点]
redis-cli --cluster add-node 192.168.80.101:7006 192.168.80.101:7005
注意
1.该节点必须以集群模式启动
2.默认情况下该节点就是以master节点形式添加
3.添加完主节点后此节点是没有slot的,需要重新的进行分片
# 1.添加从节点 新节点 集群任意节点
- ./redis-cli --cluster add-node 192.168.80.101:7006 192.168.80.101:7000 --cluster-slave
- 注意:
当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点添加当前副本节点
# 2.给指定的master节点添加主节点 新节点 集群中的任意节点
- ./redis-cli --cluster add-node 192.168.80.101:7006 192.168.80.101:7000 --cluster-slave --cluster-master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e (指定的节点id)
当添加了一个新的主节点时,此时这个新的主节点是没有slot的,需要进行重新分片
可以指定某些节点进行给指定的节点进行槽位移动,以及槽位的个数
[集群中的任意一个节点]
redis-cli --cluster reshard 192.168.80.101:7000
# 1.删除节点
[集群中任意节点]
- redis-cli --cluster del-node 192.168.80.101:7002 0ca3f102ecf0c888fc7a7ce43a13e9be9f6d3dd1 ([删除节点id])
- 注意:
1.被删除的节点必须是从节点或没有被分配hash slots的主节点
如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
(默认)
,那么 ,整个集群都挂掉`直接在配置文件中配置即可
spring:
redis:
cluster:
nodes:
#写上所有节点的ip端口
- 192.168.80.101:7000
- 192.168.80.101:7001
....
集群自带了主从+哨兵功能
,十分的强大
好处
不足
指查询一个不存在的key
,但是redis没有将null值写入缓存,导致所有的请求全去查询数据库,大量请求进来DB有可能崩掉
解决办法
1、对空值缓存(最简单)
如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
2、设置可访问的名单(白名单)
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
3、采用布隆过滤器
(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
4、进行实时监控:
当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
在某一时刻大量的key过期
,但是有大量的请求进来,因为这些key过期了,大量的请求全都去查询数据库,可能导致数据库崩掉
解决方案
1、构建多级缓存架构
nginx缓存 + redis缓存 +其他缓存(ehcache等)
2、使用锁或队列:
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
3、设置过期标志更新缓存:
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
4、过期时间给随机值(常用)
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
有一个热点key突然过期
,这时大量请求进来全部去查询数据库,可能导致数据库崩掉
解决方案
1、预先设置热门数据:
在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
2、实时调整:
现场监控哪些数据热门,实时调整key的过期时长
3、使用锁:(一般都是分布式锁 redisson等)
大量请求进来如果缓存失效,那么加锁只有一个线程去查询数据库,查到数据后将结果放入缓存,后面的线程直接从缓存中获取即可
word文档笔记