全面解析JTA 深度历险

什么是事务处理

事务是计算机应用中不可或缺的组件模型,它保证了用户操作的原子性 ( Atomicity )、一致性 ( Consistency )、隔离性 ( Isolation ) 和持久性 ( Durabilily )。关于事务最经典的示例莫过于信用卡转账:将用户 A 账户中的 500 元人民币转移到用户 B 的账户中,其操作流程如下 :

1. 将 A 账户中的金额减少 500

2. 将 B 账户中的金额增加 500

这两个操作必须保正 ACID 的事务属性:即要么全部成功,要么全部失败;假若没有事务保障,用户的账号金额将可能发生问题:

假如第一步操作成功而第二步失败,那么用户 A 账户中的金额将就减少 500 元而用户 B 的账号却没有任何增加(不翼而飞);同样如果第一步出错 而第二步成功,那么用户 A 的账户金额不变而用户 B 的账号将增加 500 元(凭空而生)。上述任何一种错误都会产生严重的数据不一致问题,事务的缺失对于一个稳定的生产系统是不可接受的。
J2EE 事务处理方式

1. 本地事务:紧密依赖于底层资源管理器(例如数据库连接 ),事务处理局限在当前事务资源内。此种事务处理方式不存在对应用服务器的依赖,因而部署灵活却无法支持多数据源的分布式事务。在数据库连接中使用本地事务示例如下:

清单 1. 本地事务处理实例

public void transferAccount() {
    Connection conn = null;
    Statement stmt = null;
    try{
      conn = getDataSource().getConnection();
      // 将自动提交设置为 false,
      //若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
      conn.setAutoCommit(false);
      stmt = conn.createStatement();
      // 将 A 账户中的金额减少 500
      stmt.execute("\
      update t_account set amount = amount - 500 where account_id = 'A'");
      // 将 B 账户中的金额增加 500
      stmt.execute("\
      update t_account set amount = amount + 500 where account_id = 'B'");
      // 提交事务
      conn.commit();
      // 事务提交:转账的两步操作同时成功
    } catch(SQLException sqle){
      try{
        // 发生异常,回滚在本事务中的操做
        conn.rollback();
        // 事务回滚:转账的两步操作完全撤销
        stmt.close();
        conn.close();
      }catch(Exception ignore){
      }
      sqle.printStackTrace();
    }
  }

2. 分布式事务处理 : Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS;Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。分布式事务(Distributed Transaction)包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器承担着所有事务参与单元的协调与控制。JTA 事务有效的屏蔽了底层事务资源,使应用可以以透明的方式参入到事务处理中;但是与本地事务相比,XA 协议的系统开销大,在系统开发过程中应慎重考虑是否确实需要分布式事务。若确实需要分布式事务以协调多个事务资源,则应实现和配置所支持 XA 协议的事务资源,如 JMS、JDBC 数据库连接池等。使用 JTA 处理事务的示例如下(注意:connA 和 connB 是来自不同数据库的连接)

清单 2. JTA 事务处理

