redis学习笔记(干货)
单机mysql
- 数据量总大小,在一台机器上放不下
- 数据的索引(B+Tree)一个机器的内存放不下时
- 访问量(读写混合)一个实例不能承受
App → DAL →mysql
Memache(缓存)+mysql+垂直拆分
对于数据库频繁的访问会增加数据库的压力,可以把频繁访问的固定数据放在缓存中减小数据库的压力
Mysql主从读写分离
Master-slaver模式 主库写从库读
分表分库+水平拆分+mysql集群
主库的写压力出现瓶颈而数据量持续猛增,myisam使用的是表锁,在高并发下会出现锁问题,大量的高并发mysql开始使用innodb引擎代替myisam
分库分表
频繁热点高度活跃的数据放在一个库中,冷门的数据放在另一个库中;如果同一张表中数据量过大,可采用适合的算法分表存储。
Mysql的扩展性瓶颈
大数据下io压力大,表结构更改困难
目前Nginx负载均衡
为什么要用nosql
传统的关系型数据库不在满足日益增长的用户数据和操作的成倍增长
Nosql泛指非关系型数据库,这些类型的数据存储不需要固定的模式,无需多余的操作就可以横向扩展
Nosql特性
- 易扩展(数据之间无关系)
- 大数据量高性能(非常高的读写性能)
- 多样灵活的数据类模型(无需事先为要存储的数据建立字段,随时可以存储自定义字段)
- 传统RDBMS VS NOsql
大数据时代的3V:海量(Volume)、多样(variety)、实时(velocity)
互联网时代需求3高:高并发(访问量大)、高可扩(横向,纵向)、高性能
升级服务器(纵向)的成本是指数增长的,而增加另一台商用服务器(横向)的成本是线性增长的。
Nosql四大分类:
KV键值
文档型书数据库(bson格式比较多)
MongoDB是一个基于分布式文件存储的数据库
列存储数据库
图关系数据库
CAP原理CAP+BASE
传统型数据库ACID:原子性、一致性、持久性、隔离性
Nosql CAP:
C:Consistency(强一致性)
A:Availability(可用性)
P:Partition tolerance(分区容错性)
CAP的3进2:
对于传统的关系型数据库来说ACID必须都要满足;
CAP理论的核心是:一个分布式系统不可能同时的很好满足一致性,高可用性,最多只能同时较好的满足两个。
因此,根据CAP原理将nosql数据库分成了满足CA、CP、AP原则三大类:
CA - 单点集群,满足一致性、可用性的系统,通常在可扩展性上不是太强大;
CP - 满足一致性、分区容错性的系统,通常性能不是特别高
AP – 满足可用性、分区容错性的系统,通常可能对一致性要求较低
由于当前的网络硬件肯定会出现丢包等问题,所以分区容错性是我们必须要满足的。
CA传统的oracle数据库;AP大多数的网站的架构得选择;CP Redis、Mongodb
BASE就是为了解决关系数据库前一致性引起的问题而引起的可用性降低而提出的解决方案
BASE:
基本可用,软状态,最终一致性
它的思想就是让系统放松某一时刻数据一致性的要求来换取系统整体伸缩性和性能的改观。
Redis(remote dictionary server 远程服务字典)
Redis支持持久化;支持除了键值对外还支持list、set、zset、hash等数据结构;支持master-slave数据的备份
Redis是单进程的,对读写等事件的响应是通过epoll函数包装来实现的,redis的实际处理速度完全依靠主进程的执行效率。
Epoll是linux内核为处理大批量文件描述符而做了改进的epoll,是linux下多路复用IO借口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
关于linux的一些操作
关于linux设置nat模式下设置静态ip的时候,网关设置需要一致
Redis数据类型
String 一个redis的键值对value最多512M
Hash 键值对集合
List 列表,可双向操作
Set 无序集合,通过HashTable实现的
Zset 有序集合,每个元素会关联一个double类型的分数,成员不允许重复,但是分数可以重复
Key
Keys * 查看所有键值
exists key 判断某个键值是否存在
move key db 将指定key值动到指定库中,当前库中没有了
expire key 秒 为给定key值设定过期时间
ttl key 查看当前key还有多少秒过期,-1 表示永不过期,-2表示已过期(过期会从库中删除)
type key 查看当前key的类型
Stirng(单值单value)
set/get/del/append/strlen append在指定键后追加值,不是追加元素,返回值是追加后值得长度
incr/decr/incrby/decrby, 一定要是数字才能加减
getrange/setrange 范围内获取/设置
setex(set with expire)/setnx 设置键值的同时设置过期时间/如果键值不存在就设置
mset/mget/msetnx 同时设置/获取多个键值/设置的同时,如果部分存在,则不成功
getset 先get后set
list(单值多value)
lpush/rpush/lrange lpush后进先出,rpush先进先出
lpop/rpop
lindex
llen
lrem key 删N个value
ltrim key 开始index 结束index 截取指定范围的之后再赋值给key
rpoplpush 源列表 目的列表
linsert key before/after 值1 值2
性能总结:
它是一个字符串链表,left、right都可以操作
键不存在,创建新链表;存在,增加内容
如果只全移除,对应的键也就消失了
头尾操作效率高,中间差
Set(单值多value)
Sadd/smembers/sismember
Scard 获取集合里面的元素个数
Srem key value 删除集合中元素
Srandmember key 某个整数(随机出几个数)
Spop key 随机出栈
Smove key1 key2 在key1里面的某个值 作用是将key1里的某个值移动到key2
Sdiff 差集 只在第一个里面,不在第二个里面
Sinter 交集
Sunion 并集
Hash(KV模式不变,但V是一个键值对)
hset/hget/hmset/hmget/hgetall/hdel
hlen
hexists key 在key里面的某个值的key
hkeys/hvals
hincrby/hincrbyfloat
hsetnx
Zset(sorted set)
在set基础上加了一个score值。
zadd/zrange withscores
zrangebyscore key 开始score 结束score withscores|(不包含|limit作用是返回值
zrem key 某score下对应的value值,作用是删除元素
zcard/zcount key score 区间/zrank key values值,作用是获取下标值/zscore key 对应值获取分数
zrevrank key values值,作用是逆序获取下标值
zrevrange 开始分数到结束分数
zrevrangebyscore key 分数 结束分数到开始分数
查看redis进程
配置
参数说明
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 <dbid>命令在连接上指定数据库id
databases 16
9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save <seconds> <changes>
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 <masterip> <masterport>
14. 当master服务设置了密码保护时,slav服务连接master的密码
masterauth <master-password>
15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
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是指在一定时间间隔内将内存中的数据集快照写到磁盘中,就是常讲的Snapshot快照,在恢复时直接将快照文件读回到内存中。
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量,环境变量,程序计数器等)都与原进程一致,但是是一个全新的进程,并作为原进程的子进程
- SAVE直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
- BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。因为 rdbSave 在子进程被调用,所以 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求。可以通过lastsave命令来获取最后一次成功执行快照的时间
使用flushall命令也会产生dump.rdb文件,但是里面是空的,无意义。
RDB优点:
RDB是一个非常紧凑的文件
RDB是在保存RDB文件时父进程唯一需要做的就是fork一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能
与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。
RDB缺点:
数据丢失风险大
RDB需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致redis在一些毫秒级不能相应客户端请求。
以日志的形式来记录每个写操作,将redis执行的每个写操作指令记录下(读操作不记录),只允许追加数据不允许改写数据,在redis启动之初会读取日志文件重新构建数据。
文件写入和保存
每当服务器常规任务函数被执行、 或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作:
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
两个步骤都需要根据一定的条件来执行, 而这些条件由 AOF 所使用的保存模式来决定, 以下小节就来介绍 AOF 所使用的三种保存模式, 以及在这些模式下, 步骤 WRITE 和 SAVE 的调用条件。
AOF 保存模式
Redis 目前支持三种 AOF 保存模式,它们分别是:
- AOF_FSYNC_NO :不保存。
- AOF_FSYNC_EVERYSEC :每一秒钟保存一次。
- AOF_FSYNC_ALWAYS :每执行一个命令保存一次。
以下三个小节将分别讨论这三种保存模式。
不保存
在这种模式下, 每次调用 flushAppendOnlyFile 函数, WRITE 都会被执行, 但 SAVE 会被略过。
在这种模式下, SAVE 只会在以下任意一种情况中被执行:
- Redis 被关闭
- AOF 功能被关闭
- 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)
这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。
每一秒钟保存一次
在这种模式中, SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程调用的, 所以它不会引起服务器主进程阻塞。
注意, 在上一句的说明里面使用了词语“原则上”, 在实际运行中, 程序在这种模式下对 fsync 或 fdatasync 的调用并不是每秒一次, 它和调用 flushAppendOnlyFile 函数时 Redis 所处的状态有关。
每当 flushAppendOnlyFile 函数被调用时, 可能会出现以下四种情况:
- 子线程正在执行 SAVE ,并且:
- 这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的 SAVE 。
- 这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。注意,因为这时 WRITE 的写入必须等待子线程先完成(旧的) SAVE ,因此这里 WRITE 会比平时阻塞更长时间。
- 上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。
- 上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。
- 子线程没有在执行 SAVE ,并且:
可以用流程图表示这四种情况:
根据以上说明可以知道, 在“每一秒钟保存一次”模式下, 如果在情况 1 中发生故障停机, 那么用户最多损失小于 2 秒内所产生的所有数据。
如果在情况 2 中发生故障停机, 那么用户损失的数据是可以超过 2 秒的。
Redis 官网上所说的, AOF 在“每一秒钟保存一次”时发生故障, 只丢失 1 秒钟数据的说法, 实际上并不准确。
每执行一个命令保存一次
在这种模式下,每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。
另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令请求。
Redis重写
重写并不需要对原有的AOF进行读和写操作, 而是针对的是数据库中键的当前值。
在重写过程中避免对原有数据进行修改而照成记录数据的错误,会将修改同时记录到AOF缓存中,当写命令和缓存中写命令都追加到现有的AOF文件中时,重写完成。
在整个 AOF 后台重写过程中, 只有最后的写入缓存和改名操作会造成主进程阻塞, 在其他时候, AOF 后台重写都不会对主进程造成阻塞, 这将 AOF 重写对性能造成的影响降到了最低。
AOF 后台重写的触发条件
AOF 重写可以由用户通过调用 BGREWRITEAOF 手动触发。
另外, 服务器在 AOF 功能开启的情况下, 会维持以下三个变量:
- 记录当前 AOF 文件大小的变量 aof_current_size 。
- 记录最后一次 AOF 重写之后, AOF 文件大小的变量 aof_rewrite_base_size 。
- 增长百分比变量 aof_rewrite_perc 。
每次当 serverCron 函数执行时, 它都会检查以下条件是否全部满足, 如果是的话, 就会触发自动的 AOF 重写:
- 没有 BGSAVE 命令在进行。
- 没有 BGREWRITEAOF 在进行。
- 当前 AOF 文件大小大于 server.aof_rewrite_min_size (默认值为 1 MB)(配置文件中时64M)。
- 当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分比。
默认情况下, 增长百分比为 100% , 也即是说, 如果前面三个条件都已经满足, 并且当前 AOF 文件大小比最后一次 AOF 重写时的大小要大一倍的话, 那么触发自动 AOF 重写。
RDB和AOF同时使用
当redis重启时,会优先载入AOF文件来恢复原始的数据,这是因为通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
虽然RDB的数据不实时,并且二者同时使用是服务器重启也只会找AOF文件,但是不建议只使用AOF,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF潜在的Bug,留着作为一个万一的手段。
性能建议
因为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的事务
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞
一个队列中,一次性、顺序性、排他性的执行一系列命令
MULTI
标记一个事务块的开始
DISCARD
取消一个事务,放弃执行事务块内的所有命令
EXEC
执行所有事务快内的命令
WATCH
监视一个或多个key,如果在事务执行之前这个或这些key被其他命令所改动,那么事务将被打断。
UNWATCH
取消WATCH命令个对所有的key的监视
Redis事务的三个阶段:开启、入队、提交
三个特性:所有操作被单独执行;没有隔离级别;不保证原子性
Redis入队错误,则事务块中所有的操作都不执行;如果是执行错误,其他命令都执行,发生错误的命令报错
Redis的事务是不可嵌套的,当一个事务开启时,如果输入MULTI,服务器只会简单的向客户端发送一个错误,然后继续等待其他命令入队,并不会造成整个事务的失败,也不会修改事务队列中已有的数据。WATCH只能在客户端进入事务状态之前执行。在事务状态下发送WATCH命令,会引发一个错误,但并不会造成整个事务的失败,也不会修改事务队列中已有的数据。
Redis有消息订阅和发布功能
Redis的复制(Master/Slaver):
主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slaver以读为主。
配从不配主,从库配置slaveof主库IP主库端口,每次与master断开之后都要重新连接。
从库配置:slaveof 主库IP 主库端口
哨兵模式:配置sentinel.conf,sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1
启动哨兵模式:redis-sentinel 路径/sentinel.conf
Jedis需要的jar包:commons-pool-1.6.jar;jedis-2.1.0.jar
Demo:
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());
}
}
Demo:
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);
}
}
}
Demo:事务(加锁)
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);
}
}
Demo:
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