Interviewer: how does the bottom layer of netty implement the heartbeat mechanism and disconnection reconnection of the service? I'm confused

Zhu Xiaosi 2021-01-21 11:21:55
interviewer layer netty implement heartbeat


Click on the above “ Zhu Xiaosi's blog ”, choice “ Set to star ”

The background to reply " book ", obtain

The background to reply “k8s”, Can claim k8s Information

heartbeat

What is heartbeat

So-called heartbeat , That is to say  TCP  Long connected , A special packet that is sent periodically between the client and the server , Inform the other party that they are still online , In order to ensure that  TCP  Validity of connection .

notes : The heartbeat pack has another function , Often overlooked , namely : If a connection is not used for a long time , The firewall or router will disconnect the connection .

How to achieve

The core Handler —— IdleStateHandler

stay  Netty  in , The key to realizing the heartbeat mechanism is  IdleStateHandler, So this  Handler  How to use it ? Let's take a look at its constructors first :

public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
    this((long)readerIdleTimeSeconds, (long)writerIdleTimeSeconds, (long)allIdleTimeSeconds, TimeUnit.SECONDS);
}

Here's what the next three parameters mean :

  • readerIdleTimeSeconds: Read timeout . That is, when there is no change from  Channel  When reading data , It will trigger a  READER_IDLE  Of  IdleStateEvent  event .

  • writerIdleTimeSeconds: Write timeout . That is, when no data is written to within the specified time interval  Channel  when , It will trigger a  WRITER_IDLE  Of  IdleStateEvent  event .

  • allIdleTimeSeconds: read / Write timeout . That is, when there is no read or write operation within the specified time interval , It will trigger a  ALL_IDLE  Of  IdleStateEvent  event .

notes : The default time unit of these three parameters is second . If other time units need to be specified , You can use another construction method :IdleStateHandler(boolean observeOutput, long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit)

Before looking at the implementation below , It's suggested to know about IdleStateHandler Implementation principle of .

Let's go straight to the code , What to pay attention to , It will be explained by comments in the code .

Use IdleStateHandler Achieve heartbeat

The following will be used IdleStateHandler To achieve heartbeat ,Client End to Server After end , Will cycle through a task : Wait a few seconds at random , then ping once Server End , Send a heartbeat packet . When the waiting time exceeds the specified time , Will send failed , think Server The end has been disconnected before that . The code is as follows :

Client End

ClientIdleStateTrigger —— Heartbeat trigger

class ClientIdleStateTrigger Also a Handler, It's just rewritten userEventTriggered Method , Used to capture IdleState.WRITER_IDLE event ( Data was not sent to the server within the specified time ), And then to Server The client sends a heartbeat packet .

/**
 * <p>
 *   Used to capture {@link IdleState#WRITER_IDLE} event ( Data was not sent to the server within the specified time ), And then to <code>Server</code> The client sends a heartbeat packet .
 * </p>
 */
public class ClientIdleStateTrigger extends ChannelInboundHandlerAdapter {
    public static final String HEART_BEAT = "heart beat!";
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.WRITER_IDLE) {
                // write heartbeat to server
                ctx.writeAndFlush(HEART_BEAT);
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}
Pinger —— Heartbeat transmitter
/**
 * <p> After the client connects to the server , Will cycle through a task : Wait a few seconds at random , then ping once Server End , Send a heartbeat packet .</p>
 */
public class Pinger extends ChannelInboundHandlerAdapter {
    private Random random = new Random();
    private int baseRandom = 8;
    private Channel channel;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        this.channel = ctx.channel();
        ping(ctx.channel());
    }
    private void ping(Channel channel) {
        int second = Math.max(1, random.nextInt(baseRandom));
        System.out.println("next heart beat will send after " + second + "s.");
        ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                if (channel.isActive()) {
                    System.out.println("sending heart beat to the server...");
                    channel.writeAndFlush(ClientIdleStateTrigger.HEART_BEAT);
                } else {
                    System.err.println("The connection had broken, cancel the task that will send a heart beat.");
                    channel.closeFuture();
                    throw new RuntimeException();
                }
            }
        }, second, TimeUnit.SECONDS);
        future.addListener(new GenericFutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                if (future.isSuccess()) {
                    ping(channel);
                }
            }
        });
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //  When Channel When it's disconnected ,  Still sending data ,  Would throw exceptions ,  This method will be called .
        cause.printStackTrace();
        ctx.close();
    }
}
ClientHandlersInitializer —— Initialization class for the collection of client processors
public class ClientHandlersInitializer extends ChannelInitializer<SocketChannel> {
    private ReconnectHandler reconnectHandler;
    private EchoHandler echoHandler;
    public ClientHandlersInitializer(TcpClient tcpClient) {
        Assert.notNull(tcpClient, "TcpClient can not be null.");
        this.reconnectHandler = new ReconnectHandler(tcpClient);
        this.echoHandler = new EchoHandler();
    }
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new Pinger());
    }
}

