1
0
mirror of https://github.com/redis/go-redis.git synced 2025-07-22 10:01:50 +03:00
Files
go-redis/docs/clients-and-connections.md
Nedyalko Dyakov f316244da4 docs(devdocs): Add generated dev documentation
Add AI generated dev documentation in the `docs` folder.
2025-04-29 23:37:03 +03:00

874 lines
21 KiB
Markdown

# Redis Client Architecture
This document explains the relationships between different components of the Redis client implementation, focusing on client types, connections, pools, and hooks.
## Client Hierarchy
### Component Relationships
```mermaid
classDiagram
class baseClient {
+*Options opt
+pool.Pooler connPool
+hooksMixin
+onClose func() error
+clone() *baseClient
+initConn()
+process()
}
class Client {
+baseClient
+cmdable
+hooksMixin
+NewClient()
+WithTimeout()
+Pipeline()
+TxPipeline()
}
class Conn {
+baseClient
+cmdable
+statefulCmdable
+hooksMixin
+newConn()
}
class Pipeline {
+exec pipelineExecer
+init()
+Pipelined()
}
class TxPipeline {
+Pipeline
+wrapMultiExec()
}
class hooksMixin {
+*sync.RWMutex hooksMu
+[]Hook slice
+hooks initial
+hooks current
+clone() hooksMixin
+AddHook()
+chain()
}
Client --> baseClient : embeds
Conn --> baseClient : embeds
Pipeline --> Client : uses
TxPipeline --> Pipeline : extends
baseClient --> hooksMixin : contains
Client --> hooksMixin : contains
Conn --> hooksMixin : contains
```
### Hook Chain Flow
```mermaid
sequenceDiagram
participant Client
participant Hook1
participant Hook2
participant Redis
Client->>Hook1: Execute Command
Hook1->>Hook2: Next Hook
Hook2->>Redis: Execute Command
Redis-->>Hook2: Response
Hook2-->>Hook1: Process Response
Hook1-->>Client: Final Response
```
### Connection Pool Management
```mermaid
stateDiagram-v2
[*] --> Idle
Idle --> InUse: Get Connection
InUse --> Idle: Release Connection
InUse --> Closed: Connection Error
Idle --> Closed: Pool Shutdown
Closed --> [*]
```
### Pipeline Execution Flow
```mermaid
sequenceDiagram
participant Client
participant Pipeline
participant Redis
Client->>Pipeline: Queue Command 1
Client->>Pipeline: Queue Command 2
Client->>Pipeline: Queue Command 3
Pipeline->>Redis: Send Batch
Redis-->>Pipeline: Batch Response
Pipeline-->>Client: Processed Responses
```
### Transaction Pipeline Flow
```mermaid
sequenceDiagram
participant Client
participant TxPipeline
participant Redis
Client->>TxPipeline: Queue Command 1
Client->>TxPipeline: Queue Command 2
TxPipeline->>Redis: MULTI
TxPipeline->>Redis: Command 1
TxPipeline->>Redis: Command 2
TxPipeline->>Redis: EXEC
Redis-->>TxPipeline: Transaction Result
TxPipeline-->>Client: Processed Results
```
### Base Client (`baseClient`)
The `baseClient` is the foundation of all Redis client implementations. It contains:
- Connection pool management
- Basic Redis command execution
- Hook management
- Connection lifecycle handling
```go
type baseClient struct {
opt *Options
connPool pool.Pooler
hooksMixin
onClose func() error
}
```
### Client Types
1. **Client (`Client`)**
- The main Redis client used by applications
- Represents a pool of connections
- Safe for concurrent use
- Embeds `baseClient` and adds command execution capabilities
- Primary entry point for most Redis operations
- Handles connection pooling and retries automatically
2. **Conn (`Conn`)**
- Represents a single Redis connection
- Used for stateful operations like pub/sub
- Required for blocking operations (BLPOP, BRPOP)
- Also embeds `baseClient`
- Has additional stateful command capabilities
- Not safe for concurrent use
3. **Pipeline (`Pipeline`)**
- Used for pipelining multiple commands
- Not a standalone client, but a wrapper around existing clients
- Batches commands and sends them in a single network roundtrip
4. **Transaction Pipeline (`TxPipeline`)**
- Similar to Pipeline but wraps commands in MULTI/EXEC
- Ensures atomic execution of commands
- Also a wrapper around existing clients
## Pointer vs Value Semantics
### When `baseClient` is a Pointer
The `baseClient` is used as a pointer in these scenarios:
1. **Client Creation**
```go
func NewClient(opt *Options) *Client {
c := Client{
baseClient: &baseClient{
opt: opt,
},
}
}
```
- Used as pointer to share the same base client instance
- Allows modifications to propagate to all references
- More efficient for large structs
2. **Connection Pooling**
- Pooled connections need to share the same base client configuration
- Pointer semantics ensure consistent behavior across pooled connections
### When `baseClient` is a Value
The `baseClient` is used as a value in these scenarios:
1. **Cloning**
```go
func (c *baseClient) clone() *baseClient {
clone := *c
clone.hooksMixin = c.hooksMixin.clone()
return &clone
}
```
- Creates independent copies for isolation
- Prevents unintended sharing of state
- Used when creating new connections or client instances
2. **Temporary Operations**
- When creating short-lived client instances
- When isolation is required for specific operations
## Hooks Management
### HooksMixin
The `hooksMixin` is a struct that manages hook chains for different operations:
```go
type hooksMixin struct {
hooksMu *sync.RWMutex
slice []Hook
initial hooks
current hooks
}
```
### Hook Types
1. **Dial Hook**
- Called during connection establishment
- Can modify connection parameters
- Used for custom connection handling
2. **Process Hook**
- Called before command execution
- Can modify commands or add logging
- Used for command monitoring
3. **Pipeline Hook**
- Called during pipeline execution
- Handles batch command processing
- Used for pipeline monitoring
### Hook Lifecycle
1. **Initialization**
- Hooks are initialized when creating a new client
- Default hooks are set up for basic operations
- Hooks can be added or removed at runtime
2. **Hook Chain**
- Hooks are chained in LIFO (Last In, First Out) order
- Each hook can modify the command or response
- Chain can be modified at runtime
- Hooks can prevent command execution by not calling next
3. **Hook Inheritance**
- New connections inherit hooks from their parent client
- Hooks are cloned to prevent shared state
- Each connection maintains its own hook chain
- Hook modifications in child don't affect parent
## Connection Pooling
### Pool Types
1. **Single Connection Pool**
- Used for dedicated connections
- No connection sharing
- Used in `Conn` type
2. **Multi Connection Pool**
- Used for client pools
- Manages multiple connections
- Handles connection reuse
### Pool Management
1. **Connection Acquisition**
- Connections are acquired from the pool
- Pool maintains minimum and maximum connections
- Handles connection timeouts
2. **Connection Release**
- Connections are returned to the pool
- Pool handles connection cleanup
- Manages connection lifecycle
### Pool Configuration
1. **Pool Options**
- Minimum idle connections
- Maximum active connections
- Connection idle timeout
- Connection lifetime
- Pool health check interval
2. **Health Checks**
- Periodic connection validation
- Automatic reconnection on failure
- Connection cleanup on errors
- Pool size maintenance
## Transaction and Pipeline Handling
### Pipeline
1. **Command Batching**
- Commands are queued in memory
- Sent in a single network roundtrip
- Responses are collected in order
2. **Error Handling**
- Pipeline execution is atomic
- Errors are propagated to all commands
- Connection errors trigger retries
### Transaction Pipeline
1. **MULTI/EXEC Wrapping**
- Commands are wrapped in MULTI/EXEC
- Ensures atomic execution
- Handles transaction errors
2. **State Management**
- Maintains transaction state
- Handles rollback scenarios
- Manages connection state
### Pipeline Limitations
1. **Size Limits**
- Maximum commands per pipeline
- Memory usage considerations
- Network buffer size limits
- Response size handling
2. **Transaction Behavior**
- WATCH/UNWATCH key monitoring
- Transaction isolation
- Rollback on failure
- Atomic execution guarantees
## Error Handling and Cleanup
### Error Types and Handling
```mermaid
classDiagram
class Error {
<<interface>>
+Error() string
}
class RedisError {
+string message
+Error() string
}
class ConnectionError {
+string message
+net.Error
+Temporary() bool
+Timeout() bool
}
class TxFailedError {
+string message
+Error() string
}
Error <|-- RedisError
Error <|-- ConnectionError
Error <|-- TxFailedError
net.Error <|-- ConnectionError
```
### Error Handling Flow
```mermaid
sequenceDiagram
participant Client
participant Connection
participant Redis
participant ErrorHandler
Client->>Connection: Execute Command
Connection->>Redis: Send Command
alt Connection Error
Redis-->>Connection: Connection Error
Connection->>ErrorHandler: Handle Connection Error
ErrorHandler->>Connection: Retry or Close
else Redis Error
Redis-->>Connection: Redis Error
Connection->>Client: Return Error
else Transaction Error
Redis-->>Connection: Transaction Failed
Connection->>Client: Return TxFailedError
end
```
### Connection and Client Cleanup
```mermaid
sequenceDiagram
participant User
participant Client
participant ConnectionPool
participant Connection
participant Hook
User->>Client: Close()
Client->>Hook: Execute Close Hooks
Hook-->>Client: Hook Complete
Client->>ConnectionPool: Close All Connections
ConnectionPool->>Connection: Close
Connection->>Redis: Close Connection
Redis-->>Connection: Connection Closed
Connection-->>ConnectionPool: Connection Removed
ConnectionPool-->>Client: Pool Closed
Client-->>User: Client Closed
```
### Error Handling Strategies
1. **Connection Errors**
- Temporary errors (network issues) trigger retries
- Permanent errors (invalid credentials) close the connection
- Connection pool handles reconnection attempts
- Maximum retry attempts configurable via options
2. **Redis Errors**
- Command-specific errors returned to caller
- No automatic retries for Redis errors
- Error types include:
- Command syntax errors
- Type errors
- Permission errors
- Resource limit errors
3. **Transaction Errors**
- MULTI/EXEC failures return `TxFailedError`
- Individual command errors within transaction
- Watch/Unwatch failures
- Connection errors during transaction
### Cleanup Process
1. **Client Cleanup**
```go
func (c *Client) Close() error {
// Execute close hooks
if c.onClose != nil {
c.onClose()
}
// Close connection pool
return c.connPool.Close()
}
```
- Executes registered close hooks
- Closes all connections in pool
- Releases all resources
- Thread-safe operation
2. **Connection Cleanup**
```go
func (c *Conn) Close() error {
// Cleanup connection state
c.state = closed
// Close underlying connection
return c.conn.Close()
}
```
- Closes underlying network connection
- Cleans up connection state
- Removes from connection pool
- Handles pending operations
3. **Pool Cleanup**
- Closes all idle connections
- Waits for in-use connections
- Handles connection timeouts
- Releases pool resources
### Best Practices for Error Handling
1. **Connection Management**
- Always check for connection errors
- Implement proper retry logic
- Handle connection timeouts
- Monitor connection pool health
2. **Resource Cleanup**
- Always call Close() when done
- Use defer for cleanup in critical sections
- Handle cleanup errors
- Monitor resource usage
3. **Error Recovery**
- Implement circuit breakers
- Use backoff strategies
- Monitor error patterns
- Log error details
4. **Transaction Safety**
- Check transaction results
- Handle watch/unwatch failures
- Implement rollback strategies
- Monitor transaction timeouts
### Context and Cancellation
1. **Context Usage**
- Command execution timeout
- Connection establishment timeout
- Operation cancellation
- Resource cleanup on cancellation
2. **Pool Error Handling**
- Connection acquisition timeout
- Pool exhaustion handling
- Connection validation errors
- Resource cleanup on errors
## Best Practices
1. **Client Usage**
- Use `Client` for most operations
- Use `Conn` for stateful operations
- Use pipelines for batch operations
2. **Hook Implementation**
- Keep hooks lightweight
- Handle errors properly
- Call next hook in chain
3. **Connection Management**
- Let the pool handle connections
- Don't manually manage connections
- Use appropriate timeouts
4. **Error Handling**
- Check command errors
- Handle connection errors
- Implement retry logic when needed
## Deep Dive: baseClient Embedding Strategies
### Implementation Examples
1. **Client Implementation (Pointer Embedding)**
```go
type Client struct {
*baseClient // Pointer embedding
cmdable
hooksMixin
}
func NewClient(opt *Options) *Client {
c := Client{
baseClient: &baseClient{ // Created as pointer
opt: opt,
},
}
c.init()
c.connPool = newConnPool(opt, c.dialHook)
return &c
}
```
The `Client` uses pointer embedding because:
- It needs to share the same `baseClient` instance across all operations
- The `baseClient` contains connection pool and options that should be shared
- Modifications to the base client (like timeouts) should affect all operations
- More efficient for large structs since it avoids copying
2. **Conn Implementation (Value Embedding)**
```go
type Conn struct {
baseClient // Value embedding
cmdable
statefulCmdable
hooksMixin
}
func newConn(opt *Options, connPool pool.Pooler, parentHooks hooksMixin) *Conn {
c := Conn{
baseClient: baseClient{ // Created as value
opt: opt,
connPool: connPool,
},
}
c.cmdable = c.Process
c.statefulCmdable = c.Process
c.hooksMixin = parentHooks.clone()
return &c
}
```
The `Conn` uses value embedding because:
- Each connection needs its own independent state
- Connections are short-lived and don't need to share state
- Prevents unintended sharing of connection state
- More memory efficient for single connections
3. **Tx (Transaction) Implementation (Value Embedding)**
```go
type Tx struct {
baseClient // Value embedding
cmdable
statefulCmdable
hooksMixin
}
func (c *Client) newTx() *Tx {
tx := Tx{
baseClient: baseClient{ // Created as value
opt: c.opt,
connPool: pool.NewStickyConnPool(c.connPool),
},
hooksMixin: c.hooksMixin.clone(),
}
tx.init()
return &tx
}
```
The `Tx` uses value embedding because:
- Transactions need isolated state
- Each transaction has its own connection pool
- Prevents transaction state from affecting other operations
- Ensures atomic execution of commands
4. **Pipeline Implementation (No baseClient)**
```go
type Pipeline struct {
cmdable
statefulCmdable
exec pipelineExecer
cmds []Cmder
}
```
The `Pipeline` doesn't embed `baseClient` because:
- It's a temporary command buffer
- Doesn't need its own connection management
- Uses the parent client's connection pool
- More lightweight without base client overhead
### Embedding Strategy Comparison
```mermaid
classDiagram
class Client {
+*baseClient
+cmdable
+hooksMixin
+NewClient()
+WithTimeout()
}
class Conn {
+baseClient
+cmdable
+statefulCmdable
+hooksMixin
+newConn()
}
class Tx {
+baseClient
+cmdable
+statefulCmdable
+hooksMixin
+newTx()
}
class Pipeline {
+cmdable
+statefulCmdable
+exec pipelineExecer
+cmds []Cmder
}
Client --> baseClient : pointer
Conn --> baseClient : value
Tx --> baseClient : value
Pipeline --> baseClient : none
```
### Key Differences in Embedding Strategy
1. **Pointer Embedding (Client)**
- Used when state needs to be shared
- More efficient for large structs
- Allows modifications to propagate
- Better for long-lived instances
- Memory Layout:
```
+-------------------+
| Client |
| +-------------+ |
| | *baseClient | |
| +-------------+ |
| | cmdable | |
| +-------------+ |
| | hooksMixin | |
| +-------------+ |
+-------------------+
```
2. **Value Embedding (Conn, Tx)**
- Used when isolation is needed
- Prevents unintended state sharing
- Better for short-lived instances
- More memory efficient for small instances
- Memory Layout:
```
+-------------------+
| Conn/Tx |
| +-------------+ |
| | baseClient | |
| | +---------+ | |
| | | Options | | |
| | +---------+ | |
| | | Pooler | | |
| | +---------+ | |
| +-------------+ |
| | cmdable | |
| +-------------+ |
| | hooksMixin | |
| +-------------+ |
+-------------------+
```
3. **No Embedding (Pipeline)**
- Used for temporary operations
- Minimizes memory overhead
- Relies on parent client
- Better for command batching
- Memory Layout:
```
+-------------------+
| Pipeline |
| +-------------+ |
| | cmdable | |
| +-------------+ |
| | exec | |
| +-------------+ |
| | cmds | |
| +-------------+ |
+-------------------+
```
### Design Implications
1. **Resource Management**
- Pointer embedding enables shared resource management
- Value embedding ensures resource isolation
- No embedding minimizes resource overhead
2. **State Management**
- Pointer embedding allows state propagation
- Value embedding prevents state leakage
- No embedding avoids state management
3. **Performance Considerations**
- Pointer embedding reduces memory usage for large structs
- Value embedding improves locality for small structs
- No embedding minimizes memory footprint
4. **Error Handling**
- Pointer embedding centralizes error handling
- Value embedding isolates error effects
- No embedding delegates error handling
5. **Cleanup Process**
- Pointer embedding requires coordinated cleanup
- Value embedding enables independent cleanup
- No embedding avoids cleanup complexity
### Best Practices
1. **When to Use Pointer Embedding**
- Long-lived instances
- Shared state requirements
- Large structs
- Centralized management
2. **When to Use Value Embedding**
- Short-lived instances
- State isolation needs
- Small structs
- Independent management
3. **When to Avoid Embedding**
- Temporary operations
- Minimal state needs
- Command batching
- Performance critical paths
### Authentication System
#### Streaming Credentials Provider
The Redis client supports a streaming credentials provider system that allows for dynamic credential updates:
```go
type StreamingCredentialsProvider interface {
Subscribe(listener CredentialsListener) (Credentials, UnsubscribeFunc, error)
}
type CredentialsListener interface {
OnNext(credentials Credentials)
OnError(err error)
}
type Credentials interface {
BasicAuth() (username string, password string)
RawCredentials() string
}
```
Key Features:
- Dynamic credential updates
- Error handling and propagation
- Basic authentication support
- Raw credential access
- Subscription management
#### Re-Authentication Listener
The client includes a re-authentication listener for handling credential updates:
```go
type ReAuthCredentialsListener struct {
reAuth func(credentials Credentials) error
onErr func(err error)
}
```
Features:
- Automatic re-authentication on credential updates
- Error handling and propagation
- Customizable re-authentication logic
- Thread-safe operation
#### Basic Authentication
The client provides a basic authentication implementation:
```go
type basicAuth struct {
username string
password string
}
func NewBasicCredentials(username, password string) Credentials {
return &basicAuth{
username: username,
password: password,
}
}
```
Usage:
- Simple username/password authentication
- Raw credential string generation
- Basic authentication support
- Thread-safe operation
// ... rest of existing content ...