序
本文主要講述如何在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);
這個應該是支援資料庫的事務成功才執行的意思。
/**
* 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;
}
不要跟本文的樂觀鎖說的事務混淆在一起。