1 unstable release
Uses new Rust 2024
0.3.0 | Jul 13, 2025 |
---|
#1 in #consul
Used in 2 crates
45KB
624 lines
revoke-registry
Service registry module for the Revoke microservices framework, providing service discovery and health monitoring capabilities.
Features
- Multiple Backends: Support for Consul, etcd, and in-memory implementations
- Service Discovery: Automatic service registration and discovery
- Health Monitoring: Active and passive health checks
- Load Balancing: Integration with various load balancing strategies
- Metadata Support: Attach custom metadata to services
- Watch Support: Real-time updates on service changes
- High Availability: Support for registry clustering
Installation
Add to your Cargo.toml
:
[dependencies]
revoke-registry = { version = "0.1", features = ["consul"] }
Feature Flags
memory
: In-memory registry (default, useful for testing)consul
: HashiCorp Consul integrationetcd
: etcd v3 integrationkubernetes
: Kubernetes service discoveryfull
: Enable all backends
Quick Start
use revoke_registry::{ServiceRegistry, ServiceInfo};
use revoke_core::{ServiceRegistry as RegistryTrait, Protocol};
use uuid::Uuid;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create registry client
let registry = ServiceRegistry::consul("http://localhost:8500").await?;
// Register service
let service = ServiceInfo {
id: Uuid::new_v4(),
name: "user-service".to_string(),
version: "1.0.0".to_string(),
address: "192.168.1.100".to_string(),
port: 8080,
protocol: Protocol::Http,
metadata: Default::default(),
};
registry.register(service).await?;
// Discover services
let services = registry.get_service("user-service").await?;
for svc in services {
println!("Found: {}:{}", svc.address, svc.port);
}
Ok(())
}
Backend Implementations
Memory Registry
In-memory implementation for development and testing:
use revoke_registry::memory::MemoryRegistry;
let registry = MemoryRegistry::new();
// Register with TTL
registry.register_with_ttl(service, Duration::from_secs(30)).await?;
// Get all services
let all_services = registry.list_all_services().await?;
Consul Registry
HashiCorp Consul integration with full feature support:
use revoke_registry::consul::{ConsulRegistry, ConsulConfig};
use std::time::Duration;
let config = ConsulConfig {
address: "localhost:8500".to_string(),
datacenter: Some("dc1".to_string()),
token: None, // ACL token if needed
check_interval: Duration::from_secs(10),
deregister_critical: "30s".to_string(),
..Default::default()
};
let registry = ConsulRegistry::new(config).await?;
// Enable health checks
registry.enable_health_check(
service_id,
HealthCheckConfig {
interval: Duration::from_secs(10),
timeout: Duration::from_secs(5),
deregister_after: Duration::from_secs(30),
}
).await?;
etcd Registry
etcd v3 API integration:
use revoke_registry::etcd::{EtcdRegistry, EtcdConfig};
let config = EtcdConfig {
endpoints: vec!["http://localhost:2379".to_string()],
username: None,
password: None,
ttl: 60, // Service TTL in seconds
..Default::default()
};
let registry = EtcdRegistry::new(config).await?;
// Watch for service changes
let mut watcher = registry.watch("user-service").await?;
while let Some(event) = watcher.next().await {
match event {
ServiceEvent::Added(service) => println!("Service added: {}", service.id),
ServiceEvent::Modified(service) => println!("Service modified: {}", service.id),
ServiceEvent::Removed(id) => println!("Service removed: {}", id),
}
}
Service Discovery Patterns
Client-Side Discovery
use revoke_registry::discovery::DiscoveryClient;
let discovery = DiscoveryClient::new(registry);
// Get service instance with load balancing
let instance = discovery
.get_instance("user-service")
.with_load_balancer(LoadBalancer::RoundRobin)
.await?;
// Make request to the instance
let url = format!("http://{}:{}/api/users", instance.address, instance.port);
Server-Side Discovery
use revoke_registry::discovery::ServiceResolver;
let resolver = ServiceResolver::new(registry);
// Resolve service name to address
let endpoint = resolver.resolve("user-service").await?;
// Use with HTTP client
let client = reqwest::Client::new();
let response = client.get(&endpoint.url("/api/users")).send().await?;
Health Checking
Active Health Checks
use revoke_registry::health::{HealthChecker, HealthCheckType};
let checker = HealthChecker::new(registry);
// HTTP health check
checker.add_check(
service_id,
HealthCheckType::Http {
url: "http://localhost:8080/health".to_string(),
method: "GET".to_string(),
expected_status: 200,
timeout: Duration::from_secs(5),
}
).await?;
// TCP health check
checker.add_check(
service_id,
HealthCheckType::Tcp {
address: "localhost:8080".to_string(),
timeout: Duration::from_secs(5),
}
).await?;
// Script health check
checker.add_check(
service_id,
HealthCheckType::Script {
command: "/usr/local/bin/check-health.sh".to_string(),
args: vec![service_id.to_string()],
timeout: Duration::from_secs(10),
}
).await?;
Passive Health Checks
use revoke_registry::health::PassiveHealthCheck;
let passive_check = PassiveHealthCheck::new()
.failure_threshold(5) // Mark unhealthy after 5 failures
.success_threshold(2) // Mark healthy after 2 successes
.timeout(Duration::from_secs(30));
// Report request outcome
passive_check.report_success(service_id).await;
passive_check.report_failure(service_id).await;
// Check health status
let is_healthy = passive_check.is_healthy(service_id).await;
Advanced Features
Service Metadata
let mut metadata = HashMap::new();
metadata.insert("region".to_string(), "us-west-2".to_string());
metadata.insert("zone".to_string(), "us-west-2a".to_string());
metadata.insert("version".to_string(), "1.2.3".to_string());
metadata.insert("commit".to_string(), "abc123".to_string());
let service = ServiceInfo {
metadata,
..Default::default()
};
// Query by metadata
let services = registry
.query_services()
.with_metadata("region", "us-west-2")
.with_metadata("version", "1.2.3")
.execute()
.await?;
Service Groups
use revoke_registry::groups::ServiceGroup;
// Create service group
let group = ServiceGroup::new("api-services")
.add_service("user-service")
.add_service("order-service")
.add_service("payment-service");
registry.register_group(group).await?;
// Get all services in group
let services = registry.get_group("api-services").await?;
Circuit Breaker Integration
use revoke_registry::resilience::RegistryCircuitBreaker;
let breaker = RegistryCircuitBreaker::new(registry)
.failure_threshold(5)
.recovery_timeout(Duration::from_secs(60));
// Discover with circuit breaker
match breaker.get_service("user-service").await {
Ok(services) => {
// Use services
}
Err(e) if e.is_circuit_open() => {
// Use fallback
}
Err(e) => {
// Handle other errors
}
}
Multi-Region Support
use revoke_registry::multiregion::{MultiRegionRegistry, RegionConfig};
let regions = vec![
RegionConfig {
name: "us-west-2".to_string(),
endpoint: "http://consul-us-west-2:8500".to_string(),
priority: 1,
},
RegionConfig {
name: "us-east-1".to_string(),
endpoint: "http://consul-us-east-1:8500".to_string(),
priority: 2,
},
];
let registry = MultiRegionRegistry::new(regions).await?;
// Prefer local region
let services = registry
.get_service("user-service")
.prefer_region("us-west-2")
.await?;
Performance Optimization
Caching
use revoke_registry::cache::{CachedRegistry, CacheConfig};
let cache_config = CacheConfig {
ttl: Duration::from_secs(5),
max_size: 1000,
refresh_interval: Duration::from_secs(30),
};
let cached_registry = CachedRegistry::new(registry, cache_config);
// Cached lookups
let services = cached_registry.get_service("user-service").await?;
Batch Operations
// Register multiple services
let services = vec![service1, service2, service3];
registry.register_batch(services).await?;
// Deregister multiple services
let ids = vec![id1, id2, id3];
registry.deregister_batch(ids).await?;
// Batch health updates
let updates = vec![
(id1, HealthStatus::Healthy),
(id2, HealthStatus::Unhealthy),
];
registry.update_health_batch(updates).await?;
Monitoring and Metrics
use revoke_registry::metrics::RegistryMetrics;
let metrics = RegistryMetrics::new(®istry);
// Get registry statistics
let stats = metrics.get_stats().await?;
println!("Total services: {}", stats.total_services);
println!("Healthy services: {}", stats.healthy_services);
println!("Unhealthy services: {}", stats.unhealthy_services);
// Export Prometheus metrics
let prometheus_metrics = metrics.export_prometheus();
Best Practices
- Service Naming: Use consistent naming conventions (e.g.,
service-name-v1
) - Health Checks: Always configure appropriate health checks
- Metadata: Include version, region, and deployment information
- TTL: Set reasonable TTL values for service registration
- Graceful Shutdown: Always deregister services on shutdown
- Connection Pooling: Reuse registry clients across your application
- Error Handling: Implement retry logic for transient failures
Examples
See the examples directory:
basic_registration.rs
- Simple service registrationhealth_checks.rs
- Health check configurationservice_discovery.rs
- Client-side discovery patternsmulti_region.rs
- Multi-region deployment
Dependencies
~5–18MB
~236K SLoC