开发环境:
与传统关系型数据库类似,每次客户端与服务端建立连接是需要开销的,对于每一个客户端而言,将多个操作封装在一次连接内是十分有必要的,从而产生了Redis的流水线操作,应用步骤如下:
程序如下:
/**
* 10619ms
* 普通执行
*/
private static void test3()
{
Jedis jedis = new Jedis("127.0.0.1", 6379);
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++)
{
// 写
jedis.set("key_" + i, "value_" + i);
// 读
jedis.get("key_" + i);
}
long end = System.currentTimeMillis();
System.out.println("普通耗时:" + (end - start));
}
/**
* 630ms
* 流水线执行
*/
private static void test4()
{
Jedis jedis = new Jedis("127.0.0.1", 6379);
long start = System.currentTimeMillis();
// 开启流水线
Pipeline pipeline=jedis.pipelined();
for (int i = 0; i < 100000; i++)
{
// 写
pipeline.set("key_" + i, "value_" + i);
// 读
pipeline.get("key_" + i);
}
// 执行流水线,但不返回读的结果,syncAndReturnAll方法会返回结果
pipeline.sync();
long end = System.currentTimeMillis();
System.out.println("流水线耗时:" + (end - start));
}
刚开始学的时候笔者也是十分懵,这个发布订阅的作用不如流水线、锁机制等来的明显,其实捋一捋还是有点眉目的,Redis发布订阅无非就是通过Redis提供的渠道将消息发送到这个渠道上,而通过多个系统去监听这个渠道就可以获取消息。
典型应用的一个例子就是移动支付,当你通过微信或支付宝来消费时,就会把消费信息发布到该渠道,此时短信、微信或者支付宝都在监听,获取到消息后银行会发送短信,而微信或支付宝也会有一定的消费信息。
这非常类似于JMS(Java Message Service),其最大的优势笔者认为是作用于不同系统和异步调用。
应用步骤:
定义消息监听器程序如下:
package xyz.lsm1998.listener;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
/**
* 作者:刘时明
* 日期:2018/11/20
* 时间:12:52
* 说明:消息监听器,作用是处理接受到的消息
*/
@Component
public class RedisMessageListener implements MessageListener
{
@Override
public void onMessage(Message message, byte[] pattern)
{
System.err.println("获取到消息");
// 获取消息体
String body = new String(message.getBody());
// 获取渠道名称
String topic = new String(pattern);
// 调用业务
message(topic, body);
weChat(topic, body);
}
/**
* 模拟短信发送业务
* @param topic
* @param body
*/
private void message(String topic, String body)
{
System.out.println("短信提示:" + body + ",消息来自=" + topic);
}
/**
* 模拟微信发送业务
* @param topic
* @param body
*/
private void weChat(String topic, String body)
{
System.out.println("微信提示:" + body + ",消息来自=" + topic);
}
}
创建消息监听配置类程序如下:
package xyz.lsm1998.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* 作者:刘时明
* 日期:2018/11/20
* 时间:12:58
* 说明:
*/
@Configuration
public class RedisMessageConfig
{
@Autowired
private RedisConnectionFactory factory;
@Autowired
private MessageListener listener;
@Autowired
private ThreadPoolTaskScheduler taskScheduler;
/**
* 任务池,等待处理Redis的消息
*
* @return
*/
@Bean
public ThreadPoolTaskScheduler initTaskScheduler()
{
if (taskScheduler != null)
return taskScheduler;
taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(20);
return taskScheduler;
}
/**
* 消息监听容器
*
* @return
*/
@Bean
public RedisMessageListenerContainer initRedisContainer()
{
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// Redis连接工厂
container.setConnectionFactory(factory);
// 设置运行任务池
container.setTaskExecutor(initTaskScheduler());
// 定义监听渠道,名称为myTopic
Topic topic = new ChannelTopic("myTopic");
// 使用监听器监听Redis的消息
container.addMessageListener(listener, topic);
return container;
}
}
控制器发送消息程序如下:
package xyz.lsm1998.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 作者:刘时明
* 日期:2018/11/20
* 时间:14:36
* 说明:消息发送控制器
*/
@RestController
@RequestMapping("msg")
public class MessageController
{
@Autowired
private RedisTemplate redisTemplate;
/**
* 发送消息
* @return
*/
@GetMapping("test1")
public Object test1()
{
redisTemplate.convertAndSend("myTopic", "你已经消费了100万人民币");
return "消息已经成功发送";
}
}
最后结果亲测可用。
由于Redis是基于内存的,所以空间占用非常宝贵,类似于Java的gc机制,当运行内存占用过高时,Redis会自动回收一些键值对,至于具体如何回收?回收哪些键值对?笔者目前也不太清除,但可以肯定的是Redis进行自动垃圾回收时极大可能引发系统停顿,因此使用超时命令可以有效提高系统的性能。
程序如下:
package xyz.lsm1998;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import redis.clients.jedis.Jedis;
import xyz.lsm1998.config.RedisConfig;
import java.util.concurrent.TimeUnit;
/**
* 作者:刘时明
* 日期:2018/11/29
* 时间:22:46
* 说明:
*/
public class OutTimeTest
{
public static void main(String[] args)
{
var context = new AnnotationConfigApplicationContext(RedisConfig.class);
RedisTemplate template = context.getBean(RedisTemplate.class);
outTimeByJedis();
outTimeByRedisTemplate(template);
}
/**
* Jedis实现超时命令
*/
private static void outTimeByJedis()
{
Jedis jedis = new Jedis("127.0.0.1", 6379);
String key = "key_1";
jedis.set(key, "value");
printOutTime1(jedis, key);
// 设置超时,秒为单位
jedis.expire(key, 100);
printOutTime1(jedis, key);
// 设置超时,毫秒为单位
jedis.pexpire(key, 5000L);
printOutTime1(jedis, key);
}
/**
* RedisTemplate实现超时命令
*
* @param template
*/
private static void outTimeByRedisTemplate(RedisTemplate template)
{
ValueOperations operations = template.opsForValue();
String key = "key_1";
operations.set(key, "value");
printOutTime2(operations, key);
// 设置10秒超时
operations.getOperations().expire(key, 5L, TimeUnit.SECONDS);
printOutTime2(operations, key);
try
{
Thread.sleep(5 * 1000);
}catch (Exception e)
{
e.printStackTrace();
}
System.out.println("线程休眠5秒");
printOutTime2(operations, key);
// 等待key超时,再取出该键值对
System.out.println("超时后的value="+operations.get(key));
}
private static void printOutTime1(Jedis jedis, String key)
{
// -1代表不会超时,-2代表key不存在或者已经超时
System.out.println(key + "的超时时间=" + jedis.ttl(key));
}
private static void printOutTime2(ValueOperations operations, String key)
{
// -1代表不会超时,-2代表key不存在或者已经超时
RedisOperations redisOperations = operations.getOperations();
long time = redisOperations.getExpire(key);
System.out.println(key + "的超时时间=" + time);
}
}
PS:Redis的key超时不会被自动回收,而是被标识为超时,但此时程序显示键值对已经为null了,具体原因请看如下的惰性回收。
原因:虽然这样做会浪费一些内存空间,但对于一个很大的键值对来说,超时回收会产生系统停顿。
Redis有两种方式回收超时键值对: