Kafka系列(二)将消息数据写入Kafka系统--生产者【异步发送、同步发送、单线程发送、多线程发送、配置生产者属性、自定义序列化、自定义主题分区】

Kafka系列

    • 发送消息到 Kafka 主题
    • 了解异步模式
    • 了解同步模式
    • 线程发送消息的步骤
      • 生产者用单线程发送消息
      • 生产者用多线程发送消息
    • 配置生产者属性
    • 保存对象的各个属性一序列化
      • 序列化一个对象
      • 序列化对象的存储格式
      • 自己实现 序列化的步骤
        • 1. 创建序列化对象
        • 2. 编写序列化工具类
        • 3. 编写自定义序列化逻辑代码
        • 4. 编写生产者应用程序
    • 自定义主题分区
      • 编写自定义主题分区算法
      • 演示自定义分区的作用

转自 《Kafka并不难学!入门、进阶、商业实战》

发送消息到 Kafka 主题

Kafka 0.10.0.0 及以后的版本,对生产者代码的底层实现进行了重构。kafka.producer.Producer类被 org.apache.kafka.clients.producer.KafkaProducer 类替换
Kafka 系统支持两种不同的发送方式–同步模式(Sync)和异步模式(ASync)。

了解异步模式

在 Kafka 0.10.0.0 及以后的版本中,客户端应用程序调用生产者应用接口,默认使用异步的方式发送消息。
生产者客户端在通过异步模式发送消息时,通常会调用回调函数的 send()方法发送消息。生产者端收到 Kafka 代理节点的响应后会触发回调函数

  1. 什么场景下需要使用异步模式
    假如生产者客户端与 Kafka 集群节点间存在网络延时(100ms),此时发送 10 条消息记录,则延时将达到 1s。而大数据场景下有着海量的消息记录,发送的消息记录是远不止 10条,延时将非常严重。
    大数据场景下,如果采用异步模式发送消息记录,几乎没有任何耗时,通过回调函数可以知道消息发送的结果。
  2. 异步模式数据写入流程
    例如,一个业务主题(ip login)有6个分区。生产者客户端写入一条消息记录时,消息记录会先写入某个缓冲区,生产者客户端直接得到结果(这时,缓冲区里的数据并没有写到 Kafka代理节点中主题的某个分区)。之后,缓冲区中的数据会通过异步模式发送到 Kafka 代理节点中主题的某个分区中
	//实例化一个消息记录对象,用来保存主题名、分区索引、键、值和时间戳ProducerRecord<byte[],byte[]> record =new ProducerRecord<byte[],byte[]>("ip login", key, value);//调用 send()方法和回调函数producer.send(myRecord,new Callback() {public void onCompletion (RecordMetadata metadata, Exception e){if (e != null) {e.printStackTrace();} else {System.out.println("The offset of the record we just sent is:" + metadata.offset());}}};

消息记录提交给 send()方法后,实际上该消息记录被放入一个缓冲区的发送队列,然后通过后台线程将其从缓冲区队列中取出并进行发送;发送成功后会触发send方法的回调函数-Callback.

了解同步模式

生产者客户端通过 send()方法实现同步模式发送消息,并返回一个 Future 对象,同时调用get()方法等待 Future 对象,看 send()方法是否发送成功。

  1. 什么场景下使用同步模式
    如果要收集用户访问网页的数据,在写数据到 Kafka 集群代理节点时需要立即知道消息是否写入成功,此时应使用同步模式。
// 将字符串转换成字节数组
byte[] key = "key".getBytes();
byte[] value ="value".getBytes();
// 实例化一个消息记录对象,用来保存主题名、分区索引、键、值和时间戳
ProducerRecord<byte[],byte[]> record = new ProducerRecord<byte[],byte[]>("ip_login",key, value);
//调用 send()函数后,再通过 get()方法等待返回结果
producer.send(record).get();

这里通过调用 Future 接口中的 get()方法等待 Kafka 集群代理节点(Broker)的状态返回如果 Producer 发送消息记录成功了,则返回 RecordMetadata 对象,该对象可用来查看消息记录的偏移量(Offset)。

线程发送消息的步骤

