(ROOT)kafka&rocketmq详解-站在前人的肩膀上拼命总结

一.kafka

1.定义

kafka的传统定义:kafka是一个分布式的基于发布\订阅模式的消息队列,主要用于大数据实时处理领域

kafka的最新概念:kafka是一个开源的分布式事件流平台,(80%的公司都在用),用于高性能数据管道、流分析、数据集成和关键任务应用

2.kafka架构原理详解

Kafka 设计架构原理详细解析(超详细图解) - 知乎 (zhihu.com)

 3.源码分析

​​​​​​​KAFKA详解-CSDN博客

【Kafka系列 06】Kafka Producer源码解析-CSDN博客

Kafka 源码解析 - 消息收集器机制 - 知乎 (zhihu.com)

传统消息队列应用场景

传统的消息队列的主要应用场景包括:缓存/消峰解耦异步通信。

万字长文讲透 RocketMQ 消费逻辑 - 知乎 (zhihu.com)

19.RocketMQ之消息丢失的场景以及解决方案_然而,然而的博客-CSDN博客

RocketMQ 的刷盘机制、主从复制、存储形式_rocketmq刷盘机制-CSDN博客

RocketMQ 详解_罗志宏的博客-CSDN博客

kafka与rocketmq异同

架构设计

Kafka和RocketMQ都是基于发布/订阅模式的消息系统,但是它们的架构设计有所不同。

Kafka的架构设计比较简单,主要由生产者、消费者和Kafka集群三个组件组成。生产者将消息发布到Kafka集群中的Broker节点,然后消费者从Broker节点中获取消息进行消费。Kafka的数据模型是基于Topic和Partition的,每个Topic可以有多个Partition,每个Partition可以在多个Broker节点上复制,保证数据的高可用性。

RocketMQ的架构设计比较复杂,主要由Namesrv、Broker和Producer/Consumer三个角色组成。Namesrv主要负责服务注册和发现,Broker节点负责存储和传输消息,Producer和Consumer分别将消息发送到和从Broker节点中获取消息。RocketMQ也是基于Topic和Partition的数据模型,但它采用了一种主从复制的机制,确保了数据的高可用性和容错性。

零拷贝

对于RocketMQ来说这两个步骤使用的是mmap+write,而Kafka则是使用mmap+write持久化数据,发送数据使用sendfile(零拷贝和应用 - 蚂蚁吃豆腐 - 博客园 (cnblogs.com))

rocketmq使用了mmap,没有使用sendfile,但为什么rocketmq不使用sendfile, 因为rocketmq可以处理消息的顺序,消息的过滤,而Kafka不能有这些功能。 kafka中,sendfile压根就没有到达数据应用层,数据只在内核中中,不会进入用户进程的,也就是不会进入java程序,无法对数据进行操作(修改、排序)(RocketMQ原理 - 简书 (jianshu.com))
作者:lesline
链接:https://www.jianshu.com/p/0620ceefaced
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/weixin_38681369/article/details/132909670

应用场景

在大数据场景主要采用Kafka作为消息队列(kafka很快)。在JavaEE开发中主要采用ActiveMQ、RabbitMQ、RocketMQ。

重试机制

RocketMQ 重试机制详解及最佳实践 - 知乎 (zhihu.com)

rocketmq的流控

警惕!这 8 个场景下 RocketMQ 会发生流量控制_rocketmq的流控-CSDN博客

消息发送重试和流控机制 | RocketMQ (apache.org)

存储

  1. Kafka采用partition,每个topic的每个partition对应一个文件。顺序写入,定时刷盘。但一旦单个broker的partition过多,则顺序写将退化为随机写,Page Cache脏页过多,频繁触发缺页中断,性能大幅下降。
  2. RocketMQ采用CommitLog+ConsumeQueue,单个broker所有topic在CommitLog中顺序写,Page Cache只需保持最新的页面即可。同时每个topic下的每个queue都有一个对应的ConsumeQueue文件作为索引。ConsumeQueue占用Page Cache极少,刷盘影响较小。

支持队列数

