SpringDataRedis知识概括

207 阅读11分钟

@[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对象池借还是否采用lifotrue
fairness对于借对象的线程阻塞恢复公平性false
maxWaitMillis借对象阻塞最大等待时间-1
minEvictableIdleTimeMillis最小驱逐空闲时间30分钟
numTestsPerEvictionRun每次驱逐数量3
testOnCreate创建后有效性测试false
testOnBorrow出借前有效性测试false
testOnReturn还回前有效性测试false
testWhileIdle空闲有效性测试false
timeBetweenEvictionRunsMillis驱逐定时器周期false
blockWhenExhausted对象池耗尽是否blocktrue

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高级: