转载

redis 实战分析

简介

后台开发日新月异,各种高高能高挑战!~~~ 例如:数据高并发的读写、海量数据的读写、对扩展性要求高的数据,要怎么操作达到性能最大化?分布式架构,如何做session共享?等等一些列问题困惑着前进的步伐…

redis 入门

redis 是 单进程

单进程模型来处理客户端的请求。对读写等事件的响应是通过对epoll函数的包装来做到的。Redis的实际处理速度完全依靠主进程的执行效率。

epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,

它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

数据库

默认16个数据库,类似数组下表从零开始,初始默认使用零号库

select命令切换数据库

dbsize查看当前数据库的key的数量

flushdb:清空当前库

Flushall:通杀全部库

统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上

序列是从零开始

默认端口

6379

功能

内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务

取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面

模拟类似于HttpSession这种需要设定过期时间的功能

发布、订阅消息系统

定时器、计数器

redis 数据类型

string(字符串)

string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。

string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

例如:

set/get/del/append/strlen

Incr/decr/incrby/decrby,一定要是数字才能进行加减

getrange/setrange :获取指定区间范围内的值,类似between......and的关系

从零到负一表示全部                                   

mset:同时设置一个或多个 key-value 对。

mget:获取所有(一个或多个)给定 key 的值。

msetnx:同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

getset:将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

简单一句话,先get然后立即set

hash(哈希,类似java里的Map)

Redis hash 是一个键值对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

类似Java里面的Map<String,Object>

例如:

hset/hget/hmset/hmget/hgetall/hdel

hlen

hexists key 在key里面的某个值的key

hkeys/hvals

hincrby/hincrbyfloat

hsetnx

