Spring集成Redis

最近在做一个关于群聊的项目,由于聊天要求实时性,不可能直接访问数据库,所以使用了Redis来做缓存,这里将使用过程中遇到的问题记录一下。
使用Redis之前需要合理设计存储在其中的数据结构,比如聊天系统,就需要存储包括用户登录状态信息,可以使用Hash数据结构,以用户Id做键,状态做值,还可以存储每个用户在每个群的未读消息计数,
客户端使用长轮询拉取。
最主要的就是群里的聊天记录的缓存了,这里可以使用Redis的List数据结构来做消息队列,其中按顺序存储消息实体对象(序列化后存入Redis)。还可以维护一个List存储聊天记录的主键便于定位聊天记录,等等的一系列数据都可以缓存到Redis。
Redis在Java中的实现是Jedis,其中封装了连接Redis的连接池实现、对Redis数据库的各种操作等。
在JavaWeb项目中使用Redis做缓存时有两种实现方式,第一种是使用原生的Jedis包,其中封装的方法可以直接操作Redis。第二种方法是使用Spring框架集成的Redis,其中封装Jedis对Redis的各种操作,包括事务、连接、连接池的维护等,其对Redis的操作进行了很多封装。
这个聊天项目一开始采用的是Spring封装的Redis,使用过程中发现,经常会报一些类型转换异常的问题,后来发现这种错误经常在网络连接异常或是长轮询的地方出现。深入底层发现是由于Redis底层的Socket连接使用了缓冲区,导致有些时候如果前面的连接出现问题,后面的请求再使用前面的连接会获取到前面连接的数据,导致类型转换异常的出现,
尤其是长轮询不断请求Redis服务器的时候,问题出现的频率尤其高,而且一旦出现问题,后面的连接都会出问题,这也许是由于Redis连接池中出问题的连接没有完全释放的原因。
因此建议大家如果使用Redis做缓存,最好使用原生的Jedis,这样可以在连接异常时自己实现异常连接的处理,而使用Spring集成的Redis由于连接已经被封装了,限制了自己对异常连接的处理。
下面就将两种集成Redis的方法分别介绍一下。
第一种是使用Spring集成的Redis。

1、首先是Jar包,需要jedis包、Spring集成Redis的包spring-data-redis、Redis连接池需要的commons-pool2包,其中spring-data-redis包和jedis包的版本需要适配,否则会报错,具体要求网上可以查到,这3个包的下载可以从Maven仓库获取。

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
	<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
	</dependency>
<!-- spring-data-redis2.0.7版本下载失败 -->
	<dependency>
	   <groupId>org.springframework.data</groupId>
	   <artifactId>spring-data-redis</artifactId>
	   <version>1.8.4.RELEASE</version>
	</dependency>
	<dependency>
	   <groupId>redis.clients</groupId>
	   <artifactId>jedis</artifactId>
	   <version>2.9.0</version>
	</dependency>
2、Spring集成Redis的Xml配置

2.1、Redis.properties配置(相当于jdbc.properties)

#============================#
#==== Redis settings ====#
#============================#
#redis 服务器 IP
redis.host=192.168.1.222
#192.168.1.222

#格式:redis://:[密码]@[服务器地址]:[端口]/[db index]
redis.uri = redis://:12345@127.0.0.1:6379/0

#redis 服务器端口
redis.port=6379

#redis 密码
redis.pass=
#redis#2018

#超时时间
redis.timeOut=2000
#redis 支持16个数据库(相当于不同用户)可以使不同的应用程序数据彼此分开同时又存储在相同的实例上
redis.dbIndex=0

#redis 缓存数据过期时间单位秒
redis.expiration=3000

#连接池中最大连接数。高版本:maxTotal,低版本:maxActive
#控制一个 pool 可分配多少个jedis实例
redis.maxActive=6

#连接池中最少空闲的连接数
redis.minIdle=1

#连接池中最大空闲的连接数,控制一个 pool 最多有多少个状态为 idle 的jedis实例
redis.maxIdle=300

#当连接池资源耗尽时,调用者最大阻塞的时间,超时将抛出异常。单位,毫秒数;默认为-1.表示永不超时。高版本:maxWaitMillis,低版本:maxWait
#当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;
redis.maxWait=1000

#连接空闲的最小时间,达到此值后空闲连接将可能会被移除。负值(-1)表示不移除
redis.minEvictableIdleTimeMillis=60000

#对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3
redis.numTestsPerEvictionRun=3

#“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1
redis.timeBetweenEvictionRunsMillis=30000

#testOnBorrow:向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为false。建议保持默认值
#在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的;
redis.testOnBorrow=true

#testOnReturn:向连接池“归还”链接时,是否检测“链接”对象的有效性。默认为false。建议保持默认值
#testWhileIdle:向调用者输出“链接”对象时,是否检测它的空闲超时;默认为false。如果“链接”空闲超时,将会被移除。建议保持默认值
#whenExhaustedAction:当“连接池”中active数量达到阀值时,即“链接”资源耗尽时,连接池需要采取的手段, 默认为1(0:抛出异常。1:阻塞,直到有可用链接资源。2:强制创建新的链接资源)

2.2、applicationContext.xml引入redis.properties文件

<!-- 这里配置数据库properties文件位置 -->
	<!-- 使用PropertyPlaceholderConfigurer加载redis.properties文件失败改用PropertySourcesPlaceholderConfigurer -->
	<bean id="propertyConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
		<property name="locations">
		    <list>
			    <value>classpath:jdbc.properties</value>
			    <value>classpath:system.properties</value>	
			    <value>classpath:redis.properties</value>
			</list>
		</property>
	</bean>

