We are excited to announce the release of go-zero v1.9.3! This release brings several important enhancements and bug fixes that improve the framework's reliability, performance, and alignment with industry best practices.
🎉 Highlights
- Consistent Hash Load Balancing: New gRPC load balancer for session affinity
- gRPC Best Practices: Changed NonBlock default to align with gRPC recommendations
- Improved Distributed Tracing: Fixed gateway trace header propagation
- ORM Improvements: Fixed zero value scanning for pointer destinations
✨ New Features
Consistent Hash Balancer Support (#5246)
Contributor: @zhoushuguang
A new consistent hash load balancer has been added to the zRPC package, enabling session affinity for gRPC services.
Key Features:
- Hash-based request routing to maintain session affinity
- Distributes requests based on a hash key from context
- Minimal request redistribution on node changes
- Built on go-zero's existing
core/hash/ConsistentHashimplementation
Usage Example:
// Set hash key in context
ctx := zrpc.SetHashKey(ctx, "user_123")
// Requests with the same key will be routed to the same backend
resp, err := client.SomeMethod(ctx, req)Configuration:
c := zrpc.RpcClientConf{
Endpoints: []string{"localhost:8080", "localhost:8081"},
BalancerName: "consistent_hash", // Use consistent hash balancer
}Benefits:
- Enables stateful service interactions
- Improves cache hit rates on backend services
- Reduces session data synchronization overhead
- Maintains load distribution while supporting affinity
🐛 Bug Fixes
Fixed Gateway Trace Headers (#5256, #5248)
Contributor: @kevwan
Fixed an issue where OpenTelemetry trace propagation headers were not being properly forwarded through the gateway to upstream gRPC services, breaking distributed tracing.
Problem:
The gateway was not forwarding critical W3C Trace Context headers (traceparent, tracestate, baggage) to gRPC metadata, causing trace context to be lost at the gateway boundary.
Solution:
- Enhanced
ProcessHeadersfunction to forward trace propagation headers - Headers are now properly converted to lowercase per gRPC metadata conventions
- Maintains distributed tracing across HTTP → gRPC boundaries
Impact:
- End-to-end tracing now works correctly through the gateway
- Improved observability for microservice architectures
- Better debugging and performance analysis capabilities
Technical Details:
// Trace headers now properly forwarded
var traceHeaders = map[string]bool{
"traceparent": true, // W3C Trace Context
"tracestate": true, // Additional trace state
"baggage": true, // W3C Baggage propagation
}Fixed Multiple Trace Initialization (#5244)
Contributor: @kevwan
Problem:
When running multiple services (e.g., REST + RPC) in the same process, the trace agent could be initialized multiple times, potentially causing resource leaks or unexpected behavior.
Solution:
- Used
sync.Onceto ensure trace agent is initialized only once - Aligned with similar patterns used in
prometheus.StartAgentandlogx.SetUp - Added
sync.OnceFuncfor shutdown to prevent double cleanup
Code Changes:
var (
once sync.Once
shutdownOnceFn = sync.OnceFunc(func() {
if tp != nil {
_ = tp.Shutdown(context.Background())
}
})
)
func StartAgent(c Config) {
if c.Disabled {
return
}
once.Do(func() {
if err := startAgent(c); err != nil {
logx.Error(err)
}
})
}Benefits:
- Prevents resource conflicts in multi-server processes
- Ensures single global tracer provider instance
- Safer concurrent initialization
- Proper cleanup on shutdown
Fixed ORM Zero Value Scanning for Pointer Destinations (#5270)
Contributor: @lerity-yao (first contribution! 🎊)
Problem:
When scanning database results into struct fields with pointer types, zero values (0, false, empty string) were not being properly distinguished from NULL values. This caused nil pointers to be set to zero values incorrectly.
Solution:
Enhanced the getValueInterface function to properly initialize nil pointers before scanning, ensuring the SQL driver can correctly populate them with zero or non-zero values.
Code Changes:
func getValueInterface(value reflect.Value) (any, error) {
if !value.CanAddr() || !value.Addr().CanInterface() {
return nil, ErrNotReadableValue
}
// Initialize nil pointer before scanning
if value.Kind() == reflect.Pointer && value.IsNil() {
baseValueType := mapping.Deref(value.Type())
value.Set(reflect.New(baseValueType))
}
return value.Addr().Interface(), nil
}Impact:
type User struct {
Name string `db:"name"` // Always set
Age *int `db:"age"` // Can distinguish NULL vs 0
Active *bool `db:"active"` // Can distinguish NULL vs false
}
// Before: age=0 and age=NULL both resulted in nil pointer
// After: age=0 → *int(0), age=NULL → nil pointer ✓Benefits:
- Correct handling of NULL vs zero values
- Better semantic representation of optional fields
- Prevents unexpected nil pointer dereferences
- Aligns with Go's SQL scanning best practices
🔄 Breaking Changes (With Backward Compatibility)
Changed NonBlock Default to True (#5259)
Contributor: @kevwan
Motivation:
Aligned with gRPC official best practices which discourage blocking dials.
Change:
// zrpc/config.go
type RpcClientConf struct {
// Before: NonBlock bool `json:",optional"`
// After:
NonBlock bool `json:",default=true"` // Now defaults to true
}Why This Matters:
- Blocking dials are deprecated:
grpc.WithBlock()is an anti-pattern - Connection state is dynamic: Being connected at dial time doesn't guarantee future connectivity
- RPCs handle waiting: All RPCs automatically wait until connection or deadline
- Simpler code: No need to check "ready" state before making calls
Migration Guide:
For most users, no action required - the new default is the recommended behavior.
If you explicitly need blocking behavior (not recommended):
// Option 1: Configuration
c := zrpc.RpcClientConf{
NonBlock: false, // Explicit blocking
}
// Option 2: Client option (deprecated)
client := zrpc.MustNewClient(c, zrpc.WithBlock())Backward Compatibility:
- Existing configs with
NonBlock: falsecontinue to work - New
WithBlock()option available (marked deprecated) - No changes needed for services already using
NonBlock: true
Documentation:
See GRPC_NONBLOCK_CHANGE.md for detailed migration guide and rationale.
👥 New Contributors
We're thrilled to welcome new contributors to the go-zero community! 🎉
- @JackGod001 - Made their first contribution in #4343
- @stemlaud - Made their first contribution in #5245
- @gfischer7 - Made their first contribution in #5254
- @lerity-yao - Made their first contribution in #5270 (ORM fix)
Thank you for your contributions! We look forward to your continued involvement in the project.
📦 Installation & Upgrade
Install
go get -u github.com/zeromicro/go-zero@v1.9.3Update
# Update go.mod
go get -u github.com/zeromicro/go-zero@v1.9.3
go mod tidy🔗 Links
- Full Changelog: v1.9.2...v1.9.3
- Documentation: https://go-zero.dev
- GitHub Repository: https://github.com/zeromicro/go-zero
📝 Detailed Changes
Core Enhancements
Load Balancing
- Added consistent hash balancer for gRPC (
zrpc/internal/balancer/consistenthash) - Context-based hash key API:
SetHashKey()andGetHashKey() - Configurable replica count and hash function
- Comprehensive test coverage
Distributed Tracing
- Fixed trace header propagation in gateway
- Proper handling of W3C Trace Context headers
- Case-insensitive header matching per gRPC conventions
- Single initialization with
sync.Oncepattern
ORM/Database
- Fixed pointer field scanning for zero values
- Proper NULL vs zero value distinction
- Enhanced
getValueInterface()with nil pointer initialization - Support for
sql.Null*types
gRPC Client
- Changed NonBlock default to true
- Added deprecated
WithBlock()option for compatibility - Explicit handling of both blocking and non-blocking modes
- Updated client initialization logic
Testing & Quality
- Added comprehensive test coverage for all changes
- Edge case handling in ORM tests
- Gateway trace header test cases
- Consistent hash balancer benchmarks
🙏 Acknowledgments
Special thanks to all contributors, issue reporters, and community members who made this release possible. Your feedback and contributions continue to make go-zero better!
💬 Feedback
If you encounter any issues or have suggestions for future releases, please:
- Open an issue: https://github.com/zeromicro/go-zero/issues
Happy coding with go-zero! 🚀