redis学习笔记

在这里插入图片描述

一、基础的知识

redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件;
redis默认有16个数据库(0~15),默认使用第一个(对应0);
在这里插入图片描述
可以使用select命令切换数据库,切换第三个数据库:
在这里插入图片描述

1、常用命令

插值 set key value
批量插值 mset k1 v1 k2 v2 kn vn
取值 get key
批量取值mget k1 k2 k3
组合命令 getset 先get再set

setex key seconds value
设置key对应字符串value,并设置key在给定的seconds时间后超时过期。这个命令等效于执行下面的命令:
SET mykey value
EXPIRE mykey seconds

setnx key value 单个
msetnx k1 v1 k2 v2 批量

key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。如果是批量的情况,那这个命令是一个原子性操作,要么一起成功要么一起失败;例:

在这里插入图片描述

截图解释:msetnx k1 v1 k3 v3 因为k1已经存在了,所以整个命令行是什么也不做,即使不存在K3,也不会去set k3 v3;要么都做,要么都不做;

查看所有keykey *
清空数据库 flushdb
清空所有数据库 flushall
将数据移出数据库 move key db (将key对应的数据移动到其他数据库db)
设置过期时间 expire key 10 (单位:秒)——实战:给登录的cookies设置过期时间
查看还剩多少时间过期 ttl key
查看类型 type key
查看是否存在key: exists key
返回随机的一个key randomkey
key也可以改名 rename oldkey newkey

更多命令 Redis命令中心(Redis commands) – Redis中国用户组(CRUG)****

2、为什么Redis是单线程的还这么快?

Redis读写效率极其高:读的速度是110000次/s,写的速度是81000次/s

误区:多线程一定比单线程效率高;其实不然,多线程也有很多弊端例如CPU上下文切换耗性能;

数据存取效率:CPU > 内存 > 硬盘

Redis的数据全部放在内存中,多次读写操作都是在一个CPU上,不会像多线程那样要切换CPU上下文(耗时操作),所以redis用单线程就是最佳方案;

Redis是基于内存操作,CPU不是Redis性能瓶颈,机器的内存和带宽影响性能;

二、Redis的数据类型

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)。

常见5中数据类型

1、String

使用场景:value是字符串或者整数

  • 基本的get、set
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6P3bACCF-1642065254702)(C:\Users\Mr.pan\AppData\Roaming\Typora\typora-user-images\image-20220113153451342.png)]

  • 动态的修改redis中存储的字符串:k1对应的值追加字符串”+hello“(字符串拼接);如果k1不存在,这条命令就等价于 set k1 +hello
    在这里插入图片描述

  • 获取字符串长度 strlen key

  • 截取字符串 [0,4] getrange k1 0 4 (截取全部:getrange k1 0 -1)
    在这里插入图片描述

  • 替换指定位置开始的字符 setrange k1 offset value
    在这里插入图片描述

  • 存储user对象——此时的Key是user:{$id}
    在这里插入图片描述

  • 存储user对象——此时key是user:{$ id}:{$fied}
    在这里插入图片描述

    自增命令 incr key (等价于Java中的i++)
    自减命令 decr key(等价于Java中的i–)
    按步长增 incrby key 10 (指定增量)
    按步长减 decrby key 10 (指定减量)

2、Redis Lists

应用场景:希望能非常快的在很大的列表上添加元素(快速访问不重要,快速访问是Redis sorted sets干的),例如消息排队、消息队列~

Redis lists基于Linked Lists实现。这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。用LPUSH 命令在十个元素的list头部添加新元素,和在千万元素list头部添加新元素的速度相同。

Redis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:

  1. 能非常快的在很大的列表上添加元素
  2. 能在常数时间取得常数长度。
