Redis基本概念

tanqi
13
2025-04-12

1.     NoSQL数据库简介

1.1.   技术发展

技术的分类

1、解决功能性的问题:Java、JSP、RDBMS、Tomcat、HTML、Linux、JDBC、SVN

2、解决扩展性的问题:Struts、Spring、SpringMVC、Hibernate、Mybatis

3、解决性能的问题:NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch

 

1.1.1.    Web1.0时代

Web1.0的时代,数据访问量很有限,用一夫当关的高性能的单点服务器可以解决大部分问题。

 

1.1.2.    Web2.0时代

随着Web2.0的时代的到来,用户访问量大幅度提升,同时产生了大量的用户数据。加上后来的智能移动设备的普及,所有的互联网平台都面临了巨大的性能挑战。

1.1.3.    解决CPU及内存压力

 

1.1.4.    解决IO压力

1.2.   NoSQL数据库

1.2.1.    NoSQL数据库概述

NoSQL(NoSQL = Not Only SQL ),即“不仅仅是SQL”,泛指非关系型的数据库

NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。

l  不遵循SQL标准。

l  不支持ACID。

l  远超于SQL的性能。

1.2.2.    NoSQL适用场景

l  对数据高并发的读写

l  海量数据的读写

l  对数据高可扩展性的

1.2.3.    NoSQL不适用场景

l  需要事务支持

l  基于sql的结构化查询存储,处理复杂的关系,需要即席查询。

(用不着sql的和用了sql也不行的情况,请考虑用NoSql)

1.2.4.    Memcache

ü  很出现的NoSql数据库

ü  数据都在内存中,一般不持久化

ü  支持简单的key-value模式,支持类型单一

ü  一般是作为缓存数据库辅助持久化的数据库

1.2.5.    Redis

ü  几乎覆盖了Memcached的绝大部分功能

ü  数据都在内存中,支持持久化,主要用作备份恢复

ü  除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等。

ü  一般是作为缓存数据库辅助持久化的数据库

1.2.6.    MongoDB

 

ü  高性能、开源、模式自由(schema  free)的文档型数据库

ü  数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘

ü  虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能

ü  支持二进制数据及大型对象

ü  可以根据数据的特点替代RDBMS ,成为独立的数据库。或者配合RDBMS,存储特定的数据。

1.3.   行列式存储数据库(大数据时代)

1.3.1.    行式数据库

1.3.2.    列式数据库

1.3.2.1. Hbase

HBase是Hadoop项目中的数据库。它用于需要对大量的数据进行随机、实时的读写操作的场景中。

HBase的目标就是处理数据量非常庞大的表,可以用普通的计算机处理超过10亿行数据,还可处理有数百万元素的数据表。

1.3.2.2. Cassandra[kəˈsændrə]

Apache Cassandra是一款免费的开源NoSQL数据库,其设计目的在于管理由大量商用服务器构建起来的庞大集群上的海量数据集(数据量通常达到PB级别)。它最初由Facebook开发,于2008将 Cassandra 开源。在众多显著特性当中,Cassandra最为卓越的长处是对写入及读取操作进行规模调整,而且其不强调主集群的设计思路能够以相对直观的方式简化各集群的创建与扩展流程。

计算机存储单位 计算机存储单位一般用B,KB,MB,GB,TB,EB,ZB,YB,BB来表示,它们之间的关系是:

位 bit (比特)(Binary Digits):存放一位二进制数,即 0 或 1,最小的存储单位。

字节 byte:8个二进制位为一个字节(B),最常用的单位。

1KB (Kilobyte 千字节)=1024B,

1MB (Megabyte 兆字节 简称“兆”)=1024KB,

1GB (Gigabyte 吉字节 又称“千兆”)=1024MB,

1TB (Trillionbyte 万亿字节 太字节)=1024GB,其中1024=2^10 ( 2 的10次方),

1PB(Petabyte 千万亿字节 拍字节)=1024TB,

1EB(Exabyte 百亿亿字节 艾字节)=1024PB,

1ZB (Zettabyte 十万亿亿字节 泽字节)= 1024 EB,

1YB (Jottabyte 一亿亿亿字节 尧字节)= 1024 ZB,

1BB (Brontobyte 一千亿亿亿字节)= 1024 YB.

注:“兆”为百万级数量单位。

1.4.   图关系型数据库

主要应用:社会关系,公共交通网络,地图及网络拓谱(n*(n-1)/2)

图形数据库是以图形结构的形式存储数据的数据库。 它以节点,关系和属性的形式存储应用程序的数据。 正如RDBMS以表的“行,列”的形式存储数据,GDBMS以“图形”的形式存储数据。

Neo4j是一个世界领先的开源图形数据库, 它使用Java语言完全开发的。

1.5.   DB-Engines 数据库排名

http://db-engines.com/en/ranking

 

2.   Redis概述安装

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库

Ø  Redis是一个开源key-value存储系统。

Ø  Redis可以用作数据库、缓存和消息中间件。

Ø  Redis采用C/S架构。Client和Server可以是在一台机器上的,也可以不在。

Ø  和Memcached相比,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。

Ø  这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。

Ø  与memcached一样,为了保证效率,数据都是缓存在内存中。区别是Redis会周期性把更新的数据写入磁盘或把修改操作写入追加的记录文件。

Ø  Redis支持使用master-slave主从同步来实现高可用性,支持使用cluster集群来实现高性能。

2.1.   应用场景

2.1.1.    配合关系型数据库做高速缓存

Ø  高频次,热门访问的数据,降低数据库IO

Ø  分布式架构,做session共享,session将不再由容器管理

2.1.2.    多样的数据结构存储持久化数据

2.2.   Redis安装

Redis官方网站

Redis中文官方网站

http://redis.io

http://redis.cn/

 

2.2.1.    下载地址

https://download.redis.io/releases/?_ga=2.179844832.551072071.1617763911-1920209215.1617763911

Ø  6.2.1 for Linux(redis-6.2.1.tar.gz

Ø  不用考虑在windows环境下对Redis的支持

2.2.2.    安装步骤

1)     准备工作:下载安装最新版的gcc编译器

