5.10.1. @Transactional Support
Transaction Support is disabled by default and has to be explicitly enabled for each RedisTemplate in use by setting setEnableTransactionSupport(true).
This will force binding the RedisConnection in use to the current Thread triggering MULTI.
If the transaction finishes without errors, EXEC is called, otherwise DISCARD.
Once in MULTI, RedisConnection would queue write operations, all readonly operations, such as KEYS are piped to a fresh (non thread bound) RedisConnection.

/** Sample Configuration **/
@Configuration
public class RedisTxContextConfiguration {
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// explicitly enable transaction support
template.setEnableTransactionSupport(true);
return template;
} @Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource());
} @Bean
public RedisConnectionFactory redisConnectionFactory( // jedis, lettuce, srp,... ); @Bean
public DataSource dataSource() throws SQLException { // ... }
}
/** Usage Constrainsts **/ // executed on thread bound connection
template.opsForValue().set("foo", "bar"); // read operation executed on a free (not tx-aware)
connection template.keys("*"); // returns null as values set within transaction are not visible
template.opsForValue().get("foo");

    public Long leftPush(V value) {
return this.ops.leftPush(this.getKey(), value);
}
public Long leftPush(K key, V value) {
final byte[] rawKey = this.rawKey(key);
final byte[] rawValue = this.rawValue(value);
return (Long)this.execute(new RedisCallback() {
public Long doInRedis(RedisConnection connection) {
return connection.lPush(rawKey, new byte[][]{rawValue});
}
}, true);
}
    <T> T execute(RedisCallback<T> callback, boolean b) {
return this.template.execute(callback, b);
}
    public <T> T execute(RedisCallback<T> action, boolean exposeConnection) {
return this.execute(action, exposeConnection, false);
}
 public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(action, "Callback object must not be null");
RedisConnectionFactory factory = this.getConnectionFactory();
RedisConnection conn = null; Object var11;
try {
if(this.enableTransactionSupport) {
conn = RedisConnectionUtils.bindConnection(factory, this.enableTransactionSupport);
} else {
conn = RedisConnectionUtils.getConnection(factory);
} boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
RedisConnection connToUse = this.preProcessConnection(conn, existingConnection);
boolean pipelineStatus = connToUse.isPipelined();
if(pipeline && !pipelineStatus) {
connToUse.openPipeline();
} RedisConnection connToExpose = exposeConnection?connToUse:this.createRedisConnectionProxy(connToUse);
Object result = action.doInRedis(connToExpose);
if(pipeline && !pipelineStatus) {
connToUse.closePipeline();
} var11 = this.postProcessResult(result, connToUse, existingConnection);
} finally {
if(!this.enableTransactionSupport) {
RedisConnectionUtils.releaseConnection(conn, factory);
} } return var11;
}

本文主要讲述如何在java里头使用redis进行cas操作。其实呢,redis不像memcached那样显示地支持cas操作,不过它有事务的概念。

准备

redis的乐观锁支持

Redis通过使用WATCH, MULTI, and EXEC组成的事务来实现乐观锁(注意没有用DISCARD),Redis事务没有回滚操作。在SpringDataRedis当中通过RedisTemplate的SessionCallback中来支持(否则事务不生效)。discard的话不需要自己代码处理,callback返回null,成的话,返回非null,依据这个来判断事务是否成功(没有抛异常)。

实例

   @Test
public void cas() throws InterruptedException, ExecutionException {
String key = "test-cas-1";
ValueOperations<String, String> strOps = redisTemplate.opsForValue();
strOps.set(key, "hello");
ExecutorService pool = Executors.newCachedThreadPool();
List<Callable<Object>> tasks = new ArrayList<>();
for(int i=0;i<5;i++){
final int idx = i;
tasks.add(new Callable() {
@Override
public Object call() throws Exception {
return redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.watch(key);
String origin = (String) operations.opsForValue().get(key);
operations.multi();
operations.opsForValue().set(key, origin + idx);
Object rs = operations.exec();
System.out.println("set:"+origin+idx+" rs:"+rs);
return rs;
}
});
}
});
}
List<Future<Object>> futures = pool.invokeAll(tasks);
for(Future<Object> f:futures){
System.out.println(f.get());
}
pool.shutdown();
pool.awaitTermination(1000, TimeUnit.MILLISECONDS);
}

输出