# 从左边添加元素(右边同理)—当队列用
127.0.0.1:6379> lpush mylist one
(integer) 1
127.0.0.1:6379> LPUSH mylist two
(integer) 2
127.0.0.1:6379> lpush mylist three
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange mylist 0 0 #取栈顶元素(左边第一个)
1) "three"
# 更新指定下标的元素 lset key index value
# 往指定位置之前/之后插入元素 linsert key before/after pivot value
127.0.0.1:6379> lrange mylist 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
5) "two"
127.0.0.1:6379> linsert mylist before four four1
(integer) 6
127.0.0.1:6379> lrange mylist 0 -1
1) "four1"
2) "four"
3) "three"
4) "two"
5) "one"
6) "two"
127.0.0.1:6379> linsert mylist after four four0
(integer) 7
127.0.0.1:6379> lrange mylist 0 -1
1) "four1"
2) "four"
3) "four0"
4) "three"
5) "two"
6) "one"
7) "two"
# 删除元素
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> lpop mylist #删除左边第一个(list第一个)
"three"
127.0.0.1:6379> rpop mylist #删除右边第一个(list最后一个)
"four"
# 取指定下标的值-当数组用
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lindex mylist 0
"four"
127.0.0.1:6379> lindex mylist 2
"two"
127.0.0.1:6379> llen mylist # 获取list的长度
(integer) 4
# 删除指定的值
1) "two"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> lrem mylist 1 four # 精确删除指定个数的value : lrem key count value
(integer) 1
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem mylist 2 two
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
2) "one"
# 截取元素—会改变原本的list,截取后的list会覆盖原来的list
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "one"
3) "three"
4) "one"
127.0.0.1:6379> ltrim mylist 0 1
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "one"
127.0.0.1:6379>
# 组合命令rpoplpush 弹出左边第一个元素push到另一个list
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "one"
127.0.0.1:6379> rpoplpush mylist mylist2
"one"
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
127.0.0.1:6379> lrange mylist2 0 -1
1) "one"
3、Set

无序不重复集合

# 添加元素 sadd key members...
127.0.0.1:6379> sadd myset 1 2 3 4 5
(integer) 5

# 查看set所有元素
127.0.0.1:6379> smembers myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

# 移除指定元素
127.0.0.1:6379> srem myset 1 2
(integer) 2
127.0.0.1:6379> smembers myset
1) "3"
2) "4"
3) "5"

# 查看set中是否存在某元素 
127.0.0.1:6379> sismember myset 3 
(integer) 1

# 查看set集合元素个数 scard key
127.0.0.1:6379> scard myset
(integer) 3

# 随机抽取指定个数的元素(不指定个数默认随机取一个) srandmembers key [conut]
127.0.0.1:6379> smembers myset
1) "panwj"
2) "99"
3) "5"
4) "4"
5) "3"
6) "2"
7) "1"
127.0.0.1:6379> SRANDMEMBER myset 1
1) "panwj"
127.0.0.1:6379> SRANDMEMBER myset
"panwj"
127.0.0.1:6379> SRANDMEMBER myset
"3"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "99"
2) "1"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "99"
2) "2"

# 随机移除指定个数的元素 spop myset [count]

# 将指定元素从一个集合移动到另一个集合
127.0.0.1:6379> SMEMBERS myset2
1) "set2"
127.0.0.1:6379> SMEMBERS myset
1) "panwj"
2) "99"
3) "5"
4) "4"
5) "3"
127.0.0.1:6379> smove myset myset2 panwj
(integer) 1
127.0.0.1:6379> SMEMBERS myset2
1) "panwj"
2) "set2"

# 求差集 sdiff set1 set2 
# 求交集 sinter set1 set2
# 求并集 sunion set1 set2 
4、Hash (key-map)

是一个Map集合,key-value中的v类型是Map,key指的是hash类型集合的命名;本质上和String类型没多大区别

# set一个k-v get同理
127.0.0.1:6379> hset myhash filed1 panwj
(integer) 1

# set多个k-v get同理
127.0.0.1:6379> hmset myhash filed3 world filed4 world2
OK

# 删除键值对  hdel key field [field ...]
127.0.0.1:6379> hdel myhash filed1 filed2
(integer) 2

# 获取hash长度: hlen key
127.0.0.1:6379> hgetall myhash
1) "filed3"
2) "world"
3) "filed4"
4) "world2"
127.0.0.1:6379> hlen myhash
(integer) 2

# 获取hash中所有键值对
127.0.0.1:6379> hgetall myhash 
5) "filed3"
6) "world"
7) "filed4"
8) "world2"

# 获取hash中所有key
127.0.0.1:6379> hkeys myhash
1) "filed3"
2) "filed4"

# 获取hash中所有value
127.0.0.1:6379> hvals myhash
1) "world"
2) "world2"
5、Zset (有序集合 按key排序)
# 添加元素
127.0.0.1:6379> zadd myZset 1 "one" 2 "two" 3 three""
(integer) 3 

# 查看所有元素
127.0.0.1:6379> zrange myZset 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"

# 删除元素: zrem key member [member ...]
127.0.0.1:6379> zrem myZset one two
(integer) 2

