Mit6.830-SimpleDB-Lab4-事务-学习笔记

Lab4主要包括5个exercise

exercise1、2 :实现一个page级别,遵从二段锁协议的锁管理器。即在访问任何page之前,事务应该获取该page上适当类型的锁,并且在事务提交之前不应该释放任何锁。


exercise3 :完善BufferPool中的evictPage()方法,避免数据丢失,当需要置换的页面为脏页时,要跳过脏页,置换掉不是脏页的page


exercise4 :实现事务的功能,当事务提交时,将事务涉及的脏页写回磁盘,然后释放锁。当事务回滚时,清理该事务涉及到的脏页,重新从磁盘中读取清理的page


exercise5:实现死锁防范功能,当发生死锁时,抛出AbortException

两段锁协议

  • 第一阶段:扩展阶段,事务可以申请获得任意数据上的任意锁,但是不能释放任何锁。
  • 第二阶段:收缩阶段,事务可以释放任何数据上的任何类型的锁,但是不能再次申请任何锁

遵守两端所协议可能会发生死锁:两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁。

simpleDB中的实现过程为,事务提交之前可以获取任何锁,事务提交之后释放该事务所拥有的所有锁。同时在获取锁的过程中进行死锁检测。

实验概述

事务

事务是一组以原子方式执行的数据库操作(例如,插入、删除和读取);也就是说,要么所有的动作都完成了,要么一个动作都没有完成。

  • 原子性:通过两段锁协议和BufferPool的管理实现simpleDB的原子性
  • 一致性:通过原子性实现事务的一致性,simpleDB中没有解决其他一致性问题(例如,键约束)
  • 隔离性:严格的两段锁提供隔离
  • 持久性:事务提交时将脏页强制写进磁盘

锁的赋予

  • 在事务可以读取一个对象之前,它必须拥有一个共享锁。
  • 在一个事务可以写一个对象之前,它必须有一个排他锁。
  • 多个事务可以在一个对象上拥有一个共享锁。
  • 只有一个事务可能对一个对象具有排他锁。
  • 如果对象o上只有事务t持有共享锁,则t可以将 其对o的锁升级为排他锁。

exercise 1

在BufferPool中编写获取和释放锁的方法 ,构造一个page级别的锁管理器

在BufferPool中构造两个类,PageLock类和LockManager类。当事务通过BufferPool访问page时,需要通过LockManager申请锁。

PageLock类

存储事务id和该事务获取的锁的类型。

    private class PageLock{
        public static final int SHARE = 0;
        public static final int EXCLUSIVE = 1;
        private TransactionId tid;
        private int type;

        public PageLock(TransactionId tid, int type){
            this.tid =  tid;
            this.type = type;
        }

        public TransactionId getTid() {
            return tid;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }
    }

LockManager类

参数:

  • ConcurrentHashMap<PageId,ConcurrentHashMap<TransactionId,PageLock>> lockMap;:pageId与锁的映射,记录page上现存的锁。
    在这里插入图片描述