安装C 语言的编译环境

yum install centos-release-scl scl-utils-build

yum install -y devtoolset-8-toolchain

scl enable devtoolset-8 bash

也可以采用下面一个命令实现快速安装

执行 :  yum install gcc

测试 gcc版本

gcc --version

2)     下载redis-6.2.1.tar.gz放/opt目录

3)     解压命令:tar -zxvf redis-6.2.1.tar.gz

4)     解压完成后进入目录:cd redis-6.2.1

5)     在redis-6.2.1目录下再次执行make命令(只是编译好)

make工具是通过编写的makefile脚本文件描述整个工程的编译、链接规则;通过脚本文件,对于复杂的工程也可以只通过一个命令就完成整个编译过程。

如没有准备好C语言编译环境,make 会报错—Jemalloc/jemalloc.h:没有那个文件

解决方案:运行make distclean

在redis-6.2.1目录下再次执行make命令(只是编译好)、

6)     跳过make test 继续执行: make install

make是用来编译的,它从Makefile中读取指令,然后编译。

make install是用来安装的,它也从Makefile中读取指令,安装到指定的位置

 

2.2.3.    安装目录:/usr/local/bin

查看默认安装目录:

redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何

redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲

redis-check-dump:修复有问题的dump.rdb文件

redis-sentinel:Redis集群使用

redis-server:Redis服务器启动命令

redis-cli:客户端,操作入口

2.2.4.    前台启动(不推荐)

前台启动,命令行窗口不能关闭,否则服务器停止

2.2.5.    后台启动(推荐

2.2.5.1. 备份redis.conf

拷贝一份redis.conf到其他目录

[root@localhost opt]# mkdir /myredis

[root@localhost opt]# cp redis-6.2.1/redis.conf /myredis/redis.conf

2.2.5.2. 后台启动设置daemonize no改成yes

修改redis.conf(128行)文件将里面的daemonize no 改成 yes,让服务在后台启动

2.2.5.3. Redis启动

redis-server  /myredis/redis.conf

2.2.5.4. 用客户端访问:redis-cli

2.2.5.5. 多个端口可以:redis-cli -p 6379

2.2.5.6. 测试验证: ping

Redis Ping 命令使用客户端向 Redis 服务器发送一个 PING ,如果服务器运作正常的话,会返回一个 PONG 。通常用于测试与服务器的连接是否仍然生效,或者用于测量延迟值。

2.2.5.7. Redis关闭

单实例关闭:redis-cli shutdown

也可以进入终端后再关闭

多实例关闭,指定端口关闭:redis-cli -p 6379 shutdown

2.2.6.    Redis介绍相关知识

端口6379从何而来

Alessia  Merz

默认16个数据库,类似数组下标从0开始,初始默认使用0号库

使用命令 select   <dbid>来切换数据库。如: select 8

统一密码管理,所有库同样密码。

dbsize查看当前数据库的key的数量

flushdb清空当前库

flushall通杀全部库

Redis是单线程+多路IO复用技术

串行   vs   多线程+锁(memcached) vs   单线程+多路IO复用(Redis)

为什么采用单线程:采用单线程可以避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。

由于Redis基于内存已经很快了,所以直接采用了单线程。Redis服务端对于命令的处理是单线程的,但是在I/O层面却可以同时面对多个客户端并发的提供服务,并发到内部单线程的转化通过多路复用框架实现,可以提高性能。

多路IO复用:使用单独一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)

(与Memcache三点不同: 支持多数据类型,支持持久化,单线程+多路IO复用) 

Redis6.0引入了多线程

Redis作者是如何点评 “多线程”这个新特性的:

Redis支持多线程有2种可行的方式:

第一种: 就是像“memcached”那样,一个Redis实例开启多个线程,从而提升GET/SET等简单命令中每秒可以执行的操作;这涉及到I/O、命令解析等多线程处理,因此,我们将其称之为“I/O threading”。

第二种:就是允许在不同的线程中执行较耗时较慢的命令,以确保其它客户端不被阻塞; 我们将这种线程模型称为“Slow commands threading”。

Redis不会采用“I/O threading”,redis运行时主要受制于网络和内存,提升redis性能主要是通过在多个redis实例,特别是redis集群。

Redis6.0之前为什么一直不使用多线程?

²  单线程已经够用:Redis 的瓶颈并不在 CPU,而在内存和网络。如果要使用 CPU 多核,可以搭建多个 Redis 实例来解决,而不是引入多线程。

²  多线程会引发很多问题:引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。

Redis6.0为什么要引入多线程呢?

² 多线程任务可以分摊 Redis 同步 IO 读写负荷

² 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核

默认情况下,Redis6.0的多线程并没有开启。如果开启,需要在配置文件redis.conf中配置。

推荐Redis命令手册网址:

https://www.redis.net.cn/order/

http://doc.redisfans.com/

http://www.redis.cn/commands.html

3.   Redis五大数据类型

Redis是一个开源的key-value存储系统,key都是String类型value支持字符串String、列表List、集合Set、哈希Hash、有序集合ZSet五种类型

3.1.  Redis键(key)

keys 查看当前库所有key    (匹配:keys 1)

exists key判断某个key是否存在

type key 查看你的key是什么类型

del key       删除指定的key数据

unlink key   根据value选择非阻塞k删除

仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。

expire key 10   10秒钟:为给定的key设置过期时间

ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期

select命令切换数据库

dbsize查看当前数据库的key的数量

flushdb清空当前库

flushall通杀全部库

3.2.   Redis字符串(String)

3.2.1.    简介

String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。

String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。

String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M

3.2.2.    常用命令

set   <key><value>添加键值对

*NX:当数据库中key不存在时,可以将key-value添加数据库

*XX:当数据库中key存在时,可以将key-value添加数据库,与NX参数互斥

*EX:key的超时秒数

*PX:key的超时毫秒数,与EX互斥

 

get   <key>查询对应键值

append  <key><value>将给定的<value> 追加到原值的末尾

strlen  <key>获得值的长度

