Scénario d'application le plus typique de Zookeeper (théorique + pratique)

Pile technologique Java 2021-10-14 09:10:32
sc nario application le typique


1.Préface

J'ai écrit surZookeeperLes bases de,ZookeeperEn tant que service d'ordonnancement de haute performance pour la coordination des applications distribuées,Les scénarios d'application réels sont également très variés,Voici quelques exemples pour illustrerZookeeperUtilisation dans un scénario particulier(Ces fonctions sont estimées ci - dessousconsulEtetcdPeut également être réalisé,Je t'apprendrai plus tard).

2.Applications spécifiques

2.1.Gestion cohérente de la configuration

Au moment du développement,Il faut parfois obtenir une configuration commune,Comme les informations de connexion à la base de données,Et occasionnellement, vous devrez peut - être mettre à jour la configuration.Si notre serveur aNSi plusieurs stations,Ce serait particulièrement gênant de le modifier,Et un redémarrage est nécessaire.Ici.ZookeeperIl est facile d'implémenter des fonctions similaires.

2.1.1.Idées

Stocker la configuration commune dansZookeeperDans le noeud de

L'application peut être connectée àZookeeperCentrage et alignementZookeeperConfigurer les noeuds pour lire ou modifier(Les paramètres de validation des permissions peuvent être définis pour les opérations d'écriture),Voici un organigramme détaillé:

2.1.2.Exemples

Maintenance de la cohérence des informations de configuration de la base de données

Configurer la classe:

public class CommonConfig implements Serializable{
// Configuration de la connexion à la base de données
private String dbUrl;
private String username;
private String password;
private String driverClass;
public CommonConfig() {}
public CommonConfig(String dbUrl, String username, String password, String driverClass) {
super();
this.dbUrl = dbUrl;
this.username = username;
this.password = password;
this.driverClass = driverClass;
}
public String getDbUrl() {
return dbUrl;
}
public void setDbUrl(String dbUrl) {
this.dbUrl = dbUrl;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClass() {
return driverClass;
}
public void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}
@Override
public String toString() {
return "CommonConfig:{dbUrl:" + this.dbUrl +
", username:" + this.username +
", password:" + this.password +
", driverClass:" + this.driverClass + "}";
}
}

Centre de gestion de la configuration

  • Obtenir des informations de configuration locales
  • Modifier la configuration,Et synchroniser

Synchroniser les informations de configuration vers ZookeeperServeur

public class ZkConfigMng {
private String nodePath = "/commConfig";
private CommonConfig commonConfig;
private ZkClient zkClient;
public CommonConfig initConfig(CommonConfig commonConfig) {
if(commonConfig == null) {
this.commonConfig = new CommonConfig("jdbc:mysql://127.0.0.1:3306/mydata?useUnicode=true&characterEncoding=utf-8",
"root", "root", "com.mysql.jdbc.Driver");
} else {
this.commonConfig = commonConfig;
}
return this.commonConfig;
}
/**
* Mise à jour de la configuration
*
* @param commonConfig
* @return
*/
public CommonConfig update(CommonConfig commonConfig) {
if(commonConfig != null) {
this.commonConfig = commonConfig;
}
syncConfigToZookeeper();
return this.commonConfig;
}
public void syncConfigToZookeeper() {
if(zkClient == null) {
zkClient = new ZkClient("127.0.0.1:2181");
}
if(!zkClient.exists(nodePath)) {
zkClient.createPersistent(nodePath);
}
zkClient.writeData(nodePath, commonConfig);
}
}

Ce sont les fournisseurs ,Nous avons besoin d'un client pour obtenir ces configurations

public class ZkConfigClient implements Runnable {
private String nodePath = "/commConfig";
private CommonConfig commonConfig;
@Override
public void run() {
ZkClient zkClient = new ZkClient(new ZkConnection("127.0.0.1:2181", 5000));
while (!zkClient.exists(nodePath)) {
System.out.println(" Le noeud de configuration n'existe pas !");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Obtenir le noeud
commonConfig = (CommonConfig)zkClient.readData(nodePath);
System.out.println(commonConfig.toString());
zkClient.subscribeDataChanges(nodePath, new IZkDataListener() {
@Override
public void handleDataDeleted(String dataPath) throws Exception {
if(dataPath.equals(nodePath)) {
System.out.println("Noeud:" + dataPath + "Supprimé!");
}
}
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
if(dataPath.equals(nodePath)) {
System.out.println("Noeud:" + dataPath + ", Données:" + data + " - Mise à jour");
commonConfig = (CommonConfig) data;
}
}
});
}
}

