redis笔记

182 阅读23分钟

Redis学习笔记

​ 整理博客版本,之前自己写博客的时候是在CSDN上的,但是后面觉得不喜欢它的界面风格(广告还多),所以准备边迁移边整理,把之前在那里的博客都迁移到掘金上来。

一、NoSQL数据库简介

“NOT ONLY SQL”意为不仅仅是数据库,指的是非关系型数据库,以简单的key-value的模式存储。不支持事务,远超SQL的性能。

二、redis数据库简介

Memcashed简介:

  • 很早出现的NoSql数据库
  • 数据都在内存中,一般不支持持久化
  • 只支持支持简单的key-value模式
  • 一般是作为缓存数据库辅助持久化的数据库

redis数据库简介:

  • 几乎覆盖了Memcashed的绝大部分功能
  • 数据都在内存中,支持持久化,主要用作备份恢复
  • 除了支持简单的key-value模式,还支持多种数据结构存储,比如listsethashzset等。
  • 一般是作为缓存数据库辅助持久化数据库

Redis是一个开源的key-value存储系统。 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash (哈希类型)。 这些数据类型都支持push/pop、add/remove及 取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。与memcached一 样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

三、应用场景

1. 高速缓存

	首先要说明的是,`redis`是**单线程**的,除了本身基于是基于内存、数据结构简单、没有了多线程的锁事件之外,更重要的是`redis`时使用了I/O多路复用技术。正是因为`redis`中具备了这些特性,所以经常用来**缓存热点数据**,降低数据库IO。除此之外,也用来做分布式环境中的**session共享**。

2. 存储数据

​ 由于redis具有持久化的能力,利用其多样的数据结构特性,便可以应用多样的业务场景:

  1. 数据自然时间排序——list结构
  2. 排行榜——zset有序集合
  3. 时效性数据——redis键可以设置有效期
  4. 计数器,秒杀——自增方法的原子性

四、安装教程

  1. 官网下载redis-3.2.5.tar.gz的压缩包

  2. 将这个压缩包复制到linux系统的opt目录下

  3. 使用解压命令 tar -zxvf radis-3.2.5.tar.gz解压这个文件夹

  4. 进入解压后的文件夹,执行make命令,发现出现错误,错误如下: 在这里插入图片描述

  5. 执行yum install gccyum install gcc-c++等待安装完成(安装运行环境)

  6. 使用make distclean命令清除上面执行过程的缓存

  7. 重新在redis解压目录下使用make命令开始安装redis,默认的安装目录为usr/local/bin

五、启动 Redis

1、使用redis-server前台启动redis服务器

在这里插入图片描述

2、将redis更改为后台启动

  • 将解压下的目录下的redis.conf文件复制到opt/myRedis目录下(自己的安装目录)

  • 使用vi redis.conf命令进入这个配置文件并搜索daemonize,将其值改为yes,保存并退出。 在这里插入图片描述

  • 使用配置文件启动redis,命令:redis-server /opt/myRedis/redis.conf ; 使用redis-cli -h 主机名 -p 端口号进入redis客户端

3、redis查看与关闭

  • 使用ps -ef | grep redis查看服务端和客户端是否在后台运行

  • 在前台启动redis,进入之后使用shutdown命令

  • 在进程中使用kill p

  • 使用redis-cli shutdown命令

六、redis的使用

redis默认有16个数据库,类似数组从小标0开始,默认使用的是0号数据库。使用select index的方式选择数据库。redis的五大数据类型string, list, set, zset, hash, 都是以键值对的形式存在的

4、基本语法

1)String:
  • keys * :查看所有的键
  • exists key :查看某个键值对是否存在,存在返回1,不存在返回0
  • type key : 查看键的类型
  • expire key secends :为已存在的键设置存在时间
  • setex key secends value: 设置一个键值对的同时设置过期的时间。
  • ttl key : 查看键的生存时间(time to live)。-1表示永不过期,-2表示已经过期。
  • dbsize: 查看当前数据库的key的数量。
  • flushdb:清除当前库。
  • flushall:清除全部库。
  • append key value : 往key对应的值中追加内容。
  • strlen key : 获取字符串的长度。
  • setnx key value: 只有当可以不存在的时候才设置key的值。
  • incr/decr key : 将值加一/减一,只对数值有效。
  • incrby/decrby key 步长: 将值按步长进行加减
  • mset key1 value2 key2 value2...: 设置多个键值对。
  • mget key1 key2...:获取多个键值对。
  • msetnx key1 key2 ...: 同时设置对个键值对,当且仅当所有给定的key值都不存在的时候设置才生效。
  • getrange key start end:获取指定范围的值。
  • setrange key start value: 从一个位置复写这个值。
  • getset key value : 重写这个键并返回这个键的值。