setnx  <key><value>只有在 key 不存在时    设置 key 的值

incr  <key>

将 key 中储存的数字值增1

只能对数字值操作,如果为空,新增值为1

decr  <key>

将 key 中储存的数字值减1

只能对数字值操作,如果为空,新增值为-1

incrby / decrby  <key><步长>将 key 中储存的数字值增减。自定义步长。

原子性

所谓原子操作是指不会被线程调度机制打断的操作

这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

(1)在单线程中, 能够在单条指令中完成的操作都可以认为是"原子操作",因为中断只能发生于指令之间。

(2)在多线程中,不能被其它进程(线程)打断的操作就叫原子操作。

Redis单命令的原子性主要得益于Redis的单线程。

mset  <key1><value1><key2><value2>  .....

同时设置一个或多个 key-value对 

mget  <key1><key2><key3> .....

同时获取一个或多个 value 

msetnx <key1><value1><key2><value2>  .....

同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

原子性,有一个失败则都失败

getrange  <key><起始位置><结束位置>

获得值的范围,类似java中的substring,前包,后包

setrange  <key><起始位置><value>

用 <value>  覆写<key>所储存的字符串值,从<起始位置>开始(索引从0开始)。

setex  <key><过期时间><value>

设置键值的同时,设置过期时间,单位秒。

getset <key><value>

以新换旧,设置了新值同时获得旧值。

 

3.3.   Redis列表(List) 

3.3.1.    简介

单键多值

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

3.3.2.    常用命令

lpush/rpush  <key><value1><value2><value3> .... 从左边/右边插入一个或多个值。

lpop/rpop  <key>从左边/右边吐出一个值。值在键在,值光键亡

 

rpoplpush  <key1> <key2>从<key1>列表右边吐出一个值,插到<key2>列表左边。

lrange <key><start><stop>

按照索引下标获得元素(从左到右)

lrange mylist 0 -1   0左边第一个,-1右边第一个,(0  -1表示获取所有)

lindex <key><index>按照索引下标获得元素(从左到右)

llen <key>获得列表长度

linsert <key>  before <value><newvalue>在<value>的后面插入<newvalue>插入值

lrem <key><n><value>从左边删除n个value(从左到右)

lset<key><index><value>将列表key下标为index的值替换成value

 

3.4.   Redis集合(Set)

3.4.1.    简介

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

Redis的Set是string类型的无序集合它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)

一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变

3.4.2.    常用命令

sadd <key><value1><value2> .....

将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略

smembers <key>取出该集合的所有值。

sismember <key><value>判断集合<key>是否为含有该<value>值,有1,没有0

scard<key>返回该集合的元素个数。

srem <key><value1><value2> .... 删除集合中的某个元素。

spop <key>随机从该集合中吐出一个值。

srandmember <key><n>随机从该集合中取出n个值。不会从集合中删除

smove <source><destination>value把集合中一个值从一个集合移动到另一个集合

sinter <key1><key2>返回两个集合的交集元素。

sunion <key1><key2>返回两个集合的并集元素。

sdiff <key1><key2>返回两个集合的差集元素(key1中的,不包含key2中的)

Set经典应用场景,如社交场景中,通过交集、并集和差集运算,通过Set类型可以非常方便地查找共同好友、共同关注和共同偏好等社交关系。

 

3.5.   Redis哈希(Hash)

3.5.1.    简介

Redis hash 是一个键值对集合,value是一个string类型的fieldvalue的映射表,hash特别适合用于存储对象,类似Java里面的Map<String,Object>

用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储

主要有以下2种存储方式:

每次修改用户的某个属性需要,先反序列化改好后再序列化回去。开销较大。

用户ID数据冗余

 

 

通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题

 

3.5.2.    常用命令

hset <key><field><value>给<key>集合中的  <field>键赋值<value>

hget <key1><field>从<key1>集合<field>取出 value

hmset <key1><field1><value1><field2><value2>... 批量设置hash的值

hmget <key1><field1> <field2>... 批量获取hash的值

hgetall <key> 获取中所有的field和value

hlen  <key> 获取hash中field-value的数量

hdel  <key><field> 删除<key>集合中指定<field>

hexists<key1><field>查看哈希表 key 中,给定域 field 是否存在。

hkeys <key>列出该hash集合的所有field

hvals <key>列出该hash集合的所有value

hincrby <key><field><increment>为哈希表 key 中的域 field 的值加上增量 1   -1

hsetnx <key><field><value>将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .

 

3.6.   Redis有序集合Zset(sorted set)

3.6.1.    简介

Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。

不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了

因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。

访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

3.6.2.    常用命令

zadd  <key><score1><member 1><score2><member 2>…

将一个或多个 member 元素及其 score 值加入到有序集 key 当中。

zcard<key>返回该有序集合的元素个数。

zcount <key><min><max>统计该集合,分数区间内的元素个数

zrange <key><start><stop>  [WITHSCORES]  

返回有序集 key 中,下标在<start><stop>之间的元素

带WITHSCORES,可以让分数一起和值返回到结果集。

zrevrange <key><start><stop>  [WITHSCORES]  

zrangebyscore key min max [withscores] [limit offset count]

返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。

zrevrangebyscore key max min [withscores] [limit offset count]              

同上,改为从大到小排列。

zincrby <key><increment><member >      为元素的score加上增量

zrem  <key><member >删除该集合下,指定值的元素

zrank <key><member > 返回有序集 key 中成员member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。排名以 0 为底,也就是说, score 值最小的成员排名为 0 。

zrevrank <key><member > 获得成员按 score 值递减(从大到小)排列的排名。

案例:如何利用zset实现一个文章访问量的排行榜?

4.   Redis配置文件介绍

自定义目录:/myredis/redis.conf

4.1.   ###Units单位###

配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit

大小写不敏感

4.2.   ###INCLUDES包含###

类似jsp中的include,多实例的情况可以把公用的配置文件提取出来

4.3.   ###网络相关配置 ###

4.3.1.    bind

默认情况bind=127.0.0.1只能接受本机的访问请求

不写的情况下,无限制接受任何ip地址的访问

