@[TOC](springData Redis知识概括)
SpringDataRedis简介
SpringDataRedis介绍:
- Spring DataRedis是对redis客户端(如jedis)的高度封装,支持多种客户端,因其高抽象,所以在某一个客户端不支持更新的时候可以容易切换到其他客户端.
- SpringDataRedis使用:
①
需要引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Spring-data-redis和Spring-boot-starter-data-Redis的关系:
在Spring-boot-starter-data-Redis的pom.xml可以清晰的看到,它里面包含了Spring-data-redis,所以两者的关系是包含与被包含的关系,或者说前者更好的封装了后者。
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
SpringDataRedis配置:
在SpringBoot下默认使用jedis作为客户端,并在包org.springframework.boot.autoconfigure.data.redis下,提供自动配置类RedisProperties,RedisAutoConfiguration等.- 根据RedisProperties可以定位到可配置的属性,如:
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=115.159.185.14
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=2000
在application.properties中配置即可,另外还有Sentinel和Cluster说明支持分布式和集群,- 自动配置主要在RedisAutoConfiguration中,该类会提供三个bean:
①
JedisConnectionFactory : jedis连接控制工厂②RedisTemplate : redis操作入口③StringRedisTemplate :redis操作入口
SSM整合redis:
- 在不使用springdata redis而使用SSM整合redis时,则需要手动注入一些Bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置读取properties文件的工具类 -->
<context:property-placeholder location="classpath:redis.properties"/>
<!-- jedis连接池 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数 -->
<property name="maxTotal" value="${redis.pool.maxTotal}" />
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<!-- 最小空闲连接数 -->
<property name="minIdle" value="${redis.pool.minIdle}" />
</bean>
<!-- Jedis的连接工厂 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.conn.hostName}"/>
<property name="port" value="${redis.conn.port}"/>
<property name="poolConfig" ref="poolConfig"/>
</bean>
<!-- Redis模板对象 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<!-- 序列化器:能够把我们储存的key与value做序列化处理的对象 -->
<!-- 配置默认的序列化器 -->
<!-- keySerializer、valueSerializer 配置Redis中的String类型key与value的序列化器 -->
<!-- HashKeySerializer、HashValueSerializer 配置Redis中的Hash类型key与value的序列化器 -->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
</bean>
</beans>
Common-pool对象池
为什么使用对象池:
- 我们在服务器开发的过程中,往往会有一些对象,它的创建和初始化需要的时间比较长,比如数据库连接,网络IO,大数据对象等。在大量使用这些对象时,如果不采用一些技术优化,就会造成一些不可忽略的性能影响。
- 一种办法就是使用对象池,每次创建的对象并不实际销毁,而是缓存在对象池中,下次使用的时候,不用再重新创建,直接从对象池的缓存中取即可。
- 为了避免重新造轮子,我们可以使用优秀的开源对象池化组件apache-common-pool2,它对对象池化操作进行了很好的封装,我们只需要根据自己的业务需求重写或实现部分接口即可,使用它可以快速的创建一个方便,简单,强大对象连接池管理类。
Common-pool对象池:
- Apache Common-pool2包提供了一个通用的对象池技术的实现。可以很方便的基于它来实现自己的对象池,
比如DBCP和Jedis他们的内部对象池的实现就是依赖于Common-pool2。 - 对象的创建和销毁在一定程度上会消耗系统的资源,虽然jvm的性能在近几年已经得到了很大的提高,对于多数对象来说,没有必要利用对象池技术来进行对象的创建和管理。但是对于有些对象来说,其创建的代价还是比较昂贵的,比如线程、tcp连接、数据库连接等对象,因此对象池技术还是有其存在的意义。
- Common-pool2由三大模块组成:ObjectPool、PooledObject和PooledObjectFactory:
①
ObjectPool:提供所有对象的存取管理。 ②PooledObject:池化的对象,是对对象的一个包装,加上了对象的一些其他信息,包括对象的状态(已用、空闲),对象的创建时间等。 ③PooledObjectFactory:工厂类,负责池化对象的创建,对象的初始化,对象状态的销毁和对象状态的验证。
对象池的常见配置一览:
| 配置参数 | 意义 | 默认值 |
|---|---|---|
| maxTotal | 对象总数 | 8 |
| maxIdle | 最大空闲对象数 | 8 |
| minIdle | 最小空闲对象数 | 0 |
| lifo | 对象池借还是否采用lifo | true |
| fairness | 对于借对象的线程阻塞恢复公平性 | false |
| maxWaitMillis | 借对象阻塞最大等待时间 | -1 |
| minEvictableIdleTimeMillis | 最小驱逐空闲时间 | 30分钟 |
| numTestsPerEvictionRun | 每次驱逐数量 | 3 |
| testOnCreate | 创建后有效性测试 | false |
| testOnBorrow | 出借前有效性测试 | false |
| testOnReturn | 还回前有效性测试 | false |
| testWhileIdle | 空闲有效性测试 | false |
| timeBetweenEvictionRunsMillis | 驱逐定时器周期 | false |
| blockWhenExhausted | 对象池耗尽是否block | true |
Jedis与Redisson与Lettuce选型
Jedis技术特点:
- Jedis 是老牌的 Redis 的 Java 实现客户端,提供了比较全面的 Redis 命令的支持,包括redis 2.8.x and 3.x.x。
- 支持Redis cluster(集群),支持sentinel哨兵模式。
- 使用阻塞的 I/O,且其方法调用都是同步的,程序流需要等到 sockets 处理完 I/O 才能执行,不支持异步。
- 不是线程安全的,所以需要通过连接池来使用 Jedis。
Jedis 在实现上是直接连接 Redis-Server,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程场景下使用 Jedis,需要使用连接池,每个线程都使用自己的 Jedis实例,当连接数量增多时,会消耗较多的物理资源。
Lettuce技术特点:
- lettuce是一种可扩展的线程安全的 Redis客户端。
- 支持异步通信模式。
- Lettuce 的 API 是线程安全的,如果不是执行阻塞和事务操作,如BLPOP和MULTI/EXEC,多个线程就可以共享一个连接。
- lettuce 底层基于Netty,支持高级的 Redis 特性,比如哨兵,集群,管道,自动重新连接和Redis数据模型。
Lettuce需要依赖 Apache的 common-pool2(至少是2.2)提供连接池。
Redisson技术特点:
- Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务。其中包括( BitSet, Set,Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque,BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish /Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson 提供了使用Redis的最简单和最便捷的方法。Redisson 的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
- Redisson有两个版本:Open-source edition和PRO edition, 它的主要技术特点就是数据网格(In-Memory Data Grid),它支持的集群模式有:Redis Cluster、Sentinel哨兵模式、Master/Slave、Single单节点。下面给出 Pro 版本和开源版本的功能对比。
- 优点: ①使用者对 Redis 的关注分离,可以类比 Spring 框架,这些框架搭建了应用程序的基础框架和功能,提升开发效率,让开发者有更多的时间来关注业务逻辑; ②提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列等。
- 缺点: ①Redisson 对字符串的操作支持比较差。
总结:
结论:lettuce + Redisson在springboot 1.5.x版本的默认的Redis客户端是Jedis实现的,springboot2.x版本中默认客户端是用 lettuce实现的。这就一定程度说明了lettuce 和Jedis的优劣。- Jedis 和 lettuce 是比较纯粹的 Redis 客户端,几乎没提供什么高级功能。Jedis 的性能比较差,所以如果你不需要使用Redis 的高级功能的话,优先推荐使用 lettuce。
- Redisson 的优势是提供了很多开箱即用的 Redis 高级功能,如果你的应用中需要使用到 Redis 的高级功能,建议使用Redisson。
- 如果使用redis连接池,无论lettuce还是jedis客户端,都需要导入common-pool2依赖。
- 链接: ①Lettuce官网 ②Redisson官网 ③Jedis官网 ④springboot2.0 集成redis服务详解,以及 (Lettuce & Jedis)
RedisTemplate
RedisTemplate简介:
- Jedis是Redis官方推荐的面向Java的操作Redis的客户端,而RedisTemplate是SpringDataRedis中对JedisApi的高度封装。
- SpringDataRedis相对于Jedis来说可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache
- RedisTemplate是操作的入口.该类继承了RedisAccessor,可以通过其拿到redis连接,实现了RedisOperations接口,获得了操作redis的能力,如下图所示::
一个简单的测试案例:①会发现存入了数据,但是get("ping")获取不到值。
//往redis中设置key为ping的字串.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class RedisConnectTest {
@Resource
private RedisTemplate<String,String> redisTemplate;
@Test
public void testSetAndGet() {
redisTemplate.opsForValue().set("ping","pong");
System.out.println(redisTemplate.opsForValue().get("ping"));
}
}
XXXOperations:①XXXOperations对应测试案例中的opsForValue(),意思是操作什么类型的数据。②在RedisTemplate中同样还有其他XXXOperations,根据官方文档,这些接口是针对redis的每一种命令的操作。 ③其中BoundXXXOperations是在key已知的情况下使用,其所有操作都是建立在有一个certain key的前提. ④如下表:XXXSerializer:①XXXSerializer对应测试案例中的set(key,velue),意思是如何序列化对应的类型数据。②rawKey()方法和rawValue()方法对key和value进行了一次序列化操作.该序列化使用的类为RedisTemplate中的XXXSerializer。 ③那么回到RedisTemplate,在afterPropertiesSet()方法中有以下初始化方法,默认使用的序列化方式为JdkSerializationRedisSerializer,也就是ObjectInputStream和ObjectOutputStream写入和读取,这也是上述测试案例中写入到redis中却在redis数据库通过”ping”访问不到的原因。 ④那么SpringDataRedis支持的序列化: <1>StringRedisSerializer::string类型序列化,也是最常用的类型<2>JdkSerializationRedisSerializer:jdk默认序列化<3>OxmSerializer :xml格式<4>JacksonJsonRedisSerializer :json格式⑤通过手动注入RedisTemplate,更改所选择的序列化方式.另外Spring提供了最常使用的StringRedisTemplate,实现了StringRedisSerializer序列化方式。
----------------------set()方法----------------------
public void set(K key, V value) {
final byte[] rawValue = rawValue(value);
execute(new ValueDeserializingRedisCallback(key) {
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
connection.set(rawKey, rawValue);
return null;
}
}, true);
}
----------------------JdkSerializationRedisSerializer----------------------
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
----------------------StringRedisTemplate----------------------
public StringRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
setKeySerializer(stringSerializer);
setValueSerializer(stringSerializer);
setHashKeySerializer(stringSerializer);
setHashValueSerializer(stringSerializer);
}
- 总结过程:
①
获取RedisTemplate②获取操作入口XXXOperations③使用RedisSerializer序列化key和value④获取conn连接⑤执行命令
RedisTemplate高级
RedisTemplate事务:
- redis客户端执行multi后返回ok,表明redis进入事务状态。进入事务状态以后redis并不会立即执行命令,会将redis客户端发送的命令存入队列,暂不执行,此时返回queued。最后调用exec,将命令从队列中取出来,然后一次性执行,这些,命令同时成功同时失败,最后将命令执行结果一次性返回,并且将事务状态标志复位。在执行这些命令的过程中,使用同一客户端,并且不会被其它客户端中断。
- 在Spring Data Redis提供了RedisTemplate对redis进行读写操作并且支持事务,先配置开启事务。
redisTemplate.setEnableTransactionSupport(true);
- 然后在要使用事务的方法上面添加@Transactional,这样就使用了redis的事务了。
- 链接:Spring Data Redis 事务
RedisTemplate发布订阅:
- 生产者发布消息:
@Component
public class Publisher {
@Autowired
@Qualifier(value = "customRedisTemplate")
private RedisTemplate redisTemplate;
/**
* 向指定频道发布消息
* @param channel 频道名称
* @param object 消息
*/
public void publish(String channel,Object object){
redisTemplate.convertAndSend(channel,object);
}
- 消费者订阅配置:
@Configuration
public class MessageConfig {
@Autowired
private Subscriber subscriber;
@Autowired
@Qualifier(value = "customRedisTemplate")
private RedisTemplate redisTemplate;
/**
* RedisMessageListenerContainer充当消息侦听器容器。
* 它用于从Redis通道接收消息并驱动注入其中的MessageListener实例。
* 侦听器容器负责消息接收的所有线程并将其分派到侦听器进行处理。
* 消息监听器容器是MDP和消息传递提供者之间的中介,并负责注册以接收消息,资源获取和释放,异常转换等。
*
* 此外,为了最小化应用程序占用空间,RedisMessageListenerContainer允许多个侦听器共享一个连接和一个线程,即使它们不共享订阅。
* 因此,无论应用程序跟踪多少个侦听器或通道,运行时成本在其整个生命周期内保持不变。
* 此外,容器允许更改运行时配置,以便您可以在应用程序运行时添加或删除侦听器,而无需重新启动。
* 此外,容器使用延迟订阅方法,仅在需要时使用RedisConnection。
* 如果所有侦听器都已取消订阅,则会自动执行清理,并释放该线程。
* 为了帮助消息的异步性,容器需要一个java.util.concurrent.Executor(或Spring的TaskExecutor)来分派消息。
* 根据负载,侦听器数量或运行时环境,您应该更改或调整执行程序以更好地满足您的需求。 强烈建议选择适当的TaskExecutor来利用其运行时。
* @param listenerAdapter
* @return
*/
@Bean
RedisMessageListenerContainer container(MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisTemplate.getConnectionFactory());
List<Topic> topicList = new ArrayList<>();
topicList.add(new PatternTopic("testChannel"));
container.addMessageListener(listenerAdapter, topicList);
return container;
}
/**
* 消息侦听器适配器,能将消息委托给目标侦听器方法
* @return
*/
@Bean
MessageListenerAdapter listenerAdapter() {
return new MessageListenerAdapter(subscriber);
}
}
- 消费者订阅:
@Component
public class Subscriber implements MessageListener{
@Autowired
@Qualifier(value = "customRedisTemplate")
private RedisTemplate redisTemplate;
/**
* 每次新消息到达时,都会调用回调
* @param message
* @param pattern
*/
@Override
public void onMessage(Message message, byte[] pattern) {
RedisSerializer<?> keySerializer = redisTemplate.getKeySerializer();
RedisSerializer<?> valueSerializer = redisTemplate.getValueSerializer();
Object channel = keySerializer.deserialize(message.getChannel());
Object body = valueSerializer.deserialize(message.getBody());
System.out.println("渠道: " + channel);
System.out.println("消息内容: " + String.valueOf(body));
}
}
RedisTemplate高级:
- 此外还有其他许多操作: ①集群 ②哨兵 ⑤等等
- 链接:SpringDataRedis分析