Redis基础

Redis

redis是一款高性能的NOSQL系列的非关系型数据库
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。

安装:
解压直接可以使用:
redis.windows.conf:配置文件
redis-cli.exe:redis的客户端
redis-server.exe:redis服务器端

redis的数据结构:
redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构
value的数据结构:
1) 字符串类型 string
2) 哈希类型 hash : map格式  
3) 列表类型 list : linkedlist格式。支持重复元素
4) 集合类型 set  : 不允许重复元素
5) 有序集合类型 sortedset:不允许重复元素,且元素有顺序

单机安装Redis

1,首先需要安装Redis所需要的依赖
yum install -y gcc tcl
2,将本地redis-6.2.4.tar.gz文件上传到/tmp路径
3,解压缩
tar -xvf redis-6.2.4.tar.gz
4,进入redis目录
cd redis-6.2.4
5,运行编译命令
make && make install
6,修改redis.conf文件中的一些配置
# 绑定地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问
bind 0.0.0.0
# 数据库数量,设置为1
databases 1
7,启动Redis
redis-server redis.conf
//Windows启动命令,shift+右击,打开power shell命令行
D:\Developer\redis-2.8.9> .\redis-server.exe .\redis.windows.conf
8,停止redis服务
redis-cli shutdown

redis持久化机制RDB

持久化
1. redis是一个内存数据库,当redis服务器重启,或者电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。
2. redis持久化机制:
	2.1. RDB:默认方式,不需要进行配置,默认就使用这种机制
	RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照

	RDB持久化在四种情况下会执行:
	执行save命令
		redis-cli
		save # 由redis主线程来执行RDB,会阻塞所有命令
		save命令会导致主进程执行RDB,这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。
	执行bgsave命令
		bgsave #开启子线程进行RDB,避免主线程受到影响
		这个命令执行后会开启独立进程完成RDB,主进程可以持续处理用户请求,不受影响。
	Redis停机时
		Redis停机时会执行一次save命令,实现RDB持久化。
	触发RDB条件时
		Redis内部有触发RDB的机制,可以在redis.conf文件中找到,格式如下:
		在一定的间隔时间中,检测key的变化情况,然后持久化数据。
		持久化的数据被保存在{redis_install_path}/dump.rdb文件中
		优点效率高,缺点可能数据丢失
			1. 编辑redis.windwos.conf(windows)/redis.conf(linux)文件
			# after 900 sec (15 min) if at least 1 key changed
			save 900 1
			# after 300 sec (5 min) if at least 10 keys changed
			save 300 10
			# after 60 sec if at least 10000 keys changed
			save 60 10000
			RDB的其它配置也可以在redis.conf文件中设置:
			# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
			rdbcompression yes
			# RDB文件名称
			dbfilename dump.rdb  
			# 文件保存的路径目录
			dir ./ 
			2. 重新启动redis服务器,并指定配置文件名称
			{redis_install_path}下cmd执行
			redis-server.exe redis.windows.conf
			或者在linux端重启
			
	

RDB原理

bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。

fork采用的是copy-on-write技术:
当主进程执行读操作时,访问共享内存;
当主进程执行写操作时,则会拷贝一份数据,执行写操作。
在这里插入图片描述
RDB方式bgsave的基本流程?
fork主进程得到一个子进程,共享内存空间
子进程读取内存数据并写入新的RDB文件
用新RDB文件替换旧的RDB文件

RDB会在什么时候执行?save 60 1000代表什么含义?
默认是服务停止时
代表60秒内至少执行1000次修改则触发RDB

RDB的缺点?
RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
fork子进程、压缩、写出RDB文件都比较耗时

redis持久化机制AOF

2.2. AOF:日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据(io写入日志,对性能影响比较大),持久化后,生成appendonly.aof文件
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。

优点,即使特殊情况,也能保证数据恢复。缺点,做增删改时性能较低,因为需要日志记录。

AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF
1. 编辑redis.windwos.conf(windows)/redis.conf(linux)文件
appendonly no(关闭aof) --> appendonly yes (开启aof)
# AOF文件的名称
appendfilename "appendonly.aof"

AOF的命令记录的频率也可以通过redis.conf文件来配
# appendfsync always : 每一次操作都进行持久化
appendfsync everysec : 每隔一秒进行一次持久化
# appendfsync no	 : 不进行持久化

三种策略对比
在这里插入图片描述

AOF文件重写

因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
在这里插入图片描述
如图,AOF原本有三个命令,但是set num 123 和 set num 666都是对num的操作,第二次会覆盖第一次的值,因此第一个命令记录下来没有意义。
所以重写命令后,AOF文件内容就是:mset name jack num 666
Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:

# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写 
auto-aof-rewrite-min-size 64mb

RDB与AOF对比

RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
在这里插入图片描述

Java客户端 Jedis

Jedis: 一款java操作redis数据库的工具

@Test
public void test(){
    //获取连接
    Jedis jedis = new Jedis("localhost", 6379);
    //操作

    //1,String存储
    jedis.set("username","zs");
    //获取
    String username = jedis.get("username");

    //可以使用setex()方法存储可以指定过期时间的 key value
    //将activecode:hehe键值对存入redis,并且20秒后自动删除该键值对
    jedis.setex("activecode",20,"hehe");

    //2,hash存储
    jedis.hset("user","name","zs");
    jedis.hset("user","age","23");
    jedis.hset("user","gender","male");
    //获取hash
    String name = jedis.hget("user", "name");
    System.out.println(name);
    //获取hash的所有map的数据
    Map<String, String> user = jedis.hgetAll("user");
    Set<String> keySet = user.keySet();
    for (String key : keySet) {
        String value = user.get(key);
        System.out.println(key+":"+value);
    }

    //3,list存储
    //从左边存
    jedis.lpush("mylist","a","b","c");
    //从右边存
    jedis.rpush("mylist","a","b","c");
    //list范围获取
    //[c, b, a, a, b, c]
    List<String> mylist = jedis.lrange("mylist", 0, -1);
    System.out.println(mylist);
    //list弹出,相当于删除元素
    String lpop = jedis.lpop("mylist");//c
    String rpop = jedis.rpop("mylist");//c
    System.out.println(lpop);
    System.out.println(rpop);

    //4,set 数据结构操作
    // set 存储
    jedis.sadd("myset","java","php","c++");
    // set 获取
    Set<String> myset = jedis.smembers("myset");
    System.out.println(myset);

    //5,sortedset 数据结构操作
    // sortedset 存储
    jedis.zadd("mysortedset",33,"亚瑟");
    jedis.zadd("mysortedset",30,"后羿");
    jedis.zadd("mysortedset",55,"孙悟空");

    // sortedset 获取
    Set<String> mysortedset = jedis.zrange("mysortedset", 0, -1);
    System.out.println(mysortedset);//[后羿, 亚瑟, 孙悟空]

    //关闭连接
    jedis.close();
}

/**
 * jedis连接池使用
 */
@Test
public void testJedisPool(){

    //0.创建一个配置对象
    JedisPoolConfig config = new JedisPoolConfig();
    config.setMaxTotal(50);
    config.setMaxIdle(10);
    //1.创建Jedis连接池对象
    JedisPool jedisPool = new JedisPool(config,"localhost",6379);
    //2.获取连接
    Jedis jedis = jedisPool.getResource();
    //3. 使用
    jedis.set("hehe","heihei");
    //4. 关闭 归还到连接池中
    jedis.close();;
}

/**
 * jedis连接池工具类使用
 */
@Test
public void testJedisPoolTool(){
    //获取连接
    Jedis jedis = JedisPoolUtils.getJedis();
    //使用
    jedis.set("JedisPoolTool","heihei");
    //关闭 归还到连接池中
    jedis.close();;

}

JedisPool工具类

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 JedisPool工具类
    加载配置文件,配置连接池的参数
    提供获取连接的方法

 */
public class JedisPoolUtils {

    private static JedisPool jedisPool;

    static{
	    //读取配置文件
	    InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
        //创建Properties对象
        Properties pro = new Properties();
        //关联文件
        try {
            pro.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //获取数据,设置到JedisPoolConfig中
        JedisPoolConfig config = new JedisPoolConfig();
     	config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
       	config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));
        //初始化JedisPool
        jedisPool = new JedisPool(config,pro.getProperty("host"),Integer.parseInt(pro.getProperty("port")));
    }
    /**
     * 获取连接方法
     */
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

Redis主从集群

在这里插入图片描述
共包含三个节点,一个主节点,两个从节点。
这里我们会在同一台虚拟机中开启3个redis实例,模拟主从集群,信息如下:

IPPORT角色
192.168.150.1017001master
192.168.150.1017002slave
192.168.150.1017003slave
1,准备实例和配置
创建三个文件夹,名字分别叫7001、7002、7003
# 进入/tmp目录
cd /tmp
# 创建目录
mkdir 7001 7002 7003

