Zookeeper選舉Leader源碼剖析

来源:https://www.cnblogs.com/chafry/archive/2022/10/20/16804987.html
-Advertisement-
Play Games

開始分析 【1】分析入口類做了什麼 //org.apache.zookeeper.server.quorum包下QuorumPeerMain類 public static void main(String[] args) { QuorumPeerMain main = new QuorumPeerM ...


 

開始分析

  【1】分析入口類做了什麼

//org.apache.zookeeper.server.quorum包下QuorumPeerMain類
public static void main(String[] args) {
    QuorumPeerMain main = new QuorumPeerMain();
    try {
        main.initializeAndRun(args);
    } catch (IllegalArgumentException e) {..} catch (ConfigException e) {..} catch (DatadirException e) {..} catch (AdminServerException e) {..} catch (Exception e) {..}

    ServiceUtils.requestSystemExit(ExitCode.EXECUTION_FINISHED.getValue());
}

protected void initializeAndRun(String[] args) throws ConfigException, IOException, AdminServerException {
    QuorumPeerConfig config = new QuorumPeerConfig();
    if (args.length == 1) {
        //解析配置文件載入到記憶體
        //主要是調用了QuorumPeerConfig類#parse方法,解析邏輯在parseProperties方法
        config.parse(args[0]);
    }

    //啟動延時的定期清理快照數據文件
    DatadirCleanupManager purgeMgr = new DatadirCleanupManager(
        config.getDataDir(),
        config.getDataLogDir(),
        config.getSnapRetainCount(),
        config.getPurgeInterval());
    purgeMgr.start();

    if (args.length == 1 && config.isDistributed()) {
        //集群的入口
        runFromConfig(config);
    } else {
        //單機的入口
        ZooKeeperServerMain.main(args);
    }
}

  【2】runFromConfig方法做了什麼

