Spring集成Redis做cacheable 缓存时间,Redis宕机时Spring处理的问题

spring结合redis如何实现数据的缓存
作者:WhyWin
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了spring结合redis如何实现数据的缓存,实现的目的目的不是加快查询的速度,而是减少数据库的负担,需要的朋友可以参考下
1、实现目标
  通过redis缓存数据。(目的不是加快查询的速度,而是减少数据库的负担)  
2、所需jar包
  注意:jdies和commons-pool两个jar的版本是有对应关系的,注意引入jar包是要配对使用,否则将会报错。因为commons-pooljar的目录根据版本的变化,目录结构会变。前面的版本是org.apache.pool,而后面的版本是org.apache.pool2...
style="background-color: #0098 color: font-size: 17 font-weight:"3、redis简介
  redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)
3、编码实现
1)、配置的文件(properties)
  将那些经常要变化的参数配置成独立的propertis,方便以后的修改redis.properties
redis.hostName=127.0.0.1
redis.port=6379
redis.timeout=15000
redis.usePool=true
redis.maxIdle=6
redis.minEvictableIdleTimeMillis=300000
redis.numTestsPerEvictionRun=3
redis.timeBetweenEvictionRunsMillis=60000
2)、spring-redis.xml
  redis的相关参数配置设置。参数的值来自上面的properties文件
&beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName"&
&bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"&
&!-- &property name="maxIdle" value="6"&&/property&
&property name="minEvictableIdleTimeMillis" value="300000"&&/property&
&property name="numTestsPerEvictionRun" value="3"&&/property&
&property name="timeBetweenEvictionRunsMillis" value="60000"&&/property& --&
&property name="maxIdle" value="${redis.maxIdle}"&&/property&
&property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"&&/property&
&property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"&&/property&
&property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"&&/property&
&bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"&
&property name="poolConfig" ref="jedisPoolConfig"&&/property&
&property name="hostName" value="${redis.hostName}"&&/property&
&property name="port" value="${redis.port}"&&/property&
&property name="timeout" value="${redis.timeout}"&&/property&
&property name="usePool" value="${redis.usePool}"&&/property&
&bean id="jedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"&
&property name="connectionFactory" ref="jedisConnectionFactory"&&/property&
&property name="keySerializer"&
&bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/&
&/property&
&property name="valueSerializer"&
&bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/&
&/property&
3)、applicationContext.xml
  spring的总配置文件,在里面假如一下的代码
&bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"&
&property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" /&
&property name="ignoreResourceNotFound" value="true" /&
&property name="locations"&
&value&classpath*:/META-INF/config/redis.properties&/value&
&/property&
&import resource="spring-redis.xml" /&
4)、web.xml
  设置spring的总配置文件在项目启动时加载
