序
本文主要讲述如何在java里头使用redis进行cas操作。其实呢,redis不像memcached那样显示地支持cas操作,不过它有事务的概念。
准备
redis的docker搭建
SpringBoot应用之分布式缓存
redis的乐观锁支持
Redis通过使用WATCH, MULTI, and EXEC组成的事务来实现乐观锁(注意没有用DISCARD
),Redis事务没有回滚操作。在SpringDataRedis当中通过RedisTemplate的SessionCallback中来支持(否则事务不生效
)。discard的话不需要自己代码处理,callback返回null,成的话,返回非null,依据这个来判断事务是否成功(没有抛异常
)。
实例
@Testpublic 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() {@Overridepublic Object call() throws Exception {return redisTemplate.execute(new SessionCallback() {@Overridepublic 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;}
不要跟本文的乐观锁说的事务混淆在一起。
参考
Redis-Transactions-via-Spring-Data-Redis
Spring-data-redis 第二天(事务)