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("事務正常執行了。");
        }
    }
}

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM