Netty游戏服务器实战开发(7):利用redis或者zookeeper实现3pc分布式事务锁(一)。支撑腾讯系列某手游百万级流量公测

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_23086307/article/details/82966129

引导:博主在跳槽之前在一家和腾讯代理游戏厂商做手游服务器开发。在那学习到很多有关腾讯的开发规范和知识。此文的部分知识就是在那学习到的。
正文:
在分布式系统中,我们实现对临界资源加锁的方式不能像在独立进程中使用jdk自带的锁的方式进行加锁了。我们需要夸进程加锁,所以实现的是分布式事务锁。对于分布式事务锁实现的方案估计很多人都知道,有关于redis的,zookeeper的。
下面我们采用提问的方式来一步步讲解分布式事务锁的学习过程。
1:什么是分布式系统?
2:什么是分布式事务?
2:什么是分布式事务锁?
3:事务锁提交的每个阶段性的原理。
4:如何打造一款通用的分布式所务锁组件

一:什么是分布式系统?

我们在分布式系统中采用分布式事务锁。那我们必须要明白什么是分布式系统。所谓分布式系统,维基百科是这样定义的:

是建立在网络之上的软件系统。 正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。 因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。 内聚性是指每一个数据库分布节点高度自治,有本地的数据库管理系统。

用通俗一点比喻来说就是:就想火车站有东站西站北站南站那样,都可以坐高铁。

二:什么是分布式事务?

说起事务,我们应该在上大学的时候学习数据库基础原理的时候就有事务的介绍。一个事物具有特定的性质。

  • 1.原子性:不可在细分,要么执行,要么不执行
  • 2.一致性:主要针对数据库,事物执行成功则数据库变更,失败不变更。
  • 3.隔离性:事物之间可以同时执行,事物之间是隔离的
  • 4.持续性:事物一旦执行成功,执行之后的结果是持续的

分布式事务也是事务的一种,所以也具有上述几种特性。那么什么是分布式事务呢?

分布式事务是指会涉及到操作多个数据的事务。其实就是将对同数据源事务的概念扩大到了对多个数据源的事务。目的是为了保证分布式系统中的数据一致性。分布式事务处理的关键是必须有一种方法可以知道事务在任何地方所做的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或全部回滚)

但是随着大型网站的各种高并发访问、海量数据处理等场景越来越多,如何实现网站的高可用、易伸缩、可扩展、安全等目标就显得越来越重要。为了解决这样一系列问题,大型网站的架构也在不断发展。提高大型网站的高可用架构,不得不提的就是分布式。提到分布式就不得不说分布式中存在的一致性问题,所以我们就得解决分布式的一致性问题,其中包括什么是分布式事务,二阶段提交和三阶段提交。

下面我们来简单的介绍一下什么是二阶段提交和三阶段提交,以及他们的区别。

2.1二阶段提交2PC

二阶段提交(Two-phaseCommit)是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常,二阶段提交也被称为是一种协议(Protocol))。在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。

所谓的两个阶段是指:第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)

准备阶段:事务协调者(事务管理器)给每个参与者(资源管理器)发送Prepare消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。

准备阶段可以细分为三个步骤

1)协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。

2)参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)

3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。

提交阶段

如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)

接下来分两种情况分别讨论提交阶段的过程。

(1):当协调者节点从所有参与者节点获得的相应消息都为”同意”时:

在这里插入图片描述

1)协调者节点向所有参与者节点发出”正式提交(commit)”的请求。

2)参与者节点正式完成操作,并释放在整个事务期间内占用的资源。

3)参与者节点向协调者节点发送”完成”消息。

4)协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。

如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
在这里插入图片描述

1)协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。

2)参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。

3)参与者节点向协调者节点发送”回滚完成”消息。

4)协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。

2.2:三阶段提交3PC

三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
在这里插入图片描述

与两阶段提交不同的是,三阶段提交有两个改动点。

1、引入超时机制。同时在协调者和参与者中都引入超时机制。
2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

2.2.1:CanCommit
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

  • 1.事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。

  • 2.响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

2.2.2:PreCommit阶段
协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

  • 1.发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。

  • 2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。

  • 3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

  • 1.发送中断请求 协调者向所有参与者发送abort请求。

  • 2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

2.2.3:doCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况。
执行提交

  • 1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。

  • 2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。

  • 3.响应反馈 事务提交完之后,向协调者发送Ack响应。

  • 4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。

