分布式锁、任务、事务

本文探讨了分布式锁的实现,包括使用Zookeeper和Redis的方法,并介绍了分布式事务在MQ和LCN框架下的应用。通过压力测试展示了分布式锁防止超卖问题的重要性。同时,详细说明了LCN分布式事务管理器的配置和使用,以及在遇到错误时如何影响事务处理。

一、分布式锁

在这里插入图片描述
java测试

@RestController
public class SecondKillController {
    //1准备商品库存
    private static Map<String,Integer> itemStock = new HashMap<>();
    //2.准备订单
    private static Map<String,Integer> itemOrder = new HashMap<>();
    static {
        itemStock.put("牙刷", 10000);
        itemOrder.put("牙刷", 0);
    }
    @GetMapping("kill")
    public String kill(String item) throws InterruptedException {
        //减库存
        Integer stock = itemStock.get(item);
        if(stock <= 0){
            return "没有商品了";

        }else{
            Thread.sleep(100);
            itemStock.put(item, stock-1);
        }
        Thread.sleep(100);
        itemOrder.put(item, itemOrder.get(item)+1);
        return "抢够成功了,"+item+"剩余:"+itemStock.get(item)+",订单数--"+itemOrder.get(item);
    }

在这里插入图片描述
下载ab压力测试
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1000请求 500并发
D:\environment\ab\Apache24\bin>ab -n 1000 -c 500 http://localhost:8082/kill?ite
=%E7%89%99%E5%88%B7
刷新发现超卖现象
在这里插入图片描述

zoekeeper实现分布式锁

在这里插入图片描述

@Configuration
public class ZkConfig {

    @Bean
    public CuratorFramework cf(){
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2 );
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectString("192.168.232.128:2181")
                .retryPolicy(retryPolicy)
                .build();
        curatorFramework.start();
        return curatorFramework;

    }
}
 @Autowired
    private CuratorFramework cf;
    //1准备商品库存
    private static Map<String,Integer> itemStock = new HashMap<>();
    //2.准备订单
    private static Map<String,Integer> itemOrder = new HashMap<>();
    static {
        itemStock.put("牙刷", 10000);
        itemOrder.put("牙刷", 0);
    }
    @GetMapping("kill")
    public String kill(String item) throws Exception {
        //开始访问共享资源,这里是访问商品信息
        InterProcessMutex lock = new InterProcessMutex(cf,"/lock" );
        //。。。分布式 加锁
        lock.acquire();
        //减库存
        Integer stock = itemStock.get(item);
        if(stock <= 0){
            return "没有商品了";
        }
        Thread.sleep(100);
        itemStock.put(item, stock-1);
        Thread.sleep(100);
        itemOrder.put(item, itemOrder.get(item)+1);
        //释放锁
        lock.release();
        //将锁归还
        return "抢够成功了,"+item+"剩余:"+itemStock.get(item)+",订单数--"+itemOrder.get(item);
    }

redis实现分布式锁

在这里插入图片描述

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-redis</artifactId>
        <version>1.4.5.RELEASE</version>
    </dependency>
@Component
public class RedisLockUtil {
    @Autowired
    private RedisTemplate redisTemplate;
    //加锁
    public boolean lock(String key, String value,int second){
        return redisTemplate.opsForValue().setIfAbsent(key, value, second, TimeUnit.SECONDS);
    }
    //释放锁
    public void unlock(String key){
        redisTemplate.delete(key);
    }
}
@Autowired
    private RedisLockUtil lockUtil;