notes : above Handler aggregate , except Pinger, Others are codecs and solutions to sticky packages , You can ignore .

TcpClient —— TCP Connected clients
public class TcpClient {
    private String host;
    private int port;
    private Bootstrap bootstrap;
    /**  take <code>Channel</code> Save up ,  Can be used in other non handler Send data from the same place  */
    private Channel channel;
    public TcpClient(String host, int port) {
        this(host, port, new ExponentialBackOffRetry(1000, Integer.MAX_VALUE, 60 * 1000));
    }
    public TcpClient(String host, int port, RetryPolicy retryPolicy) {
        this.host = host;
        this.port = port;
        init();
    }
    /**
     *  To remote TCP The server requests a connection
     */
    public void connect() {
        synchronized (bootstrap) {
            ChannelFuture future = bootstrap.connect(host, port);
            this.channel = future.channel();
        }
    }
    private void init() {
        EventLoopGroup group = new NioEventLoopGroup();
        // bootstrap  reusable ,  Just in TcpClient It can be initialized during instantiation .
        bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ClientHandlersInitializer(TcpClient.this));
    }
    public static void main(String[] args) {
        TcpClient tcpClient = new TcpClient("localhost", 2222);
        tcpClient.connect();
    }
}

Server End

ServerIdleStateTrigger —— Disconnect trigger
/**
 * <p> No packets from the client are received within the specified time ,  Will actively disconnect the connection </p>
 */
public class ServerIdleStateTrigger extends ChannelInboundHandlerAdapter {
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.READER_IDLE) {
                //  The uplink data of the client is not received within the specified time ,  Active disconnection
                ctx.disconnect();
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}
ServerBizHandler —— The service processor on the server side
/**
 * <p> After receiving a packet from the client ,  Print it directly on the console .</p>
 */
@ChannelHandler.Sharable
public class ServerBizHandler extends SimpleChannelInboundHandler<String> {
    private final String REC_HEART_BEAT = "I had received the heart beat!";
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String data) throws Exception {
        try {
            System.out.println("receive data: " + data);
//            ctx.writeAndFlush(REC_HEART_BEAT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Established connection with the remote client.");
        // do something
        ctx.fireChannelActive();
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Disconnected with the remote client.");
        // do something
        ctx.fireChannelInactive();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
ServerHandlerInitializer —— The initialization class of the server-side processor collection
/**
 * <p> It is used to initialize all the <code>Handler</code></p>
 */
public class ServerHandlerInitializer extends ChannelInitializer<SocketChannel> {
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(5, 0, 0));
        ch.pipeline().addLast("idleStateTrigger", new ServerIdleStateTrigger());
        ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
        ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4));
        ch.pipeline().addLast("decoder", new StringDecoder());
        ch.pipeline().addLast("encoder", new StringEncoder());
        ch.pipeline().addLast("bizHandler", new ServerBizHandler());
    }
}

notes :new IdleStateHandler(5, 0, 0) The handler Represents if in 5 No packets were received from the client in seconds ( Including but not limited to heartbeat package ), Will actively disconnect from the client .

TcpServer —— Server side
public class TcpServer {
    private int port;
    private ServerHandlerInitializer serverHandlerInitializer;
    public TcpServer(int port) {
        this.port = port;
        this.serverHandlerInitializer = new ServerHandlerInitializer();
    }
    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(this.serverHandlerInitializer);
            //  Binding port , Start receiving incoming connections
            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("Server start listen at " + port);
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws Exception {
        int port = 2222;
        new TcpServer(port).start();
    }
}

thus , All the code has been written .

test

Start the client first , Then start the server . After startup , On the client's console , You can see that the log is printed as follows :

Log output from client console

On the server side, you can see that the console outputs a log similar to the following :

img

The log output from the server console

You can see , Client sending 4 After a heartbeat package , The first 5 Because of the long waiting time , When it's actually sent , Found that the connection has been disconnected ; And the server receives 4 After a heartbeat packet , Can't wait for the next packet , So decisively disconnect the connection .

During the test , It is possible that :

Abnormal situation

The reason for this is : When the connection is disconnected , Still sending heartbeat packets to the server . Although it will be used to determine whether the connection is available before sending heartbeat packets , But it is also possible to judge that the result is available at the last moment , But the next moment before sending a packet , The connection broke .

