1、分布式锁实现原理与最佳实践(一)

单体的应用开发场景中涉及并发同步时,大家往往采用Synchronized(同步)或同一个JVM内Lock机制来解决多线程间的同步问题。而在分布式集群工作的开发场景中,就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题,这种跨机器的锁就是分布式锁。接下来本文将为大家分享分布式锁的最佳实践。

一、超卖问题复现

1.1 现象

存在如下的几张表:
商品表
在这里插入图片描述
订单表
在这里插入图片描述
订单item表
在这里插入图片描述
商品的库存为1,但是并发高的时候有多笔订单。

错误案例一:数据库update相互覆盖
直接在内存中判断是否有库存,计算扣减之后的值更新数据库,并发的情况下会导致相互覆盖发生:

@Transactional(rollbackFor = Exception.class)
public Long createOrder() throws Exception {Product product = productMapper.selectByPrimaryKey(purchaseProductId);// ... 忽略校验逻辑//商品当前库存Integer currentCount = product.getCount();//校验库存if (purchaseProductNum > currentCount) {throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}// 计算剩余库存Integer leftCount = currentCount - purchaseProductNum;// 更新库存product.setCount(leftCount);product.setGmtModified(new Date());productMapper.updateByPrimaryKeySelective(product);Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());// ... 省略 Setreturn order.getId();
}

错误案例二:扣减串行执行,但是库存被扣减为负数

在 SQL 中加入运算避免值的相互覆盖,但是库存的数量变为负数,因为校验库存是否足够还是在内存中执行的,并发情况下都会读到有库存:


@Transactional(rollbackFor = Exception.class)
public Long createOrder() throws Exception {Product product = productMapper.selectByPrimaryKey(purchaseProductId);// ... 忽略校验逻辑//商品当前库存Integer currentCount = product.getCount();//校验库存if (purchaseProductNum > currentCount) {throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}// 使用 set count =  count - #{purchaseProductNum,jdbcType=INTEGER}, 更新库存productMapper.updateProductCount(purchaseProductNum,new Date(),product.getId());Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());// ... 省略 Setreturn order.getId();
}

错误案例三:使用 synchronized 实现内存中串行校验,但是依旧扣减为负数
因为我们使用的是事务的注解,synchronized加在方法上,方法执行结束的时候锁就会释放,此时的事务还没有提交,另一个线程拿到这把锁之后就会有一次扣减,导致负数。

@Transactional(rollbackFor = Exception.class)
public synchronized Long createOrder() throws Exception {Product product = productMapper.selectByPrimaryKey(purchaseProductId);// ... 忽略校验逻辑//商品当前库存Integer currentCount = product.getCount();//校验库存if (purchaseProductNum > currentCount) {throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}// 使用 set count =  count - #{purchaseProductNum,jdbcType=INTEGER}, 更新库存productMapper.updateProductCount(purchaseProductNum,new Date(),product.getId());Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());// ... 省略 Setreturn order.getId();
}

1.2 解决办法

从上面造成问题的原因来看,只要是扣减库存的动作,不是原子性的。多个线程同时操作就会有问题。
单体应用:使用本地锁 + 数据库中的行锁解决分布式应用:使用数据库中的乐观锁,加一个 version 字段,利用CAS来实现,会导致大量的 update 失败使用数据库维护一张锁的表 + 悲观锁 select,使用 select for update 实现使用Redis 的 setNX实现分布式锁使用zookeeper的watcher + 有序临时节点来实现可阻塞的分布式锁使用Redisson框架内的分布式锁来实现使用curator 框架内的分布式锁来实现

二、单体应用解决超卖的问题

正确示例:将事务包含在锁的控制范围内

保证在锁释放之前,事务已经提交。
//@Transactional(rollbackFor = Exception.class)
public synchronized Long createOrder() throws Exception {TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition);Product product = productMapper.selectByPrimaryKey(purchaseProductId);if (product == null) {platformTransactionManager.rollback(transaction1);throw new Exception("购买商品:" + purchaseProductId + "不存在");}//商品当前库存Integer currentCount = product.getCount();//校验库存if (purchaseProductNum > currentCount) {platformTransactionManager.rollback(transaction1);throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}productMapper.updateProductCount(purchaseProductNum, new Date(), product.getId());Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());// ... 省略 Setreturn order.getId();platformTransactionManager.commit(transaction1);
}