@GetMapping("redis/kill")
    public String redisKill(String item) throws Exception {
        //。。。分布式 加锁
        if(lockUtil.lock(item, System.currentTimeMillis()+"",1 )){
            //减库存
            Integer stock = itemStock.get(item);
            if(stock <= 0){
                return "没有商品了";
            }
            Thread.sleep(100);
            itemStock.put(item, stock-1);
            Thread.sleep(100);
            itemOrder.put(item, itemOrder.get(item)+1);
            //释放锁
            lockUtil.unlock(item);
            //将锁归还
            return "抢够成功了,"+item+"剩余:"+itemStock.get(item)+",订单数--"+itemOrder.get(item);
        }else{
            return "你没有抢到商品";
        }

分布式事务

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

MQ实现分布式事务

在这里插入图片描述
LCN实现分布式事务

在这里插入图片描述
准备3个项目 txlcn-manager txlcn-order txlcn-item

txlcn-manager:

 <dependency>
   <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-tm</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
server:
  port: 8083
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/lcn?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    username: root
    password:
  redis:
    host: 192.168.232.128
    port: 6379
tx-lcn:
  manager:
    port: 8070
@SpringBootApplication
@EnableTransactionManagerServer
public class application {
    public static void main(String[] args) {
        SpringApplication.run(application.class,args);
    }
}

lcn对yml不友好,可再建一个空application.properties
访问http://localhost:8083/admin/index.html#/login
类似注册中心那种,可以看到你有哪些服务需要做事务处理的。登录的默认密码为codingapi
在这里插入图片描述

txlcn-order:

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-tc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-txmsg-netty</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>
server:
  port: 8084
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/lcn?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    username: root
    password:
tx-lcn:
  client:
    manager-address: localhost:8070
@SpringBootApplication
@EnableDistributedTransaction
@MapperScan(basePackages = "com.rayc.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class,args);
    }

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
 @GetMapping("/order")
    public String create(){
        orderService.createOrder();
        return "创建订单成功";
    }
    
    public interface OrderService {
    	void createOrder();
   }
   
    @Service
	public class OrderServiceImpl implements OrderService {
	    @Autowired
	    private OrderMapper orderMapper;
	    @Autowired
	    RestTemplate restTemplate;
	    @Override
	    @Transactional
	    @LcnTransaction
	    public void createOrder() {
	        //1、减库存
	        restTemplate.getForObject("http://localhost:8085/item", String.class);
	        System.out.println("减库存");
	//        int i = 1/0;
	        //2、创建订单
	        orderMapper.save();
	    }
	}

    public interface OrderMapper {
    @Insert("insert into order_item (id,name,money) values (1,'张三',123)")
    void save();
}

txlcn-item

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-tc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-txmsg-netty</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>
server:
  port: 8085
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/lcn?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    username: root
    password:
tx-lcn:
  client:
    manager-address: localhost:8070
@SpringBootApplication
@EnableDistributedTransaction
@MapperScan(basePackages = "com.rayc.mapper")
public class ItemApplication {
    public static void main(String[] args) {
        SpringApplication.run(ItemApplication.class,args);
    }
}

@RestController
public class ItemController {
    @Autowired
    private ItemService itemService;
    @GetMapping("/item")
    public String item(){
        System.out.println("开始减库存");
        itemService.update();
        return null;
    }
}

public interface ItemService {
    void update();
}

@Service
public class ItemServiceImpl implements ItemService {
    @Autowired
    private ItemMapper itemMapper;
    @Override
    @Transactional
    @LcnTransaction
    public void update() {
        //1、减库存
        itemMapper.update();
    }
}

public interface ItemMapper {
    @Update("update item set stock = stock - 1 where id = 1")
    void update();
}

另需创建lcn数据库及表

CREATE TABLE `t_tx_exception`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `transaction_state` tinyint(4) NULL DEFAULT NULL,
  `registrar` tinyint(4) NULL DEFAULT NULL,
  `remark` varchar(4096) NULL DEFAULT  NULL,
  `ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解决 1已解决',
  `create_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

加上自己的数据库 order_item和item表

int i = 1/0;
失败库存未减订单未生成
去掉则成功
在这里插入图片描述
学习视频: https://www.bilibili.com/video/BV17D4y1S7GQ?p=17

### 概念 分布式事务锁是在分布式系统中,为了保证多个进程或服务对共享资源的互斥访问,以确保数据的一致性和完整性而使用的一种锁机制。与单机环境下的锁不同,分布式事务锁需要跨越多个节点,在整个分布式系统范围内实现对共享资源的同步控制[^3]。 ### 原理 分布式事务锁的核心原理是利用分布式系统中的共享存储或协调服务来实现锁的状态管理。其主要基于以下几个特性来保障锁的正常工作: - **互斥性**:同一时刻只能有一个客户端获取锁,其他客户端无法同时获取,以此保证对共享资源的独占访问[^4]。 - **安全性**:锁只能被获取者释放,不能被其他客户端任意释放,避免出现误释放问题,确保锁的正确使用[^4]。 - **容错性**:当持有锁的节点崩溃或网络故障时,锁需要能被安全释放,避免出现“死锁”,常见的做法是设置锁的过期时间[^4]。 - **可用性**:系统部分节点故障时,分布式锁服务仍能正常提供加锁和解锁功能,保证系统的稳定性和可靠性[^4]。 - **可重入性**:同一客户端在持有锁的情况下,可再次获取该锁而不被阻塞,类似于 Java 的可重入锁,方便客户端的使用[^4]。 ### 应用场景 - **资源共享访问**:在分布式系统中,多个服务可能会同时访问共享的数据库、缓存、文件系统等资源,使用分布式事务锁可以确保在同一时间只有一个服务对这些资源进行写操作,避免数据冲突和不一致性。 - **任务调度**:在分布式任务调度系统中,可能会有多个节点同时竞争执行同一个任务,使用分布式事务锁可以保证只有一个节点能够获取到任务的执行权,避免任务的重复执行。 - **并发控制**:在高并发的业务场景中,如秒杀活动、抢红包等,使用分布式事务锁可以限制同一时间内对资源的访问数量,防止超卖、数据不一致等问题的发生。 ### 实现方式 #### Redis 实现 Redis 是一种常用的实现分布式事务锁的工具,其实现主要基于 Redis 的原子操作。以下是一个使用 Python 和 Redis 实现分布式锁的示例代码: ```python import redis import time # 连接 Redis redis_client = redis.Redis(host='localhost', port=6379, db=0) def acquire_lock(lock_name, acquire_timeout=10, lock_timeout=10): end_time = time.time() + acquire_timeout while time.time() < end_time: # 使用 SETNX 命令尝试获取锁 if redis_client.setnx(lock_name, 'locked'): # 设置锁的过期时间 redis_client.expire(lock_name, lock_timeout) return True time.sleep(0.1) return False def release_lock(lock_name): # 释放锁 redis_client.delete(lock_name) # 使用示例 lock_name = 'my_distributed_lock' if acquire_lock(lock_name): try: # 执行需要加锁的操作 print("获取到锁,执行操作...") finally: release_lock(lock_name) else: print("未能获取到锁") ``` #### Zookeeper 实现 Zookeeper 是一个分布式协调服务,也可以用来实现分布式事务锁。其原理是利用 Zookeeper 的临时顺序节点和 Watcher 机制。以下是一个简单的 Java 代码示例: ```java import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; import java.util.Collections; import java.util.List; public class ZookeeperDistributedLock { private static final String ZOOKEEPER_SERVER = "localhost:2181"; private static final String LOCK_PATH = "/distributed_lock"; private ZooKeeper zooKeeper; public ZookeeperDistributedLock() throws IOException { this.zooKeeper = new ZooKeeper(ZOOKEEPER_SERVER, 3000, null); } public boolean acquireLock() throws KeeperException, InterruptedException { // 创建临时顺序节点 String myNode = zooKeeper.create(LOCK_PATH + "/lock-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // 获取所有子节点 List<String> children = zooKeeper.getChildren(LOCK_PATH, false); Collections.sort(children); String firstNode = children.get(0); if (myNode.endsWith(firstNode)) { return true; } // 监听前一个节点 String prevNode = LOCK_PATH + "/" + children.get(children.indexOf(myNode.substring(LOCK_PATH.length() + 1)) - 1); Stat stat = zooKeeper.exists(prevNode, new Watcher() { @Override public void process(WatchedEvent event) { if (event.getType() == Event.EventType.NodeDeleted) { try { acquireLock(); } catch (KeeperException | InterruptedException e) { e.printStackTrace(); } } } }); if (stat != null) { synchronized (this) { wait(); } } return false; } public void releaseLock() throws KeeperException, InterruptedException { List<String> children = zooKeeper.getChildren(LOCK_PATH, false); for (String child : children) { zooKeeper.delete(LOCK_PATH + "/" + child, -1); } zooKeeper.close(); } public static void main(String[] args) throws IOException, KeeperException, InterruptedException { ZookeeperDistributedLock lock = new ZookeeperDistributedLock(); if (lock.acquireLock()) { try { System.out.println("获取到锁,执行操作..."); } finally { lock.releaseLock(); } } else { System.out.println("未能获取到锁"); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值