There is no elegant solution to deal with this situation , If you have a good solution , Also hope not hesitate to grant advice . bese !!!

Break line reconnection

Disconnection and reconnection is just a little more , I believe you all know what's going on . Here is the general idea , And then go straight to the code .

Realize the idea

After the client detects the disconnection from the server , Or you can't connect in the first place , Use the specified reconnection policy to perform the reconnection operation , Until the connection is reestablished or the number of retries is exhausted .

How to monitor whether the connection is disconnected , By rewriting ChannelInboundHandler#channelInactive To achieve , But the connection is not available , This method will be triggered , So we just need to do the reconnection work in this method .

Code implementation

notes : The following code is modified on the basis of the above heartbeat mechanism / Added .

Because disconnection and reconnection is the client's job , So just modify the client code .

Retrying strategy

RetryPolicy —— Retrying policy interface

public interface RetryPolicy {
    /**
     * Called when an operation has failed for some reason. This method should return
     * true to make another attempt.
     *
     * @param retryCount the number of times retried so far (0 the first time)
     * @return true/false
     */
    boolean allowRetry(int retryCount);
    /**
     * get sleep time in ms of current retry count.
     *
     * @param retryCount current retry count
     * @return the time to sleep
     */
    long getSleepTimeMs(int retryCount);
}

ExponentialBackOffRetry —— The default implementation of the reconnection policy

/**
 * <p>Retry policy that retries a set number of times with increasing sleep time between retries</p>
 */
public class ExponentialBackOffRetry implements RetryPolicy {
    private static final int MAX_RETRIES_LIMIT = 29;
    private static final int DEFAULT_MAX_SLEEP_MS = Integer.MAX_VALUE;
    private final Random random = new Random();
    private final long baseSleepTimeMs;
    private final int maxRetries;
    private final int maxSleepMs;
    public ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries) {
        this(baseSleepTimeMs, maxRetries, DEFAULT_MAX_SLEEP_MS);
    }
    public ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs) {
        this.maxRetries = maxRetries;
        this.baseSleepTimeMs = baseSleepTimeMs;
        this.maxSleepMs = maxSleepMs;
    }
    @Override
    public boolean allowRetry(int retryCount) {
        if (retryCount < maxRetries) {
            return true;
        }
        return false;
    }
    @Override
    public long getSleepTimeMs(int retryCount) {
        if (retryCount < 0) {
            throw new IllegalArgumentException("retries count must greater than 0.");
        }
        if (retryCount > MAX_RETRIES_LIMIT) {
            System.out.println(String.format("maxRetries too large (%d). Pinning to %d", maxRetries, MAX_RETRIES_LIMIT));
            retryCount = MAX_RETRIES_LIMIT;
        }
        long sleepMs = baseSleepTimeMs * Math.max(1, random.nextInt(1 << retryCount));
        if (sleepMs > maxSleepMs) {
            System.out.println(String.format("Sleep extension too large (%d). Pinning to %d", sleepMs, maxSleepMs));
            sleepMs = maxSleepMs;
        }
        return sleepMs;
    }
}

ReconnectHandler—— Reconnecting the processor

@ChannelHandler.Sharable
public class ReconnectHandler extends ChannelInboundHandlerAdapter {
    private int retries = 0;
    private RetryPolicy retryPolicy;
    private TcpClient tcpClient;
    public ReconnectHandler(TcpClient tcpClient) {
        this.tcpClient = tcpClient;
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Successfully established a connection to the server.");
        retries = 0;
        ctx.fireChannelActive();
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (retries == 0) {
            System.err.println("Lost the TCP connection with the server.");
            ctx.close();
        }
        boolean allowRetry = getRetryPolicy().allowRetry(retries);
        if (allowRetry) {
            long sleepTimeMs = getRetryPolicy().getSleepTimeMs(retries);
            System.out.println(String.format("Try to reconnect to the server after %dms. Retry count: %d.", sleepTimeMs, ++retries));
            final EventLoop eventLoop = ctx.channel().eventLoop();
            eventLoop.schedule(() -> {
                System.out.println("Reconnecting ...");
                tcpClient.connect();
            }, sleepTimeMs, TimeUnit.MILLISECONDS);
        }
        ctx.fireChannelInactive();
    }
    private RetryPolicy getRetryPolicy() {
        if (this.retryPolicy == null) {
            this.retryPolicy = tcpClient.getRetryPolicy();
        }
        return this.retryPolicy;
    }
}

ClientHandlersInitializer

