1.1 什么是NoSQL
NoSQL 是 Not Only SQL 的缩写,意即"不仅仅是SQL"的意思,泛指非关系型的数据库。强调Key-Value Stores和文档数据库的优点。
NoSQL产品是传统关系型数据库的功能阉割版本,通过减少用不到或很少用的功能,来大幅度提高产品性能(性能为王)
1.2 NoSQL起源
过去,关系型数据库(SQL Server、Oracle、MySQL)是数据持久化的唯一选择,但随着发展,关系型数据库存在以下问题。 2000次/s 秒杀 抢购 QPS
1.2.1 问题1:不能满足高性能查询需求
我们使用:Java、.Net等语言编写程序,是面向对象的。但用数据库都是关系型数据库。存储结构是面向对象的,但是数据库却是关系的,所以在每次存储或者查询数据时,我们都需要做转换。类似Hibernate、Mybatis这样的ORM框架确实可以简化这个过程,但是在对高性能查询需求时,这些ORM框架就捉襟见肘了。
1.2.2 问题2:应用程序规模的变大
网络应用程序的规模变大,需要储存更多的数据、服务更多的用户以及需求更多的计算能力。为了应对这种情形,我们需要不停的扩展。
扩展分为两类:一种是纵向扩展,即购买更好的机器,更多的磁盘、更多的内存等等。另一种是横向扩展,即购买更多的机器组成集群。在巨大的规模下,纵向扩展发挥的作用并不是很大。首先单机器性能提升需要巨额的开销并且有着性能的上限,在Google和Facebook这种规模下,永远不可能使用一台机器支撑所有的负载。鉴于这种情况,我们需要新的数据库,因为关系数据库并不能很好的运行在集群上
1.3 NoSQL数据库类型
1.3.1 键值(Key-Value)数据库[Redis/Memcached]—HashMap
适用场景:
储存用户信息,比如会话、配置文件、参数、购物车等等。这些信息一般都和ID(键)挂钩,这种情景下键值数据库是个很好的选择。
不适用场景:
1.取代通过键查询,而是通过值来查询。Key-Value数据库中根本没有通过值查询的途径
2.需要储存数据之间的关系。在Key-Value数据库中不能通过两个或以上的键来关联数据。
3.事务的支持。在Key-Value数据库中故障产生时不可以进行回滚。
1.3.2 ②面向文档(Document-Oriented)数据库[MongoDB] ES
数据可以使用XML、JSON或者JSONB等多种形式存储。 {“name”:“abc”}
适用场景:1.日志 2.分析
不适用场景:不支持事务
1.3.3 ③列存储(Wide Column Store/Column-Family)数据库[HBASE]
列存储数据库将数据储存在列族(column family)中,一个列族存储经常被一起查询的相关数据。举个例子,如果我们有一个Person类,我们通常会一起查询他们的姓名和年龄而不是薪资。这种情况下,姓名和年龄就会被放入一个列族中,而薪资则在另一个列族中。
适用场景:1.日志 2.博客平台,我们储存每个信息到不同的列族中。举个例子,标签可以储存在一个,类别可以在一个,而文章则在另一个。
不适用场景:1.ACID事务 2.原型设计。在模型设计之初,我们根本不可能去预测它的查询方式,而一旦查询方式改变,我们就必须重新设计列族。
1.3.4 ④图(Graph-Oriented)数据库[Neo4J,infoGird]
适用范围很小,主要用用网络拓扑分析 如脉脉的人员关系图等
1.4 传统RDBMS VS NOSQL
RDBMS(关系型数据库)Mysql
高度组织化结构化数据
结构化查询语言(SQL)
数据和关系都存储在单独的表中。
数据操纵语言(DML),数据定义语言(DDL)
严格的一致性
基础事务(ACID)刚性事务特性
NoSQL(非关系型数据库)
代表着不仅仅是SQL
没有声明性查询语言
没有预定义的模式
键 - 值对存储,列存储,文档存储,图形数据库
不能完整的支持事务(弱事务)
灵活、快速、简单 MAP
最终一致性,而非ACID【原子,一致,隔离,持久】属性
非结构化和不可预知的数据(NoSql 数据比较松散)
CAP定理【一致性,可用性,容错性】
高性能,高可用性和可伸缩性
总结NoSql
NoSql就是为了性能而诞生的
NoSql是Sql的阉割版本,是为了解决行业内特定问题
NoSql没有特定的数据结构定义,使用时不需要表
NoSql对事物的支持很差
2.2 区别
1,Memcached
挥发性(临时性)的键值存储
一般作为关系型数据库的缓存来使用
具有非常快的处理速度
由于存在数据丢失的可能,所以一般用来处理不需要持久保存的数据
用于需要使用expires时(需要定期清除数据)
使用一致性散列(Consistent Hashing)算法来分散数据
2,Tokyo Tyrant
持久性的键值存储
用来处理需要持久保存,高速处理的数据
具有非常快的处理速度
用于不需要定期清除的数据
使用一致性散列(Consistent Hashing)算法来分散数据
3,Redis
兼具Memcached和Tokyo Tyrant优势的键值存储
擅长处理数组类型的数据
具有非常快的处理速度
可以高速处理时间序列的数据,易于处理集合运算
拥有很多可以进行原子操作的方法
使用一致性散列(Consistent Hashing)算法来分散数据
4,MongoDB
面向无需定义表结构的文档数据
具有非常快的处理速度
通过BSON的形式可以保存和查询任何类型的数据
无法进行JOIN处理,但是可以通过嵌入(embed)来实现同样的功能
使用sharding(范围分割)算法来分散数据
3.1 Redis简介
Redis:Remote Dictionary Server(远程字典服务器)
Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的key-value存储系统(区别于MySQL的二维表格的形式存储。)。和Memcache类似,但很大程度补偿了Memcache的不足。和Memcache一样,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失。所以Memcache的应用场景适用于缓存无需持久化的数据。而Redis不同的是它会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化
Cpu – > 内存(内存频率) ---->磁盘(机械硬盘(5400r/min),固态硬盘 0 1)
Mysql每次查询会做磁盘IO (耗时)
3.2 Redis的特点
Redis读取的速度是110000次/s,写的速度是81000次/s 适用于现在Web2.0的时代
原子性 。Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)key(string) -value
持久化,主从复制(集群)
支持过期时间,支持事务,消息订阅。
官方不支持window,但是有第三方版本。
3.3 Redis的应用场景
3.3.1 数据缓存(提高访问性能)
将一些数据在短时间之内不会发生变化,而且它们还要被频繁访问,为了提高用户的请求速度和降低网站的负载,降低数据库的读写次数,就把这些数据放到缓存中。
3.3.2 会话缓存(共享session)
(session cache,保存web会话信息)
3.3.3 排行榜/计数器
(NGINX+lua+redis计数器进行IP自动封禁)
3.3.4 消息队列(ActiveMQ/RabbitMQ/Kafka/RocketMQ)
(构建实时消息系统,聊天,群聊)
重点做缓存(数据,用户信息)
4.1 windows下面的安装
解压
运行服务端
运行客户端
4.2 下载Redis5
redis目前的最新版本是6.X此教程是以5.07为基础来整理的,大家尽量保持一致
4.3 打开VM虚拟机把文件copy到software里面
4.4 开始安装
4.4.1 安装gcc 目地是编译软件
yum install gcc-c++
4.4.2 解压
tar -zxvf redis-5.0.7.tar.gz
4.4.3 把解压的文件copy到/usr/local/src里面
cp -r /root/software/redis-5.0.7 /usr/local/src/redis
4.4.4 打开/usr/local/src/redis/deps进行编译依赖项
cd /usr/local/src/redis/deps
进入redis的deps目录下
make hiredis lua jemalloc linenoise
4.4.5 打开/usr/local/src/redis进行编译
cd /usr/local/src/redis
在redis的目录下
4.4.6 在上面的Redis目录安装把它安装到/usr/local/redis里面
mkdir -p /usr/local/redis
make install PREFIX=/usr/local/redis
看到上面的说明安装成功了哦
4.4.7 验证安装是否成功
cd /usr/local/redis/bin
ls
4.4.8 把配置文件移动到/usr/local/redis/conf目录[目录可以自定义] 可以为/usr/local/redis/conf
mkdir /usr/local/redis/conf
cp /usr/local/src/redis/redis.conf /usr/local/redis/conf
4.4.9 启动Redis
cd /usr/local/redis/bin
./redis-server /usr/local/redis/conf/redis.conf
4.4.10 默认情况,Redis不是在后台运行,我们需要把redis放在后台运行
vi /usr/local/redis/conf/redis.conf
4.4.11 再次启动查看进程
./redis-server /usr/local/redis/conf/redis.conf
#查看进程
可以看到在6379端口号已启动了redis
4.4.12 客户端链接和退出
#连接
cd /usr/local/redis/bin
./redis-cli 默认是-h 127.0.0.1 -p 6379
#退出
quit
ping
4.4.13 停止redis
cd /usr/local/redis/bin
./redis-cli shutdown
或者
4.4.14 开机自启Redis的配置
服务器开机 每次开机 都会执行/etc/rc.local 这个文件
vim /etc/rc.local
加入 redis的绝对路径
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
4.4.15 bin目录的文件说明
redis-benchmark:redis性能测试工具
redis-check-aof:检查aof日志的工具
redis-check-rdb:检查rdb日志的工具
redis-cli:连接用的客户端
redis-server:redis服务进程
4.4.16 redis配置[后面细说]
daemonize:如需要在后台运行,把该项的值改为yes
pdifile:把pid文件放在/var/run/redis.pid,可以配置到其他地址
bind:指定redis只接收来自该IP的请求,如果不设置,那么将处理所有请求,在生产环节中最好设置该项
port:监听端口,默认为6379
timeout:设置客户端连接时的超时时间,单位为秒
loglevel:等级分为4级,debug,revbose,notice和warning。生产环境下一般开启notice
logfile:配置log文件地址,默认使用标准输出,即打印在命令行终端的端口上
database:设置数据库的个数,默认使用的数据库是0
save:设置redis进行数据库镜像的频率
rdbcompression:在进行镜像备份时,是否进行压缩
dbfilename:镜像备份文件的文件名
dir:数据库镜像备份的文件放置的路径
slaveof:设置该数据库为其他数据库的从数据库
masterauth:当主数据库连接需要密码验证时,在这里设定
requirepass:设置客户端连接后进行任何其他指定前需要使用的密码
maxclients:限制同时连接的客户端数量
maxmemory:设置redis能够使用的最大内存
appendonly:开启appendonly模式后,redis会把每一次所接收到的写操作都追加到appendonly.aof文件中,当redis重新启动时,会从该文件恢复出之前的状态
appendfsync:设置appendonly.aof文件进行同步的频率
vm_enabled:是否开启虚拟内存支持
vm_swap_file:设置虚拟内存的交换文件的路径
vm_max_momery:设置开启虚拟内存后,redis将使用的最大物理内存的大小,默认为0
vm_page_size:设置虚拟内存页的大小
vm_pages:设置交换文件的总的page数量
vm_max_thrrads:设置vm IO同时使用的线程数量
2,使用
这个没有层级关系
5.2 下载客户端redis plus
链接:https://pan.baidu.com/s/1hPBErS–3zOZv6EXL2PEsA
提取码:048c
5.4 连接
5.4.1 关闭linux防火墙
Centos6
查看防火墙状态:
[root@centos6 ~]# service iptables status
iptables:未运行防火墙。
开启防火墙:
[root@centos6 ~]# service iptables start
关闭防火墙:
[root@centos6 ~]# service iptables stop
centos7
查看防火墙状态:
firewall-cmd --state
关闭防火墙
systemctl stop firewalld.service
开启防火墙
systemctl start firewalld.service
禁用防火墙
systemctl disable firewalld.service
5.4.2 注释redis.conf里面的bind 127.0.0.1
6.1 单进程单线程(重点)
IO ----BIO readLine() NIO
采用 I/O 多路复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)
why?
多线程处理可能涉及到锁 (加锁是比较慢)
多线程处理会涉及到线程切换而消耗CPU jvm PC寄存器 一次cpu调度线程上下文切换(1500ns)
单进程不存在线程安全问题
缺点:
无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善
为什么用redis
快
为什么快(1.单线程不需要线程切换开销,2.没有锁的竞争,3.IO多路复用)
6.2 默认16个兄弟一起站台
默认16个数据库,类似数组下表从零开始,初始默认使用零号库
6.3 切换数据库命令
select index命令切换数据库
6.4 常用基本命令
dbsize查看当前数据库的key的数量
flushdb:清空当前库
Flushall;通杀全部库
6.5 其它说明
统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上
Redis索引都是从零开始0-15
6.6 为什么默认端口是6379
8080
3306
80
先介绍下redis的作者Salvatore Sanfilippo(Antirez),意大利人,就是下图这位。
Antirez现在已经40多岁了,依然奋斗在代码一线,为开源社区做贡献。Antirez出生在非英语系国家,所以英语一直是他的短板。他曾经写过一篇博文,《英语伤痛 15 年》,以自己的实际经历鼓励非英语系国家的程序员突破英语障碍。或说回来,在他的另一篇博文《Redis as an LRU cache》中,写到了为什么选用6379端口:
用一张图片来翻译一下,6379 就是这个意思:
而Merz全名Alessia Merz,是意大利的一位广告女郎,就是下面这位:
在Antirez看来,这个名字是愚蠢的代名词,所以就选了这个6379
7.1 概述
使用Redis进行应用设计和开发的一个核心概念是数据类型。与关系数据库不同,在Redis中不存在需要我们担心的表,在使用Redis进行应用设计和开发时,我们首先应该考虑的是Redis原生支持的哪种数据类型适合我们的应该场景,此外,我们无法像在关系数据库中那样,使用sql来操作Redis中的数据,相反,我们需要直接使用API发送对应的命令,来操作想要操作的数据
7.2 字符串类型Map
字符串类型是编程语言和应用程序中最常见和最有用的数据类型,也是Redis的基本数据类型之一,事实上,Redis中所有键都必须是字符串。
7.3 list数据类型Map
列表是应用我只是应该程序开发中非常有用的数据类型之一,列表能存在一组对象,因此它也可以被用于栈或者队列,在Redis中,与键相关的联的值可以是字符串组成的列表,Redis中的列表更像是数据结构中的双向链表。
7.4 hash数据类型Map
哈希表示字段和值之间的映射关系,与JAVA中的Map类似,Redis数据集本身就可以看做一个哈希,其中字符串类型的键关联到如字符串和列表之类的数据对象,而Reidis的数据对象也可以再次使用哈希,其字段和值必须 是字符串。
7.5 set数据类型Map
集合类型是由唯一,无序对象组成的集合(collection).它经常用于测试某个成员是集合中,重复项删除和集合运算(求并,交,差集),Redis的值对象可以是字符串集合。
7.6 zset(sortset)数据类型
有序集合是一个类似于set但是更复杂的数据类型,单词sorted意为着这种集合中的每个元素都有一个可用于排序的权重,并且我们可以按顺序从集合中得到元素在某些需要一个保持数据有序的场景中,使用这种原生的序的特性是很方便的。
关于命令的学习查询看这个网站http://www.redis.net.cn/order/
或者:http://redisdoc.com/
http://doc.redisfans.com/index.html
8.1 常用命令
keys * 获取当前库所有的key
select index 选择第index个库
flushdb 清空当前库
del key1 删除key
expire key 10 设置key的过期时间单位是秒 放在session 设置过期时间
怎么实现一个任务在将来的某个时间点发生
ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期
move k1 1 将当前的数据库key移动到某个数据库,目标库有,则不能移动
randomkey 从当前数据库中随机返回
type key 类型
exists key 判断是否存在key
pexpire key 1000 设置key的过期时间单位是毫秒
persist key 删除过期时间
8.2 string类型相关命令
语法set key value 存放key-vulue
get key 获取key的值
getset name new_cxx 设置值,返回旧值
mset key1 v1 key2 v2 批量设置
mget key1 key2 批量获取
setnx key value 不存在就插入(set if not exists) 分布式锁
incr age 递增
incrby age 10 递增
decr age 递减
decrby age 10 递减
strlen 长度
getrange name 0 -1 字符串分段 0 -1是全部 0 -2 ==n-1
setrange key index value 从index开始替换value
incrbyfloat 增减浮点数
append 追加
object encoding key 得到key 的类型 string里面有三种编码
int 用于能够作用64位有符号整数表示的字符串
embstr 用于长度小于或等于44字节 Redis3.x中是39字节,这种类型的编码在内存使用时性能更好
raw 用于长度大于44字节的
8.3 list【集合数组】
语法lpush key values l=left r =rigth
lpush mylist a b c 左插入
rpush mylist x y z 右插入
lrange mylist 0 -1 取出数据集合 0 -1是取出所有 0 1取第一个和第二个
lpop mylist 弹出集合最后一个元素 弹出之后就没有了哦
rpop mylist 弹出第一个元素 弹出之后就没有了哦
lrem mylist count value 删除
|-COUNT 的值可以是以下几种:
|--count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
|--count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
|--count = 0 : 移除表中所有与 VALUE 相等的值。
lindex mylist 2 指定索引的值
llen mylist 长度
lset mylist 2 n 索引设值
ltrim mylist 0 4
|--对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
linsert mylist before a 插入
linsert mylist after a 插入
|--命令用于在列表的元素前或者后插入元素。 当指定元素不存在于列表中时,不执行任何操作。
当列表不存在时,被视为空列表,不执行任何操作。 如果 key 不是列表类型,返回一个错误。
rpoplpush list list2 转移列表的数据
|--命令用于移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
8.4 Hash Map
语法:hset key key value
hset myhash name cxx
|--命令用于为哈希表中的字段赋值 。
|--如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。
|--如果字段已经存在于哈希表中,旧值将被覆盖。
hget myhash name
hmset myhash name cxx age 25 note "i am notes" 批量插入hmset key k1 v1 k2 v2
hmget myhash name age note 批量获取 hmget key k1 k2
hgetall myhash 获取所有的
hexists myhash name 是否存在
hdel myhash name 删除
hsetnx myhash score 100 设置不存在的 如果存在,不做处理
hincrby myhash id 1 递增
hkeys myhash 只取key
hvals myhash 只取value
hlen myhash 长度
8.5 set(无序且唯一)
sadd key value [value2 value3] 添加一个或者多个成员
smembers key 返回集合中的所有成员
srem key value [value2 value3] 移除集合中一个或多个成员
scard key 获取当前key下的元素个数
spop 从集合中随机弹出一个元素 就从集合中删掉了
sismember myset set1 判断元素是否在集合中
sdiff key1 key2 …… | sinter | sunion 操作:集合间运算:差集 | 交集 | 并集
srandmember key count 随机获取集合中的元素
8.6 zset(排序和隐藏分数–排序码)
zadd key score value 添加元素key 并且设置分数
zrange key 0 -1 withscores 返回集合中所有的元素 按照分数从小到大排序
zrevrange key 0 -1 withscores 返回集合中所有的元素 按照分数从大到小排序
zrangebyscore key 10 25 withscores 取指定分数范围的值
zcard key 元素数量
zrem key value1 value2 删除一个或多个元素
zincrby zset 1 one 增长分数
zscore zset two 获取分数
zrangebyscore zset 10 25 withscores 指定范围的值
zrangebyscore zset 10 25 withscores limit 1 2 分页
zrevrangebyscore zset 10 25 withscores 指定范围的值
zcount zset 获得指定分数范围内的元素个数
1 配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit
2 对大小写不敏感
默认情况下,如果没有指定“bind”配置指令,则Redis侦听用于连接服务器上所有可用的网络接口。
可以只监听一个或多个选择的接口 "bind"配置指令,后面跟着一个或多个IP地址。
1,bind
默认情况下,redis 在 server 上所有有效的网络接口上监听客户端连接。如果只想让它在一个或多个网络接口上监听,那你就绑定一个IP或者多个IP。多个ip空格分隔即可。
2,prot运行端口
3,Tcp-backlog
设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值
来达到想要的效果
4,timeout
当客户端闲置多少秒后关闭连接,如果设置为0表示关闭该功能。
5,tcp-keepalive 300
单位是秒,表示将周期性的使用SO_KEEPALIVE检测客户端是否还处于健康状态,避免服务器一直阻塞,官方给出的建议值是300S
daemonize no 是否以守护模式启动,默认为no,配置为yes时以守护模式启动,这时redis instance会将进程号pid写入默认文件/var/run/redis.pid。
supervised no 可以通过upstart和systemd管理Redis守护进程,这个参数是和具体的操作系统相关的。
pidfile /var/run/redis_6379.pid 配置pid文件路径。当redis以守护模式启动时,如果没有配置pidfile,pidfile默认值是/var/run/redis.pid 。
loglevel notice 日志级别。可选项有:debug(记录大量日志信息,适用于开发、测试阶段); verbose(较多日志信息); notice(适量日志信息,使用于生产环境);warning(仅有部分重要、关键信息才会被记录)。
logfile “” 日志文件的位置,当指定为空字符串时,为标准输出,如果redis已守护进程模式运行,那么日志将会输出到 /dev/null(linux的无底洞文件) 。
syslog-enabled no 是否把日志记录到系统日志。
syslog-ident 设置系统日志的id 如 syslog-ident redis
databases 16 设置数据库的数目。默认的数据库是DB 0 ,可以在每个连接上使用select 命令选择一个不同的数据库,dbid是一个介于0到databases - 1 之间的数值。
always-show-logo yes 是否一直显示日志
save 保存数据到磁盘。格式是:save ,含义是在 seconds 秒之内至少有 changes个keys 发生改变则保存一次。
如 save 900 1 900秒有一条数据改变就保存
save 300 10 300秒有10条数据改变就保存
save 60 10000 600秒有10000条数据改变就保存
stop-writes-on-bgsave-error yes 默认情况下,如果 redis 最后一次的后台保存失败,redis 将停止接受写操作,这样以一种强硬的方式让用户知道数据不能正确的持久化到磁盘, 否则就会没人注意到灾难的发生。 如果后台保存进程重新启动工作了,redis 也将自动的允许写操作。然而你要是安装了靠谱的监控,你可能不希望 redis 这样做,那你就改成 no 好了。
rdbcompression yes 是否在dump .rdb数据库的时候压缩字符串,默认设置为yes。如果你想节约一些cpu资源的话,可以把它设置为no,这样的话数据集就可能会比较大。
rdbchecksum yes 是否CRC64校验rdb文件,会有一定的性能损失(大概10%)
dbfilename dump.rdb rdb文件的名字。
dir ./ 数据文件保存路径指redis.conf配置文件所在的路径
9.8 LIMITS限制【淘汰策略】【重点】
maxclients 10000
设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你
无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自身内部处理逻辑留一些句柄出来。如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
maxmemory
设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素
maxmemory-policy
Ø noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
Ø allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最近最久没有使用的键
Ø volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
Ø allkeys-random:加入键的时候如果过限,从所有key随机删除
Ø volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
Ø volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
Ø volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
Ø allkeys-lfu:从所有键中驱逐使用频率最少的键
LRU : 最近最久没有使用的算法(对最近的表现做总结,考虑不完善,但是很容易实现)
如果一个数据最近一段时间都没有被访问到,那么认为这个数据在将来访问的可能性也比较小,因此,当空间满时,最久没有访问的数据最先被淘汰
LFU : 使用频率最少的算法(对历史数据做总结,考虑更全面一些,但是会耗费总结历史的时间)
如果一个数据很少被访问到,那么认为这个数据在将来访问的可能性也比较小,因此,当空间满时,最小频率访问的数据最先被淘汰
maxmemory-samples
设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,
redis默认会检查这么多个key并选择其中LRU的那个
9.9 REPLICATION复制
讲主从复制再说
9.10 APPEND ONLY MODE 追加[持久化再说]【重点】
9.11 常见配置redis.conf总结
参数说明
redis.conf 配置项说明如下:
daemonize no
pidfile /var/run/redis.pid
port 6379
bind 127.0.0.1 0.0.0.0
5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
loglevel verbose
logfile stdout
databases 16
save
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
rdbcompression yes
dbfilename dump.rdb
dir ./ /redis的安装目录/bin
slaveof
masterauth
requirepass foobared
maxclients 128
maxmemory
appendonly no
appendfilename appendonly.aof
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
vm-enabled no
vm-swap-file /tmp/redis.swap
vm-max-memory 0
vm-page-size 32
vm-pages 134217728
vm-max-threads 4
glueoutputbuf yes
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
activerehashing yes
include /path/to/local.conf
LRU --根据使用时间
LFU --根据使用频率
我们给一个key设置了一个过期时间(expire key time),在未来的某个时间获取这个key的时候,发现key已经被删除了
这就是说一个操作将在未来的时间点发生
10.1 惰性删除(取数据的时候删除)
/**
* 测试惰性删除
* 要点:
* 在set的时候,不建立任务 也不开启线程
* 在get的时候,判断key是否过期,过期了则删除
*/
public class TestExpire1 {
/**
* 自己的redis,本质就是一个map集合
*/
private static Map<String, Object> redis = new HashMap<>();
/**
* 如果给key设置了过期时间,就将key记录起来
*/
private static Map<String, Long> expireKey = new HashMap<>();
/**
* 存储
*
* @param key
* @param value
* @param expireTime
*/
public static void set(String key, Object value, Long expireTime) {
redis.put(key, value);
if (expireTime != null) {
//如果设置了过期时间 就放在记录的集合中
expireKey.put(key, System.currentTimeMillis() + expireTime);
}
}
/**
* 获取
*
* @param key
* @return
*/
public static Object get(String key) {
//先判断过期集合中有没有这个key
if (expireKey.containsKey(key)) {
//如果存在这个key 拿到时间
Long expireTime = expireKey.get(key);
if (System.currentTimeMillis() > expireTime) {
//说明过期了 要从redis里面移除掉
redis.remove(key);
}
}
//如果不包含这个key,直接从redis里面取走
return redis.get(key);
}
/**
* 测试惰性删除
*
* @param args
*/
public static void main(String[] args) {
//放一个
set("test", "test", 5000L);
while (true) {
try {
//睡一下
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object test = get("test");
System.out.println(test);
}
}
}
10.2 定时删除(定时任务)
/**
* 定时删除,也就是立即删除
* 要点:在设置的key的时候 就分配一个线程去跟着他,一单过期,立马干掉
* 优点:保证热点key
* 缺点:cpu占用高
*/
public class TestExpire2 {
/**
* 自己的redis,存放数据
*/
private static Map<String, Object> redis = new HashMap<>();
/**
* 因为我们需要用到多线程,所以我们直接用标准的线程池
*/
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
5,
8,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
/**
* 存储
*
* @param key
* @param value
* @param expireTime
*/
public static void set(String key, Object value, Long expireTime) {
redis.put(key, value);
if (expireTime != null) {
//如果设置了过期时间,我们就分配一个线程去做一个定时器任务(一个线程+sleep时间)
setExpireTimeForKey(key, expireTime);
}
}
/**
* 定时删除的定时器
* 每一个key都需要给一个线程去执行,导致cpu切换频繁,对cpu影响很大
*
* @param key
* @param expireTime
*/
private static void setExpireTimeForKey(String key, Long expireTime) {
threadPool.submit(() -> {
//线程池提交一个任务,时间到了就删除这个key
try {
Thread.sleep(expireTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
//睡好了就删除
redis.remove(key);
});
}
/**
* 测试定时删除的方法
*
* @param args
*/
public static void main(String[] args) {
set("test", "test", 5000L);
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (redis.containsKey("test")) {
System.out.println("redis存在该值");
} else {
System.out.println("redis已经将他删掉了");
}
}
}
}
10.3 定期删除(闹钟定期)
/**
* 定期删除
* 就是隔一段时间,执行一次过期检测
* 优点:cpu合理利用
* 缺点:可能会出现脏读问题
* 编码要点,开启一个守护线程,定时执行检测和删除操作
*/
public class TestExpire3 {
/**
* 存放数据的集合,自己的redis
*/
private static Map<String, Object> redis = new HashMap<>();
/**
* 存放设定了过期时间的key
*/
private static Map<String, Long> expire = new HashMap<>();
/**
* 创建线程池
*/
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
5,
8,
30,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
/**
* 当类加载的时候,就创建守护线程
*/
static {
threadPool.submit(() -> {
while (true) {
//执行一个任务,每10秒一次
TimeUnit.SECONDS.sleep(10);
//睡醒了就看过期的集合中,有没有过期的
expire.forEach((k, v) -> {
if (System.currentTimeMillis() > v) {
//如果当前时间大于过期时间,就说明过期了 要删除
if (redis.containsKey(k)) {
redis.remove(k);
}
}
});
}
});
}
/**
* 存放数据
*
* @param key
* @param value
* @param expireTime
*/
public static void set(String key, Object value, Long expireTime) {
//存redis
redis.put(key, value);
if (expireTime != null) {
//存放过期的集合
expire.put(key, System.currentTimeMillis() + expireTime);
}
}
/**
* 获取数据
*
* @param key
* @return
*/
public static Object get(String key) {
return redis.get(key);
}
/**
* 测试的方法
*
* @param args
*/
public static void main(String[] args) {
set("test", "test", 5000L);
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (redis.containsKey("test")) {
System.out.println("redis存在");
} else {
System.out.println("这个key被删除了");
}
}
}
}
10.4 最终方案(惰性+定期)
/**
* 删除策略最终版本
* 惰性+定期
*/
public class TestExpireFinal {
private static Map<String, Object> redis = new HashMap<>();
private static Map<String, Long> expire = new HashMap<>();
//用来存放过期的key
private static List<String> expireKeys = new ArrayList<>();
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
5,
8,
30,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
static {
threadPool.submit(() -> {
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我睡了十秒了");
expire.forEach((k, v) -> {
if (System.currentTimeMillis() > v) {
redis.remove(k);
//放到过期的集合中
expireKeys.add(k);
}
});
if (!CollectionUtils.isEmpty(expireKeys)) {
//如果过期的key有值,就循环
expireKeys.forEach(k -> {
//把存放过期的key的集合也清理一下
expire.remove(k);
});
//记得清理一下这个存放的集合
expireKeys.clear();
}
}
});
}
public static void set(String key, Object value, Long expireTime) {
redis.put(key, value);
if (expireTime != null) {
expire.put(key, System.currentTimeMillis() + expireTime);
}
}
public static Object get(String key) {
//记得惰性删除的要点
if (expire.containsKey(key)) {
//就看key是否过期
Long time = expire.get(key);
if (System.currentTimeMillis() > time) {
//过期了
redis.remove(key);
expire.remove(key);
}
}
return redis.get(key);
}
public static void main(String[] args) {
set("test", "test", 5000L);
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object test = get("test");
System.out.println(test);
}
}
}
11.1 概述
https://redis.io/topics/persistence
11.2 RDB【Redis DataBase】
11.2.1 什么是RDB
save 900 1
save 300 10
save 60 10000
在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方 式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
优点:RDB 数据很稳定 文件比较小
缺点:最后一次持久化后的数据可能丢失
11.2.2 什么是FORK
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
11.2.3 保存位置及配置位置
Rdb 保存的是dump.rdb文件
执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
注意不要在生产环境去手动save拍照
11.2.5 如何恢复数据
将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可
11.2.6 优点
备份的文件小
适合大规模的数据恢复
恢复速度快
提高redis的性能
11.2.7 缺点
数据不完整,可能会丢失最后一次save的数据
在持久化时需要fork子进程,需要考虑内存消耗问题
11.2.8 RDB的关闭
save “”
11.3.2 原理
set k1 v1
set k2 v2
del k2
以日志的形式来记录每个写[添加 修改 删除]操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
11.3.3 保存位置及位置配置
Aof保存的是appendonly.aof文件
11.3.4 AOF启动/修复/恢复
正常恢复
启动:设置Yes 修改默认的appendonly no,改为yes
将有数据的aof文件复制一份保存到对应目录(config get dir)
恢复:重启redis然后重新加载
异常恢复
启动:设置Yes 修改默认的appendonly no,改为yes
备份被写坏的AOF文件
修复: redis-check-aof --fix appendonly.aof (修复文件)
恢复:重启redis然后重新加载
11.3.5 AOF持久化模式
每修改同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
每秒同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失
不同步:appendfsync no 从不同步
11.3.6 优势
AOF更持久,数据完整性更高
AOF是追加文件的方式,可以进行文件重写
AOF本身是日志文件,该日志文件包含所有的写操作,我们可以直接打开观看
11.3.7 劣势
AOF文件比较大
AOF的持久化方案会导致性能不好
AOF官方承认可能有一些bug,可能数据和原来的不一致
11.4 说了那么多,我们选择哪一个呢
11.4.1 官方建议
4.2,整理我们的理解及处理方式
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些 命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾. Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
只做缓存(RDB)
做用户登录保存session会话(RDB+AOF)
极致追求性能(关闭RDB,关闭AOF)
极致追求安全性(RDB+AOF (appendfsync always))
RDB(拍照)+AOF(日志)就是制定的redis持久化方案
只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
同时开启两种持久化方式
在这种情况下,当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同时倒掉,会丢失十几分钟的数据,启动脚本也要比