SlideShare une entreprise Scribd logo
1  sur  69
Télécharger pour lire hors ligne
DISTRIBUTED
PATTERNS IN
ACTION
Eric Redmond
@coderoshi
http://git.io/MYrjpQ
basho
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
RESOURCE EXPANSION
(SOLUTION: SHARDING)
Thursday, July 25, 13
SHARDING INCREASES RISK
Thursday, July 25, 13
FAULT-TOLERANCE
(SOLUTION: REPLICATION)
Thursday, July 25, 13
REPLICATION ISTHE
ROOT OF ALL EVIL
Thursday, July 25, 13
THE CAUSE OF MOST
NETWORK PARTITIONS
Thursday, July 25, 13
THE CAPTHEOREM SUCKS
• Consistent
• Available
• Partition-Tolerant*
* http://codahale.com/you-cant-sacrifice-partition-tolerance
Thursday, July 25, 13
DON’T DISTRIBUTE DATASTORES,
STORE DISTRIBUTED DATA
Thursday, July 25, 13
IF IT CAN HAPPEN,
AT SCALE IT WILL HAPPEN
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
h	
  =	
  NaiveHash.new(("A".."J").to_a)
tracknodes	
  =	
  Array.new(100000)
100000.times	
  do	
  |i|
	
  	
  tracknodes[i]	
  =	
  h.node(i)
end
h.add("K")
misses	
  =	
  0
100000.times	
  do	
  |i|
	
  	
  misses	
  +=	
  1	
  if	
  tracknodes[i]	
  !=	
  h.node(i)
end
puts	
  "misses:	
  #{(misses.to_f/100000)	
  *	
  100}%"
misses:	
  90.922%
Thursday, July 25, 13
0
2160/2
2160
a single partition
SHA1(Key)
ring with 32 partitions
Node 0
Node 1
Node 2
Thursday, July 25, 13
0
2160/2
2160
a single partition
ring with 32 partitions
Node 0
Node 1
Node 2
Node 3
SHA1(Key)
Thursday, July 25, 13
SHA1BITS	
  =	
  160
class	
  PartitionedConsistentHash
	
  	
  def	
  initialize(nodes=[],	
  partitions=32)
	
  	
  	
  	
  @partitions	
  =	
  partitions
	
  	
  	
  	
  @nodes,	
  @ring	
  =	
  nodes.clone.sort,	
  {}
	
  	
  	
  	
  @power	
  =	
  SHA1BITS	
  -­‐	
  Math.log2(partitions).to_i
	
  	
  	
  	
  @partitions.times	
  do	
  |i|
	
  	
  	
  	
  	
  	
  @ring[range(i)]	
  =	
  @nodes[0]
	
  	
  	
  	
  	
  	
  @nodes	
  <<	
  @nodes.shift
	
  	
  	
  	
  end
	
  	
  	
  	
  @nodes.sort!
	
  	
  end
	
  	
  def	
  range(partition)
	
  	
  	
  	
  (partition*(2**@power)..(partition+1)*(2**@power)-­‐1)
	
  	
  end
	
  	
  def	
  hash(key)
	
  	
  	
  	
  Digest::SHA1.hexdigest(key.to_s).hex
	
  	
  end
	
  	
  def	
  add(node)
	
  	
  	
  	
  @nodes	
  <<	
  node
	
  	
  	
  	
  partition_pow	
  =	
  Math.log2(@partitions)
	
  	
  	
  	
  pow	
  =	
  SHA1BITS	
  -­‐	
  partition_pow.to_i
	
  	
  	
  	
  (0..@partitions).step(@nodes.length)	
  do	
  |i|
	
  	
  	
  	
  	
  	
  @ring[range(i,	
  pow)]	
  =	
  node
	
  	
  	
  	
  end
	
  	
  end
	
  	
  def	
  node(keystr)
	
  	
  	
  	
  return	
  nil	
  if	
  @ring.empty?
	
  	
  	
  	
  key	
  =	
  hash(keystr)
	
  	
  	
  	
  @ring.each	
  do	
  |range,	
  node|
	
  	
  	
  	
  	
  	
  return	
  node	
  if	
  range.cover?(key)
	
  	
  	
  	
  end
	
  	
  end
end
h	
  =	
  PartitionedConsistentHash.new(("A".."J").to_a)
nodes	
  =	
  Array.new(100000)
100000.times	
  do	
  |i|
	
  	
  nodes[i]	
  =	
  h.node(i)
end
puts	
  "add	
  K"
h.add("K")
misses	
  =	
  0
100000.times	
  do	
  |i|
	
  	
  misses	
  +=	
  1	
  if	
  nodes[i]	
  !=	
  h.node(i)
end
puts	
  "misses:	
  #{(misses.to_f/100000)	
  *	
  100}%n"
misses:	
  9.473%
Thursday, July 25, 13
class	
  Node
	
  	
  def	
  initialize(name,	
  nodes=[],	
  partitions=32)
	
  	
  	
  	
  @name	
  =	
  name
	
  	
  	
  	
  @data	
  =	
  {}
	
  	
  	
  	
  @ring	
  =	
  ConsistentHash.new(nodes,	
  partitions)
	
  	
  end
	
  	
  def	
  put(key,	
  value)
	
  	
  	
  	
  if	
  @name	
  ==	
  @ring.node(key)
	
  	
  	
  	
  	
  	
  puts	
  "put	
  #{key}	
  #{value}"
	
  	
  	
  	
  	
  	
  @data[	
  @ring.hash(key)	
  ]	
  =	
  value
	
  	
  	
  	
  end
	
  	
  end
	
  	
  def	
  get(key)
	
  	
  	
  	
  if	
  @name	
  ==	
  @ring.node(key)
	
  	
  	
  	
  	
  	
  puts	
  "get	
  #{key}"
	
  	
  	
  	
  	
  	
  @data[@ring.hash(key)]
	
  	
  	
  	
  end
	
  	
  end