# 获取有序集合中的个数
127.0.0.1:6379> zcard myZset
(integer) 3

# 获取满足范围的元素个数
127.0.0.1:6379> zcount myZset 0 2
(integer) 2

# 按key正序排序——只打印value
127.0.0.1:6379> zrangebyscore myZset -inf +inf
1) "one"
2) "two"
3) "three"

# 按key正序排序——打印键值对
127.0.0.1:6379> zrangebyscore myZset -inf +inf withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"

# 按key倒序排序——只打印value
127.0.0.1:6379> zrevrange myZset 0 -1
1) "three"
2) "two"
3) "one"

# 按key倒序排序——打印键值对
127.0.0.1:6379>  zrevrange myZset 0 -1 withscores
1) "three"
2) "3"
3) "two"
4) "2"
5) "one"
6) "1"

三、Redis基本的事务操作

1、redis事务

redis事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行的过程中所有命令会按顺序执行,具有一次性、顺序性、排他性(不受干扰)!

编译型异常——如果事务代码编译错误(命令写错),其他命令不会再执行

运行时异常——如果事务代码存在语法错误,其他命令不受影响(如除0操作, get不存在的值,字符串自增操作…),这种情况Redis 事务不支持事务回滚机制,只会返回当前命令执行的错误给客户端,并不会影响下面的命令的执行;

简而言之:redis事务不会检查程序员自己的错误!但会检查命令本身是否能编译通过

  • redis单条命令保证原子性,但是redis事务不保证原子性::Redis 事务不支持事务回滚机制,如果事务执行中出现了命令执行错误,只会返回当前命令执行的错误给客户端,并不会影响下面的命令的执行。
  • redis事务没有隔离级别的概念

redis的事务:

  • 开启事务(命令:multi)
  • 命令入队(直接敲命令回车即命令入队)
  • 执行事务(命令:exec)
#开启事务
127.0.0.1:6379> multi 
OK
# 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> mset k3 v3
QUEUED
127.0.0.1:6379> del k1
QUEUED
# 执行事务
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
4) OK
5) (integer) 1

如果命令入队后,又不想执行事务了,则可以执行放弃事务命令 discard

2、监控

悲观锁:很悲观,认为什么时候都会出问题,无论做什么都会加锁!

乐观锁:认为什么时候都不会出问题,所以不上锁;更新数据的时候去判断一下,在此期间是否有人修改过这个数据 =》 先获取版本version判断是否有过更新,然后再决定是否去更新

3、redis的乐观锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-72SzR7Ur-1644291866267)(C:\Users\Mr.pan\AppData\Roaming\Typora\typora-user-images\image-20220127172009882.png)]

如果发现事务执行失败,解决办法:

  • 先解锁:unwatch money
  • 重新上锁:watch money #获取最新的值

四、Jedis

我们要使用Java Api来操作Redis。Jedis是Redis官方推荐的的Java连接工具;

测试步骤:

  • 新建一个Maven项目,导入jedis依赖包;去mavenrepository官网上查找导入jedis依赖的代码块

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>
    
  • 测试连接

    Jedis jedis = new Jedis("127.0.0.1", 6379);
            System.out.println(jedis.ping());
    

然后就可以通过对象jedis调用redis的所有命令行进行操作;

五、springboot整合

新建一个空的springboot项目时,springboot初始化会默认导入操作redis的依赖;
springboot2.x开始,原来使用的jedis被替换成了lettuce?

  • jedis采用的是直连,多个线程操作是不安全的;多线程操作需要使用jedis 线程池,更像BIO模式
  • lettuce采用netty框架,实例可以在多个线程中共享,是线程安全的,更像NIO模式;
<!--操作redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

源码说明:springboot所有的配置类都会有一个自动配置类 如:RedisAutoConfiguration;

@Bean
@ConditionalOnMissingBean(
    name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    return new StringRedisTemplate(redisConnectionFactory);
}

所有的自动配置类都会有一个 properties类,如:RedisProperties

redis保存的对象必须要序列化,如果没序列化会报异常:DefaultSerializer requires a Serializable payload but received an object of type

自定义redisTemplate