public void transferAccount() {
	UserTransaction userTx = null;
    Connection connA = null;
    Statement stmtA = null;
    Connection connB = null;
    Statement stmtB = null;
    try{
       // 获得 Transaction 管理对象
      userTx = (UserTransaction)getContext().lookup("\
         java:comp/UserTransaction");
      // 从数据库 A 中取得数据库连接
      connA = getDataSourceA().getConnection();
      // 从数据库 B 中取得数据库连接
      connB = getDataSourceB().getConnection();
            // 启动事务
      userTx.begin();
      // 将 A 账户中的金额减少 500
      stmtA = connA.createStatement();
      stmtA.execute("
      update t_account set amount = amount - 500 where account_id = 'A'");
      // 将 B 账户中的金额增加 500
      stmtB = connB.createStatement();
      stmtB.execute("\
      update t_account set amount = amount + 500 where account_id = 'B'");
      // 提交事务
      userTx.commit();
      // 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
    } catch(SQLException sqle){
      try{
         // 发生异常,回滚在本事务中的操纵
         userTx.rollback();
        // 事务回滚:转账的两步操作完全撤销
        //( 数据库 A 和数据库 B 中的数据更新被同时撤销)
        stmt.close();
        conn.close();
        ...
      }catch(Exception ignore){
      }
      sqle.printStackTrace();
    } catch(Exception ne){
      e.printStackTrace();
    }
  }

JTA 实现原理

很多开发人员都会对 JTA 的内部工作机制感兴趣:我编写的代码没有任何与事务资源(如数据库连接)互动的代码,但是我的操作(数据库更新)却实实在在的被包含在了事务中,那 JTA 究竟是通过何种方式来实现这种透明性的呢? 要理解 JTA 的实现原理首先需要了解其架构:它包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。 根据所面向对象的不同,我们可以将 JTA 的事务管理器和资源管理器理解为两个方面:面向开发人员的使用接口(事务管理器)和面向服务提供商的实现接口(资源管理器)。其中开发接口的主要部分即为上述示例中引用的 UserTransaction 对象,开发人员通过此接口在信息系统中实现分布式事务;而实现接口则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。以数据库为例,IBM 公司提供了实现分布式事务的数据库驱动程序,Oracle 也提供了实现分布式事务的数据库驱动程序, 在同时使用 DB2 和 Oracle 两种数据库连接时, JTA 即可以根据约定的接口协调者两种事务资源从而实现分布式事务。正是基于统一规范的不同实现使得 JTA 可以协调与控制不同数据库或者 JMS 厂商的事务资源。

开发人员使用开发人员接口,实现应用程序对全局事务的支持;各提供商(数据库,JMS 等)依据提供商接口的规范提供事务资源管理功能;事务管理器( TransactionManager )将应用对分布式事务的使用映射到实际的事务资源并在事务资源间进行协调与控制。 下面,本文将对包括 UserTransaction、Transaction 和 TransactionManager 在内的三个主要接口以及其定义的方法进行介绍。

面向开发人员的接口为 UserTransaction (使用方法如上例所示),开发人员通常只使用此接口实现 JTA 事务管理,其定义了如下的方法:

begin()- 开始一个分布式事务,(在后台 TransactionManager 会创建一个 Transaction 事务对象并把此对象通过 ThreadLocale 关联到当前线程上 )

commit()- 提交事务(在后台 TransactionManager 会从当前线程下取出事务对象并把此对象所代表的事务提交)

rollback()- 回滚事务(在后台 TransactionManager 会从当前线程下取出事务对象并把此对象所代表的事务回滚)

getStatus()- 返回关联到当前线程的分布式事务的状态 (Status 对象里边定义了所有的事务状态,感兴趣的读者可以参考 API 文档 )

setRollbackOnly()- 标识关联到当前线程的分布式事务将被回滚

面向提供商的实现接口主要涉及到 TransactionManager 和 Transaction 两个对象

Transaction 代表了一个物理意义上的事务,在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程。UserTransaction 接口中的 commit()、rollback(),getStatus() 等方法都将最终委托给 Transaction 类的对应方法执行。Transaction 接口定义了如下的方法:

commit()- 协调不同的事务资源共同完成事务的提交

rollback()- 协调不同的事务资源共同完成事务的回滚

setRollbackOnly()- 标识关联到当前线程的分布式事务将被回滚

getStatus()- 返回关联到当前线程的分布式事务的状态

enListResource(XAResource xaRes, int flag)- 将事务资源加入到当前的事务中(在上述示例中,在对数据库 A 操作时 其所代表的事务资源将被关联到当前事务中,同样,在对数据库 B 操作时其所代表的事务资源也将被关联到当前事务中)
delistResourc(XAResource xaRes, int flag)- 将事务资源从当前事务中删除

registerSynchronization(Synchronization sync)- 回调接口,Hibernate 等 ORM 工具都有自己的事务控制机制来保证事务, 但同时它们还需要一种回调机制以便在事务完成时得到通知从而触发一些处理工作,如清除缓存等。这就涉及到了 Transaction 的回调接口 registerSynchronization。工具可以通过此接口将回调程序注入到事务中,当事务成功提交后,回调程序将被激活。

TransactionManager 本身并不承担实际的事务处理功能,它更多的是充当用户接口和实现接口之间的桥梁。下面列出了 TransactionManager 中定义的方法,可以看到此接口中的大部分事务方法与 UserTransaction 和 Transaction 相同。 在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程上;同样 UserTransaction.commit() 会调用 TransactionManager.commit(), 方法将从当前线程下取出事务对象 Transaction 并把此对象所代表的事务提交, 即调用 Transaction.commit()

begin()- 开始事务

commit()- 提交事务

rollback()- 回滚事务

getStatus()- 返回当前事务状态

setRollbackOnly()

getTransaction()- 返回关联到当前线程的事务

setTransactionTimeout(int seconds)- 设置事务超时时间

resume(Transaction tobj)- 继续当前线程关联的事务

suspend()- 挂起当前线程关联的事务

在系统开发过程中会遇到需要将事务资源暂时排除的操作,此时就需要调用 suspend() 方法将当前的事务挂起:在此方法后面所做的任何操作将不会被包括在事务中,在非事务性操作完成后调用 resume()以继续事务(注: 要进行此操作需要获得 TransactionManager 对象, 其获得方式在不同的 J2EE 应用服务器上是不一样的)。

清单 3. 开始事务 - UserTransactionImpl implenments UserTransaction

public void begin() throws NotSupportedException, SystemException {
  // 将开始事务的操作委托给 TransactionManagerImpl
  TransactionManagerImpl.singleton().begin();
   }

清单 4. 开始事务 - TransactionManagerImpl implements TransactionManager

// 此处 transactionHolder 用于将 Transaction 所代表的事务对象关联到线程上
private static ThreadLocal transactionHolder
    = new ThreadLocal();
   //TransacationMananger 必须维护一个全局对象,因此使用单实例模式实现
   private static TransactionManagerImpl singleton = new TransactionManagerImpl();
   private TransactionManagerImpl(){
   }
   public static TransactionManagerImpl singleton(){
    return singleton;
   }
   public void begin() throws NotSupportedException, SystemException {
     //XidImpl 实现了 Xid 接口,其作用是唯一标识一个事务
     XidImpl xid = new XidImpl();
     // 创建事务对象,并将对象关联到线程
     TransactionImpl tx = new TransactionImpl(xid);
     transactionHolder.set(tx);
   }

现在我们就可以理解 Transaction 接口上没有定义 begin 方法的原因了:Transaction 对象本身就代表了一个事务,在它被创建的时候就表明事务已经开始,因此也就不需要额外定义 begin() 方法了。

清单 5. 提交事务 - UserTransactionImpl implenments UserTransaction

public void commit() throws RollbackException, HeuristicMixedException,
    HeuristicRollbackException, SecurityException,
    IllegalStateException, SystemException {
    // 检查是否是 Roll back only 事务,如果是回滚事务
      if(rollBackOnly){
      rollback();
      return;
     } else {
      // 将提交事务的操作委托给 TransactionManagerImpl
      TransactionManagerImpl.singleton().commit();
     }
}

清单 6. 提交事务 - TransactionManagerImpl implenments TransactionManager

public void commit() throws RollbackException, HeuristicMixedException,
  HeuristicRollbackException, SecurityException,
  IllegalStateException, SystemException {
   // 取得当前事务所关联的事务并通过其 commit 方法提交
   TransactionImpl tx = transactionHolder.get();
   tx.commit();
       }

同理, rollback、getStatus、setRollbackOnly 等方法也采用了与 commit() 相同的方式实现。 UserTransaction 对象不会对事务进行任何控制, 所有的事务方法都是通过 TransactionManager 传递到实际的事务资源即 Transaction 对象上。

上述示例演示了 JTA 事务的处理过程,下面将为您展示事务资源(数据库连接,JMS)是如何以透明的方式加入到 JTA 事务中的。首先需要明确的一点是,在 JTA 事务 代码中获得的数据库源 ( DataSource ) 必须是支持分布式事务的。在如下的代码示例中,尽管所有的数据库操作都被包含在了 JTA 事务中,但是因为 MySql 的数据库连接是通过本地方式获得的,对 MySql 的任何更新将不会被自动包含在全局事务中。

清单 7. JTA 事务处理

public void transferAccount() {
    UserTransaction userTx = null;
    Connection mySqlConnection = null;
    Statement mySqlStat = null;
    Connection connB = null;
    Statement stmtB = null;
    try{
        // 获得 Transaction 管理对象
      userTx =
      (UserTransaction)getContext().lookup("java:comp/UserTransaction");
      // 以本地方式获得 mySql 数据库连接
      mySqlConnection = DriverManager.getConnection("localhost:1111");
      // 从数据库 B 中取得数据库连接, getDataSourceB 返回应用服务器的数据源
      connB = getDataSourceB().getConnection();
            // 启动事务
      userTx.begin();
      // 将 A 账户中的金额减少 500
      //mySqlConnection 是从本地获得的数据库连接,不会被包含在全局事务中
      mySqlStat = mySqlConnection.createStatement();
      mySqlStat.execute("
      update t_account set amount = amount - 500 where account_id = 'A'");
      //connB 是从应用服务器得的数据库连接,会被包含在全局事务中
      stmtB = connB.createStatement();
      stmtB.execute("
      update t_account set amount = amount + 500 where account_id = 'B'");
      // 事务提交:connB 的操作被提交,mySqlConnection 的操作不会被提交
      userTx.commit();
    } catch(SQLException sqle){
      // 处理异常代码
    } catch(Exception ne){
      e.printStackTrace();
    }
  }

为什么必须从支持事务的数据源中获得的数据库连接才支持分布式事务呢?其实支持事务的数据源与普通的数据源是不同的,它实现了额外的 XADataSource 接口。我们可以简单的将 XADataSource 理解为普通的数据源(继承了 java.sql.PooledConnection),只是它为支持分布式事务而增加了 getXAResource 方法。另外,由 XADataSource 返回的数据库连接与普通连接也是不同的,此连接除了实现 java.sql.Connection 定义的所有功能之外还实现了 XAConnection 接口。我们可以把 XAConnection 理解为普通的数据库连接,它支持所有 JDBC 规范的数据库操作,不同之处在于 XAConnection 增加了对分布式事务的支持。

应用程序从支持分布式事务的数据源获得的数据库连接是 XAConnection 接口的实现,而由此数据库连接创建的会话(Statement)也为了支持分布式事务而增加了功能,如下代码所示:

清单 8. JTA 事务资源处理

public void transferAccount() {
    UserTransaction userTx = null;
    Connection conn = null;
    Statement stmt = null;
    try{
        // 获得 Transaction 管理对象
      userTx = (UserTransaction)getContext().lookup("
      java:comp/UserTransaction");
      // 从数据库中取得数据库连接, getDataSourceB 返回支持分布式事务的数据源
      conn = getDataSourceB().getConnection();
            // 会话 stmt 已经为支持分布式事务进行了功能增强
      stmt = conn.createStatement();
            // 启动事务
      userTx.begin();
      stmt.execute("update t_account ... where account_id = 'A'");
      userTx.commit();
    } catch(SQLException sqle){
      // 处理异常代码
    } catch(Exception ne){
      e.printStackTrace();
    }
  }

我们来看一下由 XAConnection 数据库连接创建的会话(Statement)部分的代码实现(不同的 JTA 提供商会有不同的实现方式,此处代码示例只是向您演示事务资源是如何被自动加入到事务中)。 我们以会话对象的 execute 方法为例,通过在方法开始部分增加对 associateWithTransactionIfNecessary 方法的调用,即可以保证在 JTA 事务期间,对任何数据库连接的操作都会被透明的加入到事务中。

清单 9. 将事务资源自动关联到事务对象 - XAStatement implements Statement

public void execute(String sql) {
        // 对于每次数据库操作都检查此会话所在的数据库连接是否已经被加入到事务中
    associateWithTransactionIfNecessary();
    try{
           // 处理数据库操作的代码
       ....
    } catch(SQLException sqle){
      // 处理异常代码
    } catch(Exception ne){
      e.printStackTrace();
    }
  }
public void associateWithTransactionIfNecessary(){
    // 获得 TransactionManager
    TransactionManager tm = getTransactionManager();
        Transaction tx = tm.getTransaction();
      // 检查当前线程是否有分布式事务
      if(tx != null){
      // 在分布式事务内,通过 tx 对象判断当前数据连接是否已经被包含在事务中,
      //如果不是那么将此连接加入到事务中
      Connection conn = this.getConnection();
      //tx.hasCurrentResource, xaConn.getDataSource() 不是标准的 JTA
            // 接口方法,是为了实现分布式事务而增加的自定义方法
      if(!tx.hasCurrentResource(conn)){
        XAConnection xaConn = (XAConnection)conn;
        XADataSource xaSource = xaConn.getDataSource();
        // 调用 Transaction 的接口方法,将数据库事务资源加入到当前事务中
        tx.enListResource(xaSource.getXAResource(), 1);
        }
      }
    }

XAResource 与 Xid: XAResource 是 Distributed Transaction Processing: The XA Specification 标准的 Java 实现,它是对底层事务资源的抽象,定义了分布式事务处理过程中事务管理器和资源管理器之间的协议,各事务资源提供商(如 JDBC 驱动,JMS)将提供此接口的实现。使用此接口,开发人员可以通过自己的编程实现分布式事务处理,但这些通常都是由应用服务器实现的(服务器自带实现更加高效,稳定) 为了说明,我们将举例说明他的使用方式。

在使用分布式事务之前,为了区分事务使之不发生混淆,必须实现一个 Xid 类用来标识事务,可以把 Xid 想象成事务的一个标志符,每次在新事务创建是都会为事务分配一个 Xid,Xid 包含三个元素:formatID、gtrid(全局事务标识符)和 bqual(分支修饰词标识符)。 formatID 通常是零,这意味着你将使用 OSI CCR(Open Systems Interconnection Commitment, Concurrency 和 Recovery 标准)来命名;如果你要使用另外一种格式,那么 formatID 应该大于零,-1 值意味着 Xid 为无效。
gtrid 和 bqual 分别包含 64 个字节二进制码来分别标识全局事务和分支事务, 唯一的要求是 gtrid 和 bqual 必须是全局唯一的。

XAResource 接口中主要定义了如下方法:

commit()- 提交事务

isSameRM(XAResource xares)- 检查当前的 XAResource 与参数是否同一事务资源

prepare()- 通知资源管理器准备事务的提交工作

rollback()- 通知资源管理器回滚事务

在事务被提交时,Transaction 对象会收集所有被当前事务包含的 XAResource 资源,然后调用资源的提交方法,如下代码所示:

清单 10. 提交事务 - TransactionImpl implements Transaction

public void commit() throws RollbackException, HeuristicMixedException,
       HeuristicRollbackException, SecurityException,
       IllegalStateException, SystemException {
       // 得到当前事务中的所有事务资源
        List list = getAllEnlistedResouces();
       // 通知所有的事务资源管理器,准备提交事务
            // 对于生产级别的实现,此处需要进行额外处理以处理某些资源准备过程中出现的异常
       for(XAResource xa : list){
         xa.prepare();
       }
       // 所有事务性资源,提交事务
       for(XAResource xa : list){
         xa.commit();
       }
    }

结束语

通过如上介绍相信读者对 JTA 的原理已经有所了解,本文中的示例代码都是理想情况下的假设实现。一款完善成熟的 JTA 事务实现需要考虑与处理的细节非常多,如性能(提交事务的时候使用多线程方式并发提交事务)、容错(网络,系统异常)等, 其成熟也需要经过较长时间的积累。感兴趣的读者可以阅读一些开源 JTA 实现以进一步深入学习。希望本文对大家能有所帮助。

(0)

相关推荐

  • Java Swing中的下拉式菜单(menu)、弹出式菜单(JPopupMenu)、选项卡窗体(JTabbedPane)组件使用案例

    菜单是GUI中最常用的组件,菜单不是Component类的子类,不能放置在普通容器中,不受布局管理器的约束,只能放置在菜单栏中. 菜单组件由菜单栏 (MenuBar).菜单(Menu)和菜单项(MenuItem)三部分组成. 一个菜单栏由若干个菜单组成,一个菜单又由若干个菜单项组成.一般菜单栏放 Frame 窗口中,只要调用 Frame 类的 setMenuBar()方法即可. 常用的菜单有:下拉式菜单和弹出式菜单(独立显示,可出现在任意地方). 一:下拉式菜单的创建步骤: 1.创建一个菜单栏.

  • Java Swing中的表格(JTable)和树(JTree)组件使用实例

    一:表格(JTable): 1.基本概念: 表格(JTable)是Swing 新增加的组件,主要是为了将数据以表格的形式显示.给显示大块数据提供了简单的机制. 2.常用构造方法: * JTable():使用系统默认的模型创建一个JTable 实例.  * JTable(int numRows,int numColumns):创建一个使用DefaultTableModel 指定行.列的空表格.  * JTable(Object[ ][ ] rowData,Object[ ][ ] columnNa

  • 详解Java的MyBatis框架中的事务处理

    一.MyBatis单独使用时,使用SqlSession来处理事务: public class MyBatisTxTest { private static SqlSessionFactory sqlSessionFactory; private static Reader reader; @BeforeClass public static void setUpBeforeClass() throws Exception { try { reader = Resources.getResourc

  • Java中JDBC事务与JTA分布式事务总结与区别

    Java事务的类型有三种:JDBC事务.JTA(Java Transaction API)事务.容器事务.常见的容器事务如Spring事务,容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现.所以本文暂不讨论容器事务.本文主要介绍J2EE开发中两个比较基本的事务:JDBC事务和JTA事务. JDBC事务 JDBC的一切行为包括事务是基于一个Connection的,在JDBC中是通过Connection对象进行事务管理.在JDBC中,

  • java Swing实现选项卡功能(JTabbedPane)实例代码

     Swing实现选项卡功能(JTabbedPane) 先创建JTabbedPane对象,构造函数可使用JTabbedPane(int tabPlacement).tabPlacement是JTabbedPane从接口 javax.swing.SwingConstants 继承的字段.可以是BUTTOM,TOP等.如下代码所示: JFrame jframe = new JFrame("TEST"); <a href="http://lib.csdn.net/base/do

  • 全面解析JTA 深度历险

    什么是事务处理 事务是计算机应用中不可或缺的组件模型,它保证了用户操作的原子性 ( Atomicity ).一致性 ( Consistency ).隔离性 ( Isolation ) 和持久性 ( Durabilily ).关于事务最经典的示例莫过于信用卡转账:将用户 A 账户中的 500 元人民币转移到用户 B 的账户中,其操作流程如下 : 1. 将 A 账户中的金额减少 500 2. 将 B 账户中的金额增加 500 这两个操作必须保正 ACID 的事务属性:即要么全部成功,要么全部失败:假

  • 解析JAVA深度克隆与浅度克隆的区别详解

    在JAVA克隆对象不能简单的使用clone方法,clone方法只是进行浅克隆.请看下方:深度克隆类:Java代码 复制代码 代码如下: import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public cla

  • asp,asp.net学习教程下载

    XML与ASP网站实作大全 ASP经典百例 ASP.NET 1.1专业开发 ASP.NET完全手册 ASP.NET 技术参考 ASP.netdatabase开发圣经 ASP数据库系统开发实例导航 怎样用ASP和COM进行Web编程 Asp.Net技术文档 ASP Net密技集錦(C#) 即时应用ASP脚本第二版 简单易学的ASP教程 ASP开发中的错误信息中文说明大全 10天学会ASP ASP中文使用手册 ASP.NET服务器控件高程 ASP.NET 实用全书 ASP 3.0高级编程 ASP.N

  • 多业务解决方案的QoS描述

    一.QoS概述 在任何时间.任何地点和任何人实现任何媒介信息的交流是人类在通信领域的永恒需求,在IP技术成熟以前,所有的网络都是单一业务网络,如PSTN只能开电话业务,有线电视网只能承载电视业务,X.25网只能承载数据业务等.网络的分离造成业务的分离,降低了沟通的效率. 由于互联网的流行,IP应用日益广泛,IP网络已经渗入各种传统的通信范围,基于IP构建一个多业务网络成为可能.但是,不同的业务对网络的要求是不同的,如何在分组化的IP网络实现多种实时和非实时业务成为一个重要话题,人们提出了QoS(

  • 详解RedisTemplate下Redis分布式锁引发的系列问题

    自己的项目因为会一直抓取某些信息,但是本地会和线上经常一起跑,造成冲突.这其实就是我们常说的分布式集群的问题了,本地和线上的服务器构成了集群以及QPS为2的小并发(其实也不叫并发,不知道拿什么词形容?). 首先,分布式集群的问题大家都知道,会造成数据库的插入重复问题,会造成一系列的并发性问题. 解决的方式呢也大概如下几点,百度以及谷歌上都能搜到的解决方式: 1:数据库添加唯一索引 2:设计接口幂等性 3:依靠中间件使用分布式锁,而分布式锁又分为Redis和Zookeeper 由于Zookeepe

  • 基于Redis位图实现用户签到功能

    场景需求 适用场景如签到送积分.签到领取奖励等,大致需求如下: 签到1天送1积分,连续签到2天送2积分,3天送3积分,3天以上均送3积分等. 如果连续签到中断,则重置计数,每月初重置计数. 当月签到满3天领取奖励1,满5天领取奖励2,满7天领取奖励3--等等. 显示用户某个月的签到次数和首次签到时间. 在日历控件上展示用户每月签到情况,可以切换年月显示--等等. 设计思路 对于用户签到数据,如果每条数据都用K/V的方式存储,当用户量大的时候内存开销是非常大的.而位图(BitMap)是由一组bit

  • Redis基于Bitmap实现用户签到功能

    目录 功能分析 更多应用场景 总结 参考资料 很多应用上都有用户签到的功能,尤其是配合积分系统一起使用.现在有以下需求: 签到1天得1积分,连续签到2天得2积分,3天得3积分,3天以上均得3积分等. 如果连续签到中断,则重置计数,每月重置计数. 显示用户某月的签到次数和首次签到时间. 在日历控件上展示用户每月签到,可以切换年月显示. ... 功能分析 对于用户签到数据,如果直接采用数据库存储,当出现高并发访问时,对数据库压力会很大,例如双十一签到活动.这时候应该采用缓存,以减轻数据库的压力,Re

  • Redis限流的几种实现

    目录 一.简单的限流 基本原理 二.漏斗限流 基本原理 Redis-Cell 参考来源 一.简单的限流 基本原理 当系统处理能力有限,如何组织计划外的请求对系统施压.首先我们先看下一些简单的限流策略,防止暴力攻击.比如要对IP访问,没5s只能访问10次,超过进行拦截. 如上图,一般使用滑动窗口来统计区间时间内的访问次数. 使用 zset 记录 IP 访问次数,每个 IP 通过 key 保存下来,score 保存当前时间戳,value 唯一用时间戳或者UUID来实现 代码实现 public cla

  • 详细聊聊Redis的过期策略

    保存过期时间 Redis可以为每个key设置过期时间,会将每个设置了过期时间的key放入一个独立的字典中. typedef struct redisDb { int id; //id是数据库序号,为0-15(默认Redis有16个数据库) long avg_ttl; //存储的数据库对象的平均ttl(time to live),用于统计 dict *dict; //存储数据库所有的key-value dict *expires; //存储key的过期时间 dict *blocking_keys;

  • JavaScript 数组的深度复制解析

    对于javascript而言,数组是引用类型,如果要想复制一个数组就要动脑袋想想了,因为包括concat.slice在内的函数,都是浅层复制.也就是说,对于一个二维数组来说,用concat来做复制,第二维的数组还是引用,修改了新数组同样会使旧数组发生改变. 于是乎,想要写一个深度复制的函数,来帮助做组数的深度复制. 一般情况下,使用 "=" 可以实现赋值.但对于数组.对象.函数等这些引用类型的数据,这个符号就不好使了. 1. 数组的简单复制 1.1 简单遍历 最简单也最基础的方式,自然

随机推荐