end
Thursday, July 25, 13
nodeA	
  =	
  Node.new(	
  'A',	
  ['A',	
  'B',	
  'C']	
  )
nodeB	
  =	
  Node.new(	
  'B',	
  ['A',	
  'B',	
  'C']	
  )
nodeC	
  =	
  Node.new(	
  'C',	
  ['A',	
  'B',	
  'C']	
  )
nodeA.put(	
  "foo",	
  "bar"	
  )
p	
  nodeA.get(	
  "foo"	
  )	
  	
  	
  #	
  nil
nodeB.put(	
  "foo",	
  "bar"	
  )
p	
  nodeB.get(	
  "foo"	
  )	
  	
  	
  #	
  "bar"
nodeC.put(	
  "foo",	
  "bar"	
  )
p	
  nodeC.get(	
  "foo"	
  )	
  	
  	
  #	
  nil
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Client Service
Request
Reply
Thursday, July 25, 13
module	
  Services
	
  	
  def	
  connect(port=2200,	
  ip="127.0.0.1")
	
  	
  	
  	
  ctx	
  =	
  ZMQ::Context.new
	
  	
  	
  	
  sock	
  =	
  ctx.socket(	
  ZMQ::REQ	
  )
	
  	
  	
  	
  sock.connect(	
  "tcp://#{ip}:#{port}"	
  )
	
  	
  	
  	
  sock
	
  	
  end
	
  	
  def	
  service(port)
	
  	
  	
  	
  thread	
  do
	
  	
  	
  	
  	
  	
  ctx	
  =	
  ZMQ::Context.new
	
  	
  	
  	
  	
  	
  rep	
  =	
  ctx.socket(	
  ZMQ::REP	
  )
	
  	
  	
  	
  	
  	
  rep.bind(	
  "tcp://127.0.0.1:#{port}"	
  )
	
  	
  	
  	
  	
  	
  while	
  line	
  =	
  rep.recv
	
  	
  	
  	
  	
  	
  	
  	
  msg,	
  payload	
  =	
  line.split('	
  ',	
  2)
	
  	
  	
  	
  	
  	
  	
  	
  send(	
  msg.to_sym,	
  rep,	
  payload	
  )	
  	
  	
  	
  	
  #	
  EVVVIILLLL!!!
	
  	
  	
  	
  	
  	
  end
	
  	
  	
  	
  end
	
  	
  end
	
  	
  def	
  method_missing(method,	
  *args,	
  &block)
	
  	
  	
  	
  socket,	
  payload	
  =	
  args
	
  	
  	
  	
  payload.send(	
  "bad	
  message"	
  )	
  if	
  payload
	
  	
  end
end
Thursday, July 25, 13
class	
  Node
	
  	
  include	
  Configuration
	
  	
  include	
  Threads
	
  	
  include	
  Services
	
  	
  def	
  start()
	
  	
  	
  	
  service(	
  config("port")	
  )
	
  	
  	
  	
  puts	
  "#{@name}	
  started"
	
  	
  	
  	
  join_threads()
	
  	
  end
	
  	
  def	
  remote_call(name,	
  message)
	
  	
  	
  	
  puts	
  "#{name}	
  <=	
  #{message}"
	
  	
  	
  	
  req	
  =	
  connect(config("port",	
  name),	
  config("ip",	
  name))
	
  	
  	
  	
  resp	
  =	
  req.send(message)	
  &&	
  req.recv
	
  	
  	
  	
  req.close
	
  	
  	
  	
  resp
	
  	
  end
	
  	
  #	
  ...
Thursday, July 25, 13
 	
  #	
  ...
	
  	
  def	
  put(socket,	
  payload)
	
  	
  	
  	
  key,	
  value	
  =	
  payload.split('	
  ',	
  2)
	
  	
  	
  	
  socket.send(	
  do_put(key,	
  value).to_s	
  )
	
  	
  end
	
  	
  def	
  do_put(key,	
  value)
	
  	
  	
  	
  node	
  =	
  @ring.node(key)
	
  	
  	
  	
  if	
  node	
  ==	
  @name
	
  	
  	
  	
  	
  	
  puts	
  "put	
  #{key}	
  #{value}"
	
  	
  	
  	
  	
  	
  @data[@ring.hash(key)]	
  =	
  value
	
  	
  	
  	
  else
	
  	
  	
  	
  	
  	
  remote_call(node,	
  "put	
  #{key}	
  #{value}"	
  )
	
  	
  	
  	
  end
	
  	
  end
Thursday, July 25, 13
#	
  start	
  a	
  Node	
  as	
  a	
  Server
name	
  =	
  ARGV.first
node	
  =	
  Node.new(name,	
  ['A','B','C'])
node.start()
$	
  ruby	
  node.rb	
  A
$	
  ruby	
  node.rb	
  B
$	
  ruby	
  node.rb	
  C
#	
  connect	
  with	
  a	
  client
require	
  'zmq'
