Redis 学习笔记

redis 学习笔记

1 简介

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它支持存储的 value 类型相对更多,包括 String(字符串)、List(列表)、Set(集合)、Sorted Set(有序集合) 和 Hash(哈希)。在此基础上,Redis 支持各种不同方式的排序。为了保证效率,数据都是缓存在内存中。Redis 可以周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

2 安装

**官方下载地址:**https://redis.io/download

windos版本(长时间停更,不建议使用) 需要到github 下载

  1. 解压

    tar -zxf redis-4.0.9.tar.gz

  2. 安装gcc

    yum install gcc-c++  6.0 之后版本要求 需要升级 gcc到 9.0+

  3. 安装redis

    make

    make install

  4. 默认安装到 /usr/loccal/bin 下

  5. 拷贝配置文件 cp redis-config 到 自己指定文件夹下

  6. 修改daemonize 的值为 yes 改为后台启动

  7. 开启服务端 redis-server /指定的文件路径/redis.conf

  8. 开启客户端 redis-cli -p 6379 使用ping 测试连接

  9. 查看 redis 服务开启情况 ps -ef|grep redis

  10. 性能测试 redis-benchmark -p 6379 -c 100 -n 100000 含义:6379端口 100并发 100000数据

3 redis 基础知识

3.1 基础操作

  1. ping 测试命令
  2. set key value 设置值
  3. get key 获取值
  4. keys * 查看所有key
  5. exists key 判断是否存在
  6. expire key 10 设置10秒自动过期 ttl key 查看剩余时间
  7. type key 查看key的数据类型

3.2 数据库

  1. select 3 切换数据库(默认16个 0-15)
  2. dbsize 数据库数据个数
  3. flushdb 清空当前数据库
  4. flushall 清空所有数据库

3.3 redis 线程问题

6.0前只是单线程 6.0后支持多线程

不管是单线程或者是多线程都是为了提升Redis的开发效率,因为Redis是一个基于内存的数据库,还要处理大量的外部的网络请求,这就不可避免的要进行多次IO。好在Redis使用了很多优秀的机制来保证了它的高效率。

3.3.1 为什么早期是单线程的

为什么redis 单线程这么快? 官方:100000+的QPS 不比key-value 的Memecache

  1. 误区1:高性能服务器一定是多线程
  2. 误区2:多线程(cpu上下文操作)一定比单线程效率高

cpu->内存->硬盘的速度

redis数据都是存放在内存的,多线程cpu切换有性能损耗

(1)IO多路复用

一旦受到网络请求就会在内存中快速处理,由于绝大多数的操作都是纯内存的,所以处理的速度会非常地快。也就是说在单线程模式下,即使连接的网络处理很多,因为有IO多路复用,依然可以在高速的内存处理中得到忽略。

(2)可维护性高

多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题。单线程模式下,可以方便地进行调试和测试。

(3)基于内存,单线程状态下效率依然高

基于内存而且使用多路复用技术,单线程速度很快,又保证了多线程的特点。因为没有必要使用多线程。

3.3.2 为什么又引入多线程

读写网络的read/write系统调用在Redis执行期间占用了大部分CPU时间,如果把网络读写做成多线程的方式对性能会有很大提升。

Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想 Redis 因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题。

3.3.3 小结

Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率。

之前用单线程是因为基于内存速度快,而且多路复用有多路复用的作用,也就是足够了,现在引入是因为在某些操作要优化,比如删除操作,因此引入了多线程。

4 五大数据类型

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

4.1 Redis - Key

  1. set key value 设置值
  2. get key 获取值
  3. keys * 查看所有key
  4. exists key 判断是否存在
  5. expire key 10 设置10秒自动过期 ttl key 查看剩余时间