/**
 * 编写自己的redisTemplate
 * @param redisConnectionFactory
 * @return
 */
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    // 为了开发方便,一般直接使用<String, Object>类型
    RedisTemplate<String, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);

    // jackson序列化方式配置
    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();

    // key采用String序列化方式
    template.setKeySerializer(stringRedisSerializer);
    // hash的key采用String序列化方式
    template.setHashKeySerializer(stringRedisSerializer);
    // value采用Jackson序列化方式
    template.setValueSerializer(jackson2JsonRedisSerializer);
    // hash的value采用Jackson序列化方式
    template.setHashValueSerializer(jackson2JsonRedisSerializer);

    template.afterPropertiesSet();
    return template;
}

六、redis配置文件详解

redis启动的时候,通过配置文件来启动

  1. 配置文件对大小写不敏感
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GoMtxfIB-1644291866269)(../../PicGo/img/image-20220208104448606.png)]

  2. 可以把其他配置文件包含进来
    在这里插入图片描述

  3. 网络配置

    bind 127.0.0.1 #如要绑定远程的话可以使用*通配符
    protected-mode yes
    port 6379
    
  4. 通用配置

    # 日志
    # 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 'stdout' can be used to force
    # Redis to log on the standard output.
    logfile "" #日志文件位置
    databases 16 #默认16个数据库
    
  5. 快照SNAPSHOTTING配置

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

    save 900 1 # 如果900s内,如果至少有1个key进行了修改,则进行持久化操作;下面两行同理
    save 300 10
    save 60 10000
    stop-writes-on-bgsave-error yes #持久化出错是否继续工作。
    rdbcompression yes # 压缩rdb文件,耗cpu
    rdbchecksum yes # 启动数据校验,耗CPU
    dbfilename dump.rdb # 数据持久文件名
    dir ./ # 指定数据持久化保存路径
    

redis是内存数据库,如果不持久化,那么数据断点即失

  1. REPLICATION 主从复制配置

  2. SECURITY 安全机制配置

    # 可以设置密码
    # requirepass foobared
    requirepass 1234556
    
  3. 内存管理 MEMORY MANAGEMENT

    # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory is reached. You can select among five behaviors:
    # 内存满了可以采取的几种机制
    # volatile-lru -> Evict using approximated LRU among the keys with an expire set.
    # allkeys-lru -> Evict any key using approximated LRU.
    # volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
    # allkeys-lfu -> Evict any key using approximated LFU.
    # volatile-random -> Remove a random key among the ones with an expire set.
    # allkeys-random -> Remove a random key, any key.
    # volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
    # noeviction -> Don't evict anything, just return an error on write operations.、
    # 翻译
    1.volatile-lru(least recently used):最近最少使用算法,从设置了过期时间的键key中选择空转时间最长的键值对清除掉;
    
    2.volatile-lfu(least frequently used):最近最不经常使用算法,从设置了过期时间的键中选择某段时间之内使用频次最小的键值对清除掉;
    
    3.volatile-ttl:从设置了过期时间的键中选择过期时间最早的键值对清除;
    
    4.volatile-random:从设置了过期时间的键中,随机选择键进行清除;
    
    5.allkeys-lru:最近最少使用算法,从所有的键中选择空转时间最长的键值对清除;
    
    6.allkeys-lfu:最近最不经常使用算法,从所有的键中选择某段时间之内使用频次最少的键值对清除;
    
    7.allkeys-random:所有的键中,随机选择键进行删除;
    
    8.noeviction:不做任何的清理工作,在redis的内存超过限制之后,所有的写入操作都会返回错误;但是读操作都能正常的进行;
    
    
    1. APPEND ONLY MODE (AOF)

      Redis持久化的两种方式:RDB和AOF

      • AOF:AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

        img

      • RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
        img

      redis默认使用rdb方式持久化,AOF默认是关闭的,在大部分情况下rdb完全够用;

      appendonly no
      
      # The name of the append only file (default: "appendonly.aof")
      
      appendfilename "appendonly.aof"
      

七、Redis持久化

1、持久化之RDB操作

RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
img

RDB默认配置:

save 900 1 # 如果900s内,如果至少有1个key进行了修改,则进行持久化操作;下面两行同理
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes #持久化出错是否继续工作。
rdbcompression yes # 压缩rdb文件,耗cpu
rdbchecksum yes # 启动数据校验,耗CPU
dbfilename dump.rdb # 数据持久文件名
dir ./ # 指定数据持久化保存路径

RDB触发机制

  • save命令 执行成功,触发RDB
  • 执行flushall命令,触发RDB
  • 退出redis,触发RDB

