1. 序论
redis是一种非关系型数据库。
1.1 NoSQL
我们通常使用的MySQL和Oracle是关系型数据库。非关系型数据库的背景是:随着互联网发展以及海量数据处理的需求,数据格式和类型的不固定与不规则,使得关系型数据库在应对这些场景并不方便。在思考数据存储模型时,出现了非关系型数据库。
1.2 Redis
1.2.1 简介
概念
redis是一种基于键值存储的非关系型内存数据库(底层C编写的)。key都是字符串的,value字符串(String),哈希(map),列表(list),集合(set)和有序集合(sorted set)等7种类型。
特点
redis足够简单和稳定
支持丰富的数据结构
内存存储读写性能优秀
提供持久化功能
支持部分的事务操作(例外:在运行时才知道命令错误)
1.2.2 redis安装与使用
windows
在 https://github.com/tporadowski/redis/releases 下载对应的压缩包,解压后即可。解压后在cmd里通过cd语句进入到redis目录下,执行:
redis-server.exe redis.windows.conf
因为redis实际上是一个服务器,所以要先打开服务器。出现下图所示的界面,表示redis服务器已经打开了,期间不能关闭redis服务器。(与MySQL类似)
在打开redis服务器之后,再另外开启一个cmd用作客户端,进入redis目录下输入:
F:\redis>redis-cli.exe -h 127.0.0.1 -p 6379
表示开启一个redis客户端,连接上主机号和端口号。这时界面变成:
表示进入到redis客户端模式下。若想关闭redis,即可先关闭客户端exit,然后shutdown服务器。
redis中很重要的配置文件是redis.conf(window下是redis.window.conf),里面规定了一些redis的基本配置信息,比如默认端口号,数据库数量(16)等。
linux
- 下载
wget http://219.238.7.66/files/502600000A29C8D5/download.redis.io/releases/redis-3.2.9.tar.gz
或者
在linux使用rz上传本地的包 - 安装
解压:tar -zxvf redis-4.0.1.tar.gz -C /usr/local/
切换目录: cd redis-4.0.1,执行命令:make
1.2.3 基础命令
redis默认使用0号库,可以用select db可以切换库。
删除所有库的数据:flushall
删除当前库的数据:flushdb
获得redis所有配置值:config get *
查看当前数据库key的数目:dbsize
查看redis服务器配置信息:info
1.2.4 redis图形化客户端
使用redis desktop manager,在连接好地址后,可以查看数据库信息。
2. 数据操作
redis是以键值存储的,所以命令分为两部分,一类是key,一类是value。
2.1 操作key命令
keys * :列出所有的key
exists key:检查某个key是否存在
move key db:将当前库的key移动到指定的库中
expire key seconds:设置key的值的过期时间
ttl key:查看key还有多少秒过期,-1是永不过期,-2是已过期或key不存在
type key:查看key所存储的值的类型
del key:删除key
2.2 操作value命令
key永远都是String。
2.2.1 String
将字符串value设置到key中 (引号不做强求)
set k1 k1_123456
set k2 "k2_abcdefg"
获取指定key的value值
127.0.0.1:6379> get k1
"k1_123456"
127.0.0.1:6379> get k2
"k2_abcdefg"
将key中存储的数字值加1,如果key不存在,则key的值被初始化为0再执行INCR操作(value只能是数字)
127.0.0.1:6379> incr k3
(integer) 1
将key中存储的数字值减1,如果key不存在,则key的值被初始化为0再执行DECR操作(value只能是数字)
decr k3
setnx,如果key不存在,则设置key的值,存在则不设置
setnx k3
设置key的值为value,同时返回key的旧值
127.0.0.1:6379> getset k5 k5_123123
(nil)
127.0.0.1:6379> get k5
"k5_123123"
2.2.2 hash
往hash表中key放入数据,这个数据是键值对类型
127.0.0.1:6379> hset k6 id 100 name "zhangsan" age 20
(integer) 3
往hash表中key获取数据,分别取每个字段
127.0.0.1:6379> hget k6 id
"100"
127.0.0.1:6379> hget k6 name
"zhangsan"
127.0.0.1:6379> hget k6 age
"20"
往hash表中key获取所有数据
127.0.0.1:6379> hgetall k6
1) "id"
2) "100"
3) "name"
4) "zhangsan"
5) "age"
6) "20"
hmset ,hmget 一次获取多个值
127.0.0.1:6379> hmset k7 id 200 name "lisi" age 30
OK
127.0.0.1:6379> hmget k7 id name age
1) "200"
2) "lisi"
3) "30"
删除一个或多个字段
127.0.0.1:6379> hdel k7 age
(integer) 1
127.0.0.1:6379> hgetall k7
1) "id"
2) "200"
3) "name"
4) "lisi"
2.2.3 List
lpush:将一个或多个value插入到列表key的表头(最左边)
rpush:将一个或多个value插入到列表key的表尾(最右边)
127.0.0.1:6379> lpush k1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> rpush k1 6 7 8
(integer) 8
获取元素,0表示第一个元素,1表示第二个,-1表示最后一个,-2表示倒数第二个
127.0.0.1:6379> lrange k1 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
6) "6"
7) "7"
8) "8"
lpop:从左边获取列表key的第一个元素,并移除
rpop:从右边获取列表key的第一个元素,并移除
127.0.0.1:6379> lpop k1
"5"
127.0.0.1:6379> rpop k1
"8"
127.0.0.1:6379> lrange k1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "6"
6) "7"
获取列表key指定下标的元素
127.0.0.1:6379> lindex k1 3
"1"
获取列表key的长度
127.0.0.1:6379> llen k1
(integer) 6
2.2.4 set
sadd将一个或多个member加入到集合key中,已经存在集合的member元素不会加入成功,并通过smember列出所有集合元素
127.0.0.1:6379> sadd k1 "beijing" "shanghai" "xian" "hangzhou"
(integer) 4
127.0.0.1:6379> smembers k1
1) "hangzhou"
2) "shanghai"
3) "beijing"
4) "xian"
判断集合key中是否包含某一member
127.0.0.1:6379> sismember k1 "xian"
(integer) 1
127.0.0.1:6379> sismember k1 "nanjing"
(integer) 0
获取集合key中的元素个数
127.0.0.1:6379> scard k1
(integer) 4
删除集合key中一个或多个元素
127.0.0.1:6379> srem k1 xian
(integer) 1
127.0.0.1:6379> smembers k1
1) "hangzhou"
2) "shanghai"
3) "beijing"
127.0.0.1:6379
2.2.5 zset
zadd将一个或多个member以及它所对应的score值加入到集合key中,已经存在集合的member元素不会加入成功
127.0.0.1:6379> zadd k2 100 "hello" 90 "world" 80 "gogo"
(integer) 3
127.0.0.1:6379> type k2
zset
获取有序集合key中,指定区间内的成员,按score值从小到大排序
127.0.0.1:6379> zrange k2 0 -1
1) "gogo"
2) "world"
3) "hello"
获取有序集合key中,指定区间内的成员,按score值从大到小排序
127.0.0.1:6379> ZREVRANGE k2 0 -1
1) "hello"
2) "world"
3) "gogo"
获取有序集合key中,成员member的排名,有序成员按照score从小到大排序
127.0.0.1:6379> zrank k2 hello
(integer) 2
获取有序集合key中,成员member的排名,有序成员按照score从大到小排序
127.0.0.1:6379> zrevrank k2 gogo
(integer) 2
删除有序集合key中一个或者多个成员
127.0.0.1:6379> zrem k2 temp
(integer) 1
获取有序集合key中元素成员的个数
127.0.0.1:6379> zcard k2
(integer) 3
3. redis编程
3.1 Java中使用redis
先创建一个maven项目,然后引入jedis依赖包。具体操作数据的方法的使用和操作redis命令类似。
基础代码:
package test01;
import redis.clients.jedis.Jedis;
import java.util.*;
public class Redis01 {
public static void main(String[] args) throws InterruptedException {
//****************基本操作******************
//通过ip和端口号连接redis,若有密码再通过auth方法传入密码
Jedis jedis = new Jedis("127.0.0.1",6379);
//检测redis是否连接正常
System.out.println("ping一下redis服务:"+jedis.ping());
//选择0号库
jedis.select(0);
//设置键值对k3,v1 都是字符串类型
jedis.set("k1","啦啦啦德玛西亚");
jedis.set("k2","断剑重铸之日,骑士归来之时");
//获取k3的value值
String v1 = jedis.get("k1");
System.out.println("k1的值:"+v1);
//****************key相关操作******************
//列出所有的key
Set<String> set = jedis.keys("*");
Iterator<String> it = set.iterator();
while (it.hasNext()){
System.out.print(it.next()+" ");
}
//检查某个key是否存在
boolean flag = jedis.exists("k1");
System.out.println("是否存在k1:"+flag);
//移动k1到3号库
long i = jedis.move("k1",3);
System.out.println("移动k1到3号库:"+i);
//设置key的过期时间
String statusCode = jedis.set("k3", "temp");
System.out.println(statusCode);
jedis.expire("k3",5);
Thread.sleep(6000);
System.out.println(jedis.get("k3"));
//查看key的过期时间
long ttl = jedis.ttl("k1");
System.out.println("k1的过期时间:"+ttl);
//查看key值的数据类型
String type = jedis.type("k1");
System.out.println(type);
//删除key
long del = jedis.del("k2");
System.out.println(jedis.exists("k2"));
//****************value相关操作******************
//String
jedis.select(1);
jedis.set("k4","123");
jedis.incr("k4");
System.out.println(jedis.get("k4"));
//hash
jedis.hset("k5","001","001_apple");
jedis.hset("k5","002","002_apple");
System.out.println("判断key003是否存在:"+jedis.hexists("k5", "001"));
System.out.println("获取key004对应的值:"+jedis.hget("k5", "002"));
//list
jedis.lpush("k6","Runoob");
jedis.lpush("k6", "Google");
jedis.rpush("k6", "Taobao");
List<String> list = jedis.lrange("k6",0,-1);
for(int j=0;j<list.size();j++){
System.out.println("list :"+list.get(j));
}
//关闭redis连接
jedis.close();
}
}
结果:
ping一下redis服务:PONG
k1的值:啦啦啦德玛西亚
k1 k2 是否存在k1:true
移动k1到3号库:1
OK
null
k1的过期时间:-2
none
false
124
判断key003是否存在:true
获取key004对应的值:002_apple
list :Google
list :Runoob
list :Taobao
客户端界面:
3.2 Java中使用redis连接池
首先使用单例模式创建一个redis连接池。
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisPoolInstance {
private static final String HOST = "127.0.0.1";
private static final int PORT = 6379;
private static JedisPool jedisPool = null;
private JedisPoolInstance(){}
public static JedisPool getJedisPoolInstance(){
if(jedisPool == null){
synchronized (JedisPoolInstance.class){
if(jedisPool == null){
//对连接池的参数进行配置,根据项目的实际情况配置这些参数
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(1000);//最大连接数
poolConfig.setMaxIdle(32);//最大空闲连接数
poolConfig.setMaxWaitMillis(90*1000);//获取连接时的最大等待毫秒数
poolConfig.setTestOnBorrow(true);//在获取连接的时候检查连接有效性
jedisPool = new JedisPool(poolConfig, HOST, PORT);
}
}
}
return jedisPool;
}
}
然后通过连接池来获取redis。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class TestPool {
public static void main(String[] args) {
//获取连接池对象
JedisPool jedisPool = JedisPoolInstance.getJedisPoolInstance();
JedisPool jedisPool2 = JedisPoolInstance.getJedisPoolInstance();
System.out.println("连接池对象1:" + jedisPool);
System.out.println("连接池对象2:" + jedisPool2);
Jedis jedis1 = null;
Jedis jedis2 = null;
try {
//从jedis连接池中获取两个jedis对象
jedis1 = jedisPool.getResource();
jedis2 = jedisPool.getResource();
System.out.println("具体连接对象1:" + jedis1);
System.out.println("具体连接对象2:" + jedis2);
jedis1.set("k1","v1");
String str1 = jedis1.get("k1");
System.out.println(str1);
jedis2.set("k2","v2");
String str2 = jedis2.get("k2");
System.out.println(str2);
} catch (Exception e) {
e.printStackTrace();
} finally {
//在jedis未关闭前,看一下jedis连接池中活跃的连接对象有几个
System.out.println("在jedis未关闭前,连接池中活跃的连接对象个数:" + jedisPool.getNumActive());
//在jedis未关闭前,看一下jedis连接池中空闲的连接对象有几个
System.out.println("在jedis未关闭前,连接池中空闲的连接对象个数:" + jedisPool.getNumIdle());
if (null != jedis1) {
//将这个Jedis实例归还给JedisPool
jedis1.close();
}
if (null != jedis2) {
//将这个Jedis实例归还给JedisPool
jedis2.close();
}
//将jedis连接归还给JedisPool连接池之后,活跃的连接有几个
System.out.println("在jedis关闭后,连接池中活跃的连接对象个数:" + jedisPool.getNumActive());
//将jedis连接归还给JedisPool连接池之后,空闲的连接有几个
System.out.println("在jedis关闭后,连接池中空闲的连接对象个数:" + jedisPool.getNumIdle());
}
}
}
4. 消息队列
这里简单了解一下消息队列以及搭配redis的使用方法。
4.1 概念
消息队列是实现异构系统之间数据通信的一种方式,A系统将消息发送到消息队列服务器上,B系统从消息队列服务器上接收消息,Java领域常用的消息队列有:ActiveMQ,RabbitMQ。
**核心三要素:**生产者,消费者和消息队列。
用途:系统解耦,异步处理并且不需实时返回结果的场景。
消息队列有两种模式:
- 点对点:可能有多个接收端,A发送消息到消息队列,只能有一个接收端接收消息
- 发布与订阅:A发送消息,多个接收端都可以接收消息,并且只有所有接收端都收到消息之后,才能删除消息。
4.2 发布与订阅
发布与订阅是一种消息通信模式:发送者发送消息,订阅者接收消息。也称生产者消费者模式,是实现消息队列方式的一种。
使用redis命令行实现:
3. 开启3个redis客户端,其中2个作为订阅者,1个作为消息发布者。
2. 让2个订阅者订阅某个频道。(如果是订阅匹配模式的频道主题:psubscribe chan* 表示匹配以chan开头的频道主题)
3. 让1个发布者向频道发送消息。
4. 观察通信情况。
订阅者:
第一个客户端:SUBSCRIBE channel01
第二个客户端:SUBSCRIBE channel01
发布者:
第三个客户端:
127.0.0.1:6379> publish channel01 "hello world!...."
(integer) 2
127.0.0.1:6379>
结果:
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel01"
3) (integer) 1
1) "message"
2) "channel01"
3) "hello world!...."
使用java编程实现:
/**
* redis消息订阅者
*
* JedisPubSub类是Jedis定义的一个抽象类,在这个类中定义publish/subsribe的回调方法。
* 通过继承JedisPubSub类并重新实现这些回调方法,当publish/subsribe事件发生时,我们可以定制自己的处理逻辑。
*
*/
public class RedisSubscriber extends JedisPubSub {
/**
* 当接收到消息后触发该方法
*/
@Override
public void onMessage(String channel, String message) {
System.out.println("频道[" + channel + "]发布了一条消息[" + message + "]");
}
/**
* main方法运行,接收消息
*
* @param args
*/
public static void main(String[] args) {
//连接到redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
//创建一个JedisPubSub对象
RedisSubscriber redisSubscriber = new RedisSubscriber();
//从redis消息频道订阅消息
jedis.subscribe(redisSubscriber, "cctv");
}
}
/**
* redis消息发布者
*
*/
public class RedisPublisher {
public static void main(String[] args) {
//连接到redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
//向redis消息频道发布一条消息
jedis.publish("cctv", "欢迎学习Redis");
jedis.close();
}
}
这样就能实现两个项目之间的消息通信。
5. redis事务
Redis中的事务(transaction)是一组命令的集合,一个或两个或两个以上的命令,redis事务保证这些命令被执行时中间不会被任何其他操作打断。
5.1 事务
事务就是一组操作,要么全部执行成功,要么全部失败回滚。
关于MySQL事务可以看我的这篇文章:
https://blog.csdn.net/one23_man_show/article/details/106984481
redis事务实现
正常情况
用MULTI命令告诉Redis,接下来要执行的命令你先不要执行,而是把它们暂时存起来(开启事务)
执行set k1 v1 ,第一条命令进入等待队列
执行set k2 v2 ,第二条命令进入等待队列
EXEC ,告知redis执行前面发送的两条命令(提交事务)。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> exec
1) OK
过程有点类似MySQL手动提交事务,也是先begin一个事务,然后执行操作逻辑,最后要么失败rollback,要么成功commit。
异常情况
因为第二条语句语法本身就错了,所以所有等待队列里的操作全部回滚。
例外情况
里面的单条语句本身没有语法错误,只是在最后提交的时候发现第二条语句没法执行,因为redis的事务特性不是那么强,所以除了该条错误的语句之外,其他正确的语句依然能够执行。
例外情况
放弃所有队列中的命令。
复杂情况
乐观锁
监视一个(或多个)key,如果在事务exec执行之前这个(或这些)key被其他命令所改动,那么事务将被打断。
127.0.0.1:6379> set k1 10
OK
127.0.0.1:6379> WATCH k1
OK
127.0.0.1:6379> get k1
"10"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k2 hahaha
QUEUED
127.0.0.1:6379> set k1 12
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get k1
"20"
在watch k1 和 exec之间另外一个线程修改了k1的值
127.0.0.1:6379> set k1 20
OK
所以最后k1结果是20,因为事务1执行k1的时候k1已经被修改了,和watch的时候不一致了,所以不能set k1
悲观锁
redis悲观锁简单实现就是事务。
一个客户端:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k3 123
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get k3
"123"
另一个客户端:
在第一个客户端的事务过程中加入:
127.0.0.1:6379> set k3 000
OK
最后结果:
127.0.0.1:6379> get k3
"123"
上述redis命令也可以转换为java编程。。。
6. redis持久化
Redis的数据存储在内存中,由于内存是瞬时的,如果linux宕机或重启,又或者Redis崩溃或重启,所有的内存数据都会丢失。为解决这个问题,Redis提供两种机制对数据进行持久化存储,将数据保存在磁盘上,便于发生故障后能迅速恢复数据。
6.1 RDB
Redis Database(RDB),就是在指定的时间间隔内将内存中的所有数据的快照写入磁盘,保存为一个后缀为.rdb的文件,数据恢复时将磁盘上的快照文件直接再读到内存。Redis默认已经开启RDB方式的持久化。RDB方式的数据持久化,仅需在redis.conf文件中配置即可实现:
- 配置文件redis.conf中搜索 SNAPSHOTTING
- 配置格式:save 例如:save 300 10
表示每300s,数据库发送改变10次,那么就持久化一次。 - dbfilename:设置RDB的文件名,默认文件名为dump.rdb
- dir:指定RDB和AOF文件的目录
Redis在将内存中的数据写入到磁盘的快照文件中时,将当前进程fork分支出一个子进程,然后在子进程中循环扫描所有的内存数据,并且将内存数据先写到一个一个临时文件中,然后再将临时文件重命名为rdb文件,覆盖掉原来的rdb文件。
- 优点:由于存储的是数据快照文件,直接将快照文件里的数据再放入内存中就可以了,恢复数据很方便,也比较快。
- 缺点:会丢失最后一次快照以后更改的数据。而且,因为需要经常操作磁盘,RDB会经常fork出一个子进程。如果redis数据库很大,fork会占用更多的时间,甚至可能会影响redis暂定服务一段时间。
6.2 AOF
Append-only File(AOF),Redis每次接收到一条改变数据的命令时,它将把该命令写到一个AOF文件中(只记录写操作,读操作不记录),当Redis重启时,它通过执行AOF文件中所有的命令来恢复数据。(非默认模式)AOF方式的数据持久化,仅需在redis.conf文件中配置即可实现:
- appendonly:默认是no,改成yes即开启了aof持久化
- appendfilename:指定AOF文件名,默认文件名为appendonly.aof
- dir:指定AOF和RDB文件的目录
- appendfsync:配置向aof文件写命令数据的策略
- no:不主动进行同步操作,而是完全交由操作系统来做(即每30秒一次),比较快但不是很安全
- always:每次执行写入都会执行同步,慢一些但是比较安全
- everysec:每秒执行一次同步操作,比较平衡,介于速度和安全之间
- auto-aof-rewrite-percentage:当目前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,如果之前没有重写,则以启动时的aof文件大小为依据。(aof文件的重写就是对文件内容的整理,将一些命令进行优化,从而可以让文件体积变小,比如 set k1 v1, 然后又set k1 v2,那么重写后就只会留下set k1 v2,前一个set k1
v1会被删除,因为没有作用。) - auto-aof-rewrite-min-size:允许重写的最小AOF文件大小
append-only 持久化方式是另一个可以提供完全数据保障的方案;对于一般性的业务需求,建议使用RDB的方式进行持久化,原因是RDB的开销比AOF方式要低很多,对于那些无法忍数据丢失的应用,才使用AOF方式;Redis重启后通过AOF文件恢复数据,由于需要逐个执行AOF文件中的命令来将硬盘中的数据载入到内存中,所以载入的速度相较RDB会慢一些;当然可以同时使用这两种方式,redis重启时默认优先加载aof文件。
7. redis集群
7.1 主从复制
概念:
为了避免单点故障,我们需要将数据复制多份部署在多台不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。所以需要实现一台服务器负责读数据,并在数据更新之后,自动保存在从服务器上。
Redis提供了复制(replication)功能来自动实现多台redis服务器的数据同步。我们可以通过部署多台redis,并在配置文件中指定这几台redis之间的主从关系,主负责写入数据,同时把写入的数据实时同步到从机器,这种模式叫做主从复制,即master/slave,并且redis默认master用于写,slave用于读,向slave写数据会导致错误;实现Redis的主从复制,只需要修改Redis的主配置文件redis.conf即可。
具体操作:
比如,我们要创建1台主服务,2台从服务。那么先要另外创建3个redis的配置文件:
touch redis 6380.conf
:主服务redis配置文件
touch redis 6381.conf
:从服务1redis配置文件
touch redis 6382.conf
:从服务2redis配置文件
然后分别配置这几个文件
vim redis 6380.conf
include /usr/local/redis-4.0.1/redis.conf
port 6380
pidfile /var/run/redis_6380.pid
logfile /var/run/6380.log
dir /var/run/
dbfilename dump6380.rdb
第一行是导入原始redis配置文件,相当于已经拥有原始redis配置文件所有的配置信息。为了避免冲突,修改端口号,pid文件,log文件和持久化目录和文件名。
vim redis 6381.conf
include /usr/local/redis-4.0.1/redis.conf
port 6381
pidfile /var/run/redis_6381.pid
logfile /var/run/6381.log
dir /var/run/
dbfilename dump6381.rdb
slaveof 127.0.0.1 6380
其余与主配置文件一样,只是多了最后一行,表示把当前的redis服务配置为6380的从服务。另外一台从服务也是类似配置。
至此主从配置完毕,然后启动三台服务,注意用./redis-cli -p 6380指定端口号,否则进入默认端口里。然后往master里写数据,slave可以get到数据。可以用info replication 查看redis服务器所处角色。
容灾处理:
当master发生故障,需手动将其中一台slave使用slaveof no one命令提升为master,其它slave执行slaveof命令指向这个新的master,从而构成新的主从关系。
也就是说抛弃原有的发生故障的master,然后从众多的slave中选择一个提升为新的master,修改其他slave的从属关系,使它们指向新的master。手动操作并不智能,所以需要哨兵。
127.0.0.1:6381> slaveof no one
127.0.0.1:6382> slaveof 127.0.0.1:6381
7.2 Sentinel哨兵
Sentinel哨兵是Redis官方提供的高可用方案,使用Sentinel哨兵可以监控多个Redis服务实例的运行情况。
7.2.1 基本原理
Sentinel哨兵用来监视Redis的主从服务器,它会不断检查Master和Slave是否正常;如果Sentinel自身故障了,就无法监控,所以需要多个哨兵,组成Sentinel网络,监控同一个Master的各个Sentinel哨兵会相互通信,组成一个分布式的Sentinel哨兵网络,互相交换彼此关于被监控redis服务器的运行情况;当一个Sentinel哨兵认为被监控的redis服务器出现故障时,它会向网络中的其它Sentinel哨兵进行确认,判断该服务器是否真的已故障;如果故障的redis服务器为主服务器,那么Sentinel哨兵网络将对故障的主redis服务器进行自动故障转移,通过将故障的主redis服务器下的某个从服务器提升为新的主服务器,并让其它从服务器转移到新的主服务器下,以此来让系统重新回到正常状态;待出现故障的旧主服务器重新启动上线时,Sentinel哨兵会让它变成一个从redis服务器,并挂到新的主redis服务器下;所以哨兵是自动实现故障转移,不需要人工干预,是高可用的一种集群方案。
7.2.2 实现
首先,依然要先搭建好主从服务器。然后:
- 在redis主目录下复制三份sentinel.conf文件(3个哨兵):sentinel26380.conf;sentinel26381.conf;sentinel26382.conf。这个sentinel是redis安装目录下的sentinel.conf配置文件。
- 三份sentinel配置文件修改(两处):
1、修改 port 26380、 port 26381、 port 26382 (Sentinel默认端口号为26379)
2、修改 sentinel monitor mymaster 127.0.0.1 6380 2
格式:Sentinel monitor <Quorum投票数>
Sentinel会根据Master的配置自动发现Master的Slave - 启动Sentinel
redis安装时make编译后就产生了redis-sentinel的可执行程序文件,可以在一个redis中运行多个sentinel进程
启动三个Sentinel哨兵进程,将创建三个监视主服务器的Sentinel实例,执行如下命令:
./redis-sentinel …/sentinel26380.conf
./redis-sentinel …/sentinel26381.conf
./redis-sentinel …/sentinel26382.conf
8. redis安全
8.1 命令禁止或者重命名
禁用或重命名一些可能会对数据库造成严重影响的命令,以防止别人的篡改。在redis.conf文件中进行命令禁止或重命名配置。
- rename-command FLUSHALL xxx
#重命名FLUSHALL命令。注意:对于FLUSHALL命令,需要保证appendonly.aof文件没有flushall命令,否则服务器无法启动 - rename-command FLUSHALL “” # 禁用FLUSHALL命令
- rename-command FLUSHDB “” #禁止FLUSHDB命令
- rename-command CONFIG xxx # 重命名CONFIG命令
- rename-command CONFIG “” #禁止CONFIG命令
8.2 修改默认端口
修改redis的端口,使用默认的端口很危险,redis.conf中修改port 6379 将其修改为自己指定的端口(可随意)。