redis快速入门

redis快速入门

一、redis简介

Redis 是一种开源(BSD 许可)内存中数据结构存储,用作数据库、缓存、消息代理和流引擎。Redis 提供数据结构,例如 字符串、散列、列表、集合、带范围查询的排序集、位图、超级日志、地理空间索引和流。Redis 具有内置复制、Lua 脚本、LRU 驱逐、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供高可用性。

您可以对这些类型运行原子操作 ,例如附加到字符串; 增加哈希中的值;将元素推入列表;计算集合的交、 并、差;或获取排序集中排名最高的成员。

为了实现最佳性能,Redis 使用 内存数据集。根据您的使用案例,Redis 可以通过定期将数据集转储到磁盘 或将每个命令附加到基于磁盘的日志来持久保存您的数据。如果您只需要功能丰富的网络内存缓存,您还可以禁用持久性。

Redis 支持异步复制,具有快速非阻塞同步和自动重新连接以及网络分割上的部分重新同步。

下面是官方的bench-mark数据:
测试完成了50个并发执行100000个请求。
设置和获取的值是一个256字节字符串。
结果:读的速度是110000次/s,写的速度是81000次/s

二、redis安装

下载redis linux版本

https://redis.io/download/

上传redis压缩包到创建的文件夹并进行解压

mkdir -p /usr/redis  #创建文件
cd /usr/redis
tar -zxvf redis-XXX.tar.gz #解压缩
cd redis-XXX #进入压缩包根目录
make  #执行c 预编译
make PREFIX=/usr/local/redis/  install #指定安装目录进行安装
cd /usr/local/redis/bin  #进入安装目录
cp /usr/redis/redis-xxx/redis.conf /usr/local/redis/redis.conf #复制压缩包配置文件到安装目录下
vim /usr/local/redis/bin/redis.conf #修改配置文件

如果未指定安装目录,默认安装在/usr/local/目录下

image-20230926114942029

image-20230926115012080

image-20230926115048991

image-20230926115333253

  • 启动命令
cd /usr/local/redis/bin/  #进入安装路径下
./redis-server   ./redis.conf  #根据配置文件启动redis
./redis-cli -h [主机Ip] -p [端口号:6379] -a [密码] #redis-cli进行服务连接

如果出现使用make预编译命令报错,先安装c语言编译环境。

yum -y install install gcc-c++ autoconf automake  #安装c语言编译依赖环境 

如果更改了配置文件需要重启使用以下命令

ps -ef|grep redis-server #查看redis-server的进程PID号
kill -9 [PID号]  #关闭进程
cd /usr/local/redis/bin 
./redis-server ./redis.conf #启动redis

三、关系型数据库与非关系型数据区别

关系型数据库是以二维表数据格式存储数据,非关系型数据库则以键值对格式存储,非关系型数据没有复杂的联表查询,是基于内存进行数据访问,查询效率比关系型数据库快,但是不适合海量数据可持久化存储,事务操作不实时,是执行最终事务操作,可以不按照ACID四大特性事务操作。

image-20230926135839038

四、redis常用命令

基本数据类型:strings(字符串)、lists(列表)、hash(哈希)、set(集合)、linkedset(有序集合)

  • strings常用命令
select [数据库序列0开始]  #选择数据库
set [key] [value] #设置键值对
get [key] #获取键值
mset [key1] [value1] [key2] [value2] [key3] [value3] ... #设置多个键值对
mget [key1] [key2] [key3] ... #获取多个键值
del  [key1] [key2] ... #删除一个或者多个键值对
keys * #查看所有保存的键值。
append [key] [value]  #追加键值字符串  如果键值不存在设置键值
set [key] [数值型] #设置初始值
incr [key] #自增一
decr [key] #自减一
incrBy [key] [数值] #设置自增步长
decrBy [key] [数值]  #设置自步长
getRange [key] [baginIndex] [endIndex] #获取[bgIndex,endIndex]值
msetnx k1 v1 k2 v2 #同时设置多个不存在的键值,一个失败全部失败,操作具有原子性。
#树形设置值
set [path1]:[path2]:[path3] [value] #设置一个树状值
get [path1]:[path2]:[path3]  #获取一个树状值
mset [path1]:[path2-1]:[path3-2] [value] [path1]:[path2-2]:[path3-2] [value] #设置多个树状值
mget [path1]:[path2-1]:[path3-2] [path1]:[path2-2]:[path3-2] #获取多个树状值
getset [key] [value] #存在key值就返回key值,然后vlue覆盖原来的key值。
  • lists常用命令