set:hello2 rs:null
set:hello3 rs:[]
set:hello1 rs:null
set:hello4 rs:null
set:hello0 rs:null

查看该值

127.0.0.1:6379> get test-cas-1
"\"hello3\""

SessionCallback

没有在SessionCallback里头执行watch、multi、exec,而是自己单独写

与数据库事务的混淆

template.setEnableTransactionSupport(true);
这个应该是支持数据库的事务成功才执行的意思。因为Spring默认的事务,都是基于DB事务的
   /**
* Gets a Redis connection. Is aware of and will return any existing corresponding connections bound to the current
* thread, for example when using a transaction manager. Will create a new Connection otherwise, if
* {@code allowCreate} is <tt>true</tt>.
*
* @param factory connection factory for creating the connection
* @param allowCreate whether a new (unbound) connection should be created when no connection can be found for the
* current thread
* @param bind binds the connection to the thread, in case one was created
* @param enableTransactionSupport
* @return an active Redis connection
*/
public static RedisConnection doGetConnection(RedisConnectionFactory factory, boolean allowCreate, boolean bind,
boolean enableTransactionSupport) { Assert.notNull(factory, "No RedisConnectionFactory specified"); RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory); if (connHolder != null) {
if (enableTransactionSupport) {
potentiallyRegisterTransactionSynchronisation(connHolder, factory);
}
return connHolder.getConnection();
} if (!allowCreate) {
throw new IllegalArgumentException("No connection found and allowCreate = false");
} if (log.isDebugEnabled()) {
log.debug("Opening RedisConnection");
} RedisConnection conn = factory.getConnection(); if (bind) { RedisConnection connectionToBind = conn;
if (enableTransactionSupport && isActualNonReadonlyTransactionActive()) {
connectionToBind = createConnectionProxy(conn, factory);
} connHolder = new RedisConnectionHolder(connectionToBind); TransactionSynchronizationManager.bindResource(factory, connHolder);
if (enableTransactionSupport) {
potentiallyRegisterTransactionSynchronisation(connHolder, factory);
} return connHolder.getConnection();
} return conn;
}

不要跟本文的乐观锁说的事务混淆在一起。

参考

https://segmentfault.com/a/1190000004393573

org.springframework.data.redis.core.RedisTemplate

 public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(action, "Callback object must not be null");
RedisConnectionFactory factory = this.getConnectionFactory();
RedisConnection conn = null; Object var11;
try {
if(this.enableTransactionSupport) {
conn = RedisConnectionUtils.bindConnection(factory, this.enableTransactionSupport);
//这个是否支持的开关可以在@Configuration中配置:
} else {
conn = RedisConnectionUtils.getConnection(factory);
} boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
RedisConnection connToUse = this.preProcessConnection(conn, existingConnection);
boolean pipelineStatus = connToUse.isPipelined();
if(pipeline && !pipelineStatus) {
connToUse.openPipeline();
} RedisConnection connToExpose = exposeConnection?connToUse:this.createRedisConnectionProxy(connToUse);
Object result = action.doInRedis(connToExpose);
if(pipeline && !pipelineStatus) {
connToUse.closePipeline();
} var11 = this.postProcessResult(result, connToUse, existingConnection);
} finally {
if(!this.enableTransactionSupport) {
RedisConnectionUtils.releaseConnection(conn, factory);
} } return var11;
}
    @Bean
public RedisTemplate redisTemplate(){
RedisTemplate<StringRedisSerializer, Serializable> rt = new RedisTemplate<>();
rt.setConnectionFactory(jedisConnectionFactory());
StringRedisSerializer stringSerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
RedisHashKeySerializer redisHashKeySerializer = new RedisHashKeySerializer();
rt.setKeySerializer(stringSerializer);
rt.setValueSerializer(jdkSerializationRedisSerializer);
rt.setHashKeySerializer(redisHashKeySerializer);
rt.setHashValueSerializer(jdkSerializationRedisSerializer);
rt.afterPropertiesSet();
rt.setEnableTransactionSupport(true);
return rt;
}