list(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。

它的底层实际是个链表

例如:

lpush/rpush/lrange

lpop/rpop

lindex,按照索引下标获得元素(从上到下).

llen

lrem key 删N个value

ltrim key 开始index 结束index,截取指定范围的值后再赋值给key

rpoplpush 源列表 目的列表

lset key index value

linsert key  before/after 值1 值2

它是一个字符串链表,left、right都可以插入添加;

如果键不存在,创建新的链表;

如果键已存在,新增内容;

如果值全移除,对应的键也就消失了。

链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。

set(集合)

Redis的Set是string类型的无序集合。它是通过HashTable实现实现的。

例如:

sadd/smembers/sismember

scard,获取集合里面的元素个数

srem key value 删除集合中元素

srandmember key 某个整数(随机出几个数)

spop key 随机出栈

smove key1 key2 在key1里某个值      作用是将key1里的某个值赋给key2

数学集合类 差集:sdiff 交集:sinter 并集:sunion

zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。

redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

例如:

zadd/zrange

zrangebyscore key 开始score 结束score

zrem key 某score下对应的value值,作用是删除元素

zcard/zcount key score区间/zrank key values值,作用是获得下标值/zscore key 对应值,获得分数

zrevrank key values值,作用是逆序获得下标值

zrevrange

zrevrangebyscore  key 结束score 开始score

key 命令

keys *

exists key的名字,判断某个key是否存在

move key db   --->当前库就没有了,被移除了

expire key 秒钟:为给定的key设置过期时间

ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期

type key 查看你的key是什么类型

redis.conf 配置信息

# Redis 配置说明

# 单位: 指定内存大小时
# 如下形式出现:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# 单位不区分大小写,1GB 1Gb 1gB 一个意思

################################## INCLUDES ###################################
include 包括一个或多个配置文件
如果想包含指定配置里面的信息 则可如下:
有一个标准的模板,去所有的redis服务器还需要
注意:include 命令最后执行,之前的值会被此配置覆盖

# include ./path/to/local.conf
# include c:/path/to/other.conf

################################ GENERAL  #####################################
Windows版本不支持 daemonize和pidfile,但是可以把Redis作为Windows服务,并指定日志文件。
Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
 daemonize no

当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
  pidfile /var/run/redis.pid

指定Redis监听端口,默认端口为6379
port 6379


设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值来达到想要的效果

tcp-backlog 511

默认情况下,Redis监听所有网络接口连接
也可以 监听指定的网络
#
# Examples:
# 绑定的主机地址
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1


指定用于监听的UNIX套接字的文件
默认情况,不会监听

# unixsocket /tmp/redis.sock
# unixsocketperm 700

# Close the connection after a client is idle for N seconds (0 to disable)
#当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 0

# TCP keepalive.
#
# 非零,多少间隔会触发一个心跳!
# 作用:检测网络情况
# 需要根据系统内核参数进行配置
# 单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60 

tcp-keepalive 0

#指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel notice

# 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile "Logs/redis_log.txt"

#是否自动
# automatically be enabled.

#设置的系统日志功能 是否把日志输出到syslog中
syslog-enabled yes

# 指定syslog里的日志标志
syslog-ident redis

指定syslog设备,值可以是USER或LOCAL0-LOCAL7
syslog-facility

#设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
databases 16

################################ SNAPSHOTTING  ################################

RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,
默认
是1分钟内改了1万次,
或5分钟内改了10次,
或15分钟内改了1次。

如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以

指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
如下:分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

save 900 1
save 300 10
save 60 10000

快照功能 默认是关闭的,快照功能要使用正确,否则会发生灾难。
redis重启 会自动重写

如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制

stop-writes-on-bgsave-error yes


rdbcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用
LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能

指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes


rdbchecksum:在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约
10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能

rdbchecksum yes

#指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb


#指定本地数据库存放目录
dir ./

################################# REPLICATION #################################
主从复制
Redis复制过程是异步的,主从会执行同步,
如果主机丢失,从机会继续工作,直到主机重连。
复制的过程是自动的,不需要干预。

# 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
# slaveof <masterip> <masterport>

#
# 当master服务设置了密码保护时,slav服务连接master的密码
# masterauth <master-password>


#主机丢失时
# yes 选项  从机继续工作 但不同步
# no 选项  从机继续同步
slave-serve-stale-data yes

#配置 从机的只读属性 实现保护
slave-read-only yes

# 复制同步策略:磁盘或套接字
# 从机 将不再继续复制,只接收差异
# 数据库数据的传输过程是 主机 到 从机
# 磁盘支持:Redis创建一个新进程写入数据库,并由主进程传输到从机
# 无盘:Redis创建一个新进程写RDB文件

repl-diskless-sync no

# 磁盘复制功能,可以设置延迟(使从机延迟等待)
repl-diskless-sync-delay 5


# repl 响应时间一般是10 
# 需要从I/O读写、数据量等衡量设置
# repl-timeout 60

# 增加一个延迟 达到 传输更少的数据到从机
repl-disable-tcp-nodelay no

# 设置传输包的大小,数据积压会形成一个缓冲区,全同步会受影响
# repl-backlog-size 1mb

# 主从断开后 多少秒进行释放缓存区
# repl-backlog-ttl 3600


#从机可能有很多个,所以有优先级 越小优先级越低 0标志着奴隶不能够执行
slave-priority 100

# 如果主机个数过低,有可能停止写入操作,从机有可能延迟。
# 此选项指定多少个从机可写入
# min-slaves-to-write 3
# min-slaves-max-lag 10
#

# 默认为0
# min-slaves-max-lag is set to 10.

################################## SECURITY ###################################


# 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
# requirepass foobared


# 命令重命名,改变一个命令名称很危险!
# rename-command CONFIG ""
#

################################### LIMITS ####################################


设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你
无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自
身内部处理逻辑留一些句柄出来。如果达到了此限制,redis则会拒绝新的连接请求,并且向这
些连接请求方发出“max number of clients reached”以作回应。

#设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
如果设置 maxclients 0,表示不作限制。
当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
# maxclients 10000

# 持久性 不得配置AOF和RDB 原因fork中bgsave和bgrewriteaof
# persistence-available [(yes)|no]


设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,
那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。

但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,
需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素

#指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,
当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。
Redis新的vm机制,会把Key存放内存,Value会存放在swap区
  
# maxmemory <bytes>

(1)volatile-lru:使用LRU算法移除key,只对设置了过期时间的键
(2)allkeys-lru:使用LRU算法移除key
(3)volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
(4)allkeys-random:移除随机的key
(5)volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
(6)noeviction:不进行移除。针对写操作,只是返回错误信息

# maxmemory-policy noeviction


设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,
redis默认会检查这么多个key并选择其中LRU的那个

# maxmemory-samples 3

############################## APPEND ONLY MODE ###############################

# 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。
因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no

# 指定更新日志文件名,默认为appendonly.aof
appendfilename "appendonly.aof"

指定更新日志条件,共有3个可选值: 
  no:表示等操作系统进行数据缓存同步到磁盘(快) 
  always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) 
  everysec:表示每秒同步一次(折衷,默认值)