方法:

  • public synchronized boolean acquireLock(PageId pageId, TransactionId tid, int requiredType):事务向LockManager请求获取page上锁的方法,申请思路如下:
    在这里插入图片描述
    根据以上思路实现acquireLock的主要内容
        public synchronized boolean acquireLock(PageId pageId, TransactionId tid, int requiredType) throws TransactionAbortedException, InterruptedException {
            final String lockType = requiredType == 0 ? "read lock" : "write lock";
            final String thread = Thread.currentThread().getName();
            if(lockMap.get(pageId) == null){
                PageLock pageLock = new PageLock(tid,requiredType);
                ConcurrentHashMap<TransactionId,PageLock> pageLocks = new ConcurrentHashMap<>();
                pageLocks.put(tid,pageLock);
                lockMap.put(pageId,pageLocks);
                //System.out.println(thread + ": the " + pageId + " have no lock, transaction" + tid + " require " + lockType + ", accept");
                return true;
            }
            ConcurrentHashMap<TransactionId,PageLock> pageLocks = lockMap.get(pageId);

            if(pageLocks.get(tid) == null){
                // tid没有该page上的锁
                if(pageLocks.size() > 1){
                    //page 上有其他事务的读锁
                    if (requiredType == PageLock.SHARE){
                        //tid 请求读锁
                        PageLock pageLock = new PageLock(tid,PageLock.SHARE);
                        pageLocks.put(tid,pageLock);
                        lockMap.put(pageId,pageLocks);
                        //System.out.println(thread + ": the " + pageId + " have many read locks, transaction" + tid + " require " + lockType + ", accept and add a new read lock");
                        return true;
                    }
                    if (requiredType == PageLock.EXCLUSIVE){
                        // tid 需要获取写锁
                        wait(20);
                        System.out.println(thread + ": the " + pageId + " have lock with diff txid, transaction" + tid + " require write lock, await...");
                        return false;
                    }
                }
                if (pageLocks.size() == 1){
                    //page 上有一个其他事务的锁  可能是读锁,也可能是写锁
                    PageLock curLock = null;
                    for (PageLock lock : pageLocks.values()){
                        curLock = lock;
                    }
                    if (curLock.getType() == PageLock.SHARE){
                        //如果是读锁
                        if (requiredType == PageLock.SHARE){
                            // tid 需要获取的是读锁
                            PageLock pageLock = new PageLock(tid,PageLock.SHARE);
                            pageLocks.put(tid,pageLock);
                            lockMap.put(pageId,pageLocks);
                            //System.out.println(thread + ": the " + pageId + " have one read lock with diff txid, transaction" + tid + " require read lock, accept and add a new read lock");
                            return true;
                        }
                        if (requiredType == PageLock.EXCLUSIVE){
                            // tid 需要获取写锁
                            wait(10);
                            System.out.println(thread + ": the " + pageId + " have lock with diff txid, transaction" + tid + " require write lock, await...");
                            return false;
                        }
                    }
                    if (curLock.getType() == PageLock.EXCLUSIVE){
                        // 如果是写锁
                        wait(10);
                        System.out.println(thread + ": the " + pageId + " have one write lock with diff txid, transaction" + tid + " require read lock, await...");
                        return false;
                    }
                }

            }
            if (pageLocks.get(tid) != null){
                // tid有该page上的锁
                PageLock pageLock = pageLocks.get(tid);
                if (pageLock.getType() == PageLock.SHARE){
                    // tid 有 page 上的读锁
                    if (requiredType == PageLock.SHARE){
                        //tid 需要获取的是读锁
                        //System.out.println(thread + ": the " + pageId + " have one lock with same txid, transaction" + tid + " require " + lockType + ", accept");
                        return true;
                    }
                    if (requiredType == PageLock.EXCLUSIVE){
                        //tid 需要获取的是写锁
                        if(pageLocks.size() == 1){
                            // 该page上 只有tid的 读锁,则可以将其升级为写锁
                            pageLock.setType(PageLock.EXCLUSIVE);
                            pageLocks.put(tid,pageLock);
                            //System.out.println(thread + ": the " + pageId + " have read lock with same txid, transaction" + tid + " require write lock, accept and upgrade!!!");
                            return true;
                        }
                        if (pageLocks.size() > 1){
                            // 该page 上还有其他事务的锁,则不能升级
                            System.out.println(thread + ": the " + pageId + " have many read locks, transaction" + tid + " require write lock, abort!!!");
                            throw new TransactionAbortedException();
                        }
                    }
                }
                if (pageLock.getType() == PageLock.EXCLUSIVE){
                    // tid 有 page上的写锁
                    //System.out.println(thread + ": the " + pageId + " have write lock with same txid, transaction" + tid + " require " + lockType + ", accept");
                    return true;
                }
            }

            System.out.println("----------------------------------------------------");
            return false;
        }
  • public synchronized boolean isholdLock(TransactionId tid, PageId pid):判断指定事务是否持有某一page上的锁
        public synchronized boolean isholdLock(TransactionId tid, PageId pid){
            ConcurrentHashMap<TransactionId,PageLock> pageLocks;
            pageLocks = lockMap.get(pid);
            if(pageLocks == null){
                return false;
            }
            PageLock pageLock = pageLocks.get(tid);
            if(pageLock == null){
                return false;
            }
            return true;
        }
  • public synchronized boolean releaseLock(TransactionId tid, PageId pid):释放指定事务在某一page上的所有锁
        public synchronized boolean releaseLock(TransactionId tid, PageId pid){
            if (isholdLock(tid,pid)){
                ConcurrentHashMap<TransactionId,PageLock> pageLocks = lockMap.get(pid);
                pageLocks.remove(tid);
                if (pageLocks.size() == 0){
                    lockMap.remove(pid);
                }
                this.notifyAll();
                return true;
            }
            return false;
        }
  • public synchronized void completeTransaction(TransactionId tid):事务执行完成,释放该事务在所有page上的锁
        public synchronized void completeTransaction(TransactionId tid){
            Set<PageId> pageIds = lockMap.keySet();
            for (PageId pageId : pageIds){
                releaseLock(tid,pageId);
            }
            //System.out.println(Thread.currentThread().getName() + "  transaction" + tid + "   release the lock on the all locks");
        }