org.springframework.data.redis.core.RedisConnectionUtils

    public static RedisConnection doGetConnection(RedisConnectionFactory factory, boolean allowCreate, boolean bind, boolean enableTransactionSupport) {
Assert.notNull(factory, "No RedisConnectionFactory specified");
RedisConnectionUtils.RedisConnectionHolder connHolder = (RedisConnectionUtils.RedisConnectionHolder)TransactionSynchronizationManager.getResource(factory);
if(connHolder != null) {
if(enableTransactionSupport) {
potentiallyRegisterTransactionSynchronisation(connHolder, factory);
} return connHolder.getConnection();
} else if(!allowCreate) {
throw new IllegalArgumentException("No connection found and allowCreate = false");
} else {
if(log.isDebugEnabled()) {
log.debug("Opening RedisConnection");
} RedisConnection conn = factory.getConnection();
if(bind) {
RedisConnection connectionToBind = conn;
if(enableTransactionSupport && isActualNonReadonlyTransactionActive()) {
connectionToBind = createConnectionProxy(conn, factory);
} connHolder = new RedisConnectionUtils.RedisConnectionHolder(connectionToBind);
TransactionSynchronizationManager.bindResource(factory, connHolder);
if(enableTransactionSupport) {
potentiallyRegisterTransactionSynchronisation(connHolder, factory);
} return connHolder.getConnection();
} else {
return conn;
}
}
}
private static void potentiallyRegisterTransactionSynchronisation(RedisConnectionUtils.RedisConnectionHolder connHolder, RedisConnectionFactory factory) {
if(isActualNonReadonlyTransactionActive() && !connHolder.isTransactionSyncronisationActive()) {
connHolder.setTransactionSyncronisationActive(true);
RedisConnection conn = connHolder.getConnection();
conn.multi();
TransactionSynchronizationManager.registerSynchronization(new RedisConnectionUtils.RedisTransactionSynchronizer(connHolder, conn, factory));
} }

RedisTemplate  api详解

1. RedisTemplate的事务

    private boolean enableTransactionSupport = false;
private boolean exposeConnection = false;
private boolean initialized = false;
private boolean enableDefaultSerializer = true;
private RedisSerializer<?> defaultSerializer = new JdkSerializationRedisSerializer(); private RedisSerializer keySerializer = null;
private RedisSerializer valueSerializer = null;
private RedisSerializer hashKeySerializer = null;
private RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = new StringRedisSerializer(); private ScriptExecutor<K> scriptExecutor; // cache singleton objects (where possible)
private ValueOperations<K, V> valueOps;
private ListOperations<K, V> listOps;
private SetOperations<K, V> setOps;
private ZSetOperations<K, V> zSetOps;

enableTransactionSupport:是否启用事务支持。
在代码中搜索下用到这个变量的地方,会看到,在调用RedisCallback之前,有一行代码是如果启用事务支持,那么conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport),
也就是说,系统自动帮我们拿到了事务中绑定的连接
可以在一个方法的多次对Redis增删该查中,始终使用同一个连接。但是,即使使用了同样的连接,没有进行connection.multi()和connection.exec(),依然是无法启用事务的。

我没有仔细的查阅代码,但是可以知道的是,Spring已经对这个,给了我们一个更好的支持:@Transactional 

在调用RedisTempalte中的execute()方法的地方,加入这个注解(是spring包下面提供的,不要引用成rt包下的注解),能让这个方法中的所有execute,自动加入multi()以及异常的回滚或者是正常运行时候的提交!

2. RedisTempalte的Serializer

用过jedis操作的都知道,所有connection的操作方法,都是传入字节数组。那么,将一个对象和字节相互转换,就需要通过序列化和反序列化。

模版方法中,Spring提供了默认的StringSerializer和JdkSerializer,第一个很简单,就是通过String.getBytes()来实现的。而且在Redis中,所有存储的值都是字符串类型的。所以这种方法保存后,通过Redis-cli控制台,是可以清楚的查看到我们保存了什么key,value是什么。但是对于JdkSerializationRedisSerializer来说,这个序列化方法就是Jdk提供的了。首先要求我们要被序列化的类继承自Serializeable接口,然后通过,然后通过Jdk对象序列化的方法保存。(注:这个序列化保存的对象,即使是个String类型的,在redis控制台,也是看不出来的,因为它保存了一些对象的类型什么的额外信息,)

这么一长串,其实就是一个int类型的123。

keySerializer:这个是对key的默认序列化器。默认值是StringSerializer。

valueSerializer:这个是对value的默认序列化器,默认值是取自DefaultSerializer的JdkSerializationRedisSerializer。

