redis基础
1、Redis简介
REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。
Redis 是一个基于内存的高性能key-value数据库。
定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
Redis的出色之处不仅仅是性能,Redis是支持保存多种数据结构,此外单个value的最大限制是512MB。 Redis是单进程单线程的.
大家都知道了redis是基于key-value的no sql数据库,因此,先来了解一下关于key相关的知识点
1、任何二进制的序列都可以作为key使用
2、Redis有统一的规则来设计key3、对key-value允许的最大长度是512MB
Redis 为何这么快
面试官:Redis 作为缓存大家都在用,那 Redis 一定很快咯?
我:当然了,官方提供的数据可以达到 100000+ 的 QPS(每秒内的查询次数),这个数据不比 Memcached 差!
面试官:Redis 这么快,它的“多线程模型”你了解吗?(露出邪魅一笑)
我:您是想问 Redis 这么快,为什么还是单线程的吧。Redis 确实是单进程单线程的模型,因为 Redis 完全是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。
既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章的采用单线程的方案了(毕竟采用多线程会有很多麻烦)。
面试官:嗯,是的。那你能说说 Redis 是单线程的,为什么还能这么快吗?
我:可以这么说吧,总结一下有如下四点:
- Redis 完全基于内存,绝大部分请求是纯粹的内存操作,非常迅速,数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度是 O(1)。
- 数据结构简单,对数据操作也简单。
- 采用单线程,避免了不必要的上下文切换和竞争条件,不存在多线程导致的 CPU 切换,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁问题导致的性能消耗。
- 使用多路复用 IO 模型,非阻塞 IO。
redis-cli –raw 设置可以显示中文,不会乱码
2、支持的语言
ActionScript Bash C C# C++ Clojure Common Lisp
Crystal D Dart Elixir emacs lisp Erlang Fancy
gawk GNU Prolog Go Haskell Haxe Io Java Javascript
Julia Lua Matlab mruby Nim Node.js Objective-C
OCaml Pascal Perl PHP Pure Data Python R Racket
Rebol Ruby Rust Scala Scheme Smalltalk Swift Tcl VB VCL*
3、Redis的应用场景到底有哪些??
1、最常用的就是会话缓存
2、消息队列,比如支付
3、活动排行榜或计数
4、发布、订阅消息(消息通知)
5、商品列表、评论列表等
Redis在互联网公司一般有以下应用:
- String:缓存、限流、计数器、分布式锁、分布式Session
- Hash:存储用户信息、用户主页访问量、组合查询
- List:微博关注人时间轴列表、简单队列
- Set:赞、踩、标签、好友关系
- Zset:排行榜
4、Redis安装
安装的大概步骤如下:
Redis是c语言开发的,安装redis需要c语言的编译环境
如果没有gcc需要在线安装:yum install gcc-c++
第一步:获取源码包:wget http://download.redis.io/releases/redis-3.0.0.tar.gz
第二步:解压缩redis:tar zxvf redis-3.0.0.tar.gz
第三步:编译。进入redis源码目录(cd redis-3.0.0)。执行 make
第四步:安装。make install PREFIX=/usr/local/redis
#PREFIX参数指定redis的安装目录
5、Redis数据类型
Redis一共支持五种数据类型
1、string(字符串)
2、hash(哈希)
3、list(列表)
4、set(集合)
5、zset(sorted set 有序集合)
redis 命令参考
string(字符串)
它是redis最基本的数据类型,一个key对应一个value,需要注意是一个键值最大存储512MB。
128.127.0.0.1:6379> set key "hello world"
OK
127.0.0.1:6379> get key
"hello world"
127.0.0.1:6379> getset key "nihao"
"hello world"
127.0.0.1:6379> mset key1 "hi" key2 "nihao" key3 "hello"
OK
127.0.0.1:6379> get key1
"hi"
127.0.0.1:6379> get key2
"nihao"
127.0.0.1:6379> get key3
"hello"
相关命令介绍
set 为一个Key设置value(值)
get 获得某个key对应的value(值)
getset 为一个Key设置value(值)并返回对应的值
mset 为多个key设置value(值)
hash(哈希)
redis hash是一个键值对的集合, 是一个string类型的field和value的映射表,适合用于存储对象
128.127.0.0.1:6379> hset redishash 1 "001"
(integer) 1
127.0.0.1:6379> hget redishash 1
"001"
127.0.0.1:6379> hmset redishash 1 "001" 2 "002"
OK
127.0.0.1:6379> hget redishash 1
"001"
127.0.0.1:6379> hget redishash 2
"002"
127.0.0.1:6379> hmget redishash 1 2
1) "001"
2) "002"
相关命令介绍
hset 将Key对应的hash中的field配置为value,如果hash不存则自动创建,
hget 获得某个hash中的field配置的值
hmset 批量配置同一个hash中的多个field值
hmget 批量获得同一个hash中的多个field值
list(列表)
是redis简单的字符串列表,它按插入顺序排序
128.127.0.0.1:6379> lpush word hi
(integer) 1
127.0.0.1:6379> lpush word hello
(integer) 2
127.0.0.1:6379> rpush word world
(integer) 3
127.0.0.1:6379> lrange word 0 2
1) "hello"
2) "hi"
3) "world"
127.0.0.1:6379> llen word
(integer) 3
相关命令介绍
lpush 向指定的列表左侧插入元素,返回插入后列表的长度
rpush 向指定的列表右侧插入元素,返回插入后列表的长度
llen 返回指定列表的长度
lrange 返回指定列表中指定范围的元素值
应用场景
List 应用场景非常多,也是 Redis 最重要的数据结构之一,比如 Twitter 的关注列表,粉丝列表都可以用 List 结构来实现。
数据结构
List 就是链表,可以用来当消息队列用。Redis 提供了 List 的 Push 和 Pop 操作,还提供了操作某一段的 API,可以直接查询或者删除某一段的元素。
实现方式
Redis List 的是实现是一个双向链表,既可以支持反向查找和遍历,更方便操作,不过带来了额外的内存开销
set(集合)
是string类型的无序集合,也不可重复
128.127.0.0.1:6379> sadd redis redisset
(integer) 1
127.0.0.1:6379> sadd redis redisset1
(integer) 1
127.0.0.1:6379> sadd redis redisset2
(integer) 1
127.0.0.1:6379> smembers redis
1) "redisset1"
2) "redisset"
3) "redisset2"
127.0.0.1:6379> sadd redis redisset2
(integer) 0
127.0.0.1:6379> smembers redis
1) "redisset1"
2) "redisset"
3) "redisset2"
127.0.0.1:6379> smembers redis
1) "redisset1"
2) "redisset3"
3) "redisset"
4) "redisset2"
127.0.0.1:6379> srem redis redisset
(integer) 1
127.0.0.1:6379> smembers redis
1) "redisset1"
2) "redisset3"
3) "redisset2"
相关命令介绍
sadd 添加一个string元素到key对应的set集合中,成功返回1,如果元素存在返回0
smembers 返回指定的集合中所有的元素
srem 删除指定集合的某个元素
应用场景
Redis Set 对外提供的功能和 List 一样是一个列表,特殊之处在于 Set 是自动去重的,而且 Set 提供了判断某个成员是否在一个 Set 集合中。
zset(sorted set 有序集合)
是string类型的有序集合,也不可重复
sorted set中的每个元素都需要指定一个分数,根据分数对元素进行升序排序
如果多个元素有相同的分数,则以字典序进行升序排序
sorted set 因此非常适合实现排名
128.127.0.0.1:6379> zadd nosql 0 001
(integer) 1
127.0.0.1:6379> zadd nosql 0 002
(integer) 1
127.0.0.1:6379> zadd nosql 0 003
(integer) 1
127.0.0.1:6379> zcount nosql 0 0
(integer) 3
127.0.0.1:6379> zcount nosql 0 3
(integer) 3
127.0.0.1:6379> zrem nosql 002
(integer) 1
127.0.0.1:6379> zcount nosql 0 3
(integer) 2
127.0.0.1:6379> zscore nosql 003
"0"
127.0.0.1:6379> zrangebyscore nosql 0 10
1) "001"
2) "003"
127.0.0.1:6379> zadd nosql 1 003
(integer) 0
127.0.0.1:6379> zadd nosql 1 004
(integer) 1
127.0.0.1:6379> zrangebyscore nosql 0 10
1) "001"
2) "003"
3) "004"
127.0.0.1:6379> zadd nosql 3 005
(integer) 1
127.0.0.1:6379> zadd nosql 2 006
(integer) 1
127.0.0.1:6379> zrangebyscore nosql 0 10
1) "001"
2) "003"
3) "004"
4) "006"
5) "005"
相关命令介绍
zadd 向指定的sorteset中添加1个或多个元素
zrem 从指定的sorteset中删除1个或多个元素
zcount 查看指定的sorteset中指定分数范围内的元素数量
zscore 查看指定的sorteset中指定分数的元素
zrangebyscore 查看指定的sorteset中指定分数范围内的所有元素
使用场景:Sorted Set 可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。
当你需要一个有序的并且不重复的集合列表,那么可以选择 Sorted Set 结构。
和 Set 相比,Sorted Set关联了一个 Double 类型权重的参数 Score,使得集合中的元素能够按照 Score 进行有序排列,Redis 正是通过分数来为集合中的成员进行从小到大的排序。
实现方式:Redis Sorted Set 的内部使用 HashMap 和跳跃表(skipList)来保证数据的存储和有序,HashMap 里放的是成员到 Score 的映射。
而跳跃表里存放的是所有的成员,排序依据是 HashMap 里存的 Score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
五种数据的区别
6、键值相关的命令
128.127.0.0.1:6379> exists key
(integer) 1
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379> exists key100
(integer) 0
127.0.0.1:6379> get key
"nihao,hello"
127.0.0.1:6379> get key1
"hi"
127.0.0.1:6379> del key1
(integer) 1
127.0.0.1:6379> get key1
(nil)
127.0.0.1:6379> rename key key0
OK
127.0.0.1:6379> get key
(nil)
127.0.0.1:6379> get key0
"nihao,hello"
127.0.0.1:6379> type key0
string
exists 确认key是否存在
del 删除key
expire 设置Key过期时间(单位秒)
persist 移除Key过期时间的配置
rename 重命名key
type 返回值的类型
7、Redis服务相关的命令
128.127.0.0.1:6379> select 0
OK
127.0.0.1:6379> info
# Server
redis_version:3.0.6
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:347e3eeef5029f3
redis_mode:standalone
os:Linux 3.10.0-693.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.5
process_id:31197
run_id:8b6ec6ad5035f5df0b94454e199511084ac6fb12
tcp_port:6379
uptime_in_seconds:8514
uptime_in_days:0
hz:10
lru_clock:14015928
config_file:/usr/local/redis/redis.conf
-------------------省略N行
127.0.0.1:6379> CONFIG GET 0
(empty list or set)
127.0.0.1:6379> CONFIG GET 15
(empty list or set)
slect 选择数据库(数据库编号0-15)
quit 退出连接
info 获得服务的信息与统计
monitor 实时监控
config get 获得服务配置
flushdb 删除当前选择的数据库中的key
flushall 删除所有数据库中的key
8、Redis的发布与订阅
Redis发布与订阅(pub/sub)是它的一种消息通信模式,一方发送信息,一方接收信息。
下图是三个客户端同时订阅同一个频道
下图是有新信息发送给频道1时,就会将消息发送给订阅它的三个客户端
9、Redis事务
Redis事务可以一次执行多条命令,发送exec命令前放入队列缓存,结束事务,收到exec命令后执行事务操作,如果某一命令执行失败,其它命令仍可继续执行,一个事务执行的过程中,其它客户端提交的请求不会被插入到事务执行的命令列表中。
很多情况下我们需要一次执行不止一个命令,而且需要其同时成功或者失败。为了保证多条命令组合的原子性,Redis 提供了简单的事务功能以及集成 Lua 脚本来解决这个问题。
Redis 提供了简单的事务功能,将一组需要一起执行的命令放到 multi 和 exec 两个命令之间。Multi 命令代表事务开始,exec 命令代表事务结束,它们之间的命令是原子顺序执行的
一个事务经历三个阶段
开始事务(命令:multi)
命令执行
结束事务(命令:exec)128.127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key key1
QUEUED
127.0.0.1:6379> get key
QUEUED
127.0.0.1:6379> rename key key001
QUEUED
127.0.0.1:6379> exec1) OK
2) “key1”
3) OK
Redis 提供了简单的事务,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算,主要有以下几点:
不够满足原子性。一个事务执行过程中,其他事务或 client 是可以对相应的 key 进行修改的(并发情况下,例如电商常见的超卖问题),想要避免这样的并发性问题就需要使用 WATCH 命令,但是通常来说,必须经过仔细考虑才能决定究竟需要对哪些 key 进行 WATCH 加锁。然而,额外的 WATCH 会增加事务失败的可能,而缺少必要的 WATCH 又会让我们的程序产生竞争条件。
- WATCH:监控一个或多个key值
- UNWATCH:取消对所有key值的监控
为了弥补Redis单线程事务在开启后执行前可被“队头插队”的问题,Redis提供了Watch指令,它可以在事务开启前指定多个key,在事务执行前会校验key值是否发生改变,如果在unwatch之前变化过,则事务也不会执行,可以利用WATCH实现简单的CAS操作,假定key值不会被其他应用修改,一旦修改便回退选择放弃或者重新执行
后执行的命令无法依赖先执行命令的结果。由于事务中的所有命令都是互相独立的,在遇到 exec 命令之前并没有真正的执行,所以我们无法在事务中的命令中使用前面命令的查询结果。我们唯一可以做的就是通过 watch 保证在我们进行修改时,如果其它事务刚好进行了修改,则我们的修改停止,然后应用层做相应的处理。
事务中的每条命令都会与 Redis 服务器进行网络交互。Redis 事务开启之后,每执行一个操作返回的都是 queued,这里就涉及到客户端与服务器端的多次交互,明明是需要一次批量执行的 n 条命令,还需要通过多次网络交互,显然非常浪费(这个就是为什么会有 pipeline 的原因,减少 RTT 的时间)。
在执行是语法错误的命令,如 incr abc,本来应该是数值的,但是执行字符串,redis客户端在执行时并不会检测,除非是直接执行 incr,redis会提示语法错误并且回滚,但incr abc不会检测,并且加入到了执行的队列中,其他先执行的命令就不会回滚
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set b bbbbbbbbbb QUEUED 127.0.0.1:6379(TX)> incr abb QUEUED 127.0.0.1:6379(TX)> exec 1) OK 2) (integer) 1 127.0.0.1:6379> get b "bbbbbbbbbb" 127.0.0.1:6379>
Redis 事务缺陷的解决 – Lua
Lua 是一个小巧的脚本语言,用标准 C 编写,几乎在所有操作系统和平台上都可以编译运行。一个完整的 Lua 解释器不过 200k,在目前所有脚本引擎中,Lua 的速度是最快的,这一切都决定了 Lua 是作为嵌入式脚本的最佳选择。
Redis 2.6 版本之后内嵌了一个 Lua 解释器,可以用于一些简单的事务与逻辑运算,也可帮助开发者定制自己的 Redis 命令(例如:一次性的执行复杂的操作,和带有逻辑判断的操作),在这之前,必须修改源码。
在 Redis 中执行 Lua 脚本有两种方法:eval 和 evalsha,这里以 eval 做为案例介绍:
eval 语法:
eval script numkeys key [key ...] arg [arg ...]
其中:
- script 一段 Lua 脚本或 Lua 脚本文件所在路径及文件名
- numkeys Lua 脚本对应参数数量
- key [key …] Lua 中通过全局变量 KEYS 数组存储的传入参数
- arg [arg …] Lua 中通过全局变量 ARGV 数组存储的传入附加参数
EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
Lua 执行流程图:
SCRIPT LOAD 与 EVALSHA 命令
对于不立即执行的 Lua 脚本,或需要重用的 Lua 脚本,可以通过 SCRIPT LOAD 提前载入 Lua 脚本,这个命令会立即返回对应的 SHA1 校验码
当需要执行函数时,通过 EVALSHA 调用 SCRIPT LOAD 返回的 SHA1 即可
SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
"232fd51614574cf0867b83d384a5e898cfd24e5a"
EVALSHA "232fd51614574cf0867b83d384a5e898cfd24e5a" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
通过 Lua 脚本执行 Redis 命令
在 Lua 脚本中,只要使用 redis.call()
或 redis.pcall()
传入 Redis 命令就可以直接执行:
eval "return redis.call('set',KEYS[1],'bar')" 1 foo --等同于在服务端执行 set foo bar
案例,使用 Lua 脚本实现访问频率限制:
--
-- KEYS[1] 要限制的ip
-- ARGV[1] 限制的访问次数
-- ARGV[2] 限制的时间
--
local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]
local is_exists = redis.call("EXISTS", key)
if is_exists == 1then
if redis.call("INCR", key) > limit then
return0
else
return1
end
else
redis.call("SET", key, 1)
redis.call("EXPIRE", key, expire_time)
return1
end
使用方法,通过:
eval(file_get_contents(storage_path("limit.lua")), 3, "127.0.0.1", "3", "100");
10、Redis安全配置
可以通过修改配置文件设备密码参数来提高安全性
# requirepass foobared
去掉注释#号就可以配置密码
没有配置密码的情况下查询如下
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) ""
配置密码之后,就需要进行认证
127.0.0.1:6379> CONFIG GET requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> AUTH foobared #认证
OK
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) "foobared"
11、Redis持久化
Redis持久有两种方式:Snapshotting(快照),Append-only file(AOF)
Snapshotting(快照) rdb
1、将存储在内存的数据以快照的方式写入二进制文件中,如默认dump.rdb中
2、save 900 1 #900秒内如果超过1个Key被修改,则启动快照保存
3、save 300 10 #300秒内如果超过10个Key被修改,则启动快照保存
4、save 60 10000 #60秒内如果超过10000个Key被修改,则启动快照保存
Append-only file(AOF)
1、使用AOF持久时,服务会将每个收到的写命令通过write函数追加到文件中(appendonly.aof)
2、AOF持久化存储方式参数说明
appendonly yes #开启AOF持久化存储方式
appendfsync always #收到写命令后就立即写入磁盘,效率最差,效果最好
appendfsync everysec #每秒写入磁盘一次,效率与效果居中
appendfsync no #完全依赖OS,效率最佳,效果没法保证
12、Redis 性能测试
自带相关测试工具
[root@test ~]# redis-benchmark --help
Usage: redis-benchmark [-h ] [-p ] [-c ] [-n [-k ]
-h Server hostname (default 127.0.0.1)
-p Server port (default 6379)
-s Server socket (overrides host and port)
-a Password for Redis Auth
-c Number of parallel connections (default 50)
-n Total number of requests (default 100000)
-d Data size of SET/GET value in bytes (default 2)
-dbnum SELECT the specified db number (default 0)
-k 1=keep alive 0=reconnect (default 1)
-r Use random keys for SET/GET/INCR, random values for SADD
Using this option the benchmark will expand the string __rand_int__
inside an argument with a 12 digits number in the specified range
from 0 to keyspacelen-1. The substitution changes every time a command
is executed. Default tests use this to hit random keys in the
specified range.
-P Pipeline requests. Default 1 (no pipeline).
-q Quiet. Just show query/sec values
--csv Output in CSV format
-l Loop. Run the tests forever
-t Only run the comma separated list of tests. The test
names are the same as the ones produced as output.
-I Idle mode. Just open N idle connections and wait.
Examples:
Run the benchmark with the default configuration against 127.0.0.1:6379:
$ redis-benchmark
Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:
$ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20
Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:
$ redis-benchmark -t set -n 1000000 -r 100000000
Benchmark 127.0.0.1:6379 for a few commands producing CSV output:
$ redis-benchmark -t ping,set,get -n 100000 --csv
Benchmark a specific command line:
$ redis-benchmark -r 10000 -n 10000 eval 'return redis.call("ping")' 0
Fill a list with 10000 random elements:
$ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__
On user specified command lines __rand_int__ is replaced with a random integer
with a range of values selected by the -r option.
实际测试同时执行100万的请求
[root@test ~]# redis-benchmark -n 1000000 -q
PING_INLINE: 152578.58 requests per second
PING_BULK: 150308.14 requests per second
SET: 143266.47 requests per second
GET: 148632.58 requests per second
INCR: 145857.64 requests per second
LPUSH: 143781.45 requests per second
LPOP: 147819.66 requests per second
SADD: 138350.86 requests per second
SPOP: 134282.27 requests per second
LPUSH (needed to benchmark LRANGE): 141302.81 requests per second
LRANGE_100 (first 100 elements): 146756.67 requests per second
LRANGE_300 (first 300 elements): 148104.27 requests per second
LRANGE_500 (first 450 elements): 152671.75 requests per second
LRANGE_600 (first 600 elements): 148104.27 requests per second
MSET (10 keys): 132731.62 requests per second
13、Redis的备份与恢复
Redis自动备份有两种方式
第一种是通过dump.rdb文件实现备份
第二种使用aof文件实现自动备份
dump.rdb备份
Redis服务默认的自动文件备份方式(AOF没有开启的情况下),在服务启动时,就会自动从dump.rdb文件中去加载数据。
具体配置在redis.conf
save 900 1
save 300 10
save 60 10000
RDB有两种方式,save和bgsave
save,会阻塞服务器的其他操作,直到save执行完成,所以,这个期间的所有命令请求都会被拒绝。对客户端影响较大。
BGSave,由子进程进行数据保存,期间redis仍然可以继续处理客户端请求。为了防止竞争和冲突,bgsave被设计成和save/bgrewriteaof操作互斥。
Redis服务器默认每100毫秒执行一次,如果数据库修改次数(dirty计数器)大于设置的阈值,并且距离上次执行保存的时间(lastsave属性)大于设置的阈值,则执行保存操作。
因为是统一批量的保存操作,rdb文件有二进制存储、结构紧凑、空间消耗少、恢复速度快等特点,在持久化方案上不可或缺。
也可以手工执行save命令实现手动备份
128.127.0.0.1:6379> set name key
OK
127.0.0.1:6379> SAVE
OK
127.0.0.1:6379> set name key1
OK
127.0.0.1:6379> BGSAVE
Background saving started
redis快照到dump文件时,会自动生成dump.rdb的文件
# The filename where to dump the DB
dbfilename dump.rdb
-rw-r--r-- 1 root root 253 Apr 17 20:17 dump.rdb
SAVE命令表示使用主进程将当前数据库快照到dump文件
BGSAVE命令表示,主进程会fork一个子进程来进行快照备份
两种备份不同之处,前者会阻塞主进程,后者不会。
恢复举例
# Note that you must specify a directory here, not a file name.
dir /usr/local/redisdata/
备份文件存储路径
127.0.0.1:6379> CONFIG GET dir
1) "dir"
2) "/usr/local/redisdata"
127.0.0.1:6379> set key 001
OK
127.0.0.1:6379> set key1 002
OK
127.0.0.1:6379> set key2 003
OK
127.0.0.1:6379> save
OK
将备份文件备份到其它目录
[root@test ~]# ll /usr/local/redisdata/
total 4
-rw-r--r-- 1 root root 49 Apr 17 21:24 dump.rdb
[root@test ~]# date
Tue Apr 17 21:25:38 CST 2018
[root@test ~]# cp ./dump.rdb /tmp/
删除数据
128.127.0.0.1:6379> del key1
(integer) 1
127.0.0.1:6379> get key1
(nil)
关闭服务,将原备份文件拷贝回save备份目录
[root@test ~]# redis-cli -a foobared shutdown
[root@test ~]# lsof -i :6379
[root@test ~]# cp /tmp/dump.rdb /usr/local/redisdata/
cp: overwrite ‘/usr/local/redisdata/dump.rdb’? y
[root@test ~]# redis-server /usr/local/redis/redis.conf &
[1] 31487
登录查看数据是否恢复
[root@test ~]# redis-cli -a foobared
127.0.0.1:6379> mget key key1 key2
1) "001"
2) "002"
3) "003"
AOF自动备份
然而,因为bgsave的周期间隔和保存触发条件等原因,在服务器宕机时,不可避免的会丢失一部分最新的数据。这就需要一些辅助手段来做持久化补充。
RDB保存的是键值对,而AOF则用来保存写命令。
AOF刷盘策略,由于aof追加动作是和客户端请求处理串行执行的,所以每次都刷盘对性能影响较大,因此都是先追加到aof_buf缓存区里,而是否同步到AOF文件中则依赖always、everysec(默认)、no的刷盘配置。想比everysec ,always对性能影响较大,而no则容易丢失数据。
AOF文件重写压缩,AOF因为保存了请求命令,自然要比RDB更大,并且随着程序的运行,会越来越大,然而,文件中有很多冗余的命令数据是可以压缩的,因为对于某个键值对,某一时刻只会有一个状态。
redis服务默认是关闭此项配置
###### APPEND ONLY MODE #####
、#####
appendonly no
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof"
# appendfsync always
appendfsync everysec
# appendfsync no
配置文件的相关参数,前面已经详细介绍过。
AOF文件备份,是备份所有的历史记录以及执行过的命令,和mysql binlog很相似,在恢复时就是重新执次一次之前执行的命令,需要注意的就是在恢复之前,和数据库恢复一样需要手工删除执行过的del或误操作的命令。
AOF与dump备份不同
1、aof文件备份与dump文件备份不同
2、服务读取文件的优先顺序不同,会按照以下优先级进行启动
如果只配置AOF,重启时加载AOF文件恢复数据
如果同时 配置了RBD和AOF,启动是只加载AOF文件恢复数据
如果只配置RBD,启动时将加载dump文件恢复数据
注意:只要配置了aof,但是没有aof文件,这个时候启动的数据库会是空的
14、Redis 生产优化介绍
1、内存管理优化
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
list的成员与值都不太大的时候会采用紧凑格式来存储,相对内存开销也较小,在linux环境运行Redis时,如果系统的内存比较小,这个时候自动备份会有可能失败,需要修改系统的vm.overcommit_memory 参数,这个参数是linux系统的内存分配策略。
0 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。 1 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。 2 表示内核允许分配超过所有物理内存和交换空间总和的内存
Redis官方的说明是,建议将vm.overcommit_memory的值修改为1,可以用下面几种方式进行修改:
(1)编辑/etc/sysctl.conf ,改vm.overcommit_memory=1,然后sysctl -p 使配置文件生效
(2)sysctl vm.overcommit_memory=1
(3)echo 1 > /proc/sys/vm/overcommit_memory
2、内存预分配
3、持久化机制
定时快照:效率不高,会丢失数据 AOF:保持数据完整性(一个实例的数量不要太大2G最大)
优化总结
1)根据业务需要选择合适的数据类型
2)当业务场景不需持久化时就关闭所有持久化方式(采用ssd磁盘来提升效率)
3)不要使用虚拟内存的方式,每秒实时写入AOF
4)不要让REDIS所在的服务器物理内存使用超过内存总量的3/5
5)要使用maxmemory
6)大数据量按业务分开使用多个redis实例
15、Redis集群应用
集群是将多个redis实例集中在一起,实现同一业务需求,或者实现高可用与负载均衡
到底有哪些集群方案呢??
1、haproxy+keepalived+redis集群
1)通过redis的配置文件,实现主从复制、读写分离
2)通过haproxy的配置,实现负载均衡,当从故障时也会及时从集群中T除
3)利用keepalived来实现负载的高可用
2、redis官方Sentinel集群管理工具
1)sentinel负责对集群中的主从服务监控、提醒和自动故障转移
2)redis集群负责对外提供服务
3、Redis Cluster
Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的。
1)官方推荐,毋庸置疑。
2)去中心化,集群最大可增加1000个节点,性能随节点增加而线性扩展。
3)管理方便,后续可自行增加或摘除节点,移动分槽等等。
4)简单,易上手。
16、Pipeline(管道)机制
Redis 提供了批量操作命令(例如 mget、mset 等),有效地节约 RTT。但大部分命令是不支持批量操作的,例如要执行 n 次 hgetall 命令,并没有 mhgetall 命令存在,需要消耗 n 次 RTT。
Redis
的客户端和服务端可能部署在不同的机器上。例如客户端在北京,Redis
服务端在上海,两地直线距离约为 1300 公里,那么 1 次 RTT
时间 = 1300×2/(300000×2/3) = 13 毫秒(光在真空中 传输速度为每秒 30 万公里,这里假设光纤为光速的 2/3),那么客户端在 1 秒 内大约只能执行 80 次左右的命令,这个和 Redis
的高并发高吞吐特性背道而驰。
Pipeline(流水线)机制能改善上面这类问题,它能将一组 Redis
命令进 行组装,通过一次 RTT
传输给 Redis
,再将这组 Redis
命令的执行结果按顺序返回给客户端。
不使用 Pipeline 的命令执行流程:
使用 Pipeline 的命令执行流程:
Redis
的流水线是一种通信协议,没有办法通过客户端演示给大家,这里以 Jedis
为例,通过 Java API
或者使用 Spring
操作它(代码来源于互联网):
/**
* 测试Redis流水线
* @author liu
*/
publicclass TestPipelined {
/**
* 使用Java API测试流水线的性能
*/
@SuppressWarnings({ "unused", "resource" })
@Test
public void testPipelinedByJavaAPI() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(20);
jedisPoolConfig.setMaxTotal(10);
jedisPoolConfig.setMaxWaitMillis(20000);
JedisPool jedisPool = new JedisPool(jedisPoolConfig,"localhost",6379);
Jedis jedis = jedisPool.getResource();
long start = System.currentTimeMillis();
// 开启流水线
Pipeline pipeline = jedis.pipelined();
// 测试10w条数据读写
for(int i = 0; i < 100000; i++) {
int j = i + 1;
pipeline.set("key" + j, "value" + j);
pipeline.get("key" + j);
}
// 只执行同步但不返回结果
//pipeline.sync();
// 以list的形式返回执行过的命令的结果
List<Object> result = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
// 计算耗时
System.out.println("耗时" + (end - start) + "毫秒");
}
/**
* 使用RedisTemplate测试流水线
*/
@SuppressWarnings({ "resource", "rawtypes", "unchecked", "unused" })
@Test
public void testPipelineBySpring() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
RedisTemplate redisTemplate = (RedisTemplate)applicationContext.getBean("redisTemplate");
List<Object> list = redisTemplate.executePipelined(
(RedisConnection connection)->{
//此行可以略去,调用与否均可
//connection.openPipeline();
//模拟一千次写入与查询
for(int i =0 ;i<1000;i++){
//选择直接复用redisTemplate的序列化方法
connection.set(keySerializer.serialize("testKey"),
valueSerializer.serialize("value"));
connection.get(keySerializer.serialize("testKey"));
}
//不可以调用connection.closePipeline()方法,
因为本来就有隐式的代理调用收集了返回值
//List<Object> closePipeline =connection.closePipeline();
//依靠代理的返回,此处应该固定返回null
return null;
},redisTemplate.getValueSerializer());
}
}
网上写的测试结果为:使用 Java API 耗时在 550ms 到 700ms 之间,也就是不到 1s 就完成了 10 万次读写,使用 Spring 耗时在 1100ms 到 1300ms 之间。这个与之前一条一条命令使用,1s 内就发送几十几百条(客户端和服务端距离导致)命令的差距不是一般的大了。
注意,这里只是为了测试性能而已,当你要执行很多的命令并返回结果的时候,需要考虑 List 对象的大小,因为它会“吃掉”服务器上许多的内存空间,严重时会导致内存不足,引发 JVM
溢出异常,所以在工作环境中,是需要读者自己去评估的,可以考虑使用迭代的方式去处理。
– 缺点—
注意,Pipelined并不能保证原子性,即pipelined执行的内容可能会被其他客户端或是线程的指令”插队”,若想要原子性操作,需要使用事务。
17、pipeline 和原生批处理区别
原生批处理:提供了批量操作命令(例如mget
、mset
等),有效地节约RTT
。但大部分命令是不支持批量操作的,例如要执行n次hgetall
命令,并没有mhgetall
命令存在,需要消耗n次RTT
。Redis
的客户端和服务端可能部署在不同的机器上。例如客户端在北京,Redis
服务端在上海,两地直线距离约为1300公里,那么1次RTT
时间=1300×2/(300000×2/3)=13毫秒(光在真空中传输速度为每秒30万公里,这里假设光纤为光速的2/3),那么客户端在1秒内大约只能执行80次左右的命令,这个和Redis
的高并发高吞吐特性背道而驰。
Pipeline(流水线):机制能改善上面这类问题,它能将一组Redis
命令进行组装,通过一次RTT
传输给Redis
,再将这组Redis
命令的执行结果按顺序返回给客户端。
原生批量命令(mset
, mget
)与Pipeline对比
可以使用Pipeline模拟出批量操作的效果,但是在使用时要注意它与原生批量命令的区别,具体包含以下几点:
- 原生批量命令是原子的,Pipeline是非原子的。
- 原生批量命令是一个命令对应多个key,
Pipeline
支持多个命令。 - 原生批量命令是
Redis
服务端支持实现的,而Pipeline
需要服务端和客户端的共同实现。
Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成。
Pipeline
只能操作一个Redis
实例,但是即使在分布式Redis
场景中,也可以作为批量操作的重要优化手段;
工作总结遇到的问题:
1、集群中使用lua脚本执行:多个命令对不同的键操作,报错”CROSSSLOT Keys in request don’t hash to the same slot”主要原因为:redis 集群中,会将键分配的不同的槽位上,然后分配到对应的机器上,当操作的键为一个的时候,自然没问题,但如果操作的键为多个的时候,集群如何知道这个操作落到那个机器呢?比如简单的mget
命令,mget test1 test2 test3
针对这个问题,redis官方为我们提供了hash tag
这个方法来解决,什么意思呢,我们取键中的一段来计算 hash,计算落入那个槽中,这样同一个功能不同的 key 就可以落入同一个槽位了,hash tag 是通过{}
这对括号括起来的字符串,比如上面的,我们改为mset lua{yes} fascinating redis{yes} powerful
,就可以执行成功了,我这里 mset 这个操作落到了 7002 端口的机器。