redis中的事务

首先明白在java中线程和进程的区别:

1.什么是多线程? 
是指一个应用程序同时执行多个任务,一般来说一个任务就是一个线程 ,而一个应用程序有一个以上的线程我们称之为多线程。 
2.什么是进程? 
进程是一个正在执行的程序 ,比如QQ,迅雷等 一个进程的运行会向CPU申请在内存中开辟一个内存块。 
他是向CPU申请资源的,进程之间数据相互独立,一个进程至少有一个线程。 
3.什么是线程? 
线程是进程中的单一的顺序控制流程也可以叫做最小控制单元,线程是进程中执行单元,开启一个线程比开启一个进程更加节省资源。 
多线程与多进程的区别? 
多进程拥有自己的一套数据变量,而多线程是共享数据,而共享数据也会带来一系列的安全问题(安全问题稍后再提)。

redis中的事务:

1, redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。 由于redis是单线程来处理所有client的请求的所以做到这点是很容易的。一般情况下redis在接受到一个client发来的命令后会立即处理并返回处理结果,但是当一个client在一个连接中发出multi命令有,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一 个队列中。当从此连接受到exec命令后,redis会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给client.然后此连接就 结束事务上下文。下面可以看一个例子

 

 

2,从这个例子我们可以看到incr f ,incr r 命令发出后并没执行而是被放到了队列中。调用exec后俩个命令被连续的执行,最后返回的是两条命令执行后的结果
我们可以调用discard命令来取消一个事务。接着上面例子

 

可以发现这次incr a incr b都没被执行。discard命令其实就是清空事务的命令队列并退出事务上下文。

3,由于get a 和set a并不能保证两个命令是连续执行的(get操作不在事务上下文中)。很可能有两个client同时做这个操作。主要问题我们没有对共享资源a的访问进行任何的同步,也就是说redis没提供任何的加锁机制来同步对a的访问。而watch命令可以用来实现乐观锁。看个正确实现incr命令的例子,只是在前面加了watch a

watch 命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key.这 样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了 exec,discard,unwatch命令都会清除连接中的所有监视.

4,redis事务比较简单,所以会存在一些问题。第一个问题是redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配。  

前滚未完全提交的事务,即该事务已经被执行commit命令了,只是现在该事务修改所对应的脏数据块中只有一部分被写到磁盘上的数据文件中,还有一部分已经被置为提交标记的脏块还在内存上,如果此时数据库实例崩溃了,则当数据库实例恢复时,就需要用前滚(这个机制)来完成事务的完全提交,即将先前那部分已经被置为提交标记且还在内存上的脏块写入到磁盘上的数据文件中。

回滚未提交的事务,即该事务未被执行commit命令。但是此时,该事务修改的脏块中也有可能一部分脏块写入到数据文件中了。如果此时数据库实例崩溃了,则当数据库实例恢复时,就需要用回滚(这个机制)来将先前那部分已经写入到数据文件的脏块从数据文件上撤销掉。

可以看到虽然incr b失败了,但是其他两个命令还是执行了。

 

代码实现incr命令:

 

package cn.crxy.redis;

import static org.junit.Assert.*;

import java.util.List;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.Test;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;

public class RedisTest {
    
    String ip = "192.168.1.170";
    int port = 6379;
    Jedis jedis = new Jedis(ip, port);
    /**
     * 仅供测试使用,单机无连接池方式
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {
        //获取到redis服务器的链接
        jedis.set("crxy", "wwww");
        String value = jedis.get("crxy");
        System.out.println(value);
    }
    
    /**
     * 单机连接池方式
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {
        
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //指定连接池中最大空闲连接数
        jedisPoolConfig.setMaxIdle(10);
        //链接池中创建的最大连接数
        jedisPoolConfig.setMaxTotal(100);
        //设置创建链接的超时时间
        jedisPoolConfig.setMaxWaitMillis(2000);
        //表示连接池在创建链接的时候会先测试一下链接是否可用,这样可以保证连接池中的链接都可用的。
        jedisPoolConfig.setTestOnBorrow(true);
        
        //创建一个jedis连接池
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, ip, port);
        //从连接池中获取一个链接
        Jedis jedis = jedisPool.getResource();
        String value = jedis.get("crxy");
        System.out.println(value);
        
        //把连接返回给连接池
        jedisPool.returnResource(jedis);
    }

    /**
     * 手工实现incr命令
     * @throws Exception
     */
    @Test
    public void test4() throws Exception {
        //监控指定键的值
        jedis.watch("a");
        String value = jedis.get("a");
        int parseInt = Integer.parseInt(value);
        parseInt++;
        System.out.println("开始休息");
        Thread.currentThread().sleep(5000);
        Transaction transaction = jedis.multi();
        transaction.set("a", parseInt+"");
        List<Object> exec = transaction.exec();//exec执行后,watch的监控状态就取消了
        if(exec==null){
            System.out.println("a的值被修改了,事务没有执行");
            test4();
        }else{
            System.out.println("事务正常执行了。");
        }
    }
}

 

 

  

 

posted @ 2017-03-04 18:08  亲爱的不二999  阅读(2760)  评论(0编辑  收藏  举报