中断事务 协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

  • 1.发送中断请求 协调者向所有参与者发送abort请求

  • 2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。

  • 3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息

  • 4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

段落总结

在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。

2.3:2PC与3PC的区别

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos.

了解了2PC和3PC之后,我们可以发现,无论是二阶段提交还是三阶段提交都无法彻底解决分布式的一致性问题。

三:什么是分布式事务锁?

3.1什么是分布式事务锁

锁是开发过程中十分常见的工具,在处理高并发请求的时候和数据的时候往往需要锁来帮助我们保证数据的安全。java提供了两种内置的锁的实现,一种是由JVM实现的synchronized和JDK提供的Lock,当你的应用是单机或者说单进程应用时,可以使用synchronized或Lock来实现锁。

但是,当你的应用涉及到多机、多进程共同完成时,例如现在的互联网架构,一般都是分布式的RPC框架来支撑,那么这样你的Server有多个,由于负载均衡的路由规则随机,相同的请求可能会打到不同的Server上进行处理,那么这时候就需要一个全局锁来实现多个线程(不同的进程)之间的同步。

3.2分布式事物锁实现的方案

分布式事务锁实现的方案一般有3种

  • 基于数据库主键实现
  • 基于redis实现
  • 基于zookeeper实现

四:如何打造一款通用的分布式所务锁组件

在实际生产项目中,一般基于redis或者zookeeper实现一套分布式事务锁。本文实现的分布式锁组件就是利用redis和zookeeper来实现的。

4.1:项目创建和配置说明
首先我们在idea中创建工程项目,当然我们使用maven进行管理,所以我们创建一个maven项目
第一步:创建maven项目
在这里插入图片描述

输入项目名称后点击next
第二步:选入合适的位置,然后finish
创建项目

第三步:添加相关pom.xml和配置文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>twjitm-transaction</groupId>
    <artifactId>twjitm-transaction</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>


        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!-- Logging -->

        <dependency>
            <groupId>org.jdom</groupId>
            <artifactId>jdom2</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>


        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.4.2</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>


        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.9</version>
        </dependency>
    </dependencies>

    <build>

        <resources>
            <resource>
                <directory>src/main/Java</directory>
                <!-- 包含 -->
                <includes>
                    <include>**/*.vm</include>
                    <include>**/*.properties</include>
                </includes>
                <!-- 排除  -->
                <excludes>
                    <exclude>**/*.xml</exclude>
                </excludes>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <generatedSourcesDirectory>${project.build.directory}</generatedSourcesDirectory>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <!-- 定义在prepare-package时将classes/com打jar -->
                <goals>
                    <goal>jar</goal>
                </goals>
                <configuration>
                    <classesDirectory>${project.basedir}/target/classes</classesDirectory>
                    <includes>
                        <include>com/**</include>
                    </includes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <phase>verify</phase><!--  要绑定到的生命周期的阶段 在verify之后,install之前执行下面指定的goal -->
                        <goals>
                            <goal>jar-no-fork</goal><!-- 类似执行mvn source:jar -->
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

项目利用spring来管理bean对象,所以添加spring的相关依赖。
添加spring的配置文件
(1):bean对象扫描配置applicationContext-service.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
						http://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/context
						http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config />
    <context:component-scan base-package="com.twjitm.transaction.*" />
    <bean class="com.twjitm.transaction.config.NettyRedisConfigService"
          init-method="initRedisPoolConfig">
    </bean>
</beans>

(2):支持分布式事务的redis基本信息的配置
单机版:applicationContext-redis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
						http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- ************************ 全局配置信息 ************************ -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="30"/>
        <!-- 打开检查,用异步线程evict进行检查 -->
        <property name="testWhileIdle" value="true"/>
        <!-- 设置的Evict线程的时间,单位ms,大于0才会开启evict检查线程,两次扫描之间要sleep的毫秒数 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 代表每次检查链接的数量,建议设置和maxActive一样大,这样每次可以有效检查所有的链接 -->
        <property name="numTestsPerEvictionRun" value="30"/>
        <!-- 校验连接池中闲置时间超过minEvictableIdleTimeMillis的连接对象 -->
        <property name="minEvictableIdleTimeMillis" value="60000"/>
    </bean>

    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <constructor-arg index="0" ref="poolConfig"/>
        <constructor-arg index="1" value="127.0.0.1"/>
        <constructor-arg index="2" value="6379"/>
        <constructor-arg index="3" value="3000"/>
        <!--<constructor-arg index="4" value="" ></constructor-arg>-->
        <!--<constructor-arg index="5" value="0" />-->
    </bean>

    <!--单机模式-->
    <bean id="nettyTransactionRedisService"
          class="com.twjitm.transaction.service.redis.impl.NettyTransactionRedisServiceImpl">
        <property name="jedisPool" ref="jedisPool"/>
    </bean>
</beans>

集群版applicationContext-redis-cluster.xml

<?xml version='1.0' encoding='UTF-8'?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-destroy-method="close" default-lazy-init="false">
    <bean name="genericObjectPoolConfig" class="org.apache.commons.pool2.impl.GenericObjectPoolConfig">
        <property name="maxWaitMillis" value="-1"/>
        <property name="maxTotal" value="8"/>
        <property name="minIdle" value="0"/>
        <property name="maxIdle" value="8"/>
    </bean>

    <bean id="jedisCluster" class="com.twjitm.transaction.service.redis.impl.JedisClusterFactory">
        <property name="connectionTimeout" value="3000"/>
        <property name="soTimeout" value="3000"/>
        <property name="maxAttempts" value="5"/>
        <property name="genericObjectPoolConfig" ref="genericObjectPoolConfig"/>
        <property name="jedisClusterNodes">
            <set>
                <value>127.0.0.1:7000</value>
                <value>127.0.0.1:7001</value>
                <value>127.0.0.1:7002</value>
                <value>127.0.0.1:7003</value>
                <value>127.0.0.1:7004</value>
                <value>127.0.0.1:7005</value>
            </set>
        </property>
    </bean>

    <!--集群模式-->
    <bean id="nettyTransactionClusterRedisService"
          class="com.twjitm.transaction.service.redis.impl.NettyTransactionClusterRedisServiceImpl">
        <property name="jedisCluster" ref="jedisCluster"/>
    </bean>
</beans>

(3)支持分布式事务锁zookeeper的基本配置 applicationContext-zookeeper.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
						http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="nettyTransactionZookeeperService"
          class="com.twjitm.transaction.service.zookeeper.impl.NettyTransactionZookeeperServiceImpl" init-method="init">
        <property name="host" value="127.0.0.1"/>
        <property name="port" value="2181"/>
        <property name="rootNode" value="/register_lock"/>
    </bean>

</beans>

其他配置文件
redis-pool配置 redis-pool-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- ************************ 全局配置信息 ************************ -->
<config id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"   maxIdle="30" testWhileIdle="true" timeBetweenEvictionRunsMillis="60000"
	 numTestsPerEvictionRun="30" minEvictableIdleTimeMillis="60000"> 
</config>
	

redis数据配置 redis-data.xml

<?xml version="1.0" encoding="UTF-8"?>

<config id="redisPool" class="redis.clients.jedis.JedisPool" host="127.0.0.1" port="6379" timeout="3000"  database="0">
</config>

第四步:添加包

然后添加相关的包路径,整体项目结构如下所示:
在这里插入图片描述

4.2:基础功能实现
为了能够让大家都能够看明白,文章将使用大量代码和相关文字描述的方式来进行讲解。

首先我们需要redis的支持和zookeeper的支持,所以我们需要将redis操作和zookeeper操作整合到系统中来,根据以往的项目,我们把需要用到的操作就写出来,让代码显得更加简洁。
4.2.1:redis组件支持

package com.twjitm.transaction.service.redis.impl;

import com.twjitm.transaction.service.redis.NettyTransactionRedisService;
import com.twjitm.transaction.utils.TimeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Date;

/**
 * 采用Redis 实现分布式锁,单机模式redis
 *
 * @author twjtim- [Created on 2018-08-27 11:57]

 * @jdk java version "1.8.0_77"
 */
public class NettyTransactionRedisServiceImpl implements NettyTransactionRedisService {

    protected static Logger logger = LoggerFactory.getLogger(NettyTransactionRedisServiceImpl.class);
    /**
     * 数据源
     */
    private JedisPool jedisPool;