Kafka单机超过64个队列/分区,消息发送性能降低严重(原因见为什么当topic多时kafka延迟比rocketmq延迟高

RocketMQ 单机支持最高5万个队列,性能稳定

**结论:**长远来看,RocketMQ 胜出,这也是适合业务处理的原因之一

性能对比

Rocket单机写入7万 条/秒
Kafka单机百万条/秒,主要 是由于Producer端将多个消息进行合并发送

消息查询

Kafka不支持消息查询
RocketMQ支持根据Message Id查询消息,也支持根据消息内容查询消息
总结:消息查询对于定位消息丢失问题非常有帮助,例如某个订单处理失败,是消息没收到还是收到处理出错了

(同)消息回溯

Kafka理论上可以按照Offset来回溯消息(Kafka需要先根据时间戳找到offset,然后从offset开始消费)。

RocketMQ支持按照时间来回溯消息,精度毫秒,例如从一天之前的某时某分某秒开始重新消费消息
总结:典型业务场景如consumer做订单分析,但是由于程序逻辑或者依赖的系统发生故障等原因,导致今天消费的消息全部无效,需要重新从昨天零点开始消费,那么以时间为起点的消息重放功能对于业务非常有帮助。

Broker端消息过滤

Kafka不支持Broker端的消息过滤
RocketMQ支持两种Broker端消息过滤方式根据Message Tag来过滤,相当于子topic概念
向服务器上传一段Java代码,可以对消息做任意形式的过滤,甚至可以做Message Body的过滤拆分。

broker和消费端的消息过滤可以解释为什么消费组内订阅同topic不同tag会导致异常,详见:

RocketMQ 为什么要保证订阅关系一致 - 知乎 (zhihu.com)

 消息支持

事务消息
Kafka事务
  • Kafka从0.11版本开始支持事务消息,除支持最终一致性外,还实现了消息Exactly Once语义(单个partition)。

一文读懂 kafka 的事务机制 - 知乎 (zhihu.com)
三、优缺点
优点:
事务吞吐量大. 因为不需要等待其他数据源响应.
容错性好. A服务在发布事件的时候, B服务甚至可以不在线。
缺点:
1、容易出现较多的中间状态,保证不了实时性。
比如生产者已经发送数据了,但是消费者才执行到第一步
(拉取消息,留证据到本地数据库消息表),这个时候用户登录之后,
可能就看不到消费后的数据。在实时性要求高的业务不太适用。
2、与具体业务场景绑定,偶尔性强,不可以共用。
四、适用场景:
1、实时性要求不高,只要满足最终一致性的情况
2、生产者的逻辑是否成功,不依赖于消费者的逻辑执行是否成功的情况,
比如:
下订单和出库,这就是典型的生产者的逻辑要依赖于消费者的逻辑
是否执行成功。
因为下订单,如果库存不够,那么订单也是不能成功的。

RocketMq事务(保证的是自身消息的事务)
  • RocketMQ支持事务消息,采用二阶段提交+broker定时回查。但也只能保证生产者与broker的一致性,broker与消费者之间只能单向重试。即保证的是最终一致性。

整体流程
Producer执行本地事务;
Step4:本地事务完毕,根据事务的状态,Producer向Broker发送二次确认消息,确认该Half Message的Commit或者Rollback状态。Broker收到二次确认消息后,对于Commit状态,则直接发送到Consumer端执行消费逻辑,而对于Rollback则直接标记为失败,一段时间后清除,并不会发给Consumer。正常情况下,到此分布式事务已经完成,剩下要处理的就是超时问题,即一段时间后Broker仍没有收到Producer的二次确认消息;
Step5:针对超时状态,Broker主动向Producer发起消息回查;
Step6:Producer处理回查消息,返回对应的本地事务的执行结果;
Step7:Broker针对回查消息的结果,执行Commit或Rollback操作,同Step4。
————————————————
版权声明:本文为CSDN博主「Jack  Chao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_50221071/article/details/124695185

定时/延迟消息

-RocketMQ支持延迟消息,即可以指定消息在一定时间后才被消费。

ScheduleMessageService5 张图带你理解 RocketMQ 延时消息机制 (baidu.com)

支持定时消息,基于时间轮

Kafka不支持延迟消息,但是可以通过一些技术手段来实现类似的效果。

kafka实现延时消息 - 哔哩哔哩 (bilibili.com)Kafka延迟队列的实现方式_kafka延迟队列如何实现_一叶飘零_sweeeet的博客-CSDN博客

消息重复

  • RocketMQ仅支持At Least Once。
  • Kafka支持At Least Once、Exactly Once。

可靠性保证

 ------------------ 发送端 -----------------------

发送策略

kafka

kafka的ACK参数的详解_kafka ack_Li-YC的博客-CSDN博客

rocketmq

同步(Sync)发送、异步(Async)发送和单向(Oneway)发送

RocketMQ—Producer(三)发送方式和消息类型_rocketmq异步发送消息_IT巅峰技术的博客-CSDN博客

在 RocketMQ 中采用了 Dledger 。他要求在写入消息的时候,要求至少消息复制到半数以上的节点之后,才给客⼾端返回写⼊成功,并且它是⽀持通过选举来动态切换主节点的。

三种消息的类型介绍如下:
普通消息:消息是无序的,任意发送发送哪一个队列都可以。
普通有序消息:同一类消息(例如某个用户的消息)总是发送到同一个队列,在异常情况下,也可以发送到 其他队列。
严格有序消息:消息必须被发送到同一个队列,即使在异常情况下,也不允许发送到其他队列。
对于这三种类型的消息,RocketMQ分别提供了对应的方法来发送消息,例如同步发送(异步/批 量/oneway也是类似):

//普通消息:不需要指定MessageQueue或者MessageQueueSelector参数
 SendResult send(final Message msg) 
 //普通有序消息:指定MessageQueueSelector参数,动态决定需要发送到哪个队列,出现异常情况下,才 发送到其他对下列 
 SendResult send(final Message msg, final MessageQueueSelector selector, final Object arg)
  //严格有序消息:指定MessageQueue参数,明确指定发送到哪个队列,如我们可以将一个用户的数据总是发 送到某个队列 
  SendResult send(final Message msg, final MessageQueue mq)
————————————————
版权声明:本文为CSDN博主「Java超神之路」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wwg1234wwg1234/article/details/122985732

RocketMQ—Producer(三)发送方式和消息类型_rocketmq异步发送消息_IT巅峰技术的博客-CSDN博客

RocketMQ 的刷盘机制、主从复制、存储形式_rocketmq 同步刷盘_黑夜无路人的博客-CSDN博客

---------服务器端-----------------

(同)主从复制(影响可用性)

kafka

当主挂掉自动切换选主,详解Kafka 复制与副本机制_kafka 副本_~奔跑的简默~的博客-CSDN博客

rocketmq

RocketMq的数据刷盘策略和复制策略以及RAID介绍 (rstk.cn)

在RocketMQ 4.5之后,RocketMQ支持Dledger机制,该机制基于Raft协议实现的。

11 RocketMQ主从原理、读写分离与故障转移_rocketmq主从切换原理_鮀城小帅的博客-CSDN博客

RocketMQ 的刷盘机制、主从复制、存储形式_rocketmq 同步刷盘_黑夜无路人的博客-CSDN博客

RocketMQ四种模式安装(单Master模式,多Master模式,多Master多Slave模式-异步复制,多Master多Slave模式-同步双写)_rocketmq单master部署_dream21st的博客-CSDN博客

 是否支持同步刷盘

kafka

Kafka本身是没有实现任何同步刷盘机制的,就是说在掉电这种场景下测试,Kafka注定是要丢消息的。但要想做到每一条消息都在落盘后才返回,我们可以通过修改异步刷盘的频率来实现。设置参数log.flush.interval.messages=1,即每条消息都刷一次磁盘。这样的做法,Kafka也不会丢消息了,但是频繁的磁盘读写直接导致性能的下降。

log.flush.interval.messages  //多少条消息,刷盘1次 默认值 LONG.MAX_VALUE
log.flush.interval.ms  //割多长时间,刷盘1次 默认为null(LONG.MAX_VALUE)log.flush.scheduler.interval.ms //周期性的刷盘,缺省3000,即3s。
public static final String FLUSH_MESSAGES_INTERVAL_CONFIG = "flush.messages";
public static final String FLUSH_MESSAGES_INTERVAL_DOC = "This setting allows specifying an interval at " +
    "which we will force an fsync of data written to the log. For example if this was set to 1 " +
    "we would fsync after every message; if it were 5 we would fsync after every five messages. " +
    "In general we recommend you not set this and use replication for durability and allow the " +
    "operating system's background flush capabilities as it is more efficient. This setting can " +
    "be overridden on a per-topic basis (see <a href=\"#topicconfigs\">the per-topic configuration section</a>).";

public static final String FLUSH_MS_CONFIG = "flush.ms";
public static final String FLUSH_MS_DOC = "This setting allows specifying a time interval at which we will " +
    "force an fsync of data written to the log. For example if this was set to 1000 " +
    "we would fsync after 1000 ms had passed. In general we recommend you not set " +
    "this and use replication for durability and allow the operating system's background " +
    "flush capabilities as it is more efficient.";
rocketmq

支持同步刷盘

源码解析:深入源码聊聊RocketMQ刷盘机制 (taodudu.cc)

RocketMQ刷盘机制 (taodudu.cc)

FlushCommitLogService(异步刷盘)

GroupCommitService(同步刷盘)

----------消费端-----------

实时性

- RocketMQ支持重复消费,即可以让消费者重新消费已经消费过的消息。Kafka也支持重复消费,但是需要消费者自己维护分区的偏移量(Offset)。

kafka只支持pull模式。而rocketmq有pull、push两种模式 (虽然这个push模式是假push),push模式延迟肯定是比pull模式延迟低。

rocketmq的push模式是基于pull模式的,本地有个定时线程去pull broker的消息,缓存到本地,然后push到消费线程那边。

(同)有序性

Kafka 某些配置下,支持消息顺序,但是一台Broker宕机后,就会产生消息乱序;

具有<PID, Partition, SeqNumber>相同主键的消息提交时 保证有序和幂等。Pid保证单区内幂等,SeqNumber保证单区内有序。
分区策略选择key。保证一个分区。或者同一个指定区号。


RocketMQ的topic内的队列机制,可以保证存储满足FIFO(First Input First Output 简单说就是指先进先出),剩下的只需要消费者顺序消费即可。

仅保证顺序发送,支持严格的消息顺序,在顺序消息场景下,RocketMQ支持生产者在投放消息的时候自定义投放策略,我们实现一个MessageQueueSelector接口。另外,顺序消息必须使用同步发送的方式才能保证生产者发送的消息有序。实际上,采用队列选择器的方法不能保证消息的严格顺序,我们的目的是将消息发送到同一个队列中,如果某个broker挂了,那么队列就会减少一部分,如果采用取余的方式投递,将可能导致同一个业务中的不同消息被发送到不同的队列中,导致同一个业务的不同消息被存入不同的队列中,短暂的造成部分消息无序。同样的,如果增加了服务器,那么也会造成短暂的造成部分消息无序。

顺序消费

broker端的分布式锁:
在负载均衡的处理新分配队列的updateProcessQueueTableInRebalance方法,以及ConsumeMessageOrderlyService服务启动时的start方法中,都会尝试向broker申请当前消费者客户端分配到的messageQueue的分布式锁。
broker端的分布式锁存储结构为ConcurrentMap<String/* group */, ConcurrentHashMap<MessageQueue, LockEntry>>,该分布式锁保证同一个consumerGroup下同一个messageQueue只会被分配给一个consumerClient。
获取到的broker端的分布式锁,在client端的表现形式为processQueue. locked属性为true,且该分布式锁在broker端默认60s过期,而在client端默认30s过期,因此ConsumeMessageOrderlyService#start会启动一个定时任务,每过20s向broker申请分布式锁,刷新过期时间。而负载均衡服务也是每20s进行一次负载均衡。
broker端的分布式锁最先被获取到,如果没有获取到,那么在负载均衡的时候就不会创建processQueue了也不会提交对应的消费请求了。
messageQueue的本地synchronized锁:
在执行消费任务的开头,便会获取该messageQueue的本地锁对象objLock,它是一个Object对象,然后通过synchronized实现锁定。
这个锁的锁对象存储在MessageQueueLock.mqLockTable属性中,结构为ConcurrentMap<MessageQueue, Object>,所以说,一个MessageQueue对应一个锁,不同的MessageQueue有不同的锁。
因为顺序消费也是通过线程池消费的,所以这个synchronized锁用来保证同一时刻对于同一个队列只有一个线程去消费它。
ProcessQueue的本地consumeLock:
在获取到broker端的分布式锁以及messageQueue的本地synchronized锁的之后,在执行真正的消息消费的逻辑messageListener#consumeMessage之前,会获取ProcessQueue的consumeLock,这个本地锁是一个ReentrantLock。
那么这把锁有什么作用呢?
在负载均衡时,如果某个队列C被分配给了新的消费者,那么当前客户端消费者需要对该队列进行释放,它会调用removeUnnecessaryMessageQueue方法对该队列C请求broker端分布式锁的解锁。
而在请求broker分布式锁解锁的时候,一个重要的操作就是首先尝试获取这个messageQueue对应的ProcessQueue的本地consumeLock。只有获取了这个锁,才能尝试请求broker端对该messageQueue的分布式锁解锁。
如果consumeLock加锁失败,表示当前消息队列正在消息,不能解锁。那么本次就放弃解锁了,移除消息队列失败,只有等待下次重新分配消费队列时,再进行移除。
如果没有这把锁,假设该消息队列因为负载均衡而被分配给其他客户端B,但是由于客户端A正在对于拉取的一批消费消息进行消费,还没有提交消费点位,如果此时客户端A能够直接请求broker对该messageQueue解锁,这将导致客户端B获取该messageQueue的分布式锁,进而消费消息,而这些没有commit的消息将会发送重复消费。
所以说这把锁的作用,就是防止在消费消息的过程中,该消息队列因为发生负载均衡而被分配给其他客户端,进而导致的两个客户端重复消费消息的行为。RocketMq如何保证消息的顺序性_rocketmq如何保证消息有序_壹佰大多的博客-CSDN博客

由消费者业务保证!!!
这里很好理解,一个订单你发送的时候放到一个队列里面去,你同一个的订单号Hash一下是不是还是一样的结果,那肯定是一个消费者消费,那顺序是不是就保证了?

注册中心

rocketmq:nameserver在Broker节点启动时,轮询 NameServer 列表,与每个 NameServer 节点建立长连接,发起注册 请求。在 NameServer 内部维护着⼀个 Broker 列表,用来动态存储 Broker 的信息。
NameServer路由信息有四个Map,
topic信息,brokerIp等信息,集群信息,心跳具体信息

RocketMQ之NameServer详解_rocketmq nameserver_LuciferCoder的博客-CSDN博客

kafka:Kafka 为什么要抛弃 ZooKeeper? - 哔哩哔哩 (bilibili.com)

QUES:

为什么当topic多时kafka延迟比rocketmq延迟高


说kafka延迟比rocketmq延迟高 是有一个前提的 就是topic较多的时候 这个和这2个MQ的数据存储结构有关系的 在topic少的时候延迟基本一致。

kafka的数据存储结构设计师尽可能的保证吞吐量,所以在设计时是尽可能保证log日志小,它的数据结构如下 其中topic是逻辑概念,分区对应就是一个物理文件夹:


所以在topic比较多时,分区文件数量会非常庞大 磁盘顺序读效率还不如随机读效率,则会在topic比较多时 磁盘顺序读就蜕变为随机读,延迟也就高了。

即 kafka 性能对于topic 有阈值(20 个)。

淘宝业务比较复杂,topic会比较多 则为了解决这个痛点 rockertmq 就诞生了 它的数据存储结构 对此做了优化 日志目录只有一个 commit log ,结构如下:


出发点不一样,kafka定位就是处理日志和大数据 在这些业务领域,topic不会太多,延迟问题自然也就没有。

数据存储结构是主要原因,还有就是kafka只支持pull模式。而rocketmq有pull、push两种模式 (虽然这个push模式是假push),push模式延迟肯定是比pull模式延迟低。

push模式是基于pull模式的,本地有个定时线程去pull broker的消息,缓存到本地,然后push到消费线程那边。

rabbit 的push模式 是真的push 所以 延迟最低的就是兔子。 兔子不支持分布式,只支持主从模式 本身设计就是小而美的单机版。cpu消耗比kafka之类低多了。
————————————————
版权声明:本文为CSDN博主「—Phoenix」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/haoweng4800/article/details/130437652

为什么kafka这么快

kafka为什么快/吞吐量大

顺序读写:Kafka每个分区对应一个日志文件,消息写入是追加到日志文件后面、顺序写磁盘的速度快于随机写。
批量发送:Kafka发送消息时将消息缓存到本地,达到一定数量或者间隔一定时间再发送,减少了网络请求的次数。
批量压缩:发送的时候对数据进行压缩。
页面缓存:Kafka大量使用了页面缓存,就是将数据写入磁盘前会先写入系统缓存,然后进行刷盘;读取数据也会先读取缓存,没有再读磁盘。虽然异步刷盘会因单点故障导致数据丢失,但是多副本的机制保障了数据的持久化。
零拷贝:Kafka使用了DMA的技术,使Socket缓冲池可以直接读取内核内存的数据,减少了数据拷贝到应用再拷贝到Socket缓冲池的过程,也减少了2次上下文切换。
————————————————
版权声明:本文为CSDN博主「xiaolong1894」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Sober1894/article/details/123343503

Kafka是一个号称能用普通的PC机也能处理超千万亿的消息吞吐量的实时消息流处理平台。我认为Kafka之所以能支持如此大的吞吐量,而且还能做到性能优秀主要有四个原因:分别是磁盘顺序读写、稀疏索引、批量文件压缩零拷贝机制。下面分别给大家详细介绍一下:

1、磁盘顺序读写

首先要讲一下,磁盘寻址的过程,如图所示:

这个是磁盘的构造。磁盘的盘片不停地旋转,磁头会在磁盘表面画出一个圆形轨迹,这个就叫磁道。从内到位半径不同有很多磁道。然后又用半径线,把磁道分割成了扇区(两根射线之内的扇区组成扇面)。如果要读写数据,必须找到数据对应的扇区,这个过程就叫寻址。

如果读写的多条数据在磁盘上是分散的,寻址会很耗时,这叫随机I/O。

如果读写的数据在磁盘上是集中的,不需要重复寻址的过程,这叫顺序I/O。

而Kafka的Message是不断追加到本地磁盘文件末尾的,而不是随机的写入,这使得Kafka写入吞吐量得到了显著提升。

在一定条件下测试,磁盘的顺序读写可以达到53.2M每秒,比内存的随机读写还要快。

2、稀疏索引

Kafka的索引并不是每一条消息都会建立索引,而是一种稀疏索引。如图所示,

也就是说,Kafka插入一批消息才会产生一条索引记录。后续利用二分查找,可以大大提高检索效率。

3、批量文件压缩

Kafka默认不会删除数据,它会把所有的消息都变成一个批量的文件。如图所示,它会把相同的Key合并为最后一个Value。

这样对消息进行合理的批量压缩,可以减少网络IO损耗。

4、零拷贝机制

操作系统虚拟内存分成两部分,一部分是内核空间,一部分是用户空间。这样就可以避免用户进程直接操作内核,保证内核安全。如图所示:

正常情况下,如果用户要从磁盘读取数据,必须先把数据从磁盘拷贝到内核缓冲区,然后在从内核缓冲区到用户缓冲区,最后才能返回给用户。

在Linux操作系统里面提供了一个sendfile函数,可以实现“零拷贝”。意思就是不需要经过用户缓冲区,可以直接把数据拷贝到网卡。

而Kafka中文件传输最终调用的是Java NIO 库里的 transferTo 方法,实际上最后就会使用到Linux sendfile() 系统调用函数。零拷贝技术可以大大地提升文件传输的性能。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值