&context-param&
&param-name&contextConfigLocation&/param-name&
&param-value&classpath*:/META-INF/applicationContext.xml&/param-value&&!-- --&
&/context-param&
5)、redis缓存工具类
ValueOperations  ——基本数据类型和实体类的缓存
ListOperations  && ——list的缓存
SetOperations  & ——set的缓存
HashOperations  Map的缓存
import java.io.S
import java.util.ArrayL
import java.util.HashM
import java.util.HashS
import java.util.I
import java.util.L
import java.util.M
import java.util.S
import org.springframework.beans.factory.annotation.A
import org.springframework.beans.factory.annotation.Q
import org.springframework.context.support.ClassPathXmlApplicationC
import org.springframework.data.redis.core.BoundSetO
import org.springframework.data.redis.core.HashO
import org.springframework.data.redis.core.ListO
import org.springframework.data.redis.core.RedisT
import org.springframework.data.redis.core.SetO
import org.springframework.data.redis.core.ValueO
import org.springframework.stereotype.S
public class RedisCacheUtil&T&
@Autowired @Qualifier("jedisTemplate")
public RedisTemplate redisT
* 缓存基本的对象,Integer、String、实体类等
* @param key 缓存的键值
* @param value 缓存的值
缓存的对象
public &T& ValueOperations&String,T& setCacheObject(String key,T value)
ValueOperations&String,T& operation = redisTemplate.opsForValue();
operation.set(key,value);
* 获得缓存的基本对象。
* @param key
* @param operation
缓存键值对应的数据
public &T& T getCacheObject(String key/*,ValueOperations&String,T& operation*/)
ValueOperations&String,T& operation = redisTemplate.opsForValue();
return operation.get(key);
* 缓存List数据
* @param key
缓存的键值
* @param dataList 待缓存的List数据
缓存的对象
public &T& ListOperations&String, T& setCacheList(String key,List&T& dataList)
ListOperations listOperation = redisTemplate.opsForList();
if(null != dataList)
int size = dataList.size();
for(int i = 0; i & i ++)
listOperation.rightPush(key,dataList.get(i));
return listO
* 获得缓存的list对象
* @param key 缓存的键值
缓存键值对应的数据
public &T& List&T& getCacheList(String key)
List&T& dataList = new ArrayList&T&();
ListOperations&String,T& listOperation = redisTemplate.opsForList();
Long size = listOperation.size(key);
for(int i = 0 ; i & i ++)
dataList.add((T) listOperation.leftPop(key));
return dataL
* @param key
* @param dataSet 缓存的数据
缓存数据的对象
public &T& BoundSetOperations&String,T& setCacheSet(String key,Set&T& dataSet)
BoundSetOperations&String,T& setOperation = redisTemplate.boundSetOps(key);
/*T[] t = (T[]) dataSet.toArray();
setOperation.add(t);*/
Iterator&T& it = dataSet.iterator();
while(it.hasNext())
setOperation.add(it.next());
return setO
* 获得缓存的set
* @param key
* @param operation
public Set&T& getCacheSet(String key/*,BoundSetOperations&String,T& operation*/)
Set&T& dataSet = new HashSet&T&();
BoundSetOperations&String,T& operation = redisTemplate.boundSetOps(key);
Long size = operation.size();
for(int i = 0 ; i & i++)
dataSet.add(operation.pop());
return dataS
* @param key
* @param dataMap
public &T& HashOperations&String,String,T& setCacheMap(String key,Map&String,T& dataMap)
HashOperations hashOperations = redisTemplate.opsForHash();
if(null != dataMap)
for (Map.Entry&String, T& entry : dataMap.entrySet()) {
/*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); */
hashOperations.put(key,entry.getKey(),entry.getValue());
return hashO
* 获得缓存的Map
* @param key
* @param hashOperation
public &T& Map&String,T& getCacheMap(String key/*,HashOperations&String,String,T& hashOperation*/)
Map&String, T& map = redisTemplate.opsForHash().entries(key);
/*Map&String, T& map = hashOperation.entries(key);*/
* @param key
* @param dataMap
public &T& HashOperations&String,Integer,T& setCacheIntegerMap(String key,Map&Integer,T& dataMap)
HashOperations hashOperations = redisTemplate.opsForHash();
if(null != dataMap)
for (Map.Entry&Integer, T& entry : dataMap.entrySet()) {
/*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); */
hashOperations.put(key,entry.getKey(),entry.getValue());
return hashO
* 获得缓存的Map
* @param key
* @param hashOperation
public &T& Map&Integer,T& getCacheIntegerMap(String key/*,HashOperations&String,String,T& hashOperation*/)
Map&Integer, T& map = redisTemplate.opsForHash().entries(key);
/*Map&String, T& map = hashOperation.entries(key);*/
  这里测试我是在项目启动的时候到数据库中查找出国家和城市的数据,进行缓存,之后将数据去除。
6.1& 项目启动时缓存数据
import java.util.HashM
import java.util.L
import java.util.M
import org.apache.log4j.L
import org.springframework.beans.factory.annotation.A
import org.springframework.context.ApplicationL
import org.springframework.context.event.ContextRefreshedE
import org.springframework.stereotype.S
import com.test.model.C
import com.test.model.C
import com.zcr.test.U
* 监听器,用于项目启动的时候初始化信息
public class StartAddCacheListener implements ApplicationListener&ContextRefreshedEvent&
private final Logger log= Logger.getLogger(StartAddCacheListener.class);
@Autowired
private RedisCacheUtil&Object& redisC
@Autowired
private BrandStoreService brandStoreS
public void onApplicationEvent(ContextRefreshedEvent event)
//spring 启动的时候缓存城市和国家等信息
if(event.getApplicationContext().getDisplayName().equals("Root WebApplicationContext"))
System.out.println("\n\n\n_________\n\n缓存数据 \n\n ________\n\n\n\n");
List&City& cityList = brandStoreService.selectAllCityMessage();
List&Country& countryList = brandStoreService.selectAllCountryMessage();
Map&Integer,City& cityMap = new HashMap&Integer,City&();
Map&Integer,Country& countryMap = new HashMap&Integer, Country&();
int cityListSize = cityList.size();
int countryListSize = countryList.size();
for(int i = 0 ; i & cityListS i ++ )
cityMap.put(cityList.get(i).getCity_id(), cityList.get(i));
for(int i = 0 ; i & countryListS i ++ )
countryMap.put(countryList.get(i).getCountry_id(), countryList.get(i));
redisCache.setCacheIntegerMap("cityMap", cityMap);
redisCache.setCacheIntegerMap("countryMap", countryMap);
6.2& 获取缓存数据
@Autowired
private RedisCacheUtil&User& redisC
@RequestMapping("testGetCache")
public void testGetCache()
/*Map&String,Country& countryMap = redisCacheUtil1.getCacheMap("country");
Map&String,City& cityMap = redisCacheUtil.getCacheMap("city");*/
Map&Integer,Country& countryMap = redisCacheUtil1.getCacheIntegerMap("countryMap");
Map&Integer,City& cityMap = redisCacheUtil.getCacheIntegerMap("cityMap");
for(int key : countryMap.keySet())
System.out.println("key = " + key + ",value=" + countryMap.get(key));
System.out.println("------------city");
for(int key : cityMap.keySet())
System.out.println("key = " + key + ",value=" + cityMap.get(key));
由于Spring在配置文件中配置的bean默认是单例的,所以只需要通过Autowired注入,即可得到原先的缓存类。
以上就是spring+redis实现数据缓存的方法,希望对大家的学习有所帮助。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具Redis与spring的整合
相关依赖jar包
spring把专门的数据操作独立封装在spring-data系列中,spring-data-redis是对Redis的封装
&dependencies&
&!-- 添加spring-data的支持 --&
&dependency&
&groupId&org.springframework.data&/groupId&
&artifactId&spring-data-redis&/artifactId&
&version&1.4.2.RELEASE&/version&
&/dependency&
&dependency&
&groupId&redis.clients&/groupId&
&artifactId&jedis&/artifactId&
&version&2.6.2&/version&
&/dependency&
&dependency&
&groupId&mons&/groupId&
&artifactId&commons-pool2&/artifactId&
&version&2.4.2&/version&
&/dependency&
&dependency&
&groupId&javax.persistence&/groupId&
&artifactId&persistence-api&/artifactId&
&version&1.0.2&/version&
&/dependency&
&!-- 添加junit支持 --&
&dependency&
&groupId&junit&/groupId&
&artifactId&junit&/artifactId&
&version&4.12&/version&
&/dependency&
&dependency&
&groupId&org.springframework&/groupId&
&artifactId&spring-test&/artifactId&
&version&4.3.2.RELEASE&/version&
&/dependency&
&dependency&
&groupId&commons-logging&/groupId&
&artifactId&commons-logging&/artifactId&
&version&1.2&/version&
&/dependency&
&/dependencies&
Spring&配置文件applicationContext.xml
&?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schema/jaxws.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"&
&!-- 命令空间加入上面这行
&context:property-placeholder location="classpath:redis.properties"/&
&bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"&
&property name="maxIdle" value="${redis.maxIdle}" /&
&property name="maxTotal" value="${redis.maxTotal}" /&
&property name="MaxWaitMillis" value="${redis.MaxWaitMillis}" /&
&property name="testOnBorrow" value="${redis.testOnBorrow}" /&
&bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig" /&
&bean id="redisTemplate" class="org.spring.framework.data.redis.core.RedisTemplate"&
&property name="connectionFactory" ref="connectionFactory" /&
注意新版的maxTotal,MaxWaitMillis这两个字段与旧版的不同。
redis连接池配置文件redis.properties
redis.host=192.168.2.129
redis.port=6379
redis.pass=redis129
redis.maxIdle=300
redis.maxTotal=600
redis.MaxWaitMillis=1000
redis.testOnBorrow=true
好了,配置完成,下面写上代码
import javax.persistence.E
import javax.persistence.T
* @date 创建时间:日 上午8:51:02
* @parameter
@Table(name= "t_user")
public class User {
private String userN
public String getId() {
public void setId(String id) {
public String getUserName() {
return userN
public void setUserName(String userName) {
this.userName = userN
BaseRedisDao
import org.springframework.beans.factory.annotation.A
import org.springframework.data.redis.core.RedisT
* @date 创建时间:日 上午9:02:16
* @parameter
public class BaseRedisDao&K,V& {
@Autowired(required=true)
protected RedisTemplate&K,V& redisT
public interface IUserDao {
public boolean save(User user);
public boolean update(User user);
public boolean delete(String userIds);
public User find(String userId);
package com.shanheyongmu.
import org.springframework.dao.DataAccessE
import org.springframework.data.redis.connection.RedisC
import org.springframework.data.redis.core.RedisC
import org.springframework.data.redis.core.RedisT
import org.springframework.data.redis.serializer.RedisS
import com.shanheyongmu.entity.U
* @date 创建时间:日 上午9:08:28
* @parameter
public class UserDao extends BaseRedisDao&String, User& implements IUserDao
public boolean save(final User user) {
boolean res = redisTemplate.execute(new RedisCallback&Boolean&() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer&String& serializer = redisTemplate.getStringSerializer();
byte[] key = serializer.serialize(user.getId());
byte[] value = serializer.serialize(user.getUserName());
//set not exits
return connection.setNX(key, value);
public boolean update(final User user) {
boolean result = redisTemplate.execute(new RedisCallback&Boolean&() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer&String& serializer = redisTemplate.getStringSerializer();
byte[] key = serializer.serialize(user.getId());
byte[] name = serializer.serialize(user.getUserName());
connection.set(key, name);
return true;
public boolean delete(final String userId) {
boolean result = redisTemplate.execute(new RedisCallback&Boolean&() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer&String& serializer = redisTemplate.getStringSerializer();
byte[] key = serializer.serialize(userId);
connection.del(key);
return true;
public User find(final String userId) {
User result = redisTemplate.execute(new RedisCallback&User&() {
public User doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer&String& serializer = redisTemplate.getStringSerializer();
byte[] key = serializer.serialize(userId);
byte[] value = connection.get(key);
if(value == null) {
return null;
String name = serializer.deserialize(value);
User resUser = new User();
resUser.setId(userId);
resUser.setUserName(name);
return resU
写这个类的时候需要引入junit包和spring-test.jar
package com.shanheyongmu.
import org.junit.A
import org.junit.T
import org.junit.runner.RunW
import org.springframework.beans.factory.annotation.A
import org.springframework.test.context.ContextC
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextT
import org.springframework.test.context.junit4.SpringJUnit4ClassR
import com.shanheyongmu.dao.IUserD
import com.shanheyongmu.entity.U
* @date 创建时间:日 上午10:42:55
* @parameter
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
public class RedisTest extends AbstractJUnit4SpringContextTests {
@Autowired
private IUserDao userD
public void testSaveUser() {
User user = new User();
user.setId("e8de0b0000");
user.setUserName("zhangsan");
public void testGetUser() {
User user = new User();
user = userDao.find("e8de0b0000");
System.out.println(user.getId() + "-" +user.getUserName());
public void testUpdateUser() {
User user = new User();
user.setId("e8de0b0000");
user.setUserName("lisi");
boolean res = userDao.update(user);
Assert.assertTrue(res);
public void testDeleteUser() {
boolean res = userDao.delete("e8de0b0000");
Assert.assertTrue(res);
String类型的增删该查已完成,Hash,List,Set数据类型的操作就不举例了,和使用命令的方式差不多。如下
connection.hSetNX(key, field, value);
connection.hDel(key, fields);
connection.hGet(key, field);
connection.lPop(key);
connection.lPush(key, value);
connection.rPop(key);
connection.rPush(key, values);
connection.sAdd(key, values);
connection.sMembers(key);
connection.sDiff(keys);
connection.sPop(key);
整合可能遇到的问题
1.NoSuchMethodError
java.lang.NoSuchMethodError: org.springframework.core.serializer.support.DeserializingConverter.&init&(Ljava/lang/ClassL)V
Caused by: java.lang.NoSuchMethodError: redis.clients.jedis.JedisShardInfo.setTimeout(I)V
类似找不到类,找不到方法的问题,当确定依赖的jar已经引入之后,此类问题多事spring-data-redis以及jedis版本问题,多换个版本试试,本文上面提到的版本可以使用。
1.No qualifying bean
No qualifying bean of type [org.springframework.data.redis.core.RedisTemplate] found for dependency
找不到bean,考虑applicationContext.xml中配置redisTemplate bean时实现类是否写错。例如,BaseRedisDao注入的是RedisTemplate类型的对象,applicationContext.xml中配置的实现类却是RedisTemplate的子类StringRedisTemplate,那肯定报错。整合好后,下面我们着重学习基于redis的分布式锁的实现。
基于redis实现的分布式锁
我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源。锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标识,因此基于redis实现的分布式锁主要依赖redis的SETNX命令和DEL命令,SETNX相当于上锁,DEL相当于释放锁,当然,在下面的具体实现中会更复杂些。之所以称为分布式锁,是因为客户端可以在redis集群环境中向集群中任一个可用Master节点请求上锁(即SETNX命令存储key到redis缓存中是随机的)。
现在相信你已经对在基于redis实现的分布式锁的基本概念有了解,需要注意的是,这个和前面文章提到的使用WATCH 命令对key值进行锁操作没有直接的关系。java中synchronized和Lock对象都能对共享资源进行加锁,下面我们将学习用java实现的redis分布式锁。
java中的锁技术
在分析java实现的redis分布式锁之前,我们先来回顾下java中的锁技术,为了直观的展示,我们采用“多个线程共享输出设备”来举例。
不加锁共享输出设备
package com.shanheyongmu.
import java.util.concurrent.locks.Limport java.util.concurrent.locks.ReentrantL
import com.shanheyongmu.redislock.RedisL
public class LockTest {
static class Outputer {
public void output(String name) {
for(int i=0; i&name.length(); i++) {
System.out.print(name.charAt(i));
System.out.println();
public static void main(String[] args) {
final Outputer output = new Outputer();
//线程1打印zhangsan
new Thread(new Runnable(){
public void run() {
while(true) {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
output.output("zhangsan");
}).start();
//线程2打印lingsi
new Thread(new Runnable(){
public void run() {
while(true) {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
output.output("lingsi");
}).start();
//线程3打印wangwu
new Thread(new Runnable(){
public void run() {
while(true) {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
output.output("huangwu");
}).start();
上面例子中,三个线程同时共享输出设备output,线程1需要打印zhangsan,线程2需要打印lingsi,线程3需要打印wangwu。在不加锁的情况,这三个线程会不会因为得不到输出设备output打架呢,我们来看看运行结果:
zhangslingsi
从运行结果可以看出,三个线程打架了,线程1没打印完zhangsan,线程2就来抢输出设备......可见,这不是我们想要的,我们想要的是线程之间能有序的工作,各个线程之间互斥的使用输出设备output。
使用java5中的Lock对输出设备加锁
现在我们对Outputer进行改进,给它加上锁,加锁之后每次只有一个线程能访问它。
//使用java5中的锁
static class Outputer{
Lock lock = new ReentrantLock();
public void output(String name) {
//传统java加锁
//synchronized (Outputer.class){
lock.lock();
for(int i=0; i&name.length(); i++) {
System.out.print(name.charAt(i));
System.out.println();
//任何情况下都有释放锁
lock.unlock();
看看加锁后的输出结果:
从运行结果中可以看出,三个线程之间不打架了,线程之间的打印变得有序。有个这个基础,下面我们来学习基于Redis实现的分布式锁就更容易了。
Redis分布式锁
从上面java锁的使用中可以看出,锁对象主要有lock与unlock方法,在lock与unlock方法之间的代码(临界区)能保证线程互斥访问。基于redis实现的Java分布式锁主要依赖redis的SETNX命令和DEL命令,SETNX相当于上锁(lock),DEL相当于释放锁(unlock)。我们只要实现Lock接口重写lock()和unlock()即可。但是这还不够,安全可靠的分布式锁应该满足满足下面三个条件:
l&互斥,不管任何时候,只有一个客户端能持有同一个锁。
l&不会死锁,最终一定会得到锁,即使持有锁的客户端对应的master节点宕掉。
l&容错,只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。
那么什么情况下回不满足上面三个条件呢。多个线程(客户端)同时竞争锁可能会导致多个客户端同时拥有锁。比如,
(1)线程1在master节点拿到了锁(存入key)
(2)master节点在把线程1创建的key写入slave之前宕机了,此时集群中的节点已经没有锁(key)了,包括master节点的slaver节点
(3)slaver节点升级为master节点
(4)线程2向新的master节点发起锁(存入key)请求,很明显,能请求成功。
可见,线程1和线程2同时获得了锁。如果在更高并发的情况,可能会有更多线程(客户端)获取锁,这种情况就会导致上文所说的线程“打架”问题,线程之间的执行杂乱无章。
那什么情况下又会发生死锁的情况呢。如果拥有锁的线程(客户端)长时间的执行或者因为某种原因造成阻塞,就会导致锁无法释放(unlock没有调用),其它线程就不能获取锁而而产生无限期死锁的情况。其它线程在执行lock失败后即使粗暴的执行unlock删除key之后也不能正常释放锁,因为锁就只能由获得锁的线程释放,锁不能正常释放其它线程仍然获取不到锁。解决死锁的最好方式是设置锁的有效时间(redis的expire命令),不管是什么原因导致的死锁,有效时间过后,锁将会被自动释放。
为了保障容错功能,即只要有Redis节点正常工作,客户端应该都能获取和释放锁,我们必须用相同的key不断循环向Master节点请求锁,当请求时间超过设定的超时时间则放弃请求锁,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,应该尽快尝试下一个master节点。释放锁比较简单,因为只需要在所有节点都释放锁就行,不管之前有没有在该节点获取锁成功。
Redlock算法
根据上面的分析,官方提出了一种用Redis实现分布式锁的算法,这个算法称为RedLock。RedLock算法的主要流程如下:
RedLock算法主要流程
结合上面的流程图,加上下面的代码解释,相信你一定能理解redis分布式锁的实现原理
public class RedisLock implements Lock{
protected StringRedisTemplate redisStringT
// 存储到redis中的锁标志
private static final String LOCKED = "LOCKED";
// 请求锁的超时时间(ms)
private static final long TIME_OUT = 30000;
// 锁的有效时间(s)
public static final int EXPIRE = 60;
// 锁标志对应的
// state flag
private volatile boolean isLocked = false;
public RedisLock(String key) {
this.key =
@SuppressWarnings("resource")
ApplicationContext
new ClassPathXmlApplicationContext("classpath*:applicationContext.xml");
redisStringTemplate = (StringRedisTemplate)ctx.getBean("redisStringTemplate");
public void lock() {
//系统当前时间,毫秒
long nowTime = System.nanoTime();
//请求锁超时时间,毫秒
long timeout = TIME_OUT*1000000;
final Random r = new Random();
//不断循环向Master节点请求锁,当请求时间(System.nanoTime() - nano)超过设定的超时时间则放弃请求锁
//这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间
//如果一个master节点不可用了,应该尽快尝试下一个master节点
while ((System.nanoTime() - nowTime) & timeout) {
//将锁作为key存储到redis缓存中,存储成功则获得锁
if (redisStringTemplate.getConnectionFactory().getConnection().setNX(key.getBytes(),
LOCKED.getBytes())) {
//设置锁的有效期,也是锁的自动释放时间,也是一个客户端在其他客户端能抢占锁之前可以执行任务的时间
//可以防止因异常情况无法释放锁而造成死锁情况的发生
redisStringTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
isLocked = true;
//上锁成功结束请求
//获取锁失败时,应该在随机延时后进行重试,避免不同客户端同时重试导致谁都无法拿到锁的情况出现
//睡眠3毫秒后继续请求锁
Thread.sleep(3, r.nextInt(500));
} catch (Exception e) {
e.printStackTrace();
public void unlock() {
//不管请求锁是否成功,只要已经上锁,客户端都会进行释放锁的操作
if (isLocked) {
redisStringTemplate.delete(key);
public void lockInterruptibly() throws InterruptedException {
// TODO Auto-generated method stub
public boolean tryLock() {
// TODO Auto-generated method stub
return false;
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
// TODO Auto-generated method stub
return false;
public Condition newCondition() {
// TODO Auto-generated method stub
return null;
好了,RedisLock已经实现,我们对Outputer使用RedisLock进行修改
/使用RedisLock
static class Outputer {
//创建一个名为redisLock的RedisLock类型的锁
RedisLock redisLock = new RedisLock("redisLock");
public void output(String name) {
redisLock.lock();
for(int i=0; i&name.length(); i++) {
System.out.print(name.charAt(i));
System.out.println();
//任何情况下都要释放锁
redisLock.unlock();
看看使用RedisLock加锁后的的运行结果
可见,使用RedisLock加锁后线程之间不再“打架”,三个线程互斥的访问output。
现在我无法论证RedLock算法在分布式、高并发环境下的可靠性,但从本例三个线程的运行结果看,RedLock算法确实保证了三个线程互斥的访问output(redis.maxIdle=300&redis.maxTotal=600,运行到Timeout waiting for idle object都没有出现线程“打架”的问题)。我认为RedLock算法仍有些问题没说清楚,比如,如何防止宕机时多个线程同时获得锁;RedLock算法在释放锁的处理上,不管线程是否获取锁成功,只要上了锁,就会到每个master节点上释放锁,这就会导致一个线程上的锁可能会被其他线程释放掉,这就和每个锁只能被获得锁的线程释放相互矛盾。这些有待后续进一步交流学习研究。
全部系列到此告一段落了 ,可能很多人觉得还不够 在此我在搜索问题的过程中发现了比较好的 博客spring data+redis
阅读(...) 评论()}

我要回帖

更多关于 440s 集成显卡 缓存 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信