# appendfsync always

appendfsync everysec
# appendfsync no

指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,
访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
  vm-enabled no

虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
   vm-swap-file /tmp/redis.swap

将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),
也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
   vm-max-memory 0

Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,
vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,
则可以使用更大的page,如果不确定,就使用默认值
   vm-page-size 32

设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
   vm-pages 134217728

设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。
默认值为4
   vm-max-threads 4

设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
  glueoutputbuf yes

#AOF fsync策略设置为everysec时,当进行大量I/O磁盘时,fsync会被redis会阻止。
no-appendfsync-on-rewrite no

# aof文件重写策略 百分比和文件大小
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb


# 自动重写文件(当文件达到某条件 比如百分比、大小)
aof-load-truncated yes

################################ LUA SCRIPTING  ###############################

# lua 脚本执行时间限制 (小于等于0 无限制)
lua-time-limit 5000

################################ REDIS CLUSTER  ###############################
#
# 如果配置yes则开启集群功能,此redis实例作为集群的一个节点,否则,它是一个普通的单一的redis实例
# cluster-enabled yes

# 虽然此配置的名字叫"集群配置文件",但是此配置文件不能人工编辑,它是集群节点自动维护的文件,
# 主要用于记录集群中有哪些节点、他们的状态以及一些持久化参数等,方便在重启时恢复这些状态。
# 通常是在收到请求之后这个文件就会被更新。
#
# cluster-config-file nodes-6379.conf

# 这是集群中的节点能够失联的最大时间,超过这个时间,该节点就会被认为故障。如果主节点超过这个时间还是不可达,则用它的从节点将启动故障迁移,升级成主节点。注意,任何一个节点在这个时间之内如果还是没有连上大部分的主节点,则此节点将停止接收任何请求。一般设置为15秒即可。
#
# cluster-node-timeout 15000

# 如果设置成0,则无论从节点与主节点失联多久,从节点都会尝试升级成主节点。如果设置成正数,则cluster-node-timeout乘以cluster-slave-validity-factor得到的时间,是从节点与主节点失联后,此从节点数据有效的最长时间,超过这个时间,从节点不会启动故障迁移。假设cluster-node-timeout=5,cluster-slave-validity-factor=10,则如果从节点跟主节点失联超过50秒,此从节点不能成为主节点。注意,如果此参数配置为非0,将可能出现由于某主节点失联却没有从节点能顶上的情况,从而导致集群不能正常工作,在这种情况下,只有等到原来的主节点重新回归到集群,集群才恢复运作。
#
# cluster-slave-validity-factor 10

# 主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移。更详细介绍可以看本教程后面关于副本迁移到部分。
#
# cluster-migration-barrier 1

# 在部分key所在的节点不可用时,如果此参数设置为"yes"(默认值), 则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的key提供读操作。
#
# cluster-require-full-coverage yes

################################## SLOW LOG ###################################

# slowlog的划定界限,只有query执行时间大于slowlog-log-slower-than的才会定义成慢查询,才会被slowlog进行记录。slowlog-log-slower-than设置的单位是微妙,默认是10000微妙,也就是10ms 

slowlog-log-slower-than 10000

# 慢查询最大的条数,当slowlog超过设定的最大值后,会将最早的slowlog删除,是个FIFO队列
slowlog-max-len 128

################################ LATENCY MONITOR ##############################

# 延迟监控默认是关闭状态,即使延迟监控处理几乎不耗时。然而,当延迟监控只需非常小的内存时,则没有必要为一个运行良好的Redis实例提高基线内存使用量(baseline memory usage)。
latency-monitor-threshold 0

############################# Event notification ##############################

# Redis 可以通过pub/sub 完成发布和订阅等功能
#
# PUBLISH __keyspace@0__:foo del
# PUBLISH __keyevent@0__:del foo
#
# 
# 集合通知事件
#  K     Keyspace events, published with __keyspace@<db>__ prefix.
#  E     Keyevent events, published with __keyevent@<db>__ prefix.
#  g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
#  $     String commands
#  l     List commands
#  s     Set commands
#  h     Hash commands
#  z     Sorted set commands
#  x     Expired events (events generated every time a key expires)
#  e     Evicted events (events generated when a key is evicted for maxmemory)
#  A     Alias for g$lshzxe, so that the "AKE" string means all the events.
#
#  The "notify-keyspace-events" takes as argument a string that is composed
#  of zero or multiple characters. The empty string means that notifications
#  are disabled.
#
#  Example: to enable list and generic events, from the point of view of the
#           event name, use:
#
#  notify-keyspace-events Elg
#
#  Example 2: to get the stream of the expired keys subscribing to channel
#             name __keyevent@0__:expired use:
#
#  notify-keyspace-events Ex
#
#  By default all notifications are disabled because most users don't need
#  this feature and the feature has some overhead. Note that if you don't
#  specify at least one of K or E, no events will be delivered.
notify-keyspace-events ""

设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
  glueoutputbuf yes

指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
  hash-max-zipmap-entries 64
  hash-max-zipmap-value 512

############################### ADVANCED CONFIG ###############################

# 哈希是使用高效的数据结构进行编码的。
# 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

# 散列,列表也以特殊的方式顺序编码
list-max-ziplist-entries 512
list-max-ziplist-value 64

# 集合合成时,使用特殊的编码(10的整数或64位有符号整数)以达到内存最优
set-max-intset-entries 512

# 散列和列表 排序的集合 使用特殊编码 以至内存最优
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

# HyperLogLog稀疏结构表示字节的限制。该限制包括
# 16个字节的头。当HyperLogLog使用稀疏结构表示
# 这些限制,它会被转换成密度表示。
# 值大于16000是完全没用的,因为在该点 密集的表示是更多的内存效率。
# 建议值是3000左右,以便具有的内存好处, 减少内存的消耗
hll-sparse-max-bytes 3000

# 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes

# 客户端的输出缓冲区的限制,可用于强制断开那些因为某种原因从服务器读取数据的速度不够快的客户端
256mb 是一个硬性限制,当output-buffer的大小大于256mb之后就会断开连接
64mb 60 是一个软限制,当output-buffer的大小大于64mb并且超过了60秒的时候就会断开连接

client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

# 默认情况下,“hz”的被设定为10。提高该值将在Redis空闲时使用更多的CPU时,但同时当有多个key
# 同时到期会使Redis的反应更灵敏,以及超时可以更精确地处理
hz 10

# 当一个子进程重写AOF文件时,如果启用下面的选项,则文件每生成32M数据会被同步
aof-rewrite-incremental-fsync yes

################################## INCLUDES ###################################

# 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
# include /path/to/local.conf
# include /path/to/other.conf

redis 持久化

RDB(Redis DataBase)

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等),数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。

rdb 保存的是dump.rdb文件。

如何触发RDB快照?

1、配置文件中默认的快照配置

冷拷贝后重新使用,可以cp dump.rdb dump_new.rdb 。

2、命令save或者是bgsave

Save:save时只管保存,其它不管,全部阻塞

BGSAVE:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间。

3、执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义。

如何恢复 ?

将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可(CONFIG GET dir获取目录)。

优势

1、适合大规模的数据恢复

2、对数据完整性和一致性要求不高

劣势

1、在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。

2、fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。

如何停止 ?

动态所有停止RDB保存规则的方法:redis-cli config set save ""

AOF(Append Only File)

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

Aof保存的是appendonly.aof文件。

AOF启动/修复/恢复

正常恢复:

1、启动:设置Yes  修改默认的appendonly no,改为yes

2、将有数据的aof文件复制一份保存到对应目录(config get dir)

3、恢复:重启redis然后重新加载

异常恢复:

1、启动:设置Yes 修改默认的appendonly no,改为yes

2、备份被写坏的AOF文件

3、修复:redis-check-aof --fix进行修复

4、恢复:重启redis然后重新加载

rewrite 重写

场景:AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof。

原理:AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

触发机制:Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。

优势:

1、每修改同步:appendfsync always   同步持久化 每次发生数据变更会被立即记录到磁盘  性能较差但数据完整性比较好

2、每秒同步:appendfsync everysec    异步操作,每秒记录   如果一秒内宕机,有数据丢失

3、不同步:appendfsync no   从不同步

劣势:

1、相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb

2、aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同

小结

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

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

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

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

5、性能

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

如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。

redis 事务

说明

可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。

一个队列中,一次性、顺序性、排他性的执行一系列命令。

一个事务从开始到执行会经历以下三个阶段:

1、开启:以MULTI开始一个事务

2、入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面

3、执行:由EXEC命令触发事务

命令

1	DISCARD 
取消事务,放弃执行事务块内的所有命令。
2	EXEC 
执行所有事务块内的命令。
3	MULTI 
标记一个事务块的开始。
4	UNWATCH 
取消 WATCH 命令对所有 key 的监视。
5	WATCH key [key ...] 
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

Demo

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED

redis 127.0.0.1:6379> GET book-name
QUEUED

redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED

redis 127.0.0.1:6379> SMEMBERS tag
QUEUED

redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming"

特性

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

没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题。

不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

redis 发布订阅

说明

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

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

命令

1	PSUBSCRIBE pattern [pattern ...] 
订阅一个或多个符合给定模式的频道。
2	PUBSUB subcommand [argument [argument ...]] 
查看订阅与发布系统状态。
3	PUBLISH channel message 
将信息发送到指定的频道。
4	PUNSUBSCRIBE [pattern [pattern ...]] 
退订所有给定模式的频道。
5	SUBSCRIBE channel [channel ...] 
订阅给定的一个或多个频道的信息。
6	UNSUBSCRIBE [channel [channel ...]] 
指退订给定的频道。

Demo

以下实例演示了发布订阅是如何工作的。在我们实例中我们创建了订阅频道名为 redisChat:

redis 127.0.0.1:6379> SUBSCRIBE redisChat

Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。

redis 127.0.0.1:6379> PUBLISH redisChat "Redis is a great caching technique"

(integer) 1

redis 127.0.0.1:6379> PUBLISH redisChat "Learn redis by runoob.com"

(integer) 1

# 订阅者的客户端会显示如下消息
1) "message"
2) "redisChat"
3) "Redis is a great caching technique"
1) "message"
2) "redisChat"
3) "Learn redis by runoob.com"
先订阅后发布后才能收到消息,
1 可以一次性订阅多个,SUBSCRIBE c1 c2 c3
2 消息发布,PUBLISH c2 hello-redis
===========================================================================================================
3 订阅多个,通配符*, PSUBSCRIBE new*
4 收取消息, PUBLISH new1 redis2015

redis 复制 Master/Slave

说明

行话:也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。

作用

读写分离、容灾恢复

玩法

1、配从(库)不配主(库)

2、从库配置:slaveof 主库IP 主库端口

每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件

info replication

3、修改配置文件细节操作

1、拷贝多个redis.conf文件

2、开启daemonize yes

3、pid文件名字

4、指定端口

5、log文件名字

6、dump.rdb名字

常用3招

一主二仆:

1、Init

2、一个Master两个Slave

3、日志查看

主机日志

备机日志

info replication

4、主从问题演示

薪火相传:

1、上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力。

2、中途变更转向:会清除之前的数据,重新建立拷贝最新的。

3、slaveof 新主库IP 新主库端口。

反客为主:

SLAVEOF no one   使当前数据库停止与其他数据库的同步,转成主数据库

原理

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

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

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

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

5、但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

哨兵模式(sentinel)

说明:反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

步骤:

1、调整结构,6379带着80、81

2、自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错

3、配置哨兵,填写内容

sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1

上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机

4、启动哨兵

redis-sentinel /myredis/sentinel.conf

上述目录依照各自的实际情况配置,可能目录不同

5、正常主从演示

6、原有的master挂了

7、投票新选

8、重新主从继续开工,info replication查查看

监控:

一组sentinel能同时监控多个Master

缺点

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

redis jedis

测试连通性

public class Demo01 {
  public static void main(String[] args) {
    //连接本地的 Redis 服务
    Jedis jedis = new Jedis("127.0.0.1",6379);
    //查看服务是否运行,打出pong表示OK
    System.out.println("connection is OK==========>: "+jedis.ping());
  }
}

5+1

