当前位置:   article > 正文

Redis完整体系知识_redis体系

redis体系

前置知识

点击进入NoSQL前置知识详情

Redis入门介绍

Redis简介

Redis:REmote DIctionary Server(远程字典服务器)

Redis是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。

Redis作为K-V缓存产品有以下三个特点:

1、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
2、Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
3、Redis支持数据的备份,即master-slave模式的数据备份。

Redis作用

  • 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务。
  • 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面。
  • 模拟类似于HttpSession这种需要设定过期时间的功能。
  • 发布、订阅消息系统
  • 定时器、计数器

Redis官网

  • Http://redis.io/
  • Http://www.redis.cn/

安装Redis

主要讲解在CentOS上安装Redis
1、下载获得redis-X.X.X.tar.gz后将它放入我们的Linux目录/opt
2、/opt目录下,解压命令:tar -zxvf redis-X.X.X.tar.gz
3、解压完成后出现文件夹:redis-X.X.X
4、进入目录:cd redis-X.X.X
5、在redis-X.X.X目录下执行make命令

  • 如果出现没有gcc的错误则联网执行:yum install gcc-c++,然后二次make
  • 如果报Jemalloc/jemalloc.h:没有那个文件或目录则执行make distclean之后再make

6、make完成后继续执行make install
7、查看默认安装目录:usr/local/bin

  • Redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何,可以在启动服务后执行。
#常用文件解析
Redis-check-aof:		修复有问题的AOF文件
Redis-check-dump:		修复有问题的dump.rdb文件。
Redis-cli:				客户端,操作入口。
Redis-sentinel:		redis集群使用。
Redis-server:			Redis服务器启动命令
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

8、启动Redis

  • 修改redis-X.X.X目录下redis.conf文件将里面的daemonize no 改成 yes,让服务在后台启动。(最好先拷贝一份放到自己指定的文件夹,比如myredis目录下,修改自己拷贝的那份)
  • redis-server /myredis/redis.conf
  • redis-cli -p 6379(此命令需要在/usr/local/bin下使用)
  • 连通测试:执行ping(看到响应PONG则连通成功)

9、关闭Redis

redis-cli shutdown				#单实例关闭
redis-cli -p 6379 shutdown		#多实例关闭,指定端口关闭
  • 1
  • 2

Redis前置知识

1、Redis是单进程程序

  • 单进程模型来处理客户端的请求。对读写等事件的响应是通过对epoll函数的包装来做到的。Redis的实际处理速度完全依靠主进程的执行效率
  • Epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

2、Redis默认有16个数据库

  • 默认使用0号库
  • redis-conf中对此的设置为:databases 16
  • 可以通过SELECT [dbid]的方式进行切换数据库

作用:可以根据业务逻辑进行分库操作。

[root@localhost bin]# redis-cli -p 6379
127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]>
  • 1
  • 2
  • 3
  • 4

3、DBSIZE查看当前数据库key的数量

127.0.0.1:6379> set k1 helloworld!
OK
127.0.0.1:6379> dbsize
(integer) 1
127.0.0.1:6379> keys * #查看所有key的值
1) "k1"
127.0.0.1:6379> get k1
"helloworld!"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4、FLUSHDB:删除当前库的所有key干掉。
5、FLUSHALL:将所有库的所有key都干掉。
6、Redis有统一密码管理,所有库都有相同密码,要么都ok要么一个也连接不上,默认进入Redis无需输入密码。
7、Redis索引从0开始
8、Redis默认端口为6379

Redis的数据类型与数据操作命令

Redis的五大数据类型

1、String类型

String是Redis最基础的数据类型,一个key对应一个value,而且此String类型是二进制安全的,可以包含任何数据,比如jpg图片或者序列化的对象等,而String类型的value最大可存储512M。

2、Hash(哈希)

Hash类型是键值对集合,类似于Java中的Map<String,Object>类型,所以很适合存储对象类型数据。

3、List(列表)

