Redis tair
3V : 海量Volume多样Variety实时Velocity
3高: 高并发 高可扩 高性能
1.NOSql数据库四大分类
https://blog.csdn.net/xiaocai9999/article/details/79647241
2.分布式数据库中CAP原理CAP+BASE
CAP
C:Consistency(强一致性)
A:Availability(可用性)
P:Partition tolerance(分区容错性)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA–单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP–满足一致性,分区容忍必的系统,通常性能不是特别高。
AP–满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
BASE
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案
BASE其实是下面三个术语的缩写:
基本可用(Basically Available)
软状态(Soft state)|
最终一致(Eventually consistent)
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法
3.Redis的三个特点
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用<br /> Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset, hash等数据结构的存储<br /> Redis支持数据的备份,即master-slave模式的数据备份
4.Redis作用
内存存储和持久化: redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务<br /> 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面<br /> 模拟类似于HttpSession这种需要设定过期时间的功能<br /> 发布、订阅消息系统<br /> 定时器、计数器
5.Redis知识讲解
1.select 0/1/2/ 0-15 选库
2.DBSIZE 当前库的键的数量
3.keys * 所有key
127.0.0.1:6379> set key1 va1
OK
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> keys k?
1) "k2"
2) "k1"
127.0.0.1:6379> keys k??
(empty array)
127.0.0.1:6379> keys k???
1) "key1"
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> keys *
(empty array)
4.FLUSHDB 清空当前库
5.FLUSHALL 清空所有库
6.五大数据类型
Redis键(key)
EXISTS K1 是否存在,<br /> move k3 2 移动元素到那个库<br /> expire key秒钟:为给定的key设置过期时间<br /> ttl key查看还有多少秒过期,-1表示永不过期,-2表示已过期<br /> type key查看你的key是什么类型
Redis字符串(String)
string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。<br /> string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象。<br /> string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
set/ get/del/append/strlen<br /> Incr/decr/incrby/decrby,一定要是数字才能进行加减<br /> getrange/setrange<br /> setex(set with expire)键秒值/setnx(set if not exist)<br /> mset/mget/msetnx(要不成功都不成功)<br /> getset(先get再set)
Redis列表(List)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部 (右边) 。它的底层实际是个链表
lpush/rpush/lrange<br /> lpop/rpop_<br /> lindex,按照索引下标获得元素(从上到下)<br /> llen<br /> lrem key删N个value lrem key count value 从上到下<br /> ltrim key开始index结束index,截取指定范围的值后再赋值给key<br /> rpoplpush源列表 目的列表 将最后一个元素放入目的的第一个<br /> lset key index value<br /> linsert key before/after值1值2
Redis集合(Set)
Redis的Set是string类型的无序集合。它是通过HashTable实现的, 有序的
sadd/smembers/sismember 添加/查询/判断<br /> scard,获取集合里面的元素个数<br /> srem key value_删除集合中元素<br /> srandmember key某个整数(随机出几个数)<br /> spop key随机出栈<br /> smove key1 key2在key1里某个值 作用是将key1里的某个值赋给key2数学集合类<br /> sdiff 差集<br /> dinter 交集<br /> sunion 并集
Redis哈希(Hash) 类似Java的map
Redis hash是一个键值对集合。<br /> Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。<br /> 类似ava里面的Map<String,object>
hset/hget/hmset/hmget/hgetall/hdel<br /> hlen<br /> hexists key在key里面的某个值的key<br /> hkeys/hvals<br /> hincrby/hincrbyfloat<br /> hsetnx 如果不存在则添加
Redis有序集合Zset(sorted set)
Redis zset和 set一样也是string类型元素的集合,且不允许重复的成员。<br /> 不同的是每个元素都会关联一个double类型的分数。<br /> redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
zadd/zrange(withscores)<br /> zrangebyscore key开始score结束score<br /> withscores<br /> ( 不包含 ZRANGEBYSCORE zset01 6o (9o 不包含90<br /> Limit作用是返回限制 limit开始下标位置 偏移多少位) ZRANGEBYSCORE zseto1 6o 90 1imit 2 2<br /> <br />
zrem key 某value下对应的value值,作用是删除元素<br /> zcard/zcount key score区间/zrank key values值,作用是获得下标值/zscore key对应值,获得分数<br /> zrevrank key values值,作用是逆序获得下标值<br /> zrevrange<br /> zrevrangebyscore key结束score开始score
7.redis 内存过期策略 maxmemory-policy
(1) volatile-1ru:使用LRU算法移除key,只对设置了过期时间的键<br /> (2)allkeys-Iru:使用LRU算法移除key<br /> (3) volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键<br /> (4) allkeys-random:移除随机的key<br /> (5) volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key(6) noeviction:不进行移除。针对写操作,只是返回错误信息
8.maxmemory-samples 设置样本数量
设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个
9.redis.conf介绍
redis.conf配置项说明如下:
- Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no - 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件, 可以通过pidfile指定
pidfile /var/run/redis.pid - 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大
利歌女Alessia Merz的名字
port 6379 - 绑定的主机地址
bind 127.0.0.1 - 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300 - 指定日志记录级别,Redis,总共支持四个级别: debug、 verbose、 notice、 warning, 默认为verbose
loglevel verbose - 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout - 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
databases 16 - 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。 - 指定存储至本地数据库时是否压缩数据,默认为yes, Redis采用 LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes - 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb - 指定本地数据库存放目录
dir ./ - 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof - 当master服务设置了密码保护时,slav服务连接master的密码
masterauth - 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
requirepass foobared - 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128 - 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中, 达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制, 会把Key存放内存,Value会存放在swap区
maxmemory - 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一 段时间内的数据丢失。因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一 段时间内只存在于内存中。默认为no
appendonly no - 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly .aof - 指定更新日志条件,共有3个可选值:
no: 表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步次(折衷,默认值)
appendfsync everysed - 指定是否启用虚拟内存机制,默认值为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 - 指定在超过-定的数量或者最大的元素超过某- -临界值时, 采用一-种特殊的哈希算法
hash-max- zipmap-entries 64
hash-max -zipmap-value 512 - 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes - 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一-份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf
10.redis持久化
rdb redis database
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
Redis会单独创建( fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
5.snapshotting快照
5.1save
RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,默认<br /> 是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次。<br /> **这里不太懂**
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving completely by commenting out all "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""
#注意:您可以通过注释掉所有“save”行来完全禁用保存。
#还可以通过添加带有单个空字符串参数的save指令来删除所有先前配置的保存点
#如下例所示
# save ""
save 900 1
save 120 10
save 60 10000
如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以
<br />
5.2stop-writes-on-bgsave-error
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes
# 如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制
stop-writes-on-bgsave-error这个配置默认的是yes,意思是异步创建快照(硬盘上,产生一个新的rdb文件)的时候,出错了,比如磁盘满了,那么就会报错:Redis is configured to save RDB snapshots, but is currently not able to persist on disk. but is currently not able to persist on disk.
redis就会拒绝 新的写入,也就是说,它认为,你当下,持久化数据出现了问题,你就不要再set啦,所以根源的问题是rdb持久化数据的时候出错了。
save: save时只管保存,其它不管,全部阻塞
BGSAVE: Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间
Redis Bgsave 命令用于在后台异步保存当前数据库的数据到磁盘。
BGSAVE 命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。
5.3rdbcompression
rdbcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能
# Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes
5.4rdbchecksum
rdbchecksum:在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes
5.5dbfilename
# The filename where to dump the DB
dbfilename dump.rdb
5.6dir
config get dir
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"
5.7优势
适合大规模的数据恢复<br /> 对数据完整性和一致性要求不高
5.8劣势
在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改<br /> Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
5.9如何停止
如何停止动态所有停止RDB保存规则的方法: redis-cli config set save ""
小结

aof append only file
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后批行一次以完成数据的恢复工作
先加载aof 如果aof文件出错则 启动失败
appendonly.aof 文件修复
redis-check-aof --fix appendonly.aof
0x 76: Expected \r\n, got: 7366
AOF analyzed: size=143, ok_up_to=110, diff=33
This will shrink the AOF from 143 bytes, with 33 bytes, to 110 bytes
Continue? [y/N]: y
Successfully truncated AOF
root@lushun-virtual-machine:/usr/local/bin#
6.append only mode 追加
6.1appendonly
是否开启
6.2appendfilename
文件名字
6.3appdenfsync
Always:同步持久化每次发生数据变更会被立即记录到磁盘性能较差臼数据完整性比较好
Everysec:出厂默认推荐,异步操作,每秒记录﹐如果一秒内宕机,有数据丢失
No
6.4No-appendfsync-on-rewrite
重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性。
6.5auto-aof-rewrite-min-size
最小64m 后才重写
6.6auto-aof-rewrite-percentage
是上次rewrite的一倍后可能重写
6.7 aof启动/修复/恢复
正常恢复:
启动:设置Yes
修改默认的appendonly no,改为yes
将有数据的aof文件复制一份保存到对应目录(config get dir)恢复:重启redis然后重新加载
异常恢复:
启动:设置Yes
备份被写坏的AOF文件
修复:Redis-check-aof --fix 文件名称 进行修复 //Redis-check-dump/rdb
恢复:重启redis然后重新加载
6.8rewrite
是什么:
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,I只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
重写原理:
AOF文件持续增长而过大时,会fork一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类
触发机制:
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发 //大厂3g以上
6.9优劣势
优势:
每修改同步: appendfsync always同步持久化每次发生数据变更会被立即记录到磁盘性能较差但数据完整性比较差
每秒同步: appendfsynceverysec异步操作,每秒记录―如果一秒内宕机,有数据丢失
不同步: appendfsvnc no从不同步
劣势:
相同数据集的数据而言aof文件要远大于rdb文件,
恢复速度慢于rdbAof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
6.10小结
总结
- RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得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文件,载入较新的那个。新浪微博就选用了这种架构
===============
11.redis事务
一个队列中,一次性、顺序性、排他性的执行一系列命令
开启:以MULTI开始一个事务<br /> 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列 里面<br /> 执行:由EXEC命令触发事务
1.常用命令
DISCARD<br /> 取消事务,放弃执行事务块内的所有命令。<br /> EXEC<br /> 执行所有事务块内的命令。<br /> MULTI<br /> 标记—个事务块的开始。<br /> UNWATCH<br /> 取消WATCH命令对所有key的监视。<br /> WATCH key [key ...]<br /> 监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断。
2.Case1:正常执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
127.0.0.1:6379>
3.Case2:放弃事务
//放弃当前的所有命令
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v11
QUEUED
127.0.0.1:6379> set k2 v22
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v33
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>
4.Case3:全体连坐
//只要有一个出错的 全部失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> get k3
(nil)
127.0.0.1:6379>
5.Case4:冤头债主
//执行过程中 没有出现错误 exec后 才有错误 所以谁错谁失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> set k3 33
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> get k4
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) OK
5) "v4"
127.0.0.1:6379> get k1
"v1"
两者的区别就像java的运行时异常和非运行时异常。。
redis执行部分事务
6.Case5: watch监控
6.1悲观锁/乐观锁/CAS(Check And set)
- 悲观锁
锁表
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁 - 乐观锁
锁行 每行加一个版本号
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
乐观锁策略:提交版本必须大于记录当前版本才能执行更新 - CAS
6.2初始化信用卡可用余额和欠额
balance 100 debt 10
127.0.0.1:6379> clear
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 30
6.3无加塞篡改,先监控再开启multi,保证两笔金额变动在同一个事务内团有加塞篡改
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec
(nil)
//另外开启一个线程 监视后 执行
127.0.0.1:6379> set balance 800
OK
6.4 unwatch
//成功
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> set balance 500
OK
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set balance 80
QUEUED
127.0.0.1:6379> set debt 20
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
//失败
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> set balance 500
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set balance 80
QUEUED
127.0.0.1:6379> exec
(nil)
6.5一旦执行了exec之前加的监控锁都会被取消掉了
6.6小结
Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行
通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败
7.特性
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在"事务内的查询要看到事务里的更新,在事务外查询不能看到"这个让人万分头痛的问题
不保证原子性: redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行没有回滚。部分支持。
8.redis的发布订阅
1.是什么
进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
2.命令
PSUBSCRIBE pattern [pattern] 订阅—个或多个符合给定模式的频道。<br /> PUBSUB subcommand [argument [argument ...]] 查看订阅与发布系统状态。
PUBLISH channel message 将信息发送到指定的频道。<br /> PUNSUBSCRIBE [pattern [pattern ...]] 退订所有给定模式的频道。
SUBSCRIBE channel [channel ...] 订阅给定的一个或多个频道的信息。<br /> UNSUBSCRIBE [channel [channel ...] 指退订给定的频道。
3.案例
先订阅后发布后才能收到消息,<br /> 1可以一次性订阅多个,SUBSCRIBE c1 c2 c3<br /> 2消息发布,PUBLISH c2 hello-redis
3订阅多个,通配符`*`,PSUBSCRIBE new`*`<br /> 4收取消息,PUBLISH new1 redis2015
================================================
12 主从复制
1.配从(库)不配主(库)
2.从库配置: slaveof主库IP主库端口
每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件Info replication
3.修改配置文件细节操作
拷贝多个redis.conf文件<br /> 开启daemonize yes<br /> Pid文件名字<br /> 指定端口<br /> Log文件名字<br /> Dump.rdb名字
4.常用3招
一主二仆
一个主机两个从机 主机断开 从机待命,从机断开需要重连。
1.info replication127.0.0.1:6379> info replication # Replication role:master connected_slaves:0 master_replid:146cd09e6e0f608335fed2337ce53e0da5924b2c master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0- slave ip port
- 薪火相传
上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力
中途变更转向:会清除之前的数据,重新建立拷贝最新的Slaveof 新主库IP新主库端口 - 反客为主
slaveof no one
使当前数据库停止与其他数据库的同步,转成主数据库
5.复制原理
Slave启动成功连接到master后会发送一个sync命令。<br /> Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。<br /> 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。<br /> 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步。<br /> 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。
6.哨兵模式(Sentinel )
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。<br /> 一组sentinel能够同时监控多个Master
配置步骤
- 调整结构,6379带着80、81
- 自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错。
- 配置哨兵,填写内容。
- sentinel monitor被监控数据库名字(自己起名字)127.0.0.1 6379 1
- 上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机
- 启动哨兵。
- Redis-sentinel /myredis/sentinel.conf
- 上述目录依照各自的实际情况配置,可能目录不同
- 正常主从演示
- 原有的master挂了
- 投票新选
- 重新主从继续开工,info replication查看
- 问题:如果之前的master重启回来,会不会双master冲突?
再次连接回来后,原有的master被哨兵监控,变成slave。跟随新的master
7,主从复制的缺点
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
6.java连接redis jedis
Jedis jedis = new Jedis("10.2.172.175",6379);
// jedis.auth("");
String ping = jedis.ping();
System.out.println(ping);
1.事务
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("10.2.172.175",6380);
Transaction transaction = jedis.multi();
transaction.set("k4","v44");
transaction.set("k5","v55");
transaction.discard();
//事务的执行
// transaction.exec();
}
}
public class TestTXSock {
public boolean transMethod() throws InterruptedException {
Jedis jedis = new Jedis("10.2.172.175",6380);
int balance;
int debt;
int amToSubtract=10;
jedis.watch("balance");
// jedis.set("balance","5000");
Thread.sleep(10000);
balance=Integer.parseInt(jedis.get("balance"));
if(balance<amToSubtract){
jedis.unwatch();
System.out.println("modify");
return false;
}else {
System.out.println("******transaction");
Transaction transaction = jedis.multi();
transaction.decrBy("balance",amToSubtract);
transaction.incrBy("debt",amToSubtract);
transaction.exec();
//这里有问题 只是模拟了 balance不足的情况。 如果balance超出了 执行失败。
balance =Integer.parseInt(jedis.get("balance"));
debt = Integer.parseInt(jedis.get("debt"));
System.out.println("balance="+balance);
System.out.println("debt="+debt);
return true;
}
}
/**
* 通俗点讲, watch命令就是标记一个键,如果标记了一个键,
* 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中重新再尝试一次。
* 首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减;足够的话,就启动事务进行更新操作,
* 如果在此期间键balance被其它人修改,那在提交事务(执行exec)时就会报错,程序中通常可以捕获这类错误再重新执行一次,直到成功。
* @param args
*/
public static void main(String[] args) throws InterruptedException {
TestTXSock testTXSock = new TestTXSock();
boolean retValue = testTXSock.transMethod();
System.out.println("main retValue-----:"+retValue);
}
}
2.主从复制
public class TestMS {
public static void main(String[] args) {
Jedis jedis_M = new Jedis("192.168.43.68",6379);
Jedis jedis_S = new Jedis("192.168.43.68",6380);
jedis_S.slaveof("192.168.43.68",6379);
jedis_M.set("class","1122132");
String result = jedis_S.get("class");
System.out.println(result);
}
}
3.连接池
- 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时抛出JedisConnectionEXWHEN_EXHAUSTED_GROW —>则表示新建一个jedis实例,也就说设置的maxActive无用;
- maxWait:表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛
- JedisConnectionException;
- testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping())﹔如果为true,则得到的jedis实例均是可用的;
- testOnReturn: return一个jedis实例给pool时,是否检查连接可用性(ping()) ;
public class JedisPoolUtil {
private static volatile JedisPool jedisPool=null;
private JedisPoolUtil(){}
public static JedisPool getJedisPool() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(32);
poolConfig.setMaxWaitMillis(100 * 1000);
poolConfig.setMaxTotal(1000);
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, "192.168.43.68", 6379);
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool, Jedis jedis){
if(null !=jedis){
// jedis.close();
// jedis.quit();
// jedis.disconnect();
jedisPool.returnResourceObject(jedis);
}
}
}
public class TestPool {
public static void main(String[] args) {
JedisPool jedisPool=JedisPoolUtil.getJedisPool();
Jedis jedis = null;
try {
jedis=jedisPool.getResource();
jedis.set("aa","bb");
System.out.println(jedis.toString());
}catch (Exception e){
}finally {
JedisPoolUtil.release(jedisPool,jedis);
}
System.out.println(jedis.toString());
}
}us