Démarre en basMainFonctions

Démarrage du Service de gestion de la configuration

public static void main(String[] args) throws InterruptedException {
SpringApplication.run(ZookeeperApiDemoApplication.class, args);
ZkConfigMng zkConfigMng = new ZkConfigMng();
zkConfigMng.initConfig(null);
zkConfigMng.syncConfigToZookeeper();
TimeUnit.SECONDS.sleep(10);
// Modifier la valeur
zkConfigMng.update(new CommonConfig("jdbc:mysql://192.168.1.122:3306/mydata?useUnicode=true&characterEncoding=utf-8",
"root", "wxh", "com.mysql.jdbc.Driver"));
}
}

Démarrage du client:

public static void main(String[] args) throws InterruptedException {
SpringApplication.run(ZookeeperApiDemoApplication.class, args);
ExecutorService executorService = Executors.newFixedThreadPool(3);
// Simuler plusieurs configurations d'acquisition de clients 
executorService.submit(new ZkConfigClient());
executorService.submit(new ZkConfigClient());
executorService.submit(new ZkConfigClient());
}
}

2.2.Serrure distribuée

Dans notre développement quotidien,S'il s'agit d'un accès à une ressource partagée dans un seul processus,On a juste besoin de l'utiliser.synchronizedOulock Pour réaliser des opérations mutuellement exclusives . Mais pour les processus croisés 、Inter - Host、Les ressources partagées entre les réseaux semblent impuissantes.

En plus,Les questions d'entrevue et les réponses de la série distribuée sont toutes en ordre,Recherche WechatJavaPile technologique,Envoyer en arrière - plan:Interview,Peut être lu en ligne.

2.1.1.Idées
  • Tout d'abord,zookeeper Où nous pouvons créer un /distributed_lockNoeud persistant
  • Et ensuite/distributed_lock Créer son propre noeud d'ordre temporaire sous le noeud ,Par exemple,:/distributed_lock/task_00000000008
  • Obtenir tous/distributed_lock Tous les noeuds enfants sous ,Et trier
  • Déterminer si le noeud que vous créez est le plus petit(Numéro un)
  • Si oui, La serrure est obtenue ,Exécuter sa propre logique d'entreprise, Supprimer enfin ce noeud temporaire .
  • Si ce n'est pas le minimum ,Vous devez écouter les changements de données du noeud précédent avant de créer le noeud vous - même, Et bloquer .
  • Lorsque le noeud actuel est supprimé ,Nous devons déterminer par récurrence si le noeud que nous créons est le plus petit,Si oui, exécuter5); Sinon, exécuter 6)( Est le jugement de la boucle récursive )

Voici un organigramme détaillé:

2.1.3.Exemples
public class DistributedLock {
// Lumière constante
static class Constant {
private static final int SESSION_TIMEOUT = 10000;
private static final String CONNECTION_STRING = "127.0.0.1:2181";
private static final String LOCK_NODE = "/distributed_lock";
private static final String CHILDREN_NODE = "/task_";
}
private ZkClient zkClient;
public DistributedLock() {
// Connectez - vous àZookeeper
zkClient = new ZkClient(new ZkConnection(Constant.CONNECTION_STRING));
if(!zkClient.exists(Constant.LOCK_NODE)) {
zkClient.create(Constant.LOCK_NODE, " Noeud de verrouillage distribué ", CreateMode.PERSISTENT);
}
}
public String getLock() {
try {
// 1.InZookeeper Créer un noeud d'ordre temporaire sous le noeud spécifié 
String lockName = zkClient.createEphemeralSequential(Constant.LOCK_NODE + Constant.CHILDREN_NODE, "");
// Essayer d'obtenir une serrure
acquireLock(lockName);
return lockName;
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Obtenir la serrure
* @throws InterruptedException
*/
public Boolean acquireLock(String lockName) throws InterruptedException {
// 2.Accèslock Tous les noeuds enfants sous le noeud 
List<String> childrenList = zkClient.getChildren(Constant.LOCK_NODE);
// 3. Trier les noeuds enfants ,Obtenir la valeur minimale
Collections.sort(childrenList, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.parseInt(o1.split("_")[1]) - Integer.parseInt(o2.split("_")[1]);
}
});
// 4.Détermine si le noeud actuellement créé est en premier
int lockPostion = childrenList.indexOf(lockName.split("/")[lockName.split("/").length - 1]);
if(lockPostion < 0) {
// Le noeud n'existe pas 
throw new ZkNodeExistsException(" Noeud inexistant :" + lockName);
} else if (lockPostion == 0) {
// Obtenir la serrure
System.out.println("Obtenir la serrure:" + lockName);
return true;
} else if (lockPostion > 0) {
// Verrouillage non obtenu,Blocage
System.out.println("...... Verrouillage non obtenu,Bloquez l'attente ......");
// 5. Si la serrure n'est pas obtenue ,Écouter le noeud précédent du noeud actuellement créé
final CountDownLatch latch = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataDeleted(String dataPath) throws Exception {
// 6. Le noeud précédent a été supprimé , Quand il n'y a aucune garantie que c'est à son tour 
System.out.println("...... Le noeud précédent a été supprimé ......");
acquireLock(lockName);
latch.countDown();
}
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
// Ne vous en faites pas.
}
};
try {
zkClient.subscribeDataChanges(Constant.LOCK_NODE + "/" + childrenList.get(lockPostion - 1), listener);
latch.await();
} finally {
zkClient.unsubscribeDataChanges(Constant.LOCK_NODE + "/" + childrenList.get(lockPostion - 1), listener);
}
}
return false;
}
/**
* Relâchez la serrure.(Supprimer le noeud)
*
* @param lockName
*/
public void releaseLock(String lockName) {
zkClient.delete(lockName);
}
public void closeZkClient() {
zkClient.close();
}
}
@SpringBootApplication
public class ZookeeperDemoApplication {
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(ZookeeperDemoApplication.class, args);
DistributedLock lock = new DistributedLock();
String lockName = lock.getLock();
/**
* Exécuter notre logique d'entreprise
*/
if(lockName != null) {
lock.releaseLock(lockName);
}
lock.closeZkClient();
}
}

2.3.File d'attente distribuée

Dans l'utilisation quotidienne, En particulier dans le Modèle producteur - consommateur ,Souvent utiliséBlockingQueue Pour agir comme un tampon .Mais cette méthode ne peut pas être utilisée dans les systèmes distribuésBlockingQueueC'est fait.,MaisZookeeperPeut être réalisé.

2.1.1.Idées
  • Utiliser d'abordZookeeper Caractéristiques des noeuds séquentiels temporaires
  • Lorsque le producteur crée une production de noeuds ,Le nombre de noeuds enfants de l'ordre temporaire sous le noeud parent doit être déterminé, Si la limite supérieure est atteinte ,Bloque l'attente;Si elle n'est pas atteinte, Créer un noeud .
  • Lorsque le consommateur obtient un noeud ,S'il n'y a pas de noeud enfant séquentiel temporaire dans le noeud parent,Bloque l'attente;S'il y a des noeuds enfants, Pour obtenir l'exécution de votre propre entreprise , Supprimer le noeud après l'exécution .
  • Obtenir la valeur minimale à obtenir ,GarantieFIFOCaractéristiques.
2.1.2.Exemples

C'est un consommateur contre un producteur. ,Si plus d'un consommateur contre plus d'un producteur, Le code doit être ajusté .