在 Kafka 系统中,为了保证生产者客户端应用程序的独立运行,通常使用线程的方式发送消息。
创建一个简单的生产者应用程序的步骤如下。
(1)实例化 Properties 类对象,配置生产者应答机制。有以下三个属性是必须设置的。其他属性一般都会有默认值,可以按需添加设置。

  • bootstrap.servers:配置Kafka集群代理节点地址;
  • key.serializer:序列化消息主键;
  • value.serializer:序列化消息数据内容,

(2)根据属性对象实例化一个 KafkaProducer.
(3)通过实例化一个ProducerRecord 对象,将消息记录以“键-值”对的形式进行封装。
(4)通过调用 KafkaProducer 对象中带有回调函数的 send方法发送消息给 Kafka 集群
(5)关闭KafkaProducer 对象,释放连接资源,

生产者用单线程发送消息

/*** 实现一个生产者客户端应用程序.*/
public class JProducer extends Thread {private final Logger LOG = LoggerFactory.getLogger(JProducer.class);/** 配置Kafka连接信息. */public Properties configure() {Properties props = new Properties();props.put("bootstrap.servers", "dn1:9092,dn2:9092,dn3:9092");// 指定Kafka集群地址props.put("acks", "1"); // 设置应答模式, 1表示有一个Kafka代理节点返回结果props.put("retries", 0); // 重试次数props.put("batch.size", 16384); // 批量提交大小props.put("linger.ms", 1); // 延时提交props.put("buffer.memory", 33554432); // 缓冲大小props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 序列化主键props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");// 序列化值return props;}public static void main(String[] args) {JProducer producer = new JProducer();producer.start();}/** 实现一个单线程生产者客户端. */public void run() {Producer<String, String> producer = new KafkaProducer<>(configure());// 发送100条JSON格式的数据for (int i = 0; i < 100; i++) {// 封装JSON格式JSONObject json = new JSONObject();json.put("id", i);json.put("ip", "192.168.0." + i);json.put("date", new Date().toString());String k = "key" + i;// 异步发送producer.send(new ProducerRecord<String, String>("test_kafka_game_x", k, json.toJSONString()), new Callback() {public void onCompletion(RecordMetadata metadata, Exception e) {if (e != null) {LOG.error("Send error, msg is " + e.getMessage());} else {LOG.info("The offset of the record we just sent is: " + metadata.offset());}}});}try {sleep(3000);// 间隔3秒} catch (InterruptedException e) {LOG.error("Interrupted thread error, msg is " + e.getMessage());}producer.close();// 关闭生产者对象}
}

这里的主题只有一个分区和一个副本,所以,发送的所有消息会写入同一个分区中
如果希望发送完消息后获取一些返回信息(比如获取消息的偏移量、分区索引值、提交的时间戳等),则可以通过回调函数 CallBack 返回的 RecordMetadata 对象来实现。
由于 Kafka 系统的生产者对象是线程安全的,所以,可通过增加生产者对象的线程数来提高 Kafka 系统的吞吐量。

生产者用多线程发送消息

public class JProducerThread extends Thread {// 创建一个日志对象private final Logger LOG = LoggerFactory.getLogger(JProducerThread.class);// 声明最大线程数private final static int MAX_THREAD_SIZE = 6;/** 配置Kafka连接信息. */public Properties configure() {Properties props = new Properties();props.put("bootstrap.servers", "dn1:9092,dn2:9092,dn3:9092");// 指定Kafka集群地址props.put("acks", "1"); // 设置应答模式, 1表示有一个Kafka代理节点返回结果props.put("retries", 0); // 重试次数props.put("batch.size", 16384); // 批量提交大小props.put("linger.ms", 1); // 延时提交props.put("buffer.memory", 33554432); // 缓冲大小props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 序列化主键props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");// 序列化值props.put("partitioner.class", "org.smartloli.kafka.game.x.book_4.JPartitioner");// 指定自定义分区类return props;}public static void main(String[] args) {// 创建一个固定线程数量的线程池ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREAD_SIZE);// 提交任务executorService.submit(new JProducerThread());// 关闭线程池executorService.shutdown();}/** 实现一个单线程生产者客户端. */public void run() {Producer<String, String> producer = new KafkaProducer<>(configure());// 发送100条JSON格式的数据for (int i = 0; i < 10; i++) {// 封装JSON格式JSONObject json = new JSONObject();json.put("id", i);json.put("ip", "192.168.0." + i);json.put("date", new Date().toString());String k = "key" + i;// 异步发送producer.send(new ProducerRecord<String, String>("ip_login_rt", k, json.toJSONString()), new Callback() {public void onCompletion(RecordMetadata metadata, Exception e) {if (e != null) {LOG.error("Send error, msg is " + e.getMessage());} else {LOG.info("The offset of the record we just sent is: " + metadata.offset());}}});}try {sleep(3000);// 间隔3秒} catch (InterruptedException e) {LOG.error("Interrupted thread error, msg is " + e.getMessage());}producer.close();// 关闭生产者对象}}

配置生产者属性

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

保存对象的各个属性一序列化

序列化一个对象

在分布式环境下,无论哪种格式的数据都会被分解成二进制,以便存储在文件中或者在网络上传输。
序列化就是,将对象以一连串的字节进行描述,用来解决对象在进行读写操作时所引发的问题。
序列化可以将对象的状态写成数据流,并进行网络传输或者保存在文件或数据库中,在需要时再把该数据流读取出来,重新构造一个相同的对象。

  1. 为什么需要序列化
    在传统的企业应用中,不同的组件分布在不同的系统和网络中。如果两个组件之间想要进行通信,那么它们之间必须有数据转换机制。实现这个过程需要遵照一个协议来传输对象,这意味着,接收端需要知道发送端所使用的协议才能重新构建对象,以此来保证两个组件之间的通信是安全的。
public class JObjectSerial implements Serializable {private static Logger LOG = LoggerFactory.getLogger(JObjectSerial.class);/*** 序列化版本ID.*/private static final long serialVersionUID = 1L;public byte id = 1; // 用户IDpublic byte money = 100; // 充值金额/** 实例化入口函数. */public static void main(String[] args) {try {FileOutputStream fos = new FileOutputStream("/tmp/salary.out"); // 实例化一个输出流对象ObjectOutputStream oos = new ObjectOutputStream(fos);// 实例化一个对象输出流JObjectSerial jos = new JObjectSerial(); // 实例化序列化类oos.writeObject(jos); // 写入对象oos.flush(); // 刷新数据流oos.close();// 关闭连接} catch (Exception e) {LOG.error("Serial has error, msg is " + e.getMessage());// 打印异常信息}}
}

序列化对象的存储格式

在这里插入图片描述

自己实现 序列化的步骤

在这里插入图片描述
如果使用原生的序列化方式,则需要将传输的内容拼接成字符串或转成字符数组,抑或是其他类型,这样在实现代码时就会比较麻烦。而 Kafka 为了解决这种问题,提供了序列化的接口,让用户可以自定义对象的序列化方式,来完成对象的传输。
以下实例将演示生产者客户端应用程序中序列化的用法,利用 Serializable 接口来序列化对象。

1. 创建序列化对象
/*** 声明一个序列化类.* * @author smartloli.**         Created by Apr 30, 2018*/
public class JSalarySerial implements Serializable {/*** 序列化版本ID.*/private static final long serialVersionUID = 1L;private String id;// 用户IDprivate String salary;// 金额public String getId() {return id;}public void setId(String id) {this.id = id;}public String getSalary() {return salary;}public void setSalary(String salary) {this.salary = salary;}// 打印对象属性值@Overridepublic String toString() {return "JSalarySerial [id=" + id + ", salary=" + salary + "]";}}
2. 编写序列化工具类
/*** 封装一个序列化的工具类.* * @author smartloli.**         Created by Apr 30, 2018*/
public class SerializeUtils {/** 实现序列化. */public static byte[] serialize(Object object) {try {return object.toString().getBytes("UTF8");// 返回字节数组} catch (Exception e) {e.printStackTrace(); // 抛出异常信息}return null;}/** 实现反序列化. */public static <T> Object deserialize(byte[] bytes) {try {return new String(bytes, "UTF8");// 反序列化} catch (Exception e) {e.printStackTrace();}return null;}}
3. 编写自定义序列化逻辑代码
/*** 自定义序列化实现.* * @author smartloli.**         Created by Apr 30, 2018*/
public class JSalarySeralizer implements Serializer<JSalarySerial> {@Overridepublic void configure(Map<String, ?> configs, boolean isKey) {}/** 实现自定义序列化. */@Overridepublic byte[] serialize(String topic, JSalarySerial data) {return SerializeUtils.serialize(data);}@Overridepublic void close() {}}
4. 编写生产者应用程序
/*** 自定义序列化, 发送消息给Kafka.* * @author smartloli.**         Created by Apr 30, 2018*/
public class JProducerSerial extends Thread {private static Logger LOG = LoggerFactory.getLogger(JProducerSerial.class);/** 配置Kafka连接信息. */public Properties configure() {Properties props = new Properties();props.put("bootstrap.servers", "dn1:9092,dn2:9092,dn3:9092");// 指定Kafka集群地址props.put("acks", "1"); // 设置应答模式, 1表示有一个Kafka代理节点返回结果props.put("retries", 0); // 重试次数props.put("batch.size", 16384); // 批量提交大小props.put("linger.ms", 1); // 延时提交props.put("buffer.memory", 33554432); // 缓冲大小props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 序列化主键props.put("value.serializer", "org.smartloli.kafka.game.x.book_4.serialization.JSalarySeralizer");// 自定义序列化值return props;}public static void main(String[] args) {JProducerSerial producer = new JProducerSerial();producer.start();}/** 实现一个单线程生产者客户端. */public void run() {Producer<String, JSalarySerial> producer = new KafkaProducer<>(configure());JSalarySerial jss = new JSalarySerial();jss.setId("2018");jss.setSalary("100");producer.send(new ProducerRecord<String, JSalarySerial>("test_topic_ser_des", "key", jss), new Callback() {public void onCompletion(RecordMetadata metadata, Exception e) {if (e != null) {LOG.error("Send error, msg is " + e.getMessage());} else {LOG.info("The offset of the record we just sent is: " + metadata.offset());}}});try {sleep(3000);// 间隔3秒} catch (InterruptedException e) {LOG.error("Interrupted thread error, msg is " + e.getMessage());}producer.close();// 关闭生产者对象}
}

在这里插入图片描述

自定义主题分区

在这里插入图片描述

编写自定义主题分区算法

/*** 实现一个自定义分区类.** @author smartloli.**         Created by Apr 30, 2018*/
public class JPartitioner implements Partitioner {@Overridepublic void configure(Map<String, ?> configs) {}/** 实现Kafka主题分区索引算法. */@Overridepublic int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {int partition = 0;String k = (String) key;partition = Math.abs(k.hashCode()) % cluster.partitionCountForTopic(topic);return partition;}@Overridepublic void close() {}}

演示自定义分区的作用

/*** 实现一个生产者客户端应用程序.* * @author smartloli.**         Created by Apr 27, 2018*/
public class JProducerThread extends Thread {// 创建一个日志对象private final Logger LOG = LoggerFactory.getLogger(JProducerThread.class);// 声明最大线程数private final static int MAX_THREAD_SIZE = 6;/** 配置Kafka连接信息. */public Properties configure() {Properties props = new Properties();props.put("bootstrap.servers", "dn1:9092,dn2:9092,dn3:9092");// 指定Kafka集群地址props.put("acks", "1"); // 设置应答模式, 1表示有一个Kafka代理节点返回结果props.put("retries", 0); // 重试次数props.put("batch.size", 16384); // 批量提交大小props.put("linger.ms", 1); // 延时提交props.put("buffer.memory", 33554432); // 缓冲大小props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 序列化主键props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");// 序列化值props.put("partitioner.class", "org.smartloli.kafka.game.x.book_4.JPartitioner");// 指定自定义分区类return props;}public static void main(String[] args) {// 创建一个固定线程数量的线程池ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREAD_SIZE);// 提交任务executorService.submit(new JProducerThread());// 关闭线程池executorService.shutdown();}/** 实现一个单线程生产者客户端. */public void run() {Producer<String, String> producer = new KafkaProducer<>(configure());// 发送100条JSON格式的数据for (int i = 0; i < 10; i++) {// 封装JSON格式JSONObject json = new JSONObject();json.put("id", i);json.put("ip", "192.168.0." + i);json.put("date", new Date().toString());String k = "key" + i;// 异步发送producer.send(new ProducerRecord<String, String>("ip_login_rt", k, json.toJSONString()), new Callback() {public void onCompletion(RecordMetadata metadata, Exception e) {if (e != null) {LOG.error("Send error, msg is " + e.getMessage());} else {LOG.info("The offset of the record we just sent is: " + metadata.offset());}}});}try {sleep(3000);// 间隔3秒} catch (InterruptedException e) {LOG.error("Interrupted thread error, msg is " + e.getMessage());}producer.close();// 关闭生产者对象}}

在这里插入图片描述

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

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

相关文章

Mysql运维篇(四) MySQL常用命令

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。如有侵权&#xff0c;请留言&#xff0c;我及时删除&#xff01; 一、MySQL命令速查表 https://www.cnblogs.com/pyng/p/15560059.html Mysql DBA运维命令大全 - 墨…

【机器学习、深度学习和强化学习原理】

目录 机器学习、深度学习和强化学习都是人工智能的重要领域&#xff0c;它们的代码原理与实现有所不同。机器学习是一种通过训练模型来从数据中学习规律和模式的技术。其代码实现通常包括以下步骤&#xff1a;深度学习是一种模仿人脑神经网络的算法&#xff0c;通过多层神经网络…

批量注册与自动下单:探索速卖通跨境智能系统的操作方法

速卖通跨境智能系统是一款功能强大的软件&#xff0c;可以帮助用户批量注册速卖通买家号、绑定地址、加购加心愿单以及自动下单等任务。 该软件具有以下优势&#xff1a; 强大的指纹系统&#xff1a;采用最新的反指纹技术&#xff0c;可以设置与代理IP相对应的语言和时区&…

Java swing——创建对话框JDialog

之前我们讲了怎么建立一个简易的窗口&#xff0c;链接&#xff1a;http://t.csdnimg.cn/l7QSs&#xff0c;接下来继续讲解窗口的进阶。 对话框 上一篇文章中我们讲到了JFrame是一种顶层容器&#xff0c;本文接下来介绍其余的顶层容器。 跟JFrame一样&#xff0c;&#xff0c;这…

C/C++ 回调函数 callback 异步编程

一、C语言的回调函数 1.小试牛刀 #include <iostream> using namespace std; #include <memory> #include <stdlib.h>int add(int a, int b) {return a b; }void test01() {// 函数指针可以指向任何类型的函数&#xff0c;只要函数的参数列表和返回值类型…

如何结合ChatGPT生成个人魔法咒语词库

3.6.1 ChatGPT辅助力AI绘画 3.6.1.1 给定主题让ChatGPT直接描述 上面给了一个简易主题演示一下&#xff0c;这是完全我没有细化的提问&#xff0c;然后把直接把这些关键词组合在一起。 关键词&#xff1a; 黄山的美景&#xff0c;生机勃勃&#xff0c;湛蓝天空&#xff0c;青…

厕所革命与可持续发展的“九牧方案”

人类文明的历史&#xff0c;就是厕所的革命史&#xff0c;小小的厕所里&#xff0c;承载着大故事。 2015 年&#xff0c;印度一个名叫娜尔的女孩&#xff0c;因为丈夫不愿意在家盖厕所&#xff0c;向法庭提出了离婚申请&#xff0c;由此引发了全印度“无厕所&#xff0c;无新娘…

OSG帧渲染,如何实现自定义动画效果

看到这个标题,老司机可能会想到OSG动画相关的内容,比如osg::AnimationPath类和osg::AnimationPathCallback类,这些动画类,可以实现按照一定的插值方式,生成路径,物体对象按照生成的路径或者预先指定的路径来完成相应的动作的动画。 路径动画有三种动画模式,分别为单摆环…

【百度Apollo】探索创新之路:深入了解Apollo开放平台

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

树莓派突然不能ssh远程连接的踩坑记录及解决方案

在家研究树莓派&#xff0c;远程连接树莓派吃了不少苦&#xff0c;总是一些意想不到的问题出现&#xff0c;明明昨天还能远程连接&#xff0c;今天又不能了。经过一系列排查&#xff0c;终于锁定&#xff1a; 因为我之前设置的树莓派的静态ip地址&#xff0c;但是可能因为是家…

ai创作软件有哪些?这5个软件了解一下

ai创作软件有哪些&#xff1f;随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;在各个领域都展现出了惊人的实力。特别是在内容创作领域&#xff0c;AI技术已经成为了助力创作者们提高效率、释放创意的得力助手。今天&#xff0c;我们将为大家介绍五款AI创作…

软件工程(最简式总结)

目录 第一章:概述 1.软件危机的表现原因 2.常见的软件开发方法包括&#xff1a; 3.软件工程基本原则 4.软件工程三要素 5.设计模式的分类 6.针对变换型数据流设计步骤 7.针对事务型数据流设计步骤 第二章&#xff1a;软件过程 1.软件生命周期 2.软件过程模型 &…

flask_django_python五金电商网络营销的可视化分析研究

前面部分完成了系统需求分析&#xff0c;了解到新闻数据业务方面的需求&#xff0c;系统主要分为用户管理、五金信息管理、在线留言、系统管理等功能。销的可视化研究&#xff0c;并对这些数据进行处理&#xff0c; 然后对这些数据进行可视化分析和统计。 Python 爬虫技术目前来…

js数组和字符串之间的转换方式以及数组的一些方法

一、数组和字符串之间的转换方式 1&#xff09;将字符串切割成字符串数组—stringObject.split(separator, howmany) seperator-----字符串、正则表达式&#xff0c;必需 howmany------指定返回的数组的最大长度&#xff0c;可省略&#xff0c;省略后全量返回 源代码 var str&q…

c++阶梯之类与对象(一)

目录 1.面向过程与面向对象 c语言的视角&#xff1a; c的视角 2. 类的引入 3. 类的定义 3.1 类的两种定义方式 3.2 成员变量如何命名 4. 类的访问限定符与封装 4.1 访问限定符 4.2 封装 5. 类的作用域 6. 类的实例化 7. 类对象模型 7.1 怎么计算一个类对象的…

redis布隆过滤器(Bloom)详细使用教程

文章目录 布隆过滤器1. 原理2. 结构和操作3. 特点和应用场景4. 缺点和注意事项 应用-redis插件布隆过滤器使用详细过程安装以及配置springboot项目使用redis布隆过滤器下面是布隆过滤器的一些基础命令 扩展 布隆过滤器 Bloom 过滤器是一种概率型数据结构&#xff0c;用于快速判…

CUDA/TensorRT部署知识点

CUDA相关: 1、CUDA核函数嵌套核函数的用法多吗? 答:这种用法非常少,主要是因为启动一个kernel本身就有一定延迟,会造成执行的不连续性。 2、如下代码里的 grid/block 对应硬件上的 SM 的关系是什么? 答:首先需要理解grid/block是软件层的概念,而SM是硬件层的概念。所…

springboot151基于web的人力资源管理系统的设计与实现

人力资源管理系统的设计与实现 摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;员工信息因为其管理内容繁杂&#xff0c;管理数量繁…

SSH免密切换服务器案例-ssh协议(公钥和私钥)

公钥和私钥理解 公钥提供加密&#xff0c;私钥解密&#xff0c;公钥可以共享&#xff0c;私钥不可以。举例公钥相当于锁头&#xff0c;可以给别人用&#xff0c;钥匙相当于私钥&#xff0c;只能开自己发出去的锁头&#xff0c;也就是私钥和公钥成对&#xff0c;私钥只能解密对…

~小青蛙跳台阶~C语言~刷题

引言 这次&#xff0c;我们要与一只活泼可爱的小青蛙合作&#xff0c;并引导它跳台阶。小青蛙的体力十分充沛&#xff0c;尤其喜欢跳跃&#xff0c;让它作为我们的助手&#xff0c;来看看有几种跳跃指定台阶数的方法。 本文会涉及到函数递归的知识&#xff0c;后续我会更新讲解…