    /**
     * 设置连接池
     */
    public void setJedisPool(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 正常返还链接
     */
    private void returnResource(Jedis jedis) {
        try {
            jedisPool.returnResource(jedis);
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
    }

    /*
     * 释放错误链接
     */
    private void returnBrokenResource(Jedis jedis, String name, Exception exception) {
        logger.error(TimeUtil.getDateString(new Date()) + ":::::" + name);
        if (jedis != null) {
            try {
                jedisPool.returnBrokenResource(jedis);
            } catch (Exception e) {
                logger.error(e.toString(), e);
            }
        }
        if (exception != null) {
            logger.error(exception.toString(), exception);
        }
    }

    /**
     * 释放成功链接
     *
     * @param success
     * @param jedis
     */
    private void releaseRedisSource(boolean success, Jedis jedis) {
        if (success && jedis != null) {
            returnResource(jedis);
        }
    }

    /**
     * 释放非正常链接
     *
     * @param jedis
     * @param key
     * @param string
     * @param e
     */
    private void releaseBrokenRedisSource(Jedis jedis, String key, String string, Exception e, boolean deleteKeyFlag) {
        returnBrokenResource(jedis, string, e);
        if (deleteKeyFlag) {
            expire(key, 0);
        }
    }

    /**
     * 设置缓存生命周期
     *
     * @param key
     * @param seconds
     */
    @Override
    public void expire(String key, int seconds) {
        Jedis jedis = null;
        boolean successful = true;
        try {
            jedis = jedisPool.getResource();
            jedis.expire(key, seconds);
        } catch (Exception e) {
            successful = false;
            logger.error("设置生命周期发生异常", e.getCause());
            returnBrokenResource(jedis, "expire:" + key, e);
        } finally {
            if (successful && jedis != null) {
                returnResource(jedis);
            }
        }
    }

    /**
     * 删除key
     *
     * @param key
     */
    @Override
    public boolean deleteKey(String key) {
        Jedis jedis = null;
        boolean successful = true;
        try {
            jedis = jedisPool.getResource();
            jedis.del(key);
        } catch (Exception e) {
            successful = false;
            releaseBrokenRedisSource(jedis, key, "deleteKey", e, false);
        } finally {
            releaseRedisSource(successful, jedis);
        }

        return successful;
    }

    /**
     * 设置
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public boolean setNxString(String key, String value, int seconds) throws Exception {
        Jedis jedis = null;
        boolean successful = true;
        boolean result;
        try {
            jedis = jedisPool.getResource();
            result = (jedis.setnx(key, value) != 0);
            if (seconds > -1) {
                jedis.expire(key, seconds);
            }
        } catch (Exception e) {
            successful = false;
            releaseBrokenRedisSource(jedis, key, "setNxString", e, false);
            throw e;
        } finally {
            releaseRedisSource(successful, jedis);
        }

        return result;

    }

    /**
     * 设置
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public boolean setHnxString(String key, String field, String value) throws Exception {
        Jedis jedis = null;
        boolean success = true;
        boolean result;
        try {
            jedis = jedisPool.getResource();
            result = (jedis.hsetnx(key, field, value) != 0);
        } catch (Exception e) {
            success = false;
            releaseBrokenRedisSource(jedis, key, "setHnxString", e, false);
            throw e;
        } finally {
            releaseRedisSource(success, jedis);
        }

        return result;

    }

    @Override
    public void setString(String key, String value) {
        setString(key, value, -1);
    }

    @Override
    public void setString(String key, String value, int seconds) {
        Jedis jedis = null;
        boolean successful = true;
        try {
            jedis = jedisPool.getResource();
            jedis.set(key, value);
            if (seconds > -1) {
                jedis.expire(key, seconds);
            }
        } catch (Exception e) {
            logger.info("执行setString异常" + key, value, e.getCause());
            successful = false;
            returnBrokenResource(jedis, "setString", e);
            expire(key, 0);
        } finally {
            if (successful && jedis != null) {
                returnResource(jedis);
            }
        }
    }

    @Override
    public String getString(String key) {
        return getString(key, -1);
    }

    @Override
    public String getString(String key, int seconds) {
        Jedis jedis = null;
        boolean successful = true;
        String rt = null;
        try {
            jedis = jedisPool.getResource();
            rt = jedis.get(key);
            if (seconds > -1) {
                jedis.expire(key, seconds);
            }
        } catch (Exception e) {
            logger.info("执行getString异常" + key, e.getCause());
            successful = false;
            returnBrokenResource(jedis, "getString", e);
        } finally {
            if (successful && jedis != null) {
                returnResource(jedis);
            }
        }
        return rt;
    }

    @Override
    public boolean exists(String key) {
        Jedis jedis = null;
        boolean successful = true;
        try {
            jedis = jedisPool.getResource();
            return jedis.exists(key);
        } catch (Exception e) {
            successful = false;
            logger.error("查找键是否存在发生异常" + key, e.getCause());
            returnBrokenResource(jedis, "exists:" + key, e);
        } finally {
            if (successful && jedis != null) {
                returnResource(jedis);
            }
        }
        return false;
    }
}

集群模式

package com.twjitm.transaction.service.redis.impl;

import com.twjitm.transaction.service.redis.NettyTransactionRedisService;
import com.twjitm.transaction.utils.TimeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisCluster;

import java.util.Date;

/**
 * 利用redis实现分布式锁,集群模式
 *
 * @author twjitm- [Created on 2018-08-27 12:00]
 * @jdk java version "1.8.0_77"
 */
@Service
public class NettyTransactionClusterRedisServiceImpl implements NettyTransactionRedisService {
    protected static Logger logger = LoggerFactory.getLogger(NettyTransactionClusterRedisServiceImpl.class);

    private JedisCluster jedisCluster;

    @Override
    public void expire(String key, int seconds) {
        try {
            jedisCluster.expire(key, seconds);
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "expire" + key, e);
        }
    }

    @Override
    public boolean deleteKey(String key) {
        boolean flag = false;
        try {
            jedisCluster.del(key);
            flag = true;
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "deleteKey" + key, e);
        }
        return flag;
    }

    @Override
    public boolean setNxString(String key, String value, int seconds) throws Exception {
        boolean flag = false;
        try {
            flag = (jedisCluster.setnx(key, value) != 0);
            if (seconds > -1) {
                jedisCluster.expire(key, seconds);
            }
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "setNxString" + key, e);
        }
        return flag;
    }