生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的,所以需要将其注释掉

如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应

保存配置,停止服务,重启启动查看进程,不再是本机访问了。

4.3.2.    protected-mode

将本机访问保护模式设置no

4.3.3.    Port

端口号,默认 6379

4.3.4.    tcp-backlog

设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。

在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。

注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果

4.3.5.    timeout

一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭

4.3.6.    tcp-keepalive

对访问客户端的一种心跳检测,每个n秒检测一次。

单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60

4.4.   ###GENERAL通用###

4.4.1.    daemonize

是否为守护进程。设置为yes,是守护进程,将后台启动

4.4.2.    pidfile

存放pid文件的位置,每个实例会产生一个不同的pid文件

4.4.3.    loglevel

指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice。

debug:会打印生成大量信息,适用于开发/测试阶段

verbose:包含很多不太有用的信息,但是不像debug级别那么混乱

notice:适度冗长,适用于生产环境

warning:仅记录非常重要、关键的警告消息

四个级别根据使用阶段来选择,生产环境选择notice 或者warning

4.4.4.    logfile

日志文件名称。默认是  logfile stdout ,这种日志记录方式,默认为标准输出;如果配置 Redis 为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给 /dev/null。/dev/null,或称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功)。

4.4.5.    databases 16

设定库的数量 默认16,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

4.5.   ###SECURITY安全###

4.5.1.    设置密码

访问密码的查看、设置和取消

在命令中设置密码,只是临时的。重启redis服务器,密码就还原了。

永久设置,需要再配置文件中进行设置。

4.6.   #### LIMITS限制 ###

4.6.1.    maxclients

Ø  设置redis同时可以与多少个客户端进行连接。

Ø  默认情况下为10000个客户端。

Ø  如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。

4.6.2.    maxmemory

Ø  建议必须设置,否则,将内存占满,造成服务器宕机

Ø  设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。

Ø  如redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。

Ø  但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。

4.6.3.    maxmemory-policy

Ø  volatile-lru:使用LRU算法移除key,只对设置过期时间的键;(最近最少使用)

Ø  allkeys-lru:在所有集合key中,使用LRU算法移除key

Ø  volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键

Ø  allkeys-random:在所有集合key中,移除随机的key

Ø  volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key

Ø  noeviction:不进行移除。针对写操作,只是返回错误信息

4.6.4.    maxmemory-samples

Ø  设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。

Ø  一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。

4.7.   #### THREADED I/O限制 ###

4.7.1.    io-threads-do-reads 是否开启IO多线程

默认no,如果开启,修改为yes

4.7.2.    io-threads IO线程数量

官方建议:4 核的机器建议设置为 2 或 3 个线程,8 核的建议设置为 6 个线程,线程数一定要小于机器核数,尽量不超过8个

5.   Redis_Jedis_测试

Jedis是Redis官方推荐的Java连接开发工具。好比Java提供了JDBC来访问关系型数据库一样,Java中也可以使用Jedis来操作Redis。Jedis操作非常简单,因为Jedis的API 和redis的命令基本是相同的。

5.1.   Jedis所需要的依赖

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>

5.2.   连接Redis注意事项

禁用Linux的防火墙:Linux(CentOS7)里执行命令

systemctl stop/disable firewalld.service  

redis.conf中注释掉bind 127.0.0.1 ,然后 protected-mode no

5.3.   Jedis常用操作

5.3.1.    创建Maven工程

 

5.3.2.    创建测试程序

package com.zq.jedis;

import redis.clients.jedis.Jedis;

public class Demo01 {

public static void main(String[] args) {

Jedis jedis = new Jedis("192.168.137.3",6379);

String pong = jedis.ping();

System.out.println("连接成功:"+pong);

jedis.close();

}

}

public class TestJedis3 {

    public static void main(String[] args) {

        //指定Jedis连接池的配置信息

        JedisPoolConfig poolConfig = new JedisPoolConfig();

        poolConfig.setMaxTotal(200);

        poolConfig.setMaxIdle(32);

        poolConfig.setMaxWaitMillis(100*1000);

        poolConfig.setBlockWhenExhausted(true);

        poolConfig.setTestOnBorrow(true);  // ping  PONG

  

        //根据配置创建Jedis连接池

        JedisPool jedisPool = 
new JedisPool(poolConfig, "192.168.80.128", 6379, 60000 );

        Jedis jedis = jedisPool.getResource();

        String result = jedis.ping();

        System.out.println(result);

    }

}

5.4.   测试相关数据类型

5.4.1.    Jedis-API:    Key

jedis.set("k1", "v1");

jedis.set("k2", "v2");

jedis.set("k3", "v3");

Set<String> keys = jedis.keys("*");

System.out.println(keys.size());

for (String key : keys) {

System.out.println(key);

}

System.out.println(jedis.exists("k1"));

System.out.println(jedis.ttl("k1"));               

System.out.println(jedis.get("k1"));

5.4.2.    Jedis-API:    String

jedis.mset("str1","v1","str2","v2","str3","v3");

System.out.println(jedis.mget("str1","str2","str3"));

5.4.3.    Jedis-API:    List

List<String> list = jedis.lrange("mylist",0,-1);

for (String element : list) {

System.out.println(element);

}

5.4.4.    Jedis-API:    set

jedis.sadd("orders", "order01");

jedis.sadd("orders", "order02");

jedis.sadd("orders", "order03");

jedis.sadd("orders", "order04");

Set<String> smembers = jedis.smembers("orders");

for (String order : smembers) {

System.out.println(order);

}

jedis.srem("orders", "order02");

5.4.5.    Jedis-API:    hash

jedis.hset("hash1","userName","lisi");

System.out.println(jedis.hget("hash1","userName"));

Map<String,String> map = new HashMap<String,String>();

map.put("telphone","13810169999");

map.put("address","zq");

map.put("email","abc@163.com");

jedis.hmset("hash2",map);

List<String> result = jedis.hmget("hash2", "telphone","email");

for (String element : result) {

System.out.println(element);

}

5.4.6.    Jedis-API:    zset

