redis
Linux版安装
下载获得redis-3.0.4.tar.gz后将它放入我们的Linux目录/opt
/opt目录下,解压命令:tar -zxvf redis-3.0.4.tar.gz
解压完成后出现文件夹:redis-3.0.4
进入目录:cd redis-3.0.4
在redis-3.0.4目录下执行make命令 make
运行make命令时故意出现的错误解析:
安装gcc
能上网:yum install gcc-c++
不上网:用rpm包安装gcc
然后使用gcc -v 查看gcc的版本
二次make
jemalloc/jemalloc.h:没有那个文件或目录
运行make distclean(执行make distclean命令)之后再make
Redis Test(可以不用执行)
如果make完成后继续执行make install(执行到这如果下面都显示安装成功了,就算安装完成了)
查看默认安装目录:usr/local/bin (安装完后这个目录会有一些命令)
redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何
服务启动起来后执行
redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
redis-check-dump:修复有问题的dump.rdb文件
redis-cli:客户端,操作入口
redis-sentinel:redis集群使用
redis-server:Redis服务器启动命令
启动
修改redis.conf文件将里面的daemonize no 改成 yes,让服务在后台启动(不要去修改默认的redis.conf文件,先将redis.conf文件复制到/myconf下,再去修改)
将默认的redis.conf拷贝到自己定义好的一个路径下,比如/myredis
启动 启动redis服务:在/usr/local/bin目录下运行redis-server执行命令:redis-server /myredis/redis.conf,运行拷贝出存放了自定义conf文件目录下的redis.conf文件,
客户端连接到redis服务端:执行redis-cli -p 6379
连通测试 执行命令ping 他会返回pong
关闭
在登录了客户端(已连接服务端)后执行SHUTDOWN,这时由于服务端已经被关闭了,客户端还开启着,客户端显示着not connected,客户端还在运行;退出客户端执行exit及退出客户端。
单实例关闭:redis-cli shutdown
多实例关闭,指定端口关闭:redis-cli -p 6379 shutdown
命令:ps -ef|grep redis 查看redis进程是否启动
gcc是linux下的一个编译程序,是C程序的编译工具。GCC(GNU Compiler Collection) 是 GNU(GNU's Not Unix) 计划提供的编译器家族,
它能够支持 C, C++, Objective-C, Fortran, Java 和 Ada 等等程序设计语言前端,同时能够运行在 x86, x86-64, IA-64, PowerPC, SPARC 和 Alpha 等等几乎目前所有的硬件平台上。
鉴于这些特征,以及 GCC 编译代码的高效性,使得 GCC 成为绝大多数自由软件开发编译的首选工具。虽然对于程序员们来说,编译器只是一个工具,除了开发和维护人员,很少有人关注编译器的发展,
但是 GCC 的影响力是如此之大,它的性能提升甚至有望改善所有的自由软件的运行效率,同时它的内部结构的变化也体现出现代编译器发展的新特征。
Redis启动后杂项基础知识讲解
单进程
单进程模型来处理客户端的请求。对读写等事件的响应是通过对epoll函数的包装来做到的。Redis的实际处理速度完全依靠主进程的执行效率
epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
默认16个数据库,类似数组下表从零开始,初始默认使用零号库(设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id databases 16)
select命令切换数据库 select 7
dbsize查看当前数据库的key的数量 DBSIZE (命令:keys * 罗列当前的key)
flushdb:清空当前库 FLUSHDB
Flushall;通杀全部库 FLUSHALL
统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上
Redis索引都是从零开始
默认端口是6379
命令支持rest风格:例如keys k?会把k开头的key罗列出来
Redis数据类型
Redis的五大数据类型
string(字符串):string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。string类型是二进制安全的。意思是redis的string可以包含任何数据。
比如jpg图片或者序列化的对象 。string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
hash(哈希,类似java里的Map):Redis hash 是一个键值对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。类似Java里面的Map<String,Object>
list(列表):Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。它的底层实际是个链表
set(集合):Redis的Set是string类型的无序集合。它是通过HashTable实现实现的
zset(sorted set:有序集合):Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
哪里去获得redis常见数据类型操作命令
http://redisdoc.com/
Redis 键(key)
常用
案例
keys * keys *罗列当前库的所有key
exists key的名字,判断某个key是否存在,返回1则代表存在,返回0则代表不存在
move key db --->当前库就没有了,被移除了 将当前库的指定key移动到指定的db中
expire key 秒钟:为给定的key设置过期时间
ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期(对于已过期的key,使用get访问,返回的值为nil,使用keys *查看不到,也就是说已过期的key(生命周期已经被终结)是已经被移除了内存系统)
type key 查看你的key是什么类型
Redis字符串(String)
常用
单值单value
案例
set/get/del/append/strlen 更新或赋值/查询/删除/拼接/返回value为string类型的对象长度 append key strlen key
Incr/decr/incrby/decrby,一定要是数字才能进行加减 仅能对string类型值为数字的value操作(Incr key指定key对应的value自增1)(decr key指定key对应的value自减1)(incrby key number指定key对应的value自增number;decrby类似)
getrange/setrange getrange:获取指定区间范围内的值,类似between......and的关系从零到负一表示全部。命令getrange key number1 number2 返回key对应value的number1索引到number2索引的字段,
内容包括number1索引number2索引对应的值,也就是从原来的value中截取一段,包括开头和结尾。
setrange:设置指定区间范围内的值,格式是setrange key 索引 内容值 (setrange key 索引 设置值)setrange k1 0 xxx 原来k1是123456 现在变为xxx456
setex(set with expire)键秒值/setnx(set if not exist) setex key 秒值 真实值---设置key-value的时候顺带过期时间;setnx key value---setnx:只有在 key 不存在时设置 key 的值。
mset/mget/msetnx mset:同时设置一个或多个 key-value 对(mset k1 v1 k2 v2 k3 v3)。mget:获取所有(一个或多个)给定 key 的值(mget k1 k2 k3)。msetnx(msetnx k1 v1 k2 v2):同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
getset(先get再set) getset:将给定 key 的值设为 value ,并返回 key 的旧值(old value)。简单一句话,先get然后立即set
Redis的回应:如果起效就是1,不起效就是0;报出一个null;报出一个用于展示错误的提示
Redis列表(List)
常用
单值多value
案例
lpush/rpush/lrange
lpush key 一个list数据 lpush key 1 2 3 4 创建一个key,value为一个list类型的数据,相当于把这组数据从管道的左侧插入,看起来像|4 3 2 1| (rpush就是从右侧插入)(这个是创建value为list类型的k-v对)
lrange key 开始索引 结束索引 lrange key 0 -1 其中-1代表最后一个数据的索引,相当于将|4 3 2 1|list数据从左边获取出来查看结果为4 3 2 1(这个命令是查看list中的数据)
lpop/rpop
lpop key 将list的管道的左侧第一个数据弹出,list中的数据发生了改变。lpop key使|4 3 2 1|变为|3 2 1|,并且返回数值4。rpop则是将右边第一个数据弹出
lindex,按照索引下标获得元素(从上到下) lindex key 索引 将value为list中指定索引的数据返回,也就是查看list中指定索引的数据
llen llen key 查看key对应的value的list的长度
lrem key 删N个value lrem key 删除的个数 删除list的哪个数值 例如lrem key 2 3 这个就是在list中删除2个值为3的数据(修改了list)
ltrim key 开始index 结束index,截取指定范围的值后再赋值给key 包括两个索引对应的数值(修改了list)
rpoplpush 源列表 目的列表 rpoplpush list01 list02 将list01右边的第一个移动到list02左边第一个去(注意是移动,list01少了一个数据,list02多了一个数据)
lset key index value 将value中指定索引的数据被替换掉(替换掉)
linsert key before/after 值1 值2 把值2插入到list中数值为值1的前面或者后面(修改了list)
性能总结:
它是一个字符串链表,left、right都可以插入添加;
如果键不存在,创建新的链表;
如果键已存在,新增内容;
如果值全移除,对应的键也就消失了。
链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。
Redis集合(Set)
常用
单值多value
案例
sadd/smembers/sismember
sadd key set集合 sadd set01 1 1 2 2 3 3最后生成一个集合(1 2 3)
如果key已经存在,且value也是set类型,执行该命令会将不重复的也加进去(存在重复的元素不会报错,也不会加进去)
smembers key 查看key对应set集合中的元素
sismember key 元素数据 判断key对应的set里面是否存在该元素数据
scard,获取集合里面的元素个数 scard key
srem key value 删除集合中元素 srem key value
srandmember key 某个整数(随机出几个数) srandmember key 指定个数 从指定的set集合中随机获取指定个数字
spop key 随机出栈 spop key 随机从set中去掉一个元素,并将元素返回
smove key1 key2 在key1里某个值(指定的值) 作用是将key1里的某个值赋给key2 将key1的set的指定的值移动到key2的set中
数学集合类
差集:sdiff 在第一个set里面而不在后面任何一个set里面的项 sdiff set01 set02 返回在set01中不在set02的元素
交集:sinter sinter set01 set02
并集:sunion sunion set01 set02
Redis哈希(Hash)
常用
KV模式不变,但V是一个键值对
案例
hset/hget/hmset/hmget/hgetall/hdel
hset key HashKey HashValue 例如hset user name z3 往指定key中加入一个数据类型为hashmap的value,但是hashmap中只有一个K-V
hget key HashKey 例如hget user name 查看value为Hash中指定HashKey对应的HashValue
hmset key HashKey1 HashValue1 HashKey2 HashValue2 HashKey3 HashValue3... 创建一个包含多个K-V的Hash
hmget key HashKey1 HashKey2 HashKey3... 同时查看value为Hash中指定多个HashKey对应的HashValue
hgetall key 查看整个Hash,返回的是一个个的K-V
hdel key HashKey1 删除Hash中指定HashKey1对应的K-V
hlen hlen key 查看指定Hash中存在多少个K-V
hexists key 在key里面的某个值的key HEXISTS key HashKey1 判断Hash中是否存在指定HashKey为HashKey1的K-V
hkeys/hvals hkeys key 返回指定Hash中所有的key hvals key 返回指定Hash中所有的value
hincrby/hincrbyfloat
hincrby key HashKey1 间隔数字(整数) 根据key HashKey1 获取对应的value,然后在自增指定的间隔数字。hincrbyfloat中的是用于间隔数字可以带小数
hsetnx hsetnx key HashKey HashValue 若指定key HashKey都不存在,则创建这个Hash和K-V。(不存在赋值,存在了无效)
Redis有序集合Zset(sorted set)
多说一句 在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是k1 score1 v1 score2 v2
常用
案例
zadd/zrange
zadd key score1 Value1 score2 Value2 score3 Value3 score4 Value4 创建集合Zset
zrange key 0 -1 查看Zset中所有的元素(因为这个set是带顺序的,所以0 -1索引(下标值)有用)
zrange key 0 -1 withscores 查看Zset中所有的元素及其他们的分数
zrangebyscore key 开始score 结束score 指定Zset查看元素,该元素必须在开始score和结束score之间,可以包含两端。
withscores
(不包含
zrangebyscore key (开始score 结束score 指定Zset查看元素,该元素必须在开始score和结束score之间,包含结束,不包含开始
limit 作用是返回限制
limit 开始下标步 多少步
例如:zrangebyscore key 开始score 结束score limit 开始下标步 多少步(从zrangebyscore key 开始score 结束score返回的结果集中截取,开始下标步为从该索引截取,包括该索引,多少步代表截取多少个)
zrem key 某score下对应的value值,作用是删除元素 zrem key 某score下对应的value值 删除指定set中的value
zcard/zcount key score区间/zrank key values值,作用是获得下标值/zscore key 对应值,获得分数
zcard key 查询指定set中元素的个数
zcount key 开始score 结束score 及score区间,查询指定set中,元素score在score区间的元素个数
zrank key values值 获取指定Zset中value的索引(下标值)
zscore key values值 获取指定Zset中value的分数
zrevrank key values值 作用是逆序获得下标值
zrevrange zrevrange key 0 -1 逆序返回Zset中的元素(zrange的逆序而已)
zrevrangebyscore key 结束score 开始score 命令zrangebyscore的逆序,注意他是结束score 开始score
解析配置文件:
redis.conf(以下都是在文件中的配置)
它在哪
地址 解压目录下有个redis.conf
为什么我将它拷贝出来单独执行? 并不能保证一次性修改正确,最好保留初始配置
units单位 1.配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit 2.对大小写不敏感
INCLUDES包含 和我们的Struts2配置文件类似,可以通过includes包含,redis.conf可以作为总闸,包含其他
GENERAL通用
daemonize
daemonize:yes: redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。
daemonize:no: 当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭连接工具(putty,xshell等)都会导致redis进程退出。
pidfile pidfile /var/run/redis.pid
当Redis 在后台运行的时候,Redis 默认会把pid 文件放在/var/run/redis.pid,你可以配置到其他地址。当运行多个redis 服务时,需要指定不同的pid 文件和端口
port 监听端口,默认为6379
tcp-backlog tcp-backlog设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值
来达到想要的效果
timeout
设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接(配置如果是0的话,就是禁用,则不存在自动关闭连接)
bind 指定Redis 只接收来自于该IP 地址的请求,如果不进行设置,那么将处理所有请求,在生产环境中为了安全最好设置该项。默认注释掉,不开启。
tcp-keepalive 指定TCP连接是否为长连接,"侦探"信号有server端维护。默认为0.表示禁用(这个就是看网络通信是否是良好的)
loglevel 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
logfile 配置log文件地址,默认使用标准输出,即打印在命令行终端的窗口上,修改为日志文件目录(指定日志文件的地址)
syslog-enabled 要想启用系统日志记录器,设置一下选项为yes(默认是no,不是很重要)。是否把日志输出到syslog中
syslog-ident 指明syslog身份,指定syslog里的日志标志
syslog-facility 指明syslog设备。必须是一个用户或者是local0 ~ local7之一
databases 设置数据库数目,第一个数据库编号为:0
SNAPSHOTTING快照
Save
save 秒钟 写操作次数 RDB(该持久化策略)是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件(给定时间内触发了以下条件就会db保存的动作)
默认:是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次。
禁用 如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以
stop-writes-on-bgsave-error 如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制(就是在备份的时候出现了某个错误,如果是no的话,这仅仅是让程序忽略了这个异常,使得程序能够继续往下运行,但实际上数据还是会存储到硬盘失败)
rdbcompression 对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能 例如建议用rdbcompression yes,使用压缩存储--这个功能是用于压缩存储
rdbchecksum 在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能(建议使用rdbchecksum yes)--这个功能是用来数据校验的
dbfilename 指定RDB持久化生成rdb文件后的文件名 例如:dbfilename dump.rdb
dir
REPLICATION复制
SECURITY安全
访问密码的查看、设置和取消
设置密码:客户端连接到服务端后,执行命令config set requirepass "密码"
重新让客户端连接服务端:auth 密码
LIMITS限制
maxclients maxclients 10000
设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自
身内部处理逻辑留一些句柄出来。如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
maxmemory
设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,
那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,
需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素
maxmemory-policy
volatile-lru -> remove the key with an expire set using an LRU algorithm 使用LRU算法移除key,只对设置了过期时间的键
allkeys-lru -> remove any key according to the LRU algorithm 使用LRU算法移除key
volatile-random -> remove a random key with an expire set 在过期集合中移除随机的key,只对设置了过期时间的键
allkeys-random -> remove a random key, any key 移除随机的key
volatile-ttl -> remove the key with the nearest expire time (minor TTL) 移除那些TTL值最小的key,即那些最近要过期的key
noeviction -> don't expire at all, just return an error on write operations 不进行移除。针对写操作,只是返回错误信息
maxmemory-samples 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个
APPEND ONLY MODE追加
appendonly (是否开启aof)
appendfilename (生成aof的文件名)
appendfsync
always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
everysec:出厂默认推荐,异步操作,每秒记录 如果一秒内宕机,有数据丢失
no
no-appendfsync-on-rewrite:重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性。
auto-aof-rewrite-min-size:设置重写的基准值 例如:auto-aof-rewrite-min-size 64m
auto-aof-rewrite-percentage:设置重写的基准值 例如:auto-aof-rewrite-percentage 100
常见配置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 <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. 指定本地数据库存放目录(这里需要注意,这里是设定将dump.rdb放在那里,如果是./,那么他就放在启动该服务的路径下,最好写成绝对路径。还有如果在启动的服务端的时候,该路径下没有rdb,则会认为没数据加载,打开服务端后数据为空,如果存在rdb则加载这个rdb中的数据。也就是说他决定了加载数据rdb和存储的位置)
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(Redis DataBase)
官网介绍
是什么:
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
Fork
fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)
数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
rdb 保存的是dump.rdb文件
配置位置 redis.conf中的SNAPSHOTTING快照配置
如何触发RDB快照(在执行flushall,save和shutdown命令的时候会立马生成rdb文件)
配置文件中默认的快照配置
冷拷贝后重新使用
可以cp dump.rdb dump_new.rdb
命令save或者是bgsave(直接在客户端使用save 命令 可以立即生成rdb文件)
Save:save时只管保存,其它不管,全部阻塞(在save的时候,是不能去修改(增改删)内存中的数据的,等备份完,才可以修改数据(修改数据就是前台发过来的请求中带有修改数据的命令))
BGSAVE:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间
(也就是说使用异步备份,及时前台请求会修改数据也不需要等待备份完才修改,就是修改数据和备份可以同时进行)
执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
如何恢复
将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可
CONFIG GET dir获取目录
优势
适合大规模的数据恢复
对数据完整性和一致性要求不高
劣势
在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改
fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
如何停止
动态所有停止RDB保存规则的方法:redis-cli config set save ""
小总结
AOF(Append Only File)
官网介绍
是什么:
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
Aof保存的是appendonly.aof文件
配置位置
AOF启动/修复/恢复 (aof文件和dump.rdb是可以同时存在) 若开启了aof,并且存在aof文件,他只会加载aof文件,若加载aof文件失败,则启动redis失败。
正常恢复
启动:设置Yes(原本没有appendonly.aof文件,开启appendonly后,只要有写入操作就会生成该文件(在安装目录下),但flashall命令(使当前内存中数据持久化的命令)依旧会有dump.rdb文件,但是appendonly.aof会有所有执行的写入命令,可以直接去改appendonly.aof文件的命令来修改数据生成的结果)
修改默认的appendonly no,改为yes(他默认no,yes就是打开了aof持久化)(启动时,如果开启了aof,先加载aof文件,如果aof文件有问题,aof加载会报错,从而启动失败)
将有数据的aof文件复制一份保存到对应目录(config get dir)
恢复:重启redis然后重新加载
异常恢复
启动:设置Yes
修改默认的appendonly no,改为yes
备份被写坏的AOF文件
修复:
redis-check-aof --fix进行修复(在命令窗口:redis-check-aof --fix appendonly.aof) 补充:如果是修复dump的,则命令是redis-check-dump --fix dump.rdb
恢复:重启redis然后重新加载
rewrite
是什么:
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
重写原理
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似(就是把现有的数据重新set一次,并没有用到以前的aof文件)
触发机制
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
优势
每修改同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
每秒同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失
不同步:appendfsync no 从不同步
劣势
相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
小总结
总结(Which one)
官网建议
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得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文件,载入较新的那个。新浪微博就选用了这种架构
*************这里是不存在aof文件,存在rdb文件(额外知识点:如果已经存在rdb文件(该文件是存在数据的),如果开了aof,并且之前是不存在aof文件的,那么他会新建立一个aof文件(新建的文件里面是没数据的),如果保存退出后,rdb文件的内容是以aof中的数据为准(也就是原先rdb数据等于是没用了,可以认为rdb文件不存在),在重新更新rdb文件后以当时内存中数据为准(也就是aof的数据了)。参考https://blog.csdn.net/weixin_39040059/article/details/79120444不过这里的如果存在rdb,就去找rdb是有错误的)
开了aof的情况下(appendfilename考虑这个是否一致)
若两个配置文件的appendfilename是一致,dbfilename也一致,那么他们是打开同一个数据库的数据,数据如果不存在一致性问题话,aof文件的数据和rdb的数据是一样的。
若两个配置文件的appendfilename不一致,dbfilename不一致,他会重新建个appendfilename的文件,并且保存数据在dbfilename文件rdb中。(认为不存在aof文件)
若两个配置文件的appendfilename是一致,dbfilename不一致,那么他们打开的不是同一个数据库的数据。这里有个非常有趣的现象。
如果两个配置中appendfilename和dbfilename都是一样的,那么他们是用的同一个库。
如果appendfilename是一致,dbfilename不一致,命令也会丢在appendfilename文件中(是否是同一个文件就不知道了),但是确实不是一个库,可以简单的认为,是否是一个库,由appendfilename,dbfilename决定,这两者一致则同库,任意一个不同都不是同库。
若两个配置文件的appendfilename不一致,dbfilename是一致,认为没有aof文件,前面的rdb会被覆盖。
Redis的事务
是什么
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞
官网
能干嘛
一个队列中,一次性、顺序性、排他性的执行一系列命令
怎么玩
常用命令 开启事务:MULTI 在开启事务后就是redis命令的集合了,如果是符合语法规则的命令,在输完后会返回QUEUED(入列), 最后是执行命令:EXEC。 在执行玩EXEC命令后,会返回之前命令集合返回的结果(这个结果和单条执行是一样的)。如果要放弃的话,则使用DISCARD(和EXEC类似)。
case1:正常执行 MULTI 命令集合 EXEC
Case2:放弃事务 MULTI 命令集合 DISCARD
Case3:全体连坐 集合命令要么全部执行要么全部失败。如果其中有个命令不符合语法,在输入命令集(注意这里是输入命令集的时候报的错,可以认为是语法报错,类似是java编译时的报错)的时候他报错,在执行EXEC后也会报错,报错后前面的命令集全部都不会执行。
Case4:冤头债主 这里和全体连坐有点区别,在输入完命令集后(输入命令集时没有报错),在输入完EXEC命令后,执行(注意这里是执行命令的时候,类似于java的运行阶段报错)命令集的时候报错了,所有命令都会执行,只有运行报错的命令会执行失败,其他命令执行成功。也就是说可以从EXEC命令执行后的返回结果看出命令执行结果
冤头债主的报错是在运行时产生的报错,仅仅影响报错的单条命令。全体连坐是在输入命令集时候的报错,会影响整个命令集。
Case5:watch监控 (补充:表锁和行锁锁的粒度不一样,在一次数据库操作的时候,表锁锁住的是一整张表,行锁锁住的是表中的一行数据。也就是说表锁最多只允许一个数据库操作操作某张表,行锁的话是最多允许一个数据库操作操作某张表的一个记录)
悲观锁/乐观锁/CAS(Check And Set)
悲观锁 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
乐观锁 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新
CAS
初始化信用卡可用余额和欠额
无加塞篡改,先监控再开启multi,保证两笔金额变动在同一个事务内 在输入命令multi之前,输入WATCH 监控的哪个变量,后续为事务开启multi,若在执行命令集合前,监控变量没有发生改变(无加塞篡改),那么命令集合将正常执行。
有加塞篡改(相当于监控一个变量,如果该变量没发生改变,那么后续事务执行成功,若发生了改变则失败)
监控了key,如果key被修改了,后面一个事务的执行失效 同样是在输入命令multi之前,输入WATCH 监控的哪个变量,若在命令集合执行前(也就是EXEC执行后,会去检查key),发现key的值发生了改变(被其他客户端的操作改变了,只要该值发生了改变,及时设置了一次,但是值没变,也算值变动了),那么整个事务的命令集合将不会执行
unwatch
一旦执行了exec之前加的监控锁都会被取消掉了
小结
Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行
通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败
3阶段
开启:以MULTI开始一个事务
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
执行:由EXEC命令触发事务(执行命令集合)
3特性
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
Redis的发布订阅
是什么
进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
订阅/发布消息图
命令
案列 先订阅后发布后才能收到消息,
1 可以一次性订阅多个,SUBSCRIBE c1 c2 c3 这个是订阅命令,在一个客户端输入该命令SUBSCRIBE 多个变量 (注意,这里的变量是可以随便是啥,不必是原来rdb中存在的变量)
2 消息发布,PUBLISH c2 hello-redis 这是发布消息命令,在另外一个客户端(向订阅的客户端发送消息)执行该命令就PUBLISH 变量 发送消息,这个命令可以让订阅方的客户端收到指定变量的消息
===========================================================================================================
3 订阅多个,通配符*, PSUBSCRIBE new* 通配符可以匹配多个变量
4 收取消息, PUBLISH new1 redis2015
Redis的复制(Master/Slave)
是什么
官网
行话:也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
能干嘛
读写分离(只有主机可以读和写,从机只能读)
容灾恢复
怎么玩
配从(库)不配主(库)
从库配置:slaveof 主库IP 主库端口 在从机(从库的服务端)的客户端输入命令:slaveof 127.0.0.1 6379 将他关联到主机上面去,这样他就可以获取到主库的数据(所有的数据)了
从机每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件(这个配置是让从机每次启动后自动连接到主机上去的配置,不然每次启动后都要主动的连接一次主机)
info replication(查看主机或者从机的相关信息)
修改配置文件细节操作
拷贝多个redis.conf文件
开启daemonize yes
pid文件名字 例如:pidfile /var/run/redis_6379.pid
指定端口 例如:port 6379
log文件名字 例如:logfile "6379.log"
dump.rdb名字 例如:dbfilename dump6379.rdb
常用3招
一主二仆
Init
一个Master(主机)两个Slave(从机)
日志查看
主机日志:就是配置文件中 logfile对应的文件
备机日志:就是配置文件中 logfile对应的文件
info replication :在登录进客户端后输入的命令,查看服务端信息
主从问题演示
1 切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制(主机的数据全部复制)
2 从机是否可以写?set可否? (从机不可写)
3 主机shutdown后情况如何?从机是上位还是原地待命(原地待命)
4 主机又回来了后,主机新增记录,从机还能否顺利复制?(主机回来,主机重新上位,从机可以复制新的数据)
5 其中一台从机down后情况如何?依照原有它能跟上大部队吗?(从机需要重启后,重新连接主机才能用)
薪火相传
上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave(相当于一个中转站,他的身份还是slave)作为了链条中下一个的master,可以有效减轻master的写压力
中途变更转向:会清除之前的数据,重新建立拷贝最新的
slaveof 新主库IP 新主库端口 (这个从库连接主库(中转站)的命令还是一样的)
反客为主(以前一主二仆是两个从机连接一个主机,主机挂了,从机等待主机恢复。但是反客为主,主机挂了,后面挂掉的主机恢复了,他也不会是原先的主机了,也就是说反客为主策略,已经有了一套完整的体系了,从机相当于升级为主机了(原先从机变新老大),原先的主机已经脱离关系了)
SLAVEOF no one(该命令让该从库停止与其他数据库的同步并且变成主库)
使当前数据库停止与其他数据库的同步,转成主数据库(现在从库执行该命令,从库变主库,然后将其他从库连接到该转换成主库的库)
复制原理
slave启动成功连接到master后会发送一个sync命令
Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。(连接一次master就进行一次全量复制,也就只是一次)
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步(master进行修改数据会进行增量复制)
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
哨兵模式(sentinel)
是什么
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
怎么玩(使用步骤)
调整结构,6379带着80、81(一主(79)二从(80,81),都连在79上)
自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错(新建sentinel.conf空文件)
配置哨兵,填写内容(在sentinel.conf文件中输入以下内容)
sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1 例如:sentinel monitor host6379 127.0.0.1 6379 1 (这里的host6379随便是哪个名字,127.0.0.1为主机master的ip,6379为主机使用的端口)
上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机(其实这个1是谁先超过这个投票数,就选谁,建议用1,即使一连挂也不要紧)
启动哨兵
redis-sentinel /myredis/sentinel.conf
上述目录依照各自的实际情况配置,可能目录不同(在已经启动redis服务端后再启动该命令的)
正常主从演示
原有的master挂了
投票新选
重新主从继续开工,info replication查查看
问题:如果之前的master重启回来,会不会双master冲突?(当原先的主机(老领导)回来后,他会变成从机,相当于把回归后的变成了从机,服务与新主机(通过哨兵模式新选的从机))
一组sentinel能同时监控多个Master
复制的缺点
复制延时:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
补充:(在默认配置下,未使用哨兵模式)主机挂了:当主机shutdown后,从机的身份还是从机,从机上的连接状态为down,但是从机上面还是可以查询到来自主机的数据,若此时主机重新启动,并且修改数据,从机也能得到修改后的数据,
也就是说此时的配置是,主机挂了,从机继续执行他的提供读的能力,从机不会变成主机,当主机重新启动后,主机依旧是主机,还是按照原先的模式运行。
(在默认配置下,未使用哨兵模式)从机挂了:当从机挂了后,其他从机主机可以继续执行他们的功能,若重启挂了的从机,重启后的从机身份状态是master,从机需要重新连接到主机上面去。
Redis的Java客户端Jedis
安装JDK
tar -zxvf jdk-7u67-linux-i586.tar.gz
vi /etc/profile
重启一次Centos
编码验证
安装eclipse
Jedis所需要的jar包
commons-pool-1.6.jar
jedis-2.1.0.jar
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
一个key
五大数据类型
代码演示:
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;
}
}
上述代码补充解释:在jedis.watch("balance");监视后如果balance发生了改变,那么在事务的transaction的命令将不会执行,但是单独的jedis的set命令一样可以执行,监视只对事务中命令有用。
在这里可以发现,在监控后,数据发生改变,会导致这次请求修改数据出问题,但是这个出现数据问题的间隔为 开启监控到执行exec方法时。假如这段里面出现了数据修改会导致改数据失败,
如果在业务判定之后,出现了数据修改,此时数据是存在问题的,但是逻辑上却已经跳过了校验(if中的)部分,所以这里是存在java代码级别的误差
/**
* 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中
* 重新再尝试一次。
* 首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作,
* 如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。
*/
public static void main(String[] args) {
TestTransaction test = new TestTransaction();
boolean retValue = test.transMethod();
System.out.println("main retValue-------: " + retValue);
}
}
主从复制
6379,6380启动,先各自先独立
主写
从读
代码实现:
public static void main(String[] args) {
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("class","1122V2");
String result = jedis_S.get("class");
System.out.println(result);
}
JedisPool
获取Jedis实例需要从JedisPool中获取
用完Jedis实例需要返还给JedisPool
如果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);
}
}
}
Demo5
jedisPool.getResource();
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);
}
}
}
配置总结all
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