RDB触发会产生一个dump.rdb文件,redis启动时就会自动恢复rdb文件中的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vPro2KtL-1648795739206)(…/…/PicGo/img/image-20220208155858823.png)]

缺点

  1. RDB方式数据无法做到实时持久化。因为BGSAVE每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本比较高。
  2. RDB 文件使用特定二进制格式保存,Redis 版本升级过程中有多个格式的 RDB 版本,存在老版本 Redis 无法兼容新版 RDB 格式的问题
  3. 如果服务宕机,会丢失最后一次持久化之后修改的数据

2、持久化之AOF操作

AOF:以日志的形式记录执行的每一个写、删命令,redis重启时会重新执行AOF中的命令以达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性

img

img

redis默认使用rdb方式持久化,AOF默认是关闭的,在大部分情况下rdb完全够用;

appendonly no

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"

八、Redis发布订阅

Redis 发布订阅 | 菜鸟教程 (runoob.com)

使用场景:实时消息系统、实时聊天(频道即聊天室)、订阅关注系统

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

img

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

img

实例

以下实例演示了发布订阅是如何工作的,需要开启两个 redis-cli 客户端。

在我们实例中我们创建了订阅频道名为 runoobChat:

  • 第一个 redis-cli 客户端

    redis 127.0.0.1:6379**>** SUBSCRIBE runoobChat
    
    Reading messages... **(**press Ctrl-C to quit**)**
    1**)** "subscribe"
    2**)** "runoobChat"
    3**)** **(**integer**)** 1
    

现在,我们先重新开启个 redis 客户端,然后在同一个频道 runoobChat 发布两次消息,订阅者就能接收到消息。

  • 第二个 redis-cli 客户端

    redis 127.0.0.1:6379> PUBLISH runoobChat "Redis PUBLISH test"
    
    (integer) 1
    
    redis 127.0.0.1:6379> PUBLISH runoobChat "Learn redis by runoob.com"
    (integer) 1
    \# 订阅者的客户端会显示如下消息
     \1) "message"
    \2) "runoobChat"
    \3) "Redis PUBLISH test"
     \1) "message"
    \2) "runoobChat"
    \3) "Learn redis by runoob.com"
    

九、Redis主从复制

主从配置

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

主从复制的主要作用:
1、实现数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式;
2、利于故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余;
3、负载均衡:在主从复制的基础上,实现读写分离。Master以写为主,slave以读为主;分担服务器负载,适合读多写少的场景(例如商城上传一次商品顾客多次浏览)、大大提高了Redis服务器的并发量
4、读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;
5、高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础

默认每台redis服务器都是主机,主从配置可以在配置文件中进行:

replicaof <masterip> <masterport>  # 每台从机指定主机ip和端口Port

主从配置好之后,==主机只能写,从机只能读!==主机的数据会自动同步到从机中,这样主机宕机后可以使用从机;

注意:一台redis服务器既是master又是slave时,它还是只能写入不能读;

主从配置也可以在命令行中进行配置

slaveof 主机ip 主机端口

这种配置方式比配置文件配置方便,如果有服务宕机,直接命令行修改主从配置即可,避免频繁修改配置文件;

哨兵模式

哨兵负责监督主机和从机,哨兵之间也相互监督;如果确定主机宕机,就投票选择一个票数最高的从机成为新的主机;
工作原理:

  1. 每个sentinel(哨兵)以每秒一次的频率向它所监督的master、slave及其他的sentinel发送一个ping命令
  2. 如果一个master距离最后一次有效回复ping命令的的时间超过指定值,则这个master会被sentinel标记为主观下线,然后所有监视这个master的sentinel会以每秒一次的频率去确认该master是否真正进入主观下线了
  3. 如果有足够数量(由配置文件指定数量)的sentinel在指定的时间范围内认为master主观下线,则将该master标记为客观下线;若master重新向sentinel的ping命令返回有效回复,则master的主观下线状态被解除
  4. 哨兵节点会推选出哨兵leader负责故障转移
  5. 哨兵会推选出一个从节点担任新的主节点,并通知其他从节点更新主节点信息;

缓存穿透

指的是查询的数据没有击中缓存,由于缓存是不命中是被动写的,如果DB查不到则不写入缓存,这将导致这个不存在的数据每次请求都要到DB查询,失去了缓存的意义;查询不存在数据在流量大时,DB可能会挂掉;

解决办法:采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,查询不存在的数据会被这个bitmap拦截掉,从而避免了对DB的查询压力。