正确示例:使用synchronized的代码块

public Long createOrder() throws Exception {Product product = null;//synchronized (this) {//synchronized (object) {synchronized (DBOrderService2.class) {TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition);product = productMapper.selectByPrimaryKey(purchaseProductId);if (product == null) {platformTransactionManager.rollback(transaction1);throw new Exception("购买商品:" + purchaseProductId + "不存在");}//商品当前库存Integer currentCount = product.getCount();System.out.println(Thread.currentThread().getName() + "库存数:" + currentCount);//校验库存if (purchaseProductNum > currentCount) {platformTransactionManager.rollback(transaction1);throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");}productMapper.updateProductCount(purchaseProductNum, new Date(), product.getId());platformTransactionManager.commit(transaction1);}TransactionStatus transaction2 = platformTransactionManager.getTransaction(transactionDefinition);Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();// ... 省略 SetorderItemMapper.insertSelective(orderItem);platformTransactionManager.commit(transaction2);return order.getId();

正确示例:使用Lock

private Lock lock = new ReentrantLock();public Long createOrder() throws Exception{  Product product = null;lock.lock();TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition);try {product = productMapper.selectByPrimaryKey(purchaseProductId);if (product==null){throw new Exception("购买商品:"+purchaseProductId+"不存在");}//商品当前库存Integer currentCount = product.getCount();System.out.println(Thread.currentThread().getName()+"库存数:"+currentCount);//校验库存if (purchaseProductNum > currentCount){throw new Exception("商品"+purchaseProductId+"仅剩"+currentCount+"件,无法购买");}productMapper.updateProductCount(purchaseProductNum,new Date(),product.getId());platformTransactionManager.commit(transaction1);} catch (Exception e) {platformTransactionManager.rollback(transaction1);} finally {// 注意抛异常的时候锁释放不掉,分布式锁也一样,都要在这里删掉lock.unlock();}TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);Order order = new Order();// ... 省略 SetorderMapper.insertSelective(order);OrderItem orderItem = new OrderItem();// ... 省略 SetorderItemMapper.insertSelective(orderItem);platformTransactionManager.commit(transaction);return order.getId();
}

三、常见分布式锁的使用

上面使用的方法只能解决单体项目,当部署多台机器的时候就会失效,因为锁本身就是单机的锁,所以需要使用分布式锁来实现。
3.1 数据库乐观锁

数据库中的乐观锁,加一个version字段,利用CAS来实现,乐观锁的方式支持多台机器并发安全。但是并发量大的时候会导致大量的update失败
3.2 数据库分布式锁

db操作性能较差,并且有锁表的风险,一般不考虑。
3.2.1 简单的数据库锁
在这里插入图片描述
select for update
直接在数据库新建一张表:
在这里插入图片描述
锁的code预先写到数据库中,抢锁的时候,使用select for update查询锁对应的key,也就是这里的code,阻塞就说明别人在使用锁。

// 加上事务就是为了 for update 的锁可以一直生效到事务执行结束
// 默认回滚的是 RunTimeException
@Transactional(rollbackFor = Exception.class)
public String singleLock() throws Exception {log.info("我进入了方法!");DistributeLock distributeLock = distributeLockMapper.selectDistributeLock("demo");if (distributeLock==null) {throw new Exception("分布式锁找不到");}log.info("我进入了锁!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return "我已经执行完成!";
}<select id="selectDistributeLock" resultType="com.deltaqin.distribute.model.DistributeLock">select * from distribute_lockwhere businessCode = #{businessCode,jdbcType=VARCHAR}for update
</select>

使用唯一键作为限制,插入一条数据,其他待执行的SQL就会失败,当数据删除之后再去获取锁 ,这是利用了唯一索引的排他性。
insert lock
直接维护一张锁表:

@Autowired
private MethodlockMapper methodlockMapper;@Override
public boolean tryLock() {try {//插入一条数据   insert intomethodlockMapper.insert(new Methodlock("lock"));}catch (Exception e){//插入失败return false;}return true;
}@Override
public void waitLock() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
}@Override
public void unlock() {//删除数据   deletemethodlockMapper.deleteByMethodlock("lock");System.out.println("-------释放锁------");

3.3 Redis setNx

Redis 原生支持的,保证只有一个会话可以设置成功,因为Redis自己就是单线程串行执行的。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.redis.host=localhost

封装一个锁对象:

@Slf4j
public class RedisLock implements AutoCloseable {private RedisTemplate redisTemplate;private String key;private String value;//单位:秒private int expireTime;/*** 没有传递 value,因为直接使用的是随机值*/public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){this.redisTemplate = redisTemplate;this.key = key;this.expireTime=expireTime;this.value = UUID.randomUUID().toString();}/*** JDK 1.7 之后的自动关闭的功能*/@Overridepublic void close() throws Exception {unLock();}/*** 获取分布式锁* SET resource_name my_random_value NX PX 30000* 每一个线程对应的随机值 my_random_value 不一样,用于释放锁的时候校验* NX 表示 key 不存在的时候成功,key 存在的时候设置不成功,Redis 自己是单线程,串行执行的,第一个执行的才可以设置成功* PX 表示过期时间,没有设置的话,忘记删除,就会永远不过期*/public boolean getLock(){RedisCallback<Boolean> redisCallback = connection -> {//设置NXRedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();//设置过期时间Expiration expiration = Expiration.seconds(expireTime);//序列化keybyte[] redisKey = redisTemplate.getKeySerializer().serialize(key);//序列化valuebyte[] redisValue = redisTemplate.getValueSerializer().serialize(value);//执行setnx操作Boolean result = connection.set(redisKey, redisValue, expiration, setOption);return result;};//获取分布式锁Boolean lock = (Boolean)redisTemplate.execute(redisCallback);return lock;}/*** 释放锁的时候随机数相同的时候才可以释放,避免释放了别人设置的锁(自己的已经过期了所以别人才可以设置成功)* 释放的时候采用 LUA 脚本,因为 delete 没有原生支持删除的时候校验值,证明是当前线程设置进去的值* 脚本是在官方文档里面有的*/public boolean unLock() {// key 是自己才可以释放,不是就不能释放别人的锁String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +"    return redis.call(\"del\",KEYS[1])\n" +"else\n" +"    return 0\n" +"end";RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);List<String> keys = Arrays.asList(key);// 执行脚本的时候传递的 value 就是对应的值Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);log.info("释放锁的结果:"+result);return result;}
}每次获取的时候,自己线程需要new对应的RedisLockpublic String redisLock(){log.info("我进入了方法!");try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){if (redisLock.getLock()) {log.info("我进入了锁!!");Thread.sleep(15000);}} catch (InterruptedException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}log.info("方法执行完成");return "方法执行完成";
}

3.4 zookeeper 瞬时znode节点 + watcher监听机制

临时节点具备数据自动删除的功能。当client与ZooKeeper连接和session断掉时,相应的临时节点就会被删除。zk有瞬时和持久节点,瞬时节点不可以有子节点。会话结束之后瞬时节点就会消失,基于zk的瞬时有序节点实现分布式锁:
多线程并发创建瞬时节点的时候,得到有序的序列,序号最小的线程可以获得锁;

其他的线程监听自己序号的前一个序号。前一个线程执行结束之后删除自己序号的节点;

下一个序号的线程得到通知,继续执行;

以此类推,创建节点的时候,就确认了线程执行的顺序。
在这里插入图片描述

<dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.14</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions>
</dependency>

zk 的观察器只可以监控一次,数据发生变化之后可以发送给客户端,之后需要再次设置监控。exists、create、getChildren三个方法都可以添加watcher ,也就是在调用方法的时候传递true就是添加监听。注意这里Lock 实现了Watcher和AutoCloseable:

当前线程创建的节点是第一个节点就获得锁,否则就监听自己的前一个节点的事件:

/*** 自己本身就是一个 watcher,可以得到通知* AutoCloseable 实现自动关闭,资源不使用的时候*/
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {private ZooKeeper zooKeeper;/*** 记录当前锁的名字*/private String znode;public ZkLock() throws IOException {this.zooKeeper = new ZooKeeper("localhost:2181",10000,this);}public boolean getLock(String businessCode) {try {//创建业务 根节点Stat stat = zooKeeper.exists("/" + businessCode, false);if (stat==null){zooKeeper.create("/" + businessCode,businessCode.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);}//创建瞬时有序节点  /order/order_00000001znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_", businessCode.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);//获取业务节点下 所有的子节点List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false);//获取序号最小的(第一个)子节点Collections.sort(childrenNodes);String firstNode = childrenNodes.get(0);//如果创建的节点是第一个子节点,则获得锁if (znode.endsWith(firstNode)){return true;}//如果不是第一个子节点,则监听前一个节点String lastNode = firstNode;for (String node:childrenNodes){if (znode.endsWith(node)){zooKeeper.exists("/"+businessCode+"/"+lastNode,true);break;}else {lastNode = node;}}synchronized (this){wait();}return true;} catch (Exception e) {e.printStackTrace();}return false;}@Overridepublic void close() throws Exception {zooKeeper.delete(znode,-1);zooKeeper.close();log.info("我已经释放了锁!");}@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.NodeDeleted){synchronized (this){notify();}}}
}

3.5 zookeeper curator

在实际的开发中,不建议去自己“重复造轮子”,而建议直接使用Curator客户端中的各种官方实现的分布式锁,例如其中的InterProcessMutex可重入锁。

<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.2.0</version><exclusions><exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions>
</dependency>
@Bean(initMethod="start",destroyMethod = "close")
public CuratorFramework getCuratorFramework() {RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);return client;
}

框架已经实现了分布式锁。zk的Java客户端升级版。使用的时候直接指定重试的策略就可以。

官网中分布式锁的实现是在curator-recipes依赖中,不要引用错了。

@Autowired
private CuratorFramework client;@Test
public void testCuratorLock(){InterProcessMutex lock = new InterProcessMutex(client, "/order");try {if ( lock.acquire(30, TimeUnit.SECONDS) ) {try  {log.info("我获得了锁!!!");}finally  {lock.release();}}} catch (Exception e) {e.printStackTrace();}client.close();
}

3.6 Redission

重新实现了Java并发包下处理并发的类,让其可以跨JVM使用,例如CHM等。
3.6.1 非SpringBoot项目引入
https://redisson.org/
引入Redisson的依赖,然后配置对应的XML即可:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.11.2</version><exclusions><exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions>
</dependency>

编写相应的redisson.xml

<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"xmlns:redisson="http://redisson.org/schema/redisson"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://redisson.org/schema/redissonhttp://redisson.org/schema/redisson/redisson.xsd
"><redisson:client><redisson:single-server address="redis://127.0.0.1:6379"/></redisson:client>
</beans>

配置对应@ImportResource(“classpath*:redisson.xml”)资源文件。

3.6.2 SpringBoot项目引入
或者直接使用springBoot的starter即可。
https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.19.1</version>
</dependency>

修改application.properties即可:#spring.redis.host=
3.6.3 设置配置类

@Bean
public RedissonClient getRedissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379");return Redisson.create(config);
}

3.6.4 使用

@Test
public void testRedissonLock() {RLock rLock = redisson.getLock("order");try {rLock.lock(30, TimeUnit.SECONDS);log.info("我获得了锁!!!");Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}finally {log.info("我释放了锁!!");rLock.unlock();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/216609.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp H5、小程序、APP端自定义不同运行环境(开发、测试、生产)、自定义条件编译平台、以及动态修改manifest.json值讲解

文章目录 前言一、自定义条件编译平台是什么&#xff1f;二、新增自定义条件编译平台三、动态设置服务器请求地址四、动态修改manifest.json1.根目录新增文件 modifyManifest.js2.vue.config.js引入modifyManifest.js 总结示例代码 前言 企业项目开发流程上一般都要配置多个运…

inBuilder低代码平台新特性推荐-第十期

各位知乎的友友们&#xff0c;大家好~ 今天来给大家带来的是inBuilder低代码平台特性推荐系列第十期——查看变更日志 场景介绍 【销售订单列表】中添加查看变更日志按钮&#xff0c;可以查看列表当前行数据的历史变更记录。 运行时效果 概念 系统中有些关键业务关键数据&am…

学习ComplexHeatmap复杂热图

iMeta | 复杂热图(ComplexHeatmap)可视化文章最新版&#xff0c;画热图就引它_生信宝典的博客-CSDN博客 作者贡献 顾祖光&#xff1a;研究课题的提出和设计&#xff0c; 软件编写&#xff0c;可视化&#xff0c;数据分析&#xff0c;论文编写&#xff0c;修订和审阅。 代码和…

【教3妹学编程-算法题】二叉树中的伪回文路径

3妹&#xff1a;好冷啊&#xff0c; 冻得瑟瑟发抖啦 2哥 : 又一波寒潮来袭&#xff0c; 外面风吹的呼呼的。 3妹&#xff1a;今天还有雨&#xff0c;2哥上班记得带伞。 2哥 : 好的 3妹&#xff1a;哼&#xff0c;不喜欢冬天&#xff0c;也不喜欢下雨天&#xff0c;要是我会咒语…

你听过斯大林病毒吗?

相信不少小伙伴看过这种红眼特效&#xff0c;那么你知道这个特效最早出自哪里吗&#xff1f; 其实这个红眼病毒最早出于俄罗斯的电脑病毒斯大林&#xff0c;一旦电脑感染这个病毒&#xff0c;屏幕上就会出现自带一个红眼特效的斯大林人像&#xff0c;同时不断播放苏联国歌。 …

分布式篇---第五篇

系列文章目录 文章目录 系列文章目录前言一、你知道哪些限流算法?二、说说什么是计数器(固定窗口)算法三、说说什么是滑动窗口算法前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去…

[Docker]十一.Docker Swarm集群raft算法,Docker Swarm Web管理工具

一.Docker Swarm集群raft算法讲解 Raft &#xff1a;一致性算法&#xff0c;在保证大多数管理节点存活的情况下&#xff0c;集群才能使用&#xff0c; 所以就要求如果集群的话&#xff0c; manager 节点必须 >3 台 &#xff0c;如果是两个台&#xff0c;其中一台宕机&#…

分布式锁,分布式锁应该具备哪些条件,分布式锁的实现方式有:基于Zookeeper实现、Redis实现、数据库实现

文章目录 分布式锁0-1分布式锁--包含CAP理论模型概述分布式锁&#xff1a;分布式锁应该具备哪些条件&#xff1a;分布式锁的业务场景&#xff1a; 分布式锁的实现方式有&#xff1a;基于Zookeeper - 分布式锁实现思想优缺点基于Redis - 分布式锁实现思想实现思想的具体步骤&…

计算机体系结构概念总结

CH1 基本概念 课件补充 概念汇总 CH2 指令系统 课件补充 能够改变控制流的指令&#xff1a;分支、跳转、过程调用、过程返回 概念汇总 课后习题 CH3 流水线 课件补充 概念汇总 指令发射&#xff1a;指令从流水线的译码段进入执行段的过程称为指令发射。 向量处理机&#xf…

技术阅读周刊第第7️⃣期

技术阅读周刊&#xff0c;每周更新。 历史更新 20231013&#xff1a;第一期20231022&#xff1a;第二期20231027&#xff1a;第三期20231103&#xff1a;第四期20231107&#xff1a;第五期20231117&#xff1a;第六期 What is a JWT? Understanding JSON Web Tokens URL: http…

Leetcode—2824.统计和小于目标的下标对数目【简单】

2023每日刷题&#xff08;三十九&#xff09; Leetcode—2824.统计和小于目标的下标对数目 实现代码 class Solution { public:int countPairs(vector<int>& nums, int target) {int n nums.size();sort(nums.begin(), nums.end());int left 0, right left 1;i…

数据结构与算法编程题21

判别两棵树是否相等。 #define _CRT_SECURE_NO_WARNINGS#include <iostream> using namespace std;typedef char ElemType; #define ERROR 0 #define OK 1typedef struct BiNode {ElemType data;BiNode* lchild, * rchild; }BiNode, * BiTree;bool Create_tree(BiTree&a…