jedis.zadd("zset01", 100d, "z3");

jedis.zadd("zset01", 90d, "l4");

jedis.zadd("zset01", 80d, "w5");

jedis.zadd("zset01", 70d, "z6");

 Set<String> zrange = jedis.zrange("zset01", 0, -1);

for (String e : zrange) {

System.out.println(e);

}

 

 

作业:Redis_Jedis_实例

完成一个手机验证码功能

要求:

1、输入手机号,点击发送后随机生成6位数字码,2分钟有效

2、输入验证码,点击验证,返回成功或失败

3、每个手机号每天只能输入3次

 

 

public class PhoneCode {

    public static void main(String[] args) {

          //1、输入手机号

        Scanner input = new Scanner(System.in);

        System.out.println("请输入手机号码:");

        String phone = input.next();

        //2.点击发送后随机生成6位数字码,2分钟有效

        System.out.println("输入1完成发送:");

        input.next();

        String code = sendPhone(phone);

        if("-1".equals(code)){

            System.out.println("今天输入验证码已经超过3次,请明天再试");

            return;

        }

        System.out.println("验证码是:"+code);

        //3、输入验证码,点击验证,返回成功或失败

        System.out.println("请输入验证码:");

        String myCode = input.next();

        System.out.println("输入1进行验证");

        input.next();

        boolean flag = verifyCode(phone,myCode);

        //4.输出结果

        if(flag){

            System.out.println("成功");

        }else{

            System.out.println("失败");

        }

    }

    /**     * 进行验证     */

    private static boolean verifyCode(String phone, String myCode) {

        boolean flag = false;//默认失败

        Jedis jedis = new Jedis("192.168.20.200",6379);

        //1.获取保存在redis中的正确验证码

        String codeKey = "code:"+phone;

        String code = jedis.get(codeKey);

        //2.

        if(myCode.equals(code)){

            flag = true;

        }

        jedis.close();

        return flag;

    }

    /**

     * 发送手机号码,返回验证码

       */

    public static String sendPhone(String phone) {

        Jedis jedis = new Jedis("192.168.20.200",6379);

        //1.获取验证码

        String code = getCode();

        //2.将验证码保存在redis中,两分钟有效

        String codeKey = "code:"+phone;

        jedis.setex(codeKey,120,code);

        //3.每个手机号每天只能输入3次

        String countKey = "count:"+phone;

        String count = jedis.get(countKey);

        if(count==null){ //如果是第一次,设置次数为1次

            jedis.setex(countKey,24*60*60,"1");

        }else if(Integer.parseInt(count)<=2){ //如果是第二次或者第三次,设置次数+1

            jedis.incr(countKey);

        }else{//如果超过3次,给出次数超过的提示

            //code ="今天输入验证码已经超过3次,请明天再试";

            code="-1";

        }

        jedis.close();

        //4.返回结果(验证码或者超过3次提示)

        return code;

    }

    //生成6位数字验证码

    public static String getCode() {

        Random random = new Random();

        String code = "";

        for(int i=0;i<6;i++) {

            int rand = random.nextInt(10);

            code += rand;

        }

        return code;

    }

}

 

 

6.   Redis_事务_锁机制

6.1.   Redis的事务定义

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

6.2.   Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。

组队的过程中可以通过discard来放弃组队。 

案例:

组队成功,提交成功

组队阶段报错,提交失败

组队成功,提交有成功有失败情况

6.3.   事务的错误处理

1)     组队成功、执行exec成功,所有命令都成功

2)     组队成功、选择discard放弃,所有命令不会被执行

3)     组队中某个命令出现了报告错误,执行时整个队列中所有命令都会被取消。

4)     执行阶段某个命令报错,则只有报错命令不会被执行,而其他命令都会执行,不会回滚。(Redis事务没有原子性)

 

 

6.4.   为什么要做成事务

想想一个场景:有很多人有你的账户,同时去参加双十一抢购

6.5.   事务冲突的问题

6.5.1.    例子

账户总金额10000元,非信用卡。请求取款前先判断余额是否足够。多个并发请求,一个请求想给金额减8000,一个请求想给金额减5000,一个请求想给金额减1000。结果会出现余额负值的情况。

 

6.5.2.    悲观锁

 

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。

传统的关系型数据库里边就用到了很多这种锁机制,比如行锁表锁等,读锁写锁等,都是在做操作之前先上锁。

6.5.3.    乐观锁

 

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。

乐观锁适用于多读的应用类型,这样可以提高吞吐量Redis就是利用这种check-and-set机制实现事务的。

6.5.4.    WATCH key [key ...]

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

6.5.5.    unwatch

取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。

http://doc.redisfans.com/transaction/exec.html

6.6.   Redis事务三特性

Ø  单独的隔离操作

n  事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Ø  没有隔离级别的概念

n  队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

Ø  不保证原子性

n  事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

Redis事务和MySQL事务的区别

MySQL事务遵守严格的ACID特征,而Redis 设计更多的是追求简单与高性能,所以事务不会也没有必要受制于传统 ACID 的束缚。

²  Redis 具备了一定的原子性,但不支持回滚,严格意义上无法保证原子性。

²  Redis 不支持回滚,也就无法保证业务上的数据一致性。

²  Redis 具备隔离性,但是没有隔离级别。

²  Redis 通过一定策略可以保证持久性。Redis 是否具备持久化,取决于 Redis 的持久化模式比如AOF、RDB及其策略设置。

7.   Redis_事务_秒杀案例

7.1.   解决计数器和人员记录的事务操作

7.2.   Redis事务--秒杀并发模拟

使用工具ab(apache bench)模拟测试

CentOS6 默认安装

CentOS7需要手动安装

7.2.1.    联网:yum install httpd-tools

7.2.2.    无网络

(1) 进入cd  /run/media/root/CentOS 7 x86_64/Packages(路径跟centos6不同)

(2) 顺序安装

apr-1.4.8-3.el7.x86_64.rpm

apr-util-1.5.2-6.el7.x86_64.rpm

httpd-tools-2.4.6-67.el7.centos.x86_64.rpm 

7.2.3.    测试及结果