public interface AppConstant {
static String ZK_CONNECT_STR = "127.0.0.1:2181";
static String NODE_PATH = "/mailbox";
static String CHILD_NODE_PATH = "/mail_";
static int MAILBOX_SIZE = 10;
}
public class MailConsumer implements Runnable, AppConstant{
private ZkClient zkClient;
private Lock lock;
private Condition condition;
public MailConsumer() {
lock = new ReentrantLock();
condition = lock.newCondition();
zkClient = new ZkClient(new ZkConnection(ZK_CONNECT_STR));
System.out.println("sucess connected to zookeeper server!");
// Créer s'il n'existe pasmailboxNoeud
if(!zkClient.exists(NODE_PATH)) {
zkClient.create(NODE_PATH, "this is mailbox", CreateMode.PERSISTENT);
}
}
@Override
public void run() {
IZkChildListener listener = new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("Znode["+parentPath + "] size:" + currentChilds.size());
// Ou déterminer si la boîte aux lettres est vide 
if(currentChilds.size() > 0) {
// Réveillez le fil d'attente
try {
lock.lock();
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
};
// Surveiller les changements dans les noeuds enfants , Pas besoin. whileEn boucle, Écoute - moi une fois. , Pas besoin de reliures répétées 
zkClient.subscribeChildChanges(NODE_PATH, listener);
try {
//C'est le cas de la simulation de courrier circulaire aléatoire
while(true) {
// Déterminer si le courrier peut être envoyé 
checkMailReceive();
// Accepter le courrier 
List<String> mailList = zkClient.getChildren(NODE_PATH);
// Simailsize==0,Et ça n'a pas d'importance; Il suffit de l'obtenir directement en boucle. 
if(mailList.size() > 0) {
Collections.sort(mailList, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.parseInt(o1.split("_")[1]) - Integer.parseInt(o2.split("_")[1]);
}
});
// Simuler le traitement du courrier (0-1S)
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
zkClient.delete(NODE_PATH + "/" + mailList.get(0));
System.out.println("mail has been received:" + NODE_PATH + "/" + mailList.get(0));
}
}
}catch (Exception e) {
e.printStackTrace();
} finally {
zkClient.unsubscribeChildChanges(NODE_PATH, listener);
}
}
private void checkMailReceive() {
try {
lock.lock();
// Déterminer si la boîte aux lettres est vide 
List<String> mailList = zkClient.getChildren(NODE_PATH);
System.out.println("mailbox size: " + mailList.size());
if(mailList.size() == 0) {
// La boîte aux lettres est vide , Bloquer les consommateurs , Jusqu'à ce qu'il y ait du courrier dans la boîte aux lettres 
System.out.println("mailbox is empty, please wait ...");
condition.await();
// checkMailReceive();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class MailProducer implements Runnable, AppConstant{
private ZkClient zkClient;
private Lock lock;
private Condition condition;
/**
* État d'initialisation
*/
public MailProducer() {
lock = new ReentrantLock();
condition = lock.newCondition();
zkClient = new ZkClient(new ZkConnection(ZK_CONNECT_STR));
System.out.println("sucess connected to zookeeper server!");
// Créer s'il n'existe pasmailboxNoeud
if(!zkClient.exists(NODE_PATH)) {
zkClient.create(NODE_PATH, "this is mailbox", CreateMode.PERSISTENT);
}
}
@Override
public void run() {
IZkChildListener listener = new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("Znode["+parentPath + "] size:" + currentChilds.size());
// Ou déterminer si la boîte aux lettres est pleine 
if(currentChilds.size() < MAILBOX_SIZE) {
// Réveillez le fil d'attente
try {
lock.lock();
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
};
// Surveiller les changements dans les noeuds enfants , Pas besoin. whileEn boucle, Écoute - moi une fois. , Pas besoin de reliures répétées 
zkClient.subscribeChildChanges(NODE_PATH, listener);
try {
//C'est le cas de la simulation de courrier circulaire aléatoire
while(true) {
// Déterminer si le courrier peut être envoyé 
checkMailSend();
// Envoyer un message
String cretePath = zkClient.createEphemeralSequential(NODE_PATH + CHILD_NODE_PATH, "your mail");
System.out.println("your mail has been send:" + cretePath);
// Simuler les messages envoyés à intervalles aléatoires (0-10S)
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
}
}catch (Exception e) {
e.printStackTrace();
} finally {
zkClient.unsubscribeChildChanges(NODE_PATH, listener);
}
}
private void checkMailSend() {
try {
lock.lock();
// Déterminer si la boîte aux lettres est pleine 
List<String> mailList = zkClient.getChildren(NODE_PATH);
System.out.println("mailbox size: " + mailList.size());
if(mailList.size() >= MAILBOX_SIZE) {
// Boîte aux lettres pleine , Block Producer , Jusqu'à ce qu'il y ait de la place dans la boîte aux lettres 
System.out.println("mailbox is full, please wait ...");
condition.await();
checkMailSend();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

2.4.Équilibrer la charge

D'abord, nous avons besoin d'une compréhension simple de la distribution et du regroupement,C'est un peu vulgaire.:La distribution consiste à diviser un système en plusieurs applications fonctionnant indépendamment(C'est possible sur le même hôte ou sur différents hôtes),Le regroupement consiste à placer une seule réplication d'application indépendante sur différents hôtes pour soulager le serveur de la pression.

EtZookeeperIl ne s'agit pas seulement d'enregistrer un centre d'ordonnancement en tant que service d'un Cluster distribué(Par exempledubbo), L'équilibrage de la charge du cluster peut également être réalisé .

2.4.1.Idées

D'abord, nous devons comprendre, Si c'est un Cluster , Il aura plusieurs hôtes. .Alors...,Il estZookeeper L'information doit exister comme suit: :

Structure ci - dessus , Lorsque l'appelant de service appelle un service ,L'appel au service peut être réalisé selon un algorithme de charge d'équilibrage spécifique( Besoin d'écouter avant d'appeler /service/serviceXXXNoeud, Pour mettre à jour les données de la Liste )

2.4.2.Exemples
/**
* Fournisseurs de services
*
* @author Administrator
*
*/
public class ServiceProvider {
// Constante statique
static String ZK_CONNECT_STR = "127.0.0.1:2181";
static String NODE_PATH = "/service";
static String SERIVCE_NAME = "/myService";
private ZkClient zkClient;
public ServiceProvider() {
zkClient = new ZkClient(new ZkConnection(ZK_CONNECT_STR));
System.out.println("sucess connected to zookeeper server!");
// Créer s'il n'existe pasNODE_PATHNoeud
if(!zkClient.exists(NODE_PATH)) {
zkClient.create(NODE_PATH, "this is mailbox", CreateMode.PERSISTENT);
}
}
public void registryService(String localIp, Object obj) {
if(!zkClient.exists(NODE_PATH + SERIVCE_NAME)) {
zkClient.create(NODE_PATH + SERIVCE_NAME, "provider services list", CreateMode.PERSISTENT);
}
// Inscrivez - vous à vos propres services 
zkClient.createEphemeral(NODE_PATH + SERIVCE_NAME + "/" + localIp, obj);
System.out.println("Inscription réussie![" + localIp + "]");
}
}
/**
* Consommateurs,Sélectionner un fournisseur par un algorithme d'équilibrage de charge
*
* @author Administrator
*
*/
public class ServiceConsumer {
// Constante statique
static String ZK_CONNECT_STR = "127.0.0.1:2181";
static String NODE_PATH = "/service";
static String SERIVCE_NAME = "/myService";
private List<String> serviceList = new ArrayList<String>();
private ZkClient zkClient;
public ServiceConsumer() {
zkClient = new ZkClient(new ZkConnection(ZK_CONNECT_STR));
System.out.println("sucess connected to zookeeper server!");
// Créer s'il n'existe pasNODE_PATHNoeud
if(!zkClient.exists(NODE_PATH)) {
zkClient.create(NODE_PATH, "this is mailbox", CreateMode.PERSISTENT);
}
}
/**
* Services d'abonnement
*/
public void subscribeSerivce() {
serviceList = zkClient.getChildren(NODE_PATH + SERIVCE_NAME);
zkClient.subscribeChildChanges(NODE_PATH + SERIVCE_NAME, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
serviceList = currentChilds;
}
});
}
/**
* Service d'appel analogique
*/
public void consume() {
//Algorithme d'équilibrage de charge pour obtenir un service d'appel de machine
int index = new Random().nextInt(serviceList.size());
System.out.println("Appelez[" + NODE_PATH + SERIVCE_NAME + "]Services:" + serviceList.get(index));
}
}

3.Résumé

Zookeeper Est une application très puissante , En plus des applications ci - dessus , Et le Service de nommage 、Les avis de coordination distribués, etc., sont également couramment utilisés dans les scénarios.

Lien vers le texte original:https://blog.csdn.net/u013468...

Avis de copyright:Cet article estCSDNBlogueurs「Pour toujours._ Je ne comprends pas. 」Article original de,SuivreCC 4.0 BY-SAAccord de droit d'auteur,Veuillez joindre un lien vers la source originale et cette déclaration..

Recommandations récentes:

1.1,000+ Dow. JavaQuestions d'entrevue et Organisation des réponses(2021Dernière édition)

2.Arrête de remplir l'écran. if/ else C'est,Essayez le mode Stratégie,Ca sent bon!!!

3.Merde!!Java Dans xx ≠ null Quelle nouvelle syntaxe??

4.Spring Boot 2.5 Libération de poids lourds,Le mode sombre est trop explosif.!

5.《JavaManuel de développement(Songshan Edition)》Dernière publication,Téléchargement rapide!

C'est bien.,N'oublie pas d'être gentil.+Avant.!

版权声明
本文为[Pile technologique Java]所创,转载请带上原文链接,感谢
https://javamana.com/2021/10/20211014085748860u.html

  1. Usage relations and differences of count (1), count (*) and count (a field) in MySQL
  2. 2021 Ali Java advanced interview questions sharing, Java Architect interview materials
  3. Mybatis - dynamic SQL statement - if usage - MySQL series learning notes
  4. [go to Dachang series] deeply understand the use of where 1 = 1 in MySQL
  5. [secret room escape game theme ranking list] Based on spring MVC + Spring + mybatis
  6. Redis log: the killer mace of fearless downtime and rapid recovery
  7. 5 minutes to build redis cluster mode and sentinel mode with docker
  8. Java小白入门200例106之遍历ArrayList的几种方式
  9. Java小白入门200例105之Java ArrayList类
  10. Java小白入门200例104之JDK自带记录日志类logging
  11. Practice of high availability architecture of Tongcheng travel network based on rocketmq
  12. Chapter 9 - Linux learning will - file archiving and compression tar --- zip
  13. Java小白入門200例104之JDK自帶記錄日志類logging
  14. JDK avec journalisation de classe dans 200 cas 104
  15. Java ArrayList Class for Introduction to Java LITTLE WHITE 200 example 105
  16. Plusieurs façons de traverser ArrayList à partir de 200 exemples 106
  17. Provectus / Kafka UI: open source Apache Kafka's Web GUI Graphical interface management tool
  18. Design pattern series: Singleton pattern
  19. Java小白入門200例105之Java ArrayList類
  20. Understanding Java record types
  21. Five load balancing algorithms implemented in Java
  22. Data structure must be an example to understand dynamic programming (with universal Python code)
  23. The idea and implementation of recursion in data structure (Python)
  24. The idea and implementation of linked list (Python)
  25. Data structure must be queue and double ended queue (Python)
  26. Idea and implementation of data structure must be able stack (Python)
  27. Data structure | time complexity (with video explanation)
  28. 20 flutter libraries you should know
  29. Case sharing: Online failure caused by Dubbo 2.7.12 bug
  30. Open source | didi open source, general functional components for Java authentication, authentication, management and task scheduling
  31. Flutter multi engine supports platformview and thread merging solution
  32. In depth understanding of netty: viewing netty traffic control from occasional downtime
  33. Spring AOP internal skill cultivation
  34. Interviewer: is Tomcat a symbolic parent delegation mechanism?
  35. Expérimentez la première tablette de consommation Linux. La puce et le système d'origine sont tous faits maison
  36. 2021 summary of the latest Java common open source libraries, Java interview handwritten code
  37. 2021 latest Java factory interview true questions, Kafka introduction video
  38. 01 javase - première connaissance de l'installation de Java et de l'environnement de développement
  39. The sales volume in September broke the record: Weilai and Xiaopeng both exceeded 10000, with an ideal month on month decrease of 24.7%
  40. Required for interview: HBase block cache
  41. Redis core principle and practice: implementation principle of hash type and dictionary structure
  42. MySQL deep dive: analyzing performance schema memory management
  43. Redis cache: kill interviewer 25 asked
  44. Solution of Chinese garbled code in idea integrated Tomcat console
  45. 2021 the latest java development interview, 46 interview questions take you to understand the advanced Java interview
  46. 2021 spring recruitment bat interview questions are explained in detail and explained clearly
  47. 2021 latest java developer learning route, 2021 latest Ali Java advanced interview questions and answers
  48. 2021 summary of the latest Java common open source libraries, the top java development tools necessary for developers
  49. Java communique avec les capteurs industriels via socket et DTu, RTU
  50. Does Hadoop have to run on Linux? (the root cause is the permission opening advantage of the operating system Linux)
  51. Java communique avec les capteurs industriels via socket et DTu, RTU
  52. 2021 summary of the latest java knowledge system. It took 8 days to finally understand JVM tuning
  53. Introduction au tissu de service Azure
  54. 【.Net vs Java? 】 先来看一下Java和C#的数据类型区别。
  55. 2021 summary of the latest java knowledge system, will you only have the technology of junior engineers
  56. 【.Net vs Java? 】 先來看一下Java和C#的數據類型區別。
  57. [.NET vs Java?] regardez d'abord les différences de type de données entre Java et C #.
  58. 2021 latest Java interview questions and answers, Java interviewer's favorite garbage collection mechanism
  59. 2021 latest Java interview written test, distributed ID generation algorithm in Seata project of source code analysis
  60. 2021 latest Java experience sharing, what java knowledge do we need to learn