lpush [列表名] [值] ...  #从左插入一个或多个列表值 先插入的值往右排,后插入的值往左 列表索引左往右(0-n)
rpush [列表名] [值] ... #从右往左插一个或多个列表值 先插入的值往左排,后插入的值往右排
lrange [列表名] [startIndex] [endIndex] #获取列表的[sindex,eindex]值。
lpop [列表名] #移除列表的第一个元素
rpop [列表名] #移除列表的最后一个元素
lindex [列表名] [index] #获取列表的指定索引值
llen [列表名] #返回列表的长度
lrem [列表名] [count] [value] #删除指定个数的列表值 由于列表可能有个重复的值,从左往右删。
ltrim [列表名] [startIndex] [endIndex] #截取列表索引[sindex,eindex]值
rpoplpush [列表名] [原有列表] [新列表] #移动原有列表最后一个元素给新列表
exists [列表名] #判断列表是否存在
lset [列表名] [index] [value] #替换指定列表名的索引值,列表不存在的就报错。
linsert [列表名] before [列表存在的值] [new_value] #在指定列表值之前插入新值。
  • hash常用命令
hset [哈希名] [key] [value] #设置hash值
hget [哈希名] [key]  #获取hash值
hmset [哈希名] [key1] [value1] [key2] [value2] ...#设置多个哈希键值对。
hmget [哈希名] [key1] [key2] ... #获取多个哈希键值对
hgetall [哈希名] #获取哈希对象的所有键值对
hdel [哈希名] [key] #删除hash执行的键值对
hlen [哈希名] #查看hash对象的键值数量
hexists [哈希名] [key] #查看hash对象的键值是否存在
hkeys [哈希名] #获取所有hash对象的键名
hvals [哈希名] #获取所有hash对象的值
hincrby [哈希名] [key] [number] #指定hash字段的步长
hsetnx [哈希名] [key] [value] #如果hash不存在这个字段就设置value 如果存在则不能设置。
  • set常用命令(无序且不重复列表)
sadd [集合名] [值] #往集合中添加元素
smembers [集合名] #查看set集合的所有值
sismember [集合名] [value] #判断这个指定的value值是否在set集合中
srem [集合名] [值] #移除set集合中指定的value值
scard [集合名] #查看集合的长度
srandmember [集合名] [count] #随机抽取count个值
spop [集合名] #随机删除set集合中的值
smove [old_set] [new_set] [value] #将旧集合中的值移动到新的集合中。
sdiff [集合一] [集合二] #找出两个集合的差集
sinter [集合一] [集合二]  #找出两个集合的交集
sunion [集合一] [集合二] #找出set集合并集
  • sortedSet常用命令(有序且不重复列表)
zadd [集合名] [score1] [member1] [score2] [number2] ... #添加一个或者多个根据score值从小到大有序排列集合值
zrem [集合名] [number1] [number2] ...  #移除一个或者多个指定的集合值
zrangebyscore [集合名] -inf +inf #从小到大排序查看所有值
zrangebyscore [集合名] 0 -1 #从大到小排序查看所有值
zrangebyscore [集合名] 0 -1 withscores #从大到小排序查看所有值及score值
zcount [集合名] [min] [max] #获取指定score区间的成员数量
  • 设置有效时间
expire [key] [ttl] #设置键值对有效时间  单位为秒
pexpire [key] [ttl] #设置键值对有效时间  单位为毫秒
expireat [key] [timestamp] #用于将键值设置为指定的时间戳秒
pexpireat [key] [timeStamp] #用于将键值对设置为指定时间戳毫秒
ttl [key] #查看当前剩余有效时间  返回值-1表示永久有效 -2表示已失效 
  • 组合命令
set [key] [value] [nx|xx] [ex|px] [second|msecond]

image-20230926154325758

  • 通用命令
del [key]  #通用删除所有数据类型
hdel [哈希对象] [key] #删除指定哈希值
./redis-cli -h [ip] -p [port] -a [password] #登录用户名

五、jedis快速开发

Jedis环境搭建

  • 导入依赖
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <!--排除lettuce-->
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <!--导入jedis依赖-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
    </dependencies>

配置yaml核心配置文件

spring:
  redis:
    host: 192.168.147.110
    port: 6379
    password: root@123456
    lettuce:
      pool:
        max-active: 5 #最大激活个数5个
        max-idle: 100 #最大空闲连接数 100个
        min-idle: 5 #最小空闲连接数 5个
        max-wait: 100000ms #最大阻塞时间等待时间100s
    database: 0 #默认使用数据库0 #最大连接超时时间 10s
    connect-timeout: 10000ms #最大超时等待时间10s

编写配置类

@Configuration
public class redisConfig {
    @Value("${spring.redis.host}")
      private String host;
    @Value("${spring.redis.port}")
      private Integer port;
    @Value("${spring.redis.password}")
      private  String password;
    @Value("${spring.redis.database}")
      private  Integer database;
    @Value("${spring.redis.lettuce.pool.max-active}")
      private Integer maxAction;
    @Value("${spring.redis.lettuce.pool.max-idle}")
      private Integer maxIdle;
    @Value("${spring.redis.lettuce.pool.min-idle}")
      private Integer minIdle;
    @Value("${spring.redis.lettuce.pool.max-wait}")
      private String maxWait;
    @Value("${spring.redis.connect-timeout}")
      private String connTimeOut;
      @Bean
    public JedisPool getJedisFromJedisPool(){
          JedisPoolConfig config = new JedisPoolConfig();
          config.setMaxIdle(maxIdle);
          config.setMinIdle(minIdle);
          config.setMaxTotal(maxAction);
          config.setMaxWaitMillis(Long.valueOf(maxWait.substring(0,maxWait.length()-2)));
          JedisPool pool = new JedisPool(config, "192.168.147.110", 6379, Integer.valueOf(connTimeOut.substring(0,connTimeOut.length()-2)), "root@123456");
          return pool;
      }

}

序列化工具类