ctx	
  =	
  ZMQ::Context.new
req	
  =	
  ctx.socket(ZMQ::REQ)
req.connect(	
  "tcp://127.0.0.1:2200"	
  )
puts	
  "Inserting	
  Values"
1000.times	
  do	
  |i|
	
  	
  req.send(	
  "put	
  key#{i}	
  value#{i}"	
  )	
  &&	
  req.recv
end
puts	
  "Getting	
  Values"
1000.times	
  do	
  |i|
	
  	
  puts	
  req.send(	
  "get	
  key#{i}"	
  )	
  &&	
  req.recv
end
req.close
Thursday, July 25, 13
Thursday, July 25, 13
Publisher
Subscriber
Subscriber
Subscriber
Thursday, July 25, 13
class	
  Node
	
  	
  #	
  ...
	
  	
  def	
  coordinate_cluster(pub_port,	
  rep_port)
	
  	
  	
  	
  thread	
  do
	
  	
  	
  	
  	
  	
  ctx	
  =	
  ZMQ::Context.new
	
  	
  	
  	
  	
  	
  pub	
  =	
  ctx.socket(	
  ZMQ::PUB	
  )
	
  	
  	
  	
  	
  	
  pub.bind(	
  "tcp://*:#{pub_port}"	
  )
	
  	
  	
  	
  	
  	
  rep	
  =	
  ctx.socket(	
  ZMQ::REP	
  )
	
  	
  	
  	
  	
  	
  rep.bind(	
  "tcp://*:#{rep_port}"	
  )
	
  	
  	
  	
  	
  	
  while	
  line	
  =	
  rep.recv
	
  	
  	
  	
  	
  	
  	
  	
  msg,	
  node	
  =	
  line.split('	
  ',	
  2)
	
  	
  	
  	
  	
  	
  	
  	
  nodes	
  =	
  @ring.nodes
	
  	
  	
  	
  	
  	
  	
  	
  case	
  msg
	
  	
  	
  	
  	
  	
  	
  	
  when	
  'join'
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  nodes	
  =	
  (nodes	
  <<	
  node).uniq.sort
	
  	
  	
  	
  	
  	
  	
  	
  when	
  'down'
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  nodes	
  -­‐=	
  [node]
	
  	
  	
  	
  	
  	
  	
  	
  end
	
  	
  	
  	
  	
  	
  	
  	
  @ring.cluster(nodes)
	
  	
  	
  	
  	
  	
  	
  	
  pub.send(	
  "ring	
  "	
  +	
  nodes.join(','))
	
  	
  	
  	
  	
  	
  	
  	
  rep.send(	
  "true"	
  )
	
  	
  	
  	
  	
  	
  end
	
  	
  	
  	
  end
	
  	
  end
Thursday, July 25, 13
class	
  Node
	
  	
  #	
  ...
	
  	
  def	
  track_cluster(sub_port)
	
  	
  	
  	
  thread	
  do
	
  	
  	
  	
  	
  	
  ctx	
  =	
  ZMQ::Context.new
	
  	
  	
  	
  	
  	
  sub	
  =	
  ctx.socket(	
  ZMQ::SUB	
  )
	
  	
  	
  	
  	
  	
  sub.connect(	
  "tcp://127.0.0.1:#{sub_port}"	
  )
	
  	
  	
  	
  	
  	
  sub.setsockopt(	
  ZMQ::SUBSCRIBE,	
  "ring"	
  )
	
  	
  	
  	
  	
  	
  
	
  	
  	
  	
  	
  	
  while	
  line	
  =	
  sub.recv
	
  	
  	
  	
  	
  	
  	
  	
  _,	
  nodes	
  =	
  line.split('	
  ',	
  2)
	
  	
  	
  	
  	
  	
  	
  	
  nodes	
  =	
  nodes.split(',').map{|x|	
  x.strip}
	
  	
  	
  	
  	
  	
  	
  	
  @ring.cluster(	
  nodes	
  )
	
  	
  	
  	
  	
  	
  	
  	
  puts	
  "ring	
  changed:	
  #{nodes.inspect}"
	
  	
  	
  	
  	
  	
  end
	
  	
  	
  	
  end
	
  	
  end
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
 	
  def	
  replicate(message,	
  n)
	
  	
  	
  	
  list	
  =	
  @ring.pref_list(n)
	
  	
  	
  	
  results	
  =	
  []
	
  	
  	
  	
  while	
  replicate_node	
  =	
  list.shift
	
  	
  	
  	
  	
  	
  results	
  <<	
  remote_call(replicate_node,	
  message)
	
  	
  	
  	
  end
	
  	
  	
  	
  results
	
  	
  end
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
WHATTO EAT FOR DINNER?
• Adam wants Pizza
{value:"pizza", vclock:{adam:1}}
• Barb wantsTacos
{value:"tacos", vclock:{barb:1}}
• Adam gets the value, the system can’t resolve, so he gets bolth
[{value:"pizza", vclock:{adam:1}},
{value:"tacos", vclock:{barb:1}}]
• Adam resolves the value however he wants
{value:"taco pizza", vclock:{adam:2, barb:1}}
Thursday, July 25, 13
#	
  artificially	
  create	
  a	
  conflict	
  with	
  vclocks