2) List

简单的字符串列表,底层实现是一个双向链表,对两段的操作性能很高,中间操作性能比较差,有序可重复

  • lpush key value1 value2 value3...:从左边插入链表的值

  • rpush key value1 value2...:从右边插入链表的值

  • lpop key/rpop key :从左/右弹出链表中的值。

  • rpoplpush key1 key2 :从key1的右边弹出一个值加到key2的左边。

  • lrange key start stop: 查找链表中的指定范围的元素。0表示表头,-1表示表尾。

  • lindex key index: 通过索引获取链表的元素(从左到右)

  • llen key :获得链表的长度。

  • linsert key before value newvalue: 在value的前面插入新的值

  • lrem key n value: 从左至右删除n个value,当n为负数的时候为从右至左删除。0表示删除所有。

3) set

底层是value为null的hash表,所以操作的复杂度都为1。它是无序不可重复的

  • sadd key value1 value2 value3...:创建一个类型为set的键并往里面添加数据。
  • smembers key: 取出set集合里面的元素。
  • sismember key value:判断集合里面是否有值为value的元素。
  • spop key:随机弹出集合中的一个元素会删除这个元素。
  • srandmembers key n:随机从集合中弹出n个元素,不会删除这些元素。
  • sinter key1 key2 : 返回两个集合中的交集。
  • sunion key1 key2 : 返回两个集合中的并集。
  • sdiff key1 key2:返回两个元素的差集。
4)hash

特别适合存储对象。比如在java中,可以将类转换为json字符串的方式,存储在redis中的hash中

  • hset key filed value: 创建一个hash键,其中filed为hash里面的键名,一般采用这种格式命名:user:1010:uid
  • hmset key filed1 value1 filed2 value2:批量创建hash键。
  • hexist key filed:判断键里面是否有hash里面的键filed。
  • hkeys key :列出hash集合中的所有键名称
  • hvals key:列出hash集合中的所有值。
  • hgetall key:列出hash里面的所有键值对。
  • hincrby key filed increment:为filed键对应的值加上increment并返回结果。
  • hsetnx key filed value:将哈希表中的域filed的值设置为value。
5)zSet

sort set,没有重复的有序集合,利用score来从小到大进行排序

  • zadd key score1 value1 score2 value2: 设置多个成员的值以及score的值,也可以用来修改元素的score值和添加新的元素。
  • zrange key start stop : 按照小标查找指定范围的zSet的元素,0是第一个,-1是最后一个。
  • zrangebyscore key min max: 查找所有指定范围的score的元素。当min==max的时候表示按score查找元素。
  • zrevrange key start stop :将指定范围的元素按从大到小的顺序排列(反转的意思)。
  • zincrby key increment value : 为元素的score加上增量。
  • zrem key value:删除元素。
  • zcount key min max:统计指定范围分数的元素的个数。
  • zrank key value:返回该值在集合中的排名。

5、redis的相关配置

cd /opt/myRedis/redis.conf打开里面的配置文件,可以看到redis的相关配置信息。

  • 不区分大小写

  • 关键字指定包含的配置文件

  • bind:绑定特定ip主机才可以访问服务(配合protected-mode使用)

  • tcp-backlog:请求到达后接口进程处理前的队列

  • timeout : 空闲的客户端开启的持续时间,0表示不关闭。

  • daemonize: 是否设置为后台进程

  • requirepass: 设置redis的访问密码

  • tcp keepalive: 对访问的客户端进行心跳检测,每个n秒检测一次,官方推荐设置为60秒

  • pidfile: 存放pid文件的位置,每个实例会产生一个不同的pid文件。

  • loglevel: 设置日志的级别debug verbose notice warning

  • logfile: 日志文件名称

  • syslog : 是否将redis的日志输入到linux系统日志服务中

  • database: 设置库的数量

  • maxclient: 最大的客户端连接数

  • maxmemory: redis最大可以使用的内存量

七、Java连接