//序列化工具   字节数组流转为对象流
public class SerializeUtils {
     //序列化  字节数组流转为对象流
    public static byte[] serialize(Object o){
        ByteArrayOutputStream out=null;
        ObjectOutputStream obj=null;
        try {
            out=new ByteArrayOutputStream();
            obj= new ObjectOutputStream(out);
            obj.writeObject(o);
            byte[] byteArray = out.toByteArray();
            return byteArray;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if (obj != null) {
                    obj.close();
                }
                if (out != null) {
                    out.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }

    }

    public static Object deSerialize(byte[] bytes){
        if(bytes==null){
            return null;
        }
        ByteArrayInputStream bai = null;
        ObjectInputStream in=null;
        try {
            bai=new ByteArrayInputStream(bytes);
             in=new ObjectInputStream(bai);
            return  in.readObject();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if(bai!=null){
                    bai.close();
                }

                if(in!=null){
                    in.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }


    }

}

测试类

@SpringBootTest(classes = SpringBootApplicationStarter.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class TestDemo {
   @Resource
    private JedisPool  getJedisFromJedisPool;

    private Jedis jedis;

    @Before
    public void before(){
       jedis=getJedisFromJedisPool.getResource();
    }

    @Test
    public void test2(){
//        String ping = jedis.ping();
        //String 类型操作
        jedis.select(1);  //选中第二个数据库
        jedis.set("username","root");
        System.out.println(jedis.get("username"));
         jedis.mset("key1","value1","key2","value2","key3","value3");
        Set<String> keys = jedis.keys("*");
        Iterator<String> iterator = keys.iterator();
        while (iterator.hasNext()){
            List<String> mget = jedis.mget(iterator.next());
            mget.forEach(System.out::println);
        }

    }
    //列表  list
    @Test
    public void test3(){
        //左插入
     jedis.lpush("list1","张三","李四","王五","赵六");
//     //右插入
     jedis.rpush("list1","王麻子","王二狗","赵高");
        //获取列表元素个数
        System.out.println(jedis.llen("list1"));
        //删除指定的一个元素
        System.out.println(jedis.lrem("list1",1,"赵高"));
        //左弹出第一个元素
        System.out.println(jedis.lpop("list1"));
        //右弹出第一个元素
        System.out.println(jedis.rpop("list1"));
        //从左往右打印
        List<String> list1 = jedis.lrange("list1", 0, 6);
        list1.forEach(System.out::println);
    }
    //hash值操作
    @Test
    public void  test4(){
        jedis.select(3);  //选中第二个数据库
        Map<String,String> map=new HashMap<>();
        map.put("one","1");
        map.put("two","2");
        map.put("three","3");
        map.put("four","4");
        map.put("five","5");
        jedis.hset("hash",map);
        Set<String> strings = map.keySet();
        Iterator<String> iterator = strings.iterator();
        //打印所有存储的hash值
        while (iterator.hasNext()){
            String hash = jedis.hget("hash", iterator.next());
            System.out.println(hash);
        }
        //获取hash值的个数
        System.out.println(jedis.hlen("hash"));
        //删除指定的hash值
        System.out.println(jedis.hdel("hash","five"));
    }
    @Test
       //set集合操作  无序不重复
    public void  test5(){
        jedis.select(4);
        //添加集合
        jedis.sadd("sets","one","two","three","four","five","six","sever");
        //获取所有集合值
        Set<String> sets = jedis.smembers("sets");
        sets.forEach(System.out::println);
        //获取集合个数
        Long count = jedis.scard("sets");
        System.out.println(count);
        //删除指定的member
        jedis.srem("sets","one","two","three");

    }
     @Test
      //测试有序列表
    public void test6(){
        jedis.select(6);  //选中第七个数据库
         Map<String,Double> maps=new HashMap<>();
         maps.put("one",1D);
         maps.put("two",2D);
         maps.put("three",3D);
         maps.put("four",4D);
         maps.put("five",5D);
         maps.put("six",6D);
         //添加元素
       jedis.zadd("sortedSets",maps);
       //获取集合个数
         Long sortedSets = jedis.zcard("sortedSets");
         System.out.println(sortedSets);
         //获取集合个数  从score值从1到6
         Long zcount = jedis.zcount("sortedSets",1D,6D);
         System.out.println(zcount);
         //删除指定元素
         jedis.zrem("sortedSets","four");
         //遍历集合[0,4]
         Set<String> sortedSets1 = jedis.zrange("sortedSets", 0, 4);
         sortedSets1.forEach(System.out::println);
     }

     //测试redis层级关系
    @Test
     public void test7() {
      jedis.set("treeNode:node:item","one");
         String s = jedis.get("treeNode:node:item");
         System.out.println(s);
     }
     //设置key的有效时间
       @Test
       public void test8(){
        //对已经存在的key设置时间 到了失效时间 键值自动删除
//        jedis.expire("number",60);  //时间单位为秒
//           jedis.pexpire("list1",60000); //单位是毫秒
           Long member = jedis.ttl("list1");  //获取list有效时间  单位为秒
//           System.out.println(member);
//           System.out.println(jedis.get("member"));
           //当前库 key的数量
           Long l = jedis.dbSize();
           System.out.println(l);
           //获取当前数据库所有的key
           Set<String> keys = jedis.keys("*");
           keys.forEach(System.out::println);
       }
       //操作事务
        //只会操作一个键值的事务  以前成功执行的不受影响
         @Test
       public void test9(){
               //获取事务对象
             Transaction multi = jedis.multi();
             multi.set("username","admin"); //事务对象操作数据
             multi.exec();  //提交事务
             multi.discard();  //回滚事务
         }
        @Test
      public void test10(){
        //设置定时第二种写法  创建并定时 单位是秒
//            jedis.setex("time1",60,"定时任务!");
            //设置定时第二种写法  创建并定时 单位是毫秒
//            jedis.psetex("time1",60000,"定时任务!");
            //获取有效时间 60s
//            Long time1 = jedis.ttl("time1");
//            System.out.println(time1);
            //第三种写法
            SetParams params = new SetParams();
            params.ex(60); //设置时间  秒
            params.px(6000);////设置时间  毫秒
            params.nx();  //不存在就创建
//            params.xx();//存在就创建
            jedis.set("test","6666",params);

        }
        //对象序列化存储
          @Test
        public void testSerzlizable(){
              User user=new User();
              user.setUsername("root");
              user.setPassword("root");
              user.setAge(30);
              byte[] serialize = SerializeUtils.serialize(user);
              byte[] serializek = SerializeUtils.serialize("object");
              jedis.set(serializek,serialize);
              byte[] bytes = jedis.get(serializek);
              User o =(User) SerializeUtils.deSerialize(bytes);
              System.out.println(user);
          }

      @After
    public void  after(){
        if(jedis!=null){
            jedis.close();
        }
    }

}

六、可持久化机制

1、redis有三种可持久化方式:
一、执行完一次使用命令: bgsave
二、rdb规则持久化,更改配置文件持久化规则。
三、AoF规则持久化,需要在配置文件中开启。
2、三种可持久化的特点:
第一种,需要不断输入命令操作,非常麻烦,每执行一次读写操作,就要输入命令。第二种,需要有一定时间规则触发持久化,如果未到规则发生宕机,就会出现数据丢失,第三种可以实时保持持久化,但是如果操作读写频繁,持久化保存的数据大小过大,redis在恢复读取的时候非常慢,对于这三种持久化方案,要根据实际情况去使用。

命令行式保存 (在redis-cli连接中进行操作)

bgsave

配置文件更改持久化触发规则,默认是开启rdb持久化,会在redis程序当前文件夹产生dump.rdb文件。

image-20230927085905157

rdb持久化规则,默认是三个规则:到900s产生1次读写、到300s产生10次读写、到60s产生10000次读写就进行持久化

image-20230927090049031

AOF持久化规则:实时进行持久化,以appendonly.aof保存在当前文件夹,默认是关闭状态

image-20230927090531455

七、redis搭建主从复用

搭建集群是为了缓解主机的读取数据压力,因为redis一般用于读取保存不常修改的缓存数据,要是读取次数太多,会造成主机压力过大,因为搭建集群分担主机的读取数据压力。
原理:主机通过订阅模式实现与从机的数据同步,当有大量的并发访问量时,redis会通过特定算法把一部分访问量分担给从机进行操作。好比车站窗口既买票又取票,这样会造成人员拥堵,在窗口旁边设置自动取票机实现取票功能,这样就缓解了窗口的工作压力。
注意开放相应服务器的端口号,否则远程可视化工具无法连接!!!
firewall-cmd --add-port=端口号/tcp --permanent
  • 搭建集群步骤

先复制一个配置文件公用模板到redis-server当前目录config目录下,并复制三个模板文件

cp /usr/redis/redis-5.0.3/redis.conf  redis.comm.conf
cp  redis.comm.conf  config/redis-6379.conf
cp  redis.comm.conf  config/redis-6380.conf
cp  redis.comm.conf  config/redis-6381.conf

修改模板配置文件 (把解压包中的原始配置文件,复制并改名到自定义文件夹,一般改名为redis-comm.conf),如图以下配置

配置可以访问的redis的客户端IP,多个IP以空格隔开,前提要开启保护模式,默认是开启的

image-20230927113036415

image-20230927113205782

开启进程,默认是关闭的

image-20230927113250519

配置存放dump.rdb和appendonly.aof持久化文件的位置。 ./ 表示当前程序目录路径

image-20230927113455828

由于配置三个主机,每个主机需要区别每个主机的持久化文件,因此需要注释掉公共模板文件中的 dump.rdb 和 appendonly.aof,同时也要开启aof(appendonly yes)默认是关闭的。

image-20230927113906039

image-20230927114003111

配置访问主机的密码

image-20230927114305710

设置从机只读,主机可读可写

image-20230927114355744

设置主从机访问密码(三台机器全部是这个密码)

image-20230927114510970

注释掉日志存放位置,因为三台主机有不同的日志,需要配置不同日志文件

image-20230927114828579

注释掉端口号和pid进程,因为每个主机的端口号和进程文件不一样

image-20230927115149420

image-20230927115238989

在创建一个文件专门存放配置文件的 比如我在 程序文件目录下创建config文件存放并创建三个空文件

mkdir -p /usr/local/redis/bin/config/
cd /usr/local/redis/bin/config/
touch redis-6379.conf
touch redis-6380.conf
touch redis-6381.conf

配置文件配置信息如下(每个主机的配置文件只需要改相应的日志文件名、进程文件名、aof、rdb文件名即可,然后公共配置文件注意位置和文件名不要写错)

# 导入公共配置文件
include /usr/local/redis/bin/config/redis-comm.conf
# 日志等级
loglevel notice
# 日志文件
logfile "/usr/local/redis/bin/logs/rdis-6379.log"
# 端口号
port 6379
# 进程文件
pidfile /var/run/redis-6379.pid
# aof文件名 配合公共文件的dir 路径使用 前提要开启公共文件的appendonly true
appendfilename "appendonly-6379.aof"
# rdb文件名 配合公共文件的dir 路径使用
dbfilename dump-6379.rdb

运行程序

cd /usr/local/redis/bin #进入程序根目录
./redis-server  ./config/redis-XXX.conf #启动三个服务

八、哨兵模式

哨兵模式目的就是监听主机,如果主机发生宕机,在一定时间内未启动,哨兵模式通过一定算法重新选举出新的主机,避免主机发生宕机,让redis无法进行写入数据。
  • 搭建哨兵模式环境

从redis解压缩包中,复制sentinel.conf到自己创建的目录中并更改名字sentinel-comm.conf作为公共配置文件

cp /usr/redis/redis-**/sentinel.conf  /usr/local/redis/bin/config/sentinel-comm.conf
  • 并更改以下配置

由于有三个哨兵服务器,所有会有不同的端口,所以注释端口号

image-20230927140849931

开启后台进程

image-20230927140924617

为了区别不同哨兵服务器打印出来的日志文件,需要配置不同日志文件名,注释掉日志文件路径

image-20230927141045167

三个不同哨兵服务器有三个不同的服务进程名,需要进行注释

image-20230927141146543

设置监听的主机名、主机Ip、检查主机哨兵服务器个数,一般检查主机宕机的哨兵服务器个数要大于50%,默认设置2个。比如:设置3个哨兵服务器,需要设置2个,只有2个哨兵服务器检查发现主机宕机,才重新选举主机。

image-20230927141437419

设置主机的访问密码,这个密码要与主机配置文件中的requirepass值一致。

image-20230927141514380

默认设置发生主机宕机30s,未启动,就会重新选举主机 配置文件单位是ms

image-20230927141724868

默认设置发生主机连接阻塞超过3分钟,重新连接,配置文件单位是ms

image-20230927141955316

  • 配置三个哨兵服务器的配置文件

创建三个不同的配置文件

touch sentinel-26379.conf
touch sentinel-26380.conf
touch sentinel-26381.conf

配置文件内容 (全部使用绝对路径,各个配置文件只需要更改引入路径、端口号、进程文件名、日志文件名)

#引用公共配置
include /opt/redis/conf/sentinel-comm.conf
#进程端口号
port 26379
#进程编号记录文件
pidfile /var/run/sentinel-26379.pid
#日志记录文件(为了方便查看日志,先注释掉,搭好环境后再打开)
logfile "/opt/redis/log/sentinel-26379.log"

启动服务

cd /usr/local/redis/bin #进入程序根目录
./redis-sentinel  ./config/sentinel-XXX.conf #启动三个服务

退出进程

ps -ef|grep sentinel
kill -9 [pid号]

九、lettuce快速开发

lettuce和jedis区别:lettuce是线程安全,jedis是线程不安全!

导入依赖

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

二、核心配置文件

spring:
  redis:
    host: 192.168.147.110
    port: 6380
    password: root@123456
    lettuce:
      pool:
        max-active: 5 #最大激活个数5个
        max-idle: 100 #最大空闲连接数 100个
        min-idle: 5 #最小空闲连接数 5个
        max-wait: 100000ms #最大阻塞时间等待时间100s
    database: 0 #默认使用数据库0 #最大连接超时时间 10s
    connect-timeout: 10000ms #最大超时等待时间10s
    #sentinel:
     # master: mymasterOne  #主节点名称 要与配置文件中的主机名一致
     # nodes: 192.168.147.110:26379,192.168.147.110:26380,192.168.147.110:26381 #哨兵节点

三、配置类配置对象序列化

//配置实现对象序列化  解决所有数据类型存储序列化问题
@Configuration
public class ObjTemplate {
    @Bean
    public RedisTemplate<String,Object>  redisTemplate(RedisConnectionFactory conn){

        RedisTemplate<String,Object> template=new RedisTemplate<>();
        template.setConnectionFactory(conn);  //设置redis连接工厂
        //设置string数据类型序列化
        template.setKeySerializer(new StringRedisSerializer()); //键使用字符串序列化
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); //值使用通用序列化
        //设置hash数据类型序列化
         template.setHashKeySerializer(new StringRedisSerializer());
         template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

         return template;
    }
}

测试常用命令

@SpringBootTest
class SpringbootLettuceApplicationTests {
    //此类配置对象序列化也解决了乱码问题
    @Resource
    private RedisTemplate<String,Object> redisTemplate;
    //使用以下类解决字符串序列化问题
    @Resource
    private StringRedisTemplate  stringRedisTemplate;
    @Test
    void contextLoads() {
        ValueOperations<String,Object> strings = redisTemplate.opsForValue();
//        ValueOperations<String, String> strings = stringRedisTemplate.opsForValue();
//        strings.set("中文lettuce","lettuce");
//        Object s = strings.get("中文lettuce");
        //存取对象 前提要配置序列化配置类 存取对象不乱码
        User user=new User();
        user.setUid(10);
        user.setUsername("root");
        user.setPassword("root@123456");
        strings.set("User",user);
        Object o = strings.get("User");
        System.out.println(o);
    }
    //测试字符串
    @Test
    void testDemo(){
        ValueOperations<String, Object>  strings = redisTemplate.opsForValue();
//        strings.set("simpleString","String");
//        Object o = strings.get("simpleString");
//        System.out.println(o);
        Map<String,String> maps=new HashMap<>();
        //批量添加数据
        maps.put("one","1");
        maps.put("two","2");
        maps.put("three","3");
        maps.put("four","4");
        maps.put("five","5");
        strings.multiSet(maps);
        List<Object> list = strings.multiGet(redisTemplate.keys("*"));
        list.forEach(System.out::println);
        //删除单个
//        redisTemplate.delete("one");  //删除字符值
        List<String> ls=new ArrayList<>();
        ls.add("two");
        ls.add("three");
        //批量删除
        redisTemplate.delete(ls);
    }
     @Test
    //测试列表
    void test2(){
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.leftPush("listOne","1");  //单个左插入
        list.leftPushAll("listOne","one","two","three"); //批量左插入
        list.rightPush("listOne","4");
        list.rightPushAll("listOne","right1","right2","right3");  //批量右插入
        list.leftPop("listOne"); //左弹出
        list.rightPop("listOne"); //右弹出
        Long size = list.size("listOne");//获取集合的元素个数
        System.out.println(size);
        List<Object> one = list.range("listOne", 0, 3);
        one.forEach(System.out::println);  //遍历集合
        list.remove("listOne",1,"right2");  //删除指定的元素
    }
    //测试hash数据类型
    @Test
    void testHash(){
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.put("hashObj","one","1");  //添加单个hash值
        Map<String,Object> map=new HashMap<>();
        map.put("two","2");
        map.put("three","3");
        map.put("four","4");
        map.put("five","5");
        hash.putAll("hashObj",map);  //添加批量hash值
        Object o = hash.get("hashObj", "one");  //获取单个hash值
        System.out.println(o);
        hash.multiGet("hashObj", Arrays.asList(map.keySet().toArray()));  //批量获取hash值
        hash.delete("hashObj","one","two"); //删除一个或者多个数据
        Long count = hash.size("hashObj"); //获取hash值的个数
        System.out.println(count);
        Map<Object, Object> hashObj = hash.entries("hashObj");  //遍历map
        Set<Map.Entry<Object, Object>> entries = hashObj.entrySet();
        entries.forEach(System.out::println);  //遍历hash所有值
    }

    //测试set 集合
    @Test
   void setConn(){
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add("setDemo","张三","李四","王五","赵六"); //添加一个或多个
        set.remove("setDemo","赵六");//移除一个或者多个元素
        Long setDemo = set.size("setDemo");
        System.out.println(setDemo);  //获取set集合中元素个数
        Set<Object> setDemo1 = set.members("setDemo");//获取集合中所有元素
        setDemo1.forEach(System.out::println);
        System.out.println(set.isMember("setDemo", "李四"));//判断元素是否存在 存在返回true
        System.out.println(set.size("setDemo")); //获取个数
        Object s = set.pop("setDemo");  //左弹出一个 返回弹出元素
        System.out.println(s);

    }

    @Test
    void  testSortedSet(){
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add("sorted","张三",1);
        zset.add("sorted","李四",2);
        zset.add("sorted","王五",3);
        zset.add("sorted","赵六",4);
        zset.add("sorted","王麻子",5);
        zset.add("sorted","李涵",6);
        //获取score范围大小有多少个值 [2,6]
        Long count = zset.count("sorted", 2, 6);
        System.out.println(count);


        ZSetOperations.TypedTuple defaultTypedTuple = new DefaultTypedTuple("a",10D);
        ZSetOperations.TypedTuple defaultTypedTuple1 = new DefaultTypedTuple("b",11D);
        ZSetOperations.TypedTuple defaultTypedTuple2 = new DefaultTypedTuple("c",12D);
        ZSetOperations.TypedTuple defaultTypedTuple3 = new DefaultTypedTuple("d",13D);
        Set<ZSetOperations.TypedTuple<Object>> sets=new HashSet<>();
        sets.add(defaultTypedTuple);
        sets.add(defaultTypedTuple1);
        sets.add(defaultTypedTuple2);
        sets.add(defaultTypedTuple3);
        zset.add("sorted",sets);  //批量添加数据元素
        //根据索引值进行遍历集合值 从0开始
        Set<Object> sorted = zset.range("sorted", 1, 6);
        sorted.forEach(System.out::println);
        zset.remove("sorted","b","c"); //删除一个或者多个数据
        System.out.println(zset.zCard("sorted"));  //获取元素个数

    }
    //整合哨兵模式  只能读取数据 不能用于写数据
    @Test
    void select(){
        Object o = redisTemplate.opsForValue().get("User");
        System.out.println(o);
    }
      @Test
    void commonTest(){
          Set<String> keys = redisTemplate.keys("*");//获取当前数据库的所有key值
          keys.forEach(System.out::println);
            //操作事务
          redisTemplate.execute(new SessionCallback<List<Object>>() {

              @Override
              public List<Object> execute(RedisOperations operations) throws DataAccessException {
                  operations.multi();//开启事务
                  operations.opsForValue().set("事务1","a");
                  operations.opsForValue().set("事务2","b");
                  operations.opsForValue().set("事务3","c");
                  operations.opsForValue().set("事务4","d");
//                  operations.discard(); //事务回滚
                  operations.exec(); //事务提交
                  return null;
              }
          });
//          redisTemplate.discard();//回滚事务
          //为已存在的key设置定时任务  100s
          redisTemplate.expire("中文lettuce",100, TimeUnit.SECONDS);
          //为新增值设置定时任务  30s
//          redisTemplate.opsForValue().set("短信验证码","5698",30,TimeUnit.SECONDS);
          Long time = redisTemplate.getExpire("短信验证码");
          System.out.println(time);   //获取失效时间
          System.out.println(redisTemplate.opsForValue().setIfAbsent("empty", "is empty"));
      }


}

十、springboot整合哨兵模式

  • 两种配置方式

一、配置文件配置

spring:
  redis:
    host: 192.168.147.110
    port: 6380
    password: root@123456
    lettuce:
      pool:
        max-active: 5 #最大激活个数5个
        max-idle: 100 #最大空闲连接数 100个
        min-idle: 5 #最小空闲连接数 5个
        max-wait: 100000ms #最大阻塞时间等待时间100s
    database: 0 #默认使用数据库0 #最大连接超时时间 10s
    connect-timeout: 10000ms #最大超时等待时间10s
    sentinel:
      master: mymasterOne  #主节点名称 要与配置文件中的主机名一致
      nodes: 192.168.147.110:26379,192.168.147.110:26380,192.168.147.110:26381 #哨兵节点

二、配置类配置

//配置哨兵模式
    @Bean
    public RedisSentinelConfiguration sentinelConf(){
        RedisSentinelConfiguration conf=new RedisSentinelConfiguration()
//             设置主机节点名字
                .master("mymaster")
                //哨兵主从服务器
                .sentinel("192.168.147.110",26379)
                 .sentinel("192.168.147.110",26380)
                .sentinel("192.168.147.110",26381);
               conf.setPassword("root@123456");  //访问主机节点的密码
              return conf;
       }

三、哨兵模式只能读取数据,不能写数据,测试读取数据

 //整合哨兵模式  只能读取数据 不能用于写数据
    @Test
    void select(){
        Object o = redisTemplate.opsForValue().get("User");
        System.out.println(o);
    }

十一、redis过期淘汰机制

正常来说,redis设置的过期时间的key值,到了过期时间不会立即删除,需要访问这个数据的时候,判断这个数据是否过期失效,过期失效才会删除,倘若这个数据一直没有被访问,可能会一直留在redis中。
redis删除机制:
定期删除:redis每过100ms,就会随机检查一些key,检查数据是否过期失效,如果过期失效就会删除。
惰性删除:每访问一个数据,redis就会检查访问的数据是否过期失败,若是过期失效则进行删除。
内存淘汰机制:
volatile-lru :从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。(推荐使用)
volatile-ttl :从已设置过期时间的数据集中挑选将要过期的数据淘汰。
volatile-random :从已设置过期时间的数据集中任意选择数据淘汰。
allkeys-lru :当内存不足以容纳新写入数据时移除最近最少使用的key。
allkeys-random :从数据集中任意选择数据淘汰。
no-enviction(默认) :当内存不足以容纳新写入数据时,新写入操作会报错。

十二、缓存击穿

正常来说,用户先访问redis数据,数据不存在就访问db,然后查询到的db数据放入redis缓存中,返回给用户,但是如果在一个时刻,一个热门数据突然失效,一段时间有大量用户进行数据访问,会造成数据读取数据压力增大,但是不会发生宕机,会造成数据库周期性数据访问压力。

解决方案:

1、redis设置一个永不过期值,在缓存对象中定义一个过期时间标识,判断属性标识如果即将到过期时间,就发送一个异步请求访问DB,进行数据更新,但是可能会获取过期数据。

2、如果要求最新数据,redis设置热点数据永不过期,然后加一个互斥锁,redis进行更新数据读取,然后更新老旧数据。

十三、缓存穿透

如果用户恶意访问redis中不存在的数据,每次访问redis都不存在,就会每次访问DB,会造成缓存穿透。

解决方案:

1、利用互斥锁,用户访问数据库时先拿到锁才能访问,要是没拿到锁,不允许访问数据库,则休眠一段时间重试。

2、设置缓存值,不管用户是否能访问到缓存都返回一个值,缓存如果过期,就创建一个线程发送异步请求更新redis中的数据,但是需要先缓存预热,也就是启动项目就加载缓存。

3、设置一个过滤器,保存所有合法请求数据集合,如果用户访问数据,先判断请求请求是否合法,如果不合法就直接返回。

4、如果redis缓存数据不存在就保存空,只是设置较短的过期时间,到了过期时间再进行异步请求更新时间。

十四、缓存雪崩

如果一段时间内,大量的缓存集中失效,同时有大量用户访问redis,就会发生缓存击穿,直接访问DB,会造成DB数据库压力过大宕机。

解决方案:

1、给缓存数据随机加上过期时间值,避免集体失效。如果redis是集体部署,将热点数据均匀分布在不同的redis库中也能避免全部失效。

2、使用互斥锁,要拿到锁后才能访问数据库,设置访问时间,未到时间就休眠后重试,这样会造成吞吐量下降。

3、设置热点数据永远不过期。

4、双缓存,我们有两个缓存,缓存A和缓存B,缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。自己做缓存预热操作,细分以下几个小点:

a.从缓存A读取数据库,有则直接返回。

b.A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。

c.更新线程同时更新缓存A和缓存B。

十五、redis集群

在哨兵模式下,如果只有一个主机,主机突然宕机了,会在一段时间无法读写数据,在并发量很大的时候就是一个问题。

由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集。

Redis集群是一个提供在多个Redis节点间共享数据的程序集。

Redis集群可以支持多个Master。

image-20230927230409193

​ 哨兵模式发生主机宕机状态图示

image-20230927230304805

​ redis集群图示

posted @ 2023-10-02 13:53  戴莫先生Study平台  阅读(93)  评论(0编辑  收藏  举报