public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServerException {
    try {
        ManagedUtil.registerLog4jMBeans();
    } catch (JMException e) {
        LOG.warn("Unable to register log4j JMX control", e);
    }

    LOG.info("Starting quorum peer, myid=" + config.getServerId());
    final MetricsProvider metricsProvider;
    try {
        metricsProvider = MetricsProviderBootstrap.startMetricsProvider(
            config.getMetricsProviderClassName(),
            config.getMetricsProviderConfiguration());
    } catch (MetricsProviderLifeCycleException error) {
        throw new IOException("Cannot boot MetricsProvider " + config.getMetricsProviderClassName(), error);
    }
    try {
        ServerMetrics.metricsProviderInitialized(metricsProvider);
        ProviderRegistry.initialize();
        ServerCnxnFactory cnxnFactory = null;
        ServerCnxnFactory secureCnxnFactory = null;

        if (config.getClientPortAddress() != null) {
            //初始化服務端連接對象
            cnxnFactory = ServerCnxnFactory.createFactory();
            //設置監聽埠,從配置文件中拿
            cnxnFactory.configure(config.getClientPortAddress(), config.getMaxClientCnxns(), config.getClientPortListenBacklog(), false);
        }

        if (config.getSecureClientPortAddress() != null) {
            secureCnxnFactory = ServerCnxnFactory.createFactory();
            secureCnxnFactory.configure(config.getSecureClientPortAddress(), config.getMaxClientCnxns(), config.getClientPortListenBacklog(), true);
        }

        //構建本機節點,並將配置參數的數據傳入
        quorumPeer = getQuorumPeer();
        quorumPeer.setTxnFactory(new FileTxnSnapLog(config.getDataLogDir(), config.getDataDir()));
        quorumPeer.enableLocalSessions(config.areLocalSessionsEnabled());
        quorumPeer.enableLocalSessionsUpgrading(config.isLocalSessionsUpgradingEnabled());
        //quorumPeer.setQuorumPeers(config.getAllMembers());
        quorumPeer.setElectionType(config.getElectionAlg());
        quorumPeer.setMyid(config.getServerId());
        quorumPeer.setTickTime(config.getTickTime());
        quorumPeer.setMinSessionTimeout(config.getMinSessionTimeout());
        quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout());
        quorumPeer.setInitLimit(config.getInitLimit());
        quorumPeer.setSyncLimit(config.getSyncLimit());
        quorumPeer.setConnectToLearnerMasterLimit(config.getConnectToLearnerMasterLimit());
        quorumPeer.setObserverMasterPort(config.getObserverMasterPort());
        quorumPeer.setConfigFileName(config.getConfigFilename());
        quorumPeer.setClientPortListenBacklog(config.getClientPortListenBacklog());
        quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory()));
        quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false);
        if (config.getLastSeenQuorumVerifier() != null) {
            quorumPeer.setLastSeenQuorumVerifier(config.getLastSeenQuorumVerifier(), false);
        }
        quorumPeer.initConfigInZKDatabase();
        //將連接對象也存入本節點
        quorumPeer.setCnxnFactory(cnxnFactory);
        quorumPeer.setSecureCnxnFactory(secureCnxnFactory);
        quorumPeer.setSslQuorum(config.isSslQuorum());
        quorumPeer.setUsePortUnification(config.shouldUsePortUnification());
        quorumPeer.setLearnerType(config.getPeerType());
        quorumPeer.setSyncEnabled(config.getSyncEnabled());
        quorumPeer.setQuorumListenOnAllIPs(config.getQuorumListenOnAllIPs());
        if (config.sslQuorumReloadCertFiles) {
            quorumPeer.getX509Util().enableCertFileReloading();
        }
        quorumPeer.setMultiAddressEnabled(config.isMultiAddressEnabled());
        quorumPeer.setMultiAddressReachabilityCheckEnabled(config.isMultiAddressReachabilityCheckEnabled());
        quorumPeer.setMultiAddressReachabilityCheckTimeoutMs(config.getMultiAddressReachabilityCheckTimeoutMs());

        // sets quorum sasl authentication configurations
        quorumPeer.setQuorumSaslEnabled(config.quorumEnableSasl);
        if (quorumPeer.isQuorumSaslAuthEnabled()) {
            quorumPeer.setQuorumServerSaslRequired(config.quorumServerRequireSasl);
            quorumPeer.setQuorumLearnerSaslRequired(config.quorumLearnerRequireSasl);
            quorumPeer.setQuorumServicePrincipal(config.quorumServicePrincipal);
            quorumPeer.setQuorumServerLoginContext(config.quorumServerLoginContext);
            quorumPeer.setQuorumLearnerLoginContext(config.quorumLearnerLoginContext);
        }
        quorumPeer.setQuorumCnxnThreadsSize(config.quorumCnxnThreadsSize);
        quorumPeer.initialize();

        if (config.jvmPauseMonitorToRun) {
            quorumPeer.setJvmPauseMonitor(new JvmPauseMonitor(config));
        }
        //啟動節點
        quorumPeer.start();
        ZKAuditProvider.addZKStartStopAuditLog();
        quorumPeer.join();
    } catch (InterruptedException e) {
        // warn, but generally this is ok
        LOG.warn("Quorum Peer interrupted", e);
    } finally {
        try {
            metricsProvider.stop();
        } catch (Throwable error) {
            LOG.warn("Error while stopping metrics", error);
        }
    }
}

 

  【3】通信對象的選擇

