Redis

Redis 概述

Redis 是什么?

Redis(Remote Dictionary Server ),即远程字典服务 !

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

免费和开源!是当下最热门的 NoSQL 技术之一!也被人们称之为结构化数据库!

Redis 能干嘛?

1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)
6、........

Redis有什么特性?

1、多样的数据类型
2、持久化
3、集群
4、事务
5、......

Redis官方

1、官网:https://redis.io/

2、中文网:http://www.redis.cn/

Redis的数据回收策略

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰;

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰;

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰;

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰;

no-enviction:(驱逐)禁止驱逐数据;

Redis 应用部署

1、下载安装包:redis-5.0.8.tar.gz

2、解压Redis的安装包 至 /usr/local/src目录下

3、进入解压后的文件,可以看到我们redis的配置文件

4、编译 Redis 源码

[root@redis01 redis-5.0.8]# yum -y install gcc-c++

[root@redis01 redis-5.0.8]# make && echo $?

5、创建redis安装目录

[root@redis01 redis-5.0.8]# mkdir -p /usr/local/redis         #Redis家目录
[root@redis01 redis-5.0.8]# mkdir -p /usr/local/redis/pid
[root@redis01 redis-5.0.8]# mkdir -p /usr/local/redis/log
[root@redis01 redis-5.0.8]# mkdir -p /usr/local/redis/bin
[root@redis01 redis-5.0.8]# mkdir -p /usr/local/redis/conf
[root@redis01 redis-5.0.8]# ll /usr/local/redis/
总用量 0
drwxr-xr-x 2 root root 6 6月  20 11:58 bin
drwxr-xr-x 2 root root 6 6月  20 11:58 conf
drwxr-xr-x 2 root root 6 6月  20 11:58 log
drwxr-xr-x 2 root root 6 6月  20 11:57 pid

6、拷贝Redis相关文件至此目录中

[root@redis01 redis-5.0.8]# cp redis.conf /usr/local/redis/conf/
[root@redis01 redis-5.0.8]# cp src/redis-server /usr/local/redis/bin/
[root@redis01 redis-5.0.8]# cp src/redis-cli /usr/local/redis/bin/
[root@redis01 redis-5.0.8]# cp src/redis-benchmark /usr/local/redis/bin/
[root@redis01 redis-5.0.8]# cp src/redis-check-rdb /usr/local/redis/bin/
[root@redis01 redis-5.0.8]# cp src/redis-check-aof /usr/local/redis/bin/
[root@redis01 redis-5.0.8]# cp src/redis-sentinel /usr/local/redis/bin/
文件 作用
redis.conf redis配置文件
redis-server redis服务端程序
redis-cli redis客户端程序
redis-benchmark 用于进行测试redis性能测试的工具
redis-check-rdb 用于修复出现问题的dump.rdb文件
redis-check-aof 用于修复出现问题的aof文件
redis-sentinel 用于哨兵集群管理工具

7、配置Redis命令的软连接

[root@redis01 ~]# ln -s /usr/local/redis/bin/* /usr/local/bin/

** 8、修改Redis配置文件**

[root@redis01 ~]# vim /usr/local/redis/conf/redis.conf
69 bind 192.168.2.1                                  #修改监听的IP地址
88 protected-mode no                                 #关闭redis的保护模式
136 daemonize yes                                    #开启redis的后台守护进程模式
158 pidfile "/usr/local/redis/pid/redis_6379.pid"    #指定pid文件位置
171 logfile "/usr/local/redis/log/redis_6379.log"    #指定log文件位置
263 dir "/usr/local/redis/"                          #指定本地快照数据库存放目录(dump.rdb)
507 requirepass 123456                               #开启redis密码 并 设置redis的密码为123456

9、Redis配置文件详解

配置 作用
daemonize yes 以后台daemon方式运行redis
pidfile "redis.pid" 默认pid文件路径
port 6379 默认端口
bind 127.0.0.1 默认绑定本机所有ip地址,为了安全,可以只监听内网ip
timeout 300 客户端超时设置,单位为秒
loglevel verbose 设置日志级别,支持四个级别:debug、notice、verbose、warning
logfile stdout 日志记录方式,默认为标准输出,logs不写文件,输出到空设备/deb/null
logfile "redis.log" 可以指定日志文件路径
databases 16 开启数据库的数量
save 900 1 创建本地数据库快照;900秒内,执行1次写操作后触发快照
save 300 10 创建本地数据库快照;300秒内,执行10次写操作
save 60 10000 创建本地数据库快照;60秒内,执行10000次写操作
rdbcompression yes 启用数据库lzf压缩,也可以设置为no
dbfilename dump.rdb 本地快照数据库名称
dir "/usr/local/redis" 本地快照数据库存放目录(默认在启动redis的路径)
requirepass 123456 设置redis数据库连接密码
appendfsync everysec 设置日志同步的频率, 每秒执行同步, 还有两个参数always、no一般设置为everysec, 相当于MySQL事物日志的写方式
Slaveof 设置数据库为其他数据库的从数据库
Masterauth 主数据库连接需要的密码验证
vm-enabled 是否开启虚拟内存支持 (vm开头的参数都是配置虚拟内存的)
vm-swap-file 设置虚拟内存的交换文件路径
vm-max-memory 设置redis使用的最大物理内存大小
vm-page-size 设置虚拟内存的页大小
vm-pages 设置交换文件的总的page数量
vm-max-threads 设置使用swap存储同时使用的线程数量,通常设置值为核心数相同,如果设置为0,则会以串行方式,对数据的完整性有着极大的保证
Glueoutputbuf 把小的输出缓存存放在一起
hash-max-zipmap-entries 设置hash的临界值
Activerehashing 重新hash

10、编写启动脚本并启动Redis

[root@redis01 ~]# vim /etc/init.d/redis
#!/bin/sh
# chkconfig: 2345 80 90
# description: Start and Stop redis
#PATH=/usr/local/bin:/sbin:/usr/bin:/bin
REDISPORT=6379
EXEC=/usr/local/redis/bin/redis-server
REDIS_CLI=/usr/local/redis/bin/redis-cli
PIDFILE=/usr/local/redis/pid/redis_6379.pid
CONF="/usr/local/redis/conf/redis.conf"
AUTH="123456"
LISTEN_IP=$(netstat -utpln |grep redis-server |awk '{print $4}'|awk -F':' '{print $1}')

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        if [ "$?"="0" ]
        then
              echo "Redis is running..."
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $REDIS_CLI -h $LISTEN_IP -p $REDISPORT -a $AUTH SHUTDOWN
                while [ -x ${PIDFILE} ]
               do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
   restart|force-reload)
        ${0} stop
        ${0} start
        ;;
  *)
    echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
        exit 1
esac

[root@redis01 ~]# chmod 755 /etc/init.d/redis  #给予脚本执行权限
[root@redis01 ~]# /etc/init.d/redis start      #启动Redis
Starting Redis server...
Redis is running...
[root@redis01 ~]# netstat -anptu|grep "6379"
tcp        0      0 192.168.2.1:6379        0.0.0.0:*            LISTEN      22704/redis-server

11、查看redis家目录生成的文件

[root@redis01 ~]# tree /usr/local/redis/
/usr/local/redis/
├── bin
│   ├── redis-benchmark
│   ├── redis-check-aof
│   ├── redis-check-rdb
│   ├── redis-cli
│   ├── redis-sentinel
│   └── redis-server
├── conf
│   └── redis.conf
├── dump.rdb
├── log
│   └── redis_6379.log
└── pid
    └── redis_6379.pid

4 directories, 10 files

12、使用redis-cli 连接Redis服务方式

## 方式一:redis-cli -h IP -p port -a password       #不推荐在公司这么用,适合学习的时候用
[root@redis01 ~]# redis-cli -h 192.168.2.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.2.1:6379> keys *
(empty list or set)


## 方式二:redis-cli -h IP -p port                   #这种的方式,再进去之后需要用AUTH认证密码
[root@redis01 ~]# redis-cli -h 192.168.2.1 -p 6379
192.168.2.1:6379> keys *
(error) NOAUTH Authentication required.
192.168.2.1:6379> AUTH 123456
OK
192.168.2.1:6379> keys *
(empty list or set)


## 可能会出现错误信息:(error) ERR Client sent AUTH, but no password is set
解决方法如下:
192.168.1.1:6379> CONFIG SET requirepass "123456"   #设置密码
OK
192.168.1.1:6379> AUTH 123456
OK

配置文件详解

模块化,包含,类似于Nginx中的include

网络配置

port 6379                          # 端口设置
bind 127.0.0.1                     # 默认绑定本机所有ip地址,为了安全,可以只监听内网ip
protected-mode yes                 # 保护模式

SECURITY 安全配置

requirepass 123456                 # 设置redis数据库连接密码,默认是没有密码!
masterauth <master-password>       # 主数据库连接需要的密码验证

通用配置 GENERAL

daemonize yes                      # 以守护进程的方式运行,默认是 no,我们需要自己开启为yes!
pidfile "redis.pid"                # 默认pid文件路径
logfile "redis.log"                # 指定日志文件路径
loglevel notice                    # 设置日志级别,支持四个级别:debug、notice、verbose、warning
databases 16                       # 开启数据库的数量,默认是 16 个数据库
always-show-logo yes               # 是否总是显示LOGO

rdb快照配置

save 900 1                         # 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
save 300 10                        # 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
save 60 10000                      # 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
rdbcompression yes                 # 是否压缩 rdb 文件,需要消耗一些cpu资源!
dbfilename dump.rdb                # 本地快照数据库名称
dir ./                             # 本地快照数据库存放目录(默认在启动redis的路径)
stop-writes-on-bgsave-error yes    # 持久化如果出错,是否还需要继续工作!

AOF配置

appendonly no                      # 默认关闭的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!
appendfilename "appendonly.aof"    # aof持久化的文件的名字
appendfsync always                 # 每次修改都会 sync。消耗性能
appendfsync everysec               # 每秒执行一次 sync,可能会丢失这1s的数据!
appendfsync no                     # 不执行 sync,这个时候操作系统自己同步数据,速度最快!

限制 CLIENTS

maxclients 10000                   # 设置能连接上redis的最大客户端的数量
timeout 300                        # 客户端超时设置,单位为秒
maxmemory <bytes>                  # redis 配置最大的内存容量
maxmemory-policy noeviction        # 内存到达上限之后的处理策略, 如下:
  1、allkeys-random    随机删除
  2、volatile-random   随机删除即将过期key 
  3、allkeys-lru       删除lru算法的key 
  4、volatile-ttl      删除即将过期的 
  5、noeviction        永不过期,返回错误
  6、volatile-lru      只对设置了过期时间的key进行LRU(默认值)

REPLICATION 复制,集群配置

min-slaves-to-write 3       # 设置slave节点数量,如果slave节点数量少于此值,那么master节点将停止客户端一切写请求
min-slaves-max-lag 10       # master与slave之间同步数据的超时时间,超过此时间,master节点将停止客户端一切写操作
slaveof IP PORT             # slave节点指定主master的ip地址以及端口

虚拟内存配置

vm-enabled                         # 是否开启虚拟内存支持
vm-swap-file                       # 设置虚拟内存的交换文件路径
vm-max-memory                      # 设置redis使用的最大物理内存大小
vm-page-size                       # 设置虚拟内存的页大小
vm-pages                           # 设置交换文件的总的page数量

Redis 测试性能

redis-benchmark 是一个压力测试工具,官方自带的性能测试工具。

常用选项:

选项 描述 默认值
-h 指定服务器主机名 127.0.0.1
-p 指定服务器端口 6379
-s 指定服务器 socket
-c 指定并发连接数 50
-n 指定请求数 10000
-d 以字节的形式指定 SET/GET 值的数据大小 2
-k 1=keep alive 0=reconnect 1
-r SET/GET/INCR 使用随机 key, SADD 使用随机值
-P 通过管道传输 numreq 请求 1
-q 强制退出 redis。仅显示 query/sec 值
--csv 以 CSV 格式输出
-l 生成循环,永久执行测试
-t 仅运行以逗号分隔的测试命令列表。
-I Idle 模式。仅打开 N 个 idle 连接并等待。

** 简单测试下 **

# 测试:100个并发连接 100000请求
redis-benchmark -h 192.168.2.1 -p 6379 -c 100 -n 100000

如何查看这些分析呢?

Redis 基础Key-Value

Redis 是单线程的!

明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!所有就使用了单线程了!

Redis 是C 语言写的,官方提供的数据为 100000+ 的QPS,完全不比同样是使用 key-vale的Memecache差!

Redis 为什么单线程还这么快?

1、误区1:高性能的服务器一定是多线程的?

2、误区2:多线程(CPU上下文会切换!)一定比单线程效率高!

3、对CPU>内存>硬盘的速度要有所了解!

4、核心:redis 是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案!

redis默认有16个数据库,默认使用的是第0个,可以使用 select 进行切换数据库!

后面如果遇到不会的命令,可以在官网查看帮助文档!

192.168.2.1:6379> select 3               # 切换数据库
OK
192.168.2.1:6379[3]> dbsize              # 查看DB的key个数!
(integer) 0
192.168.2.1:6379[3]> set name lemon      # 创建一对key value
OK
192.168.2.1:6379[3]> dbsize
(integer) 1
192.168.2.1:6379[3]> type name           # 查看key的value数据类型
string
192.168.2.1:6379[3]> get name            # 查看key的value值
"lemon"
192.168.2.1:6379[3]> exists name         # 判断当前的key是否存在; 1代表存在, 0代表不存在
(integer) 1
192.168.2.1:6379[3]> exists name1
(integer) 0
192.168.2.1:6379[3]> set AA aa
OK
192.168.2.1:6379[3]> dbsize
(integer) 2
192.168.2.1:6379[3]> del AA              # 删除key
(integer) 1
192.168.2.1:6379[3]> dbsize
(integer) 1
192.168.2.1:6379[3]> select 1
OK
192.168.2.1:6379[1]> set age 20
OK
192.168.2.1:6379[1]> set height 1.8
OK
192.168.2.1:6379[1]> randomkey           #随机返回一个Key
"height"
192.168.2.1:6379[1]> randomkey
"age"
192.168.2.1:6379[1]> keys *              # 查看当前数据库所有的key
1) "height"
2) "age"
192.168.2.1:6379[1]> keys h*             # 查看当前数据库所有以h开头的key
1) "height"
192.168.2.1:6379[1]> expire age 10       # 设置key的过期时间,单位是秒
(integer) 1
192.168.2.1:6379[1]> ttl age             # 查看当前key的剩余时间
(integer) 3
192.168.2.1:6379[1]> ttl age
(integer) 0
192.168.2.1:6379[1]> ttl age
(integer) -2
192.168.2.1:6379[1]> keys *
1) "height"
192.168.2.1:6379[1]> rename height AA    # 修改key名
OK
192.168.2.1:6379[1]> keys *
1) "AA"
192.168.2.1:6379[1]> flushdb             # 清空当前数据库的内容
OK
192.168.2.1:6379[1]> keys *
(empty list or set)
192.168.2.1:6379[1]> flushall            # 清空全部数据库的内容
OK
192.168.2.1:6379[1]> select 3
OK
192.168.2.1:6379[3]> keys *
(empty list or set)

Redis中操作之其他操作

192.168.2.1:6379> dbsize 				#查看所有key的数目 
192.168.2.1:6379> flushdb  				#删除当前选择数据库中的所有key 
192.168.2.1:6379> flushall  			#删除所有数据库中的所有key 
192.168.2.1:6379> save					#将数据同步保存到磁盘 
192.168.2.1:6379> bgsave 				#异步保存 
192.168.2.1:6379> lastsave 				#上次成功保存到磁盘的Unix时间戳 
192.168.2.1:6379> info					#查询server信息 
192.168.2.1:6379> slaveof				#改变复制策略设置

Redis五大数据类型

官网文档

String(字符串)

String是简单的 key-value 键值对,value 不仅可以是 String,也可以是数字;String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。

---------------------------------------------------------------------
192.168.2.1:6379> set key1 v1
OK
192.168.2.1:6379> get key1
"v1"
192.168.2.1:6379> append key1 "hello"         # 追加字符串,如果当前key不存在,就相当于set key
(integer) 7
192.168.2.1:6379> get key1
"v1hello"
192.168.2.1:6379> strlen key1                 # 获取字符串的长度
(integer) 7
---------------------------------------------------------------------
# i++
# 步长 i+=
192.168.2.1:6379> set key2 0                  # 初始浏览量为0
OK
192.168.2.1:6379> get key2
"0"
192.168.2.1:6379> incr key2                   # 自增1 浏览量变为1
(integer) 1
192.168.2.1:6379> incr key2
(integer) 2
192.168.2.1:6379> get key2
"2"
192.168.2.1:6379> decr key2                   # 自减1  浏览量-1
(integer) 1
192.168.2.1:6379> decr key2
(integer) 0
192.168.2.1:6379> get key2
"0"
192.168.2.1:6379> incrby key2 10              # 可以设置步长,指定增量!
(integer) 10
192.168.2.1:6379> incrby key2 10
(integer) 20
192.168.2.1:6379> get key2
"20"
192.168.2.1:6379> decrby key2 10              # 可以设置步长,指定减量!
(integer) 10
192.168.2.1:6379> decrby key2 10
(integer) 0
192.168.2.1:6379> get key2
"0"
---------------------------------------------------------------------
# 字符串范围 range
192.168.2.1:6379> set key3 "hello,world"
OK
192.168.2.1:6379> get key3
"hello,world"
192.168.2.1:6379> getrange key3 0 4           # 截取字符串 [0,4]
"hello"
192.168.2.1:6379> getrange key3 0 -1          # 获取全部的字符串 和 get key是一样的
"hello,world"