hashKeySerializer:对hash结构数据的hashkey序列化器,默认值是取自DefaultSerializer的JdkSerializationRedisSerializer。

hashValueSerializer:对hash结构数据的hashvalue序列化器,默认值是取自DefaultSerializer的JdkSerializationRedisSerializer。

除此之外,我们在该类中,还发现了valueOps和hashOps等操作类,这是spring给我们提供的可以直接使用来操作Redis的类,非常方便。下一篇我们将讲解这些类。

http://www.cnblogs.com/luochengqiuse/p/4640932.html?utm_source=tuicool&utm_medium=referral

把 Redis 当作数据库的用例
现在我们来看看在服务器端 Java 企业版系统中把 Redis 当作数据库的各种用法吧。无论用例的简繁,Redis 都能帮助用户优化性能、处理能力和延迟,让常规 Java 企业版技术栈望而却步。

1. 全局唯一增量计数器
我们先从一个相对简单的用例开始吧:一个增量计数器,可显示某网站受到多少次点击。Spring Data Redis 有两个适用于这一实用程序的类:RedisAtomicInteger 和 RedisAtomicLong。和 Java 并发包中的 AtomicInteger 和 AtomicLong 不同的是,这些 Spring 类能在多个 JVM 中发挥作用。

列表 1:全局唯一增量计数器

RedisAtomicLong counter = new RedisAtomicLong("UNIQUE_COUNTER_NAME", redisTemplate.getConnectionFactory());
Long myCounter = counter.incrementAndGet();// return the incremented value

请注意整型溢出并谨记,在这两个类上进行操作需要付出相对较高的代价。

2. 全局悲观锁
时不时的,用户就得应对服务器集群的争用。假设你从一个服务器集群运行一个预定作业。在没有全局锁的情况下,集群中的节点会发起冗余作业实例。假设某个聊天室分区可容纳 50 人。如果聊天室已满,就需要创建新的聊天室实例来容纳另外 50 人。

如果检测到聊天室已满但没有全局锁,集群中的各个节点就会创建自有的聊天室实例,为整个系统带来不可预知的因素。列表 2 介绍了应当如何充分利用 SETNX(SET if **N**ot e**X**ists:如果不存在,则设置)这一 Redis 命令来执行全局悲观锁。

列表2:全局悲观锁

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit; @Service
public class DistributedLockRedis { public static final Logger LOGGER = LoggerFactory.getLogger(DistributedLockRedis.class); public static final String DISTRIBUTED_LOCK_REDIS_KEY = "distributed:lock:redis:"; @Autowired
private RedisTemplate<String, Long> redisTemplate; /**
* 由于Redis是单线程模型,命令操作原子性,所以利用这个特性可以很容易的实现分布式锁。
* 获得一个锁
*
* @param bizName 业务名
* @param lockTimeout 线程占用锁的时间
* @param unit 单位
* @throws InterruptedException 锁可以被中断
*/
public void lock(String bizName, int lockTimeout, TimeUnit unit) throws InterruptedException {
// redisTemplate.getConnectionFactory().getConnection().setNX()
String redisKey;
if (StringUtils.isBlank(bizName)) {
LOGGER.warn("is not recommended!");
redisKey = DISTRIBUTED_LOCK_REDIS_KEY;
} else {
redisKey = DISTRIBUTED_LOCK_REDIS_KEY + bizName.trim();
}
BoundValueOperations<String, Long> valueOps = redisTemplate.boundValueOps(redisKey);
while (true) {
// https://redis.io/commands/setnx
long currentTimeMillis = System.currentTimeMillis();
long releaseLockTime = currentTimeMillis + unit.toMillis(lockTimeout) + 1;
//这两个if else不能混写,因为多个相同类型的线程竞争锁时,在锁超时时,设置的超时时间是一样的
if (valueOps.setIfAbsent(releaseLockTime)) {//第一次获取锁
redisTemplate.expire(redisKey, lockTimeout, unit);
return;
} else if (currentTimeMillis > valueOps.get()) {//锁已经超时
//如果其它线程占用锁,再重新设置的时间和原来时间的时间差,可以忽略
Long lockCurrentValue = valueOps.getAndSet(releaseLockTime);
//如果当前时间小于LockKey存放的时间,说明已经有其它线程加锁
if (currentTimeMillis > lockCurrentValue) {
redisTemplate.expire(redisKey, lockTimeout, unit);
return;
}
} else {
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(100, 1000));
} } } }

Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for "SET if Not eXists".
Return value
Integer reply, specifically:
1 if the key was set
0 if the key was not set
Examples
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis>

