NoSQL,提倡运用非关系型的数据存储。
四大分类:文章源自JAVA秀-https://www.javaxiu.com/1219.html
- 键值(Key-Value)存储数据库: 这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。
- 列存储数据库: 这部分数据库通常是用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列。
- 文档型数据库: 该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储。
- 图形数据库: 它是使用灵活的图形模型,并且能够扩展到多个服务器上。
Redis【redis.cn redis.io】 这是一个C开发的高性能的kV分布式内存存储系统,它支持存储的value类型包括string、list、set、zset和hash。 这些数据类型都支持push/pop、add/remove及取交集并集和差集,排序及更丰富的操作。文章源自JAVA秀-https://www.javaxiu.com/1219.html
Redis数据都是缓存在内存中,并且会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。文章源自JAVA秀-https://www.javaxiu.com/1219.html
安装
解压gz文件,执行 make MALLOC=libc && make install文章源自JAVA秀-https://www.javaxiu.com/1219.html
多数据库支持
默认支持16个数据库;可以理解为一个命名空间文章源自JAVA秀-https://www.javaxiu.com/1219.html
跟关系型数据库不一样的点文章源自JAVA秀-https://www.javaxiu.com/1219.html
- redis不支持自定义数据库名
- 每个数据库不能单独设置授权
- 数据库之间并不是完全隔离的。 可以通过flush all命令清空redis实例面的所有数据库中的数据
- 通过select dbid去选择不同的数据库命名空间,dbid的取值范围默认是0-15
Redis主要配置
daemonize no
Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
pidfile /var/run/redis.pid
当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
port 6379
Redis监听端口,默认端口为6379
bind 127.0.0.1
绑定的主机地址,只接收来自于该IP地址的请求,如果不进行设置,那么将处理所有请求
timeout 300
当客户端闲置多长时间后关闭连接,如果指定为0,表示一直连接
loglevel verbose
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
logfile stdout
日志记录方式,默认为标准输出
databases 16
设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
save <seconds> <changes>
指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
Redis默认配置文件中提供了三个条件:
save 900 1 900秒(15分钟)内有1个更改
save 300 10 300秒(5分钟)内有10个更改
save 60 10000 60 秒(1分钟)内有10000个更改
rdbcompression yes
指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,
如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
dbfilename dump.rdb
指定本地数据库文件名,默认值为dump.rdb
dir ./
指定本地数据库存放目录
slaveof <masterip> <masterport>
设置当本机为slave服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
requirepass foobared
设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
masterauth <master-password>
当master服务设置了密码保护时,slav服务连接master的密码
maxclients 128
同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxmemory <bytes>
redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则通过maxmemory-policy来指定。
如果redis无法根据移除规则来移除内存中的数据,则会针对那些需要申请内存的指令返回错误信息,但是对于无内存申请的指令,仍然会正常响应。
如果是主redis,那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在设置“不移除”的情况下,才不用考虑这个因素
maxmemory-policy noeviction
内存淘汰策略:默认不进行移除
allkeys-lru:使用LRU算法移除key
volatile-lru:使用LRU算法移除key,只对设置了过期时间的键
allkeys-random:移除随机的key
volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
noeviction:不进行移除。针对写操作,只是返回错误信息
appendonly no
指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。
因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendfilename appendonly.aof
指定更新日志文件名,默认为appendonly.aof
appendfsync everysec
指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折中,默认值)文章源自JAVA秀-https://www.javaxiu.com/1219.html
Redis五大数据类型
String(字符串):一个key对应一个value,其value值允许为二进制数据,顶线为512M。
Hash(哈希):string类型的field和value的映射表,特别适合用于存储对象
List(列表):string类型的有序可重复的列表,底层是基于链表实现的
如果键不存在,创建新的链表;如果键已存在,新增内容;如果值全移除,对应的键也就消失了。
链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。
Set(集合):string类型的无序不可重复集合,底层是基于哈希表实现的
Zset(有序集合):string类型元素的集合,且不允许重复的成员。底层是基于哈希表和跳跃表实现的
每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。文章源自JAVA秀-https://www.javaxiu.com/1219.html
常用API:
(1)库:
flushdb 清空当前库
flushall 清空所有库
select $db 切换库
(2)键:
keys * 查看所有key,支持? *匹配符
exists $Key 判断某个key是否存在
move $key $db 将当前记录从当前库移动至其他库
(p)expire $key $seconds:为给定的key设置过期时间,负数表示立即失效
presist $key 取消key的失效时间,取消成功返回1,失败(key本身已经是永久有效或者key根本不存在)返回0
(p)ttl $key 查看还有多少秒过期,-1表示永不过期,-2表示已过期,正数表示还有*秒过期
type $key 查看你的key是什么类型
(3)String:
set/get/del/append/strlen
incr/decr/incrby/decrby:一定要是数字才能进行加减
getrange/setrange:范围获取及替换
setex $key $seconds/setnx $key
mset/mget/getset(先get再set)
(4)Hash:
hset/hget/hmset/hmget/hgetall/hdel/hlen
hset如果是插入新的Key-Value返回的是1,如果是修改旧的Value,返回是0
hexists key 在key里面的某个值的key
hkeys/hvals
hincrby/hincrbyfloat
hsetnx
(5)List:
lpush/rpush/lpop/rpop
lrange/lindex,按照索引下标获得元素
llen
lrem $key $count $value:删除count个value
ltrim $key $startIndex $endIndex,截取指定范围的值后再赋值给key
rpoplpush $源列表 $目的列表:弹出源列表最右端,放在目标列表最左端
lset $key $index $value:根据索引替换
linsert key before/after 值1 值2 在列表list中寻找值为target的元素,然后根据before还是after插入新元素value。如果有多个值,则选第一个,如果没找到,不会进行插入,会返回-1.
(6)set:
sadd/smembers/sismember
scard 获取集合里面的元素个数
srem $key $value 删除集合中元素
srandmember $key $count(随机选择出几个元素数)
spop key 随机出栈
smove key1 key2 在key1里某个值 作用是将key1里的某个值赋给key2
差集:sdiff/ 交集:sinter/ 并集:sunion
(7)ZSet:
zadd/zrange(withscores)
zrangebyscore key 开始score 结束score {limit 作用是返回限制}
zrem key 某score下对应的value值,作用是删除元素
zcard/zcount key score区间/zrank key values值,作用是获得下标值/zscore key 对应值,获得分数
zrevrank key values值,作用是逆序获得下标值
zrevrange
zrevrangebyscore key 结束score 开始score文章源自JAVA秀-https://www.javaxiu.com/1219.html
事物
Redis中的事务是一组命令的集合,事务同命令一样都是Redis最小的执行单位
Redis事务的实现需要用到multi和exec两个命令:
multi命令标识事物的开始,此后的命令不会直接提交,而是缓存在一个队列中
最后发送exec命令来提交事务,即将缓存队列中的事物全部提交;或者发送discard命令来终止事物,即将缓存队列清空文章源自JAVA秀-https://www.javaxiu.com/1219.html
Redis事务错误处理:
Redis对于不同类型的事物错误有两种处理方式:
1)语法错误:忽略这个事务中的所有命令,都不执行
2)运行错误:忽略本条命令,其他命令正常执行
Redis的锁:
redis提供了乐观锁机制,使用watch命令实现操作
watch可以监控指定key,若此key在一个事务过程中被其他事务修改,则包含此键的事物中的所有命令全部执行失败,返回nil
如果使用expire指令给键设置了过期时间,到了时间自动删除这种不会被watch指令认为改变了值,但是执行del删除会被认为是改变了值
一旦执行exec、multi、discard则会结束watch监控文章源自JAVA秀-https://www.javaxiu.com/1219.html
持久化策略
Redis提供两种持久化方式:RDB和AOF。Redis允许两者结合,会先加载AOF,也允许两者同时关闭。
RDB可以定时备份内存中的数据集。服务器启动的时候,可以从RDB文件中恢复数据集。
AOF可以记录服务器的所有写操作。在服务器重新启动的时候,会把所有的写操作重新执行一遍,从而实现数据备份。
RDB:
RDB配置:
save <时间> <更新> 如果指定的秒数和数据库写操作次数都满足了就将数据库保存
15分钟内 至少1个key值改变 (则进行数据库保存--持久化)
5分钟内 至少10个key值改变 (则进行数据库保存--持久化)
1分钟内 至少10000个key值改变 (则进行数据库保存--持久化)
可以通过注释save 或者save ""禁用此配置
stop-writes-on-bgsave-error yes
默认情况下,RDB持久化操作失败,Redis会停止接受更新操作,直到后台存储操作进程再次工作,Redis会自动允许更新操作。
如果关掉这项功能,即使后台保存操作出错,redis也仍然可以继续像平常一样工作。
rdbcompression yes
是否在导出.rdb数据库文件的时候采用LZF压缩,默认yes
rdbchecksum yes
是否在.rdb数据库文件结尾放置一段CRC64的校验数据用于校验检查
dir ./
工作目录
dbfilename dump.rdb
导出数据库的文件名称
RDB的工作机制:
当RDB持久化条件满足,redis需要执行RDB的时候,服务器会执行以下操作:
1.redis调用系统函数fork(),创建一个子进程,负责将数据集写入到一个临时RDB文件中。
2.当子进程完成对临时RDB文件的写入时,redis用新的临时RDB文件替换原来的RDB文件,并删除旧RDB文件。
注意:
1)在执行fork的时候操作系统会使用写时复制(copy-on-write)策略,即fork函数发生的一刻父子进程共享同一内存数据,
当父进程要更改其中某片数据时,操作系统会将该片数据复制一份以保证子进程的数据不受影响,
所以新的RDB文件存储的是执行fork那一刻的内存数据。
2)Redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。
3)除了自动快照,还可以手动发送SAVE或BGSAVE命令让Redis执行快照,
两个命令的区别在于,前者是由主进程进行快照操作,会阻塞住其他请求,后者会通过fork子进程进行快照操作。
4)通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。
RDB的优点:
1.RDB保存了redis在某个时间点上的数据集,这种文件非常适合用于进行备份和灾难恢复。
2.生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
3.RDB在恢复大数据集时的速度比AOF的恢复速度要快。
RDB缺点:
1.因为RDB文件不是实时保存,所以一旦发生故障停机, 就可能会丢失几分钟的数据。
2.每次保存RDB的时候,Redis都要fork()出一个子进程,并由子进程来进行实际的持久化工作。
在数据集比较庞大时, fork()可能会非常耗时,造成服务器在某某毫秒内停止处理客户端。
AOF:
AOF配置:
appendonly yes
是否开启AOF,默认关闭(no)
appendfilename appendonly.aof
指定 AOF 文件名
appendfsync
Redis的刷写模式,支持三种不同的刷写模式:
appendfsync always:每次收到写命令就立即强制写入磁盘
appendfsync everysec:每秒钟强制写入磁盘一次
appendfsync no:完全依赖OS的写入,一般为30秒左右一次
auto-aof-rewrite-percentage 100
当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rewrite-min-size 64mb
当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。
no-appendfsync-on-rewrite no
日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no。
AOF工作机制:
redis的刷新模式:
由于写操作通常是有缓冲的,所以有可能AOF操作并没有写到硬盘中,一般可以通过fsync()来强制输出到硬盘中。
而fsync()的频率可以通过配置文件中的flush策略来指定
redis的日志重写:
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写,遍历新进程的内存中数据,每条记录有一条的Set语句。
重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件.
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发.
AOF的优点:
可以通过设置同步间隔保证数据完整性
AOF的缺点:
相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
RBD VS AOF:
RDB持久化方式能够在指定的时间间隔能对数据进行快照存储.
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据.
同时开启两种持久化方式:
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
但是RDB更适合用于备份数据库(AOF在不断变化不好备份)。文章源自JAVA秀-https://www.javaxiu.com/1219.html
主从复制
Redis的主从复制主要是通过master server持久化的rdb文件实现的。
master先dump出内存快照文件,然后将rdb文件传给slave,slave根据rdb文件重建内存表。
具体复制过程如下:
1. 如果设置了一个slave,无论是第一次连接还是重连到master,它都会发出一个SYNC命令;
2. 当master收到SYNC命令之后,会做两件事:
a) master执行BGSAVE,即在后台保存数据到磁盘(rdb快照文件);
b) master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);
3. master在后台把数据保存到快照文件之后会把这个文件传送给slave,而slave则把内存清空后,加载该文件到内存中;
4. master也会把收集到缓冲区中的命令,通过Reids命令协议形式转发给slave,slave执行这些命令,实现和master的同步;
5. master/slave此后会不断通过异步方式进行命令的同步,达到最终数据的同步一致;
6. 需要注意的是master和slave之间一旦发生重连都会引发全量同步操作。但在2.8之后版本,也可能是部分同步操作。
部分复制
2.8开始,当master和slave之间的连接断开之后,他们之间可以采用持续复制处理方式代替采用全量同步。
master端为复制流维护一个内存缓冲区用来记录最近发送的复制流命令;
master和slave之间都维护一个复制偏移量和当前master服务器ID。
当网络断开,slave尝试重连时:
a. 如果masterID相同(即仍是断网前的master服务器),并且从断开时到当前时刻的历史命令依然在master的内存缓冲区中存在,
则master会将缺失的这段时间的所有命令发送给slave执行,然后复制工作就可以继续执行了;
b. 否则,依然需要全量复制操作。
配置:
从机器的redis.conf添加slaveof 主IP 端口 =========> slaveof masterHost masterPort
细节:
1)redis的复制功能是支持多个数据库之间的数据同步。
2)一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
3)主从集群有放射状、流线状两种形式。
4)Redis使用异步复制,复制时不会阻塞主服务进程,也就是说同步的同时服务器仍然可以处理请求。
5)Redis不支持主主复制,任意两台机器间不能互相slaveof文章源自JAVA秀-https://www.javaxiu.com/1219.html
Redis集群
redis是单线程,但是一般的作为缓存使用的话,redis足够了,因为它的读写速度太快了。
但是在实际的生产过程中,单服的redis依旧有着低容错,低扩展的问题,这种情况下就需要redis集群。
相比单服的redis,集群有以下些好处:
1.容错性
在一个或多个节点出现宕机的情况下,集群内部通过投票的机制能够快速的进行选举和不停机的情况下进行服务持续提供。
2.扩展性
相比单服在升级性能过程中,集群也能够很好的实现缓存的性能升级
3.性能提升
性能的提升其实在扩展过程中,就能够随之的体现出来。
搭建步骤
1)首先创建单节点redis
2)将单节点复制6份,主要复制的是conf文件,配置redis的配置文件
port 7000 //端口
bind 本机ip //根据本机所在的IP或hostname去配制
daemonize yes //redis后台运行
pidfile /var/run/redis_7000.pid //pidfile文件
cluster-enabled yes //开启集群
cluster-config-file nodes_7000.conf //集群的配置
cluster-node-timeout 15000 //请求超时 默认15秒,可自行设置
3)依次启动每个节点
4)安装ruby库,为集群创建脚本提供环境
yum install ruby rubygems
gem install redis-4.0.0.rc1.gem
5)使提供的脚本创建redis集群
./redis-trib.rb create --replicas 1
192.168.145.121:7001 192.168.145.121:7002
192.168.145.121:7003 192.168.145.121:7004
192.168.145.121:7005 192.168.145.121:7006
6)测试
redis-cli -h ip -p port -c文章源自JAVA秀-https://www.javaxiu.com/1219.html
缓存系统常见问题
(1)缓存穿透
问题描述:
查询一个一定不存在的数据,因为这个数据不存在,所以永远不会被缓存,所以每次请求都会去请求数据库
解决方案:
(1) 采用布隆过滤器:使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤
(2) 缓存错误值:访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间
(2)缓存击穿
问题描述:
一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增
解决方案:
(1) 采用互斥锁:缓存未命中,需要去更新时,先让程序去获取锁,只有获取到锁的线程才有资格去更新缓存KEY。
其他没有获取到锁的线程则休眠片刻之后再次去获取最新的缓存数据
(2) 用不过期:缓存系统不设置过期时间,通过定时任务去同步缓存和数据库的数据
(3)缓存雪崩:
问题描述:
设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到数据库,最终导致数据库瞬时压力过大而崩溃。
解决方案:
在原有失效时间的基础上增加一个随机时间,这样每个缓存过期时间的重复率就会降低,从而减少缓存雪崩的发生。文章源自JAVA秀-https://www.javaxiu.com/1219.html
https://www.aliyun.com/ss/UmVkaXPlrabkuaDnrJTorrAoMSk/a https://blog.csdn.net/qq_34212276/article/category/7260536文章源自JAVA秀-https://www.javaxiu.com/1219.html

评论