//ServerCnxnFactory類#createFactory方法
//初始化通信對象
public static ServerCnxnFactory createFactory() throws IOException {
    //屬性值展示:String ZOOKEEPER_SERVER_CNXN_FACTORY = "zookeeper.serverCnxnFactory"
    //官方推薦netty:則應該是ServerCnxnFactory類的子類NettyServerCnxnFactory
    String serverCnxnFactoryName = System.getProperty(ZOOKEEPER_SERVER_CNXN_FACTORY);
    if (serverCnxnFactoryName == null) {
        //但是預設是子類NIOServerCnxnFactory
        serverCnxnFactoryName = NIOServerCnxnFactory.class.getName();
    }
    try {
        //利用反射進行初始化
        ServerCnxnFactory serverCnxnFactory = (ServerCnxnFactory) Class.forName(serverCnxnFactoryName).getDeclaredConstructor().newInstance();
        LOG.info("Using {} as server connection factory", serverCnxnFactoryName);
        return serverCnxnFactory;
    } catch (Exception e) {
        IOException ioe = new IOException("Couldn't instantiate " + serverCnxnFactoryName, e);
        throw ioe;
    }
}

 

  【4】記憶體資料庫的設計

//org.apache.zookeeper.server包下DataTree類
//節點數據是final NodeHashMap nodes;
public class DataTree {

    private static final Logger LOG = LoggerFactory.getLogger(DataTree.class);

    private final RateLogger RATE_LOGGER = new RateLogger(LOG, 15 * 60 * 1000);

    //該映射提供了對datanode的快速查找
    private final NodeHashMap nodes;

    private IWatchManager dataWatches;

    private IWatchManager childWatches;

    //緩存所有datanode的路徑和數據的總大小
    private final AtomicLong nodeDataSize = new AtomicLong(0);

    //根結點
    private static final String rootZookeeper = "/";

    private static final String procZookeeper = Quotas.procZookeeper;

    private static final String procChildZookeeper = procZookeeper.substring(1);

    private static final String quotaZookeeper = Quotas.quotaZookeeper;

    private static final String quotaChildZookeeper = quotaZookeeper.substring(procZookeeper.length() + 1);

    private static final String configZookeeper = ZooDefs.CONFIG_NODE;

    private static final String configChildZookeeper = configZookeeper.substring(procZookeeper.length() + 1);

    private final PathTrie pTrie = new PathTrie();

    public static final int STAT_OVERHEAD_BYTES = (6 * 8) + (5 * 4);

    private final Map<Long, HashSet<String>> ephemerals = new ConcurrentHashMap<Long, HashSet<String>>();

    private final Set<String> containers = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());

    private final Set<String> ttls = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());

    private final ReferenceCountedACLCache aclCache = new ReferenceCountedACLCache();

    public static final int DIGEST_LOG_LIMIT = 1024;

    public static final int DIGEST_LOG_INTERVAL = 128;

    private ZxidDigest digestFromLoadedSnapshot;

    private volatile ZxidDigest lastProcessedZxidDigest;

    private boolean firstMismatchTxn = true;

    private final List<DigestWatcher> digestWatchers = new ArrayList<>();

    private LinkedList<ZxidDigest> digestLog = new LinkedList<>();

    private final DigestCalculator digestCalculator;

}

public class DataNode implements Record {

    private volatile long digest;

    // 指示該節點的摘要是否是最新的
    volatile boolean digestCached;

    byte[] data;

    Long acl;

    public StatPersisted stat;

    private Set<String> children = null;

    private static final Set<String> EMPTY_SET = Collections.emptySet();
}

 

  【5】quorumPeer.start()節點啟動方法又做了什麼

@Override
public synchronized void start() {
    if (!getView().containsKey(myid)) {
        throw new RuntimeException("My id " + myid + " not in the peer list");
    }
    //載入快照文件數據到記憶體
    loadDataBase();
    //啟動通信對象
    startServerCnxnFactory();
    try {
        //JettyAdminServer,啟動內嵌Jetty服務,預設8080埠
        adminServer.start();
    } catch (AdminServerException e) {
        LOG.warn("Problem starting AdminServer", e);
    }
    //初始化選舉數據
    startLeaderElection();
    startJvmPauseMonitor();
    super.start();
}

private void startServerCnxnFactory() {
    if (cnxnFactory != null) {
        //如果有配置netty通信,則NettyServerCnxnFactory類#start方法
        cnxnFactory.start();
    }
    if (secureCnxnFactory != null) {
        secureCnxnFactory.start();
    }
}

