sectionZookeeper [c159b3c2]
1. API
1. API
-
create(path, data[], mode, flag)- Creates a znode, like an inode, but a unit of abstraction to lock on it
-
setData(path, data[], version)- sets the data[] in the znode at the specified flag
-
getData(path, watch)- returns
data[], also allows clients to watch
- returns
-
getChildren(path, watch)- Returns all the children names of the znode at the specified path
-
exists(path, watch)- Checks whether a znode exists
-
delete(path, version)- Deletes a znode. Must match the monotonically increasing versions
2. Server
2. Server
- Fully replicated, like etcd, into a set called a
ZooKeeper ensemble - Elected leaders and others become followers
-
Unlike chubby, clients can connect to any
- Higher availablity, not as strong consistency
- Use
sync()to assure that you have the highest consistency
- Leader broadcasts operations to the followers, and performs write operation on the coordination data placed in its memory
-
Follower can also recieve and respond to write requests. Multiple writes can be batched, but only the request needs to forwarded to the leader, the leader broadcasts all the requests to other followers
- Basically a follower gets a write, sends a request to the leader, the leader broadcasts the state to everyone after it's done
3. Replicated Database
3. Replicated Database
- In memory copy of the database so that reads and writes can be done locally
-
ZK takes periodic snapshots of all the delivered messages as a WAL
- Snapshots are fuzzy
- Enables at-most-once execution
-
If a server dies before the next snapshot is taken, it does a depth first scan of the tree to read every znode's metadata and data atomically then write that metadata and data from disk, extracted from the WAL
- This is why ZK recovery is slow
4. Atomic Broadcast
4. Atomic Broadcast
-
Used by ZK to broadcast the write request to the replicated database on all servers
- Followers forward writes to leaders
-
Uses the ZAB protocol
-
Two modes
- Broadcast is used to send messages
- Recovery is used to syncronize
- Default is $2f + 1$ for quorum where f is the number of faults
-
- When a leader dies, the new leader is elected and ensures all the updates from the previous leader are incorporated into its replicated db before it broadcasts its own requests
- At every transaction, the leader is broadcasting what it is working on into a replicated queue
5. Request Processor
5. Request Processor
- Manager that keeps the transactions atomic, only the leader uses this
- Transactions within ZK are idempotent
- All requests are linearized on the leader, so the leader converts everything into a
setDataTXN
6. Client-server interactino
6. Client-server interactino
getData()andgetChildren()can both be performed locally without notifying others- Servers generate
zxidfor every read request and retrieves the most recently updated state of the server's data -
Writes have multistep
- Write the data
- Notify the client(s) who have set watches on that data
- Sent the write request to the leader so it can be updated in the replicated database
- Data is replicated among all connected servers in the ensemble
- Everything is FIFO, including read requests when there's a write
- However if a read is in progress, multiple readers can read in parallel
- Like a giant R/W lock
- Default is async transmission of data, which comes at the cost of insconsistent or stale reads
- the
zxidallows the client to maintain a consistent view across different servers - Session timeouts and heartbeats are used to maintain connections from servers to clients for watches, which is decided by the leader
7. Primitives
7. Primitives
- zookeeper.apache.org/doc/r3.4.2/recipes.pdf
-
Config management and service discovery
newZnode = create("/config/port", 8090, EMPHEMERAL) # set a port getData("/config/port", true) # get a watch setData("/config/port", value, version) # new version -
Rendezvous
newZnode = create("/rendezvous/candidateOne", "", EMPHEMERAL) # create a znode to write getData("/rendezvous/candidateOne", true) # get a watch setData("/rendevous/candidateOne", {"10.0.0.1", 8080}) -
Group Membership
newZnode = create("/groups/member0001", EMPHEMERAL) newZnode = create("/groups/member0001/processOne", EMPHERAL,SEQUENTIAL) # set a sequential flag to generate a unique name childrenList = getChildren(/groups/member0001", true) -
Locks
newLock = create("/locks/lock-1", EMPHEMERAL) exists("/locks", true) # watch the lock tree delete(newLock) # to release the lock-
issues
-
herding: when a lock is released, many clients stampede to get the lock
-
Solved with using
SEQUENTIAL, at which point the lowest sequence number will hold the locknewZnode = create(path + "/lock-", EPHEMERAL, SEQUENTIAL) do childrenList = getChildren(path, false) if newZnode is lowest znode in children exit newPath = znode in childrenList ordered just before newZnode if exists(newPath, true) wait for event while true
-
-
exclusive lock only
-
Can implement R/W locks with
newZnode = create(path + "/write-", EPHEMERAL, SEQUENTIAL) do childrenList = getChildren(path, false) if newZnode is lowest znode in children exit newPath = znode in childrenList ordered just before newZnode if exists(newPath, true) wait for event while true - create a read lock and then reads can happen with other reads, but blocks on writes, and writes block on reads
-
-
-
Barriers
newZnode = create(path + "/" + processName) exists(path + "/ready", true) newZnodeChild = create(newZnode, EPHEMERAL) childrenList = getChildren(path, false) if fewer children in childrenList than barrierThreshold wait for watch event else create(path + "/ready", REGULAR)
-
8. Performance
8. Performance
-
Better load distribution than chubby

- Atomic broadcast decreases performance, has a CPU cost
- Failures of leaders will grind application to zero while it recovers
- Solves chubby like problems with different tradeoffs, gives up consistency for a bit of throughput