1、准备工作

  1. 下载好必要的jar包:
  • commons-pool2-2.4.2.jar
  • jedis-2.7.2.jar
  1. 修改redis的配置文件
  • 将protected-mode 改为no
  • 将bind 127.0.0.1注释掉
  1. 关闭centos的防火墙(命令)
  • sudo systemctl stop firewalld 临时关闭
  • sudo systemctl disable firewalld 然后reboot 永久关闭
  • sudo systemctl status firewalld 查看防火墙状态。

2、代码部分

import redis.clients.jedis.Jedis;
public class Client {
    public static void main(String[] args) {
        // 建立一个redis连接
        Jedis jedis = new Jedis("192.168.17.135",6379);
        // 创建一个String类型的数据
        jedis.set("achao","12");
        System.out.println(jedis.get("achao"));
        // 使用完关闭连接
        jedis.close();
    }
}

3、使用

在Jedis类中封装了对 redis操作的方法。具体的使用看上面的基本语法部分的内容。

八、redis事务

redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送过来的命令请求打断。

redis事务的主要作用就是串联多个命令防止别的命令插队。

1、multi

使用这个命令标识事务的开启,之后编写的命令都作为一个待执行的队列等待执行。

2、discard

退出multi的状态,在待执行队列里面的命令全部不执行

3、exec

在使用multi开启事务之后,使用exec可以将待执行队列里的命令按顺序执行。

4、关于错误处理:

当我们开启了一个事务,并使用exec批量执行命令时。

如果其中的命令编译时是有错误的,则整个待执行队列命令不会执行

如果其中命令运行时是有错误的,则该命令不会执行,其他命令还是会按顺序执行——这是与我们关系型数据库不一样的地方

九、redis 监控(watch)

redis 中的Watch命令可以监控一个或者多个键,一旦其中的一个键事务处理的过程中被更改,那么这个事务就不会执行,同时会解除监控。下面我们通过多线程对一个key执行修改的例子来模拟这个过程。

1、没有使用watch的时候

127.0.0.1:6379> set money 1000	# 我们设置一个money的初始金额
OK
127.0.0.1:6379> multi	# 开始执行事务
OK
127.0.0.1:6379> decrby money 100
QUEUED
127.0.0.1:6379> incrby money 100
QUEUED
# 第一个客户端执行了减100然后由增加100的事务

#####################另一个客户端开始执行减100操作##################

127.0.0.1:6379> decrby money 100

#####################第一个客户端开始执行事务#######################

127.0.0.1:6379> exec
1) (integer) 800	
2) (integer) 900	# 最后的结果是900 也就是第二个客户端影响了我们的事务操作

显然上面这种情况是我们不想发生的,所以我们这个时候使用watch命令防止这种情况发生

2、使用watch的时候

127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> watch money		# 开始监控money
OK
127.0.0.1:6379> multi	# 执行事务
OK
127.0.0.1:6379> decrby money 100
QUEUED
127.0.0.1:6379> incrby money 100
QUEUED

#####################另一个客户端开始执行减100操作##################
127.0.0.1:6379> decrby money 100
(integer) 900

#####################第一个客户端开始执行事务#######################
127.0.0.1:6379> exec
(nil)		# 返回值为null  事务执行失败
127.0.0.1:6379> get money	
"900" 	# 获取money的值,是900

3、总结

事务中的watch命令相当于对监控的键上了一把“乐观锁”,这种乐观锁的机制相当于java中的cas操作——对监控的键记录下监控时的数据和版本(version),当被监控的键执行事务的时候,发现数据或者版本与之前记录的不一致,就会终止执行事务,保证数据的一致性。