List类型是字符串的列表,按照插入顺序排序,你也可以指定插入到头或尾,即List类型如同一个双向链表。

4、Set(无序集合)

Set是String类型的无序无重复的集合,底层实现是HashTable。

5、Zset(sorted set:有序集合)

Redis Zset 和 Set一样也是String类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。
Redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

数据操作命令官网

官网命令:Http://redisdoc.com/

数据操作常见命令

Redis键(Key)

键的操作命令
常用范例:

keys *				查看当前数据库中的所有key
exists k1			判断k1是否存在
move key 2			移动到2号数据库
expire k1 10		给k1设置过期时间,单位秒
ttl k1				看看k1还有多少秒过期,-1表示永不过期,-2表示已过期
type k1				看看k1是什么类型
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Redis字符串(String)

Redis字符串操作命令Redis字符串操作命令
常用范例

set/get/del/append/strlen			存//删除/追加/长度
incr/decr/incrby/decrby				加//加多少/减多少;注意,只能操作纯数字的字符串,否则报错
getrange k1 0 2						获取k1字符串索引从0-2的字符串
setrange k1 0 xxx					在k1字符串的0号索引位置开始放置xxx字符串,如果原本有值则覆盖,否则则直接存放。
setex k2 15 v2						设置带过期时间的值,为k2设置15秒的过期时间,值为v2
setnx k2 v2							当k2不存在时设置值为v2,存在时无效。
mset k1 v1 k2 v2 					同时设置多个K-V键值对,为k1设置值为v1,为k2设置值为v2.
mget k1 k2 k3						获取k1、k2、k3的值
msetnx k1 v1 k2 v2					当且仅当所有key不存在时批量添加,当k1和k2都不存在时,分别跟他们设置值为v1和v2。
getset k1 v11						先获取当前k1的值,然后再给k1赋值为v11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Redis列表(List)

Redis列表操作命令

Redis列表操作命令
常用案例
1、lpush、rpush、lrange

127.0.0.1:6379> lpush list01 1 2 3 4 5 #从列表前面插入
(integer) 5
127.0.0.1:6379> lrange list01 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> rpush list02 1 2 3 4 5 6 #从列表后面追加
(integer) 6
127.0.0.1:6379> lrange list02 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2、lpop、rpop

lpop list01			从栈顶取一个值
rpop list01			从栈底取一个值
  • 1
  • 2

3、lindex

lindex list01 2			获取list01索引下标为2的字符串
  • 1

4、llen列表长度
5、lrem

lrem list01 2 2			删除list01中2个2
  • 1

6、itrim

itrim list01 0 2		将list01下标索引从0到2截取出来再赋给list01
  • 1

7、rpoplpush

rpoplpush list01 list02		从list01列表后面pop出一个数据然后插入到list02列表的前面
  • 1

8、lset

lset list01 2 x				在list01列表的2号索引位置插入x字符串
  • 1

9、linsert

linsert list01 before x java			在list01列表中value为x的前面插入一个value为java
linsert list02 after x oracle 			在list01列表中value为x的后面插入value为oracle
  • 1
  • 2

Redis集合(Set)

Redis集合命令
常用范例
1、sadd、smembers、sismember

sadd set01 1 2 2 3 4 4 5			将去重后的值存到set01这个key中
smembers set01						将set01集合中的所有数据打印出来
sismember set01 1					判断set01集合中是否含有1这个值,有则返回1无则返回0
  • 1
  • 2
  • 3

2、scard

scard set01 			获取集合里的元素个数
  • 1

3、srem

srem set01 7 			删除set01集合中的7
  • 1

4、srandmember

srandmember set01 2			随机从set01中打印2个数据
  • 1

5、spop:随机出栈
6、smove

smove set01 set02 8			将set01集合中的8移到set02集合中,set01中不再存在8
  • 1

7、sdiff、sinter、sunion

sdiff set01 set02			取set01和set02的差集
sinter set01 set02			取set01和set02的交集
sunion set01 set02			取set01和set02的并集
  • 1
  • 2
  • 3

