Redis ==> 高级

一、发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

示例:

以下实例演示了发布订阅是如何工作的。在我们实例中我们创建了订阅频道名为 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"

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 ...]]
指退订给定的频道。

二、事务

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段: 

  • 开始事务。
  • 命令入队。
  • 执行事务。

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

比如:

redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK

如果在 set b bbb 处失败,set a 已成功不会回滚,set c 还会继续执行。

Redis 事务命令:

序号命令及描述
1 DISCARD
取消事务,放弃执行事务块内的所有命令。
2 EXEC
执行所有事务块内的命令。
3 MULTI
标记一个事务块的开始。
4 UNWATCH
取消 WATCH 命令对所有 key 的监视。
5 WATCH key [key ...]
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

三、Redis 持久化

Redis为什么需要持久化

Redis 中的数据类型都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。为了保证效率,数据都是缓存在内存中。当重启系统或者关闭系统后,缓存在内存中的数据都会消失殆尽,再也找不回来了。所以,为了让数据能够长期保存,就要将 Redis 放在缓存中的数据做持久化存储。

Redis实现持久化的两种方式

RDB 持久化机制,是对 Redis 中的数据执行周期性的持久化。

  • RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 Redis 中的数据。
  • RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
  • 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加快速。
  • 如果想要在 Redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 Redis 进程宕机,那么会丢失最近 5 分钟的数据。
  • RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

AOF 持久化机制,是对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。

  • AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据。
  • AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
  • AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
  • AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复
  • AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的。

RDB 和 AOF 到底该如何选择

  • 不要仅仅使用 RDB,因为那样会导致你丢失很多数据。
  • 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug.
  • redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。 

四、Jedis的使用

1、连接到 redis 服务

public void test01() {
  //连接本地的 Redis 服务
  Jedis jedis = new Jedis("127.0.0.1",6379);
  System.out.println("连接成功");
  //查看服务是否运行
  System.out.println("服务正在运行: "+jedis.ping());
}

2、Redis Java String(字符串) 实例

public void test02() {
  //连接本地的 Redis 服务
  Jedis jedis = new Jedis("127.0.0.1",6379);
  System.out.println("连接成功");
  //设置 redis 字符串数据
  jedis.set("runoobkey", "www.runoob.com");
  // 获取存储的数据并输出
  System.out.println("redis 存储的字符串为: "+ jedis.get("runoobkey"));
}

3、Redis Java List(列表) 实例

public void test03() {
  //连接本地的 Redis 服务
  Jedis jedis = new Jedis("127.0.0.1",6379);
  System.out.println("连接成功");
  //存储数据到列表中
  jedis.lpush("site-list", "Runoob");
  jedis.lpush("site-list", "Google");
  jedis.lpush("site-list", "Taobao");
  // 获取存储的数据并输出
  List<String> list = jedis.lrange("site-list", 0 ,2);
  for(int i=0; i<list.size(); i++) {
    System.out.println("列表项为: "+list.get(i));
  }
}

4、Redis Java Keys 实例

public void test04() {
  //连接本地的 Redis 服务
  Jedis jedis = new Jedis("127.0.0.1",6379);
  System.out.println("连接成功");

  // 获取数据并输出
  Set<String> keys = jedis.keys("*");
  Iterator<String> it=keys.iterator() ;
  while(it.hasNext()){
    String key = it.next();
    System.out.println(key);
  }
}

5、连接池

public class RedisTest {

  public static void main(String[] args) {

    // 初始化Jedis连接池配置
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

    // 初始化连接池
    JedisPool jedisPool = new JedisPool(jedisPoolConfig,"192.168.182.128");

    // 获取资源
    Jedis jedis = jedisPool.getResource();

    // 验证密码
    jedis.auth("123456");

    // 操作
    jedis.set("jack","chen");
    String res = jedis.get("jack");

    System.out.println("res = " + res);

    // 关闭连接
    jedis.close();
  }
}

6、RedisUtils

public class RedisUtils {
  //服务器IP地址
  private static String ADDR = "127.0.0.1";
  //端口
  private static int PORT = 6379;
  //密码
  private static String AUTH = "123456";
  //连接实例的最大连接数
  private static int MAX_ACTIVE = 1024;
  //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
  private static int MAX_IDLE = 200;
  //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
  private static int MAX_WAIT = 10000;
  //连接超时的时间  
  private static int TIMEOUT = 10000;
  // 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
  private static boolean TEST_ON_BORROW = true;

  private static JedisPool jedisPool = null;
  //数据库模式是16个数据库 0~15
  public static final int DEFAULT_DATABASE = 0;


  /**
   * 初始化Redis连接池
   */
  static {
    try {
      JedisPoolConfig config = new JedisPoolConfig();
      config.setMaxTotal(MAX_ACTIVE);
      config.setMaxIdle(MAX_IDLE);
      config.setMaxWaitMillis(MAX_WAIT);
      config.setTestOnBorrow(TEST_ON_BORROW);
      jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT,AUTH,DEFAULT_DATABASE);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }


  /**
   * 获取Jedis实例
   */
  public synchronized static Jedis getJedis() {

    try {

      if (jedisPool != null) {
        Jedis resource = jedisPool.getResource();
        System.out.println("redis--服务正在运行: "+resource.ping());
        return resource;
      } else {
        return null;
      }

    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }


  /***
   *
   * 释放资源
   */
  public static void returnResource(final Jedis jedis) {
    if(jedis != null) {
      jedisPool.close();
    }
  }
}
RedisUtils

posted on 2019-10-05 23:15  破解孤独  阅读(566)  评论(0编辑  收藏  举报

导航