7.2.3.1. 通过ab测试

vim postfile 模拟表单提交参数,以&符号结尾;存放当前目录。

内容:prodid=0101&

ab -n 1000 -c 100 -k -p ~/postfile -T application/x-www-form-urlencoded http://192.168.2.115:8081/Seckill/doseckill

7.2.3.2. 超卖

 

 

7.3.   超卖问题

7.4.   利用乐观锁淘汰用户,解决超卖问题。

//增加乐观锁

jedis.watch(qtkey);

 

//3.判断库存

String qtkeystr = jedis.get(qtkey);

if(qtkeystr==null || "".equals(qtkeystr.trim())) {

System.out.println("未初始化库存");

jedis.close();

return false ;

}

 

int qt = Integer.parseInt(qtkeystr);

if(qt<=0) {

System.err.println("已经秒光");

jedis.close();

return false;

}

 

//增加事务

Transaction multi = jedis.multi();

 

//4.减少库存

//jedis.decr(qtkey);

multi.decr(qtkey);

 

//5.加人

//jedis.sadd(usrkey, uid);

multi.sadd(usrkey, uid);

 

//执行事务

List<Object> list = multi.exec();

 

//判断事务提交是否失败

if(list==null || list.size()==0) {

System.out.println("秒杀失败");

jedis.close();

return false;

}

System.err.println("秒杀成功");

jedis.close();

7.5.   继续增加并发测试

7.5.1.    连接有限制

ab -n 2000 -c 200 -k -p postfile -T 'application/x-www-form-urlencoded' http://192.168.140.1:8080/seckill/doseckill

增加-r参数,-r   Don't exit on socket receive errors.

ab -n 2000 -c 100 -r -p postfile -T 'application/x-www-form-urlencoded' http://192.168.140.1:8080/seckill/doseckill

7.5.2.    已经秒光,可是还有库存

设置库存大一些,进行测试 比如库存500

ab -n 2000 -c 100 -p postfile -T 'application/x-www-form-urlencoded' http://192.168.137.1:8080/seckill/doseckill

已经秒光,可是还有库存。原因,就是乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。

7.5.3.    连接超时,通过连接池解决

7.5.4.    连接池

节省每次连接redis服务带来的消耗,把连接好的实例反复利用。

通过参数管理连接的行为

代码见项目中

l  链接池参数

n  MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。

n  maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;

n  MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;

n  testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;

7.6.   解决库存遗留问题

7.6.1.    LUA脚本

Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言

很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。

https://www.w3cschool.cn/lua/

7.6.2.    LUA脚本在Redis中的优势

将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。

LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。

但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。

利用lua脚本淘汰用户,解决超卖问题。

redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题

 

7.7.   Redis_事务_秒杀案例_代码

7.7.1.    项目结构

7.7.2.    第一版:简单版

老师点10次,正常秒杀

同学一起点试一试,秒杀也是正常的。这是因为还达不到并发的效果。

使用工具ab模拟并发测试,会出现超卖情况。查看库存会出现负数

 

7.7.3.    第二版:加事务-乐观锁(解决超卖),但出现遗留库存和连接超时

7.7.4.    第三版:连接池解决超时问题

7.7.5.    第四版:解决库存依赖问题,LUA脚本

local userid=KEYS[1];

local prodid=KEYS[2];

local qtkey="sk:"..prodid..":qt";

local usersKey="sk:"..prodid.":usr';

local userExists=redis.call("sismember",usersKey,userid);

if tonumber(userExists)==1 then

  return 2;

end

local num= redis.call("get" ,qtkey);

if tonumber(num)<=0 then

  return 0;

else

  redis.call("decr",qtkey);

  redis.call("sadd",usersKey,userid);

end

return 1;

8.   Redis持久化之RDB

8.1.   总体介绍

官网介绍:http://www.redis.io

Redis 提供了2个不同形式的持久化方式。

l  RDB(Redis DataBase)

l  AOF(Append Of File)

8.2.   RDB(Redis DataBase)

8.2.1.    官网介绍

8.2.2.    是什么

可以在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。也可以手动完成持久化。

8.2.3.    备份是如何执行的

Redis会单独创建(fork)一个子进程来进行持久化,会将数据入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失

8.2.4.    Fork

l  Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux引入“写时复制技术(CopyOnWrite)

一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

目前有两大类存储快照,一种叫做即写即拷(copy-on-write)快照【别名:写时复制】,另一种叫做分割镜像快照;

  即写即拷快照是表现数据外观特征的“照片”。这种方式通常也被称为“元数据”拷贝,即所有的数据并没有被真正拷贝到另一个位置,只是指示数据实际所处位置的指针被拷贝。在使用这项技术的情况下,当已经有了快照时,如果有人试图改写原始的LUN上的数据,快照软件将首先将原始的数据块拷贝到一个新位置(专用于复制操作的存储资源池),然后再进行写操作。以后当你引用原始数据时,快照软件将指针映射到新位置,或者当你引用快照时将指针映射到老位置。     

  分割镜像快照引用镜像硬盘组上所有数据。每次应用运行时,都生成整个卷的快照,而不只是新数据或更新的数据。这种使离线访问数据成为可能,并且简化了恢复、复制或存档一块硬盘上的所有数据的过程。但是,这是个较慢的过程,而且每个快照需要占用更多的存储空间。  分割镜像快照也叫作原样复制,由于它是某一LUN或文件系统上的数据的物理拷贝,有的管理员称之为克隆、映像等。

 

8.2.5.    redis.conf中关于RDF核心的配置

8.2.5.1. 配置文件名称dbfilename

在redis.conf中配置文件名称,默认为dump.rdb

8.2.5.2. 配置文件位置 dir

rdb文件的保存路径,默认为Redis启动时命令行所在的目录下。也可以修改,比如dir "/myredis/"

 

8.2.5.3. Save

格式:save 秒钟 写操作次数

RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,默认是1分钟内改了1万次,或5分钟内改了100次,或60分钟内改了1次。

禁用:给save传入空字符串

8.2.6.    如何触发RDB快照;保持策略