    @Override
    public boolean setHnxString(String key, String field, String value) throws Exception {
        boolean flag = false;
        try {
            flag = (jedisCluster.hsetnx(key, field, value) != 0);
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "setNxString" + key, e);
        }
        return flag;
    }

    @Override
    public void setString(String key, String value) {
        try {
            jedisCluster.set(key, value);
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "setString" + key, e);
        }
    }

    @Override
    public void setString(String key, String value, int seconds) {
        try {
            jedisCluster.set(key, value);
            if (seconds > -1) {
                jedisCluster.expire(key, seconds);
            }
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "setString" + key, e);
        }
    }

    @Override
    public String getString(String key) {
        String flag = null;
        try {
            flag = jedisCluster.get(key);
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "getString" + key, e);
        }
        return flag;
    }

    @Override
    public String getString(String key, int seconds) {
        String flag = null;
        try {
            flag = jedisCluster.get(key);
            if (seconds > -1) {
                jedisCluster.expire(key, seconds);
            }
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "getString" + key, e);
        }
        return flag;
    }

    @Override
    public boolean exists(String key) {
        boolean flag = false;
        try {
            flag = jedisCluster.exists(key);
        } catch (Exception e) {
            logger.error(TimeUtil.getDateString(new Date()) + ":::::" + "exists" + key, e);
        }
        return flag;
    }


    public JedisCluster getJedisCluster() {
        return jedisCluster;
    }

    public void setJedisCluster(JedisCluster jedisCluster) {
        this.jedisCluster = jedisCluster;
    }
}

上面的代码没有啥好说明的,要是不熟悉redis的同学可以先去看看有关redis的操作。否者将不能继续看懂下面的功能。

Zookeeper支持

package com.twjitm.transaction.service.zookeeper.impl;

import com.twjitm.transaction.service.zookeeper.NettyTransactionZookeeperService;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CountDownLatch;

/**
 * <p>
 * 利用zookeeper实现分布式事务锁,此类竟提供分布式事务相关的
 * 业务使用,不能用做服务注册和服务发现等业务。否者将会造成数据丢失。
 *</p>
 * @author twjitm- [Created on 2018-08-28 13:50]
 * @jdk java version "1.8.0_77"
 */