4.2 String(字符串)

  1. append key value 追加字符串
  2. strlen key 获取字符串长度
  3. incr key 加一
  4. decr key 减一
  5. Incrby key 10 设置步长,指定增量
  6. getrange key 0 4 截取 0-4 字符
  7. getrange key 0 -1 获取全部字符串 和 get key 一样
  8. setrange key 1 XX 替换指定位置开始的字符串
  9. setex key 30 “hello” (set with expire) 设置过去时间
  10. setnx key value (set if not exist) key不存在才可以创建,key存在创建失败
  11. mset k1 v1 k2 v2 k3 v3 批量设置 user:{id}:{filed} 巧妙的设计
  12. msetnx key不存在才可以创建,key存在创建失败 原子性的 一个失败都失败
  13. mset user:1:name zhangsan user:1:age 2 批量设置对象存储
  14. set user:2 {name:lisi,age:23} 设置对象 user:1 对象 用 json 保存一个对象
  15. getset key value 如果key存在设置新的值,不存在返回nil

string 使用场景 除了字符串可以是数字 可以做计数器、统计多单位的数量uid:{2323}:follow 0、粉丝数、对象缓存

4.3 List (集合)

list 可以完成 栈 队列 阻塞队列

所有命令都是L开头的

  1. LPUSH list one 将一个值或多个值插入列表头部 list 代表 集合的名字
  2. RPUSH list one 将一个值或多个值插入列表尾部
  3. Lrange list 0 -1 获取全部值 也可以通过区间获取具体值(Lrange list 0 1)
  4. LPOP list 将最左边的值移除
  5. Lindex list 0 获取下标为0的值
  6. Llen list 返回列表长度
  7. Lrem list 2 one 移除指定个数指定val的元素
  8. Ltrim mylist 1 2 通过下标截取 需要的值 list 被修改
  9. rpoplpush mylist otherlist 移除列表的最后一个元素到另一个新的列表中
  10. Lset list 0 item 修改第一个元素的值,这个值必须存在 不存在报错
  11. Linsert list before world other 在world 之前加一个other
  12. Linsert list after world other 在world 之后加一个other

小结

  • 实际它是个链表,before node after left right 都可以插值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在
  • 在两边插入改动效率最高

消息排队 、队列、栈

4.4 Set(集合)

set 中的值不能重复

所有命令都是s开头的

  1. sadd myset hello 添加值
  2. smembers myset 查看所有值
  3. sismember myset hello 判断一个值在myset是否存在
  4. scard myset 获取set集合中元素的个数
  5. srem myset hello 移除hello 元素
  6. srandmember myset 随机抽取一个元素
  7. srandmember myset 2 随机抽取指定个元素
  8. spop myset 随机弹出一个元素
  9. smove myset set hello 将hello 由myset 移动到set
  10. sdiff set1 set2 差集
  11. sinter set1 set2 交集 共同好友
  12. sunion set1 set2 并集

4.5 Hash (哈希)

Map集合 ,key-map 和string 类型没有太大区别

所有命令都是h开头的

  1. hset myhash field zhj 将一个键值对存入 myhash
  2. hget myhash field 得到值
  3. hmset myhash field1 hello field2 world 将多个值存入
  4. hmget myhash field1 field2 得到多个值
  5. hgetall myhash 获取全部map
  6. hdel myhash field1 删除hash 指定的key 对应的value也被删除
  7. hlen myhash 查看有多少键值对
  8. hexists myhash field1 判断hash指定字段是否存在
  9. hkeys myhash 获取所有的key
  10. hvals myhash 获取所有的value
  11. hincrby myhash field1 1 增加指定个数
  12. hsetnx myhash field2 hello 不存在可以设置,存在不能设置

应用 : 变更的值 user name zhj user age 22 尤其是经常变动信息的保存 更适合对象的存储#

Zset (有序集合)

set 的基础上加了一个值 set k v | zset k score v

所有命令都是z开头的

  1. zadd myset 1 one 添加一个值
  2. zadd myset 2 two 3 three 添加多个值
  3. zrangebyscore salary -inf +inf 排序 从小到大
  4. zrangebyscore salary -inf +inf withscores 排序 从小到大 并附带成绩
  5. zrevrange salary 0 -1 排序 从大到小
  6. zrange salary 0 -1 查看全部
  7. zrem salary xiaohoong 移除小红的
  8. zcard salary 获取有序集合中的个数
  9. zcount myset 1 3 获取指定区间的成员数量

