【MIT 6.824 Lab4】——Sharded Key/Value Service

Lab4 Sharded Key/Value Service


PartA

本部分实验需要完成分配服务器和分片之间的控制器

  • 实验流程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rnDtRDNF-1654833524682)(MIT-6.824.assets/image-20220331163207643.png)]

  • 对象设计

    主要的对象为服务器ShardCtrler,指令Op,客户端Clerk

    • ShardCtrler

      服务器需要记录自己的编号和对应的Raft服务,以及相应的ApplyCh用于Raft服务器应用,也可以通知自身日志已经存储完成。存储所有的config的信息,并且设置通道用于通知服务器对应的指令是否已经在Raft处存储完成,为了防止不同的客户端请求不重复,设置lastRequestId记录每个客户端上次请求的ID。

      type ShardCtrler struct {
             
      	mu      sync.Mutex
      	me      int
      	rf      *raft.Raft
      	applyCh chan raft.ApplyMsg
      
      	// Your data here.
      	configs       []Config // indexed by config num
      	waitApplyCh   map[int]chan Op
      	lastRequestId map[int64]int
      }
      
    • Op

      指令需要记录类型,并且记录请求的客户端的id和请求的id,确保请求不重复。并且设置不同的值用于存储不同请求的参数。

      type Op struct {
             
      	// Your data here.
      	OperationType string //操作类型,put.get,append
      	ClientId      int64
      	RequestId     int
      	QueryNum      int
      	JoinServers   map[int][]string
      	LeaveGids     []int
      	MoveShard     int
      	MoveGid       int
      }
      
    • Clerk

      客户端有自己的编号,新的请求的编号防止重复请求,并存储所有的服务器和可能时leader的服务器id

      type Clerk struct {
             
      	servers []*labrpc.ClientEnd
      	// Your data here.
      	clientId       int64
      	requestId      int
      	recentLeaderId int
      }
      
  • 流程设计

    • 服务器启动

      初始化参数,后台启动线程持续读取已经被Raft提交应用的命令

      func StartServer(servers []*labrpc.ClientEnd, me int, persister *raft.Persister) *ShardCtrler {
             
      	sc := new(ShardCtrler)
      	sc.me = me
      
      	sc.configs = make([]Config, 1)
      	sc.configs[0].Groups = map[int][]string{
             }
      
      	labgob.Register(Op{
             })
      	sc.applyCh = make(chan raft.ApplyMsg)
      	sc.rf = raft.Make(servers, me, persister, sc.applyCh)
      
      	// Your code here.
      	sc.waitApplyCh = make(map[int]chan Op)
      	sc.lastRequestId = make(map[int64]int)
      
      	go sc.ReadRaftApplyCommand()
      	return sc
      }
      
    • 读取已经应用的命令

      根据已经应用的程序进行分类处理,然后把信息发送给WaitChan通知处理完成

      func (sc *ShardCtrler) GetCommand(msg raft.ApplyMsg) {
             
      	op := msg.Command.(Op)
      
      	if !sc.ifRequestRepetition(op.ClientId, op.RequestId) {
             
      		if op.OperationType == "Join" {
             
      			sc.ExecuteJoinOnConfig(op)
      		}
      		if op.OperationType == "Leave" {
             
      			sc.ExecuteLeaveOnConfig(op)
      		}
      		if op.OperationType == "Move" {
             
      			sc.ExecuteMoveOnConfig(op)
      		}
      	}
      
      	sc.SendMessageToWaitChan(op, msg.CommandIndex)
      }
      
    • 通知操作完成

      func (sc *ShardCtrler) SendMessageToWaitChan(op Op, index int) {
             
      	//修改之前需要上锁
      	sc.mu.Lock()
      	defer sc.mu.Unlock()
      	//检查waitChan是否已经初始化对应位置的值
      	ch, exist := sc.waitApplyCh[index]
      	if exist {
             
      		ch <- op
      	}
      }
      
    • 启动客户端

      func MakeClerk(servers []*labrpc.ClientEnd) *Clerk {
             
      	ck := new(Clerk)
      	ck.servers = servers
      	// Your code here.
      	ck.clientId = nrand()
      	ck.recentLeaderId = mathrand.Intn(len(servers))
      	return ck
      }
      
    • 客户端发起Query请求

      设置需要查看的config的Id,发起rpc请求

      func (ck *Clerk) Query(num int) Config {
             
      
      	// Your code here.
      	ck.requestId++
      	sev := ck.recentLeaderId
      	for {
             
      		args := QueryArgs{
             
      			Num:       num,
      			ClientId:  ck.clientId,
      			RequestId: ck.requestId,
      		}
      		reply := QueryReply{
             }
      		ok := ck.servers[sev].Call("ShardCtrler.Query", &args, &reply)
      
    • 服务器接收请求

      需要自身连接的Raft服务器是leader才能返回请求。将请求信息填入操作中,发给Raft存储,等待指令的完成。如果等待超时,则检查是不是已经提交的指令,若是则直接执行Query操作,否则返回WrongLeader的错误。如果从WaitChan中收到信息,则需检查是否与请求信息一致,如是则执行Query,不是则返回错误。

      func (sc *ShardCtrler) Query(args *QueryArgs, reply *QueryReply) {
             
      	// Your code here.
      	_, ifLeader := sc.rf.GetState()
      	if !ifLeader {
             
      		reply.WrongLeader = true
      		return
      	}
      	op := Op{
             
      		OperationType: "Query",
      		ClientId:      args.ClientId,
      		RequestId:     args.RequestId,
      		QueryNum:      args.Num,
      	}
      	rfIndex, _, _ := sc.rf.Start(op)
      	sc.mu.Lock()
      	chForWaitCh, exist := sc.waitApplyCh[rfIndex]
      	if !exist {
             
      		sc.waitApplyCh[rfIndex] = make(chan Op, 1)
      		chForWaitCh = sc.waitApplyCh[rfIndex]
      	}
      	sc.mu.Unlock()
      	select {
             
      	case <-time.After(time.Millisecond * RfTimeOut):
      		if sc.ifRequestRepetition(op.ClientId, op.RequestId) {
             
      			reply.Config = sc.ExecuteQueryOnConfig(op)
      			reply.Err = OK
      		} else {
             
      			reply.WrongLeader = true
      		}
      	case rfCommitOp := <-chForWaitCh:
      		if rfCommitOp.ClientId == op.ClientId && rfCommitOp.RequestId == op.RequestId {
             
      			reply.Config = sc.ExecuteQueryOnConfig(op)
      			reply.Err = OK
      		} else {
             
      			reply.WrongLeader = true
      		}
      	}
      	sc.mu.Lock()
      	delete(sc.waitApplyCh, rfIndex)
      	sc.mu.Unlock()
      	return
      }
      
    • 服务器执行Query操作

      检查Query的参数若为-1或者大于自身config的最大值,则返回最新的config的信息。否则直接返回请求的config。

      func (sc *ShardCtrler) ExecuteQueryOnConfig(op Op) Config {
             
      	sc.mu.Lock()
      	defer sc.mu.Unlock()
      	sc.lastRequestId[op.ClientId] = op.RequestId
      	if op.QueryNum == -1 || op.QueryNum >= len(sc.configs) {
             
      		DPrintf("Server:%d,Query Config:%v", sc.me, sc.configs[len(sc.configs)-1])
      		return sc.configs[len(sc.configs)-1]
      	} else {
             
      		return sc.configs[op.QueryNum]
      	}
      }
      
    • 客户端收到Query回复

      如果错误则重新选择一个服务器发起请求,若正确则返回结果

      		if !ok || reply.WrongLeader == true {
             
      			sev = (sev + 1) % len(ck.servers)
      			continue
      		}
      		if reply.Err == OK {
             
      			ck.recentLeaderId = sev
      			return reply.Config
      		}
      		time.Sleep(100 * time.Millisecond)
      
    • 客户端发起Join请求

      在请求的参数中设置希望新增的服务器,初始化相关的参数

      ck.requestId++
      	sev := ck.recentLeaderId
      	// Your code here.
      	for {
             
      		args := JoinArgs{
             
      			Servers:   servers,
      			ClientId:  ck.clientId,
      			RequestId: ck.requestId,
      		}
      		reply := JoinReply{
             }
      		ok := ck.servers[sev].Call("ShardCtrler.Join", &args, &reply)
      
    • 服务器接收Join请求

      利用Join请求的信息生产新的指令Op发送给Raft服务器,等待Raft存储提交。

      func (sc *ShardCtrler) Join(args *JoinArgs, reply *JoinReply) {
             
      	// Your code here.
      	_, ifLeader := sc.rf.GetState()
      	if !ifLeader {
             
      		reply.WrongLeader = true
      		return
      	}
      	op := Op{
             
      		OperationType: "Join",
      		ClientId:      args
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 TensorFlow 中,`tf.train.Saver` 是用于保存和恢复模型参数的类。它可以将模型的变量保存到一个二进制的 checkpoint 文件中,也可以从 checkpoint 文件中恢复模型的变量。 `tf.train.Saver` 的用法如下: ```python saver = tf.train.Saver(var_list=None, max_to_keep=5, keep_checkpoint_every_n_hours=10000.0, name=None, restore_sequentially=False, saver_def=None, builder=None, defer_build=False, allow_empty=False, sharded=False, write_version=tf.train.SaverDef.V2, pad_step_number=False, save_relative_paths=False, filename=None) ``` 其中,`var_list` 参数指定需要保存或恢复的变量列表,如果不指定,则默认保存或恢复所有变量。`max_to_keep` 参数指定最多保存的 checkpoint 文件数量,`keep_checkpoint_every_n_hours` 参数指定保存 checkpoint 文件的时间间隔,`name` 参数指定 saver 的名称。 保存模型的变量: ```python import tensorflow as tf # 创建计算图 x = tf.placeholder(tf.float32, shape=[None, 784]) y = tf.placeholder(tf.float32, shape=[None, 10]) W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([10])) logits = tf.matmul(x, W) + b loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=logits)) train_op = tf.train.GradientDescentOptimizer(0.5).minimize(loss) # 训练模型 with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for i in range(1000): batch_xs, batch_ys = ... sess.run(train_op, feed_dict={x: batch_xs, y: batch_ys}) # 保存模型参数 saver = tf.train.Saver() saver.save(sess, './model.ckpt') ``` 在这个例子中,我们创建了一个包含一个全连接层的简单神经网络,并使用梯度下降法训练模型。在训练完成后,我们调用 `tf.train.Saver` 类的 `save` 方法将模型的参数保存到文件 `'./model.ckpt'` 中。 恢复模型的变量: ```python import tensorflow as tf # 创建计算图 x = tf.placeholder(tf.float32, shape=[None, 784]) y = tf.placeholder(tf.float32, shape=[None, 10]) W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([10])) logits = tf.matmul(x, W) + b loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=logits)) train_op = tf.train.GradientDescentOptimizer(0.5).minimize(loss) # 恢复模型参数 saver = tf.train.Saver() with tf.Session() as sess: saver.restore(sess, './model.ckpt') # 使用模型进行预测 test_x, test_y = ... predictions = sess.run(logits, feed_dict={x: test_x}) ``` 在这个例子中,我们创建了与之前相同的计算图,并使用 `tf.train.Saver` 类的 `restore` 方法从文件 `'./model.ckpt'` 中恢复模型的参数。恢复参数后,我们可以使用模型进行预测。需要注意的是,恢复模型参数时,需要在调用 `tf.global_variables_initializer()` 之前调用 `saver.restore` 方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值