8.2.6.1. 配置文件中默认的快照配置

8.2.6.2. 命令save VS bgsave

save :save时只管保存,其它不管,全部阻塞。手动保存。不建议。

bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。

可以通过lastsave 命令获取最后一次成功执行快照的时间

8.2.6.3. flushall命令

执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

 

8.2.7.    redis.conf中关于RDF更多的配置

8.2.7.1. stop-writes-on-bgsave-error

当Redis无法写入磁盘的话,直接关掉Redis的写操作。推荐yes.

8.2.7.2. rdbcompression 压缩文件

对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。

如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐yes.

8.2.7.3. rdbchecksum 检查完整性

在存储快照后,还可以让redis使用CRC64算法来进行数据校验,

但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。推荐yes.

8.2.8.    rdb的备份

先通过config get dir  查询rdb文件的目录

将*.rdb的文件拷贝到别的地方

rdb的恢复

u  关闭Redis

u  先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb

u  启动Redis, 备份数据会直接加载

8.2.9.    优势

l  适合大规模的数据恢复

l  对数据完整性和一致性要求不高更适合使用

节省磁盘空间

恢复速度快

8.2.10.           劣势

l  Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑

虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。

在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

8.2.11.           如何停止

动态停止RDB:redis-cli config set save ""#save后给空值,表示禁用保存策略

8.2.12.           小总结

 

9.   Redis持久化之AOF

9.1.   AOF(Append Only File)

9.1.1.    是什么

日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

9.1.2.    AOF默认不开启

在redis.conf中有选项appendonly no,含义是不开启AOF。可以设置为appendonly yes,代表开启AOF。

AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

可以在redis.conf中通过appendfilename配置文件名称,默认为 appendonly.aof。AOF文件的保存路径,同RDB的路径一致。

9.1.3.    AOF持久化流程

(1)客户端的请求写命令会被append追加到AOF缓冲区内;

(2)AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;

(3)AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;

(4)Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

9.1.4.    AOF同步频率设置

appendfsync always

始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好

appendfsync everysec

每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。

appendfsync no

redis不主动进行同步,把同步时机交给操作系统

9.1.5.    Rewrite压缩

1是什么:

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

2重写原理,如何实现重写

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。

no-appendfsync-on-rewrite:

如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)

      如果 no-appendfsync-on-rewrite=no,  还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)

触发机制,何时重写

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。

auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)

auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。

例如:文件达到70MB开始重写,降到50MB,下次什么时候开始重写?100MB

系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,

如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。

3、重写流程

(1)bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。

(2)主进程fork出子进程执行重写操作,保证主进程不会阻塞。

(3)子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。

(4)子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。2).主进程把aof_rewrite_buf中的数据写入到新的AOF文件。

(5)使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。

9.1.6.    AOF启动/修复/恢复

l  AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。

正常恢复

n  修改默认的appendonly no,改为yes

n  将有数据的aof文件复制一份保存到对应目录(查看目录:config get dir)

n  恢复:重启redis然后重新加载

 

异常恢复

n  修改默认的appendonly no,改为yes

n  如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof--fix appendonly.aof进行恢复

n  备份被写坏的AOF文件

n  恢复:重启redis,然后重新加载

9.1.7.    优势

n  备份机制更稳健,丢失数据概率更低。

n  可读的日志文本,通过操作AOF稳健,可以处理误操作。

9.1.8.    劣势

n  比起RDB占用更多的磁盘空间。

n  恢复备份速度要慢。

n  每次读写都同步的话,有一定的性能压力。

n  存在个别Bug,造成恢复不能。

9.1.9.      小总结

9.2.   总结(Which one)

9.2.1.    用哪个好

官方推荐两个都启用。

如果对数据不敏感,可以选单独用RDB。

不建议单独用 AOF,因为可能会出现Bug。

如果只是做纯内存缓存,可以都不用。

 

10.        Redis_主从复制

10.1.      是什么

主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

10.2.      能干嘛

l  读写分离,性能扩展

l  容灾快速恢复,提高可用性

10.3.      怎么玩:主从复制

拷贝多个redis.conf文件include(写绝对路径)

开启daemonize yes

Pid文件名字pidfile

指定端口port

Log文件名字

dump.rdb名字dbfilename

Appendonly 关掉或者换名字

10.3.1.           新建redis6379.conf,填写以下内容

include /myredis/redis.conf

pidfile /var/run/redis_6379.pid

port 6379

dbfilename dump6379.rdb

10.3.2.           新建redis6380.conf,填写以下内容

10.3.3.           新建redis6381.conf,填写以下内容

 

10.3.4.           启动三台redis服务器

10.3.5.           查看系统进程,看看三台服务器是否启动

10.3.6.           连接三台redis,查看三台主机运行情况

info replication

打印主从复制的相关信息

 

10.3.7.           配从(库)不配主(库)

slaveof  <ip><port>

成为某个实例的从服务器

1、在6380和6381上执行: slaveof 127.0.0.1 6379

2、在主机上写,在从机上可以读取数据

在从机上写数据报错

3、主机挂掉,重启就行,一切如初

4、从机重启需重设:slaveof 127.0.0.1 6379

可以将配置增加到文件中。永久生效。

10.4.      常用3招

10.4.1.           一主二仆

切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的k1,k2,k3是否也可以复制?

从机是否可以写?set可否?

主机shutdown后情况如何?从机是上位还是原地待命?

主机又回来了后,主机新增记录,从机还能否顺利复制?

其中一台从机down后情况如何?依照原有它能跟上大部队吗?

 

 

10.4.2.           薪火相传

上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。通过分封制,由之前的天子--百姓结构变成了天子--诸侯--卿大夫---百姓结构,减轻了天子的负担。

用 slaveof  <ip><port>

中途变更转向:会清除之前的数据,重新建立拷贝最新的

风险是一旦某个slave宕机,后面的slave都没法备份

主机挂了,从机还是从机,无法写数据了

关注点:master和slave双重身份的机器是否可以写数据呢??不可以

10.4.3.           反客为主

当一个master宕机后,后面的slave可以变为master主机,从而可以进行写操作。

