Redis实战总结(二)

来源:互联网 发布:网络视频教学平台 编辑:程序博客网 时间:2024/06/05 15:59

本篇博客重点介绍Redis的管道,发布/订阅机制。
Redis是一种基于Client-Server模型以及请求/响应协议的TCP服务。Client端发出请求,server端处理并返回结果到客户端。在这个过程中Client端是以阻塞形式等待服务端的响应。假设从Client发送命令到收到Server的处理结果需要1/16秒,这样带来的结果是Client每秒只能发送16条命令,及时Redis每秒可以处理几百个命令,严重影响了Redis的使用效率。因而针对上述问题Redis实现以下三种机制可以批量式的处理命令(通过减少Client和Server之间的网络通信次数来提升Redis在执行多个命令的性能)。

  • 处理多参数的命令:比如MGET,MSET,HMGET,HMSET,RPUSH等命令可以接受多个参数,这样能够极大的提升性能,但是这些命令只能处理需要重复执行相同命令的操作。

  • Multi和Exec命令:将多个命令封装成一个事物,以事物的方式提交,然后等待所有回复出现。但是这种方式仍然会消耗资源,并且可能会导致其他重要的命令被延迟执行。正常会和Watch、Unwatch、Discard命令结合使用,确保自己正在使用的数据没有发生变化来避免数据出错。

  • 管道技术:本篇博客重点介绍的。

Redis的管道机制

在Server端未响应时,Client端可以连续式的向Server端发送命令,并最终一次性读取Server端的所有响应,各个命令之间互不干扰。显然这种方式可以显著性地提高了 redis 服务的性能。我们把这种方式称为:非事务性流水线方式。
下面给出一个具体的测试实例:

    /**     * 设置给定键的值     * @param key     * @param value     */    public static void set(String key, String value) {        Jedis jedis = jedisPool.getResource();        try {            jedis.set(key, value);        } finally {            safeReturn(jedis);        }    }    /**     * 测试使用管道的效率     * @param key     */    public static void batchTestPipeLine(String key) {        Jedis jedis = jedisPool.getResource();        try {            Pipeline pipeline = jedis.pipelined();            for (int i = 0; i < 10000; i++) {                Response<Long> returns = pipeline.incr(key);            }            pipeline.syncAndReturnAll();        } finally {            safeReturn(jedis);        }    }    /**     * 测试不使用管道的效率     * @param key     */    public static void batchTestWithNonePipeLine(String key) {        Jedis jedis = jedisPool.getResource();        try {            for (int i = 0; i < 10000; i++) {                jedis.incr(key);            }        } finally {            safeReturn(jedis);        }    }

主程序测试:

import org.apache.log4j.Logger;import util.JedisUtil;public class Application2 {    private static Logger logger = Logger.getLogger(Application.class);    private static String PIPELINE_HAS = "PIPELINE_HAS";    private static String PIPELINE_NONE = "PIPELINE_NONE";    public static void main(String[] args) {        logger.info("主程序开始运行:");        Long beginTime = System.currentTimeMillis();        JedisUtil.init();        JedisUtil.set(PIPELINE_HAS, "0");        JedisUtil.set(PIPELINE_NONE, "0");        JedisUtil.batchTestWithNonePipeLine(PIPELINE_NONE);        Long endTime1 = System.currentTimeMillis();        JedisUtil.batchTestWithNonePipeLine(PIPELINE_HAS);        Long endTime2 = System.currentTimeMillis();        logger.info("未使用管道技术,消耗时间:"+(endTime1-beginTime));        logger.info("使用管道技术,消耗时间:"+(endTime2-endTime1));    }}

程序运行结果:
这里写图片描述
通过测试发现,使用通道技术和不使用差距还是很明显的。

Redis发布和订阅机制

Redis的发布与订阅机制由两部分组成:发布者,订阅者。发布者(publisher)负责向频道发送二进制字符串消息;订阅者(subscriber)负责订阅频道。每当发布者将消息发送至给定频道时,频道的所有订阅者都会收到消息。

Redis的发布和订阅是一种消息通信模式,可以解除消息发布者和消息订阅者之间的耦,类似于设计模式中观察者模式。

Redis中发布和订阅命令

命令 用例及描述 SUBSCRIBE SUBSCRIBE channel [channel …]——订阅给定的一个或多个频道 UNSUBSCRIBE UNSUBSCRIBE [channel [channel …]]——退订给定的一个或多个频道,如果没有给定任何频道,退订所有 PUBLISH PUBLISH channel message——向给定频道发送消息 PSUBSCRIBE PSUBSCRIBE pattern [pattern …]——订阅给定模式相匹配的所有频道 PUNSUBSCRIBE PUNSUBSCRIBE [pattern [pattern …]]——退订给定的模式,如果没有给定任何模式,退订所有

JAVA中发布和订阅的实现

重写类JedisPubSub中onMessage方法

package listener;import redis.clients.jedis.JedisPubSub;import java.util.logging.Logger;/** * 重写Redis的PUB和SUB方法 * @author guweiyu */public class RedisPubSubListener extends JedisPubSub {    Logger logger = Logger.getLogger(RedisPubSubListener.class.getName());    @Override    public void onMessage(String channel, String message) {        logger.info("channel:" + channel + "receives message :" + message);        this.unsubscribe(channel);    }    public void unsubscribe(String channel) {        logger.info("unsubscribe channel:"+channel);        super.unsubscribe(channel);    }}
    /**     * 订阅频道     * @param jedisPubSub     * @param channel     */    public static void subscribe(JedisPubSub jedisPubSub, String channel) {        Jedis jedis = jedisPool.getResource();        try {            jedis.subscribe(jedisPubSub, channel);        } finally {            safeReturn(jedis);        }    }    /**     * 向频道发布信息     * @param channel     * @param msg     */    public static void publish(String channel, String msg) {        Jedis jedis = jedisPool.getResource();        try {            jedis.publish(channel, msg);        } finally {            safeReturn(jedis);        }    }

编写PUB端测试案例

import org.apache.log4j.Logger;import util.JedisUtil;public class ApplicationPub {    private static Logger logger = Logger.getLogger(ApplicationPub.class);    public static void main(String[] args) throws Exception{        logger.info("Main Application is starting");        JedisUtil.init();        JedisUtil.publish("channel_test", "hello, world");        Thread.sleep(5000);        JedisUtil.publish("channel_test", "hello, china");    }}

编写SUB端测试案例

import listener.RedisPubSubListener;import org.apache.log4j.Logger;import util.JedisUtil;public class ApplicationSub {    private static Logger logger = Logger.getLogger(ApplicationSub.class);    public static void main(String[] args) {        logger.info("Main Application is starting");        JedisUtil.init();        // 订阅频道        RedisPubSubListener listener = new RedisPubSubListener();        // Redis的subscribe是阻塞式方法,在取消订阅该频道前,会一直阻塞在这        JedisUtil.subscribe(listener, "channel_test");        logger.info("订阅结束");    }}

先启动SUB端,再启动PUB端,运行结果如下:
这里写图片描述
这里写图片描述

在实际中直接使用Redis的PUB和SUB机制处理消息相对较少,由于和数据传输的可靠性有关。如果客户端在执行订阅操作的过程中断线,那么客户端将会丢失在断线期间发送的所有消息。