How to do set nx option using RedisTemplate?

The method name is setIfAbsent
setIfAbsent
Boolean setIfAbsent(V value)
Set the bound key to hold the string value if the bound key is absent.
Parameters:
value -
See Also:
Redis Documentation: SETNX

http://docs.spring.io/spring-data/redis/docs/current/api/org/springframework/data/redis/core/BoundValueOperations.html#setIfAbsent-V-

用以下Python代码来实现上述的使用 SETNX 命令作分布式锁的算法。

LOCK_TIMEOUT = 3
lock = 0
lock_timeout = 0
lock_key = 'lock.foo' # 获取锁
while lock != 1:
now = int(time.time())
lock_timeout = now + LOCK_TIMEOUT + 1
lock = redis_client.setnx(lock_key, lock_timeout)
if lock == 1 or (now > int(redis_client.get(lock_key))) and now > int(redis_client.getset(lock_key, lock_timeout)):
break
else:
time.sleep(0.001) # 已获得锁
do_job() # 释放锁
now = int(time.time())
if now < lock_timeout:
redis_client.delete(lock_key)

http://blog.csdn.net/lihao21/article/details/49104695

如果使用关系数据库,一旦最先生成锁的程序意外退出,锁就可能永远得不到释放。Redis 的 EXPIRE 设置可确保在任何情况下释放锁。

3. 位屏蔽(Bit Mask)
假设 web 客户端需要轮询一台 web 服务器,针对某个数据库中的多个表查询客户指定更新内容。如果盲目地查询所有相应的表以寻找潜在更新,成本较高。为了避免这一做法,可以尝试在 Redis 中给每个客户端保存一个整型作为脏指标,整型的每个数位表示一个表。该表中存在客户所需更新时,设置数位。轮询期间,不会触发对表的查询,除非设置了相应数位。就获取并将这样的位屏蔽设置为 STRING 而言,Redis 非常高效。

4. 排行榜(Leaderboard)
Redis 的 ZSET 数据结构为游戏玩家排行榜提供了简洁的解决方案。ZSET 的工作方式有些类似于 Java 中的 PriorityQueue,各个对象均为经过排序的数据结构,井井有条。可以按照分数排出游戏玩家在排行榜上的位置。Redis 的 ZSET 定义了一份内容丰富的命令列表,支持灵活有效的查询。例如,ZRANGE(包括 ZREVRANGE)可返回有序集内的指定范围要素。

你可以使用这一命令列出排行榜前 100 名玩家。ZRANGEBYSCORE 返回指定分数范围内的要素(例如列出得分为 1000 至 2000 之间的玩家),ZRNK 则返回有序集内的要素的排名,诸如此类。

5. 布隆(Bloom)过滤器
布隆过滤器 (Bloom filter) 是一种空间利用率较高的概率数据结构,用来测试某元素是否某个集的一员。可能会出现误报匹配,但不会漏报。查询可返回“可能在集内”或“肯定不在集内”。

就在线服务和离线服务包括大数据分析等方面,布隆过滤器数据结构都能派上很多用场。Facebook 利用布隆过滤器进行输入提示搜索,为用户输入的查询提取朋友和朋友的朋友。Apache HBase 则利用布隆过滤器过滤掉不包含特殊行或列的 HFile 块磁盘读取,使读取速度得到明显提升。Bitly 用布隆过滤器来避免将用户重定向到恶意网站,而 Quara 则在订阅后端执行了一个切分的布隆过滤器,用来过滤掉之前查看过的内容。在我自己的项目里,我用布隆过滤器追踪用户对各个主题的投票情况。

借助出色的速度和处理能力,Redis 极好地融合了布隆过滤器。搜索 GitHub,就能发现很多 Redis 布隆过滤器项目,其中一些还支持可调谐精度。

6. 高效的全局通知:发布/订阅渠道
Redis 发布/订阅渠道的工作方式类似于一个扇出消息传递系统,或 JMS 语义中的一个主题。JMS 主题和 Redis 发布/订阅渠道的一个区别是,通过 Redis 发布的消息并不持久。消息被推送给所有相连的客户端后,Redis 上就会删除这一消息。换句话说,订阅者必须一直在线才能接收新消息。Redis 发布/订阅渠道的典型用例包括实时配置分布、简单的聊天服务器等。

在 web 服务器集群中,每个节点都可以是 Redis 发布/订阅渠道的一个订阅者。发布到渠道上的消息也会被即时推送到所有相连节点。这一消息可以是某种配置更改,也可以是针对所有在线用户的全局通知。和恒定轮询相比,这种推送沟通模式显然极为高效。

Redis 性能优化
Redis 非常强大,但也可以从整体上和根据特定编程场景做出进一步优化。可以考虑以下技巧。

存活时间
所有 Redis 数据结构都具备存活时间 (TTL) 属性。当你设置这一属性时,数据结构会在过期后自动删除。充分利用这一功能,可以让 Redis 保持较低的内存损耗。

管道技术
在一条请求中向 Redis 发送多个命令,这种方法叫做管道技术。这一技术节省了网络往返的成本,这一点非常重要,因为网络延迟可能比 Redis 延迟要高上好几个量级。但这里存在一个陷阱:管道中的 Redis 命令列表必须预先确定,并且应当彼此独立。如果一个命令的参数是由先前命令的结果计算得出,管道技术就不起作用。列表 3 给出了 Redis 管道技术的一个示例。

列表 3:管道技术

@Override
public List<LeaderboardEntry> fetchLeaderboard(String key, String... playerIds) {
final List<LeaderboardEntry> entries = new ArrayList<>();
redisTemplate.executePipelined(new RedisCallback<Object>() { // enable Redis Pipeline
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for(String playerId : playerIds) {
Long rank = connection.zRevRank(key.getBytes(), playerId.getBytes());
Double score = connection.zScore(key.getBytes(), playerId.getBytes());
LeaderboardEntry entry = new LeaderboardEntry(playerId,
score!=null?score.intValue():-1, rank!=null?rank.intValue():-1);
entries.add(entry);
}
return null;
}
});
return entries;
}