package com.atguigu.redis.test;
import java.util.*;
import redis.clients.jedis.Jedis;
public class Test02 
{
  public static void main(String[] args) 
  {
     Jedis jedis = new Jedis("127.0.0.1",6379);
     //key
     Set<String> keys = jedis.keys("*");
     for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
       String key = (String) iterator.next();
       System.out.println(key);
     }
     System.out.println("jedis.exists====>"+jedis.exists("k2"));
     System.out.println(jedis.ttl("k1"));
     //String
     //jedis.append("k1","myreids");
     System.out.println(jedis.get("k1"));
     jedis.set("k4","k4_redis");
     System.out.println("----------------------------------------");
     jedis.mset("str1","v1","str2","v2","str3","v3");
     System.out.println(jedis.mget("str1","str2","str3"));
     //list
     System.out.println("----------------------------------------");
     //jedis.lpush("mylist","v1","v2","v3","v4","v5");
     List<String> list = jedis.lrange("mylist",0,-1);
     for (String element : list) {
       System.out.println(element);
     }
     //set
     jedis.sadd("orders","jd001");
     jedis.sadd("orders","jd002");
     jedis.sadd("orders","jd003");
     Set<String> set1 = jedis.smembers("orders");
     for (Iterator iterator = set1.iterator(); iterator.hasNext();) {
       String string = (String) iterator.next();
       System.out.println(string);
     }
     jedis.srem("orders","jd002");
     System.out.println(jedis.smembers("orders").size());
     //hash
     jedis.hset("hash1","userName","lisi");
     System.out.println(jedis.hget("hash1","userName"));
     Map<String,String> map = new HashMap<String,String>();
     map.put("telphone","13811814763");
     map.put("address","atguigu");
     map.put("email","abc@163.com");
     jedis.hmset("hash2",map);
     List<String> result = jedis.hmget("hash2", "telphone","email");
     for (String element : result) {
       System.out.println(element);
     }
     //zset
     jedis.zadd("zset01",60d,"v1");
     jedis.zadd("zset01",70d,"v2");
     jedis.zadd("zset01",80d,"v3");
     jedis.zadd("zset01",90d,"v4");
     
     Set<String> s1 = jedis.zrange("zset01",0,-1);
     for (Iterator iterator = s1.iterator(); iterator.hasNext();) {
       String string = (String) iterator.next();
       System.out.println(string);
     }     
  }
}

事务提交

日常

package com.atguigu.redis.test;


import redis.clients.jedis.Jedis;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;


public class Test03 
{
  public static void main(String[] args) 
  {
     Jedis jedis = new Jedis("127.0.0.1",6379);
     
     //监控key,如果该动了事务就被放弃
     /*3
     jedis.watch("serialNum");
     jedis.set("serialNum","s#####################");
     jedis.unwatch();*/
     
     Transaction transaction = jedis.multi();//被当作一个命令进行执行
     Response<String> response = transaction.get("serialNum");
     transaction.set("serialNum","s002");
     response = transaction.get("serialNum");
     transaction.lpush("list3","a");
     transaction.lpush("list3","b");
     transaction.lpush("list3","c");
     
     transaction.exec();
     //2 transaction.discard();
     System.out.println("serialNum***********"+response.get());
          
  }
}
 
 

加锁

public class TestTransaction {


  public boolean transMethod() {
     Jedis jedis = new Jedis("127.0.0.1", 6379);
     int balance;// 可用余额
     int debt;// 欠额
     int amtToSubtract = 10;// 实刷额度


     jedis.watch("balance");
     //jedis.set("balance","5");//此句不该出现,讲课方便。模拟其他程序已经修改了该条目
     balance = Integer.parseInt(jedis.get("balance"));
     if (balance < amtToSubtract) {
       jedis.unwatch();
       System.out.println("modify");
       return false;
     } else {
       System.out.println("***********transaction");
       Transaction transaction = jedis.multi();
       transaction.decrBy("balance", amtToSubtract);
       transaction.incrBy("debt", amtToSubtract);
       transaction.exec();
       balance = Integer.parseInt(jedis.get("balance"));
       debt = Integer.parseInt(jedis.get("debt"));


       System.out.println("*******" + balance);
       System.out.println("*******" + debt);
       return true;
     }
  }


  /**
   * 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中
   * 重新再尝试一次。
   * 首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作,
   * 如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。
   */
  public static void main(String[] args) {
     TestTransaction test = new TestTransaction();
     boolean retValue = test.transMethod();
     System.out.println("main retValue-------: " + retValue);
  }
}
 