Redis的哈希(Hash)

Redis的哈希的操作命令
常用范例
1、hset、hget、hmset、hmget、hgetall、hdel

hset user id 1001								给user的对象中添加一个为id的key并且值为1001.
hget user id									获取user的id
hmset user name zs age 18 addr shanghai			给user批量添加k-v对
hmget user id name age addr						批量获取user的v值
hgetall user									获取user所有的k-v值
hdel user id									删除user的id
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、hlen:获取哈希中k的数量
3、hexists

hexists user id				判断user中是否含有id,1为存在,0为不存在
  • 1

4、hkeys、hvals

hkeys user				获取user的所有key
hvals user				获取user的所有value
  • 1
  • 2

5、hincrby、hincrbyfloat

hincrby user age 2				给纯数字的value添加2(整数)
hincrbyfloat age 2.0			给纯数字的value添加2.0(浮点数)
  • 1
  • 2

6、hsetnx

hsetnx user email it@163.com		当user中不存在email时添加值为“it@163.com”的键“email”
  • 1

Redis有序集合Zset(sorted set)

说明: sorted set是在set的基础上加了一个score值,利用这个score值来进行排序。

Redis的有序集合操作命令
Redis的有序集合操作命令
常用范例

zadd zset01 10 v1 20 v2 30 v3			给zset01集合中放v1、v2、v3并且给其对的score10、20、30
zrange zset01 0 -1						查看全部范围的zset01集合的所有数据
zrange zset01 0 -1 withscores			查看全部范围的zset01集合的所有数据包括其对应score
zrangebyscore zset01 20 40				根据score的范围来获取value,获取zset01集合中的包含20到40的所有数据
zrangebyscore zset01 20 (40 			获取zset01集合中包含20到40且不包含40的所有数据	
zrangebyscore zset01 20 (40 withscores	获取zset01集合中包含20到40且不包含40的所有数据,并将其对应score打印出来
zrangebyscore zset01 20 40 limit 0 2	获取zset01集合中包含20到40的所有数据且分页显示(从这些数据中的第0号索引位置往后取两个数据)
zrem zset01 v5 							删除zset01集合中的v5
zcard zset01							统计zset01集合中所有数据的个数
zcount zset01 10 40						统计zset01集合中score值10到40的value个数
zrank zset01 v3							返回zset01集合中value为v3的下标索引	
zscore zset01 v3						返回zset01集合中value为v3的score值
zrevrank zset01 v3						返回zset01集合中v3的倒序下标索引
zrevrange zset01 0 -1					返回zset01集合中所有数据的倒序
zrevrangebyscore 30 10					返回zset01集合中所有score范围从10到30的倒序数据

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

redis.conf的常用配置

redis.conf 配置项说明如下:

  1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
    daemonize no
  2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
    pidfile /var/run/redis.pid
  3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
    port 6379
  4. 绑定的主机地址
    bind 127.0.0.1
  5. 当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
    timeout 300
  6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
    loglevel verbose
  7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
    logfile stdout
  8. 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
    databases 16
  9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
    save
    Redis默认配置文件中提供了三个条件:
    save 900 1
    save 300 10
    save 60 10000
    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
  10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
    rdbcompression yes
  11. 指定本地数据库文件名,默认值为dump.rdb
    dbfilename dump.rdb
  12. 指定本地数据库存放目录
    dir ./
  13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
    slaveof
  14. 当master服务设置了密码保护时,slav服务连接master的密码
    masterauth
  15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
    requirepass foobared
  16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
    maxclients 128
  17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
    maxmemory 【bytes】
  18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
    appendonly no
  19. 指定更新日志文件名,默认为appendonly.aof
    appendfilename appendonly.aof
  20. 指定更新日志条件,共有3个可选值:
    no:表示等操作系统进行数据缓存同步到磁盘(快)
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    everysec:表示每秒同步一次(折衷,默认值)
    appendfsync everysec
  21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
    vm-enabled no
  22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
    vm-swap-file /tmp/redis.swap
  23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
    vm-max-memory 0
  24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
    vm-page-size 32
  25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
    vm-pages 134217728
  26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
    vm-max-threads 4
  27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
    glueoutputbuf yes
  28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
    hash-max-zipmap-entries 64
    hash-max-zipmap-value 512
  29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
    activerehashing yes
  30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
    include /path/to/local.conf

Redis的持久化

RDB

RDB简介

RDB是Redis Data Base的缩写。在指定的时间间隔内,将内存中的数据集快照写入到磁盘,及Snopshot快照,它恢复时是将快照文件直接读入到内存中。默认保存的文件名为:dump.rdb文件

Redis会单独创建一个子进程(fork)来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了高性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是很敏感,则RBD比AOF更快。

Fork

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

配置文件位置

配置位置

如何触发快照

方式1、如上面配置文件中默认的配置。
方式2、 命令save或bgsave。二者区别,前者只管保存其他不管,全部阻塞;后者Redis会在后台异步进行快照操作,快照同时还可以响应客户端的请求。可以通过lastsave命令获取最后一次成功执行快照的时间。
方式3、执行flushall命令也会产生dump.rdb文件,但是里面是空的,无意义。

如何恢复

将dump.rdb文件移动到Redis的安装路径下启动服务即可自动恢复。

如何停止

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

优势

适合大规模的数据恢复,对数据的完整性和一致性要求不高。

劣势

1、在一定时间间隔内做一次备份,所以如果redis意外down掉的话就会失去最后一次形成快照后的所有修改。
2、Fork的时候,内存中的数据被克隆了一份,大致两倍的膨胀性,需要考虑在内。

小总结

RDB总结

AOF

AOF简介

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

AOF保存的是appendonly.aof文件

配置文件位置

AOF位置

配置文件描述
1、appendonly是控制打开或关闭AOF。
2、appendfilename表示AOF默认文件名,一般不要修改。
3、appendfsync表示AOF的同步策略,有三种:always(同步持久化,每次发生数据变更就会被立即记录到磁盘,性能较差但是数据完整性较好。)、everysec(出厂默认推荐,异步操作,每秒记录一次,如果一秒内宕机,会丢失这一秒的数据备份)、no(不同步)
4、no-appendfsync-on-rewrite表示重写时是否可以运用appendfsync,用默认no即可,为了保证数据的安全性。
5、auto-aof-rewrite-min-size表示设置重写的基准值。
6、auto-aof-rewrite-percentage表示设置重写的基准值。

AOF启动、修复、恢复

正常恢复

启动:将配置文件中appendonly设置为yes。
备份:将有数据的aof文件复制到对应目录(config get dir)。
恢复:重启redis然后重新加载。

异常恢复

启动:同上。
备份:备份的aof文件已损坏。
修复:redis-check-aof --fix进行修复。
恢复:重启redis然后重新加载。

Rewrite

rewrite简介

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

重写原理

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

触发机制

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

AOF优势

使用AOF可以保证数据的完整性。

AOF劣势

AOF效率和磁盘占用的方面都逊色于RDB

AOF小总结

AOF总结

持久化的总结

1、RDB持久化方式能够在指定的时间间隔对数据进行快照存储。
2、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会执行这些操作命令。redis会在aof文件后面追加,并且会在aof文件过大时进行重写。
3、如果redis只是用作缓存中间件来使用,则可以关闭所有持久化。
4、同时开启两种持久化时,会优先加载AOF文件,毕竟人家存的比较全。
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的事务

Redis事务简介

1、是什么
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序的串行执行而不会被其他命令插入,不允许加塞
2、能干吗
一个队列中一次性的顺序的排他性的执行一组命令。

Redis事务命令

redis事务命令

Redis事务的三个阶段

1、开启:以MULTI开始一个事务。
2、入队:将多个命令入队到事务中,接到这些命令而不会立即执行,而是暂时放到准备执行的事务队列中。
3、执行:由EXEC命令触发执行事务。

几种事务状况

1、正常执行
正常执行
2、放弃事务
放弃事务
3、全体连坐
全体连坐
4、怨头债主
怨头债主

watch监控

使用信用卡可用余额和欠额的概念来模拟↓
1、初始化信用卡可用余额与欠额:
初始化可用余额和欠额
2、无加塞的篡改,保证两笔金额变动在同一事务内:
无加塞
3、有加塞的篡改,先监控key,如果key被修改了,后面的一个事务执行失败:
有篡改
4、取消监控(unwatch):
取消监控
5、小结

Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。

悲观锁、乐观锁、CAS

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新。

CAS(Check And Set)

Redis事务的特性

1、单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
2、没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题。
3、不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

Redis的复制(Master/Slave)

Redis的复制简介

1、是什么
Redis的复制是常说的主从复制,即主机数据更新后根据配置和策略,自动同步到备机的master/slave机制,Master以写为主,Slave以读为主。

2、能干啥

  • 读写分离
  • 容灾恢复

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

常见复制方式

0、Redis复制的通用配置

口诀:配从不配主
①、拷贝多个redis.conf文件
拷贝文件
②、开启daemonize为yes
开启参数
③、将几个库的配置文件都修改这三个配置
④、将几个库的log文件修改一下
修改log
⑤、修改几个库的rdb文件名
修改rdb文件

1、一主二仆

①、初始化
初始化
②、效果
效果
③、查看主机日志
主机日志
④、查看从机日志
从机日志
⑤、info replication
info replication
⑥、思考问题

  • 切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制?
  • 从机是否可以写?set可否?
  • 主机shutdown后情况如何?从机是上位还是原地待命?
  • 主机又回来了后,主机新增记录,从机还能否顺利复制?
  • 其中一台从机down后情况如何?依照原有它能跟上大部队吗?

2、薪火相传

说明:上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力
中途变更转向:会清除之前的数据,重新建立拷贝最新的Slaveof 新主库IP 新主库端口

3、反客为主

当主机挂掉之后,在从机中手动执行SLAVEOF no one,使当前数据库停止与其他数据库的同步,转成主数据库。

Redis复制的原理

1、Slave启动成功连接到master后会发送一个sync命令
2、Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。
3、全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
4、增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步。
5、但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。

哨兵模式(sentinel)

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

使用步骤

1、使用6379端口带着6380和6381
2、自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错
3、sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1(最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机)
配置
4、启动哨兵:Redis-sentinel /myredis/sentinel.conf
启动哨兵
5、测试master挂掉
master挂了
6、投票重新选举(自动)
投票
7、重新实现主从开工,使用info replication查看
重新主从
8、如果之前的master重启回来,会不会双master冲突?
沦为下属

Redis的Java客户端Jedis

导入依赖jar包

  • Commons-pool-1.6.jar
  • Jedis-2.1.0.jar

Jedis的常用操作

1、测试连通性

public class Demo {
  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());
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2、测试5+1(五大数据类型和key的命令)

 
package com.atguigu.redis.test;
 
import java.util.*;
 
import redis.clients.jedis.Jedis;
 
public class Test 
{
  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);
     }   
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

3、一般的事务操作

package com.atguigu.redis.test;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;
 
public class Test
{
  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());    
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

4、加锁的事务操作

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);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

5、主从复制

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

JedisPool

1、简介

获取Jedis实例需要从JedisPool中获取,用完Jedis实例需要返还给JedisPool,如果Jedis在使用过程中出错,则也需要还给JedisPool。

2、使用JedisPool

package com.atguigu.redis.test;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 依据JedisPool的特性定义的工具类
*/
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);
    }
 }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
package com.atguigu.redis.test;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* 测试上面的工具类
*/
public class Test {
  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);
     }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3、给JedisPool配置

其中粗体为重要配置:

①、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时才有意义

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/754182
推荐阅读
相关标签
  

闽ICP备14008679号