public class NettyTransactionZookeeperServiceImpl implements
        NettyTransactionZookeeperService {
    private Logger logger = LoggerFactory.getLogger(
            NettyTransactionZookeeperServiceImpl.class);
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    private ZooKeeper zooKeeper;

    private String rootNode;
    private String host;
    private int port;

    public void setHost(String host) {
        this.host = host;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setRootNode(String rootNode) {
        this.rootNode = rootNode;
    }

    public void init() {
        logger.info("初始化zookeeper服务器");
        if (this.zooKeeper == null) {
            zooKeeper = connectZookeeperServer();
        }
        initRootNode();
    }


    /**
     * 初始化更节点
     */
    public void initRootNode() {
        try {
            Stat s = zooKeeper.exists(rootNode, false);
            if (s != null) {
                return;
            }
            logger.info("创建分布式锁根节点服务:" + rootNode);
            createRootNode(rootNode, new byte[0]);
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("创建分布式锁根节点服务:" + rootNode + "失敗");

        }
    }


    /**
     * 链接到zookeeper 服务器
     *
     * @return
     */
    private ZooKeeper connectZookeeperServer() {
        ZooKeeper zk = null;
        String hostAndPort = this.host + ":" + this.port;
        try {

            zk = new ZooKeeper(hostAndPort, 50000,
                    event -> countDownLatch.countDown());
        } catch (Exception e) {
            logger.info("连接到zookeeper发生异常");
            logger.error("连接到zookeeper异常", e);
        }
        logger.info("连接到zookeeper成功:" + hostAndPort);
        return zk;
    }

    @Override
    public boolean createNode(String nodePath, String nodeData) {
        try {
            //拼上更节点
            nodePath = getRealLockPath(nodePath);
            Stat s = zooKeeper.exists(nodePath, false);
            if (s == null) {
                byte[] data = nodeData.getBytes();
                create(nodePath, data);
                logger.info("创建zookeeper根节点: " + nodeData);
            }
            return true;
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return false;
    }


    @Override
    public boolean exist(String nodePath) {
        nodePath = getRealLockPath(nodePath);
        try {
            Stat s = zooKeeper.exists(nodePath, false);
            if (s != null) {
                return true;
            }
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    private String getRealLockPath(String node) {
        return rootNode + "/" + node;
    }


    @Override
    public boolean deleteNode(String nodePath) {
        try {
            zooKeeper.delete(getRealLockPath(nodePath), -1);
            logger.debug("delete zookeeper node path c ({} => {})", nodePath);
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return true;
    }


    /**
     * 创建持久态的znode,比支持多层创建.比如在创建/parent/child的情况下,无/parent.无法通过
     *
     * @param path
     * @param data
     * @throws KeeperException
     * @throws InterruptedException
     */
    private void createRootNode(String path, byte[] data) throws Exception {
        /**
         * 此处采用的是CreateMode是PERSISTENT  表示The znode will not be automatically deleted upon client's disconnect.
         * EPHEMERAL 表示The znode will be deleted upon the client's disconnect.
         */
        this.zooKeeper.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }


    /**
     * 创建持久态的znode,比支持多层创建.比如在创建/parent/child的情况下,无/parent.无法通过
     *
     * @param path
     * @param data
     * @throws KeeperException
     * @throws InterruptedException
     */
    private String create(String path, byte[] data) throws Exception {
        /**
         * 此处采用的是CreateMode是EPHEMERAL
         * <p>
         * PERSISTENT  表示The znode will not be automatically deleted upon
         * 客户端即使关闭的时,zookeeper节点不会删除
         * client's disconnect.
         * EPHEMERAL 表示The znode will be deleted upon the client's disconnect.
         * 当客户端关闭的时候,zookeeper会自动删除
         * </p>
         */
        return this.zooKeeper.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
    }

}

工程结构图:
在这里插入图片描述

到此有关分布式事务锁的基础准备完毕。下篇文章我们将继续讲解使用redis或者zookeeper实现3pc提交的事务锁。欢迎关注。。。

Netty游戏服务器实战开发(7):利用redis或者zookeeper实现3pc分布式事务锁(二)。支撑腾讯系列某手游百万级流量公测


参考:http://www.mamicode.com/info-detail-890945.html

阅读更多

扫码向博主提问

twjitm

博客专家

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • java
  • spring
  • mybatis
  • hibernate
  • javaweb
去开通我的Chat快问
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页