//NettyServerCnxnFactory類#start方法
@Override
public void start() {
    if (listenBacklog != -1) {
        bootstrap.option(ChannelOption.SO_BACKLOG, listenBacklog);
    }
    LOG.info("binding to port {}", localAddress);
    parentChannel = bootstrap.bind(localAddress).syncUninterruptibly().channel();
    // Port changes after bind() if the original port was 0, update
    // localAddress to get the real port.
    localAddress = (InetSocketAddress) parentChannel.localAddress();
    LOG.info("bound to port {}", getLocalPort());
}

//選舉數據構建
public synchronized void startLeaderElection() {
    try {
        if (getPeerState() == ServerState.LOOKING) {
            //構建選票,myid伺服器id標記,最大的事務id,當前伺服器的選舉輪次
            currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
        }
    } catch (IOException e) {
        RuntimeException re = new RuntimeException(e.getMessage());
        re.setStackTrace(e.getStackTrace());
        throw re;
    }
    //確定選舉演算法,預設傳的是3
    this.electionAlg = createElectionAlgorithm(electionType);
}

//節點狀態
public enum ServerState {
    LOOKING,    //等待狀態
    FOLLOWING,  //從節點
    LEADING,    //主節點
    OBSERVING    //觀察狀態
}

 

  【6】選舉演算法分析(內涵多層隊列架構

//選舉演算法分析(3.8版本已經將過時的演算法去除了)
protected Election createElectionAlgorithm(int electionAlgorithm) {
    Election le = null;

    //TODO: use a factory rather than a switch
    switch (electionAlgorithm) {
    case 1:
        throw new UnsupportedOperationException("Election Algorithm 1 is not supported.");
    case 2:
        throw new UnsupportedOperationException("Election Algorithm 2 is not supported.");
    case 3:
        QuorumCnxManager qcm = createCnxnManager();
        QuorumCnxManager oldQcm = qcmRef.getAndSet(qcm);
        if (oldQcm != null) {
            LOG.warn("Clobbering already-set QuorumCnxManager (restarting leader election?)");
            oldQcm.halt();
        }
        QuorumCnxManager.Listener listener = qcm.listener;
        if (listener != null) {
            //啟動監聽線程
            listener.start();
            //構建收發消息線程
            FastLeaderElection fle = new FastLeaderElection(this, qcm);
            fle.start();
            le = fle;
        } else {
            LOG.error("Null listener when initializing cnx manager");
        }
        break;
    default:
        assert false;
    }
    return le;
}

 

  【7】翻閱監聽線程 listener 做了什麼

//翻閱監聽線程做了什麼,主要是看run方法
@Override
public void run() {
    if (!shutdown) {
        LOG.debug("Listener thread started, myId: {}", self.getId());
        Set<InetSocketAddress> addresses;

        if (self.getQuorumListenOnAllIPs()) {
            addresses = self.getElectionAddress().getWildcardAddresses();
        } else {
            addresses = self.getElectionAddress().getAllAddresses();
        }

        CountDownLatch latch = new CountDownLatch(addresses.size());
        //迴圈的方式針對每個地址構建一個ListenerHandler
        listenerHandlers = addresses.stream().map(address ->
                        new ListenerHandler(address, self.shouldUsePortUnification(), self.isSslQuorum(), latch))
                .collect(Collectors.toList());

        //針對每個ListenerHandler都會有一個對應的線程進行處理(線程池)
        final ExecutorService executor = Executors.newFixedThreadPool(addresses.size());
        try {
            listenerHandlers.forEach(executor::submit);
        } finally {
            // prevent executor's threads to leak after ListenerHandler tasks complete
            executor.shutdown();
        }

        try {
            latch.await();
        } catch (InterruptedException ie) {..} finally {
            // Clean up for shutdown.
            for (ListenerHandler handler : listenerHandlers) {
                try {
                    handler.close();
                } catch (IOException ie) {...}
            }
        }
    }

    LOG.info("Leaving listener");
    if (!shutdown) {
        if (socketException.get()) {
            // After leaving listener thread, the host cannot join the quorum anymore,
            // this is a severe error that we cannot recover from, so we need to exit
            socketBindErrorHandler.run();
        }
    }
}

class ListenerHandler implements Runnable, Closeable {
    private ServerSocket serverSocket;
    private InetSocketAddress address;
    private boolean portUnification;
    private boolean sslQuorum;
    private CountDownLatch latch;

    ListenerHandler(InetSocketAddress address, boolean portUnification, boolean sslQuorum,CountDownLatch latch) {
        this.address = address;
        this.portUnification = portUnification;
        this.sslQuorum = sslQuorum;
        this.latch = latch;
    }

    /**
     * Sleeps on acceptConnections().
     */
    @Override
    public void run() {
        try {
            Thread.currentThread().setName("ListenerHandler-" + address);
            //建立連接
            acceptConnections();
            try {
                close();
            } catch (IOException e) {...}
        } catch (Exception e) {...} finally {
            latch.countDown();
        }
    }

    @Override
    public synchronized void close() throws IOException {
        if (serverSocket != null && !serverSocket.isClosed()) {
            LOG.debug("Trying to close listeners: {}", serverSocket);
            serverSocket.close();
        }
    }

    /**
     * Sleeps on accept().
     */
    private void acceptConnections() {
        int numRetries = 0;
        Socket client = null;

        while ((!shutdown) && (portBindMaxRetry == 0 || numRetries < portBindMaxRetry)) {
            try {
                //創建serverSocket
                serverSocket = createNewServerSocket();
                LOG.info("{} is accepting connections now, my election bind port: {}", QuorumCnxManager.this.mySid, address.toString());
                while (!shutdown) {
                    try {
                        client = serverSocket.accept();
                        setSockOpts(client);
              //處理連接消息
                        if (quorumSaslAuthEnabled) {
                            receiveConnectionAsync(client);
                        } else {
                            receiveConnection(client);
                        }
                        numRetries = 0;
                    } catch (SocketTimeoutException e) {...}
                }
            } catch (IOException e) {
                if (shutdown) {
                    break;
                }

                if (e instanceof SocketException) {
                    socketException.set(true);
                }

                numRetries++;
                try {
                    close();
                    Thread.sleep(1000);
                } catch (IOException ie) {...} catch (InterruptedException ie) {...}
                closeSocket(client);
            }
        }
        if (!shutdown) {...}
    }

    private ServerSocket createNewServerSocket() throws IOException {
        ServerSocket socket;

        if (portUnification) {
            LOG.info("Creating TLS-enabled quorum server socket");
            socket = new UnifiedServerSocket(self.getX509Util(), true);
        } else if (sslQuorum) {
            LOG.info("Creating TLS-only quorum server socket");
            socket = new UnifiedServerSocket(self.getX509Util(), false);
        } else {
            socket = new ServerSocket();
        }

        socket.setReuseAddress(true);
        address = new InetSocketAddress(address.getHostString(), address.getPort());
        //綁定地址與埠
        socket.bind(address);

        return socket;
    }
}

 

  【7.1】receiveConnection方法怎麼處理接收到的消息

public void receiveConnection(final Socket sock) {
    DataInputStream din = null;
    try {
        din = new DataInputStream(new BufferedInputStream(sock.getInputStream()));

        handleConnection(sock, din);
    } catch (IOException e) {
        closeSocket(sock);
    }
}

private void handleConnection(Socket sock, DataInputStream din) throws IOException {
    Long sid = null, protocolVersion = null;
    MultipleAddresses electionAddr = null;

    try {
        // 從輸入流中讀入一個Long(實際上是服務的ID)
        protocolVersion = din.readLong();
        if (protocolVersion >= 0) { // this is a server id and not a protocol version
            sid = protocolVersion;
        } else {
            try {
                InitialMessage init = InitialMessage.parse(protocolVersion, din);
                sid = init.sid;
                if (!init.electionAddr.isEmpty()) {
                    electionAddr = new MultipleAddresses(init.electionAddr, Duration.ofMillis(self.getMultiAddressReachabilityCheckTimeoutMs()));
                }
            } catch (InitialMessage.InitialMessageException ex) {
                closeSocket(sock);
                return;
            }
        }

        if (sid == QuorumPeer.OBSERVER_ID) {
            sid = observerCounter.getAndDecrement();
        }
    } catch (IOException e) {
        closeSocket(sock);
        return;
    }

    // do authenticating learner
    authServer.authenticate(sock, din);
    //關閉不必要的連接
    //因為socket是雙工的,而之前我們是針對了每個服務都要與之建立連接(則有,我連它【自身發起的連接】,它連了我【對方發起的連接】)
    //說白了兩條通道有一條不是必要的
    if (sid < self.getId()) { //對方的id小於自身id
        SendWorker sw = senderWorkerMap.get(sid);
        if (sw != null) {
            sw.finish();
        }
        // 關閉當前連接
        closeSocket(sock);
        // 創建當前節點到對面節點的連接
        if (electionAddr != null) {
            connectOne(sid, electionAddr);
        } else {
            connectOne(sid);
        }

    } 
    //自身的話不需要做什麼
    else if (sid == self.getId()) {...} 
    else { // 對方id大於自身id
        // 使用目標節點到當前節點的連接
        SendWorker sw = new SendWorker(sock, sid);
        RecvWorker rw = new RecvWorker(sock, din, sid, sw);
        sw.setRecv(rw);

        SendWorker vsw = senderWorkerMap.get(sid);

        if (vsw != null) {
            vsw.finish();
        }
        //更新senderWorker與queueSend
        senderWorkerMap.put(sid, sw);
        queueSendMap.putIfAbsent(sid, new CircularBlockingQueue<>(SEND_CAPACITY));

        sw.start();
        rw.start();
    }
}

// 創建當前節點到對面節點的連接
synchronized boolean connectOne(long sid, MultipleAddresses electionAddr) {
    // 判斷連接是否已經存在
    if (senderWorkerMap.get(sid) != null) {
        if (self.isMultiAddressEnabled() && electionAddr.size() > 1 && self.isMultiAddressReachabilityCheckEnabled()) {
            senderWorkerMap.get(sid).asyncValidateIfSocketIsStillReachable();
        }
        return true;
    }
    //初始化連接
    return initiateConnectionAsync(electionAddr, sid);
}

public boolean initiateConnectionAsync(final MultipleAddresses electionAddr, final Long sid) {
    if (!inprogressConnections.add(sid)) {
        return true;
    }
    try {
        connectionExecutor.execute(new QuorumConnectionReqThread(electionAddr, sid));
        connectionThreadCnt.incrementAndGet();
    } catch (Throwable e) {
        inprogressConnections.remove(sid);
        return false;
    }
    return true;
}

//QuorumConnectionReqThread類#run方法
@Override
public void run() {
    try {
        initiateConnection(electionAddr, sid);
    } finally {
        inprogressConnections.remove(sid);
    }
}
//真正建立socket連接
public void initiateConnection(final MultipleAddresses electionAddr, final Long sid) {
    Socket sock = null;
    try {
        if (self.isSslQuorum()) {
            sock = self.getX509Util().createSSLSocket();
        } else {
            sock = SOCKET_FACTORY.get();
        }
        setSockOpts(sock);
        sock.connect(electionAddr.getReachableOrOne(), cnxTO);
        if (sock instanceof SSLSocket) {
            SSLSocket sslSock = (SSLSocket) sock;
            sslSock.startHandshake();
        }
    } catch (X509Exception e) {
        closeSocket(sock);
        return;
    } catch (UnresolvedAddressException | IOException e) {
        closeSocket(sock);
        return;
    }

    try {
        startConnection(sock, sid);
    } catch (IOException e) {
        closeSocket(sock);
    }
}

private boolean startConnection(Socket sock, Long sid) throws IOException {
    DataOutputStream dout = null;
    DataInputStream din = null;
    try {
        BufferedOutputStream buf = new BufferedOutputStream(sock.getOutputStream());
        dout = new DataOutputStream(buf);

        long protocolVersion = self.isMultiAddressEnabled() ? PROTOCOL_VERSION_V2 : PROTOCOL_VERSION_V1;
        dout.writeLong(protocolVersion);
        dout.writeLong(self.getId());

        // now we send our election address. For the new protocol version, we can send multiple addresses.
        Collection<InetSocketAddress> addressesToSend = protocolVersion == PROTOCOL_VERSION_V2
                ? self.getElectionAddress().getAllAddresses()
                : Arrays.asList(self.getElectionAddress().getOne());

        String addr = addressesToSend.stream()
                .map(NetUtils::formatInetAddr).collect(Collectors.joining("|"));
        byte[] addr_bytes = addr.getBytes();
        dout.writeInt(addr_bytes.length);
        dout.write(addr_bytes);
        dout.flush();


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • LRU:最近最少使用緩存 LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換演算法,選擇最近最久未使用的頁面予以淘汰。該演算法賦予每個頁面一個訪問欄位,用來記錄一個頁面自上次被訪問以來所經歷的時間 t,當須淘汰一個頁面時,選擇現有頁面中其 t 值最大的,即最近最少 ...
  • 過濾組件 查詢所有才涉及到過濾,其他介面都不需要 restful規範中有一條,請求地址中帶過濾條件:分頁、排序、過濾統稱為過濾 內置過濾類 使用內置過濾類的步驟 必須是繼承GenericAPIView+ListModelMixin的之類視圖上 1.配置過濾類 filter_backends=[Sea ...
  • 兄弟們,為了幫助大家更加高效的摸魚,今天分享一個騷操作,Python自動安裝第三方庫! 為了體現小編在懶上的造就,今天再分享一個騷操作:Python自動安裝第三方庫,全自動不需要你動! pip手動安裝 一說Python要安裝哪個模塊,我們第一反應,win+r輸入cmd,pip instll 安裝~ ...
  • SpringIOC源碼 Spring源碼大綱 https://www.processon.com/view/link/5f5075c763768959e2d109df IOC載入流程圖 https://www.processon.com/view/link/5f15341b07912906d9ae8 ...
  • 開始前有必要說一下,這才第二題就碰到了爛尾題,以自己開始的思路交了n次,錯了n詞,最後才19分,後來看了一下大佬的c++代碼(盡然沒有c的代碼),還好c和c++的差別不是特別大,仔細琢磨一遍後突然發現很多地方可以改進,整理思路在此嘗試終於AC,這才第二題啊。。。。 This time, you ar ...
  • C語言實現 順序表的存儲結構實現棧 代碼: #include <stdlib.h> #include <stdio.h> #define STACK_INIT_SIZE 100 //棧初始開闢空間大小 #define STACK_INCREMENT 10 //棧追加空間大小 //棧的結構體 type ...
  • 1.準備工作 1.在文件里找到設置 2.在項目里找到python解釋器,點擊右邊的加號 3.搜素pygame並安裝 同理下載pgzero安裝包 2.開始製作 1.創建一個小球 import pgzrun def draw(): screen.fill('green') screen.draw.fil ...
  • 許可權類 主要用途:用戶登錄了,某個介面可能只有超級管理員才能訪問,普通用戶不能訪問 案列:出版社的所有介面,必須登錄,而且是超級管理員才能訪問 分析步驟 第一步:寫一個類,繼承BasePermission 第二步:重寫has_permission方法 第三步:在方法校驗用戶時候有許可權(request ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...