Dieser Artikel wurde von der Huawei Cloud Community geteilt. „ Detaillierte Erläuterung von SpringCloud ZooKeeper und Integration mit Nicht-Java-Diensten wie Go und Rust “, Autor: Zhang Jian.
ZooKeeper ist ein verteilter Open-Source-Koordinierungsdienst, der nicht nur verteilte Wahlen und Aufgabenzuweisungen unterstützt, sondern auch als Registrierungszentrum und Konfigurationszentrum für Mikrodienste verwendet werden kann. In diesem Artikel werden wir uns mit dem Szenario befassen, in dem ZooKeeper als Microservice-Registrierungszentrum verwendet wird.
Dienstregistrierungspfad in ZooKeeper
SpringCloud ZooKeeper folgt einer bestimmten Pfadstruktur für die Dienstregistrierung
/services/${spring.application.name}/${serviceId}
Beispiel:
/services/provider-service/d87a3891-1173-45a0-bdfa-a1b60c71ef4e
/services und /${spring.application.name} sind permanente Knoten in ZooKeeper und /${serviceId} ist ein temporärer Knoten. Wenn der Dienst offline geht, löscht ZooKeeper den Knoten automatisch.
Hinweis: Wenn die letzte Instanz des Mikrodienstes offline geht, löscht das SpringCloud ZooKeeper-Framework den Knoten /${spring.application.name}.
Dienstregistrierungsdaten in ZooKeeper
Das Folgende ist ein Beispiel für einen typischen Inhalt der Dienstregistrierung:
{ „name“: „Anbieter-Dienst“, „id“: „d87a3891-1173-45a0-bdfa-a1b60c71ef4e“, „Adresse“: „192.168.0.105“, "Port":8080, „sslPort“:null, "Nutzlast":{ „@class“: „org.springframework.cloud.zookeeper.discovery.ZookeeperInstance“, „id“: „Anbieter-Dienst“, „name“: „Anbieter-Dienst“, „Metadaten“:{ „instance_status“: „UP“ } }, „registrationTimeUTC“:1695401004882, „serviceType“: „DYNAMISCH“, „uriSpec“:{ "Teile":[ { „value“: „Schema“, „Variable“:true }, { "Wert":"://", „Variable“:false }, { „Wert“: „Adresse“, „Variable“:true }, { "Wert":":", „Variable“:false }, { „Wert“: „Port“, „Variable“:true } ] } }
Darunter sind Adresse, Port und uriSpec die Kerndaten. Die Teile in uriSpec unterscheiden, welcher Inhalt variabel und welcher fest ist.
SpringCloud-Dienste verwenden OpenFeign, um sich gegenseitig aufzurufen
Sobald beide Microservices bei ZooKeeper registriert sind, können sie sich gegenseitig über OpenFeign aufrufen. Ein einfaches Beispiel ist wie folgt
Dienstleister
Erstellen Sie ein SpringBoot-Projekt
Erstellen Sie ein SpringBoot-Projekt und fügen Sie die Abhängigkeiten spring-cloud-starter-zookeeper-discovery und spring-boot-starter-web hinzu .
Konfigurieren Sie application.yaml
Frühling: Anwendung: Name: Anbieter-Dienst Wolke: Tierpfleger: Verbindungszeichenfolge: localhost:2181 Server: Port: 8082
Registrieren Sie sich bei ZooKeeper
Fügen Sie der Startklasse die Annotation @EnableDiscoveryClient hinzu.
Erstellen Sie eine einfache REST-Schnittstelle
@RestController öffentliche Klasse ProviderController { @GetMapping("/hello") public String hello() { return „Hallo vom Anbieterdienst!“; } }
Verbraucher bedienen
Erstellen Sie ein SpringBoot-Projekt
Erstellen Sie ein SpringBoot-Projekt und fügen Sie die Abhängigkeiten spring-cloud-starter-zookeeper-discovery , spring-cloud-starter-openfeign und spring-boot-starter-web hinzu .
Konfigurieren Sie application.yaml
Frühling: Anwendung: Name: Verbraucherservice Wolke: Tierpfleger: Verbindungszeichenfolge: localhost:2181 Server: Port: 8081
Registrieren Sie sich bei ZooKeeper
Fügen Sie der Startklasse die Annotation @EnableDiscoveryClient hinzu.
Erstellen Sie eine REST-Schnittstelle, um den Dienstanbieter über OpenFeign aufzurufen
@RestController öffentliche Klasse ConsumerController { @Autowired privater ProviderClient ProviderClient; @GetMapping("/getHello") öffentlicher String getHello() { return anbieterClient.hello(); } }
Laufergebnis
curl localhost:8081/getHello -i HTTP/1.1 200 Inhaltstyp: text/plain;charset=UTF-8 Inhaltslänge: 28 Datum: Mi, 18. Okt. 2023 02:40:57 GMT Hallo vom Provider Service!
Nicht-Java-Dienste sind bei SpringCloud ZooKeeper registriert
Einige Leser finden es auf den ersten Blick vielleicht etwas seltsam: Warum sollten wir Nicht-Java-Dienste in SpringCloud ZooKeeper registrieren? Ein solches Anwendungsszenario gibt es nicht.
Natürlich sind solche Szenarien relativ selten. Es ist üblich, dass die meisten Projekte mit Spring Cloud entwickelt werden, aber eine kleine Anzahl von Projekten muss aus verschiedenen Gründen andere Sprachen wie Go, Rust usw. verwenden. Zu diesem Zeitpunkt müssen wir Nicht-Java-Dienste in SpringCloud ZooKeeper registrieren.
Stellen Sie bei Diensten, die in Nicht-JVM-Sprachen entwickelt wurden, lediglich sicher, dass sie eine Rest/HTTP-Schnittstelle bereitstellen und korrekt bei ZooKeeper registriert sind, damit sie vom Feign-Client von SpringCloud aufgerufen werden können.
Go-Dienste in SpringCloud ZooKeeper
Beispielcode-Organisation:
├── Verbraucher │ └── Consumer.go ├── go.mod ├── go.sum └── Anbieter └── anbieter.go
Go-Dienstanbieter in SpringCloud ZooKeeper
Hinweis: Die Qualität dieses Codes entspricht dem Demo-Niveau. Die tatsächliche Produktionsumgebung erfordert strengeren Code, z. B. einen Wiederverbindungsmechanismus, einen Timeout-Mechanismus, einen besseren Algorithmus zur Service-ID-Generierung usw.
Paket main importieren ( „fmt“ "Protokoll" „net/http“ "Zeit" „Kodierung/json“ "github.com/gin-gonic/gin" "github.com/samuel/go-zookeeper/zk" ) const ( zkServers = "localhost:2181" // Zookeeper-Serveradresse ) func main() { // Initialisiere das Gin-Framework r := gin.Default() //Eine einfache Hallo-Schnittstelle hinzufügen r.GET("/hello", func(c *gin.Context) { c.String(http.StatusOK, „Hello from Go-Dienst!“) }) //Dienst für Zookeeper registrieren registerToZookeeper() //Starte den Gin-Server r.Run(":8080") } func registerToZookeeper() { conn, _, err := zk.Connect([]string{zkServers}, time.Second*5) if err != nil { Panik(irr) } // Überprüfe und erstelle den übergeordneten Pfad securePathExists(conn, "/services") securePathExists(conn, "/services/provider-service") // Registrierte Daten erstellen Daten, _ := json.Marshal(map[string]interface{}{}{ „name“: „provider-service“, "Adresse": "127.0.0.1", „Port“: 8080, „sslPort“: null, „payload“: map[string]interface{}{“@class“: „org.springframework.cloud.zookeeper.discovery.ZookeeperInstance“, „id“: „provider-service“, „name“: „provider-service“ , „metadata“: map[string]string{“instance_status“: „UP“}}, „serviceType“: „DYNAMISCH“, „uriSpec“: map[string]interface{}{ "parts": []map[string]interface{}{}{ {"value": "scheme", "variable": true}, {"value": "://", "variable": false}, {"value": "address", "variable": true}, {"Wert": ::, "Variable": falsch}, {"value": "port", "variable": true}, }, }, }) // Registrieren Sie den Dienst in zookeeper Pfad := "/services/provider-service/" + genericServiceId() _, err = conn.Create(path, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) if err != nil { log.Fatalf("Registrierungsdienstfehler: %s", err) } anders { log.Println(Pfad) } } func securePathExists(conn *zk.Conn, path string) { existiert, _, err := conn.Exists(path) if err != nil { log.Fatalf("Pfadfehler prüfen: %s", err) } wenn !exists { _, err := conn.Create(path, []byte{}, 0, zk.WorldACL(zk.PermAll)) if err != nil { log.Fatalf("Pfadfehler erstellen: %s", err) } } } func genericServiceId() string { // Dies wird vereinfacht, um die aktuelle Zeit zum Generieren der ID zu verwenden. Die tatsächliche Produktionsumgebung erfordert möglicherweise einen komplexeren Algorithmus. return fmt.Sprintf("%d", time.Now().UnixNano()) }
Anrufeffekt
curl localhost:8081/getHello -i HTTP/1.1 200 Inhaltstyp: text/plain;charset=UTF-8 Inhaltslänge: 28 Datum: Mi, 18. Okt. 2023 02:43:52 GMT Hallo vom Go-Service!
Gehen Sie zum Servicekonsumenten in SpringCloud ZooKeeper
Paket main importieren ( „Kodierung/json“ „fmt“ „io“ "Protokoll" „net/http“ "Zeit" "github.com/samuel/go-zookeeper/zk" ) const ( zkServers = "localhost:2181" // Zookeeper-Serveradresse ) var conn *zk.Conn func main() { // ZooKeeper-Verbindung initialisieren initializeZookeeper() // Serviceinformationen abrufen serviceInfo := getServiceInfo("/services/provider-service") fmt.Println("Abgerufene Dienstinformationen:", serviceInfo) port := int(serviceInfo["port"].(float64)) resp, err := http.Get(fmt.Sprintf("http://%s:%d/hello", serviceInfo["address"], port)) if err != nil { Panik(irr) } body, err := io.ReadAll(resp.Body) if err != nil { Panik(irr) } fmt.Println(string(body)) } func initializeZookeeper() { var err Fehler conn, _, err = zk.Connect([]string{zkServers}, time.Second*5) if err != nil { log.Fatalf("Verbindung zu ZooKeeper fehlgeschlagen: %s", err) } } func getServiceInfo(path string) map[string]interface{} { children, _, err := conn.Children(path) if err != nil { log.Fatalf("Untergeordnete Elemente von %s konnten nicht abgerufen werden: %s", Pfad, Fehler) } if len(children) == 0 { log.Fatalf("Keine Dienste unter %s gefunden", Pfad) } // Hier erhalten wir nur als Beispiel die Informationen des ersten Dienstknotens. Tatsächlich können Sie einen Dienstknoten gemäß der Lastausgleichsrichtlinie auswählen. data, _, err := conn.Get(fmt.Sprintf("%s/%s", path,children[0])) if err != nil { log.Fatalf("Fehler beim Abrufen der Daten von %s: %s", Kinder[0], err) } var serviceInfo map[string]interface{} if err := json.Unmarshal(data, &serviceInfo); ähm != null { log.Fatalf("Daten konnten nicht entmarshaliert werden: %s", Fehler) } RückgabeserviceInfo }
Rust-Dienst in SpringCloud ZooKeeper
Beispielcode-Organisation:
├── Cargo.lock ├── Cargo.toml └── src └── bin ├── Verbraucher.rs └── anbieter.rs
Rust-Dienstanbieter in SpringCloud ZooKeeper
verwenden Sie std::collections::HashMap; verwenden Sie std::time::Duration; use serde_json::Value; verwenden warp::Filter; use zookeeper::{Acl, CreateMode, WatchedEvent, Watcher, ZooKeeper}; static ZK_SERVERS: &str = "localhost:2181"; static mut ZK_CONN: Option<ZooKeeper> = None; struct LoggingWatcher; impl Watcher für LoggingWatcher { fn handle(&self, e: WatchedEvent) { println!("WatchedEvent: {:?}", e); } } #[tokio::main] async fn main() { let hello = warp::path!("hello").map(|| warp::reply::html("Hallo vom Rust-Dienst!")); register_to_zookeeper().await; warp::serve(hello).run(([127, 0, 0, 1], 8083)).await; } async fn register_to_zookeeper() { unsicher { ZK_CONN = Some(ZooKeeper::connect(ZK_SERVERS, Duration::from_secs(5), LoggingWatcher).unwrap()); let zk = ZK_CONN.as_ref().unwrap(); let path = "/services/provider-service"; if zk.exists(path, false).unwrap().is_none() { zk.create(path, vec![], Acl::open_unsafe().clone(), CreateMode::Persistent).unwrap(); } let service_data = get_service_data(); let service_path = format!("{}/{}", path, generic_service_id()); zk.create(&service_path, service_data, Acl::open_unsafe().clone(), CreateMode::Ephemeral).unwrap(); } } fn get_service_data() -> Vec<u8> { let mut data: HashMap<&str, Value> = HashMap::new(); data.insert("name", serde_json::Value::String("provider-service".to_string())); data.insert("address", serde_json::Value::String("127.0.0.1".to_string())); data.insert("port", serde_json::Value::Number(8083.into())); serde_json::to_vec(&data).unwrap() } fn generic_service_id() -> String { format!("{}", chrono::Utc::now().timestamp_nanos()) }
Rust-Dienstkonsument in SpringCloud ZooKeeper
verwenden Sie std::collections::HashMap; verwenden Sie std::time::Duration; use zookeeper::{WatchedEvent, Watcher, ZooKeeper}; benutze reqwest; use serde_json::Value; static ZK_SERVERS: &str = "localhost:2181"; struct LoggingWatcher; impl Watcher für LoggingWatcher { fn handle(&self, e: WatchedEvent) { println!("WatchedEvent: {:?}", e); } } #[tokio::main] async fn main() { letProvider_data = fetch_provider_data_from_zookeeper().await; let Response = request_provider(&provider_data).await; println!("Antwort vom Anbieter: {}", Antwort); } async fn fetch_provider_data_from_zookeeper() -> HashMap<String, Value> { let zk = ZooKeeper::connect(ZK_SERVERS, Duration::from_secs(5), LoggingWatcher).unwrap(); let children = zk.get_children("/services/provider-service", false).unwrap(); if children.is_empty() { panic!("Keine Anbieterdienste gefunden!"); } // Der Einfachheit halber nehmen wir einfach das erste untergeordnete Element (dh die Dienstinstanz). // In einem realen Szenario würden Lastausgleichsstrategien bestimmen, welche Dienstinstanz verwendet werden soll. let data = zk.get_data(&format!("/services/provider-service/{}",children[0]), false).unwrap(); serde_json::from_slice(&data.0).unwrap() } async fn request_provider(provider_data: &HashMap<String, Value>) -> String { let address =Provider_data.get("address").unwrap().as_str().unwrap(); let port =Provider_data.get("port").unwrap().as_i64().unwrap(); let url = format!("http://{}:{}/hello", Adresse, Port); let Response = reqwest::get(&url).await.unwrap(); Antwort.text().await.unwrap() }Tang Xiaoou, Gründer von SenseTime, verstarb im Alter von 55 Jahren. Im Jahr 2023 stagnierte PHP . Das Hongmeng-System steht kurz vor der Unabhängigkeit und viele Universitäten haben „Hongmeng-Kurse“ eingerichtet. Die PC-Version von Quark Browser hat mit internen Tests begonnen . ByteDance wurde von OpenAI „verboten“. Das Startup-Unternehmen von Zhihuijun wurde mit einem Betrag von über 600 Millionen Yuan und einer Pre-Money-Bewertung von 3,5 Milliarden Yuan refinanziert. KI-Code-Assistenten sind so beliebt, dass sie nicht einmal in der Programmierung mithalten können Sprachrankings . Das 5G-Modem und die Hochfrequenztechnologie des Mate 60 Pro liegen weit vorne. No Star, No Fix MariaDB spaltet SkySQL ab und gründet sich als unabhängiges Unternehmen