req.send('put	
  1	
  foo	
  {"B":1}	
  hello1')	
  &&	
  req.recv
req.send('put	
  1	
  foo	
  {"C":1}	
  hello2')	
  &&	
  req.recv
puts	
  req.send("get	
  2	
  foo")	
  &&	
  req.recv
sleep	
  5
#	
  resolve	
  the	
  conflict	
  by	
  decending	
  from	
  one	
  of	
  the	
  vclocks
req.send('put	
  2	
  foo	
  {"B":3}	
  hello1')	
  &&	
  req.recv
puts	
  req.send("get	
  2	
  foo")	
  &&	
  req.recv
Thursday, July 25, 13
• choose a value at random
• siblings (user resolution)
• defined resolution (eg. CRDT)
CONFLICT RESOLUTION
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
1.
2.
3.
4.
Thursday, July 25, 13
Thursday, July 25, 13
MERKEL TREEMERKEL TREE
Thursday, July 25, 13
* Thanks Joe Blomstedt
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
Thursday, July 25, 13
array	
  	
  =	
  [{value:1},{value:3},{value:5}]
mapped	
  =	
  array.map{|obj|	
  obj[:value]}
#	
  [1,	
  3,	
  5]
mapped.reduce(0){|sum,value|	
  sum	
  +	
  value}
#	
  9
Thursday, July 25, 13
Thursday, July 25, 13
module	
  Mapreduce
	
  	
  def	
  mr(socket,	
  payload)
	
  	
  	
  	
  map_func,	
  reduce_func	
  =	
  payload.split(/;s+reduce/,	
  2)
	
  	
  	
  	
  reduce_func	
  =	
  "reduce#{reduce_func}"
	
  	
  	
  	
  socket.send(	
  Reduce.new(reduce_func,	
  call_maps(map_func)).call.to_s	
  )
	
  	
  end
	
  	
  def	
  map(socket,	
  payload)
	
  	
  	
  	
  socket.send(	
  Map.new(payload,	
  @data).call.to_s	
  )
	
  	
  end
	
  	
  #	
  run	
  in	
  parallel,	
  then	
  join	
  results
	
  	
  def	
  call_maps(map_func)
	
  	
  	
  	
  results	
  =	
  []
	
  	
  	
  	
  nodes	
  =	
  @ring.nodes	
  -­‐	
  [@name]
	
  	
  	
  	
  nodes.map	
  {|node|
	
  	
  	
  	
  	
  	
  Thread.new	
  do
	
  	
  	
  	
  	
  	
  	
  	
  res	
  =	
  remote_call(node,	
  "map	
  #{map_func}")
	
  	
  	
  	
  	
  	
  	
  	
  results	
  +=	
  eval(res)
	
  	
  	
  	
  	
  	
  end
	
  	
  	
  	
  }.each{|w|	
  w.join}
	
  	
  	
  	
  results	
  +=	
  Map.new(map_func,	
  @data).call
	
  	
  end
end
Thursday, July 25, 13
module	
  Mapreduce
	
  	
  def	
  mr(socket,	
  payload)
	
  	
  	
  	
  map_func,	
  reduce_func	
  =	
  payload.split(/;s+reduce/,	
  2)
	
  	
  	
  	
  reduce_func	
  =	
  "reduce#{reduce_func}"
	
  	
  	
  	
  socket.send(	
  Reduce.new(reduce_func,	
  call_maps(map_func)).call.to_s	
  )
	
  	
  end
	
  	
  def	
  map(socket,	
  payload)
	
  	
  	
  	
  socket.send(	
  Map.new(payload,	
  @data).call.to_s	
  )
	
  	
  end
	
  	
  #	
  run	
  in	
  parallel,	
  then	
  join	
  results
	
  	
  def	
  call_maps(map_func)
	
  	
  	
  	
  results	
  =	
  []
	
  	
  	
  	
  nodes	
  =	
  @ring.nodes	
  -­‐	
  [@name]
	
  	
  	
  	
  nodes.map	
  {|node|
	
  	
  	
  	
  	
  	
  Thread.new	
  do
	
  	
  	
  	
  	
  	
  	
  	
  res	
  =	
  remote_call(node,	
  "map	
  #{map_func}")
	
  	
  	
  	
  	
  	
  	
  	
  results	
  +=	
  eval(res)
	
  	
  	
  	
  	
  	
  end
	
  	
  	
  	
  }.each{|w|	
  w.join}
	
  	
  	
  	
  results	
  +=	
  Map.new(map_func,	
  @data).call
	
  	
  end
end
Thursday, July 25, 13
200.times	
  do	
  |i|
	
  	
  req.send(	
  "put	
  2	
  key#{i}	
  {}	
  #{i}"	
  )	
  &&	
  req.recv
end
req.send(	
  "mr	
  map{|k,v|	
  [1]};	
  reduce{|vs|	
  vs.length}"	
  )
puts	
  req.recv
Thursday, July 25, 13
200.times	
  do	
  |i|
	
  	
  req.send(	
  "put	
  2	
  key#{i}	
  {}	
  #{i}"	
  )	
  &&	
  req.recv
end
req.send(	
  "mr	
  map{|k,v|	
  [1]};	
  reduce{|vs|	
  vs.length}"	
  )
puts	
  req.recv
Thursday, July 25, 13
ONE FINAL IMPROVEMENT
• C!
• A!
• P!
Thursday, July 25, 13
N/R/W
• N! # of Nodes to replicate a value to (in total)
• R! # of nodes to Read a value from (before success)
• W! # of nodes to Write a value to (before success)
Thursday, July 25, 13
Node A Node B Node C Node D Node E
N = 3 Write an Object
Node A Node B Node C Node D Node E
W = 2 Write an Object
replicate
replicate to
C & D
respond first
Node A Node B Node C Node D Node E
R = 2 Read an Object
C & E
respond first
eventually
replicate to
request from
Thursday, July 25, 13
EVENTUALLY CONSISTENT
• How Eventual?
• How Consistent?
Le mieux est l'ennemi du bien
Thursday, July 25, 13
Probabilistically Bounded Staleness
N=3, R=1,W=2
* http://pbs.cs.berkeley.edu
Thursday, July 25, 13
N=3,R=2,W=2
Thursday, July 25, 13
http://git.io/MYrjpQ
* thanks JD
Thursday, July 25, 13
Distributed Hash Ring
Vector Clocks
Preference List
MerkelTree
Read Repair
Key/Value
CRDT (counters, more coming)
Node Gossip
Request/
Response
Thursday, July 25, 13
basho
http://littleriakbook.comhttp://pragprog.com/book/rwdata
@coderoshi
Thursday, July 25, 13

Contenu connexe

Tendances

Code as data as code.
Code as data as code.Code as data as code.
Code as data as code.
Mike Fogus
 
Fertile Ground: The Roots of Clojure
Fertile Ground: The Roots of ClojureFertile Ground: The Roots of Clojure
Fertile Ground: The Roots of Clojure
Mike Fogus
 

Tendances (20)

D-Talk: What's awesome about Ruby 2.x and Rails 4
D-Talk: What's awesome about Ruby 2.x and Rails 4D-Talk: What's awesome about Ruby 2.x and Rails 4
D-Talk: What's awesome about Ruby 2.x and Rails 4
 
Go ahead, make my day
Go ahead, make my dayGo ahead, make my day
Go ahead, make my day
 
はじめてのGroovy
はじめてのGroovyはじめてのGroovy
はじめてのGroovy
 
Python postgre sql a wonderful wedding
Python postgre sql   a wonderful weddingPython postgre sql   a wonderful wedding
Python postgre sql a wonderful wedding
 
The Ring programming language version 1.5.2 book - Part 45 of 181
The Ring programming language version 1.5.2 book - Part 45 of 181The Ring programming language version 1.5.2 book - Part 45 of 181
The Ring programming language version 1.5.2 book - Part 45 of 181
 
Go for the paranoid network programmer
Go for the paranoid network programmerGo for the paranoid network programmer
Go for the paranoid network programmer
 
Code as data as code.
Code as data as code.Code as data as code.
Code as data as code.
 
Groovy ネタ NGK 忘年会2009 ライトニングトーク
Groovy ネタ NGK 忘年会2009 ライトニングトークGroovy ネタ NGK 忘年会2009 ライトニングトーク
Groovy ネタ NGK 忘年会2009 ライトニングトーク
 
Fertile Ground: The Roots of Clojure
Fertile Ground: The Roots of ClojureFertile Ground: The Roots of Clojure
Fertile Ground: The Roots of Clojure
 
Lập trình Python cơ bản
Lập trình Python cơ bảnLập trình Python cơ bản
Lập trình Python cơ bản
 
Groovy puzzlers по русски с Joker 2014
Groovy puzzlers по русски с Joker 2014Groovy puzzlers по русски с Joker 2014
Groovy puzzlers по русски с Joker 2014
 
Voce Tem Orgulho Do Seu Codigo
Voce Tem Orgulho Do Seu CodigoVoce Tem Orgulho Do Seu Codigo
Voce Tem Orgulho Do Seu Codigo
 
Dive into kotlins coroutines
Dive into kotlins coroutinesDive into kotlins coroutines
Dive into kotlins coroutines
 
Introduction to Groovy
Introduction to GroovyIntroduction to Groovy
Introduction to Groovy
 
Super Advanced Python –act1
Super Advanced Python –act1Super Advanced Python –act1
Super Advanced Python –act1
 
Current State of Coroutines
Current State of CoroutinesCurrent State of Coroutines
Current State of Coroutines
 
C++ Lambda and concurrency
C++ Lambda and concurrencyC++ Lambda and concurrency
C++ Lambda and concurrency
 
Implementing virtual machines in go & c 2018 redux
Implementing virtual machines in go & c 2018 reduxImplementing virtual machines in go & c 2018 redux
Implementing virtual machines in go & c 2018 redux
 
The Browser Environment - A Systems Programmer's Perspective
The Browser Environment - A Systems Programmer's PerspectiveThe Browser Environment - A Systems Programmer's Perspective
The Browser Environment - A Systems Programmer's Perspective
 
Kotlin coroutines
Kotlin coroutines Kotlin coroutines
Kotlin coroutines
 

En vedette (6)

HTTP is Hard
HTTP is HardHTTP is Hard
HTTP is Hard
 
Hardcore HTML
Hardcore HTMLHardcore HTML
Hardcore HTML
 
Hardcore CSS
Hardcore CSSHardcore CSS
Hardcore CSS
 
Riak Search 2: Yokozuna
Riak Search 2: YokozunaRiak Search 2: Yokozuna
Riak Search 2: Yokozuna
 
Yokozuna
YokozunaYokozuna
Yokozuna
 
DDS-20m
DDS-20mDDS-20m
DDS-20m
 

Similaire à Distributed Data Structures

Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
Dmitry Buzdin
 
Ruby Language - A quick tour
Ruby Language - A quick tourRuby Language - A quick tour
Ruby Language - A quick tour
aztack
 
What they don't tell you about JavaScript
What they don't tell you about JavaScriptWhat they don't tell you about JavaScript
What they don't tell you about JavaScript
Raphael Cruzeiro
 
Slides changes symfony23
Slides changes symfony23Slides changes symfony23
Slides changes symfony23
Javier López
 

Similaire à Distributed Data Structures (20)

Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
JavaOne 2013 - Clojure for Java Developers
JavaOne 2013 - Clojure for Java DevelopersJavaOne 2013 - Clojure for Java Developers
JavaOne 2013 - Clojure for Java Developers
 
ES6 is Nigh
ES6 is NighES6 is Nigh
ES6 is Nigh
 
Groovy
GroovyGroovy
Groovy
 
Java VS Python
Java VS PythonJava VS Python
Java VS Python
 
メタプログラミングPerl nagoya rubykaigi02
メタプログラミングPerl nagoya rubykaigi02メタプログラミングPerl nagoya rubykaigi02
メタプログラミングPerl nagoya rubykaigi02
 
What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)
 
Ruby Language - A quick tour
Ruby Language - A quick tourRuby Language - A quick tour
Ruby Language - A quick tour
 
ES6 PPT FOR 2016
ES6 PPT FOR 2016ES6 PPT FOR 2016
ES6 PPT FOR 2016
 
What they don't tell you about JavaScript
What they don't tell you about JavaScriptWhat they don't tell you about JavaScript
What they don't tell you about JavaScript
 
Concurrency: Rubies, Plural
Concurrency: Rubies, PluralConcurrency: Rubies, Plural
Concurrency: Rubies, Plural
 
Concurrency: Rubies, plural
Concurrency: Rubies, pluralConcurrency: Rubies, plural
Concurrency: Rubies, plural
 
Having Fun Programming!
Having Fun Programming!Having Fun Programming!
Having Fun Programming!
 
Kotlin @ Coupang Backend 2017
Kotlin @ Coupang Backend 2017Kotlin @ Coupang Backend 2017
Kotlin @ Coupang Backend 2017
 
Concurrent programming with Celluloid (MWRC 2012)
Concurrent programming with Celluloid (MWRC 2012)Concurrent programming with Celluloid (MWRC 2012)
Concurrent programming with Celluloid (MWRC 2012)
 
Elasticsearch's aggregations &amp; esctl in action or how i built a cli tool...
Elasticsearch's aggregations &amp; esctl in action  or how i built a cli tool...Elasticsearch's aggregations &amp; esctl in action  or how i built a cli tool...
Elasticsearch's aggregations &amp; esctl in action or how i built a cli tool...
 
Hidden Gems of Ruby 1.9
Hidden Gems of Ruby 1.9Hidden Gems of Ruby 1.9
Hidden Gems of Ruby 1.9
 
Slides changes symfony23
Slides changes symfony23Slides changes symfony23
Slides changes symfony23
 
ZeroMQ: Messaging Made Simple
ZeroMQ: Messaging Made SimpleZeroMQ: Messaging Made Simple
ZeroMQ: Messaging Made Simple
 
Hw09 Hadoop + Clojure
Hw09   Hadoop + ClojureHw09   Hadoop + Clojure
Hw09 Hadoop + Clojure
 

Dernier

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
Earley Information Science
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
Enterprise Knowledge
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
 

Dernier (20)

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 

Distributed Data Structures

  • 7. REPLICATION ISTHE ROOT OF ALL EVIL Thursday, July 25, 13
  • 8. THE CAUSE OF MOST NETWORK PARTITIONS Thursday, July 25, 13
  • 9. THE CAPTHEOREM SUCKS • Consistent • Available • Partition-Tolerant* * http://codahale.com/you-cant-sacrifice-partition-tolerance Thursday, July 25, 13
  • 10. DON’T DISTRIBUTE DATASTORES, STORE DISTRIBUTED DATA Thursday, July 25, 13
  • 11. IF IT CAN HAPPEN, AT SCALE IT WILL HAPPEN Thursday, July 25, 13
  • 14. h  =  NaiveHash.new(("A".."J").to_a) tracknodes  =  Array.new(100000) 100000.times  do  |i|    tracknodes[i]  =  h.node(i) end h.add("K") misses  =  0 100000.times  do  |i|    misses  +=  1  if  tracknodes[i]  !=  h.node(i) end puts  "misses:  #{(misses.to_f/100000)  *  100}%" misses:  90.922% Thursday, July 25, 13
  • 15. 0 2160/2 2160 a single partition SHA1(Key) ring with 32 partitions Node 0 Node 1 Node 2 Thursday, July 25, 13
  • 16. 0 2160/2 2160 a single partition ring with 32 partitions Node 0 Node 1 Node 2 Node 3 SHA1(Key) Thursday, July 25, 13
  • 17. SHA1BITS  =  160 class  PartitionedConsistentHash    def  initialize(nodes=[],  partitions=32)        @partitions  =  partitions        @nodes,  @ring  =  nodes.clone.sort,  {}        @power  =  SHA1BITS  -­‐  Math.log2(partitions).to_i        @partitions.times  do  |i|            @ring[range(i)]  =  @nodes[0]            @nodes  <<  @nodes.shift        end        @nodes.sort!    end    def  range(partition)        (partition*(2**@power)..(partition+1)*(2**@power)-­‐1)    end    def  hash(key)        Digest::SHA1.hexdigest(key.to_s).hex    end    def  add(node)        @nodes  <<  node        partition_pow  =  Math.log2(@partitions)        pow  =  SHA1BITS  -­‐  partition_pow.to_i        (0..@partitions).step(@nodes.length)  do  |i|            @ring[range(i,  pow)]  =  node        end    end    def  node(keystr)        return  nil  if  @ring.empty?        key  =  hash(keystr)        @ring.each  do  |range,  node|            return  node  if  range.cover?(key)        end    end end h  =  PartitionedConsistentHash.new(("A".."J").to_a) nodes  =  Array.new(100000) 100000.times  do  |i|    nodes[i]  =  h.node(i) end puts  "add  K" h.add("K") misses  =  0 100000.times  do  |i|    misses  +=  1  if  nodes[i]  !=  h.node(i) end puts  "misses:  #{(misses.to_f/100000)  *  100}%n" misses:  9.473% Thursday, July 25, 13
  • 18. class  Node    def  initialize(name,  nodes=[],  partitions=32)        @name  =  name        @data  =  {}        @ring  =  ConsistentHash.new(nodes,  partitions)    end    def  put(key,  value)        if  @name  ==  @ring.node(key)            puts  "put  #{key}  #{value}"            @data[  @ring.hash(key)  ]  =  value        end    end    def  get(key)        if  @name  ==  @ring.node(key)            puts  "get  #{key}"            @data[@ring.hash(key)]        end    end end Thursday, July 25, 13
  • 19. nodeA  =  Node.new(  'A',  ['A',  'B',  'C']  ) nodeB  =  Node.new(  'B',  ['A',  'B',  'C']  ) nodeC  =  Node.new(  'C',  ['A',  'B',  'C']  ) nodeA.put(  "foo",  "bar"  ) p  nodeA.get(  "foo"  )      #  nil nodeB.put(  "foo",  "bar"  ) p  nodeB.get(  "foo"  )      #  "bar" nodeC.put(  "foo",  "bar"  ) p  nodeC.get(  "foo"  )      #  nil Thursday, July 25, 13
  • 23. module  Services    def  connect(port=2200,  ip="127.0.0.1")        ctx  =  ZMQ::Context.new        sock  =  ctx.socket(  ZMQ::REQ  )        sock.connect(  "tcp://#{ip}:#{port}"  )        sock    end    def  service(port)        thread  do            ctx  =  ZMQ::Context.new            rep  =  ctx.socket(  ZMQ::REP  )            rep.bind(  "tcp://127.0.0.1:#{port}"  )            while  line  =  rep.recv                msg,  payload  =  line.split('  ',  2)                send(  msg.to_sym,  rep,  payload  )          #  EVVVIILLLL!!!            end        end    end    def  method_missing(method,  *args,  &block)        socket,  payload  =  args        payload.send(  "bad  message"  )  if  payload    end end Thursday, July 25, 13
  • 24. class  Node    include  Configuration    include  Threads    include  Services    def  start()        service(  config("port")  )        puts  "#{@name}  started"        join_threads()    end    def  remote_call(name,  message)        puts  "#{name}  <=  #{message}"        req  =  connect(config("port",  name),  config("ip",  name))        resp  =  req.send(message)  &&  req.recv        req.close        resp    end    #  ... Thursday, July 25, 13
  • 25.    #  ...    def  put(socket,  payload)        key,  value  =  payload.split('  ',  2)        socket.send(  do_put(key,  value).to_s  )    end    def  do_put(key,  value)        node  =  @ring.node(key)        if  node  ==  @name            puts  "put  #{key}  #{value}"            @data[@ring.hash(key)]  =  value        else            remote_call(node,  "put  #{key}  #{value}"  )        end    end Thursday, July 25, 13
  • 26. #  start  a  Node  as  a  Server name  =  ARGV.first node  =  Node.new(name,  ['A','B','C']) node.start() $  ruby  node.rb  A $  ruby  node.rb  B $  ruby  node.rb  C #  connect  with  a  client require  'zmq' ctx  =  ZMQ::Context.new req  =  ctx.socket(ZMQ::REQ) req.connect(  "tcp://127.0.0.1:2200"  ) puts  "Inserting  Values" 1000.times  do  |i|    req.send(  "put  key#{i}  value#{i}"  )  &&  req.recv end puts  "Getting  Values" 1000.times  do  |i|    puts  req.send(  "get  key#{i}"  )  &&  req.recv end req.close Thursday, July 25, 13
  • 29. class  Node    #  ...    def  coordinate_cluster(pub_port,  rep_port)        thread  do            ctx  =  ZMQ::Context.new            pub  =  ctx.socket(  ZMQ::PUB  )            pub.bind(  "tcp://*:#{pub_port}"  )            rep  =  ctx.socket(  ZMQ::REP  )            rep.bind(  "tcp://*:#{rep_port}"  )            while  line  =  rep.recv                msg,  node  =  line.split('  ',  2)                nodes  =  @ring.nodes                case  msg                when  'join'                    nodes  =  (nodes  <<  node).uniq.sort                when  'down'                    nodes  -­‐=  [node]                end                @ring.cluster(nodes)                pub.send(  "ring  "  +  nodes.join(','))                rep.send(  "true"  )            end        end    end Thursday, July 25, 13
  • 30. class  Node    #  ...    def  track_cluster(sub_port)        thread  do            ctx  =  ZMQ::Context.new            sub  =  ctx.socket(  ZMQ::SUB  )            sub.connect(  "tcp://127.0.0.1:#{sub_port}"  )            sub.setsockopt(  ZMQ::SUBSCRIBE,  "ring"  )                        while  line  =  sub.recv                _,  nodes  =  line.split('  ',  2)                nodes  =  nodes.split(',').map{|x|  x.strip}                @ring.cluster(  nodes  )                puts  "ring  changed:  #{nodes.inspect}"            end        end    end Thursday, July 25, 13
  • 33.    def  replicate(message,  n)        list  =  @ring.pref_list(n)        results  =  []        while  replicate_node  =  list.shift            results  <<  remote_call(replicate_node,  message)        end        results    end Thursday, July 25, 13
  • 36. WHATTO EAT FOR DINNER? • Adam wants Pizza {value:"pizza", vclock:{adam:1}} • Barb wantsTacos {value:"tacos", vclock:{barb:1}} • Adam gets the value, the system can’t resolve, so he gets bolth [{value:"pizza", vclock:{adam:1}}, {value:"tacos", vclock:{barb:1}}] • Adam resolves the value however he wants {value:"taco pizza", vclock:{adam:2, barb:1}} Thursday, July 25, 13
  • 37. #  artificially  create  a  conflict  with  vclocks req.send('put  1  foo  {"B":1}  hello1')  &&  req.recv req.send('put  1  foo  {"C":1}  hello2')  &&  req.recv puts  req.send("get  2  foo")  &&  req.recv sleep  5 #  resolve  the  conflict  by  decending  from  one  of  the  vclocks req.send('put  2  foo  {"B":3}  hello1')  &&  req.recv puts  req.send("get  2  foo")  &&  req.recv Thursday, July 25, 13
  • 38. • choose a value at random • siblings (user resolution) • defined resolution (eg. CRDT) CONFLICT RESOLUTION Thursday, July 25, 13
  • 44. * Thanks Joe Blomstedt Thursday, July 25, 13
  • 55. array    =  [{value:1},{value:3},{value:5}] mapped  =  array.map{|obj|  obj[:value]} #  [1,  3,  5] mapped.reduce(0){|sum,value|  sum  +  value} #  9 Thursday, July 25, 13
  • 57. module  Mapreduce    def  mr(socket,  payload)        map_func,  reduce_func  =  payload.split(/;s+reduce/,  2)        reduce_func  =  "reduce#{reduce_func}"        socket.send(  Reduce.new(reduce_func,  call_maps(map_func)).call.to_s  )    end    def  map(socket,  payload)        socket.send(  Map.new(payload,  @data).call.to_s  )    end    #  run  in  parallel,  then  join  results    def  call_maps(map_func)        results  =  []        nodes  =  @ring.nodes  -­‐  [@name]        nodes.map  {|node|            Thread.new  do                res  =  remote_call(node,  "map  #{map_func}")                results  +=  eval(res)            end        }.each{|w|  w.join}        results  +=  Map.new(map_func,  @data).call    end end Thursday, July 25, 13
  • 58. module  Mapreduce    def  mr(socket,  payload)        map_func,  reduce_func  =  payload.split(/;s+reduce/,  2)        reduce_func  =  "reduce#{reduce_func}"        socket.send(  Reduce.new(reduce_func,  call_maps(map_func)).call.to_s  )    end    def  map(socket,  payload)        socket.send(  Map.new(payload,  @data).call.to_s  )    end    #  run  in  parallel,  then  join  results    def  call_maps(map_func)        results  =  []        nodes  =  @ring.nodes  -­‐  [@name]        nodes.map  {|node|            Thread.new  do                res  =  remote_call(node,  "map  #{map_func}")                results  +=  eval(res)            end        }.each{|w|  w.join}        results  +=  Map.new(map_func,  @data).call    end end Thursday, July 25, 13
  • 59. 200.times  do  |i|    req.send(  "put  2  key#{i}  {}  #{i}"  )  &&  req.recv end req.send(  "mr  map{|k,v|  [1]};  reduce{|vs|  vs.length}"  ) puts  req.recv Thursday, July 25, 13
  • 60. 200.times  do  |i|    req.send(  "put  2  key#{i}  {}  #{i}"  )  &&  req.recv end req.send(  "mr  map{|k,v|  [1]};  reduce{|vs|  vs.length}"  ) puts  req.recv Thursday, July 25, 13
  • 61. ONE FINAL IMPROVEMENT • C! • A! • P! Thursday, July 25, 13
  • 62. N/R/W • N! # of Nodes to replicate a value to (in total) • R! # of nodes to Read a value from (before success) • W! # of nodes to Write a value to (before success) Thursday, July 25, 13
  • 63. Node A Node B Node C Node D Node E N = 3 Write an Object Node A Node B Node C Node D Node E W = 2 Write an Object replicate replicate to C & D respond first Node A Node B Node C Node D Node E R = 2 Read an Object C & E respond first eventually replicate to request from Thursday, July 25, 13
  • 64. EVENTUALLY CONSISTENT • How Eventual? • How Consistent? Le mieux est l'ennemi du bien Thursday, July 25, 13
  • 65. Probabilistically Bounded Staleness N=3, R=1,W=2 * http://pbs.cs.berkeley.edu Thursday, July 25, 13
  • 68. Distributed Hash Ring Vector Clocks Preference List MerkelTree Read Repair Key/Value CRDT (counters, more coming) Node Gossip Request/ Response Thursday, July 25, 13