第一 Redis 简介
1.1 什么是Redis
Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。
1.2 Redis 应用场景
主要能够体现 解决数据库的访问压力。
例如:短信验证码时间有效期、session共享解决方案
1.3 Redis 优势
性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
第二 Redis 基本数据类型之字符串(String)
Redis 字符串命令
命令 | 描述说明 |
此命令设置指定键的值。 | |
获取指定键的值。 | |
获取存储在键上的字符串的子字符串。 | |
设置键的字符串值并返回其旧值。 | |
返回在键处存储的字符串值中偏移处的位值。 | |
获取所有给定键的值 | |
存储在键上的字符串值中设置或清除偏移处的位 | |
使用键和到期时间来设置值 | |
设置键的值,仅当键不存在时 | |
在指定偏移处开始的键处覆盖字符串的一部分 | |
获取存储在键中的值的长度 | |
为多个键分别设置它们的值 | |
为多个键分别设置它们的值,仅当键不存在时 | |
设置键的值和到期时间(以毫秒为单位) | |
将键的整数值增加1 | |
将键的整数值按给定的数值增加 | |
将键的浮点值按给定的数值增加 | |
将键的整数值减1 | |
按给定数值减少键的整数值 | |
将指定值附加到键 |
第三 Redis 基本数据类型之列表(List)
Redis列表:是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
Redis列表命令
命令及描述 |
BLPOP key1 [key2 ] timeout |
BRPOP key1 [key2 ] timeout |
BRPOPLPUSH source destination timeout |
LINDEX key index |
LINSERT key BEFORE|AFTER pivot value |
LLEN key |
LPOP key |
LPUSH key value1 [value2] |
LPUSHX key value |
LRANGE key start stop |
LREM key count value |
LSET key index value |
LTRIM key start stop |
RPOP key |
RPOPLPUSH source destination |
RPUSH key value1 [value2] |
RPUSHX key value |
第四 Redis 基本数据类型之集合(Set)
Redis集合:是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
Redis集合命令
命令及描述 |
SADD key member1 [member2] |
SCARD key |
SDIFF key1 [key2] |
SDIFFSTORE destination key1 [key2] |
SINTER key1 [key2] |
SINTERSTORE destination key1 [key2] |
SISMEMBER key member |
SMEMBERS key |
SMOVE source destination member |
SPOP key |
SRANDMEMBER key [count] |
SREM key member1 [member2] |
SUNION key1 [key2] |
SUNIONSTORE destination key1 [key2] |
第五 Redis 基本数据类型之有序集合(Sorted Set)
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
Redis有序集合命令
第六 Redis 哈希(Hash)
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
Redis 哈希命令
命令及描述 |
HDEL key field2 [field2] |
HEXISTS key field |
HGET key field |
HGETALL key |
HINCRBY key field increment |
HINCRBYFLOAT key field increment |
HKEYS key |
HLEN key |
HMGET key field1 [field2] |
HMSET key field1 value1 [field2 value2 ] |
HSET key field value |
HSETNX key field value |
HVALS key |
HSCAN key cursor [MATCH pattern] [COUNT count] |
第七 Windows 系统安装Redis
7.1 CMD窗口启动Redis服务
切换至Redis解压文件的bin目录,执行如下代码:redis-server redis.windows.conf
7.2 将Redis作为服务安装在本机电脑中:
切换至Redis解压文件的bin目录,执行如下代码:redis-server --service-install redis.windows.conf
安装完之后,就可看到Redis已经作为windows服务了:
但是安装好之后,Redis并没有启动,启动命令如下:redis-server --service-start
Redis 服务停止,执行如下命令: redis-server --service-stop
Redif 服务协助,执行如下命令:redis-server --service-uninstall
7.3 redis.bat 脚本指令启动Redis 服务
在redis的目录下新建一个redis.bat脚本指令,脚本文件内容如下:
d:
cd redis
redis-server.exe redis.windows.conf
第8 Java项目之Redis 集成
8.1 Java Maven 项目集成Redis ,pom.xml 文件添加如下依赖:
<!-- redis jar包 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
8.2 Redis 之字符串
package com.zzg.redis.util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisFactory {
public static JedisPool jedisPool; // 池化管理jedis链接池
static {
JedisPoolConfig config = new JedisPoolConfig();
// 设置最大连接数
config.setMaxTotal(300);
// 设置最大空闲数
config.setMaxIdle(600);
// //设置超时时间
config.setMaxWaitMillis(10000);
// 初始化连接池
jedisPool = new JedisPool(config, "127.0.0.1", 6379, 10 * 1000);
}
/**
* 获取 redis链接
*
* @return 2017年9月13日
*/
public static Jedis getResource() {
return jedisPool.getResource();
}
public static void close() {
jedisPool.close();
}
}
package com.zzg.redis.string;
/**
* Redis 字符串类型
* @author Administrator
*
* @param <K>
* @param <V>
*/
public interface RedisString<K, V> {
/**
* 写入缓存 可以是对象
* @param key
* @param value
*/
void set(final K key, V value);
/**
* 读取String缓存 可以是对象
* @param key
* @return
*/
V get(final K key);
/**
* 获取存储在键上的字符串的子字符串
* @param key
* @param start
* @param end
* @return
*/
V getrange(final K key, int start, int end);
/**
* 设置键的字符串值并返回其旧值。
* @param key
* @param value
* @return
*/
V getset(final K key, V value);
/**
* 移除指定key
* @param key
*/
void del(final K key);
/**
* 同时设置多个key-value
* @param keyvalues
*/
void mset(String ...keyvalues);
/**
* 属性值增加
* @param key
*/
void incr(final K key);
/**
* 属性值追加
* @param value
*/
void append(final K key, V value);
}
package com.zzg.redis.string;
import com.zzg.redis.util.RedisFactory;
public class RedisStringHandler implements RedisString<String, String> {
private RedisFactory factory;
public RedisFactory getFactory() {
return factory;
}
public void setFactory(RedisFactory factory) {
this.factory = factory;
}
public RedisStringHandler(RedisFactory factory) {
super();
this.factory = factory;
}
@Override
public void set(String key, String value) {
// TODO Auto-generated method stub
factory.getResource().set(key, value);
}
@Override
public String get(String key) {
// TODO Auto-generated method stub
return factory.getResource().get(key);
}
@Override
public String getrange(String key, int start, int end) {
// TODO Auto-generated method stub
return factory.getResource().getrange(key, start, start);
}
@Override
public String getset(String key, String value) {
// TODO Auto-generated method stub
return factory.getResource().getSet(key, value);
}
@Override
public void del(String key) {
// TODO Auto-generated method stub
factory.getResource().del(key);
}
@Override
public void mset(String... keyvalues) {
// TODO Auto-generated method stub
factory.getResource().mset(keyvalues);
}
@Override
public void incr(String key) {
// TODO Auto-generated method stub
factory.getResource().incr(key);
}
@Override
public void append(final String key, String value) {
// TODO Auto-generated method stub
factory.getResource().append(key, value);
}
}
测试代码:
package com.zzg.redis;
import com.zzg.redis.string.RedisStringHandler;
import com.zzg.redis.util.RedisFactory;
public class StringTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
RedisStringHandler handler = new RedisStringHandler(new RedisFactory());
// redis 设置
handler.set("key", "zzg");
// redis 获取
String value = handler.get("key");
System.out.println("value is:" + value);
// redis 追加
handler.append("key", "is java developer");
System.out.println("value is:" + handler.get("key"));
// redis 移除
handler.del("key");
System.out.println("value is:" + handler.get("key"));
// redis 设置多个key-value
handler.mset("name", "zzg", "age", "28","qq","605739");
handler.incr("age"); //进行加1操作
System.out.println(handler.get("name") + "-" + handler.get("age") + "-" + handler.get("qq"));
handler.getFactory().close();
}
}
8.3 Redis 之Map
package com.zzg.redis.string;
import java.util.List;
import java.util.Set;
public interface RedisMap<K, V> {
/**
* 写入缓存
* @param key
* @param value
*/
void hmset(final K key, V value);
/**
* 获取缓存
* @param keys
* @return
*/
List<String> hmget(String key, String ...keys);
/**
* 移除Map 指定属性值
* @param key
* @param properties
*/
void hdel(final K key, String properties);
/**
* 获取指定Map中的指定属性
* @param key
* @param properties
* @return
*/
Object hmget(final K key, String properties);
/**
* 返回指定Map中的个数
* @param key
* @return
*/
Long hlen(final K key);
/**
* 判断指定key Map是否存在
* @param key
* @return
*/
boolean exists(final K key);
/**
* 返回指定Map 所包含的key 值
* @param key
*/
Set<String> hkeys(final K key);
}
package com.zzg.redis.string;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.zzg.redis.util.RedisFactory;
public class RedisMapHandler implements RedisMap<String, Map> {
private RedisFactory factory;
public RedisFactory getFactory() {
return factory;
}
public void setFactory(RedisFactory factory) {
this.factory = factory;
}
public RedisMapHandler(RedisFactory factory) {
super();
this.factory = factory;
}
@Override
public void hmset(String key, Map value) {
// TODO Auto-generated method stub
factory.getResource().hmset(key, value);
}
@Override
public List<String> hmget(String key, String... fields) {
// TODO Auto-generated method stub
return factory.getResource().hmget(key, fields);
}
@Override
public void hdel(String key, String properties) {
// TODO Auto-generated method stub
factory.getResource().hdel(key, properties);
}
@Override
public Object hmget(String key, String properties) {
// TODO Auto-generated method stub
return factory.getResource().hmget(key, properties);
}
@Override
public Long hlen(String key) {
// TODO Auto-generated method stub
return factory.getResource().hlen(key);
}
@Override
public boolean exists(String key) {
// TODO Auto-generated method stub
return factory.getResource().exists(key);
}
@Override
public Set<String> hkeys(String key) {
// TODO Auto-generated method stub
return factory.getResource().hkeys(key);
}
}
测试代码:
package com.zzg.redis;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.zzg.redis.string.RedisMapHandler;
import com.zzg.redis.util.RedisFactory;
public class MapTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
RedisMapHandler handler = new RedisMapHandler(new RedisFactory());
Map<String, String> map = new HashMap<String, String>();
map.put("name", "zzg");
map.put("age", "28");
map.put("qq", "60573");
// redis 设置
handler.hmset("user", map);
// redis 获取
List<String> rsmap = handler.hmget("user", "name", "age", "qq");
System.out.println(rsmap);
// redis 删除
handler.hdel("user","age");
System.out.println(handler.hmget("user", "age")); //因为删除了,所以返回的是null
System.out.println(handler.hlen("user")); //返回key为user的键中存放的值的个数2
System.out.println(handler.exists("user"));//是否存在key为user的记录 返回true
System.out.println(handler.hkeys("user"));//返回map对象中的所有key
Iterator<String> iter=handler.hkeys("user").iterator();
while (iter.hasNext()){
String key = iter.next();
System.out.println(key+":"+handler.hmget("user",key));
}
}
}
8.5 Redis 之List
package com.zzg.redis.string;
import java.util.List;
public interface RedisList<K, V> {
/**
* Redis 移除指定key
* @param key
*/
void del(final K key);
/**
* Redis List 写入缓存
* @param key
* @param values
*/
void lpush(final K key, String ...values);
/**
* Redis List 查询
* @param key
* @param start
* @param stop
* @return
*/
List<String> lrange(final K key, long start, long stop);
/**
* Redis List 写入缓存
* @param key
* @param values
*/
void rpush(final K key, String ...values);
}
package com.zzg.redis.string;
import java.util.List;
import com.zzg.redis.util.RedisFactory;
public class RedisListHandler implements RedisList<String, List> {
private RedisFactory factory;
public RedisFactory getFactory() {
return factory;
}
public void setFactory(RedisFactory factory) {
this.factory = factory;
}
public RedisListHandler(RedisFactory factory) {
super();
this.factory = factory;
}
@Override
public void del(String key) {
// TODO Auto-generated method stub
factory.getResource().del(key);
}
@Override
public void lpush(String key, String... values) {
// TODO Auto-generated method stub
factory.getResource().lpush(key, values);
}
@Override
public List<String> lrange(String key, long start, long stop) {
// TODO Auto-generated method stub
return factory.getResource().lrange(key, start, stop);
}
@Override
public void rpush(String key, String... values) {
// TODO Auto-generated method stub
factory.getResource().rpush(key, values);
}
}
测试功能代码:
package com.zzg.redis;
import com.zzg.redis.string.RedisListHandler;
import com.zzg.redis.util.RedisFactory;
public class ListTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
RedisListHandler handler = new RedisListHandler(new RedisFactory());
handler.del("java framework");
System.out.println(handler.lrange("java framework",0,-1));
//先向key java framework中存放三条数据
handler.lpush("java framework","spring");
handler.lpush("java framework","struts");
handler.lpush("java framework","hibernate");
//再取出所有数据jedis.lrange是按范围取出,
// 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有
System.out.println(handler.lrange("java framework",0,-1));
handler.del("java framework");
handler.rpush("java framework","spring");
handler.rpush("java framework","struts");
handler.rpush("java framework","hibernate");
System.out.println(handler.lrange("java framework",0,-1));
handler.getFactory().close();
}
}
8.6 Redis 之Set
package com.zzg.redis.string;
import java.util.List;
import java.util.Set;
public interface RedisSet<K, V> {
/**
* redis 缓存对象
* @param key
* @param value
*/
void sadd(final K key, V value);
/**
* redis 移除缓存对照
* @param key
* @param value
*/
void srem(final K key, V value);
/**
* 获取指定key
* @param key
* @return
*/
Set<V> smembers(final K key);
/**
* redis 指定key 是否包含指定的value
* @param key
* @param value
* @return
*/
boolean sismember(final K key, V value);
/**
* redis 返回指定key 的数量
* @param key
* @return
*/
long scard(final K key);
}
package com.zzg.redis.string;
import java.util.Set;
import com.zzg.redis.util.RedisFactory;
public class RedisSetHandler implements RedisSet<String, String> {
private RedisFactory factory;
public RedisFactory getFactory() {
return factory;
}
public void setFactory(RedisFactory factory) {
this.factory = factory;
}
public RedisSetHandler(RedisFactory factory) {
super();
this.factory = factory;
}
@Override
public void sadd(String key, String value) {
// TODO Auto-generated method stub
this.factory.getResource().sadd(key, value);
}
@Override
public void srem(String key, String value) {
// TODO Auto-generated method stub
this.factory.getResource().srem(key, value);
}
@Override
public Set<String> smembers(String key) {
// TODO Auto-generated method stub
return this.factory.getResource().smembers(key);
}
@Override
public boolean sismember(String key, String value) {
// TODO Auto-generated method stub
return this.factory.getResource().sismember(key, value);
}
@Override
public long scard(String key) {
// TODO Auto-generated method stub
return this.factory.getResource().scard(key);
}
}
测试功能代码:
package com.zzg.redis;
import com.zzg.redis.string.RedisSetHandler;
import com.zzg.redis.util.RedisFactory;
public class SetTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
RedisSetHandler handler = new RedisSetHandler(new RedisFactory());
handler.sadd("users","liuling");
handler.sadd("users","xinxin");
handler.sadd("users","ling");
handler.sadd("users","zhangxinxin");
handler.sadd("users","who");
//移除noname
handler.srem("users","who");
System.out.println(handler.smembers("users"));//获取所有加入的value
System.out.println(handler.sismember("users", "who"));//判断 who 是否是user集合的元素
System.out.println(handler.scard("users"));//返回集合的元素个数
handler.getFactory().close();
}
}
第9 SpringBoot项目之Redis 集成
参考文章:SpringBoot 集成Redis
第10 Windows 之Redis 主从复制
10.1 Redis 主从复制 概述
1、redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
2、通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。
10.2 Redis 主从复制 图解
过程:
1:当一个从数据库启动时,会向主数据库发送sync命令,
2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。
10.3 Windows 系统配置Redis 主从配置
目标:windows 系统实现Redis 主从配置,主端口:7001 ,从端口:7002
主从配置一般流程:
1、在redis根目录建二个文件夹,分别命名为7001、7002。这二个名称分为表示redis启动后的端口号。
2、再建立二个bat,分别命名为7001、7002。内容如下,为了方便启动二个redis服务使用。
3、将redis根目录下的redis.windows.conf文件分别拷贝至7001、7002文件夹。
4、打开7001文件夹下的redis.windows.conf文件。
修改项目内容:port 。值7001。
5、打开7002文件夹下的redis.windows.conf文件。
修改项目内容:port、slaveof。
7002的文件修改端口为7002,
slaveof 127.0.0.1 7001
这样一一主一从就配置完毕了,简单点说只是修改了一句话即可。slaveof 127.0.0.1 7001
6、先从7001、7002依次启动二个服务。
启动服务后二个服务的控制台分别会显示主从信息。
主控制台会显示那些从服务加入进来。内容显示7002分别加入进来了并同步数据
从控制台会显示加入了那个主服务。
7、主从测试
redis-cli.exe -h 127.0.0.1 -p 7001 #首先连接7001
127.0.0.1:7001> keys * #查看是否存在key
(empty list or set) #显示不存在任何key
127.0.0.1:7001> set testkey tesvvalue #设置一个名字叫做testkey的key,值为tesvvalue。
OK #赋值成功
127.0.0.1:7001> quit #退出7001
redis-cli.exe -h 127.0.0.1 -p 7002 #连接7002
127.0.0.1:7002> keys * #查看是否存在key
1) "testkey" #显示存在一个名字叫做testkey的key
127.0.0.1:7002> get testkey #获得testkey的值
"tesvvalue" #值为tesvvalue
127.0.0.1:7002> set noinsert yes #测试是否可以在7002set数据。
(error) READONLY You can't write against a read only slave. #出错,提示这是一个只读的从服务。
经过以上测试表示我们的主从复制成功了。
第11 Windows 之Redis 哨兵机制
11.1 哨兵机制概述
Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:
· 监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
· 提醒(Notification):当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
· 自动故障迁移(Automatic failover):当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。
哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master.
每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).
若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置.
虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel).
哨兵(sentinel) 的一些设计思路和zookeeper非常类似
11.2 哨兵机制图解
11.3 windows 搭建哨兵机制
哨兵机制搭建一般流程:
参考 :10.3 Windows 系统配置Redis 主从配置
哨兵配置:
1、在每一个7001 和7002文件夹中都创建一个文sentinel.conf文件。
2、7001的sentinel.conf文件内容:
#当前Sentinel服务运行的端口
port 26379
#master
#Sentinel去监视一个名为mymaster的主redis实例,这个主实例的IP地址为本机地址127.0.0.1,端口号为6379,
#而将这个主实例判断为失效至少需要2个 Sentinel进程的同意,只要同意Sentinel的数量不达标,自动failover就不会执行
sentinel monitor mymaster 127.0.0.1 7001 1
#指定了Sentinel认为Redis实例已经失效所需的毫秒数。当 实例超过该时间没有返回PING,或者直接返回错误,那么Sentinel将这个实例标记为主观下线。
#只有一个 Sentinel进程将实例标记为主观下线并不一定会引起实例的自动故障迁移:只有在足够数量的Sentinel都将一个实例标记为主观下线之后,实例才会被标记为客观下线,这时自动故障迁移才会执行
sentinel down-after-milliseconds mymaster 5000
#指定了在执行故障转移时,最多可以有多少个从Redis实例在同步新的主实例,在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长
sentinel config-epoch mymaster 12
#如果在该时间(ms)内未能完成failover操作,则认为该failover失败
sentinel leader-epoch mymaster 13
3、7002 的sentinel.conf文件内容:
#当前Sentine2服务运行的端口
port 26479
#slave1
sentinel monitor mymaster 127.0.0.1 7001 1
sentinel down-after-milliseconds mymaster 5000
sentinel config-epoch mymaster 12
sentinel leader-epoch mymaster 13
4、编写7001_sentinel.bat 和7002_sentinel.bat 脚本指令
7001_sentinel.bat 内容如下:
cd 7001
title sentinel-7001
redis-server.exe sentinel.conf --sentinel
说明:title命名规则 启动成功后显示的title, 注意“--sentinel”不是注释,必须存在。
7002_sentinel.bat 内容如下:
cd 7002
title sentinel-7002
redis-server.exe sentinel.conf --sentinel
说明:title命名规则 启动成功后显示的title, 注意“--sentinel”不是注释,必须存在。
5、测试
启动redis 主服务(7001.bat) 和redis 从服务(7002bat)
启动redis 主服务的哨兵机制(7001_sentinel.bat) 和redis 从服务的哨兵机制(7002_sentinel.bat)
查看是主库还是从库,cmd进入对应的redis文件夹-》redis-cli.exe -p 服务端口
然后再输入: info replication
7001 主:
7002 从:
3、查看哨兵sentinel状态,cmd进入对应的redis文件夹-》redis-cli.exe -p 哨兵配置端口,输入info sentinel
主:
从:
4、关闭掉主库,然后再按照步骤5测试一遍+观察。从库会变成主库,再启动原来被关闭掉的主库后,会自动变成从库。
第12 Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。
12.1 实例
以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
2) "C++"
3) "Programming"
12.2 事务常用命令
序号 | 命令及描述 |
1 | DISCARD |
2 | EXEC |
3 | MULTI |
4 | UNWATCH |
5 | WATCH key [key ...] |
第13 Redis 持久化
13.1 什么是持久化
什么是Redis持久化,就是将内存数据保存到硬盘。
Redis 持久化存储 (AOF 与 RDB 两种模式)
13.2 RDB持久化
RDB 是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
这里说的这个执行数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置redis 在 n 秒内如果超过 m 个 key 被修改这执行一次 RDB 操作。这个操作就类似于在这个时间点来保存一次 Redis 的所有数据,一次快照数据。所有这个持久化方法也通常叫做 snapshots。
RDB 默认开启,redis.conf 中的具体配置参数如下;
#dbfilename:持久化数据存储在本地的文件
dbfilename dump.rdb
#dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下
dir ./
##snapshot触发的时机,save
##如下为900秒后,至少有一个变更操作,才会snapshot
##对于此值的设置,需要谨慎,评估系统的变更操作密集程度
##可以通过“save “””来关闭snapshot功能
#save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key300s进行存储;更改10000个key60s进行存储。
save 900 1
save 300 10
save 60 10000
##当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故障/OS级别异常等
stop-writes-on-bgsave-error yes
##是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸以及较短的网络传输时间
rdbcompression yes
13.3 AOF持久化
Append-only file,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后(已经写入到文件或者即将写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。AOF 相对可靠,它和 mysql 中 bin.log、apache.log、zookeeper 中 txn-log 简直异曲同工。AOF 文件内容是字符串,非常容易阅读和解析。
优点:可以保持更高的数据完整性,如果设置追加 file 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢。
我们可以简单的认为 AOF 就是日志文件,此文件只会记录“变更操作”(例如:set/del 等),如果 server 中持续的大量变更操作,将会导致 AOF 文件非常的庞大,意味着 server 失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条 AOF 记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为 AOF 持久化模式还伴生了“AOF rewrite”。
AOF 的特性决定了它相对比较安全,如果你期望数据更少的丢失,那么可以采用 AOF 模式。如果 AOF 文件正在被写入时突然 server 失效,有可能导致文件的最后一次记录是不完整,你可以通过手工或者程序的方式去检测并修正不完整的记录,以便通过 aof 文件恢复能够正常;同时需要提醒,如果你的 redis 持久化手段中有 aof,那么在 server 故障失效后再次启动前,需要检测 aof 文件的完整性。
AOF 默认关闭,开启方法,修改配置文件 reds.conf:appendonly yes
##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能
##只有在“yes”下,aof重写/文件同步等特性才会生效
appendonly yes
##指定aof文件名称
appendfilename appendonly.aof
##指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec
appendfsync everysec
##在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”
no-appendfsync-on-rewrite no
##aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”
auto-aof-rewrite-min-size 64mb
##相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。
##每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后
##触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
auto-aof-rewrite-percentage 100
AOF 是文件操作,对于变更操作比较密集的 server,那么必将造成磁盘 IO 的负荷加重;此外 linux 对文件操作采取了“延迟写入”手段,即并非每次 write 操作都会触发实际磁盘操作,而是进入了 buffer 中,当 buffer 数据达到阀值时触发实际写入(也有其他时机),这是 linux 对文件系统的优化,但是这却有可能带来隐患,如果 buffer 没有刷新到磁盘,此时物理机器失效(比如断电),那么有可能导致最后一条或者多条 aof 记录的丢失。通过上述配置文件,可以得知 redis 提供了 3 中 aof 记录同步选项:
always:每一条 aof 记录都立即同步到文件,这是最安全的方式,也以为更多的磁盘操作和阻塞延迟,是 IO 开支较大。
everysec:每秒同步一次,性能和安全都比较中庸的方式,也是 redis 推荐的方式。如果遇到物理服务器故障,有可能导致最近一秒内 aof 记录丢失(可能为部分丢失)。
no:redis 并不直接调用文件同步,而是交给操作系统来处理,操作系统可以根据 buffer 填充情况 / 通道空闲时间等择机触发同步;这是一种普通的文件操作方式。性能较好,在物理服务器故障时,数据丢失量会因 OS 配置有关。
其实,我们可以选择的太少,everysec 是最佳的选择。如果你非常在意每个数据都极其可靠,建议你选择一款“关系性数据库”吧。
AOF 文件会不断增大,它的大小直接影响“故障恢复”的时间, 而且 AOF 文件中历史操作是可以丢弃的。AOF rewrite 操作就是“压缩”AOF 文件的过程,当然 redis 并没有采用“基于原 aof 文件”来重写的方式,而是采取了类似 snapshot 的方式:基于 copy-on-write,全量遍历内存中数据,然后逐个序列到 aof 文件中。因此 AOF rewrite 能够正确反应当前内存数据的状态,这正是我们所需要的;*rewrite 过程中,对于新的变更操作将仍然被写入到原 AOF 文件中,同时这些新的变更操作也会被 redis 收集起来(buffer,copy-on-write 方式下,最极端的可能是所有的 key 都在此期间被修改,将会耗费 2 倍内存),当内存数据被全部写入到新的 aof 文件之后,收集的新的变更操作也将会一并追加到新的 aof 文件中,此后将会重命名新的 aof 文件为 appendonly.aof, 此后所有的操作都将被写入新的 aof 文件。如果在 rewrite 过程中,出现故障,将不会影响原 AOF 文件的正常工作,只有当 rewrite 完成之后才会切换文件,因为 rewrite 过程是比较可靠的。*
触发 rewrite 的时机可以通过配置文件来声明,同时 redis 中可以通过 bgrewriteaof 指令人工干预。
redis-cli -h ip -p port bgrewriteaof
因为 rewrite 操作 /aof 记录同步 /snapshot 都消耗磁盘 IO,redis 采取了“schedule”策略:无论是“人工干预”还是系统触发,snapshot 和 rewrite 需要逐个被执行。
AOF rewrite 过程并不阻塞客户端请求。系统会开启一个子进程来完成。
13.4 AOF与RDB区别
AOF 和 RDB 各有优缺点,这是有它们各自的特点所决定:
1) AOF 更加安全,可以将数据更加及时的同步到文件中,但是 AOF 需要较多的磁盘 IO 开支,AOF 文件尺寸较大,文件内容恢复数度相对较慢。
*2) snapshot,安全性较差,它是“正常时期”数据备份以及 master-slave 数据同步的最佳手段,文件尺寸较小,恢复数度较快。
可以通过配置文件来指定它们中的一种,或者同时使用它们(不建议同时使用),或者全部禁用,在架构良好的环境中,master 通常使用 AOF,slave 使用 snapshot,主要原因是 master 需要首先确保数据完整性,它作为数据备份的第一选择;slave 提供只读服务(目前 slave 只能提供读取服务),它的主要目的就是快速响应客户端 read 请求;但是如果你的 redis 运行在网络稳定性差 / 物理环境糟糕情况下,建议你 master 和 slave 均采取 AOF,这个在 master 和 slave 角色切换时,可以减少“人工数据备份”/“人工引导数据恢复”的时间成本;如果你的环境一切非常良好,且服务需要接收密集性的 write 操作,那么建议 master 采取 snapshot,而 slave 采用 AOF。
第14 Redis 发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
14.1 实例
以下实例演示了发布订阅是如何工作的。在我们实例中我们创建了订阅频道名为 redisChat:
redis 127.0.0.1:6379> SUBSCRIBE redisChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。
redis 127.0.0.1:6379> PUBLISH redisChat "Redis is a great caching technique"
(integer) 1
redis 127.0.0.1:6379> PUBLISH redisChat "Learn redis by runoob.com"
(integer) 1
# 订阅者的客户端会显示如下消息
1) "message"
2) "redisChat"
3) "Redis is a great caching technique"
1) "message"
2) "redisChat"
3) "Learn redis by runoob.com"
14.2 Redis 发布订阅常用命令
序号 | 命令及描述 |
1 | PSUBSCRIBE pattern [pattern ...] |
2 | PUBSUB subcommand [argument [argument ...]] |
3 | PUBLISH channel message |
4 | PUNSUBSCRIBE [pattern [pattern ...]] |
5 | SUBSCRIBE channel [channel ...] |
6 | UNSUBSCRIBE [channel [channel ...]] |
第15 Redis 集群