当上述例子中的第二个客户端对被监控的键执行了减操作时,该键的版本就会改变(上述例子中被监控的键数据和版本都发生了改变,读者可以尝试让第二个客户端对这个键执行增减100的操作(即保持数据不变),看看第一个客户端的事务会不会执行成功(答案是否定的)。

十、SpringBoot整合redis

1、新建SpringBoot项目

2、说明

在SpringBoot2.x之后,原来使用的jedis被替换成为了lettuce,两者的区别如下:

jedis:采用的是直连,多个线程操作的话,是不安全的,如果想要避免不安全的,只能使用jedis pool连接池

lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!

3、连接并使用redis

  • 启动linux服务中的redis,具体的步骤看第七章。
  • 使用properties文件配置redis的运行环境
#配置redis
spring.redis.url=127.0.0.1
spring.redis.port=6379
  • 操作redis的基本数据类型:
    • 注入RedisTemplate;
    • redisTemplate.opsForValue(): 操作字符串
    • redisTemplate.opsForList():操作链表
    • redisTemplate.opsForSet():操作Set
    • 等等。。。
  • 除此之外,我们使用的一些事务、删除等常用的操作直接使用redisTemplate中的方法操作
  • 清空数据库的操作通过redisTempldate.getConnectionFactory.getConnection()来获取连接来操作
  • 更多操作自行探索

十一、Redis持久化

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能!redis持久化主要有以下的两种机制:

1、RDB(redis database)

由触发事件在指定的时间间隔将内存中的数据写入磁盘,也就是通常说的快照,恢复的时候会将快照文件直接读到内存里。

redis会单独创建一个子进程来进行持久化,会先将数据写入到一个临时文件中,等到持久化的过程结束,再用这个临时文件替换上一次持久化好的文件。**整个过程中,主进程是不进行任何IO操作的。**这就确保了极高的性能。如果需要进行大规模的数据恢复,且对于数据恢复的完整性不是非常敏感,那RDB的方式要比AOF方式更加高。缺点是最后一次持久化后的数据可能丢失。(因为持久化是有触发条件和时间间隔的)

rdb 保存的文件是dump.rdb,当我们想使用一个dump.rdb的文件的时候(通常是一种redis数据迁移),只需将这个文件放在redis程序运行时的bin目录即可。命令:config get dir

触发机制:

  • save的规则满足的情况下
  • 执行flushall命令的时候
  • 正常退出redis

优点

  • 适合大规模的数据恢复(因为备份高效,备份文件小)
  • 对数据的完整性要求不高(因为最后一次的持久化可能是不完整的)

缺点

  • 需要一定的时间间隔进程操作,如果redis意外宕机,则个最后一次修改的数据就没有了
  • fork进程的时候,会占用一定的内容空间

2、AOF(append only file)

以日志的形式去记录每个写操作,将redis执行过的所有命令记录下来(读操作不作记录),只许追加文件但不允许改写文件,redis启动之初会读取该文件重写构建数据(相当于把记录的命令重新执行了一遍),文件默认保存为appendonly.aof

默认是不开启的,需要在redis启动的配置文件中修改,将appendonly改为yes;如果这个aof回复文件出现了一些错误,可以通过redis-check-aof --fix

优点

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

缺点

  • 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
  • aof的运行效率也是比rdb慢,所以我们的redis默认的持久化就是rdb机制

十二、Redis发布订阅

Redis发布订阅是一种消息通信模式:发送者发送消息,订阅者接受消息。如微信、微博。

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

使用命令:

  • SUBSCRIBE channel1 channel2.. : 订阅多个频道

  • PSUBSCRIBE pattern1 pattern2...:订阅符合一定模式的频道

  • UNSUBSCRIBE channel1 channel2:退订多个频道

  • PUBLISH chanel message: 将消息发送到指定频道

使用场景:

  • 实时消息系统、实时聊天
  • 稍微复杂的使用的是MQ中间件实现

十三、主从复制

1、概念

主从复制,是指将一台redis服务器的数据,复制到其他的redis服务器。前者称为主节点(master、leader),后者称为从节点

(slava/follower);数据复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。

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

主从复制的作用主要包括:

1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

2、故障恢复:当主节点出现问题时,可以由多个从节点提供服务,实现快速地故障恢复;实际上是一种服务的冗余。

3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写redis数据时应用连接主节点,读redis数据时应用连接从节点,分担服务器负载,尤其是在写少读多的场景下,通过多个节点分担读负载,可以大大提高redis服务器的并发量。

4、高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是redis高可用的基础。

一般来说,要将redis运用于工程项目中,只使用一台redis服务器是万万不能的(因为总会有概率宕机),原因如下:

  1. 从结构上,单个redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大。
  2. 从容量上,单个redis服务器内存容量有限,就算一台redis服务器内存容量为256G,也不能将所有内存用作redis内存存储,一般来说,单台redis最大使用内存不应该超过20G。

电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是“多读少写”。

2、环境配置

本地测试的时候,使用下面的方法进行集群环境的搭建:

  • 在linux系统中使用不同的redis.config文件启动redis服务并启动对应的客户端:
    • 修改配置文件中的端口信息
    • 修改配置文件中的pid名字
    • 修改配置文件中的log文件
    • 修改配置文件中的dump.rdb文件
  • 将需要更改为从主机的客户端使用一下的命令绑定主主机(默认启动的时候每台机子都是master):
    • SLAVEOF host port
    • 使用info replication查看主从的相关信息
    • 说明:使用这种客户端命令行的方式只是临时的建立了这种主从关系

特性:

  • 主机能写能读,从机只能读主机中存在的redis数据
  • 主机意外宕机的时候,从机依然可以读取主机宕机里面的redis数据;主机重启后,从机会自动连接上主机。
  • 从机宕机后重启是不能连接到主机的,也不能继续读取主机里面的值。因为命令行中建立的主从关系是暂时的。

复制原理:

Slave启动成功后会自动地连接到master并发送一个sync同步命令

master接到命令,启动后台的存盘进程,同时收集所以接收到的修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。同步的方式有以下几种:

全量同步:将master中的所以redis数据同步到slave中

增量同步:将master中新的redis数据同步到redis中,增量的基础是全量

3、哨兵(sentinel)

出现问题:当我们的主机宕机的时候,我们的数据只能实现读的操作,但是我们还是想继续往这个redis服务器中进行操作,这个时候我们就希望直接连接这个主机的从机成为一个新的主机,可以在这个从机中使用slaveof no one命令手动实现。

哨兵模式:

哨兵模式是一种特殊的模式,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

哨兵模式是一种特殊的模式,首先redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理就是哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis主机。

哨兵模式需要开启额外的进程,哨兵运行的时候主要有两个作用:

  • 通过发送命令,让redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机之后,会自动将slave切换成master,然后通过发布订阅的方式通知其他的从服务器。修改配置文件,让他们切换主机。

出现问题: 然而一个哨兵进程对redis服务器监控可能会出现问题(进程以外终止),为此我们可以使用多个哨兵的模式进行互相监控。这就形成了多哨兵模式对意外终止的哨兵重新启动或者新建一个进程。

需要注意的是:当我们的一个哨兵检测到某个redis服务器宕机的时候,系统并不会马上进行修复,因为一个哨兵的检测可能有偏差,这个现象我们称之为主观下线,当多个哨兵都检测到这个服务器宕机并且数量达到一定值的时候,这个时候哨兵才会执行一定的主从切换工作,这个称之为客观下线

核心命令:

sentinel monitor name host port 1(投票机制)  # 会在bin文件夹下生产一个

优点:

  • 哨兵集群,基于主从复制模式,所有的主从配置优点,他全有
  • 主从可以切换,故障可以转移,系统的可用性就会更好
  • 哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点:

  • Redis不容易在线扩容,集群容量一旦到达上线,在线扩容就十分麻烦!
  • 实现哨兵模式的配置是很麻烦的里面有很多选择(链路复杂)哨兵模式的全部配置建议在网上找相关的资源

十四、缓存穿透和雪崩

缓存穿透

1、概念:

缓存穿透的概念很简单,用户想要查询一个数据的时候,发现redis内存数据中没有,就跳过的redis缓存直接在数据库中查找(缓存没有命中)。如果有恶意的用户(攻击者)利用这个redis缓存中没有的数据对数据库发起大量的这种查询操作,就会对数据库造成很大的压力,这个现象就叫做缓存穿透。

2、解决办法

布隆过滤器: 它是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层进行校验,不符合的查询参数则丢弃或者返回一个固定的值,从而避免了对底层数据库的查询压力

存在的问题:

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

缓存击穿

1、概念

这里指的是一个redis中一个非常热点key。在不停地被外界访问(大并发请求),在这个key失效的瞬间,持续的大并发访问会穿透redis缓存直接访问数据库,底层数据库瞬间承受的巨大的访问量,这就是击穿的概念。

2、解决方案

  • 设置热点永不过期
  • 加互斥锁

缓存雪崩

1、概念

指的是在某一个时间段,缓存几种过期失效。比如redis宕机!这个时候所有的数据访问瞬间落在了底层数据库上!

例如抢购的时候!

2、解决办法

  • redis高可用:多设置几台redis(集群)
  • 限流降级:缓存失效之后控制访问底层数据库的线程数量
  • 数据预热:实现通过对可能访问的数据缓存到redis中,设置不同的过期时间,避免过期时间过于集中