分布式锁-使用DB 实现

DB分布式锁

多线程情况下对共享资源的操作需要加锁,避免数据被写乱,在分布式系统中,这个问题也是存在的,此时就需要一个分布式锁服务。常见的分布式锁实现一般是基于DB、Redis、zookeeper。下面笔者会按照顺序分析下这3种分布式锁的设计与实现。

分布式锁的实现由多种方式,但是不管怎样,分布式锁一般要有以下特点:

  • 排他性:任意时刻,只能有一个client能获取到锁
  • 容错性:分布式锁服务一般要满足AP,也就是说,只要分布式锁服务集群节点大部分存活,client就可以进行加锁解锁操作
  • 避免死锁:分布式锁一定能得到释放,即使client在释放之前崩溃或者网络不可达

除了以上特点之外,分布式锁最好也能满足可重入、高性能、阻塞锁特性(AQS这种,能够及时从阻塞状态唤醒)。

DB 实现方式:

下面就使用数据库的方式来实现一下分布式锁,使用下属方案存在一些问题,代码还是需要改进,请谨慎用于生成。

mavn 依赖

        
            mysql
            mysql-connector-java
            5.1.6
        

DBLock 锁实现



import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class DbLock {

    // 插入数据库的值
    private static final int LOCK_ID = 1;

    // 非阻塞式加锁

    public boolean tryLock() {

        boolean bret = false;
        try {

            Statement st = null;
            Connection conn = JdbcUtils.getConnection();
            // 编写sql
            String sql = "INSERT INTO db_lock (id) VALUES (1)";
            st = conn.createStatement();
            st.execute(sql);
            JdbcUtils.close(conn, null);
            bret = true;
        } catch (Exception ex) {
            return bret;
        }
        return bret;
    }

    /**
     * 数据据库锁
     */
    public void lock() {

        // 尝试加锁
        if (tryLock()) {
            return;
        }
        // 如果没有成功等待,进行重试
        waitLock();

        // 递归调用再次尝试枷锁
        lock();
    }

    // 让当前线程休眠进行重试

    public void waitLock() {

        try {

            Thread.currentThread().sleep(10);

        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    /**
     *  释放锁
     */
    public void unlock(){

        try{
            Statement st = null;
            Connection conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            // 编写sql
            String sql = "delete from db_lock where id = "+LOCK_ID;
            st.execute(sql);
            JdbcUtils.close(conn, null);

        }catch (SQLException e){
            e.printStackTrace();
        }
    }

}

 db 工具类 (数据库脚本就不提供了)

package com.qiku.study.db;

import java.sql.*;

public class JdbcUtils {

    // 可以把几个字符串定义成常量:用户名,密码,URL,驱动类
    private static final String USER = "root";
    private static final String PWD = "root";
    private static final String URL = "jdbc:mysql://10.0.0.1:3306/db_lock";
    private static final String DRIVER = "com.mysql.jdbc.Driver";

    /**
     * 注册驱动(可以省略)
     */
    static {
        try {
            Class.forName(DRIVER); }
        catch (ClassNotFoundException e) { e.printStackTrace(); }
    }

    /**
     * 得到数据库的连接
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USER, PWD);
    }

    /**
     * 关闭所有打开的资源
     */
    public static void close(Connection conn, Statement stmt){
        if(stmt != null) {
            try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if(conn != null) {
            try { conn.close(); }catch (SQLException e) { e.printStackTrace(); }
        }

    }

    /**
     * 关闭所有打开的资源 重载
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        if(rs != null) {
            try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
        }

        close(conn, stmt);
    }
}

 测试代码如下:

import com.qiku.study.db.DbLock;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

public class DBTickerTest {

    private int count = 100;


    DbLock dbLock = new DbLock();

    @Test
    public  void tickeTest() throws Exception {

        TickRunnable tr = new TickRunnable();

        new Thread(tr,"窗口A").start();
        new Thread(tr,"窗口B").start();
        new Thread(tr,"窗口c").start();
        new Thread(tr,"窗口d").start();
        new Thread(tr,"窗口e").start();
        new Thread(tr,"窗口f").start();
        new Thread(tr,"窗口g").start();

        Thread.sleep(50000);
    }


    public class TickRunnable implements Runnable {

        @Override
        public void run() {

            while (count>0) {
                dbLock.lock();
                try {
                    if(count>0){
                        System.out.println(Thread.currentThread().getName()+"售出:"+ count -- + " 张票");
                    }
                }finally {
                    dbLock.unlock();
                }

            }
        }
    }
}

DB 实现分布式锁方案缺点如下:

缺点:

    1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
    2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
    3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
    4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

解决方案:
     1、数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
     2、没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
     3、非阻塞的?搞一个while循环,直到insert成功再返回成功。
     4、非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。

你可能感兴趣的:(java,相关,服务器相关)