副本集和切分
Redis 支持主从副本配置。和 MongoDB 一样,副本集也是不对称的,因为从节点是只读的,以便共享读取工作量。
我在文章开头提到过,也可以执行切分来横向扩展 Redis 的处理能力和存储容量。
事实上,Redis 非常强大,据亚马逊公司的内部基准显示,类型 r3.4xlarge 的一个 EC2 实例每秒可轻松处理 100000 次请求。传说还有把每秒 700000 次请求作为基准的。
对于中小型应用程序,通常无需考虑 Redis 切分。
(请参见这篇非常出色的文章《运行中的 Redis》https://www.manning.com/books/redis-in-action,进一步了解 Redis 的性能优化和切分http://www.oneapm.com/brand/apm.html。)

Redis 中的事务

Redis 并不像关系数据库管理系统那样能支持全面的 ACID 事务,但其自有的事务也非常有效。从本质上来说,Redis 事务是管道、乐观锁、确定提交和回滚的结合。其思想是执行一个管道中的一个命令列表,然后观察某一关键记录的潜在更新(乐观锁)。根据所观察的记录是否会被另一个进程更新,该命令列表或整体确定提交,或完全回滚。

下面以某个拍卖网站上的卖方库存为例。买方试图从卖方处购买某件商品时,你负责观察 Redis 事务内的卖方库存变化。同时,你要从同一个库存中删除此商品。事务关闭前,如果库存被一个以上进程触及(例如,如果两个买方同时购买了同一件商品),事务将回滚,否则事务会确定提交。回滚后可开始重试。

Spring Data Redis 中的事务陷阱

我在 Spring 的 RedisTemplate 类 redisTemplate.setEnableTransactionSupport(true); 中启用 Redis 事务时得到一个惨痛的教训:Redis 会在运行几天后开始返回垃圾数据,导致数据严重损坏。StackOverflow上也报道了类似情况。

在运行一个 monitor 命令后,我的团队发现,在进行 Redis 操作或 RedisCallback 后,Spring 并没有自动关闭 Redis 连接,而事实上它是应该关闭的。如果再次使用未关闭的连接,可能会从意想不到的 Redis 密钥返回垃圾数据。有意思的是,如果在 RedisTemplate 中把事务支持设为 false,这一问题就不会出现了。

我们发现,我们可以先在 Spring 语境里配置一个 PlatformTransactionManager(例如 DataSourceTransactionManager),然后再用 @Transactional 注释来声明 Redis 事务的范围,让 Spring 自动关闭 Redis 连接。

根据这一经验,我们相信,在 Spring 语境里配置两个单独的 RedisTemplate 是很好的做法:其中一个 RedisTemplates 的事务设为 false,用于大多数 Redis 操作,另一个 RedisTemplates 的事务已激活,仅用于 Redis 事务。当然必须要声明 PlatformTransactionManager 和 @Transactional,以防返回垃圾数值。

另外,我们还发现了 Redis 事务和关系数据库事务(在本例中,即 JDBC)相结合的不利之处。混合型事务的表现和预想的不太一样。

I learned a hard lesson when enabling Redis transactions in the Spring RedisTemplate class redisTemplate.setEnableTransactionSupport(true);:
Redis started returning junk data after running for a few days, causing serious data corruption. A similar case was reported on StackOverflow. By running a monitor command, my team discovered that after a Redis operation or RedisCallback, Spring doesn't close the Redis connection automatically, as it should do.
Reusing an unclosed connection may return junk data from an unexpected key in Redis. Interestingly, this issue doesn't show up when transaction support is set to false in RedisTemplate. We discovered that we could make Spring close Redis connections automatically by configuring a PlatformTransactionManager (such as DataSourceTransactionManager) in the Spring context,
then using the @Transactional annotation to declare the scope of Redis transactions. Based on this experience, we believe it's good practice to configure two separate RedisTemplates in the Spring context:
One with transaction set to false is used on most Redis operations; the other with transaction enabled is only applied to Redis transactions.
Of course PlatformTransactionManager and @Transactional must be declared to prevent junk values from being returned. Moreover, we learned the downside of mixing a Redis transaction with a relational database transaction, in this case JDBC.
Mixed transactions do not behave as you would expect.

结论
我希望通过这篇文章向其他 Java 企业开发师介绍 Redis 的强大之处,尤其是将 Redis 用作远程数据缓存和用于易挥发数据时。
在这里我介绍了 Redis 的六个有效用例,分享了一些性能优化技巧,还说明了我的 Glu Mobile 团队怎样解决了 Spring Data Redis 事务配置不当造成的垃圾数据问题。
我希望这篇文章能够激发你对 Redis NoSQL 的好奇心,让你能够受到启发,在自己的 Java 企业版系统里创造出一番天地。

http://blog.oneapm.com/apm-tech/778.html

http://www.javaworld.com/article/3062899/big-data/lightning-fast-nosql-with-spring-data-redis.html?page=2

https://docs.spring.io/spring-data/redis/docs/1.8.6.RELEASE/reference/html/#tx.spring

如果是使用编程的方式(通常是基于 Spring Boot 项目)配置 RedisTemplate 的话只需在你的配置类(被@Configuration注解修饰的类)中显式创建 RedisTemplate Bean,设置 Serializer 即可。

@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory); GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);//设置默认的Serialize,包含 keySerializer & valueSerializer //redisTemplate.setKeySerializer(fastJsonRedisSerializer);//单独设置keySerializer
//redisTemplate.setValueSerializer(fastJsonRedisSerializer);//单独设置valueSerializer
return redisTemplate;
}

https://github.com/alibaba/fastjson/wiki/%E5%9C%A8-Spring-%E4%B8%AD%E9%9B%86%E6%88%90-Fastjson