案例思路: set排序 班级成绩、工资表

普通消息 重要消息 加权

排行榜 TOP 10

5 三种特殊类型

5.1 geospatial (地理位置)

朋友的定位,附近的人,打车距离

  1. geoadd china:city 116.40 39.90 beijing 添加(经度、纬度、城市) 一般通过java程序添加

    有效的经度-180到180 有效纬度 -85.05112878到85.05112878

  2. geopos china:city beijing 获取指定城市的经度纬度

  3. geopos china:city beijing shanghai 获取多个指定城市的经度纬度 得先存入

  4. geodist china:city beijing shanghai km 北京到上海的距离 单位km

    m 米 km 千米 mi 英里 ft 英尺

  5. georadius china:city 110 30 1000 km 110,30 附近1000km的城市

  6. georadius china:city 110 30 10000 km withdist 附近10000km的城市 和 据中心点的距离

  7. georadius china:city 110 30 10000 km withcoord 110,30 附近10000km的城市 和 坐标

  8. georadius china:city 110 30 10000 km withcoord count 1 110,30 附近10000km的城市 和 坐标 中选一个

  9. georadiusbymember china:city beijing 1500 km 距离北京1500km 内 的城市

  10. geohash china:city beijing shanghai 将两个地方的坐标转化为一维的字符串 字符串差值越小越近

  11. zrange china:city 0 -1 所有值

  12. zrem china:city beijing 移除元素

5.2 hyperloglog (基数统计)

基数统计算法

基数 不重复的元素

A{1,3,5,7,8,9} B{1,3,5,7,8} 基数 = 5 ,可以接受误差

网站 的 UV 一个人访问网站多次,但还是一个人

传统的方式 set 保存用户id 然后统计set中的元素数量作为标准判断 如果存大量id 就有问题 目的是为了计数而不是保存id

0.81% 的错误率 可以忽略不计

  1. PFadd mykey1 a b c d e f 存入
  2. PFcount mykey1 统计唯一的
  3. PFmerge mykey mykey1 mykey2 合并key1 key2 存到key PFcount mykey 统计并集中不同的个数

必须容许容错才可以用

5.3 bitmap (位图)

位运算 两种状态的 0 1 位图

疫情 : 01010110 统计疫情感染人数 | 活跃 不活跃 | 登录 不登录

  1. setbit sign 0 1 设置 sign 0 为 1
  2. getbit sign 1 查看 sign 1
  3. bitcount sign 统计sign 里为一的个数
127.0.0.1:6379> getbit sign 1
(integer) 1
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
127.0.0.1:6379> getbit sign 4

6 事物

Redis 事物 不保证原子性,没有隔离级别的概念 (单条保证原子性)

Redis 事物本质是: 一组命令的集合 所有命令都会被序列化,在事物执行过程中会顺序执行!

一次性 顺序性 所有事物中的命令只有在执行事物时才会执行

redis 的事物

  • 开启事物(multi)
  • 命令入队(set key val 输入一组命令)
  • 执行命令(exec 会按顺序执行) (discard 可以放弃事物 队列指令不会执行)

事物中出错

编译出错:代码有问题! 命令出错,所有都不会执行

运行时异常: 错误命令,报错 其它命令正常执行

7 监控 (面试常问)

7.1 悲观锁

很悲观,认为什么时候都会出现问题,什么时候都加锁 降低性能

7.2 乐观锁

很乐观,认为什么时候都不会有问题,不加锁!更新数据时会判断,是否有人动过数据,可以用一个version来验证

redis 的监控测试

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监视 money 对象
OK
127.0.0.1:6379> multi # 事物正常结束 数据期间没有发生变化
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> 
#################################################################
#### 客户端一
127.0.0.1:6379> watch money    # 监视money 相当于乐观锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
	#### 客户端二
	127.0.0.1:6379> get money
	"80"
	127.0.0.1:6379> set money 1000
	OK

