做网站的步骤/网上培训机构
文章目录
- 项目背景
- 正文
- 一、项目架构
- 二、项目模块
- 三、业务流程
- 四、代码详解
- 1.消息队列
- 2.执行类
- 3.客户端
- 五、测试
- 六、源码
- 后记
项目背景
最近公司某物联网项目需要使用socket长连接进行消息通讯,捣鼓了一版代码上线,结果BUG不断,本猿寝食难安,于是求助度娘,数日未眠项目终于平稳运行了,本着开源共享的精神,本猿把项目代码提炼成了一个demo项目,尽量摒弃了其中丑陋的业务部分,希望与同学们共同学习进步。
正文
一、项目架构
本项目使用了netty、redis以及springboot2.2.0
二、项目模块
本项目目录结构如下图:
netty-tcp-core是公共模块,主要是工具类。netty-tcp-server是netty服务端,服务端仅作测试使用,实际项目中我们只使用了客户端。netty-tcp-client是客户端,也是本文的重点。
三、业务流程
我们实际项目中使用RocketMQ作为消息队列,本项目由于是demo项目于是改为了BlockingQueue。数据流为:
生产者->消息队列->消费者(客户端)->tcp通道->服务端->tcp通道->客户端。
当消费者接收到某设备发送的消息后,将判断缓存中是否存在该设备与服务端的连接,如果存在并且通道活跃则使用该通道发送消息,如果不存在则创建通道并在通道激活后立即发送消息,当客户端收到来自服务端的消息时进行响应的业务处理。
四、代码详解
1.消息队列
由于本demo项目移除了消息中间件,于是需要自己创建一个本地队列模拟真实使用场景
package org.example.client;import org.example.client.model.NettyMsgModel;import java.util.concurrent.ArrayBlockingQueue;/*** 本项目为演示使用本地队列 实际生产中应该使用消息中间件代替(rocketmq或rabbitmq)** @author ReWind00* @date 2023/2/15 11:20*/
public class QueueHolder {private static final ArrayBlockingQueue<NettyMsgModel> queue = new ArrayBlockingQueue<>(100);public static ArrayBlockingQueue<NettyMsgModel> get() {return queue;}
}
使用一个类保存队列的静态实例以便在任何类中都可以快速引用。接下来我们需要启动一个线程去监听队列中的消息,一但消息投递到队列中,我们就取出消息然后异步多线程处理该消息。
public class LoopThread implements Runnable {@Overridepublic void run() {for (int i = 0; i < MAIN_THREAD_POOL_SIZE; i++) {executor.execute(() -> {while (true) {//取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到try {NettyMsgModel nettyMsgModel = QueueHolder.get().take();messageProcessor.process(nettyMsgModel);} catch (InterruptedException e) {log.error(e.getMessage(), e);}}});}}}
使用take方法会使该线程一直阻塞直到队列收到消息后进入下一次循环。
2.执行类
process方法来自于MessageProcessor类,该类为单例,但是会有多线程同时执行。
public void process(NettyMsgModel nettyMsgModel) {String imei = nettyMsgModel.getImei();try {synchronized (this) { //为避免收到同一台设备多条消息后重复创建客户端,必须加锁if (redisCache.hasKey(NETTY_QUEUE_LOCK + imei)) { //上一条消息处理中log.info("imei={}消息处理中,重新入列", imei);//放回队列重新等待消费 延迟x秒(实际项目中应该使用rocketmq或者rabbitmq实现延迟消费)new Timer().schedule(new TimerTask() {@Overridepublic void run() {QueueHolder.get().offer(nettyMsgModel);}}, 2000);log.info("imei={}消息处理中,重新入列完成", imei);return;} else {//如果没有在连接中的直接加锁redisCache.setCacheObject(NETTY_QUEUE_LOCK + imei, "1", 120, TimeUnit.SECONDS);}}//缓存中存在则发送消息if (NettyClientHolder.get().containsKey(imei)) {NettyClient nettyClient = NettyClientHolder.get().get(imei);if (null != nettyClient.getChannelFuture() && nettyClient.getChannelFuture().channel().isActive()) { //通道活跃直接发送消息if (!nettyClient.getChannelFuture().channel().isWritable()) {log.warn("警告,通道不可写,imei={},channelId={}", nettyClient.getImei(),nettyClient.getChannelFuture().channel().id());}nettyClient.send(nettyMsgModel.getMsg());} else {log.info("client imei={},通道不活跃,主动关闭", nettyClient.getImei());nettyClient.close();//重新创建客户端发送this.createClientAndSend(nettyMsgModel);}} else { //缓存中不存在则创建新的客户端this.createClientAndSend(nettyMsgModel);}} catch (Exception e) {log.error(e.getMessage(), e);} finally {//执行完后解锁redisCache.deleteObject(NETTY_QUEUE_LOCK + imei);}}
其中imei是我们设备的唯一标识,我们可以用imei作为缓存的key来确认是否已创建过连接。由于我们消息的并发量可能会很大,所以存在当某设备的连接正在创建的过程中,另一个线程收到该设备消息也开始创建连接的情况,所以我们使用synchronized 代码块以及redis分布式锁来避免此情况的发生。当一条消息获得锁后,在锁释放前,后续消息将会被重新放回消息队列并延迟消费。
获取锁的线程会根据imei判断缓存是否存在连接,如果存在直接发送消息,如果不存在则进入创建客户端的方法。
private void createClientAndSend(NettyMsgModel nettyMsgModel) {log.info("创建客户端执行中imei={}", nettyMsgModel.getImei());//此处的DemoClientHandler可以根据自己的业务定义NettyClient nettyClient = SpringUtils.getBean(NettyClient.class, nettyMsgModel.getImei(), nettyMsgModel.getBizData(),this.createDefaultWorkGroup(this.workerThread), DemoClientHandler.class);executor.execute(nettyClient); //执行客户端初始化try {//利用锁等待客户端激活synchronized (nettyClient) {long c1 = System.currentTimeMillis();nettyClient.wait(5000); //最多阻塞5秒 5秒后客户端仍然未激活则自动解锁long c2 = System.currentTimeMillis();log.info("创建客户端wait耗时={}ms", c2 - c1);}if (null != nettyClient.getChannelFuture() && nettyClient.getChannelFuture().channel().isActive()) { //连接成功//存入缓存NettyClientHolder.get().put(nettyMsgModel.getImei(), nettyClient);//客户端激活后发送消息nettyClient.send(nettyMsgModel.getMsg());} else { //连接失败log.warn("客户端创建失败,imei={}", nettyMsgModel.getImei());nettyClient.close();//可以把消息重新入列处理}} catch (Exception e) {log.error("客户端初始化发送消息异常===>{}", e.getMessage(), e);}}
当netty客户端实例创建后使用线程池执行初始化,由于是异步执行,我们此时立刻发送消息很可能客户端还没有完成连接,因此必须加锁等待。进入synchronized 代码块,使用wait方法等待客户端激活后解锁,参数5000为自动解锁的毫秒数,意思是如果客户端出现异常情况迟迟未能连接成功并激活通道、解锁,则最多5000毫秒后该锁自动解开。这参数在实际使用时可以视情况调整,在并发量很大的情况下,5秒的阻塞可能会导致线程池耗尽,或内存溢出。待客户端创建成功并激活后则立即发送消息。
3.客户端
package org.example.client;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.client.handler.BaseClientHandler;
import org.example.core.util.SpringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;/*** @author ReWind00* @date 2023/2/15 9:59*/
@Slf4j
@Component
@Scope("prototype")
@Getter
@NoArgsConstructor
public class NettyClient implements Runnable {@Value("${netty.server.port}")private int port;@Value("${netty.server.host}")private String host;//客户端唯一标识private String imei;//自定义业务数据private Map<String, Object> bizData;private EventLoopGroup workGroup;private Class<BaseClientHandler> clientHandlerClass;private ChannelFuture channelFuture;public NettyClient(String imei, Map<String, Object> bizData, EventLoopGroup workGroup, Class<BaseClientHandler> clientHandlerClass) {this.imei = imei;this.bizData = bizData;this.workGroup = workGroup;this.clientHandlerClass = clientHandlerClass;}@Overridepublic void run() {try {this.init();log.info("客户端启动imei={}", imei);} catch (Exception e) {log.error("客户端启动失败:{}", e.getMessage(), e);}}public void close() {if (null != this.channelFuture) {this.channelFuture.channel().close();}NettyClientHolder.get().remove(this.imei);}public void send(String message) {try {if (!this.channelFuture.channel().isActive()) {log.info("通道不活跃imei={}", this.imei);return;}if (!StringUtils.isEmpty(message)) {log.info("队列消息发送===>{}", message);this.channelFuture.channel().writeAndFlush(message);}} catch (Exception e) {log.error(e.getMessage(), e);}}private void init() throws Exception {//将本实例传递到handlerBaseClientHandler clientHandler = SpringUtils.getBean(clientHandlerClass, this);Bootstrap b = new Bootstrap();//2 通过辅助类去构造server/clientb.group(workGroup).channel(NioSocketChannel.class).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000).option(ChannelOption.SO_RCVBUF, 1024 * 32).option(ChannelOption.SO_SNDBUF, 1024 * 32).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024 * 1024, Unpooled.copiedBuffer("\r\n".getBytes())));ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));// String解码。ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));// String解码。
// // 心跳设置ch.pipeline().addLast(new IdleStateHandler(0, 0, 600, TimeUnit.SECONDS));ch.pipeline().addLast(clientHandler);}});this.connect(b);}private void connect(Bootstrap b) throws InterruptedException {long c1 = System.currentTimeMillis();final int maxRetries = 2; //重连2次final AtomicInteger count = new AtomicInteger();final AtomicBoolean flag = new AtomicBoolean(false);try {this.channelFuture = b.connect(host, port).addListener(new ChannelFutureListener() {public void operationComplete(ChannelFuture future) throws Exception {if (!future.isSuccess()) {if (count.incrementAndGet() > maxRetries) {log.warn("imei={}重连超过{}次", imei, maxRetries);} else {log.info("imei={}重连第{}次", imei, count);b.connect(host, port).addListener(this);}} else {log.info("imei={}连接成功,连接IP:{}连接端口:{}", imei, host, port);flag.set(true);}}}).sync(); //同步连接} catch (Exception e) {log.error(e.getMessage(), e);}log.info("设备imei={},channelId={}连接耗时={}ms", imei, channelFuture.channel().id(), System.currentTimeMillis() - c1);if (flag.get()) {channelFuture.channel().closeFuture().sync(); //连接成功后将持续阻塞该线程}}
}
netty客户端为多实例,每个实例绑定一个线程,持续阻塞到客户端关闭为止,每个客户端中可以保存自己的业务数据,以便在后续与服务端交互时处理业务使用。客户端执行连接时,给了2次重试的机会,如果3次都没连接成功则放弃。后续可以选择将该消息重新入列消费。我们实际项目中,此处还应该预先给服务端发送一条登录消息,待服务端确认后才能执行后续通讯,这需要视实际情况进行调整。
另一个需要注意的点是EventLoopGroup是从构造函数传入的,而不是在客户端中创建的,因为当客户端数量非常多时,每个客户端都创建自己的线程组会极大的消耗服务器资源,因此我们在实际使用中是按业务去创建统一的线程组给该业务下的所有客户端共同使用的,线程组的大小需要根据业务需求灵活配置。
在init方法中,我们给客户端加上了一个handler来处理与服务端的交互,下面来看一下具体实现。
package org.example.client.handler;import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.example.client.NettyClient;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;import java.util.Map;/*** @author ReWind00* @date 2023/2/15 10:09*/
@Slf4j
@Component
@Scope("prototype")
public class DemoClientHandler extends BaseClientHandler {private final String imei;private final Map<String, Object> bizData;private final NettyClient nettyClient;private int allIdleCounter = 0;private static final int MAX_IDLE_TIMES = 3;public DemoClientHandler(NettyClient nettyClient) {this.nettyClient = nettyClient;this.imei = nettyClient.getImei();this.bizData = nettyClient.getBizData();}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.info("客户端imei={},通道激活成功", this.imei);synchronized (this.nettyClient) { //当通道激活后解锁队列线程,然后再发送消息this.nettyClient.notify();}}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {log.warn("客户端imei={},通道断开连接", this.imei);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("客户端imei={},收到消息: {}", this.imei, msg);//处理业务...if ("shutdown".equals(msg)) {this.nettyClient.close();}}@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) {IdleStateEvent e = (IdleStateEvent) evt;boolean flag = false;if (e.state() == IdleState.ALL_IDLE) {this.allIdleCounter++;log.info("客户端imei={}触发闲读或写第{}次", this.imei, this.allIdleCounter);if (this.allIdleCounter >= MAX_IDLE_TIMES) {flag = true;}}if (flag) {log.warn("读写超时达到{}次,主动断开连接", MAX_IDLE_TIMES);ctx.channel().close();}}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {log.error("客户端imei={},连接异常{}", imei, cause.getMessage(), cause);}
}
DemoClientHandler也是多实例bean,每个实例持有自己的NettyClient引用,以便在后续处理具体业务。在channelActive方法中,我们可以看到执行了客户端实例的notify方法,此处就是在客户端创建成功并且通道激活后解除wait锁的地方。channelRead方法就是我们处理服务端发送过来的消息的方法,我们的具体业务应该在该方法执行,当然不建议长时间阻塞客户端的工作线程,可以考虑异步处理。
最后我们看一下客户端缓存类。
package org.example.client;import java.util.concurrent.ConcurrentHashMap;/*** @author ReWind00* @date 2023/2/15 11:01*/
public class NettyClientHolder {private static final ConcurrentHashMap<String, NettyClient> clientMap = new ConcurrentHashMap<>();public static ConcurrentHashMap<String, NettyClient> get() {return clientMap;}}
由于netty的通道无法序列化,因此不能存入redis,只能缓存在本地内存中,其本质就是一个ConcurrentHashMap。
五、测试
package org.example.client.controller;import org.example.client.QueueHolder;
import org.example.client.model.NettyMsgModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author ReWind00* @date 2023/2/15 13:48*/
@RestController
@RequestMapping("/demo")
public class DemoController {/*** 间隔发送两条消息*/@GetMapping("testOne")public void testOne() {QueueHolder.get().offer(NettyMsgModel.create("87654321", "Hello World!"));try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}QueueHolder.get().offer(NettyMsgModel.create("87654321", "Hello World Too!"));}/*** 任意发送消息** @param imei* @param msg*/@GetMapping("testTwo")public void testTwo(@RequestParam String imei, @RequestParam String msg) {QueueHolder.get().offer(NettyMsgModel.create(imei, msg));}/*** 连续发送两条消息 第二条由于redis锁将会重新放回队列延迟消费*/@GetMapping("testThree")public void testThree() {QueueHolder.get().offer(NettyMsgModel.create("12345678", "Hello World!"));QueueHolder.get().offer(NettyMsgModel.create("12345678", "Hello World Too!"));}
}
测试接口代码如上,调用testOne,日志如下:
可以看到第一条消息触发了客户端创建流程,创建后发送了消息,而5秒后的第二条消息直接通过已有通道发送了。
测试接口代码如上,调用testTwo,日志如下:
发送shutdown可以主动断开已有连接。
测试接口代码如上,调用testThree,日志如下:
可以看到第二条消息重新入列并被延迟消费了。
六、源码
点击下载
后记
本demo项目仅作学习交流使用,如果要应用到生产环境还有些许不足,有问题的同学可以留言交流。
相关文章:

使用netty+springboot打造的tcp长连接通讯方案
文章目录项目背景正文一、项目架构二、项目模块三、业务流程四、代码详解1.消息队列2.执行类3.客户端五、测试六、源码后记项目背景 最近公司某物联网项目需要使用socket长连接进行消息通讯,捣鼓了一版代码上线,结果BUG不断,本猿寝食难安&am…...

【正点原子FPGA连载】第十章PS SYSMON测量温度电压实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南
1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id692450874670 3)全套实验源码手册视频下载地址: http://www.openedv.com/thread-340252-1-1.html 第十章PS SYSMON…...

AcWing《蓝桥杯集训·每日一题》—— 1460 我在哪?
AcWing《蓝桥杯集训每日一题》—— 1460. 我在哪? 文章目录AcWing《蓝桥杯集训每日一题》—— 1460. 我在哪?一、题目二、解题思路三、代码实现本次博客我是通过Notion软件写的,转md文件可能不太美观,大家可以去我的博客中查看&am…...

AcWing《蓝桥杯集训·每日一题》—— 3729 改变数组元素
AcWing《蓝桥杯集训每日一题》—— 3729. 改变数组元素 文章目录AcWing《蓝桥杯集训每日一题》—— 3729. 改变数组元素一、题目二、解题思路三、代码实现本次博客我是通过Notion软件写的,转md文件可能不太美观,大家可以去我的博客中查看:北天…...

如何熟练掌握Python在气象水文中的数据处理及绘图【免费教程】
pythonPython由荷兰数学和计算机科学研究学会的吉多范罗苏姆于1990年代初设计,作为一门叫做ABC语言的替代品。Python提供了高效的高级数据结构,还能简单有效地面向对象编程。Python语法和动态类型,以及解释型语言的本质,使它成为多…...

Leetcode详解JAVA版
目录1. 两数之和14. 最长公共前缀15. 三数之和18. 四数之和19. 删除链表的倒数第 N 个结点21. 合并两个有序链表28. 找出字符串中第一个匹配项的下标36. 有效的数独42. 接雨水43. 字符串相乘45. 跳跃游戏 II53. 最大子数组和54. 螺旋矩阵55. 跳跃游戏62. 不同路径70. 爬楼梯73.…...

LeetCode 83. 删除排序链表中的重复元素
原题链接 难度:easy\color{Green}{easy}easy 题目描述 给定一个已排序的链表的头 headheadhead , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 示例 1: 输入:head [1,1,2] 输出:…...

RMI简易实现(基于maven)
参考其它rmi(remote method invocation)的代码后,加入了自己思考。整个工程基于maven构建,我觉得maven的模块化可比较直观地演示rmi 目录 项目结构图 模块解读 pom文件 rmi-impl rmi-common-interface rmi-server rmi-cli…...

‘excludeSwitches‘ 的 [‘enable-logging‘] 和[‘enable-automation‘]
selenium 使用 chrome 浏览器的 chromedriver 时,可以加参数, chrome_optionswebdriver.ChromeOptions() chrome_options.add_experimental_option(excludeSwitches,[enable-logging]) chrome_options.add_experimental_option(excludeSwitches,[enable…...

华为OD机试 - 最短木板长度(Python)| 真题+思路+考点+代码+岗位
最短木板长度 题目 小明有 n n n 块木板,第 i i i(1≤ i i...

第一个Python程序-HelloWorld与Python解释器
数据来源 01 第一个Python程序-HelloWorld 1)打开cmd: windows R 打开运行窗口输入cmd 2)进入Python编写页面 输入:python 3)然后输入要写的Python代码然后回车 print("Hello World!!!") print() …...

C++数据类型
目录 一、基本的内置类型 二、typedef声明 三、枚举类型 一、基本的内置类型 C 为程序员提供了种类丰富的内置数据类型和用户自定义的数据类型。下表列出了七种基本的 C 数据类型: 类型关键字布尔型bool字符型char整型int浮点型float双浮点型double无类型void宽…...

华为OD机试 - 考古学家(Python)| 真题+思路+考点+代码+岗位
考古学家 题目 有一个考古学家发现一个石碑 但是很可惜 发现时其已经断成多段 原地发现 N 个断口整齐的石碑碎片 为了破解石碑内容 考古学家希望有程序能帮忙计算复原后的石碑文字组合数 ,你能帮忙吗 备注: 如果存在石碑碎片内容完全相同,则由于碎片间的顺序不影响复原后…...

常用调试golang的bug以及性能问题的实践方法
文章目录如何分析程序运行时间和CPU利用率情况1.shell内置time指令/usr/bin/time指令如何分析golang程序的内存使用情况?1.内存占用情况查看如何分析golang程序的CPU性能情况1.性能分析注意事项2.CPU性能分析A.Web界面查看B.使用pprof工具查看如何分析程序运行时间和…...

什么是溶血症?什么是ABO溶血?溶血检查些什么?
什么是溶血症,什么是ABO溶血?女人是O型血,男人是其他血型的夫妻配对,最担心的是胎儿溶血症。从理论上讲,只要夫妻双方血型不同,母亲一定缺乏胎儿从父亲那里遗传的抗原。当任何人接触到他们缺乏的抗原时&…...

NLP实践——知识图谱问答模型FiD
NLP实践——知识图谱问答模型FiD0. 简介1. 模型结构2. 召回3. 问答4. 结合知识的问答0. 简介 好久没有更新了,今天介绍一个知识图谱问答(KBQA)模型,在此之前我一直在用huggingface的Pipeline中提供的QA模型,非常方便但…...

MyBatis 多表关联查询
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...

《NFL橄榄球》:克利夫兰布朗·橄榄1号位
克利夫兰布朗(英语:Cleveland Browns)是一支职业美式橄榄球球队,位于俄亥俄州克利夫兰。 布朗隶属于美国全国橄榄球联盟(NFL)的北区,主场位于第一能源体育场。球队在1946年与AAFC联盟一同成立,并在1946年到…...

InstructGPT笔记
一、InstructGPT是在GPT3上微调,ChatGPT是在GPT3.5上微调 二、该论文展示了怎么样对语言模型和人类意图之间进行匹配,方法是在人类的反馈上进行微调。 **三、方法简介:**收集很多问题,使用标注工具将问题的答案写出来࿰…...

【uniapp】getOpenerEventChannel().once 接收参数无效的解决方案
uniapp项目开发跨平台应用常会遇到接收参数无效的问题,无法判断是哪里出错了,这里是讲替代的方案,现有三种方案可选。 原因 一般我们是这样处理向另一个页面传参,代码是这样写的 //... let { title, type, rank } args; uni.n…...

ELK分布式日志收集快速入门-(二)kafka进阶-快速安装可视化管理界面-(单节点部署)
目录安装前准备安装中安装成功安装前准备 安装kafka-参考博客 (10条消息) ELK分布式日志收集快速入门-(一)-kafka单体篇_康世行的博客-CSDN博客 安装zk 参考博客 (10条消息) 快速搭建-分布式远程调用框架搭建-dubbozookperspringboot demo 演示_康世行的…...

线程的创建
1. 多线程常用函数 1.1 创建一条新线程pthread_create 对此函数使用注意以下几点: 线程例程指的是:如果线程创建成功,则该线程会立即执行的函数。POSIX线程库的所有API对返回值的处理原则一致:成功返回0,失败返回错误…...

分布式之Paxos共识算法分析
写在前面 分布式共识是分布式系统中的重要内容,本文来一起看下,一种历史悠久(1998由兰伯特提出,并助其获得2003年图灵奖)的实现分布式共识的算法Paxos。Paxos主要分为两部分,Basic Paxos和Multi-Paxos,其中…...

35岁测试工程师,面临中年危机,我该如何自救...
被辞的原因 最近因故来了上海,联系上了一位许久不见的老朋友,老王;老王和我是大学同学,毕业之后他去了上海,我来到广州。因为我们大学专业关系,从12年毕业以后我们从事着相同的职业,软件自动化…...

时间轮算法概念
概述 在一些中间件中我们经常见到时间轮控制并发和熔断。 那么这个时间轮具体是什么呢,又是怎么使用的呢。 简介 其实时间轮可以简单的理解成我们日常生活中的时钟。 时钟里的指针一直在不停的转动,利用这个我们可以实现定时任务,目前lin…...

[SCTF2019]babyre 题解
对未来的真正慷慨,是把一切献给现在。 ——加缪 目录 1.查壳 2.处理花指令,找到main函数 这一操作过程可以参考下面的视频: 3.静态分析第一部分,psword1 4.静态分析第二部分,psword2 5.静态分析第五部分,psword3 6.根据ps…...

全志H3系统移植 | 移植主线最新uboot 2023.04和kernel 6.1.11到Nanopi NEO开发板
文章目录 环境说明uboot移植kernel移植rootfs移植测试环境说明 OS:Ubuntu 20.04.5 LTSGCC:arm-none-linux-gnueabihf-gcc 10.3.0编译器下载地址:Downloads | GNU-A Downloads – Arm Developer uboot移植 当前最新版本v2023.04-rc2下载地址:https://github.com/u-boot/u-…...

vue项目第四天
使用elementui tabplane组件实现历史访问记录组件的二次封装<el-tabs type"border-card"><el-tab-pane label"用户管理">用户管理</el-tab-pane><el-tab-pane label"配置管理">配置管理</el-tab-pane><el-tab-…...

「C语言进阶」数据内存的存储
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀 目录 🐰数据类型的介绍 🐰类型的意义 🐰数据类型的基本归类…...

面试必问:进程和线程的区别(从操作系统层次理解)
1.什么是进程?为什么要有进程? 进程有一个相当精简的解释:进程是对操作系统上正在运行程序的一个抽象。 这个概念确实挺抽象,仔细想想却也挺精准。 我们平常使用计算机,都会在同一时间做许多事,比如边看…...