- RDBMS(关系型数据库管理系统) VS NoSQL(not only sql)
RDBMS:
-
高度组织化、结构化数据
-
结构化查询语言(SQL)
-
数据和关系都存储在单独的表中
-
数据操纵语言,数据定义语言
-
严格的一致性
-
基础事务
NoSQL:
-
代表着不仅仅是SQL
-
没有声明性查询语言
-
没有预定义的模式
-
键值对存储,列存储,文档存储,图形数据库
-
最终一致性,而非ACID属性
-
非结构化和不可预知的数据
-
CAP定理(一致性、可用性、分区容错性. 分布式系统不可能同时满足这三点)
-
高性能、高可用、高可伸缩性
- 非关系型数据库四大分类
- KV键值: 阿里/百度: memcache+redis, 美团: redis+tair, 新浪: BerkeleyDB+redis
- 文档型数据库(bson格式比较多): MongoDB, CouchDB
- 列存储数据库: Cassandra, HBase.应用于分布式文件系统
- 图关系数据库: Neo4J, InfoGrid. 应用于构建关系图谱. 比如朋友圈社交网络、广告推荐系统、社交网络、推荐系统等.
- CAP + BASE
CAP:
-
consistency 一致性
-
availability 高可用性
-
partition tolerance分区容忍性
BASE: 分布式事务最终一致性的有效解决方案
-
Basically Available 基本可用(可能会出现响应时间上的损失或系统功能上的损失等.比如部分消费者被引导到一个降级页面)
-
Soft State: 软状态(系统中的数据存在中间状态,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时)
-
Eventually consistent 最终一致性(所有的数据副本,在经过一段时间的同步之后,最终都能达到一个一致的状态)
CP: redis、mongodb
CA: 传统关系型数据库
AP: 大多数网站架构的选择
- 分布式和集群
- 分布式: 不同的多台服务器上面部署不同的服务模块(工程),他们之间通过RPC/RMI通信和调用,对外提供服务和组内协作
- 集群: 不同的多台服务器上面部署相同的服务模块,通过分布式调度软件进行统一的调度,对外提供服务和访问.
- redis(K-V+cache+persistence)
- remote dictionary server 远程字典服务器. 它是一个高性能的(key-value)分布式内存数据库,基于内存运行,并支持持久化的NoSql数据库.也被称为数据结构服务器.
- 默认16个数据库,下标从0开始(及默认范围为0-15).通过select 切换,比如select 7
- redis与其它NoSql数据库相比的特点
- redis支持数据的持久化,可以将内存中的数据保存在磁盘上,重启的时候可以再次加载进行使用
- redis不仅仅支持简单的key-value类型的数据,同时还提供list、set、zset、hash等数据结构的存储
- redis支持数据的备份,即master-slave模式的数据备份
- redis的用途
- 内存存储和持久化: redis支持异步将内存中的数据写到磁盘上,同时不影响继续服务.
- 取最新的N个数据的操作: 比如可以将最新的10条平均的ID放在redis的list集合中
- 模拟类似于HttpSession这种需要设定过期时间的功能
- 发布、订阅消息系统
- 定时器、计数器
- redis的五大数据类型
- String 字符串
- Hash 哈希(类似java中的map)
- List 列表(底层使用双向链表实现,可以在列表的头部或尾部添加元素,类似LinkedList)
- Set 集合
- ZSet 有序集合.(有序底层实现原理: 每个元素关联一个double类型的分数,通过分数进行从小到大的排序. zset成员唯一,分数可以重复)
- redis常用命令
-
key关键字命令
- dbsize命令: 查看当前数据库的key数量
- keys *: 显示当前数据库所有key的值
- exists key: 查看当前数据库中是否存在key值
- type key: 返回key所存储的值的类型
- move key number: 将当前数据库中的key对应的键值对移动到number(默认0-15)数据库
- expire key time: 设定key键过期时间,单位秒
- ttl key: 显示key键生命周期(ttl: time to live 存活时间).结果为-1表示永不过期,-2表示已经过期,过期的key会从内存中移除.正数表示剩余存活时间(单位秒).
- del key: 删除key键
- flushdb: 清空当前数据库(高风险操作)
- flushall: 清空所有数据库(高风险操作)
-
string类型命令
- set key val: 在当前数据库中创建key-val对(val为字符串),如果key已经存在,则会覆盖val值
- get key:获取key对应的val值
- append: 填充到string类型的val的尾部
- strlen: 获取string类型的val的长度
- incr key: key所对应的值+1(值必须为数字)
- decr key: key所对应的值-1(值必须为数字)
- incrby key number: key所对应的值+number(值必须为数字)
- decrby key number: key所对应的值-number(值必须为数字)
- getrange key start end: 获取key所对应的val[start: end]区间值
- setrange key start new_val: key所对应的val从start开始的长度和new_val相等的字段被new_val替换
- setex key time val: 设置key所对应的值为val,在time时间后过期,从内存中移除.如果key值已经存在则会覆盖(setex: set with expire)
- setnx key val: 如果key不存在则创建key-val,否则不创建.返回值为1表示创建成功,为0表示创建失败.(setnx: set if not exist)
- mset k1 v1 k2 v2… : 同时创建一个或多个key-val对.如果之前有的key已经存在,则会覆盖.
- mget k1 k2…: 获取k1, k2…对应的val值.(允许有的key不存在,不存在的返回nil空值)
- msetnx k1 v1 k2 v2… : 如果k1、k2…等都不存在,则创建key-val对,否则创建失败,返回0.
-
list类型(本质是双向链表,所以左右都可以插入、取等操作)命令
- lpush key val1 val2…: 将一个或多个值插入到列表头部(比如lpush list 1 2 3,则list为3 2 1)
- rpush key val1 val2…: 在列表中添加一个或多个值(比如rpush list 1 2 3,则list为1 2 3)
- lrange key start end: 输出key所对应的list[start:end]
- lpop key: 移出并获取列表的第一个元素
- rpop key: 移出并获取列表的最后一个元素
- lindex key index: 通过索引获取列表中的元素
- llen: 获取列表长度
- lrem key count val: 移除列表中count个值为val的元素.如果count值大于val的个数,则有多少删多少
- ltrim key start end: 截取key所对应的val[start,end],再赋值给val
- rpoplpush source destination: 移除source列表的最后一个元素,并将该元素添加到destination列表的左侧并返回destination对应list中的元素数量
- lset key index val: 通过索引设置列表元素的值
- linsert key before/after val1 val2: 在列表中的值为val1(如果有多个值为val1,则选取左 边第一个; 如果不存在val1,则插入失败)的前面或后面加入val2元素
- 注: 如果list中没有值了则对应的键也就消失了
-
set类型命令
- sadd key val1 val2… : 向集合添加一个或多个成员(如果val1 val2…值有重复,则会去重)
- smembers key: 返回集合中的所有成员
- scard key: 获取集合中的元素个数
- srem key val: 删除集合中的val
- srandmember key number: 从集合中随机出number个数(如果number大于集合中的元素个数,则返回集合中的所有元素)
- spop key: 移除并返回集合中的一个随机元素
- smove key1 key2 val: 将val从key1集合移动到key2集合
- sdiff key1 key2… : 返回第一个集合与其他集合之间的差异
- sinter key1 key2… : 返回给定所有集合的交集
- sunion key1 key2… : 返回给定所有集合的并集
-
hash类型(v是一个键值对)命令
- hset key field value: 将哈希表 key 中的字段 field 的值设为 value
- hget key field: 获取存储在哈希表中指定字段的值
- hmset key field1 val1 field2 val2…: 同时将多个 field-val (域-值)对设置到哈希表 key 中
- hgetall key: 获取在哈希表中指定 key 的所有字段和值
- hdel key field1 field2… : 删除一个或多个哈希表字段
- hlen key: 获取哈希表中字段的数量
- hexists key field: 查看哈希表 key 中,指定的字段是否存在
- hkeys key: 获取所有哈希表中的字段
- hvals key: 获取哈希表中所有值
- hincrby key field increment: 为哈希表 key 中的指定字段的整数值加上增量 increment
- hincrbyfloat key field increment: 为哈希表 key 中的指定字段的浮点数值加上增量 increment
- hsetnx key field val: 只有在字段 field 不存在时,设置哈希表字段的值
-
Zset类型(在set的基础上加上一个score值用于排序)命令
- zadd key score1 member1 [score2 member2]: 向有序集合添加一个或多个成员,或者更新已存在成员的分数
- zrange key start end [withscores]: 通过索引区间返回有序集合指定区间内的成员(加上withscores则表示带上各字段的score)
- zrangebyscore key [(]min [(]max [withscores] [limit start number]: 通过分数返回有序集合指定区间内的成员
注: 1. 加上“(”表示开区间;- 加上withscores则表示带上各字段的score;
- 加上limit表示对获取的结果再截取从start索引开始的number个字段)
- zrem key member [member …]: 移除有序集合中的一个或多个成员
- zcard key: 获取有序集合的成员数
- zcount key min max: 计算在有序集合中指定区间分数的成员数
- zrank key nember: 返回有序集合中指定成员的索引(升序排序)
- zscore key member: 返回有序集中,成员的分数值
- zrevrank key member: 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
- zrevrange key start end [withscores]: 返回有序集中指定区间内的成员,通过索引,分数从高到低
- zrevrangebyscore key max min [withscores]: 返回有序集中指定分数区间内的成员,分数从高到低排序
-
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进行数据同步 replicaof <masterip> <masterport> (5.0.0之前是slaveof命令) 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到磁盘上,访问多的页面由磁盘自动换出到内存中 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使用心得
- 启动redis的时候通过redis.conf配置文件的方式启动,所有的东西都可以通过配置指定
- redis.conf、redis-server、redis-cli、appendonly.aof、dump.rdb等文件可能距离很远,或许与redis-cli同目录下的appendonly.aof并不是当前redis使用持久化文件,要看redis.conf文件中的具体配置
比如自己mac系统中各文件目录为:
导致总是感觉自己在redis中所做的操作总是记录不到aof/rdb持久化文件上.redis.conf所在目录: /usr/local/etc redis-server / redis-cli所在目录: /usr/local/Cellar/redis/6.0.9/bin appendonly.aof / dump.rdb所在目录: /usr/local/var/db/redis
- 若redis-server启动失败,有类似如下提示,可以根据提示通过redis-check-aof --fix appendonly.aof(该文件路径) / redis-check-rdb…修复.
Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>
-
redis持久化之RDB(redis database)
- RDB具体内容
在指定的时间间隔内将内存中的数据集快照写入磁盘.也就是行话讲的snapshot快照,它恢复时是将快照文件直接读入内存中. - RDB具体实现
redis会单独fork一个子进程来进行持久化,会先将数据写入到一个临时文件(默认dump.rdb)中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件.整个过程中,主进程不进行任何IO操作,这就确保了极高的性能.
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那么RDB方法要比AOF方法更加高效.RDB的缺点是最后一次持久化后的数据可能会丢失.
禁用RDB持久化,只要不设置任何save指令,或者给save传入一个空字符串参数也可以.默认如下:save 900 1 save 300 10 save 60 10000
- fork的详细解释
fork的作用是赋值一个与当前进行一样的进程.新进程的所有数据(变量、环境变量、程序计数器等)数值和原进程一致,但是是一个全新的进程,并作为原进程的子进程. - 关于flushall命令对RDB的影响
当执行FLUSHALL命令时,Redis会清除数据库中的所有数据。需要注意的是:不论清空数据库的过程是否触发了自动快照的条件,只要自动快照条件不为空,Redis就会执行一次快照操作,即这时dump.rdb文件是一个空文件.当没有定义自动快照条件时,执行FLUSHALL命令不会进行快照操作。 - 触发RDB快照的方式
- 满足配置文件中默认的快照配置,即如下:
save 900 1 // 900秒内有1个更改 save 300 10 // 300秒内有10个更改 save 60 10000 // 60秒内有10000个更改
- 执行save/bgsave命令
- save会阻塞主进程,客户端无法连接redis,等SAVE完成后,主进程才开始工作,客户端可以连接
- bgsave会fork一个子进程,在执行save子进程的过程中,不影响主进程,客户端可以正常连接redis,等子进程fork执行save完成后,通知主进程,子进程关闭。
- 执行flushall命令,但是产生的dump.rdb文件是一个空文件,无意义.
- 满足配置文件中默认的快照配置,即如下:
- RDB恢复的方式
将备份文件(默认是dump.rdb)移动到redis安装目录并启动服务即可.
注: 可以通过config get dir获取目录 - 关于RDB快照的几个关键字段的解释
- stop-writes-on-bgsave-error: 默认yes,作用:当最近的一次rdb快照生成失败,redis将不再接受相关的写命令,以此提醒用户备份失败.
- rdbcompression: 在进行镜像备份时,是否进行压缩。yes:采用LZF算法压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
- rdbchecksum: 是否使用CRC64算法进行数据校验,如果使用该算法当存储或者加载rbd文件的时候会有一个10%左右的性能下降.
- dbfilename: 数据库文件的文件名,默认dump.rdb
- dir: 数据目录,即存储dump.rdb等文件的目录
- RDB备份的优劣势
- 优势:
- 适合大规模的数据恢复.因为redis加载rdb文件恢复数据很快
- 对数据完整性和一致性要求不高(redis意外down掉可能会丢失最后一次快照备份后的修改,导致数据不完整.这不应该是劣势吗…)
- 劣势
- 在一定的时间间隔做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改.
- fork的时候,内存中的数据被克隆了一份,占用大约两倍的内存空间
- 优势:
- 停止rdb备份的方式
- 在redis.conf中修改: save “” 或者删掉该部分
- redis-cli config save “”
- RDB总结
- redis持久化之AOF(append only file)
- AOF具体内容
以日志的形式来记录每个写操作.将redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但是不可以改写文件,redis启动之初会读取该文件重新构建数据.
换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作. - AOF能够和RDB同时使用.如果启用了AOF,则优先使用AOF.
- 关于AOF的几个关键字段的解释
- appendonly: yes/no,使不使用aof
- appendfilename: aof文件名,默认appendonly.aof
- appendfsync: 设置持久化方式,上面的redis.conf配置项说明有详细解释
- no-appendfsync-on-rewrite: 重写时是否可以运用appendfsync,用默认no即可,保证数据安全性.
- auto-aof-rewrite-min-size: 设置重写的基准值.默认100,表示要满足AOF大小是上次rewrite后大小的一倍(超出一倍,和二倍意思一样)
- auto-aof-rewrite-percentage: 设置重写的基准值.默认64mb,表示要满足AOF文件大小要大于等于64MB.
- 上述这两个条件都满足时,AOF文件才会重写
- AOF重写rewrite
- aof rewrite含义
AOF采用文件追加的方式,导致文件越来越大,为避免这种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof. - rewrite原理
AOF文件持续增长而过大时,会fork一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,如果当前键没有过期,则通过set/rpush/sadd/hmset/zadd指令对当前键执行一次操作即可.重写aof文件的操作并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照类似. - 触发机制
redis会记录上次重写时的AOF大小,默认配置是当AOF大小是上次rewrite后大小的一倍且文件大于64M时触发. - AOF重写导致数据库中数据和重写后的AOF文件数据不一致问题解决办法
Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用,Redis服务器主进程在执行完写命令之后,会同时将这个写命令追加到AOF缓冲区和AOF重写缓冲区.这样可以保证:- AOF缓冲区的内容会定期被写入和同步到AOF文件中,对现有的AOF文件的处理工作会正常进行
- 从创建子进程开始,服务器执行的所有写操作都会被记录到AOF重写缓冲区中,等待rewrite操作完成的时候,将新操作直接追加到新的AOF中
- aof rewrite含义
- AOF的优劣势
- 优势:
- 通常AOF文件保存的数据要比RDB文件保存的数据更加完整
- 劣势:
- 相同的数据集而言,AOF文件要远大于RDB文件
- AOF恢复速度慢,因为要一步步的执行命令操作
- 使用AOF的速度一般会慢于RDB.因为AOF缓存中的数据会根据设定的fsync策略同步到AOF文件,堵塞redis-server的请求处理,而RDB会fork一个子进程,不会影响到redis-server.如果appendfsync值设置为no,表示等操作系统进行数据缓存同步到磁盘,这时速度和RDB一样,但是风险太大,如果redis出故障,可能数据全部丢失.
- 优势:
- AOF总结
注: fsync是指把缓存中的写指令记录到磁盘中 - RDB数据不实时,而且AOF和RDB同时使用会优先载入AOF文件来恢复原始数据,那么是不是只是用AOF就可以了呢?
不是.RDB更适合用于备份数据库,能够快速重启,而且不会有AOF可能潜在的bug. - 性能建议
- redis事务
-
定义
可以一次执行多个命令,本质是一组命令的集合.一个事务中的所有命令都会序列化,按顺序的串行化执行而不会被其他命令插入. -
作用:
一个队列中,一次性、顺序性、排他性的执行一系列命令 -
redis事务常用命令
- discard: 取消事务,放弃执行事务内的所有命令
- exec: 执行所有事务块内的命令
- multi: 标记一个事务块的开始.该命令总是返回OK
- watch key[key…]: 监视一个(或多个)key,如果在事务执行之前,这个(或这些)key被其他命令所改动,则放弃事务执行.
- unwatch: 取消watch命令对所有key的监视
-
redis事务的执行不是原子性的
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。redis事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做,但是如果中间某条指令直接error报错,则会取消事务,所有指令都不会执行成功。
注: 中间某条指令的失败指的是没有error报错,还是返回QUEUED,表示入队.比如给一个val为字符串的key执行incr操作,并不会报错,该指令执行失败,但并不影响前后指令的执行) -
redis事务执行三阶段
- 开启: multi开启一个redis事务
- 入队: 将多个命令入队到事务中,接到这些命令并不会立刻执行,而是放到等待执行的事务队列中
- 执行: 由exec命令触发事务
-
redis三特性
- 单独的隔离操作: 事务中的所有命令都会序列化、按顺序的执行.事务在执行的过程中不会对其他客户端发来的命令请求打断(意思是说在事务执行过程中,即使有其他客户端命令对事务中操作的数据做了修改也没关系,这样的话事务的执行是建立在其他客户端对数据做了修改的基础上的).
- 没有隔离级别的概念: 队列中的命令在没有提交之前都不会实际的执行.也就不存在“事务内的查询要看到事务的更新,在事务外查询看不到”这个头疼的问题.
- 不保证原子性: redis同一个事务中如果有一个指令执行失败,其他命令仍然会执行,不会回滚.如果指令报错那么事务中的所有操作都会执行失败(上面有具体阐述).
- redis watch监控
- 乐观锁/悲观锁/CAS
- 乐观锁(多读场景): 操作时很乐观,认为在该线程执行期间,不会有其他线程对数据进行修改,因此不会上锁.但是在更新的时候会判断其他线程在此之前有没有对数据进行修改.一般使用版本号机制(提交版本必须大于当前版本才能更新)/CAS实现.
- 悲观锁(多写场景): 操作时很悲观,认为在该线程执行期间,会有其他线程对数据进行修改,所以在每次拿数据时都会上锁.其他线程就会堵塞.比如行锁、表锁、读锁(其他线程可以读但是不能写)、写锁(其他线程既不能读也不能写)、java中的synchronized关键字.
注: 悲观锁并不是可以读但是不能写.读锁和写锁都是悲观锁的其中一种. - CAS: compare and swap. 有三个变量: 需要读写的内存位置V、预期原值A、拟写入的新值B.如果在位置V查出的数值和A相等,则执行写操作,将B写入.否则写入失败.不管执行成功还是失败都返回原值A.
- redis消息发布订阅(基本不会用redis的消息发布订阅,了解即可)
- 定义
redis消息发布订阅是进程间的一种消息通信模式:发布者(pub)发布消息,订阅者(sub)接收消息. - 常用命令
- 定义
- redis主从复制
- 含义
主机数据更新后根据配置和策略,自动同步到备机的master/slave机制,master以写为主,slave以读为主. - 作用
- 读写分离(master负责写,slave不能执行写操作,只负责读)
- 容灾恢复
- 具体实现
- 配从(库)不配主(库)
- 从库配置: replicaof主库IP 主库端口(redis5.0.0之前是replica命令).
- 从库conf文件具体配置
- 拷贝多个redis.conf文件
- 开启daemonize(守护进程: 一直在后台运行,除非手动kill或系统关闭)
daemonize yes
- 修改pid文件名字
pidfile /var/run/redis_6379.pid
- 指定端口
port: 6379
- log文件名字
logfile "6379.log"
- dump.rdb名字
dbfilename dump6379.rdb
- 从库每次和master主库断开后,需要重新连接,除非配置进redis.conf文件.
- redis复制原理
slave启动成功连接master后会发送一个sync命令,master接到命令后启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步.
全量复制: slave服务在接受到数据库文件数据后,将其存盘并加载到内存中.
增量复制: master继续将新的所有收集到的修改命令依次传给slave,完成同步.
只要重新连接master,将会自动执行全量复制(完全同步操作). - redis主从复制常见形式
- sentinel哨兵模式(实际使用,下面三个目前都不用了)
- 与redis.conf配置文件同目录下新建sentinel.conf文件(名字绝不能错)
sentinel monitor 被监控的主机名字(随便起) 主机IP 主机端口 最少得多少票能成为master
注: 一组sentinel能同时监控多个master
sentinel monitor host6379 127.0.0.1 6379 1
- 启动哨兵
redis-sentinel sentinel.conf(如果不在sentinel.conf目录下执行该文件需要写绝对路径)
- master挂掉后根据投票slave6381上位(原先的master6379恢复后变成slave)
- 与redis.conf配置文件同目录下新建sentinel.conf文件(名字绝不能错)
- 一主二仆(一个master两个slave)
- 若master故障死机,slave不会主动上位,处于等待状态.master恢复后还是主机和原来一样
注: info replication: 查看主从复制信息.
- 若master故障死机,slave不会主动上位,处于等待状态.master恢复后还是主机和原来一样
- 薪火相传(上一个slave可以是下一个slave的master,slave同样可以接受其他slaves的连接和同步请求)
指令: replicaof 新主库ip 新主库端口号(slave中途变更转向会清除之前的数据,重新建立拷贝最新的)
作用: 有效减轻master的写压力
缺点: 延迟、数据失真(和真实数据有偏差)
注: 虽然6380接受6379的消息,并向6381发布消息,但是它还是slave.master只有7379一个 - 反客为主
指令: replicaof not one(使当前数据库停止与其他数据库的同步,转成主数据库)
注: 7379master不需要挂掉后,slave才能反客为主,只需要执行replicaof no one指令就能变成master并保存原先同步的数据.但如果master不挂掉,slave也没必要反客为主.
- sentinel哨兵模式(实际使用,下面三个目前都不用了)
- redis jedisPool简单使用
- 导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.2</version>
</dependency>
- JedisPoolUtil.java
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;
private JedisPoolUtil(){
}
public static JedisPool getJedisPoolInstance(){
if(jedisPool == null){
synchronized (JedisPoolUtil.class){
if(jedisPool == null){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 连接池最大连接数
jedisPoolConfig.setMaxTotal(1000);
// 连接池最多有多少个idle(空闲)实例
jedisPoolConfig.setMaxIdle(32);
// 最大等待时间,如果超过该时间,抛出JedisConnectionException
jedisPoolConfig.setMaxWaitMillis(100*1000);
// 获得一个jedis的时候是否检查连接可用性(ping()),如果为true,则得到的jedis实例都是可用的
jedisPoolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
}
}
}
return jedisPool;
}
// 释放哪个连接池的哪个jedis实例
public static void release(JedisPool jedisPool, Jedis jedis){
if(jedis != null){
// 将jedis实例放回池中
jedisPool.returnResourceObject(jedis);
}
}
}
- TestPool.java
package com.atguigu.redis.test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class TestPool {
public static void main(String[] args) {
JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set("aa", "bb");
}catch (Exception e){
e.printStackTrace();
}finally {
JedisPoolUtil.release(jedisPool, jedis);
}
}
}
- 查看结果