127.0.0.1:6379> exec
(nil)

127.0.0.1:6379> unwatch  # 放弃监视 解锁
OK

#### 再来一次 加锁 执行事物  如果监视的对象不发生变化,事物执行成功

8 Jedis

Redis官方推荐的 java连接工具 使用java操作redis 必会工具

// 阿里云需要绑定私网ip 在redis配置文件中 才可以访问可以
public static void main(String[] args) {
    Jedis jedis = new Jedis("ip地址", 6379);
    // 之前的指令都是方法
    System.out.println(jedis.ping());
    
    System.out.println("清空数据: " + jedis.flushDB());
    System.out.println("设置name: " + jedis.set("name","zhj"));
    System.out.println("获得name: " + jedis.get("name"));
    
    jedis.close();
}

9 SpringBoot 整合 Redis

SpingBoot 整合 通过 SpringData 可以连接 Redis MongoDB JDBC

SpingBoot 2.x 之后 将原来的jedis 改为 lettuce

jedis: 采用直连,多个线程操作不安全,想避免使用jedis pool 连接池 !BIO

lettue: 采用Netty,实例可以在多线程共享,不存在线程不安全情况 ,更像 NIO

	@Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}     // 可以自定义 去替换默认的
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 默认的RedisTemplate 没有过多设置 redis对象都是需要序列化的
        // 两个泛型都是Object
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean  // 由于spring 使用频繁,单独提取一个模板
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 默认的RedisTemplate 没有过多设置 redis对象都是需要序列化的
        // 两个泛型都是Object
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        // 序列化配置
        // json 的 序列化
        Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        Jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(Jackson2JsonRedisSerializer);
        template.setHashValueSerializer(Jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }

}
package com.zhj.redis.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @PackageName:com.zhj.redis.utils
 * @ClassName:RedisUtil
 * @date:2020/7/29 0029 12:06
 **/
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }


    // ================================Map=================================

    /**
     * HashGet
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * HashSet 并设置时间
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }


    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将list放入缓存
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }

    }
}

10 Redis-conf 详解

启动的时候就是通过配置文件启动的

  1. 大小写不敏感

  2. 包含 include /path/to/local.conf

  3. bind 127.0.0.1 绑定ip 阿里云绑定内网ip

  4. protected-mode yes 保护模式

  5. port 6379 端口

  6. daemonize yes 以守护进程方式运行,默认是no,我们需要自己开启为yes

    pidfile /var/run/redis_6379.pid 如果我们以后台方式运行,我们就需要一个pid文件

  7. 日志

    # Specify the server verbosity level.
    # This can be one of:
    # debug (a lot of information, useful for development/testing)     #测试开发
    # verbose (many rarely useful info, but not a mess like the debug level)
    # notice (moderately verbose, what you want in production probably) #生产
    # warning (only very important / critical messages are logged)
    loglevel notice
    
    # Specify the log file name. Also the empty string can be used to force
    # Redis to log on the standard output. Note that if you use standard
    # output for logging but daemonize, logs will be sent to /dev/null
    logfile "" # 文件名,为空标准的输出
    
  8. database 16 数据库数量,默认16

  9. always-show-logo yes 是否总显示logo

  10. 持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb .aof

    redis 是内存数据库,如果没有持久化,断电数据丢失

    # 如果900s内,至少有一个key进行了修改,我们及进行持久化操作
    save 900 1
    # 如果300s内,至少有10个key进行了修改,我们及进行持久化操作
    save 300 10
    # 如果60s内,至少有10000个key进行了修改,我们及进行持久化操作
    save 60 10000
    
    # 我们之后学习持久化,会自定义这个测试																				
    stop-writes-on-bgsave-error yes #持久化如果出错,是否还需要继续工作
    
    rdbcompression yes  # 是否压缩rdb文件,会消耗cpu资源
    
    rdbchecksum yes # 保存rdb文件,进行错误校验
    
    dir ./ #rdb保存的目录
    
  11. 复制

  12. 安全

    config get requirpass # 得到密码 默认没有

    config set requirpass “123456” # 设置密码

    auth 123456 登录

  13. 限制

    maxclients 10000 # 设置能连接上redis的最大客户端数

    maxmemory # redis 配置最大内存容量

    maxmemooy-policy noeviction # 内存达到上线的处理策略

    1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)

    2、allkeys-lru : 删除lru算法的key

    3、volatile-random:随机删除即将过期key

    4、allkeys-random:随机删除

    5、volatile-ttl : 删除即将过期的

    6、noeviction : 永不过期,返回错误

  14. APPEND Only 模式

    appendonly no # 默认是不开启aof模式的,默认rdb方式持久化的,在大部分情况下rdb完全够用

    appendfilename “apppendonly.aof” #持久化文件名字

    appendfsync always # 每次修改都会sync,消耗性能

    appendfsync everysec # 每秒执行一次sync 可能会丢失这一秒数据

    appendfsync no # 不执行sync ,这个时候系统自己同步数据,速度最快

11 Redis 持久化

面试和工作持久化都是重点!

RDB 和 AOF 在指定时间间隔内将内存写入磁盘,恢复是将磁盘文件读入内存

Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据的完整性不是非常敏感,那么RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置。

11.1 RBD

RDB 保存的文件是 dump.rdb 生产环境会备份该文件

配置; save 60 5 # 只要60秒内修改了5次key,就会触发RDB规则

触发机制

  1. save规则满足的情况下,自动触发RDB规则
  2. flushall 命令 也会触发RDB规则
  3. 退出redis也会触发

恢复RDB文件

  1. redis 启动会自动检查dump.rdb文件,恢复数据
  2. 查看需要存放的位置 config get dir

优点:

  1. 适合大规模的数据恢复
  2. 对数据完整性要求不高

缺点:

  1. 需要一定时间间隔进行操作,如果redis意外宕机,会丢失最后一次数据
  2. fork进程的时候,会占用一定内存空间

11.2 AOF(Append Only File)

所有命令保留下来,相当于历史记录

AOF默认保存的是 appendonly.aof

以日志形式来记录每个操作,将redis执行过程记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文家重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次已完成数据恢复工作。

append only yes 默认是no不开启,改为yes启动
append filename "appendonly.aof" 持久化文件名字
appendfsync alawys 每次修改都会sync 消耗性能
appendfsync everysec 每秒一次sync ,可能会丢1秒数据
appendfsync no 不执行sync 系统自己同步数据,速度最快

# 重写规则  默认无限制增加
auto-aof-rewrite-percentage 100 
auto-aof-rewrite-min-size 64mb 大于64重写

优点:

  1. 每次修改都同步,文件的完整性会更好
  2. 每秒同步一次,可能会丢失1秒数据
  3. 从不同步,效率最高

缺点:

  1. 相对数据文件来说,aof远大于rdb,修复也比rdb慢
  2. aof运行慢

12 Redis 订阅发布

发布订阅(pub/sub) 线程之间通信 队列 生产者(发布者) 消费者 (订阅者)

  1. subscribe zhjpd 订阅消息
  2. publish zhjpd hello 发布消息

网络聊天室、及时通信、实时消息系统、订阅关注

复杂的用消息中间件做

13 Redis 主从复制

主从复制,将一台Redis服务器的数据,复制的其他Redis服务器,前者称为主节点(master/leader),后者称为从结点(slave/follower);数据的复制是单向的,只能由主节点到从结点。master以写为主,slave以读为主,

默认情况下每一台Redis服务器都是主结点,且一个主结点可以有多个或没有从结点,但一个从结点只能有一个主结点

主从复制的作用:

  1. 数据冗余:热备份,持久化的一种备份方式
  2. 故障恢复:主结点出现故障,从结点可以提供服务
  3. 负载均衡:配合实现读写分离,主结点提供写服务,从结点提供读服务
  4. 高可用

一般在项目中都会是集群,保证高可用,最少三台(一主二从)

原因:

  1. 结构:单个Redis服务容易发生故障,一台处理所有请求负载,压力巨大
  2. 容量:单个Redis服务容量有限,单台不应超过20G

主从复制,读写分离,80%的情况下是读操作,减缓服务器压力,架构经常使用,一主二从

13.1 集群搭建

  1. info replication 查看当前库信息

3个Redis服务

修改:端口号、pid、log、dump.rdb文件名

一主二从配置:从 slaveof 127.0.0.1 6379

13.2 主从复制之复制原理

主机写,从机读,主机的所有数据都会复制到从机 从机不可以写入值

测试:

  1. 主机断开连接,从机依旧连接到主机,但是没有操作,主机回来了,从机依旧可以直接获取到主机信息
  2. 使用命令行连接到主机的从机,掉了重启,就会变回主机,只要再次配置成从机,就可以立即拿到值

原理:

  1. slave 启动成功连接到master后会发送一个sync同步命令

  2. master 接到命令,启动后台的存盘进程,同时收集所接收到的用于修改的命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一次完全同步

  3. 全量复制:slave服务在接收到数据文件,将其存盘并加载到内存

  4. 增量复制:master继续将新的所有收集到的命令修改一次传给slave,完成同步

  5. 但是只要重新连接master,一次完全同步将自动执行

链式的

1主-----1从/2主-------2从

slaveof no one 主机断开让自己成为主机

宕机后手动配置:

14 哨兵模式详解

由于服务器宕机,需要手动重新配置,还会使服务在一段时间不可用,费时费力,所以一般情况下更推荐哨兵模式,redis 2.8 开始正式提供 Sentinel(哨兵)

自动版,检测到主机故障,自动将从库变成主库

哨兵模式是一种特殊模式,首先 Redis 提供了哨兵命令,哨兵是一个独立的进程,作为进程,他会独立运行,原理是哨兵通过发送命令,等到Redis服务响应,从而监控多个实例

正常配 多哨兵模式

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1的主观认为主服务器不可用,这个现象称主关下线。当后边的哨兵也检测到主服务不可用,并且数量到一定值时,那么哨兵之间就会进行一次投票,投票结果是由一个哨兵发起的,进行failover 【故障转移】操作。切换成功后,会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程叫客观下线

  1. 配置哨兵配置文件 sentinel.conf

    sentinel moitor 被监控名称 主机 端口号 1 代表主机挂掉投票,票数多的做主机

    sentinel moitor myredis 127.0.0.1 6379 1 # 哨兵模式最主要的配置

  2. 查看哨兵日志

  3. 主机回来,回归并从机下,当一个从机

优点:

  1. 哨兵集群,基于主从复制,所有的主从配置优点,它全有
  2. 主从可以切换,故障可以转移,系统可用性好
  3. 哨兵是主从模式的升级,手动变自动,更加易用

缺点:

  1. 不好在线扩容
  2. 实现哨兵配置很麻烦

哨兵配置:

# redis.conf
# 使得Redis服务器可以跨网络访问
bind 0.0.0.0
# 设置密码
requirepass "123456"
# 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
slaveof 192.168.11.128 6379
# 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
masterauth 123456


# 配置哨兵
# 在Redis安装目录下有一个sentinel.conf文件,copy一份进行修改
# 端口
port 26379

# 是否后台启动
daemonize yes

# pid文件路径
pidfile /var/run/redis-sentinel.pid

# 日志文件路径
logfile "/var/log/sentinel.log"

# 定义工作目录
dir /tmp

# 定义Redis主的别名, IP, 端口,这里的2指的是需要至少2个Sentinel认为主Redis挂了才最终会采取下一步行为
sentinel monitor mymaster 127.0.0.1 6379 2

# 如果mymaster 30秒内没有响应,则认为其主观失效
sentinel down-after-milliseconds mymaster 30000

# 如果master重新选出来后,其它slave节点能同时并行从新master同步数据的台数有多少个,显然该值越大,所有slave节点完成同步切换的整体速度越快,但如果此时正好有人在访问这些slave,可能造成读取失败,影响面会更广。最保守的设置为1,同一时间,只能有一台干这件事,这样其它slave还能继续服务,但是所有slave全部完成缓存更新同步的进程将变慢。
sentinel parallel-syncs mymaster 1

# 该参数指定一个时间段,在该时间段内没有实现故障转移成功,则会再一次发起故障转移的操作,单位毫秒
sentinel failover-timeout mymaster 180000

# 不允许使用SENTINEL SET设置notification-script和client-reconfig-script。
sentinel deny-scripts-reconfig yes

15 Redis 缓存穿透和雪崩

面试高频,工作常用,保证系统高可用

15.1 缓存穿透(查不到)

用户想查询一个数据,发现缓存中没有,也就是缓存没有命中,于是向持久层去查询,发现也没,于是本次查询失效。当用户很多的时候,缓存没有命中,都去请求持久层数据库,这就会造成很大压力,致使相当于缓存穿透。

解决方案

  1. 布隆过滤器

    布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式储存,在控制层先进行校验,不符合则丢弃,从而避免了底层存储系统的查询能力,

  2. 缓存空对象

    当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端的数据源

    存在两个问题:

    1. 如果空值被缓存起来,这就意味着需要更多的空间存储更多的键,因为这当中可能会有很多空值的键
    2. 即使对空值设置了过期时间,还是会存在缓冲层和存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响。

15.2 缓存击穿(量太大,缓存过期)

注意:缓存击穿和缓存穿透最大的区别是,缓存击穿是一个非常热的key,扛着超高的并发,大量并发集中一个点访问,当key失效瞬间,有大量请求并发访问,直接访问数据库,就相当于击穿了这堵墙

缓存过期期间,会同时访问数据库库来查询最新数据,并写回缓存,期间数据库压力过大

解决方案

  1. 设置热点数据不过期

  2. 加互斥锁

    分布式锁,使用分布式锁,保证每一个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁考验极大。

15.3 缓存雪崩

某一个时间段,缓存集中失效,Redis服务宕机

集中过期,倒不是最致命的,最怕服务器某个结点宕机或断网,因为自然形成的缓存雪崩,一定是某个时间段集中创建缓存,这个时候数据库也是可以顶住压力大,无非就是对数据库产生周期性的压力而已,而服务器宕机,对数据库服务的压力是不可预知的,可能瞬间压垮

停掉一些不必要的服务,为核心服务提供服务资源

解决方案

  1. redis 高可用

    异地多活,多设几台服务器

  2. 限流降级

  3. 数据预热

    正式访问前,先自己访问一遍,将热点数据放入缓存

尚硅谷是一个教育机构,他们提供了一份关于Redis学习笔记。根据提供的引用内容,我们可以了解到他们提到了一些关于Redis配置和使用的内容。 首先,在引用中提到了通过执行命令"vi /redis-6.2.6/redis.conf"来编辑Redis配置文件。这个命令可以让你进入只读模式来查询"daemonize"配置项的位置。 在引用中提到了Redis会根据键值计算出应该送往的插槽,并且如果不是该客户端对应服务器的插槽,Redis会报错并告知应该前往的Redis实例的地址和端口。 在引用中提到了通过修改Redis的配置文件来指定Redis的日志文件位置。可以使用命令"sudo vim /etc/redis.conf"来编辑Redis的配置文件,并且在文件中指定日志文件的位置。 通过这些引用内容,我们可以得出结论,尚硅谷的Redis学习笔记涵盖了关于Redis的配置和使用的内容,并提供了一些相关的命令和操作示例。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Redis学习笔记--尚硅谷](https://blog.csdn.net/HHCS231/article/details/123637379)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Redis学习笔记——尚硅谷](https://blog.csdn.net/qq_48092631/article/details/129662119)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值