主从复制

public static void main(String[] args) throws InterruptedException 
  {
     Jedis jedis_M = new Jedis("127.0.0.1",6379);
     Jedis jedis_S = new Jedis("127.0.0.1",6380);
     
     jedis_S.slaveof("127.0.0.1",6379);
     
     jedis_M.set("k6","v6");
     Thread.sleep(500);
     System.out.println(jedis_S.get("k6"));
  }

1、6379,6380启动,先各自先独立

2、主写

3、从读

JedisPool

1、获取Jedis实例需要从JedisPool中获取

2、用完Jedis实例需要返还给JedisPool

3、如果Jedis在使用过程中出错,则也需要还给JedisPool

JedisPoolUtil 源码

package com.atguigu.redis.test;


import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;


public class JedisPoolUtil {
  
 private static volatile JedisPool jedisPool = null;//被volatile修饰的变量不会被本地线程缓存,对该变量的读写都是直接操作共享内存。
  
  private JedisPoolUtil() {}
  
  public static JedisPool getJedisPoolInstance()
 {
     if(null == jedisPool)
    {
       synchronized (JedisPoolUtil.class)
      {
          if(null == jedisPool)
         {
           JedisPoolConfig poolConfig = new JedisPoolConfig();
           poolConfig.setMaxActive(1000);
           poolConfig.setMaxIdle(32);
           poolConfig.setMaxWait(100*1000);
           poolConfig.setTestOnBorrow(true);
            
            jedisPool = new JedisPool(poolConfig,"127.0.0.1");
         }
      }
    }
     return jedisPool;
 }
  
  public static void release(JedisPool jedisPool,Jedis jedis)
 {
     if(null != jedis)
    {
      jedisPool.returnResourceObject(jedis);
    }
 }
}
 
package com.atguigu.redis.test;


import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;


public class Test01 {
  public static void main(String[] args) {
     JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
     Jedis jedis = null;
     
     try 
     {
       jedis = jedisPool.getResource();
       jedis.set("k18","v183");
       
     } catch (Exception e) {
       e.printStackTrace();
     }finally{
       JedisPoolUtil.release(jedisPool, jedis);
     }
  }
}
 

配置

JedisPool的配置参数大部分是由JedisPoolConfig的对应项来赋值的。


maxActive:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。
maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
whenExhaustedAction:表示当pool中的jedis实例都被allocated完时,pool要采取的操作;默认有三种。
 WHEN_EXHAUSTED_FAIL --> 表示无jedis实例时,直接抛出NoSuchElementException;
 WHEN_EXHAUSTED_BLOCK --> 则表示阻塞住,或者达到maxWait时抛出JedisConnectionException;
 WHEN_EXHAUSTED_GROW --> 则表示新建一个jedis实例,也就说设置的maxActive无用;
maxWait:表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛JedisConnectionException;
testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;


testOnReturn:return 一个jedis实例给pool时,是否检查连接可用性(ping());


testWhileIdle:如果为true,表示有一个idle object evitor线程对idle object进行扫描,如果validate失败,此object会被从pool中drop掉;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;


timeBetweenEvictionRunsMillis:表示idle object evitor两次扫描之间要sleep的毫秒数;


numTestsPerEvictionRun:表示idle object evitor每次扫描的最多的对象数;


minEvictableIdleTimeMillis:表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;


softMinEvictableIdleTimeMillis:在minEvictableIdleTimeMillis基础上,加入了至少minIdle个对象已经在pool里面了。如果为-1,evicted不会根据idle time驱逐任何对象。如果minEvictableIdleTimeMillis>0,则此项设置无意义,且只有在timeBetweenEvictionRunsMillis大于0时才有意义;


lifo:borrowObject返回对象时,是采用DEFAULT_LIFO(last in first out,即类似cache的最频繁使用队列),如果为False,则表示FIFO队列;


==================================================================================================================
其中JedisPoolConfig对一些参数的默认设置如下:
testWhileIdle=true
minEvictableIdleTimeMills=60000
timeBetweenEvictionRunsMillis=30000
numTestsPerEvictionRun=-1

总结

不知不觉中感觉到了它的强大!~~ 为什么强大?需要从源码中探索!~~

原文  https://blog.csdn.net/banketree/article/details/79948843
正文到此结束
Loading...