On the basis of the previous , Add a reconnection processor ReconnectHandler.

public class ClientHandlersInitializer extends ChannelInitializer<SocketChannel> {
    private ReconnectHandler reconnectHandler;
    private EchoHandler echoHandler;
    public ClientHandlersInitializer(TcpClient tcpClient) {
        Assert.notNull(tcpClient, "TcpClient can not be null.");
        this.reconnectHandler = new ReconnectHandler(tcpClient);
        this.echoHandler = new EchoHandler();
    }
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(this.reconnectHandler);
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new Pinger());
    }
}

TcpClient

Add reconnection to the previous one 、 Reconnection strategy support .

public class TcpClient {
    private String host;
    private int port;
    private Bootstrap bootstrap;
    /**  Reconnection strategy  */
    private RetryPolicy retryPolicy;
    /**  take <code>Channel</code> Save up ,  Can be used in other non handler Send data from the same place  */
    private Channel channel;
    public TcpClient(String host, int port) {
        this(host, port, new ExponentialBackOffRetry(1000, Integer.MAX_VALUE, 60 * 1000));
    }
    public TcpClient(String host, int port, RetryPolicy retryPolicy) {
        this.host = host;
        this.port = port;
        this.retryPolicy = retryPolicy;
        init();
    }
    /**
     *  To remote TCP The server requests a connection
     */
    public void connect() {
        synchronized (bootstrap) {
            ChannelFuture future = bootstrap.connect(host, port);
            future.addListener(getConnectionListener());
            this.channel = future.channel();
        }
    }
    public RetryPolicy getRetryPolicy() {
        return retryPolicy;
    }
    private void init() {
        EventLoopGroup group = new NioEventLoopGroup();
        // bootstrap  reusable ,  Just in TcpClient It can be initialized during instantiation .
        bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ClientHandlersInitializer(TcpClient.this));
    }
    private ChannelFutureListener getConnectionListener() {
        return new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    future.channel().pipeline().fireChannelInactive();
                }
            }
        };
    }
    public static void main(String[] args) {
        TcpClient tcpClient = new TcpClient("localhost", 2222);
        tcpClient.connect();
    }
}

test

Before the test , To avoid  Connection reset by peer  abnormal , It can be slightly modified Pinger Of ping() Method , add to if (second == 5) Condition judgment of . as follows :

private void ping(Channel channel) {
        int second = Math.max(1, random.nextInt(baseRandom));
        if (second == 5) {
            second = 6;
        }
        System.out.println("next heart beat will send after " + second + "s.");
        ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                if (channel.isActive()) {
                    System.out.println("sending heart beat to the server...");
                    channel.writeAndFlush(ClientIdleStateTrigger.HEART_BEAT);
                } else {
                    System.err.println("The connection had broken, cancel the task that will send a heart beat.");
                    channel.closeFuture();
                    throw new RuntimeException();
                }
            }
        }, second, TimeUnit.SECONDS);
        future.addListener(new GenericFutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                if (future.isSuccess()) {
                    ping(channel);
                }
            }
        });
    }

Start client

Just start the client first , Watch the console output , You can see logs like this :

Disconnection and reconnection test —— Client console output

You can see , When the client finds that it cannot connect to the server , So keep trying to reconnect . As the number of retries increases , The larger the retry interval , But I don't want to grow infinitely , So we need to set a threshold , such as 60s. As shown in the figure above , The next retry time is longer than 60s when , prints Sleep extension too large(*). Pinning to 60000, Unit is ms. This sentence means , The calculated time exceeds the threshold (60s), So reset the real sleep time to the threshold (60s).

Start the server side

Then start the server side , Then continue to watch the client console output .

img

Disconnection and reconnection test —— After the server starts, the client console outputs

You can see , In the 9 After failed retries , The first 10 Before I try again , Server started , So the first 10 The result of secondary reconnection is , Successfully connected to the server . Next, because it's still an irregular server , So there is disconnection and reconnection 、 The cycle of disconnection and reconnection .

Expand

In different environments , There may be different reconnection needs . There are different reconnection needs , Just do it yourself RetryPolicy Interface , And then in creating TcpClient You can override the default reconnection policy when you want to .

source | https://urlify.cn/QfYNFz

Want to know more ? sweep Trace the QR code below and follow me

The background to reply " technology ", Join the technology group

The background to reply “k8s”, Can claim k8s Information

【 Highlights 】

Point a praise + Looking at , Less bug ????

版权声明
本文为[Zhu Xiaosi]所创,转载请带上原文链接,感谢
https://javamana.com/2021/01/20210121112036795I.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云