# 替换
192.168.2.1:6379> set key4 abcdefg
OK
192.168.2.1:6379> get key4
"abcdefg"
192.168.2.1:6379> setrange key4 1 xx
(integer) 7
192.168.2.1:6379> get key4
"axxdefg"
---------------------------------------------------------------------
# setex (set with expire)   # 设置过期时间
# setnx (set if not exist)  # 不存在在设置 (在分布式锁中会常常使用!)
192.168.2.1:6379> setex key5 30 "hello"       # 设置key5 的值为 hello, 30秒后过期
OK
192.168.2.1:6379> ttl key5
(integer) 26
192.168.2.1:6379> get key5
"hello"
192.168.2.1:6379> setnx mykey "redis"        # 如果mykey 不存在,创建mykey
(integer) 1
192.168.2.1:6379> keys *
1) "mykey"
2) "key1"
3) "key5"
4) "key4"
5) "key3"
6) "key2"
192.168.2.1:6379> ttl key5
(integer) -2
192.168.2.1:6379> keys *
1) "mykey"
2) "key1"
3) "key4"
4) "key3"
5) "key2"
192.168.2.1:6379> setnx mykey "MongoDB"      # 如果mykey存在,创建失败!
(integer) 0
---------------------------------------------------------------------
# mset  同时创建多个键值
# mget  同时获取多个键的值
192.168.2.1:6379> mset k1 'v1' k2 'v2' k3 'v3'
OK
192.168.2.1:6379> keys *
1) "k2"
2) "mykey"
3) "k1"
4) "key3"
5) "key1"
6) "key2"
7) "key4"
8) "k3"
192.168.2.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
---------------------------------------------------------------------
getset # 先get然后在set
192.168.2.1:6379> getset db 'redis'          # 如果不存在值,则返回 nil, 在设置k-v
(nil)
192.168.2.1:6379> get db
"redis"
192.168.2.1:6379> getset db 'mongodb'        # 如果存在值,获取原来的值,并设置新的值
"redis"
192.168.2.1:6379> get db
"mongodb"

List(列表)

概述:Redis列表是简单的字符串列表,可以类比到C++中的std::list,简单的说就是一个列表或者说是一个队列。可以从头部或尾部向Redis列表添加元素。列表的最大长度为2^32 - 1,也即每个列表支持超过40亿个元素。Redis list的实现为一个双向列表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

场景:Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表、粉丝列表等都可以用Redis的list结构来实现,再比如有的应用使用Redis的list类型实现一个简单的轻量级消息队列,生产者push,消费者pop/bpop。

---------------------------------------------------------------------
#rpush
#lpush
#lrange
192.168.2.1:6379> rpush list1 1         # 创建列表并在尾部添加元素 {追加}
(integer) 1
192.168.2.1:6379> rpush list1 2
(integer) 2
192.168.1.1:6379> lrange list1 0 -1     # 获取list中所有的元素值!
1) "1"
2) "2"
192.168.2.1:6379> lpush list1 one       # 将一个值或者多个值,添加到列表头部 {插入}
(integer) 3
192.168.2.1:6379> lpush list1 two
(integer) 4
192.168.2.1:6379> lrange list1 0 -1
1) "two"
2) "one"
3) "1"
4) "2"
192.168.2.1:6379> lrange list1 1 2      # 通过区间获取具体的值!
1) "one"
2) "1"
---------------------------------------------------------------------
#lpop
#rpop
192.168.2.1:6379> lrange list1 0 -1
1) "two"
2) "one"
3) "1"
4) "2"
192.168.2.1:6379> lpop list1           # 移除list1的第一个元素
"two"
192.168.2.1:6379> rpop list1           # 移除list1的最后一个元素
"2"
192.168.2.1:6379> lrange list1 0 -1
1) "one"
2) "1"
---------------------------------------------------------------------
#lindex
192.168.2.1:6379> lrange list1 0 -1
1) "one"
2) "1"
192.168.2.1:6379> lindex list1 1       # 通过下标获得 list 中的某一个值!
"1"
192.168.2.1:6379> lindex list1 0
"one"
---------------------------------------------------------------------
#Llen
192.168.2.1:6379> lpush list2 one two three three
(integer) 4
192.168.2.1:6379> llen list2           # 返回列表的长度
(integer) 4
---------------------------------------------------------------------
#移除指定的值!
#取关 uid
#Lrem
192.168.2.1:6379> lrange list2 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
192.168.2.1:6379> lrem list2 1 one          # 移除list2列表中指定个数的value,精确匹配
(integer) 1
192.168.2.1:6379> lrange list2 0 -1
1) "three"
2) "three"
3) "two"
192.168.2.1:6379> lrem list2 2 three
(integer) 2
192.168.2.1:6379> lrange list2 0 -1
1) "two"
---------------------------------------------------------------------
#ltrim : list 截断!
192.168.2.1:6379> rpush mylist 'hello-1' 'hello-2' 'hello-3' 'hello-4'
(integer) 4
192.168.2.1:6379> ltrim mylist 1 2    # 通过下标截取指定的长度,这个list已经被改变了,截断了只剩下截取的元素!
OK
192.168.2.1:6379> lrange mylist 0 -1
1) "hello-2"
2) "hello-3"
---------------------------------------------------------------------
#rpoplpush : 移除列表的最后一个元素,将他移动到新的列表中!
192.168.2.1:6379> rpush mylist "hello"
(integer) 3
192.168.2.1:6379> lrange mylist 0 -1
1) "hello-2"
2) "hello-3"
3) "hello"
192.168.2.1:6379> rpoplpush mylist myotherlist      # 将列表最后一个元素移动到新的列表中
"hello"
192.168.2.1:6379> lrange mylist 0 -1                # 查看原来的列表
1) "hello-2"
2) "hello-3"
192.168.2.1:6379> lrange myotherlist 0 -1           # 查看目标列表中,确实存在改值
1) "hello"
---------------------------------------------------------------------
#lset : 将列表中指定下标的值替换为另外一个值,更新操作
192.168.2.1:6379> exists list3             # 判断这个列表是否存在
(integer) 0
192.168.2.1:6379> lset list3 0 'item'      # 如果不存在列表我们去更新就会报错
(error) ERR no such key
192.168.2.1:6379> lpush list3 'value1'
(integer) 1
192.168.2.1:6379> LRANGE list3 0 -1
1) "value1"
192.168.2.1:6379> lset list3 0 'item'      # 如果存在,更新当前下标的值
OK
192.168.2.1:6379> LRANGE list3 0 -1
1) "item"
---------------------------------------------------------------------
#linsert : 将某个具体的value插入到列把你中某个元素的前面或者后面!
192.168.2.1:6379> rpush list4 "hello" "world"
(integer) 2
192.168.2.1:6379> linsert list4 before "world" "other"        # 插入
(integer) 3
192.168.2.1:6379> lrange list4 0 -1
1) "hello"
2) "other"
3) "world"
192.168.2.1:6379> linsert list4 after 'world' 'new'           # 追加
(integer) 4
192.168.2.1:6379> lrange list4 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
---------------------------------------------------------------------