手动用 slaveof  no one 将从机变为主机。

其他没有变为master的slave需要手动指定新master(重新拜大哥)

10.5.      复制原理

l  slave启动成功连接到master后会发送一个sync命令

l  Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步

l  全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

l  增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

l  但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

10.6.      哨兵模式(sentinel)

10.6.1.           是什么

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

反客为主缺点:小弟要手动当大哥;其他小弟要重新拜大哥;老大哥要手动变小弟。这三步能否自动完成。可以,设置一个或者多个哨兵,监视大哥的状态,一旦大哥挂掉,自动完成以上三步。

10.6.2.           怎么玩(使用步骤)

10.6.2.1.        调整为一主二仆模式,6379带着6380、6381

10.6.2.2.        自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错

10.6.2.3.        配置哨兵,填写内容

sentinel monitor mymaster 127.0.0.1 6379 1

其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。

10.6.2.4.        启动哨兵

/usr/local/bin

redis做压测可以用自带的redis-benchmark工具

执行redis-sentinel  /myredis/sentinel.conf

10.6.2.5.        当主机挂掉,从机选举中产生新的主机

(大概10秒左右可以看到哨兵窗口日志,切换了新的主机)

哪个从机会被选举为主机呢?根据优先级别:replica-priority

原主机重启后会变为从机。

10.6.2.6.        复制延时

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

10.6.3.           故障恢复

优先级在redis.conf中默认:replica-priority 100,值越小优先级越高

偏移量是指获得原主机数据最全的

每个redis实例启动后都会随机生成一个40位的runid(通过info server获取查看)

10.6.4.           主从复制

private static JedisSentinelPool jedisSentinelPool=null;
public static  Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
            Set<String> sentinelSet=new HashSet<>();
            sentinelSet.add("192.168.11.103:26379");
            JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(10); //最大可用连接数
            jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
            jedisPoolConfig.setMinIdle(5); //最小闲置连接数
            jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
            jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
           jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
            jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
            return jedisSentinelPool.getResource();
        }else{
            return jedisSentinelPool.getResource();
        }
}

 

11.        Redis集群

11.1.      问题

一个Redis主机缓存容量不够,如何进行扩容?

更多的客户端对一个Redis主机进行并发写操作,忙不过来了, Redis如何分摊?

另外,主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。

之前通过代理主机来解决,Redis3.0开始提供了解决方案,就是无中心化集群配置。

11.2.      什么是集群

Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。

Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。

11.3.      删除持久化数据

将rdb,aof文件都删除掉。

11.4.      制作6个实例,6379,6380,6381,6389,6390,6391

11.4.1.           配置基本信息

开启daemonize yes

pid文件名字

指定端口

Log文件名字

dump.rdb名字

appendonly 关掉或者换名字

11.4.2.           redis cluster配置修改

cluster-enabled yes    打开集群模式

cluster-config-file nodes-6379.conf  设定节点配置文件名

cluster-node-timeout 15000   设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。

include /etc/redis.conf

pidfile "/var/run/redis_6379.pid"

port 6379

dbfilename "dump6379.rdb"

cluster-enabled yes

cluster-config-file nodes-6379.conf

cluster-node-timeout 15000

 

11.4.3.           修改好redis6379.conf文件,拷贝多个redis.conf文件

 

11.4.4.           使用查找替换修改另外5个文件

例如::%s/6379/6380 

11.4.5.           启动6个redis服务

11.5.      将六个节点合成一个集群

组合之前,请确保所有redis实例启动后,nodes-xxxx.conf文件都生成正常。

l  合体:

cd  /opt/redis-6.2.1/src

 

redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391

此处不要用127.0.0.1, 请用真实IP地址

--replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组。

l  普通方式登录

可能直接进入读主机,存储数据时,会出现MOVED重定向操作。所以,应该以集群方式登录。

11.6.      -c 采用集群策略连接,设置数据会自动切换到相应的写主机

11.7.      通过 cluster nodes 命令查看集群信息

11.8.      redis cluster 如何分配这六个节点?

一个集群至少要有三个主节点

选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。

分配原则尽量保证每个主数据库运行在不同IP地址,每个从库主库不在一个IP地址。

11.9.      什么是slots

[OK] All nodes agree about slots configuration.

>>> Check for open slots...

>>> Check slots coverage...

[OK] All 16384 slots covered.

一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,

集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

集群中每个节点负责处理一部分插槽。 例如, 如果一个集群可以有主节点, 其中:

节点 A 负责处理 0 号至 5460 号插槽。

节点 B 负责处理 5461 号至 10922 号插槽。

节点 C 负责处理 10923 号至 16383 号插槽。

11.10. 在集群中录入值

在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。

redis-cli客户端提供了 –c 参数实现自动重定向

redis-cli  -c –p 6379 登入后,再录入、查询键值对可以自动重定向。

不在一个slot下的键值,是不能使用mget,mset等多键操作

可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。

11.11. 查询集群中的值

CLUSTER GETKEYSINSLOT <slot><count> 返回 count 个 slot 槽中的键。

11.12. 故障恢复

如果主节点下线?从节点能否自动升为主节点?注意:15秒超时

主节点恢复后,主从关系会如何?主节点回来变成从机。

如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,插槽数据全都不能使用,也无法存储。

redis.conf中的参数  cluster-require-full-coverage

11.13. 集群的Jedis开发

即使连接的不是主机,集群会自动切换主机存储。主机写,从机读。

无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据。

public class JedisClusterTest {

  public static void main(String[] args) { 

     Set<HostAndPort>set =new HashSet<HostAndPort>();

     set.add(new HostAndPort("192.168.31.211",6379));

     JedisCluster jedisCluster=new JedisCluster(set);

     jedisCluster.set("k1", "v1");

     System.out.println(jedisCluster.get("k1"));

  }

}

11.14. Redis 集群提供了以下好处

实现扩容

分摊压力

无中心配置相对简单

11.15. Redis 集群的不足

多键操作是不被支持的

多键的Redis事务是不被支持的。lua脚本不被支持

由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

动物装饰