文章目录
NoSQL
非关系型数据库用来干嘛?
负载均衡、做缓存–解决CPU内存压力、解决sesssion共享问题
不同服务器如何共享session?
- 存到客户端,但有安全问题(cookie)
- 复制多份,占用内存
- 存到缓存数据库,访问速度快
解决IO压力?
对频繁的数据访问放到缓存数据库
NoSQL特点
数据存在内存;不遵循sql标准;不支持ACID; 性能远超sql;
数据库如何提高访问效率
行式数据库、列式数据库
Redis
Redis是基于键值对的非关系型数据库,提供多种数据类型,支持数据持久化。
⚡️适合做缓存(例如热点数据,即访问量大的数据),计数器(incr/decr),分布式锁(setnx),消息队列(list双向链表),统一存放不同服务器的会话信息(session共享)
底层原理
redis:单线程(原子性)+多路IO复用
memcached :多线程 + 锁
准备环境
虚拟机:安装Vmware
理解网络适配器配置:三种网络连接模式
- 桥接模式:每个Linux系统占用一个IP,容易造成IP冲突,但是能和外部系统通讯(IP占用问题)
- NAT模式:网络地址转换模式,linux系统会配置新的ip地址,不占用外部ip(避免冲突),通过虚拟网卡( VMnet8)来访问主机,从而实现与外界通讯,但无法从外界其它机子访问linux系统(除了主机)。
- 主机模式:独立系统
linux:安装centos7
-
硬盘分区 分3区
/boot引导文件,默认200M即可; /swap交换区,设跟内存一致; / 根分区,剩余全部 -
防火墙操作命令
centos7 systemctl start/stop/status firewalld 开启,关闭,状态
centos6 service iptables start/stop/status
教程:b站搜linux即可
reids安装
先使用 gcc --version
查看是否有gcc环境 安装命令:yum install gcc
官网下载redis压缩包,使用 tar -zvxf 压缩包
解压,cd
进入解压目录,make
编译,make install
安装。
redis启动
redis安装目录:默认/usr/local/bin,包括有redis-server(redis服务)、redis-cli(redis客户端)、redis-check-aof(用于异常恢复)、redis-sentinel(哨兵模式)等
redis解压目录:有redis配置文件,即redis.conf
前台启动:/user/local/bin/redis-server
后台启动:启动时,加上配置文件(需配置) /user/local/bin/redis-server /路径/redis.conf
💡前台启动需要另开一个终端才能使用redis客户端,后台启动需要配置。
建议将配置文件拷贝到其它路径,原先的保留不动,使用的时候也使用拷贝的那个文件。
配置文件redis.conf
vi /路径/redis.conf
编辑配置文件,查找命令:/查找文本
protected-mode no 禁止保护模式
daemonize yes 开启可后台启动
#bind 127.0. 注释掉,允许远程访问
默认端口:6379
快速上手
String操作
# 添加set k v 获取get k setnx相同k则不覆盖 del k删除
127.0.0.1:6379> set name zone
OK
127.0.0.1:6379> set age 7
OK
127.0.0.1:6379> get name
"zone"
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
数值自增自减
# incr|decr k 数值自增|自减step incrby|decrby k step
127.0.0.1:6379> incr age
(integer) 8
127.0.0.1:6379> get age
"8"
💡value得为数值
设置过期时间
# expire k time设置键的过期时间 ttl k查看剩余时间(-1代表永不过期,-2代表已过期)
127.0.0.1:6379> expire age 3
(integer) 1
127.0.0.1:6379> ttl age
(integer) 0
127.0.0.1:6379> ttl age
(integer) -2
💡单位是秒
库操作
redis有16个库(0-15),用哪个都行
# keys *查看所有键 flushdb清空当前库 flushall清空所有库 select切换库(默认0不显示)
127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]> keys *
(empty array)
💡dbsize键数量 type k键类型
数据结构
Redis主要提供了5种数据结构:String字符串、List双向链表、Set无序不重复集合、Zset有序不重复集合、Hash键值对集合。 基本操作如下
💡如果是删除整个键,都是用del k
String字符串
# 添加set k v 获取get k setnx相同k则不覆盖 添加多个键值mset k1 v1 k2 v2
List双向链表
# lpush左插、rpush右插 lpop左删、rpop右删
# lrange 0 -1 查看链表(-1代表倒数第一) lindex根据下标获取元素
127.0.0.1:6379> rpush hobby study
(integer) 1
127.0.0.1:6379> rpush hobby film
(integer) 2
127.0.0.1:6379> lrange hobby 0 -1
1) "study"
2) "film"
127.0.0.1:6379> lpop hobby
"study"
127.0.0.1:6379> lindex hobby 0
"film"
Set无序不重复集合
# 添加sadd 查看集合smembers 是否存在某个元素sismember 删除某个元素srem
127.0.0.1:6379> sadd fruit apple
(integer) 1
127.0.0.1:6379> sadd fruit orange
(integer) 1
127.0.0.1:6379> smembers fruit
1) "orange"
2) "apple"
127.0.0.1:6379> srem fruit apple
(integer) 1
127.0.0.1:6379> sismember fruit apple
(integer) 0
Zset有序不重复集
# 添加zadd(根据分数排序) 查看范围zrange 根据分数查看zrangebyscore 删除zrem
127.0.0.1:6379> zadd fruit 10 apple
(integer) 1
127.0.0.1:6379> zadd fruit 15 orange
(integer) 1
127.0.0.1:6379> zadd fruit 1 peach
(integer) 1
127.0.0.1:6379> zrange fruit 0 -1
1) "peach"
2) "apple"
3) "orange"
127.0.0.1:6379> zrangebyscore fruit 10 20
1) "apple"
2) "orange"
127.0.0.1:6379> zrem fruit apple
(integer) 1
Hash键值对集合
# 添加hset 获取单个hget 获取全部hgetall 删除hdel
127.0.0.1:6379> hset jihe page 10
(integer) 1
127.0.0.1:6379> hset jihe limit 10
(integer) 1
127.0.0.1:6379> hget jihe page
"10"
127.0.0.1:6379> hdel jihe page
(integer) 1
127.0.0.1:6379> hgetall jihe
1) "limit"
2) "10"
💡除了String最大是2M,其它数据类型,最多能存放约40亿个元素,也就是2的32次方-1
消息发布和订阅
订阅名字为myChannel的频道:subscribe myChannel
myChannel频道发布message:publish myChannel message
然后订阅的客户端就能收到消息。
💡需要打开两个客户端
Jedis实现商品秒杀(基于事务+锁+lua脚本+并发工具测试)
事务
redis事务是一个单独的隔离操作,事务中的所有命令会被序列化并按顺序执行,执行过程中不受其它客户端的请求影响。
使用:
multi开启事务,之后的命令会被加入队列–组队过程,直到使用执行或取消命令
exec执行 discard取消
特点:组队过程中一旦有错误,exec执行时全部失败;exec执行时才有的错误则不影响其它正确的操作。当然,如果参与了乐观锁,只要数据遭到修改,则exec执行时都失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set a string
QUEUED
127.0.0.1:6379(TX)> get a
QUEUED
127.0.0.1:6379(TX)> incr a
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) "string"
3) (error) ERR value is not an integer or out of range
锁机制
悲观锁:操作共享数据时会上锁,这样保证其它事务无法同时操作共享数据
乐观锁:操作共享数据时,通过查询版本号判断数据是否发生改变。(例如数据对应的版本号为1,操作时检查是否为1,为1则操作并修改版本号为2,不为1说明数据被修改过,当前无法进行操作)
redis中通过watch命令监控共享数据,一旦数据发生变化,就无法对其操作,unwatch取消监控
根本问题:由于读取的共享数据不是最新的。
并发模拟工具
安装:yum install httpd-tools
ab命令模拟测试请求
命令: ab -n 1000 -c 200 http://192.168.101.1:8080/skill (-n 请求次数 -c 并发次数)
注意:关闭linux中的防火墙,其中192.168.101.1为windos电脑ip
持久化
RDB(Redis Database)
是Redis默认采用的持久化方式,以快照的形式将数据持久化到硬盘中。
触发方式
-
手动:save或
bgsave
命令 -
自动触发:配置redis.conf,让服务器在一定条件下自动执行bgsave命令
rdbcompression yes rdb文件是否压缩 save 10 3 表示10秒内如果有大于等于3个key发生变化,就执行bgsave命令 dbfilename dump.rdb rdb文件名 dir ./ 持久化文件保存路径,包括aof也是如此 stop-write-on-bgsave-error yes 磁盘无法写入时则关闭redis(例如磁盘满了)
注:关闭服务(shutdown),清空数据库时也会自动触发。
RDB原理:
- save命令会让redis服务器阻塞,直到持久化过程结束。
- bgsave命令则采用异步的方式生成快照,Redis首先会通过fork获得一个跟父进程一样的子进程,fork过程父进程会被阻塞,fork之后父进程可以继续响应其它命令,而子进程会将数据保存到临时文件,然后再用临时文件去替换dump.rdb
💡bgsave底层:COW(copy on write)写时复制,通过复制一个副本并对副本修改,而不是直接修改原先的
优缺点:
rdb文件体积小,使得恢复数据速度快;但由于要创建子进程,很消耗内存,没法做到实时的持久化。
AOF(Append Only File)
以日志的方式(追加)记录每次的写操作(有修改就记录)。Reids重启时,通过重新执行这些命令来恢复数据。能保证数据持久化的实时性。
使用
配置redis.conf
appendonly yes //开启,redis开启时优先读取的是aof文件,而不是rdb
appendfilename "appendonly.aof" //aof文件名
异常恢复
文件损坏时,通过 redis-check-aof --fix appendonly.aof
命令恢复(原理就是把报异常的命令包括后面的命令都去除)
同步频率
# 配置redis.conf
appendfsync always 始终同步,有修改操作就记入日志
appendfsync everysec 每秒同步(默认)
appendfsync no 把同步时机交给操作系统
重写策略(Rewire压缩)
问题:aof采用文件追加的方式,会使文件越来越大,所以新增了重写机制,当aof文件的大小是上一次aof文件的2倍,且aof文件大于64mb时,进行压缩。
默认配置(自动触发):
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
手动触发命令:bgrewriteaof
原理:使用rdb快照,以二进制的形式附在aof文件的头部
no-appendfsync-on-rewrite
持久化流程
写命令会被追加到AOF缓冲区;缓冲区根据AOF持久化策略将命令同步到磁盘AOF文件;
文件超过一定大小时,会进行文件压缩; Redis重启时,通过加载AOF文件恢复数据。
Redis高可用
主从复制
1.介绍:一台主机负责写操作,多台从机负责读操作,数据由主机同步到从机。
2.优缺:读写分离,容灾快速恢复(从机挂了有其它从机);
数据同步时有延迟。
3.使用:
需要开启多个窗口,可用不同端口来模拟多个redis服务
为不同的redis服务创建和配置不同文件:例如配置端口为6379的服务,创建redis6379.conf文件
include redis.conf #引入redis配置文件,从而减少配置
# 配置进程,用于模拟多个redis服务,该配置会在对应路径下创建该文件,命名随意
pidfile /var/run/redis-6329.pid
# 持久化文件名
dbfilename 6379.rdb
# 指定端口号
port 6379
后台启动和进入客户端
redis-server 路径/redis6379.conf
redis-cli -p 6379 #指定端口号
查看服务信息(主机master、从机slave)
info replication #默认都是主机
设置从机(将主机变为从机),需要指明主机服务器
slaveof ip port #主机服务器的ip和端口号
从机变主机(参见反客为主)
slave no one
注:主从复制中,如果主机挂了,可通过集群解决。
一主二从
复制原理:从机启动成功连接到主机后会发送一个同步命令;主机收到命令后进行持久化操作,然后将持久化的文件传送到从机。从机接收文件并读取(首次全量复制);发送期间,主机会在缓冲区中保存新执行的写命令,文件发送完就向从机发送这些写命令;之后主机一旦有写命令,就向从机发送。
薪火相传
当主机有很多从机时,可将部分从机(B)交由其它从机(A)管理,从而减轻主机的压力。当然,其它从机(A)仍然无法进行写操作,只是能代替主机同步数据到部分从机(B),即薪火相传。
反客为主
当主机挂了,一种措施就是把某台从机变为主机,从而维持系统正常。
哨兵模式(自动的反客为主):主机挂时,从机变主机这种操作肯定需要能自动触发,哨兵模式就是利用哨兵(也是服务器)监控后台主机是否故障,如果故障,就选择从机替代主机
实现:
-
配置哨兵配置文件sentinel.conf,名字随意。
# num代表得有num个哨兵监控到才能执行反客为主,至少一个 sentinel monitor masterName ip port num #主机名masterName(随意)、主机ip、主机端口号
-
启动
#redis-sentinel redis-sentinel 路径/sentinel.conf #加上配置文件
从机选择原则:优先级(redis.conf中的replica-priority num,num越小越优先)、偏移量(谁的数据全)、runid最小的(每个服务有一个随机的runid)
⚡️后面如果主机恢复,重启后会变为从机,因为sentinel会向其发送slaveof命令
集群
作用
当redis存储容量不够时,可通过集群扩容;当对服务器进行并发写操作时,可通过集群分担压力。
特点
无中心化的集群配置:任何一台机都可以作为入口访问,如果当前服务器不是处理该命令的,则切换到处理该命令的服务器。
原理
集群实现了对Redis的水平扩容,即启动N个Redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N,通过分区,使得集群中若有部分节点失效,集群也能继续处理命令请求。
不足:多键操作需要添加组才可以操作;不支持事务;
搭建redis集群
同样需要开启多个窗口,需要6个,分3组,一主一从
创建和配置6个redis.conf,可以沿用主从复制中的配置,以redis6379.conf为例
include redis.conf
pidfile var/run/redis-6329.pid
dbfilename 6379.rdb
port 6379
cluster-enabled yes #开启集群
cluster-config-file node-6379.conf #节点配置文件名,
cluster-node-timeout 15000 #设置节点失联时间,超过该事件,集群自动进行主从切换
将6节点合成为集群
redis-cli --cluster create --cluster-replicas 1 ip:port ip:port…
1代表采用最简单的方式,一主一从
客户端进入
redis-cli cluster -c -p port #任意端口进入 -c采用集群策略连接,操作数据时会自动进行服务器切换
服务器切换解释
slots–插槽,用于集群中保存键,有16384个,每个键都会对应一个插槽(哈希),可用数组去理解。每个服务器对应一部分插槽,每次添加的键通过计算落在那一部分插槽就切换到哪个服务。
插槽操作
cluster countkeysinslot 3564 #根据插槽3564获取键的数量
cluster getkeysinslot 3563 num #根据插槽3563获取对应的键,num代表获取几个
多键操作,集群不能直接使用mset,键后面需要携带统一的标识,这样才能使用同一插槽
mset k1{k} v1 k2{k} v2 # k为标识,也就是插槽与键要一对一
当插槽对应的主从都挂掉,redis集群是否都会挂掉(由配置决定)
cluster-require-full-coverage no 不会挂掉,yes则会
应用问题(理解为主)
缓存穿透
问题:向数据库请求不存在的数据,由于不存在,缓存也不会有,导致请求不通过缓存直接访问数据库。(漏洞攻击)
解决:
- 空值缓存:查询返回为空时也进行缓存
- 设置白名单:那些可访问,那些不行。
- 布隆过滤器:集合判断访问数据是否存在,不存在则拦截,同白名单
- 实时监控
缓存击穿
问题:缓存中热点数据过期了,此时有大量并发请求该数据,导致数据库被瞬间压垮。(热点数据)
解决:
- 预先设置热门数据,延长缓存时间
- 实时调整过期时长
- 使用锁:排队请求,例如使用setnx设置锁(存在则返回失败),如果返回成功,放行去查数据库并缓存;如果返回失败,则让但前线程sleep一会再重试。
- 记录key是否过期,过期的话则触发其它线程去更新缓存
缓存雪崩
问题:缓存中大量的热点数据集中过期,也有可能是节点故障,此时有大量并发请求该数据,导致请求无法处理。
解决:
- 分散缓存失效:对每个key的缓存失效时间增加1-5分钟的随机值,这样就很难引发大量key集中过期
- 可以使用哨兵模式和集群模式,部署多个redis实例。
- 使用锁和队列:同上
- 构建多级缓存:nginx缓存+redis缓存+等
- 设置标志更新缓存:记录key是否过期,过期的话则触发其它线程去更新缓存
分布式锁
问题:分布式集群系统,不同服务器存在数据共享问题(java无法解决,因为多个服务器可能是多个不同的JRE环境,而java自带的锁局限于当前JRE)。
实现互斥锁
setnx lock value #上锁
expire lock 10 #过期时间
del lock #解锁
原子性问题:上锁和设置过期时间时,可能出现上锁后异常导致锁永不过期,出现死锁现象。
set lock value nx ex 10 #上锁并设置过期时间;nx代表不可重复,等价setnx;ex代表过期时间
释放其它锁问题:假如某服务器未执行完某个任务,锁就过期了,此时该服务器继续执行完之后仍会释放锁,但此时可能释放了另一个台服务器的锁。
解决:value用唯一值代替,这样就能保证每个锁的value不一样,
这样,释放锁时判断value是否发生变化,就能知道锁是否被释放。
释放锁需要原子操作: 由于uuid的比较和释放锁的操作是两步,不是原子操作,这就可能出现比较完后,锁刚好先过期自动释放,此时服务器再次释放锁。
lua脚本解决
要求:
- 互斥性,也就是只能有一个客户端持有锁
- 不能出现死锁
- 不能释放别人的锁
- 加锁和解锁需要原子操作
实现分布式锁(java_redis项目)
数据淘汰策略
配置:maxmemory-policy 策略
当Redis内存达到最大值maxmemory
,会对数据进行淘汰,有6种策略,大致可分三种。
- 默认策略noeviction,不淘汰数据;
- 针对设置了过期时间的数据,有volatile-lru,volatile-ttl,volatile-random,lru代表淘汰最近最少使用的数据,ttl代表淘汰将要过期的数据,random代表随机选择数据淘汰;
- 针对所有数据,有allkeys-lru,allkeys-random,跟上面的一样,只是数据范围不同而已。
💡已设置过期时间的数据:server.db[i].expires 所有数据:server.db[i].dict
💡最近最少使用(LRU):不常用的先淘汰===>有算法题对应
⚡️noeviction:达到最大内存时,再执行命令就报错
新增功能
ACL权限控制
查看所有命令acl help
查看用户列表acl list
user default on nopass ~* &* +@all
默认用户名 开启 无密码 可操作所有key 可执行所有命令
查看权限类别acl cat
;查看具体类别对应的命令,例如string,acl cat string
新建或修改用户acl setuser uname >pwd
…基本参数具体如下
uname 用户名
>pwd 设置密码为pwd
on/off 是否开启该用户
~* 指定可操作的key,例如~a*,表示只能操作包含a的key
+@all 指定可执行的命令,例如+@string表示可操作string的所有命令,-@string则是移除
切换用户auth uname pwd
, 对于默认用户的命令为auth default nopass
多线程IO
io-threads-do-redis yes #开启
io-threads 4