exercise2、5

完善BufferPool中的getPage()方法、并实现死锁检测

  Permissions类:枚举类,有READ_ONLY READ_WRITE两个属性。当getPage()方法传入READ_ONLY时,表明该事务请求的是share_lock。传入READ_WRITE时,请求的是exclusive_lock。


  同时还要检查以往Lab中调用getPage()方法的其他方法(HeapFile.insertTuple()HeapFile.deleteTuple())是否传入了相应的Permissions对象。除此之外,要对HeapFile.insertTuple()进行完善,当事务向page中的空slot插入tuple时,首先需要获取该page上的锁。如果该page上没有空的slot,需要访问下一个page,如果该HeapFile上所有的page都已满,需要创建一个新的page将tuple插入其中。但是,虽然该事务不会对已满的page进行操作,可此时它依旧持有这些page上的锁,其它事务也不能访问这些page,所以当判断某一page上的slot已满时,需要释放掉该page上的锁。虽然这不满足两段锁协议,但该事务并没有使用page中的任何数据,后序也不会用到,所以并不会有任何影响,而且也可以让其他事务访问那些slot已满的page。


解决死锁的方式有:

  • 超时等待:对每个事务设置一个获取锁的超时时间,如果在超时时间内获取不到锁,我们就认为可能发生了死锁,将该事务进行中断。
  • 循环等待图检测:建立事务等待关系的等待图,当等待图出现了环时,说明有死锁发生,在加锁前就进行死锁检测,如果本次加锁请求会导致死锁,就终止该事务。

本exercise中采取的是超时等待的方式解决死锁。


完善getPage方法

  当事务通过BufferPool的getPage()方法获取page时,先确定该事务需要获取锁的类型。通过LockManager获取响应类型的锁,成功获取则正常读取页面。获取失败时,执行acquireLock()方法的线程会进行超时等待,等待超时或者被其他线程唤醒后会再次尝试获取锁,等待时间超过500ms时,对该事务进行回滚并抛出异常,避免死锁的发生。

    public synchronized Page getPage(TransactionId tid, PageId pid, Permissions perm)
        throws TransactionAbortedException, DbException {
        // some code goes here
		//------------------------lab3添加的内容------------------
        int lockType;
        if (perm == Permissions.READ_ONLY){
            lockType = PageLock.SHARE;
        } else {
            lockType = PageLock.EXCLUSIVE;
        }
        long st = System.currentTimeMillis();
        boolean isacquired = false;
        while(!isacquired){

            try {
                isacquired = lockManager.acquireLock(pid,tid,lockType);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long now = System.currentTimeMillis();
            if(now - st > 500){
                throw new TransactionAbortedException();
            }
        }
		//------------------------lab3添加的内容------------------

        if(!bufferPool.containsKey(pid)){
            DbFile dbFile = Database.getCatalog().getDatabaseFile(pid.getTableId());
            Page page = dbFile.readPage(pid);
            LinkedNode node = new LinkedNode(pid,page);
            if(numPages > bufferPool.size()){
                addToHead(node);
                bufferPool.put(pid,node);
                return node.page;
            }
            else{
                //LRU
                evictPage();
                addToHead(node);
                assert bufferPool.size() < numPages;
                bufferPool.put(page.getId(), node);
                return page;
            }
        }
        else{
            LinkedNode node = bufferPool.get(pid);
            moveToHead(node);
            return node.page;
        }

    }

    public void unsafeReleasePage(TransactionId tid, PageId pid) {
        // some code goes here
        // not necessary for lab1|lab2
        lockManager.releaseLock(tid,pid);
    }
	

完善InsertTuple方法

完善HeapFile中的InsertTuple方法

    public List<Page> insertTuple(TransactionId tid, Tuple t)
            throws DbException, IOException, TransactionAbortedException {
        // some code goes here
        // not necessary for lab1
        ArrayList<Page> arrayList = new ArrayList<>();
        HeapPage heapPage;
        for(int pgNo=0; pgNo<numPages(); pgNo++){
            HeapPageId pageId = new HeapPageId(getId(),pgNo);
            heapPage = (HeapPage) Database.getBufferPool().getPage(tid,pageId,Permissions.READ_WRITE);
            if(heapPage.getNumEmptySlots() == 0){
            	//完善的内容,当该page上没有空slot时,释放该page上的锁,避免影响其他事务的访问
                Database.getBufferPool().unsafeReleasePage(tid,pageId);
                continue;
            }
            else {
                heapPage.insertTuple(t);
                arrayList.add(heapPage);
                return arrayList;
            }
        }

        BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(file,true));
        byte[] emptyPage = HeapPage.createEmptyPageData();
        bw.write(emptyPage);
        bw.close();

        HeapPageId newPageId = new HeapPageId(getId(),numPages()-1);
        HeapPage newPage = (HeapPage) Database.getBufferPool().getPage(tid,newPageId,Permissions.READ_WRITE);
        newPage.insertTuple(t);
        arrayList.add(newPage);
        return arrayList;
    }