Set(集合)

概述:可以理解为一堆值不重复的列表,类似数学领域中的集合概念,且Redis也提供了针对集合的求交集、并集、差集等操作。set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

场景:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。又或者在微博应用中,每个用户关注的人存在一个集合中,就很容易实现求两个人的共同好友功能。

---------------------------------------------------------------------
#sadd :创建集合并赋值
#smembers :查看集合的值
#sismember :判断某一个值是不是在set集合中!
192.168.2.1:6379> sadd myset "one"              # 创建myset并赋值one
(integer) 1
192.168.2.1:6379> sadd myset "two" "three"
(integer) 2
192.168.2.1:6379> smembers myset                # 查看指定set的所有值
1) "two"
2) "one"
3) "three"
192.168.2.1:6379> sismember myset 'one'         # 判断某一个值是不是在set集合中!
(integer) 1
192.168.2.1:6379> sismember myset 'ten'
(integer) 0
---------------------------------------------------------------------
#scard
192.168.2.1:6379> scard myset                   # 获取set集合中的内容元素个数!
(integer) 3
---------------------------------------------------------------------
#srem :移除
192.168.2.1:6379> srem myset 'one'              # 移除set集合中的指定元素
(integer) 1
192.168.2.1:6379> scard myset
(integer) 2
192.168.2.1:6379> smembers myset
1) "two"
2) "three"
---------------------------------------------------------------------
#set 无序不重复集合。抽随机!
127.0.0.1:6379> smembers myset
1) "lovekuangshen2"
2) "lovekuangshen"
3) "kuangshen"
127.0.0.1:6379> srandmember myset  # 随机抽选出一个元素
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2  # 随机抽选出指定个数的元素
1) "lovekuangshen"
2) "lovekuangshen2"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "lovekuangshen"
2) "lovekuangshen2"

192.168.2.1:6379> sadd myset "one"
(integer) 1
192.168.2.1:6379> smembers myset
1) "one"
2) "two"
3) "three"
192.168.2.1:6379> srandmember myset       # 随机抽选出一个元素
"three"
192.168.2.1:6379> srandmember myset
"two"
192.168.2.1:6379> srandmember myset
"one"
192.168.2.1:6379> srandmember myset 2     # 随机抽选出指定个数的元素
1) "two"
2) "one"
192.168.2.1:6379> srandmember myset 2
1) "two"
2) "three"
---------------------------------------------------------------------
#spop : 随机删除key!
192.168.2.1:6379> smembers myset
1) "one"
2) "two"
3) "three"
192.168.2.1:6379> spop myset          # 随机删除一些set集合中的元素!
"two"
192.168.2.1:6379> spop myset
"one"
192.168.2.1:6379> smembers myset
1) "three"
---------------------------------------------------------------------
#smove : 将一个指定的值,移动到另外一个set集合!
192.168.2.1:6379> sadd myset "hello"
(integer) 1
192.168.2.1:6379> smembers myset
1) "hello"
2) "three"
192.168.2.1:6379> smove myset myset2 "hello"      # 将一个指定的值,移动到另外一个set集合!
(integer) 1
192.168.2.1:6379> smembers myset
1) "three"
192.168.2.1:6379> smembers myset2
1) "hello"
---------------------------------------------------------------------
#微博,B站,共同关注!(并集)
#数字集合类:
#- 差集 SDIFF
#- 交集 SINTER
#- 并集 SUNION
192.168.2.1:6379> sadd set1 a b c
(integer) 3
192.168.2.1:6379> sadd set2 c d e
(integer) 3
192.168.2.1:6379> SDIFF set1 set2        # 差集  不同的
1) "b"
2) "a"
192.168.2.1:6379> SINTER set1 set2       # 交集  相同的; 共同好友就可以这样实现
1) "c"
192.168.2.1:6379> SUNION set1 set2       # 并集  共同的
1) "e"
2) "c"
3) "a"
4) "b"
5) "d"
---------------------------------------------------------------------

Zset(有序集合)

概述:有序集合 类似 无序集合,它只是在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1

场景:Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构。

---------------------------------------------------------------------
#zadd :创建有序集合
192.168.2.1:6379> zadd myzset1 1 'one'                 # 添加一个值
(integer) 1
192.168.2.1:6379> zadd myzset1 2 'two' 3 'three'       # 添加多个值
(integer) 2
192.168.2.1:6379> zrange myzset1 0 -1
1) "one"
2) "two"
3) "three"
---------------------------------------------------------------------
#ZRANGEBYSCORE :实现排序
192.168.2.1:6379> zadd salary 2500 zhangsan          # 添加三个用户
(integer) 1
192.168.2.1:6379> zadd salary 3500 lisi
(integer) 1
192.168.2.1:6379> zadd salary 4500 wangwu
(integer) 1
192.168.2.1:6379> zrangebyscore salary -inf +inf     # 显示全部的用户 从小到大!
1) "zhangsan"
2) "lisi"
3) "wangwu"
192.168.2.1:6379> zrevrange salary 0 -1              # 从大到小进行排序!
1) "wangwu"
2) "lisi"
3) "zhangsan"
192.168.2.1:6379> zrangebyscore salary -inf +inf withscores      # 显示全部的用户并且附带成绩
1) "zhangsan"
2) "2500"
3) "lisi"
4) "3500"
5) "wangwu"
6) "4500"
192.168.2.1:6379> zrangebyscore salary -inf 3500 withscores      # 显示工资小于等于3500员工的升序排序!
1) "zhangsan"
2) "2500"
3) "lisi"
4) "3500"
---------------------------------------------------------------------
# 移除rem中的元素
192.168.2.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
192.168.2.1:6379> zrem salary lisi        # 移除有序集合中的指定元素
(integer) 1
192.168.2.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "wangwu"
192.168.2.1:6379> zcard salary            # 获取有序集合中的个数
(integer) 2
---------------------------------------------------------------------
192.168.2.1:6379> zadd myzset2 1 hello 2 world 3 good
(integer) 3
192.168.2.1:6379> zcount myzset2 1 3      # 获取指定区间的成员数量!
(integer) 3
192.168.2.1:6379> zcount myzset2 1 2
(integer) 2

Hash(哈希)

概述:Map集合,key-map! 时候这个值是一个map集合! 本质和String类型没有太大区别,还是一个简单的key-vlaue!

场景:假设有多个用户及对应的用户信息,可以用来存储以用户ID为key,将用户信息序列化为比如json格式做为value进行保存

Ps. hash变更的数据 user, 尤其是是用户信息之类的,经常变动的信息! hash 更适合于对象的存储,String更加适合字符串存储!

---------------------------------------------------------------------
192.168.2.1:6379> hset myhash1 field1 'cool'      # set一个具体 key-vlaue
(integer) 1
192.168.2.1:6379> hmset myhash1 field1 'hello' field2 'world'      # set多个 key-vlaue
OK
192.168.2.1:6379> hget myhash1 field1             # 获取一个字段值
"hello"
192.168.2.1:6379> hmget myhash1 field1 field2     # 获取多个字段值
1) "hello"
2) "world"
192.168.2.1:6379> hgetall myhash1                 # 获取全部的数据,
1) "field1"
2) "hello"
3) "field2"
4) "world"
192.168.2.1:6379> hdel myhash1 field1             # 删除hash指定key字段!对应的value值也就消失了!
(integer) 1
192.168.2.1:6379> hgetall myhash1
1) "field2"
2) "world"
---------------------------------------------------------------------
#hlen
192.168.2.1:6379> hmset myhash2 field1 'hello' field2 'world'
OK
192.168.2.1:6379> hgetall myhash2
1) "field1"
2) "hello"
3) "field2"
4) "world"
192.168.2.1:6379> hlen myhash2            # 获取hash表的字段数量!
(integer) 2
---------------------------------------------------------------------
192.168.2.1:6379> hexists myhash2 field1  # 判断hash中指定字段是否存在!
(integer) 1
192.168.2.1:6379> hexists myhash2 field3
(integer) 0
---------------------------------------------------------------------
192.168.2.1:6379> hkeys myhash2           # 只获得所有field
1) "field1"
2) "field2"
192.168.2.1:6379> hvals myhash2           # 只获得所有value
1) "hello"
2) "world"
---------------------------------------------------------------------
#incr  decr
192.168.2.1:6379> hset myhash2 field3 '5'           #指定增量!
(integer) 1
192.168.2.1:6379> hincrby myhash2 field3 1
(integer) 6
192.168.2.1:6379> hincrby myhash2 field3 -1
(integer) 5
192.168.2.1:6379> hsetnx myhash2 field4 hello       # 如果不存在则可以设置
(integer) 1
192.168.2.1:6379> hsetnx myhash2 field4 world       # 如果存在则不能设置
(integer) 0
---------------------------------------------------------------------

Redis 事务

Redis事务的概念

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

Redis事务实际上就是一次性、顺序性、排他性的执行一个队列中的一系列命令。所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!

Redis事务没有隔离级别的概念; 批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

Redis不保证原子性; Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis事务相关命令

命令 描述
WATCH key key ... 监视一或多个key, 如果在事务执行之前,被监视的key被其他命令改动,则事务被打断(类似乐观锁 )
MULTI 标记一个事务块的开始
EXEC 执行所有事务块内的命令
DISCARD 取消事务,放弃执行事务块内的所有命令
UNWATCH 取消 WATCH 命令对所有 key 的监视