将Redis相关配置单独放在一个配置文件中:

<import resource="spring-redis-context.xml" /> 

spring-redis-context.xml中的配置包括,连接池的配置、连接工厂的配置以及RedisTemplate类的相关配置。

<?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-3.2.xsd
                       http://www.springframework.org/schema/context
                       http://www.springframework.org/schema/context/spring-context.xsd">
   <description>Spring容器管理redis连接和使用 </description>

   <!--载入 redis 配置文件-->
   <!-- Spring容器仅允许最多定义一个PropertyPlaceholderConfigurer 或 <content:property-placeholder>其余的会被Spring忽略 -->
   <!-- <context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"/> -->
   
   <!-- 高版本Jedis没有maxActive变量,具体有哪些可以看JedisPoolConfig继承的类GenericObjectPoolConfig -->
   <!-- 连接池配置 -->
   <!-- 配置 JedisPoolConfig 实例 -->
   <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
       <property name="maxIdle" value="${redis.maxIdle}"/>
       <property name="maxTotal" value="${redis.maxActive}"/>
       <property name="maxWaitMillis" value="${redis.maxWait}"/>
       <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
   </bean>
   
   <!-- <bean id="clusterRedisNodes1" class="org.springframework.data.redis.connection.RedisNode">
   		<constructor-arg value="${redis.host}" />
   		<constructor-arg value="${redis.port}" type="int"/>
   </bean> -->
   
   <!-- ERR This instance has cluster support disabled -->
   <!-- <bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
   		<property name="maxRedirects" value="3"></property>
   		节点配置
   		<property name="clusterNodes">
   			<set>
   				<bean class="org.springframework.data.redis.connection.RedisClusterNode">
   					<constructor-arg name="host" value="${redis.host}"/>
   					<constructor-arg name="port" value="${redis.port}"/>
   				</bean>
   				<bean class="org.springframework.data.redis.connection.RedisClusterNode">
   					<constructor-arg name="host" value="192.168.1.111"/>
   					<constructor-arg name="port" value="6380"/>
   				</bean>
   			</set>
   		</property>
   </bean> -->
   
	<!-- Spring提供的Redis连接工厂 -->
   <!-- 配置JedisConnectionFactory -->
   <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
       <property name="hostName" value="${redis.host}"/>
       <property name="port" value="${redis.port}"/>
       <property name="password" value="${redis.pass}"/>
       <property name="database" value="${redis.dbIndex}"/> 
       <property name="poolConfig" ref="poolConfig"/>
       <!-- <constructor-arg ref="redisClusterConfiguration"></constructor-arg>
       <constructor-arg ref="poolConfig"></constructor-arg> -->
       <!-- <property name="clusterConfig" ref="redisClusterConfiguration"></property> -->
   </bean>
	<bean id="redisConfig" class="com.teriste.redis.util.RedisConfig"></bean>
   <!-- Spring提供的访问Redis类 -->
   <!-- 配置RedisTemplate -->
   <!-- <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" scope="prototype">
       <property name="connectionFactory" ref="jedisConnectionFactory"/>
       下面是设置key和value序列化的方式,不同方式导致存入的字节序列不一样,什么样的序列化方式就要使用相应的反序列化方式查询,否则差不多redis存入的数据
       由此也可以知道spring对redis序列化有很多种方式
       <property name="keySerializer">
			<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="valueSerializer">
			defaultSerializer是JdkSerializationRedisSerializer在调试时看不出来键是什么样的
			<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
			<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
			fastjson序列化
			<bean class="com.teriste.redis.util.FastJson2JsonRedisSerializer">
				<constructor-arg name="clazz" value=Object.class></constructor-arg>
			</bean>
		</property>
		<property name="hashKeySerializer">
			<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		配置true可以使用transactional控制事务,spring已经提供支持
		<property name="enableTransactionSupport" value="true"/>
   </bean> -->

   <!-- 配置RedisCacheManager -->
   <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
       <constructor-arg name="redisOperations" ref="redisTemplate"/>
       <property name="defaultExpiration" value="${redis.expiration}"/>
   </bean>

   <!-- 配置RedisCacheConfig -->
   <!-- <bean id="redisCacheConfig" class="com.teriste.redis.util.RedisCacheConfig">
       <constructor-arg ref="jedisConnectionFactory"/>
       <constructor-arg ref="redisTemplate"/>
       <constructor-arg ref="redisCacheManager"/>
   </bean> -->
   
   <!-- Redis sentinel集群配置 -->
	<!-- <bean id="sentinelConfig" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
	    <constructor-arg index="0" type="java.lang.String" value="host6379" />
	    <constructor-arg index="1" type="java.util.Set">
	        <set>
	            <value>192.168.1.111:6380</value>
	            <value>192.168.1.111:6381</value>
	        </set>
	    </constructor-arg>
	</bean> -->
	<!-- Spring提供的Redis连接工厂 -->
	<!-- <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
	    Redis sentinel集群配置
	    <constructor-arg index="0" type="org.springframework.data.redis.connection.RedisSentinelConfiguration" ref="sentinelConfig" />
	    连接池配置.
	    <constructor-arg index="1" type="redis.clients.jedis.JedisPoolConfig" ref="poolConfig" />
	    
	    Redis服务主机.
	    <property name="hostName" value="192.168.1.111" />
	    Redis服务端口号.
	    <property name="port" value="26379" />
	    Redis服务连接密码.
	    <property name="password" value="${redis.pass}" />
	    连超时设置.
	    <property name="timeout" value="15000" />
	    是否使用连接池.
	    <pr
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值