2,将redis-6.2.4/redis.conf文件拷贝到三个目录中(在/tmp目录执行下列命令)
# 方式一:逐个拷贝
cp redis-6.2.4/redis.conf 7001
cp redis-6.2.4/redis.conf 7002
cp redis-6.2.4/redis.conf 7003
# 方式二:管道组合命令,一键拷贝
echo 7001 7002 7003 | xargs -t -n 1 cp redis-6.2.4/redis.conf

3,修改每个实例的端口、工作目录
修改每个文件夹内的配置文件,将端口分别修改为7001、7002、7003,将rdb文件保存位置都修改为自己所在目录(在/tmp目录执行下列命令)
sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/tmp\/7001\//g' 7001/redis.conf
sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/tmp\/7002\//g' 7002/redis.conf
sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/tmp\/7003\//g' 7003/redis.conf

4,修改每个实例的声明IP
虚拟机本身有多个IP,为了避免将来混乱,我们需要在redis.conf文件中指定每一个实例的绑定ip信息,格式如下
# redis实例的声明 IP
replica-announce-ip 192.168.98.129
每个目录都要改,我们一键完成修改(在/tmp目录执行下列命令)
# 逐一执行
sed -i '1a replica-announce-ip 192.168.98.129' 7001/redis.conf
sed -i '1a replica-announce-ip 192.168.98.129' 7002/redis.conf
sed -i '1a replica-announce-ip 192.168.98.129' 7003/redis.conf
# 或者一键修改
printf '%s\n' 7001 7002 7003 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.98.129' {}/redis.conf

5,打开3个ssh窗口,分别启动3个redis实例,启动命令
# 第1个
redis-server 7001/redis.conf
# 第2个
redis-server 7002/redis.conf
# 第3个
redis-server 7003/redis.conf

6,开启主从关系
配置主从可以使用replicaof 或者slaveof(5.0以前)命令
有临时和永久两种模式:
修改配置文件(永久生效)
    在redis.conf中添加一行配置:slaveof <masterip> <masterport>
使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):
	slaveof <masterip> <masterport>
例如:
6.1,通过redis-cli命令连接7002,执行下面命令
# 连接7002
redis-cli -p 7002
# 执行slaveof
slaveof 192.168.98.129 7001
6.2,通过redis-cli命令连接7003,执行下面命令
# 连接7003
redis-cli -p 7003
# 执行slaveof
slaveof 192.168.98.129 7001
6.3,然后连接7001节点,查看集群状态
# 连接 7001
redis-cli -p 7001
# 查看状态
info replication

7,测试
利用redis-cli连接7001,执行set num 123
利用redis-cli连接7002,执行get num,再执行set num 666
利用redis-cli连接7003,执行get num,再执行set num 888
可以发现,只有在7001这个master节点上可以执行写操作,7002和7003这两个slave节点只能执行读操作

主从数据同步原理

全量同步

主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点,流程
在这里插入图片描述
master如何得知salve是第一次来连接呢??
有几个概念,可以作为判断依据:
Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据。

因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。
master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。
master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。
因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致

如图:
在这里插入图片描述
完整流程描述:
slave节点请求增量同步
master节点判断replid,发现不一致,拒绝增量同步
master将完整内存数据生成RDB,发送RDB到slave
slave清空本地数据,加载master的RDB
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
slave执行接收到的命令,保持与master之间的同步

增量同步

全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步
什么是增量同步?就是只更新slave与master存在差异的部分数据。如图:
在这里插入图片描述

repl_backlog原理

master怎么知道slave与自己的数据差异在哪里呢?
这就要说到全量同步时的repl_baklog文件了。
这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset:
在这里插入图片描述
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。
随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset,直到数组被填满。此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:
如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:
在这里插入图片描述
棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。

主从同步优化

主从同步可以保证主从数据的一致性,非常重要。
可以从以下几个方面来优化Redis主从就集群:
在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
主从从架构图:
在这里插入图片描述
全量同步和增量同步区别?
全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

什么时候执行全量同步?
slave节点第一次连接master节点时
slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

什么时候执行增量同步?
slave节点断开又恢复,并且在repl_baklog中能找到offset时

Redis哨兵

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
哨兵的结构如图
在这里插入图片描述
哨兵的作用如下:
监控:Sentinel 会不断检查您的master和slave是否按预期工作
自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

集群监控原理

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
在这里插入图片描述

集群故障恢复原理

一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
最后是判断slave节点的运行id大小,越小优先级越高。

当选出一个新的master后,该如何实现切换呢?
流程如下:
sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
在这里插入图片描述

搭建哨兵集群

搭建一个三节点形成的Sentinel集群,来监管之前的Redis主从集群。如图:
在这里插入图片描述
1,准备实例和配置
创建三个文件夹,名字分别叫s1、s2、s3

# 进入/tmp目录
cd /tmp
# 创建目录
mkdir s1 s2 s3

2,在s1目录创建一个sentinel.conf文件,添加下面的内容:

port 27001
sentinel announce-ip 192.168.98.129
sentinel monitor mymaster 192.168.98.129 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s1"

`port 27001`:是当前sentinel实例的端口
`sentinel monitor mymaster 192.168.150.101 7001 2`:指定主节点信息
  `mymaster`:主节点名称,自定义,任意写
  `192.168.150.101 7001`:主节点的ip和端口
  `2`:选举master时的quorum值

3,将s1/sentinel.conf文件拷贝到s2、s3两个目录中(在/tmp目录执行下列命令)

# 方式一:逐个拷贝
cp s1/sentinel.conf s2
cp s1/sentinel.conf s3
# 方式二:管道组合命令,一键拷贝
echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf

4,修改s2、s3两个文件夹内的配置文件,将端口分别修改为27002、27003

sed -i -e 's/27001/27002/g' -e 's/s1/s2/g' s2/sentinel.conf
sed -i -e 's/27001/27003/g' -e 's/s1/s3/g' s3/sentinel.conf

5,启动3个redis实例

# 第1个
redis-sentinel s1/sentinel.conf
# 第2个
redis-sentinel s2/sentinel.conf
# 第3个
redis-sentinel s3/sentinel.conf

6,测试
尝试让master节点7001宕机,查看sentinel日志,会选举7002或7003成为新的主节点,7001恢复后也将成为从节点。

RedisTemplate

在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。
1,导入Demo工程
2,项目的pom文件中引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3,配置Redis地址
然后在配置文件application.yml中指定redis的sentinel相关信息

spring:
  redis:
    sentinel:
      master: mymaster
      nodes:
        - 192.168.98.129:27001
        - 192.168.98.129:27002
        - 192.168.98.129:27003

4,配置读写分离
在项目的启动类中,添加一个新的bean

@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
    return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这个bean中配置的就是读写策略,包括四种:
MASTER:从主节点读取
MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
REPLICA:从slave(replica)节点读取
REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master

5,测试

http://localhost:8080/set/num/123
写操作在主集群上完成
http://localhost:8080/get/num
读操作在从集群上完成
实现了读写分离

搭建分片集群

1,实现扩容;2,分摊压力;3,无中心配置;
访问某一个master,set数据时,集群会根据{}key对应插槽值存储在相应的master节点中。

分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群,包含3个master节点,每个master包含一个slave节点,结构如下
在这里插入图片描述
在同一台虚拟机中开启6个redis实例,模拟分片集群

IPPORT角色
192.168.98.1297001master
192.168.98.1297002master
192.168.98.1297003master
192.168.98.1298001slave
192.168.98.1298002slave
192.168.98.1298003slave

1,创建出7001、7002、7003、8001、8002、8003目录

# 进入/tmp目录
cd /tmp
# 删除旧的,避免配置干扰
rm -rf 7001 7002 7003
# 创建目录
mkdir 7001 7002 7003 8001 8002 8003

2,在/tmp下准备一个新的redis.conf文件

port 7001
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /tmp/7001/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /tmp/7001
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 192.168.98.129
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/7001/run.log

3,将这个文件拷贝到每个目录下

# 进入/tmp目录
cd /tmp
# 执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf

4,修改每个目录下的redis.conf,将其中的7001修改为与所在目录一致

# 进入/tmp目录
cd /tmp
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf

5,后台启动

# 进入/tmp目录
cd /tmp
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf

6,ps查看状态

ps -ef | grep redis

如果要关闭所有进程,可以执行命令
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown

reids-cli -p 7002 shutdown

7,执行命令来创建集群

使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中
redis-cli --cluster create --cluster-replicas 1 192.168.98.129:7001 192.168.98.129:7002 192.168.98.129:7003 192.168.98.129:8001 192.168.98.129:8002 192.168.98.129:8003

命令说明:
`redis-cli --cluster`或者`./redis-trib.rb`:代表集群操作命令
`create`:代表是创建集群
`--replicas 1`或者`--cluster-replicas 1` :指定集群中每个master的副本个数为1,此时`节点总数 ÷ (replicas + 1)` 得到的就是master的数量。因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master

8,查看集群状态

redis-cli -p 7001 cluster nodes

散列插槽

Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
在这里插入图片描述
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
key中不包含“{}”,整个key都是有效部分

例如:key是num,那么就根据num计算,如果是{ben}num,则根据ben计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
在这里插入图片描述
如图,在7001这个节点执行set a 1时,对a做hash运算,对16384取余,得到的结果是15495,因此要存储到7003节点。
到了7003后,执行get num时,对num做hash运算,对16384取余,得到的结果是2765,因此需要切换到7001节点

Redis如何判断某个key应该在哪个实例?
将16384个插槽分配到不同的实例
根据key的有效部分计算哈希值,对16384取余
余数作为插槽,寻找插槽所在实例即可

如何将同一类数据固定的保存在同一个Redis实例?
这一类数据使用相同的有效部分,例如key都以{typeId}为前缀

集群伸缩

redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看

需求:向集群中添加一个新的master节点,并向其中存储 num = 10
启动一个新的redis实例,端口为7004
添加7004到之前的集群,并作为一个master节点
给7004节点分配插槽,使得num这个key可以存储到7004实例
1,创建新的redis实例

1,创建一个文件夹
mkdir 7004
2,拷贝配置文件
cp redis.conf /7004
3,修改配置文件
sed /s/6379/7004/g 7004/redis.conf
4,启动
redis-server 7004/redis.conf

2,添加新节点到redis
在这里插入图片描述

1,添加节点
redis-cli --cluster add-node  192.168.98.129:7004 192.168.98.129:7001,
2,通过命令查看集群状态
redis-cli -p 7001 cluster nodes
7004加入了集群,并且默认是一个master节点
但是,可以看到7004节点的插槽数量为0,因此没有任何数据可以存储到7004上

3,转移插槽
我们可以将0~3000的插槽从7001转移到7004,命令格式如下

3.1,建立连接
redis-cli --cluster reshard 192.168.98.129:7001
3.2,询问要移动多少个插槽,我们计划是3000个
3.3,那个node来接收这些插槽?
显然是7004,查看7004节点的id
3.4,source node #1 你的插槽是从哪里移动过来的?
填写7001的id
3.5,source node #2? done
3.6,确认要转移吗?输入yes
3.7,查看结果
reids-cli -p 7001 cluster node

分片集群redis.conf中
当cluster-require-full-coverover为yes,并且某段插槽主从都挂掉,那么整个集群都挂掉。
当cluster-require-full-coverover为no,并且某段插槽主从都挂掉,那么该段插槽数据不能使用也不能存储。

故障转移

自动故障转移

1,直接停止一个redis实例,例如7002
redis-cli -p 7002 shutdown
2,首先是该实例与其它实例失去连接,然后是疑似宕机
3,最后是确定下线,自动提升一个slave为新的master
4,当7002再次启动,就会变为一个slave节点了

手动故障转移

在这里插入图片描述
需求**:在7002这个slave节点执行手动故障转移,重新夺回master地位
步骤如下:
1)利用redis-cli连接7002这个节点
2)执行cluster failover命令
如图:
在这里插入图片描述效果:
在这里插入图片描述

RedisTemplate访问分片集群

RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
1)引入redis的starter依赖
2)配置分片集群地址
3)配置读写分离
与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下

spring:
  redis:
    cluster:
      nodes:
        - 192.168.98.129:7001
        - 192.168.98.129:7002
        - 192.168.98.129:7003
        - 192.168.98.129:8001
        - 192.168.98.129:8002
        - 192.168.98.129:8003

springBoot整合redis

1、创建 Maven工程

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.5.RELEASE</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
  </dependency>

  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
  </dependency>

  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
  </dependency>
</dependencies>

2、创建实体类,实现序列化接口,否则无法存入 Redis 数据库

import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class Student implements Serializable {
    private Integer id;
    private String name;
    private Double score;
    private Date birth;
}

3、创建控制器

import cn.itcast.redisdemo.pojo.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

@RestController
public class StudentController {

    @Autowired
    private RedisTemplate redisTemplate;


    @PostMapping("/save/{key}")
    public String save(@PathVariable("key")  String key, @RequestBody Student student) {
        redisTemplate.opsForValue().set(key, student);
        return "success";
    }