exercise3

实现NO STEAL策略

  事务对page的修改只有在commit之后才会写入到磁盘,但是在之前的Lab中实现页面置换策略时,当置换掉的页面是dirty page时,也会将更改写回到磁盘。这是不允许的,所以需要完善evictPage()方法,当需要置换的page是dirty page时,需要跳过此page,去置换下一个非dirty的page。当BufferPool中缓存的page都是dirty page时,抛出异常。

完善evictPage()方法

    private synchronized  void evictPage() throws DbException {
        // some code goes here
        // not necessary for lab1

        for (int i=0; i<numPages; i++){
            LinkedNode tail = removeTail();
            Page evictPage = tail.getPage();
            if (evictPage.isDirty() != null){
                addToHead(tail);
            }
            else{
                PageId evictPageId = tail.getPageId();
                discardPage(evictPageId);
                return;
            }
        }
        throw new DbException("all pages are dirty page ");
    }

exercise 4

实现BufferPool中的transactionComplete方法

transactionComplete() 有两个版本,一个接受额外的布尔参数(当布尔参数为true时,进行提交。false时进行回滚),另一个不接受。没有附加参数的版本应该总是提交,因此可以简单地通过调用来实现 transactionComplete(tid, true)。当进行回滚时,从BufferPool中清除掉该事务造成的脏页,并将原始版本重新读到BufferPool中

	 public void transactionComplete(TransactionId tid){
        // some code goes here
        // not necessary for lab1|lab2
        transactionComplete(tid,true);
    }

    public void transactionComplete(TransactionId tid, boolean commit)  {
        // some code goes here
        // not necessary for lab1|lab2
        if(commit){
            try {
                flushPages(tid);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        else {
            restorePages(tid);
        }
        lockManager.completeTransaction(tid);
    }

    public synchronized void restorePages(TransactionId tid){
        for(LinkedNode node : bufferPool.values()){
            PageId pageId = node.getPageId();
            Page page = node.getPage();
            if(tid.equals(page.isDirty())){
                int tableId = pageId.getTableId();
                DbFile table = Database.getCatalog().getDatabaseFile(tableId);
                Page pageFromDisk = table.readPage(pageId);

                node.setPage(pageFromDisk);
                bufferPool.put(pageId,node);
                moveToHead(node);
            }
        }
    }

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MIT 6.824 课程的 Lab1 是关于 Map 的实现,这里单介绍一下实现过程。 MapReduce 是一种布式计算模型,它可以用来处理大规模数据集。MapReduce 的核心想是将数据划分为多个块,每个块都可以在不同的节点上并行处理,然后将结果合并在一起。 在 Lab1 中,我们需要实现 MapReduce 的基本功能,包括 Map 函数、Reduce 函数、分区函数、排序函数以及对作业的整体控制等。 首先,我们需要实现 Map 函数。Map 函数会读取输入文件,并将其解析成一系列键值对。对于每个键值对,Map 函数会将其传递给用户定义的 Map 函数,生成一些新的键值对。这些新的键值对会被分派到不同的 Reduce 任务中,进行进一步的处理。 接着,我们需要实现 Reduce 函数。Reduce 函数接收到所有具有相同键的键值对,并将它们合并成一个结果。Reduce 函数将结果写入输出文件。 然后,我们需要实现分区函数和排序函数。分区函数将 Map 函数生成的键值对映射到不同的 Reduce 任务中。排序函数将键值对按键进行排序,确保同一键的所有值都被传递给同一个 Reduce 任务。 最后,我们需要实现整个作业的控制逻辑。这包括读取输入文件、调用 Map 函数、分区、排序、调用 Reduce 函数以及写入输出文件。 Lab1 的实现可以使用 Go 语言、Python 或者其他编程语言。我们可以使用本地文件系统或者分布式文件系统(比如 HDFS)来存储输入和输出文件。 总体来说,Lab1 是一个比较简单的 MapReduce 实现,但它奠定了 MapReduce 的基础,为后续的 Lab 提供了良好的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值