Redis事务的3个阶段

  • 开启事务(multi)
  • 命令入队(......)
  • 执行事务(exec)

Redis事务使用案例

正常执行事务!

192.168.2.1:6379> multi          # 开启事务
OK
192.168.2.1:6379> set k1 'v1'    # 命令入队
QUEUED
192.168.2.1:6379> set k2 'v2'
QUEUED
192.168.2.1:6379> get k2
QUEUED
192.168.2.1:6379> set k3 'v3'
QUEUED
192.168.2.1:6379> exec           # 执行事务
1) OK
2) OK
3) "v2"
4) OK
192.168.2.1:6379> keys *         #检查事务中的命令是否成功执行
1) "k3"
2) "k2"
3) "k1"

放弃事务!

192.168.2.1:6379> multi          # 开启事务
OK
192.168.2.1:6379> set k4 'v4'
QUEUED
192.168.2.1:6379> set k5 'v5'
QUEUED
192.168.2.1:6379> DISCARD        # 取消事务
OK
192.168.2.1:6379> keys *         # 事务队列中命令都不会被执行!
1) "k3"
2) "k2"
3) "k1"

编译型异常(源代码有问题! 命令有错!) ,事务中所有的命令都不会被执行!

192.168.2.1:6379> multi         # 开始事务
OK
192.168.2.1:6379> set k4 'v4'
QUEUED
192.168.2.1:6379> set k5 'v5'
QUEUED
192.168.2.1:6379> getset k5     # 执行错误的命令
(error) ERR wrong number of arguments for 'getset' command
192.168.2.1:6379> set k6 'v6'
QUEUED
192.168.2.1:6379> exec          # 执行事务报错!
(error) EXECABORT Transaction discarded because of previous errors.
192.168.2.1:6379> keys *        # 所有的命令都不会被执行!
1) "k3"
2) "k2"
3) "k1"

运行时异常(1/0), 如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!

192.168.2.1:6379> set k4 'v4'
OK
192.168.2.1:6379> multi
OK
192.168.2.1:6379> incr k4              # 会执行的时候失败!
QUEUED
192.168.2.1:6379> set k5 'v5'
QUEUED
192.168.2.1:6379> get k5
QUEUED
192.168.2.1:6379> exec                 # 虽然第一条事务命令报错了,但是依旧正常执行成功了!
1) (error) ERR value is not an integer or out of range
2) OK
3) "v5"
192.168.2.1:6379> keys *
1) "k2"
2) "k1"
3) "k5"
4) "k4"
5) "k3"

监控! Watch (面试常问!)

悲观锁:

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

乐观锁:

  • 很乐观,认为什么时候都不会出问题,所以不会上锁! 更新数据的时候去判断一下,在此期间是否有人修改过这个数据

Redis监控测试

正常执行成功!

192.168.2.1:6379> set money 100
OK
192.168.2.1:6379> set out 0
OK
192.168.2.1:6379> watch money         # 监视 money 对象
OK
192.168.2.1:6379> multi
OK
192.168.2.1:6379> decrby money 20
QUEUED
192.168.2.1:6379> incrby out 20
QUEUED
192.168.2.1:6379> exec               # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功!
1) (integer) 80
2) (integer) 20

模拟测试多线程修改值 , 使用 watch 可以当做redis的乐观锁操作!

192.168.2.1:6379> watch money       # 监视 money
OK
192.168.2.1:6379> multi
OK
192.168.2.1:6379> decrby money 10
QUEUED
192.168.2.1:6379> incrby out 10
QUEUED
192.168.2.1:6379> exec              # 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败!
(nil)

那怎么解决这个问题呢?

Redis 持久化

持久化概述

作用:

  • Redis的所有数据都是保存在内存中, 如果没有配置持久化, redis重启后数据就全丢失了, 于是需要开启redis的持久化功能, 将数据保存到磁盘上, 当redis重启后, 可以从磁盘中恢复数据。
  • 那么不定期的通过异步方式保存到磁盘上(半持久化模式); 也可以把每一次数据变化都写入到一个append only file里面(全持久化模式)。 如若在服务器中开启了两种持久化的方式,默认执行AOF持久化方式。

实现方式:

  • RDB持久化:将Reids在内存中的数据库记录,定时dump到磁盘上,类似于快照功能

  • AOF持久化:原理是将Reids的操作日志以追加的方式写入文件,近似实时性

  • 默认redis用的就是RDB方式的持久化,如果两种持久化都开启的话,则优先使用AOF

二者区别:

  • RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
  • AOF持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

应用场景:

  • 愿意牺牲一些性能(选择AOF);

  • 换取更高的缓存一致性,保证数据库最高可能的完整性(选择AOF);

  • 愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(RDB);

RDB(Redis DataBase)

什么是RDB ?

在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照, 它恢复时是将快照文件直接读到内存里。

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

有时候在生产环境我们会将这个文件进行备份!(dump.rdb)

优点:

  • 适合大规模的数据恢复!
  • 对数据的完整性要不高!

缺点:

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

rdb保存的文件是dump.rdb 都是在配置文件中快照中进行配置的!

# 开启RDB快照功能,在900秒内完成一个key值得变动触发快照功能,如若将所有save配置项都注释掉,那么则将关闭RDB快照功能
218 save 900 1
219 save 300 10
220 save 60 10000

# 当RDB快照后台进程失败,不影响用户得写操作
235 stop-writes-on-bgsave-error yes

# 是否将RDB快照文件压缩,关闭后会增加性能
241 rdbcompression yes

# 关闭RDB快照文件的检查校验,增加性能
250 rdbchecksum no

# 快照文件的名称
253 dbfilename dump.rdb

# 快照文件的存储路径
263 dir /usr/local/redis/

测试一下:

只要 60s 内修改了 2 次 key,就会触发 RDB 操作

触发机制

1、save的规则满足的情况下,会自动触发rdb规则

2、执行 flushall 命令,也会触发我们的rdb规则!

3、退出redis,也会产生 rdb 文件!

恢复rdb文件

1、redis在启动的时候会自动检查dump.rdb 恢复其中的数据!

2、查看需要存在的位置

192.168.2.1:6379> config get dir
1) "dir"
2) "/usr/local/redis"

AOF(Append Only File)

什么是AOF ?

  • 就相当于我们的所有命令都记录下来,history,恢复的时候就把这个文件全部在执行一遍!

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

AOF 文件的生成过程具体包括命令追加,文件写入,文件同步三个步骤。

  • AOF保存的是 appendonly.aof 文件
  • Redis 打开 AOF 持久化功能后,Redis 在执行完一个写命令后,都会将执行的写命令追回到 Redis 内部的缓冲区的末尾。这个过程是命令的追加过程。 接下来,缓冲区的写命令会被写入到 AOF 文件,这一过程是文件写入过程。对于操作系统来说,调用write函数并不会立刻将数据写入到硬盘,为了将数据真正写入硬盘,还需要调用fsync函数,调用fsync函数即是文件同步的过程。只有经过文件同步过程,AOF 文件才在硬盘中真正保存了 Redis 的写命令。appendfsync 配置选项正是用来配置将写命令同步到文件的频率的,各个选项的值的含义如表 1 所示。而值为no的话表示为写入AOF文件,但是不进行磁盘同步,根据linux系统默认进行磁盘同步,默认30s,效率是最高的。

优点:

  • 每一次修改都同步,文件的完整会更加好!
  • 每秒同步一次,可能会丢失一秒的数据

缺点:

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

aof保存的文件是 appendonly.aof 都是在配置文件中快照中进行配置的!

# 关闭RDB持久化
218 #save 900 1
219 #save 300 10
220 #save 60 10000

# 开启AOF持久化方式
672 appendonly yes

# 指定AOF持久化的文件名
676 appendfilename "appendonly.aof"

# 同步频率,具体参数见下方表一
702 appendfsync everysec

# 如若设置为yes,则redis执行的命令会存放到缓冲区,待系统自动同步到硬盘
724 no-appendfsync-on-rewrite no

#若当前写入的AOF文件达到了上次rewrite文件大小的100%,则触发rewrite操作
743 auto-aof-rewrite-percentage 100

#设置AOF持久化重写文件的最小值,当达到60M并且符合100%的条件时,则触发rewrite操作
744 auto-aof-rewrite-min-size 64mb

同步频率级别(推荐no | everysec)

频率级别 频率作用
always 每个Redis 命令都要同步写入硬盘;这个级别严重降低 Redis的性能
everysec 每秒执行一次同步,显示的将 写 多个命令同步到硬盘
no 让操作系统来决定如何进行同步

默认是不开启的,我们需要手动进行配置!我们只需要将 appendonly 改为yes就开启了 aof ; 重启 redis 就可以生效了!

触发机制

重写规则说明

aof 默认就是文件的无限追加,文件会越来越大!

如果 aof 文件大于了 64mb,Redis 会重新 fork一个新的进程来将我们的文件进行重写!

RDB转换AOF持久化

RDB持久化的数据,当改为AOF持久化的时候,数据是无法同步的,倘若想要RDB里边的数据,操作如下:

建议:不要轻易改变持久化的方式

[root@redis01 ~]# vim /usr/local/redis/conf/redis.conf
218 save 900 1            #暂时开启rdb
219 save 300 10
220 save 60 10000
672 appendonly no         #暂时关掉aof
:wq!
[root@redis01 ~]# /etc/init.d/redis restart
[root@redis01 ~]# redis-cli -h 192.168.2.1 -p 6379 -a 123456
192.168.2.1:6379> keys *
1) "name"
2) "age"
192.168.2.1:6379> save
192.168.2.1:6379> config set appendonly yes
192.168.2.1:6379> save
192.168.2.1:6379> shutdown save
not connected> exit
# 最后修改配置文件,把RDB改成AOF
[root@redis01 ~]# vim /usr/local/redis/conf/redis.conf
218 #save 900 1            #关闭rdb
219 #save 300 10
220 #save 60 10000
672 appendonly yes         #开启aof
:wq!
[root@redis01 ~]# /etc/init.d/redis restart
[root@redis01 ~]# redis-cli -h 192.168.2.1 -p 6379 -a 123123
192.168.2.1:6379> keys *
1) "age"
2) "name"
192.168.2.1:6379> exit
[root@redis01 ~]# cat /usr/local/redis/appendonly.aof 
*2
$6
SELECT
$1
0
*3
$3
SET
$4
name
$5
lemon
*3
$3
SET
$3
age
$2
18

持久化总结

1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储

2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。

3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化

4、同时开启两种持久化方式

  • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。

  • RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。

5、性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
  • 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

Redis缓存穿透和雪崩

服务的高可用问题!

Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。

另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。

缓存穿透(查不到)

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

解决方案

1、布隆过滤器

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

2、缓存空对象

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

但是这种方法会存在两个问题:

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;

2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

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

这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

当某个key在过期的瞬间, 有大量的请求并发访问, 这类数据一般是热点数据, 由于缓存过期, 会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

解决方案

1、设置热点数据永不过期

从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。

2、加互斥锁

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

缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!

产生雪崩的原因之一, 比如在写本文的时候, 马上就要到双十二零点, 很快就会迎来一波抢购, 这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候, 这批商品的缓存就都过期了。而对这批商品的访问查询, 都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

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

解决方案

1、redis高可用

这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)

2、限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

3、数据预热

数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

posted @ 2023-07-21 15:29  ArMinLi  阅读(9)  评论(0编辑  收藏  举报