SpringDataRedis事务 专题的更多相关文章

  1. SpringBoot之数据访问和事务-专题三

    SpringBoot之数据访问和事务-专题三 四.数据访问 4.1.springboot整合使用JdbcTemplate 4.1.1 pom文件引入 <parent> <groupI ...

  2. Spring事务专题(三)事务的基本概念,Mysql事务处理原理

    前言 本专题大纲: 我重新整理了大纲,思考了很久,决定单独将MySQL的事务实现原理跟Spring中的事务示例分为两篇文章,因为二者毕竟没有什么实际关系,实际上如果你对MySQL的事务原理不感兴趣也可 ...

  3. Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现

    Spring中事务的使用示例.属性及使用中可能出现的问题 前言 本专题大纲如下: 对于专题大纲我又做了调整哈,主要是希望专题的内容能够更丰富,更加详细,本来是想在源码分析的文章中附带讲一讲事务使用中的 ...

  4. Spring事务专题(五)聊聊Spring事务到底是如何实现的

    前言 本专题大纲: 本文为本专题倒数第二篇文章. 在上篇文章中我们一起学习了Spring中的事务抽象机制以及动手模拟了一下Spring中的事务管理机制,那么本文我们就通过源码来分析一下Spring中的 ...

  5. 分布式事务专题笔记(三)分布式事务解决方案之TCC(三阶段提交)

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 1.什么是TCC事务 TCC是Try.Confifirm.Cancel三个词语的缩写,TCC要求每个分支 ...

  6. 分布式事务专题笔记(二)分布式事务解决方案之 2PC(两阶段提交)

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 前面已经了解了分布式事务的基础理论,以理论为基础,针对不同的分布式场景业界常见的解决方案有2PC.TCC ...

  7. 分布式事务专题笔记(一) 基础概念 与 CAP 理论

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.基础概念 1.什么是事务 什么是事务?举个生活中的例子:你去小卖铺买东西,“一手交钱,一手交货”就是 ...

  8. 时序数据库技术体系 – InfluxDB TSM存储引擎之TSMFile

    本文转自 http://hbasefly.com/2018/01/13/timeseries-database-4/ 为了更加系统的对时序数据库技术进行全方位解读,笔者打算再写一个系列专题(嘿嘿,好像 ...

  9. 如果你每次面试前都要去背一篇Spring中Bean的生命周期,请看完这篇文章

    前言 当你准备去复习Spring中Bean的生命周期的时候,这个时候你开始上网找资料,很大概率会看到下面这张图: 先不论这张图上是否全面,但是就说这张图吧,你是不是背了又忘,忘了又背? 究其原因在于, ...

随机推荐

  1. phper必知必会(一)

    1.http返回状态 200:成功,服务器已经成功处理了请求,并正常返回了提供请求的网页 301:永久移动,服务器会将请求转移到新的服务器地址 302:临时移动 401:未授权请求,请求需要身份移动 ...

  2. Centos 配置ifconfig命令

    刚装完CentOS 后ifconfig命令可能是不可用的,那么需要以下配置 ip addr vi /etc/sysconfig/network-scripts/ifcfg-xx (ONBOOT=yes ...

  3. ios之runloop笔记

    网上关于runloop的文章不计其数,再此,贴个自认为讲的比较简单明了的文章 http://www.jianshu.com/p/536184bfd163 个人理解: ios的runloop应该是类似于 ...

  4. 【Spring学习笔记-MVC-6】SpringMVC 之@RequestBody 接收Json数组对象

    作者:ssslinppp       1. 摘要 程序流程: 前台使用ajax技术,传递json字符串到后台: 后台使用Spring MVC注解@RequestBody 接受前台传递的json字符串, ...

  5. [C#]反射遍历对象属性

    /// <summary> /// C#反射遍历对象属性 /// </summary> /// <typeparam name="T">对象类型 ...

  6. [转]NSIS常用代码整理

    转自 http://www.flighty.cn/html/bushu/20120827_156.html 这是一些常用的NSIS代码,少轻狂特意整理出来,方便大家随时查看使用.不定期更新哦~~~ ; ...

  7. windows,linux下SVN实现自动更新WEB目录

    通过SVN进行版本库管理,每次提交后,都要在SVN服务器更新最新上传的版本到WEB目录进行同步.操作比较烦琐,而且效率也低.使用SVN钩子脚本进行WEB目录同步,可很好的解决这方面的问题.由于测试机器 ...

  8. pomelo RPC调用时新增字段缺失

    接触pomelo开发一个月,正式开始参与项目开发有10天,遇到很多细节的坑,今天讲讲标题:后端服务器节点之间的rpc调用过程中,返回的数据中新增字段缺失问题. 先讲结果:原因是该rpc调用已经采用了p ...

  9. JavaScript中的闭包与匿名函数

    知识内容: 1.预备知识 - 函数表达式 2.匿名函数 3.闭包 一.函数表达式 1.定义函数的两种方式 函数声明: 1 function func(arg0, arg1, arg2){ 2 // 函 ...

  10. C# List<string>和ArrayList用指定的分隔符分隔成字符串

    原文地址:https://www.cnblogs.com/ahwwmb/p/4166707.html 串联字符串数组的所有元素,其中在每个元素之间使用指定的分隔符 List<string> ...