一个网站的访问量不大,用单个数据库可以轻松实现。一般架构为:
APP-> DAL ->MySQL
瓶颈
- 数据量逐渐增多,单机不够存放
- 数据的索引,内存不够时,存放不下
- 访问量(读写等操作)一个实例(一个数据库)不能承受
随着访问量的增加,大部分单机MySQL的网站在数据库上都出现了性能问题。开始使用缓存技术来缓解数据库的压力,优化数据库的结构和索引,此时开始使用Memcached缓存。此时的技术架构为:
APP -> DAL ->Cache-> 多个MySQL数据库(垂直拆分->表示将一个数据库拆分为多个数据库来实现)
由于数据库的写入压力增加,缓存只能缓解数据库的读取压力,读写集中在一个数据库上让数据库的不堪重负,开始使用主从复制技术来达到读写分离。其架构为:
APP->DAL->Cache ->读数据库则去从数据库(Master)获取数据
---------------------------->写数据库则去主数据库(Slave)获取数据,从数据库根据规则复制主数据内容到从数据库
在Memcached的高速缓存,MySQL的主从复制,读写分离的基础上,MySQL的写压力再次出现瓶颈,同时数据量的增多,MyISAM引擎使用表锁,使在高并发的情况下锁机制问题出现,此时使用InNoDB引擎。
同时开始使用分表分库来缓解写压力和数据增长的问题,技术架构为:
Not Only SQL 泛指非关系型数据库
- 易扩展:数据之间无关系
- 大数据量高性能:缓存
- 多样灵活的数据模型
- 键值对存储
数据特性:海量,多样,实时
需求特性:高并发,高可扩展,高性能
redis
MongoDB
HBase
特性ACID
- A:原子性->事务内所有操作要么全部成功,要么全部不成功,否则回滚
- C:一致性->数据库不会因事务而改变其一致性
- I :独立性->各事务之间是独立的,不会相互影响
- D:持久性->事务提交后,就会持久化到数据库
- C: 强一致性
- A: 高可用性
- P: 分区容错性
是一个分布式系统不可能同时满足一致性,可用性和分区容错性,最多只能同时较好的满足两个。因此将NoSQL数据库分成如下三类
- CA:单点集群,满足一致性、可用性的系统,对扩展性支持较弱。传统的关系型数据库
- CP:Redis,HBase、MongoDB等
- AP:大多数网站架构的选择
NoSQL中分区容忍性§是必须要实现的
是由如下三个词组成
基本可用(Basically Available)
软状态(Soft state)
最终一致(Eventually consistent)
是为了解决关系型数据库强一致性引起的可用性降低而提出的解决方案,主要思想是 让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能的提升。
分布式系统:由多台计算机和通信的软件组件通过网络连接组成 。
分布式:不同的多台服务器上面部署不同的服务模块(工程),互相之间通过RPC等方式通信和调用,对外提供服务和组内协作
集群:不同的多台服务器上面部署相同的服务模块(工程),通过分布式调度软件进行统一的调度,对外提供服务和访问。
远程字典服务器REmote DIctionary Server,是一个高性能的key/value分布式内存数据库,基于内存运行。
- 支持数据持久化
- 支持的数据类型不仅是string,还有list、set、zset、hash等
- 支持数据备份,即主从模式的数据备份
- 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
- 按要求取值
- 发布订阅消息
- 定时器、计数器
- 根据需要下载32位或64位安装包
- 安装到指定位置,可以设置环境变量
- 若未设置环境变量,打开命令行窗口,切换到安装目录
- 运行(执行命令) redis-server.exe redis.conf,则启动了redis的服务端
- 注意 redis.conf可省略,或指定配置文件
- 再打开另一个命令行窗口,此时成为客户端,同3
- 运行(执行命令) redis-cli.exe -h 127.0.0.1 -p 6379
- 注意 -h(指定服务器ip)和-p(指定服务器端口)可省略
- 下载获取redis.tar.gz并将其放在Linux目录/opt下
- 在/opt目录下执行解压命令 tar -zxvf redis.tar.gz
- 解压完后出现redis的文件夹,cd进入该文件夹
- 在该目录下执行 make命令
- 注意 可能会出现gcc命令未找到等,需安装gcc,若已联网,则运行yum install gcc-c++
- 完成后继续执行 make install
- 查看默认安装目录 usr/local/bin
- 启动服务端
redis是单进程的
- 默认有16个数据库,从0到15,可以在配置文件设置
- 切换数据库:select 库索引(0到15)
- 查看当前库的key的数量:dbsize
- 查看当前库的所有key:keys *
- 清空库:flushdb,flushall ,分别表示清空当前库,清空所有库
- 统一密码管理:16个库的密码一样
- redis的索引都是从0开始的
8.默认端口为6379
常用命令:
- keys * 查询当前库下的所有键
- exists key 判断指定key是否存在
- move key db 将指定key从指定库中移除
- expire key t 为指定的key设置过期时间t秒后
- ttl key 查看指定key还有多少时间过期,-1表示永不过期,-2表示已过期
- type key 查看指定key的类型
- del key 删除指定key
一个redis中字符串value最多可放512M内容
常用命令
- set/get/del/append/strlen key [n] 分别表示对指定key 设置/获取/删除/追加/获取长度,[n]表示有些命令需要的值,有些命令不需要
- incr/decr key,对指定key的值进行自增/自减操作,一定是数字才能进行加减
- incrby/decrby key num 对指定的key的值 加/减 num(数字) key的值为数字才可操作
- getrange key a b 获取指定key的指定区间范围内的值,类似between…and
当a=0,b=-1时,表示获取全部- setrange key a value 设置指定key的指定起始位置的值value,根据value长度覆盖原有值的a指定位置开始后的
- setex key num value 设置指定key的过期时间为num秒,其值为value
- setnx key value 设置如果不存在指定key,则设置为value,若存在,不设置
- mset/mget/msetnx key 批量设置/获取指定key的值,对于msetnx若一个已存在,则返回0表示执行失败
- getset key value 先get指定key的值再设置其值为value,返回获取到的值
其底层是一个链表实现,单键值对应多value
- lpush/rpush key v1 v2 … vn 从左侧/右侧依次插入指定值,通过lrange key 0 -1,获取结果分别为vn … v2 v1 ; v1 v2 … vn,相当于java中的压栈操作
- lrange key a b 获取指定key指定范围a到b的值,a=0,b=-1则表示获取全部
- lpop/rpop key 从左侧/右侧获取指定key的一个元素并删除,相当于java中的出栈操作
- lindex key index 按照索引下标获取指定key的元素值
- llen key 获取指定key的长度
- lrem key num v1 删除指定key的num个v1值,若列表中的v1个数小于num,则有多少删多少,若 大于num,则删除num个
- ltrim key a b 截取指定key的a到b的元素赋值给key
- rpoplpush 源list 目标list 将源list中的元素右侧出栈并左侧压入目标list,注意是栈顶元素(一个元素)
- lset key index value 将指定key的指定索引index的值设置为value
10.linsert key before/after v1 v2 在指定key的指定值v1前/后插入元素v2
性能总结
- 是一个字符串链表,左右都可以插入元素
- 头尾操作效率较高
是一个键值对集合,string类型的field和value的映射集,适合存放对象,类似java中的Map
常用命令
- hset/hget key field [value] 设置/获取指定key的键值对,value 是设置值时需要
- hmset/hmget key f1 [v1] f2 [v2] … 批量设置/获取指定key的键值对,[]表示设置值时使用
- hgetall key 获取指定key的所有键值对
- hdel key field 删除指定key的指定键field
- hlen key 获取指定key的长度
- hexists key field 判断指定key的指定键field是否存在
- hkeys/hvals key 获取指定key的所有键/值
- hincrby/bincrbyfloat key field num 将指定key的指定键field的值增加num(整数)/num(小数)
- hsetnx key field value 判断指定key的指定键是否存在,若存在,不操作返回0,若不存在,设置其值为value,返回1
是一个无序的无重复的集合,底层通过hashTable来实现
常用命令
- sadd/smembers/sismember key value 设置/获取key的值,指定key中是否存在元素value
- scard key 获取指定key中元素个数
- srem key value 删除指定key的指定元素value
- srandmember key num 在指定key中随机获取num个元素
- spop key 指定key随机出栈
- smove key1 key2 k1中的v1 将指定key中的元素v1移除并赋值给key2
- 数学集合操作
1. 差集:sdiff key1 key2 k3 …kn 获取key1和(key2 … kn) 的差集,即在key1中的元素,不在后面几个(key2 … kn)中的元素
2. 交集:sinter key1 key2 k3 …kn
3. 并集:sunion key1 key2 k3 …kn
与set类似,不同的是每个元素都会关联一个double类型的分数进行排序(从小到大)
常用命令
- zadd key score1 value1 s2 v2 … sn vn 设置指定key的分数(排序)和值
- zrange key a b 获取指定key 的指定范围 a到b 的值 (a,b为索引)
- zrangebyscore key scoreA scoreB
1. 获取指定key 的指定分数范围 scoreA 到scoreB 的值(包含scoreA和scoreB),
2. 若不包含需要在分数前加(,(如 zrangebyscore key (scoreA scoreB 表示不包含A
3. 限定从指定位置开始获取,命令为 zrangebyscore key A B limit 2 表示从索引2开始获取分数在大于等于A小于等于B的值
4. zrangebyscore key (A B limit 2 2 表示从索引2开始获取分数在大于A且小于等于B之间的两个值- zrem key value 删除指定元素
- zcard key 获取指定key对应的值的个数
zcount key scoreA scoreB 获取指定key的分数区间内值的个数
zrank key value 获取指定key的指定值value的下标值
zscore key value 获取指定key的指定值对应的分数score- zrevrank key value 获取指定key的指定值value的反向下标值
- zrevrange 反转
- zrevrangebyscore key scoreB scoreA
一般在启动redis服务的时候,会加载指定的该配置文件
- 默认为安装目录下的该文件,默认文件一般不会去修改
- 一般会拷贝一份到指定目录,然后启动服务时加载拷贝的这一份配置文件,
- 然后根据需要修改该文件
redis的安装目录下
对于配置文件中的单位(如kb,Mb,GB)只支持bytes,不支持bit,且大小写不敏感
若配置文件内容过多,可分为多个配置文件,通过include将其他文件包含进来,类似于Struts2的配置文件
比较重要的
- daemonize --》默认不是以守护进程的方式运行(即默认为no),使用 yes 启用守护进程
- pidfile /var/run/redis.pid --》当 Redis 以守护进程方式运行时,Redis 默认会把 pid 写入 /var/run/redis.pid 文件,可以通过 pidfile 指定
- port 6379 --》设置端口,默认为6379
- bind 127.0.0.1 --》绑定的主机地址,
- timeout 300 --》客户端闲置多长时间后关闭连接,如果指定为 0,表示关闭该功能
- loglevel notice --》指定日志记录级别,Redis 支持四个级别:debug、verbose、notice、warning,默认为 notice
- logfile stdout --》日志文件,默认为标准输出(输出到控制台)
- database 16 默认16个数据库
- dir ./ 指定本地数据库存放目录
等等
save 表示time秒内有至少有1个key,有num次更新操作
- 一般有3条配置(复合触发条件),满足一条即生成一个快照,即dump.rdb文件
- 若想禁用RDB持久化策略,不设置或者save “” 即可
- 或者手动生成快照(即某条指令执行后需立即生成快照),执行save即可
客户端连接redis后,
- 通过config get requirepass可以获取默认密码为空,
- 可通过config set requirepass “123” 设置其密码为123
- 此时要执行命令需先执行auth 123通过后才能执行
当redis内存将达到最大值时,需要清理一些内存空间,其策略方式有如下几种
- volatile-lru: 对设置了过期时间的键,使用lru算法移除key
- allkeys-lru: 对所有键,使用lru算法移除key
- volatile-random: 对设置了过期时间的键,在过期集合中移除随机的key
- allkeys-random: 对所有键,随机移除key
- volatile-ttl: 移除ttl值较小的key,即即将过期的key
- noevication: 不进行移除,即永不过期, 默认为该值,生产环境中不可能使用该策略
- 配置文件中默认为关闭,appendonly no
- 默认的文件为 appendfilename “appendonly.aof”
- 当修改启动为aof后,启动服务安装目录下即生成一个appernonly.aof文件
- 当aof文件有错时,服务停止
- 当aof文件有错时,可以执行安装目录下的redis-check-aof命令,格式为redis-check-aof --fix 指定aof文件
- 配置策略 配置文件中为 appendfsync everysec
1. 可选值为always:同步持久化,每次发生数据变更立即记录到磁盘,性能较差,数据完整性好
2. everysec:出厂默认配置,异步操作,每秒记录,若出现down机,会丢失1秒的数据
- 在指定的时间间隔内将内存中的数据集快照文件写入磁盘,恢复时是将快照文件直接读取到内存
- Redis会单独创建(fork[^1])一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件
- 这个过程中主进程不会进行任何IO 操作,确保了极大的性能
- 快照文件为dump.rdb
[^1] 关于fork:复制一个与当前进程一样的进程作为原进程的子进程
- 默认为dump.rdb,即在快照配置中的dbfilename
- 当redis执行flushall等操作时,会立即更新快照
- 配置文件中默认的快照配置,三条save 配置
- 通过命令save或bgsave
- 执行命令flushall
- 将备份文件(dump.rdb)拷贝到redis的安装目录下并启动redis服务即可
- 若dump文件损坏,可通过备份文件,或通过redis-check-dump来修复
- 适合进行大规模数据恢复,
- 对数据完整性和一致性要求不高
以上情况下,RDB效率更高
- 在一定时间间隔内做一次备份,当redis意外down掉后,会丢失最后一次快照后的修改。
- fork的时候,内存中的数据被拷贝了一份,2倍的膨胀性会影响
以日志的形式记录每个写操作,只许追加文件,不许改写文件
redis启动之初会读取该文件并构建数据,即完成数据恢复
该文件即为appendonly.aof
见 aof配置
- RDB 和AOF可以共存,协作的方式工作
- 当rdb和aof共存时,优先寻找aof
- 启动redis服务,配置文件设置appendonly yes
- 将备份的appendonly.aof文件复制到dir指定的目录下(默认为安装目录),然后重启redis服务
- 备份文件
- redis-check-aof修复命令
AOF文件内容会越写越多,当AOF文件的大小超过设定的阈值,redis会自动对aof文件的内容进行压缩,只保留可以恢复数据的最小指令集,也可通过手动来重写,手动命令为bgrewriteaof
记录上次重写时aof大小,默认配置为当aof文件是上次重写后大小的一倍且文件大小大于64M触发
根据配置策略,不同
- 相同数据集的数据aof文件远大于rdb,恢复速度慢
- aof运行效率慢于rdb,每秒同步策略略好
》redis对事务的支持是部分支持,注意参考全体连坐和冤头债主
可以一次执行多个命令,一个事务中的所有命令都会序列化,即按顺序的串行化执行,不会允许其他命令加塞。
- multi:标记一个事务块的开始
- exec:执行事务块内的命令,一旦执行了该命令,之前加的监控都会被取消掉
- discard:取消事务,放弃执行事务块内的命令
- watch key …:监视一个或多个key,若在事务执行(exec)之前,这些key被改动,则事务被打断
5.unwatch:取消watch对所有key的监视
>multi
>set k1 v1 ## 将该命令加入队列
>set k2 v2
>...
>exec
>multi
>set k1 v1 ## 将该命令加入队列
>set k2 v2
>...
>discard
事务中一条命令出错,其他命令都出错
>multi
>set k1 v1 ## 将该命令加入队列
>set k2 v2
>getset k3 ## 加入队列的过程中就已经报错,错误指令
>...
>set kn vn
>exec ##执行会报错,注意此时上面执行的命令都是失败的,没有成功的
>set k1 aa
>multi
>incr k1 ## 加入队列不报错,k1的值为aa,无法加1
>set k2 v2
>...
>set kn vn
>exec ## 执行时,除incr命令错误,其他执行成功
比较悲观,每次去拿数据的时候都认为别人会修改,所以在拿数据的时候就会上锁,这样别人要拿数据就会block(堵塞),直到它拿到锁
并发性极差,一致性最好
很乐观,每次去拿取数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断在此期间别人有没有去更新这个数据,一般使用版本号(version)等机制来判定
watch 指令类似于乐观锁,事务提交时,如果key的值已被别的客户端改变,整个事务队列都不会执行
通过watch指令在事务执行之前监控多个key,如偶在watch之后有任何key的值发生改变,exec命令执行的事务都将被放弃,同时返回nullmulit-bulk应答以通知调用者事务执行失败
开启:mulit开始
入队:将多条命令加入队列
执行:exec触发执行事务,按队列顺序执行
单独的隔离操作:事务中的所有命令都会序列化,按顺序执行,事务在执行过程中,不会被其他客户端的命令打断
没有隔离级别的概念:队列内的命令在exec执行之前都不会被执行
不保证原子性:redis同一个事务内若有一条指令执行失败,其后的命令仍会执行,不会回滚
进程间的一种消息通信模式,发送者(pub)发送消息,订阅者(sub)接收消息
- 订阅:subscribe c1 c2 订阅频道c1,c2
- 发布:publish c2 message 发布消息message到c2频道
- 批量订阅:psubscribe new* 批量订阅以new开头的频道
主机数据库更新后根据配置和策略,自动同步到备机的数据库中的master/slaver机制,
master以写为主,slave以读为主
- 读写分离
- 容灾恢复
从库配置:在从库上执行slaveof 主库ip 主库端口
注意:1. 命令行配置后,与master断开后,都需要重新连接,可将其配置进redis.conf
- 通过拷贝多个配置文件redis.conf,
- 开启daemonize yes
- 指定端口port
- log文件名字
- dump.rdb文件的名称
通过前面的基础操作,配置好环境,本地测试可通过一台机器多个不同端口配置文件的方式
即一台主机A(6379)两台备机B(6380)、C(6381)
- 分别启动三个不同端口(如6379、6380、6381)的redis server,并各启动一个客户端进行各自server的连接
- 分别在三台客户端执行命令
info replication
查看信息,可以查看其信息中role为master,即此时三台服务都是主机,各自独立- 若需求为6379为master,6380和6381为slave,则需在6380和6381客户端分别执行如下命令,
slaveof 127.0.0.1 6379
(注意其中127.0.0.1为master的ip地址,6379 为master的端口)- 此时需注意,master在slave连接之前插入的数据也可以访问。即连接后全部复制,后续执行则为增量复制
- 此时在master执行命令
info replication
,可以查看其中信息role为master,connected_slaves为2,说明有2台从机; 同时可查看到从机的ip、端口、状态(1)等信息- slave执行命令
info replication
,可查看到其role为slave,可查看到主机的ip、端口、状态(up)等信息- slave不可以执行写命令
- 出故障,主机出故障down掉,从机仍可获取信息,从机通过命令
info replication
,仍可查看到其role为slave,其主机的ip、端口、状态(down)等信息;当主机恢复后,主从机制恢复,即当主机down掉后,从机原地待命,直到主机恢复,然后恢复主从机制- 出故障,某台从机出故障down掉,主机通过命令
info replication
,可以查看其中信息role为master,connected_slaves为1,说明有1台从机,另一台已down掉;当该从机恢复后,主从机制不会恢复,即当从机down掉后,down掉的从机和主机的主从机制失效,从机恢复后,不会恢复主从机制,若想从机恢复后,仍恢复主从机制,需将主从命令()配置到该从机的配置文件redis.conf中
理解:一个A为主机,一个B为从机,将该从机B再作为主机,另一个C为B的从机,依次往下,即去中心化,相对一仆二主中master的压力会逐渐增大,而该方式主机的负担减轻了
在一仆二主的基础上进行如下操作
- 在C上执行命令
slaveof 127.0.0.1 6380
,则B相对C成为了master,相对A仍为slave,但通过info replication
查看B的信息,role仍为slave,但多了connected_slaves:1
在一仆二主的基础上进行如下操作
- 当A master down掉后,此时B和C都处于静默等待状态;
- 在B上执行命令
slaveof no one
(该命令表示使当前数据库停止与其他数据库的同步,转成主数据库(master)),此时B成为了一个单独的master(可通过命令info replication
查看),C仍处于等待状态- 当在C上执行
slaveof 127.0.0.1 6380
命令后,此时B和C组成了master/slave机制- 当此时A恢复后,A为单独的,与B和C无关了
- slave 启动后通过
slaveof 127.0.0.1 6379
连接master成功后,会发送一个命令sync(同步命令)- master接收到命令启动后台的存盘进程,同时手机所有烧到的用于修改数据集的命令,在后台进程执行完毕后,master将传送整个数据文件到slave,完成一次完全同步
- 全量复制:slave接收到数据文件后,将其存盘并加载到内存
- 增量复制:master继续将新的所有收集到的修改命令一次传给slave,完成同步
- 只要重新连接master,就会自动完成一次完全同步(全量复制)
理解:即反客为主的自动版,通过哨兵监控主机是否故障,若出现故障通过投票的方式从从机中选择一台成为主机
A(6379)、B(6380)、C(6381)三个库
- 在redis安装目录或者配置目录下创建sentinel.conf文件,名称特定
- 配置哨兵,在该配置文件中添加如下配置
sentinel monitor 被监控的数据库名称(自定义主机名称) 127.0.0.1 6379 1
,表示监控主机,当主机down后,剩余从机中投票数大于1的成为新的主机- 启动哨兵
- 当A down后,在B和C中自动投票选择B成为新的master,则此时B和C组成了master/slave
- 当A恢复后,在哨兵监控到后,B和A组成了master/slave,此时B仍为master,A成为了slave,转换了身份
复制延时问题,由于所有写操作在master,然后同步更新到slave,所以从master到slave有一定的延时,当系统繁忙的时候,延时问题会更严重,同时slave数量增多也会增加延时
- 导入jedis的jar包
- 测试即可
public class A {
public static void main(String [] args){
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.ping());
}
}
其实就是在controller层或者service层进行应用,通过如下几步进行操作
- 在pom文件中引入redis的相关依赖
- 在需要应用的类中注入RedisTemplate或StringRedisTemplate
- 在需要的方法内通过template来操作即可。
参考:
尚硅谷周阳redis教程