缓存击穿

大量的请求同时查询一个已失效的key,就会导致大量请求都落到DB上;击穿是查询已失效的key,而穿透是查询不存在的key

解决方法:加分布式锁,第一个请求的线程可以拿到锁,拿到锁的线程查询到了数据之后设置缓存,其他的线程获取锁失败会等待50ms然后重新到缓存取数据,这样便可以避免大量的请求落到数据库。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重挂掉。

解决方法:在原有的失效时间基础上增加一个随机值,使得过期时间分散一些。

十、redis面试题

  1. redis是什么?
    redis是一个高性能的非关系型的键值对数据库,与传统数据库不同的是,redis中的数据缓存于内存中的,所以读写速度非常快,被广泛用于缓存方向;存读的速度能达到110000次/s,写的速度是81000次/s;且redis的数据可以持久化到磁盘中,保证了数据的安全不丢失;

  2. redis优缺点?
    优点:

    1. 数据存于内存。读写速度快
    2. 基于单线程。避免线程切换开销和多线程的竞争;这里的单线程是指所有的网络请求都由一个线程来处理;但是redis持久化时需要另开一个线程;
    3. 支持数据持久化。支持AOF和RDB两种持久化方案,有效避免了数据的安全不丢失
    4. 支持事务。redis的操作命令是原子性的,支持几个命令合并后执行的原子性
    5. 支持多种数据类型,比如String、list、hash、set、zset等等
    6. 支持主从复制。主节点数据会自动同步到从节点,可以实现读写分离;

    缺点:

    1. 对结构化数据的查询的支持比较差
    2. redis数据库收到物理内存的限制,不适合用作海量数据的高性能读写,因此redis适合的场景主要局限于较小数据量的操作
    3. redis较难进行在线扩容
  3. redis为什么这么快?

    • 基于内存
    • 单线程实现
    • IO多路复用模型。Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
    • 高效的数据结构。Redis 每种数据类型底层都做了优化,目的就是为了追求更快的速度。
    • Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
  4. sortSet和list的区别?

    1. list是基于链表实现,存取两端元素速度快,访问中间元素慢;
    2. sortSet是基于散列表和跳跃表实现,访问中间元素快;
  5. 说一下redis的事务机制?

    redis的事务指的是将一个事务范围的若干命令发给redis,然后再让redis依次执行这些命令;redis的事务机制不能保证原子性,出现语法错误的命令(运行时异常)不会影响其他命令的执行(编译型错误会影响)

  6. 说一下持久化?

    • RDB恢复快、文件体积小、但是数据持久化不是实时性的、redis异常关闭会丢失数据
    • AOF回复慢、文件体积大、解决了数据持久化实时性
  7. Redis常见的部署方式有哪些?

    单机版:很少使用。存在的问题:1、内存容量有限 2、处理能力有限 3、无法高可用。

    主从模式:master 节点挂掉后,需要手动指定新的 master,可用性不高,基本不用。

    哨兵模式:master 节点挂掉后,哨兵进程会主动选举新的 master,可用性高,但是每个节点存储的数据是一样的,浪费内存空间。数据量不是很多,集群规模不是很大,需要自动容错容灾的时候使用。

    Redis cluster:主要是针对海量数据+高并发+高可用的场景,如果是海量数据,如果你的数据量很大,那么建议就用Redis cluster,所有主节点的容量总和就是Redis cluster可缓存的数据容量。

  8. 主从复制的原理?

    主从复制功能是支持多个redis数据库之间的数据同步。一个集群只能有一个主机,一个主机对应多个从机;主机数据发生变化时会自动同步给从机;主机一般只用于写操作,从机一搬只用于读操作;redis集群可以达到读写分离的效果;

    主从复制原理:

    1. 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的命令(除了查询命令);快照文件发送完毕后,开始向从服务器发送存储在缓冲区的命令
    2. 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,再接收主服务器发送来的命令
    3. 主服务器每执行一次命令(除了查询命令),就向从服务发送相同的命令

随着负载不断上升,主服务器可能无法很快地更新所有的从服务器,重新连接和重新同步都有可能导致主服务器超载;为了解决这个问题,可以创建一个中间层来分担主机的复制工作;中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器;

9.内存清理策略?

  1. 被动删除:访问key时,如果发现Key已过期,则删除
  2. 主动删除,定期清理过期的key
  3. 内存不够时使用内存淘汰策略进行内存释放
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值