    @GetMapping("/get/{key}")
    public Student get(@PathVariable("key") String key){
        return (Student) redisTemplate.opsForValue().get(key);
    }

    @DeleteMapping("/delete/{key}")
    public boolean delete(@PathVariable("key") String key){
        redisTemplate.delete(key);
        return redisTemplate.hasKey(key);
    }
}

4、创建配置文件 application.yml

spring:
  redis:
    database: 0
    host: 192.168.98.128
    port: 6379

5、postman发送请求

存数据post
http://localhost:8080/save/stu1
requestbody:{
    "id":1001,
    "name":"张三",
    "score":96.5,
    "birth":"2023-03-29"
}
取数据get
http://localhost:8080/get/stu1

redis存储字符串、列表、集合、有序集合、hash

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Set;

@RestController
public class StudentController {
    @Autowired
    private RedisTemplate redisTemplate;
	//字符串
    @PostMapping("/saveStr/{key}")
    public String saveStr(@PathVariable("key")  String key) {
        redisTemplate.opsForValue().set(key, "hello world");
        String str = (String) redisTemplate.opsForValue().get(key);
        return str;
    }
	//列表
    @PostMapping("/saveList")
    public List saveList() {
        //存数据
        ListOperations<String,String> listOperations = redisTemplate.opsForList();
        listOperations.rightPush("list","v1");
        listOperations.rightPush("list","v2");
        listOperations.rightPush("list","v3");
        //取数据
        List list = redisTemplate.opsForList().range("list", 0, 2);
        return list;
    }
	//set集合
    @GetMapping("/set")
    public Set<String> setTest(){
        //存数据
        SetOperations<String,String> setOperations = redisTemplate.opsForSet();
        setOperations.add("set","Hello");
        setOperations.add("set","Hello");
        setOperations.add("set","World");
        setOperations.add("set","World");
        setOperations.add("set","Java");
        setOperations.add("set","Java");
        //取数据
        Set<String> set = setOperations.members("set");
        return set;
    }
	//有序set集合
    @GetMapping("/zset")
    public Set<String> zsetTest(){
        ZSetOperations<String,String> zSetOperations = redisTemplate.opsForZSet();
        zSetOperations.add("zset","Hello",1);
        zSetOperations.add("zset","World",2);
        zSetOperations.add("zset","Java",3);
        Set<String> set = zSetOperations.range("zset",0,2);
        return set;
    }
	//hash存储的hashmap
    @GetMapping("/hashTest")
    public String hashTest(){
        HashOperations<String,String,String> hashOperations = redisTemplate.opsForHash();
        hashOperations.put("redisk1","k1","v1");
        return hashOperations.get("redisk1","k1");

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于学习Redis基础知识,可以按照以下思路进行学习: 1. 了解Redis的概念和特点:首先需要了解Redis是什么,它的主要特点是什么,它为什么被广泛应用于缓存、消息队列、会话管理等场景。 2. 安装和配置Redis:根据你的操作系统,安装Redis并进行相关配置。可以参考Redis官方文档或其他教程来完成这一步。 3. 学习Redis的数据结构:Redis支持多种数据结构,如字符串、哈希、列表、集合和有序集合等。了解每种数据结构的特点、用途和操作命令,并通过实际操作来加深理解。 4. 掌握Redis的常用命令:学习Redis的常用命令,如get、set、hget、hset、lpush、lrange、sadd、smembers等,了解每个命令的具体用法和参数含义。 5. 理解Redis的持久化机制:了解Redis的RDB和AOF两种持久化方式,以及它们的优缺点。学习如何进行备份和恢复数据。 6. 学习Redis的事务和Lua脚本:了解Redis事务的基本概念和使用方法,以及如何使用Lua脚本来进行复杂的操作。 7. 深入了解Redis的性能优化和高可用方案:学习如何优化Redis的性能,包括配置调优、使用合适的数据结构、合理地使用缓存等。同时了解Redis的高可用方案,如主从复制、哨兵模式和集群模式。 8. 学习Redis与其他技术的结合:了解Redis如何与其他技术进行结合,如与Python、Java等编程语言的配合使用,以及与Spring、Django等框架的整合。 以上是学习Redis基础知识的一个思路,你可以根据自己的实际情况和需求进行学习和拓展。推荐参考一些经典的Redis教程和实战案例,通过实际操作和项目实践来提升自己的技能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值