不会开机的男孩

2014年度总结

| Comments

2014年已经过去了,人生已经到了第27个年头,越来越发现自己不能像20出头的时候那样不加思考的做事情,每件事情每个决定都变得小心翼翼。我的世界已经不仅仅是代码,发现有更多更多的事情需要我去做,需要我去学。

工作

年初我换了一份工作,在加入百度之前,我曾经和部门的高经谈过这个职业迷茫的问题。他讲了他的故事,也告诉我迷茫哪里都有,干我们这一行,必须拥抱变化。这一年来看似什么都没发生,但其实暗流涌动,从一开始的兴奋,到后面因为项目变动而team剧烈变动,并一度考虑离职,再到新的team,再到非常开心,再到归于平静。中间也发生了一些有趣的事情,一次被动面试被拒,一次主动拒绝了一份待遇丰厚的offer。感受到了不变化,也是一种选择。

我非常的幸运,我的工作是我最大的兴趣,而这个也给我带来了别人无法体会的迷茫。工作对我来说不仅仅是钱,我不会因为更多的钱而换工作。我更在乎我是否在做着我喜欢做的事情,我是否有机会施展我所谓的“才华”,是否有足够的时间,让我学习我感兴趣的东西。

听上去似乎有些复杂,但这个问题不仅仅只有我有。很多同事老大都有,而通常的解决办法就是耐心等,做好平时积累,沉下心来学习。另外赶快去找个女朋友吧。

生活

2014年在生活角度来看是个好兆头,我开始学会独立生活,交水、电费。一开始非常开心,但是后来却发现,独处是一件非常难的事情。发现在一个只有自己的空间中,我的确变得懒了。看书少了,颓废多了。早起少了,晚睡多了。仅仅是一个细微的变化————一个人住,就改变了这么多,这让我惊讶,也让我兴奋。我感到了这是一种挑战。别人称为”君子慎独”。感到了更多的压力与责任。是的,自己是否能够约束自己,为自己的行为负责,照顾好自己

年底的时候,自己感冒了。可能对很多人都不是个事儿,但是对于一个高中之后就再也没有感冒吃药的人来说,还是一件蛮重要的事情。我突然发现自己的身体,出了问题。变得弱了很多。走在路上我想到了很多,我感觉害怕,是的,因为我发现我的身体没有往日的灵活与力量,那些马路上快速来往的汽车让我感到恐惧。我想到了老妈老爸,还有家里已经8岁的小白。年纪大的家伙们,需要人的照顾。

学习

学习在我毕业之后就成了我生命中的主线,今年我参加了非常非常多的网课。我非常开心前几年对英语的不断积累,让我可以几乎无字幕的情况下,完成大部分课程。并且有不少课程深深的改变了我对这个世界的认识,打开了另一扇窗户。2014我对历史产生了极大兴趣,对古书中蕴含的道理也非常赞同,他们对人性的把握让我脑洞大开。他们对失败的看法,对成功的定义,面对问题,解决问题的思路让我感到深深的不如,也感到由衷的开心。求知的道路还有很多需要努力。唯一的问题在于看书太少了,附上DNA的统计

女朋友

2014年我有很多的时间和精力放在了如何找到女朋友这个问题上面。在问了不少“有经验的前辈”,并且“打入敌人内部”之后,我得出了一个结论——我的处境非常非常糟糕。很多人在10几岁的时候就已经可以开始追妹子,并且已经开始看各种书,积累各种方法论了。对于一个26岁。还不能看下一本书的我来说,已经落后了整整10年。扪心而我,我发现我做了很多感动自己,恶心别人的事情。妹子们早已被表白到无所谓,拿礼物拿到手软,当然不会在意多一个我,少一个我。世界无外乎有用和无用,在深深的感受到这个世界的恶意之后,我不得不开诚布公的和老妈进行了长谈,表示我注孤的情况,并得到了她的同情。然后开始了相亲之旅。

和绝大多数人一样,非常不顺利,相亲就像是打怪升级,不断的练习,积累经验。虽然没有结果,但是最用心的那个人必然收获最大。再见了10多个之后,真的很不容易不容易遇到一个合适的妹子。可以让我跳入下一个学习区——如何维持一段长期的亲密关系。

追妹子的过程中让我慢慢意识到一个问题,如果你始终都没有办法追到一个妹子,这就说明自己在建立人与人信任关系上面,有问题,至少也是你不愿意向另一个人share你的时间,另一个人也不愿意share她的时间给你。建立信任的过程很难,妹子又是个体差异极大的生物,你无法预知眼前的人是不是最后一个,所能做得只能是尽自己最大努力,珍惜彼此之间的缘分。

最后

这就是我的2014,4个维度。看似平稳,实则充满变化的1年。上海的事情告诉我们,生命有时候很短暂。我希望我的家人,朋友,自己能够平平安安就好。

iOS APP 架构漫谈二

| Comments

上一篇《iOS APP 架构漫谈》简单介绍了information flow的概念。这篇文章简单介绍另一个在编程中非常重要的思想或工具——状态机(State machine)。对大多数计算机专业的家伙们来说,这应该是一门比较难学的课程,里面包含一大堆揪心的名字比如DFA,NFA,还有一大堆各种各样的数学符号,又是编译原理的基础。不过很遗憾,似乎在做完编译原理课程作业之后,很多人再也没有实现过或是用过状态机了。本文通过一个游戏demo来简单描述一下状态机在实践中的应用。demo code

背景

首先看下我们的使用场景,假如我们需要设计一套联网对战的小游戏。第一个难题可能是如何建立一个通道,让2个手机相互发送消息。这里我并不打算引入server端开发,希望只是通过客户端来实现这个逻辑,这里使用LeanCloud API来简化这个过程。这样我们可以暂时不考虑技术细节,直接站在业务角度去思考如何建立这个游戏。

业务场景–邀请

正式开始游戏之前,总会有一个邀请的环节。假如我们有2个用户,分别是Host,Guest。Host创建游戏,Guest加入游戏。游戏的整个流程和我们平时玩的对战游戏流程并没有多大不同。

1-1

  1. Host创建游戏,他就相当于进入一个等待队列里面。
  2. Guest加入游戏,他从等待队列中找到一个匹配,比如Host。然后对Host发送join message
  3. Host会收到很多join message。由于我们只是选择1vs1。这里假定Host同意Guest加入游戏。Host向Guest发送join confirm message
  4. Guest收到join confirm message, 向Host发送Go消息,表示Guest已经进入游戏
  5. Host收到Go消息。也进入游戏。

具体实现业务逻辑

现在的构想的逻辑只有5步,但其实还会包含很多逻辑,比如超时机制,重发机制。由于中间状态很多,还可能有我们没有想到过的问题。在面对这种复杂逻辑时,会通过状态机来帮助我们理顺逻辑。这时,我们脑中思考的业务其实是一个状态到一个状态的图。 如下

1-1

上半部分是游戏的创建者,下半部分是游戏的加入者。

一开始,尽量简化模型,这里红色剪头表示我们的正确主流路线,黑色表现出错路线。也就是说,一旦错误,就回到原始Idle状态。

开始写代码

在想清楚所有逻辑,并考虑清楚正常路线和错误路线之后,就可以开始写代码了。为了方便,这里直接使用第三方的状态机框架TransitionKit

定义State(HOST)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 TKState *idleState = [TKState stateWithName:@"idle"];
  TKState *waitingJoinState = [TKState stateWithName:@"waitingJoin"];
  TKState *waitingConfirmState = [TKState stateWithName:@"waitingConfirm"];
  TKState *goState = [TKState stateWithName:@"go"];

  [waitingConfirmState setDidEnterStateBlock:^(TKState *state, TKTransition *transition) {
      [selfWeak sendJoinConfirm];
  }];

  [goState setDidEnterStateBlock:^(TKState *state, TKTransition *transition) {
      NSLog(@"happy ending");

      [SVProgressHUD showSuccessWithStatus:@"ok"];
  }];

定义Event(HOST)

Event 是建立State到State的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 TKEvent *waitingJoinEvent = [TKEvent eventWithName:CUHostGameManagerWaitingJoinEvent
                           transitioningFromStates:@[idleState]
                                           toState:waitingJoinState];

  TKEvent *receiveInviteEvent = [TKEvent eventWithName:CUHostGameManagerReceiveInviteEvent
                               transitioningFromStates:@[waitingJoinState]
                                               toState:waitingConfirmState];

  TKEvent *receiveConfirmEvent = [TKEvent eventWithName:CUHostGameManagerReceiveConfirmEvent
                                transitioningFromStates:@[waitingConfirmState]
                                                toState:goState];

  TKEvent *disconnectedEvent = [TKEvent eventWithName:CUHostGameManagerDisconnectedEvent
                               transitioningFromStates:nil
                                              toState:idleState];

定义过程(HOST)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 - (void)startGame {

      NSAssert(self.session.peerId != nil, @"");

      //这里,如果不是idle,我们切换状态机到idle
      if (![self.stateMachine.currentState.name isEqual:@"idle"]) {
          [self fireEvent:CUHostGameManagerDisconnectedEvent userInfo:nil];
      }

      //这里调用LeanCloud 入队
      AVObject *waitingId = [AVObject objectWithClassName:@"waiting_join_Ids"];
      [waitingId setObject:self.session.peerId forKey:@"peerId"];
      [waitingId saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
          //enqueue 之后,进入waitingJoin状态
          [self fireEvent:CUHostGameManagerWaitingJoinEvent userInfo:nil];
      }];
  }

  - (void)sendJoinConfirm {
      //发送加入确认消息给Guest
      AVMessage *message = [AVMessage messageForPeerWithSession:self.session
                                                   toPeerId:self.peerId
                                                    payload:@"join_confirm"];
      [self.session sendMessage:message transient:YES];
  }

  - (void)session:(AVSession *)session didReceiveMessage:(AVMessage *)message
  {
      if ([message.payload isEqualToString:@"join"]) {
          //收到Join(邀请)之后,发送确认消息
          self.peerId = message.fromPeerId;

          //因为LeanCloud的API比较挫,watch 之后才能发送消息,但是我们不知道什么时候才watch成功。。。。
          //好在只是demo,我们只好用这种方式work around,延迟2s发送消息
          [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(sendInviteConfirmRequest:) object:nil];
          [self performSelector:@selector(sendInviteConfirmRequest:)
                      withObject:@[message.fromPeerId]
                      afterDelay:2.0f];
      }
      else if ([message.payload isEqualToString:@"go"]) {
          //收到go消息,流程结束
          [self fireEvent:CUHostGameManagerReceiveConfirmEvent userInfo:nil];
      }
  }

  - (void)sendInviteConfirmRequest:(NSArray *)watchPeerIds {
      [self.session watchPeerIds:watchPeerIds];
      [self fireEvent:CUHostGameManagerReceiveInviteEvent userInfo:nil];
  }

定义State(Guest)

1
2
3
4
5
6
7
8
9
10
11
12
13
 TKState *idleState = [TKState stateWithName:@"idle"];
  TKState *waitingReplyState = [TKState stateWithName:@"waitingReply"];
  TKState *goState = [TKState stateWithName:@"go"];

  [waitingReplyState setWillEnterStateBlock:^(TKState *state, TKTransition *transition) {
      [selfWeak searchingGames];
  }];

  [goState setDidEnterStateBlock:^(TKState *state, TKTransition *transition) {
      [selfWeak sendGo];
      NSLog(@"happy ending");
      [SVProgressHUD showSuccessWithStatus:@"ok"];
  }];

定义Event(Guest)

1
2
3
4
5
6
7
8
9
10
11
 TKEvent *searchingEvent = [TKEvent eventWithName:CUGestGameManagerSearchingEvent
                           transitioningFromStates:@[idleState]
                                           toState:waitingReplyState];

  TKEvent *receiveConfirmEvent = [TKEvent eventWithName:CUGestGameManagerReceiveConfirmEvent
                                transitioningFromStates:@[waitingReplyState]
                                                toState:goState];

  TKEvent *disconnectedEvent = [TKEvent eventWithName:CUGestGameManagerDisconnectedEvent
                              transitioningFromStates:nil
                                              toState:idleState];

定义过程(Guest)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (void)joinGame {

  if (![self.stateMachine.currentState.name isEqual:@"idle"]) {
    [self fireEvent:CUGestGameManagerDisconnectedEvent userInfo:nil];
  }

  [self fireEvent:CUGestGameManagerSearchingEvent userInfo:nil];
}

- (void)searchingGames {
  AVQuery *query = [AVQuery queryWithClassName:@"waiting_join_Ids"];
  [query orderByDescending:@"updatedAt"];
  [query setLimit:1];

  [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    NSMutableArray *installationIds = [[NSMutableArray alloc] init];
    for (AVObject *object in objects) {
      if ([object objectForKey:@"peerId"]) {
        [installationIds addObject:[object objectForKey:@"peerId"]];
      }
    }

    [self.session watchPeerIds:installationIds];

    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(sendJoinRequest) object:nil];
    [self performSelector:@selector(sendJoinRequest)
               withObject:nil
               afterDelay:2.0f];
  }];
}

- (void)sendJoinRequest {

  for (NSString *item in self.session.watchedPeerIds) {
    AVMessage *message = [AVMessage messageForPeerWithSession:self.session
                                                     toPeerId:item
                                                      payload:@"join"];
    [self.session sendMessage:message transient:YES];
  }
}

- (void)sendGo{
  AVMessage *message = [AVMessage messageForPeerWithSession:self.session
                                                   toPeerId:self.otherPeerId
                                                    payload:@"go"];
  [self.session sendMessage:message transient:YES];
}

最后

state machine 是一个蛮厉害的锤子,只要是一个工具,就肯定会被滥用。。。state machine最大的好处是在于,方便我们思考清楚所有细节,主线,和错误流程。避免因为考虑不周全而产生的bug。结合之前的information flow的思路,会让我们的软件设计更加清楚。

demo code

分布式系统原理–日志技术No Redo Log

| Comments

上一篇介绍了Redo Log,这篇介绍No Redo Log。

在分布式系统中,某些情况下我们依然需要实现原子操作,有很多方式,其中No Redo(Undo) Log 便是在工程中运用最广泛的思想之一。他的过程非常简单。

下面是一个简单系统的状态。

image

为了实现原子的修改 A,B,C的值。我们把A,B,C 看成一个集合,或是一个“目录”。

1.做一次copy

image

2.对于每个更新的操作,创建一个新的item,然后在新的目录中保存修改后的新值。

image

3.原子性的修改生效目录指针。

image

通过原子性的修改一个值,切换一个状态,完成一系列分布式操作原子性的修改。

分布式系统原理–日志技术Redo Log

| Comments

问题概述

在分布式系统中,宕机是需要考虑的重要组成部分。日志技术是宕机恢复的重要技术之一。日志技术应用广泛,早些更是广泛应用在数据库设计实现中。本文先介绍基本原理概念,最后通过redis介绍生产环境中的实现方法。

Redo Log

数据库设计中,需要满足ACID,尤其是在支持事务的系统中。当系统遇到未知错误时,可以恢复到一个稳定可靠的状态。有一个很简单的思路,就是记录所有对数据库的写操作日志。那么一旦发生故障,即使丢失掉内存中所有数据,当下一次启动时,通过复现已经记录的数据库写操作日志,依然可以回到故障之前的状态(如果在写操作作日志的时候发生故障,那么这次数据库操作失败)。

操作流程简单如下(假设每次数据变化,都提交):

  1. 更新的操作方式依次记录到磁盘日志文件。
  2. 更新内存中的数据。
  3. 返回更新成功结果。

恢复流程:

  1. 读取日志文件,依次修改内存中的数据。

优点:

  1. 日志文件有序,可以通过append的方式写入磁盘,性能很高。
  2. 简单可靠,应用广泛。可以把内存中的数据,做备份在磁盘中。

缺点:

  1. 使用时间一长,恢复宕机的时间很慢。

解决办法

先具体化下,如果我们内存中保留一个a的值,记录了写操作比如 a = 4; a++; a--; 当这些操作上千万、亿之后,恢复非常慢。甚至可能最后一条就是a=0,按照之前的算法,我们却跑了很长时间。

那么根据这个场景,很容易想到一个解决方案。

操作流程:

  1. 日志文件记录begin check point
  2. 在某个时刻,把内存中的数值,直接snapshot或dump到磁盘上。(比如直接记录a=4)
  3. 日志文件记录end check point

恢复流程:

  1. 扫描日志文件,找到最后的end check point中配对的begin check point
  2. 读入dump文件。
  3. 依次回放记录的日志操作。

优点:

  1. 应用广泛,包括 mysql,oracle。

一些棘手的问题:

  1. 在做snapshot的时候,往往不能停止数据库的服务,那么很可能记录了begin check point之后的日志。那么在重新load begin check point之后的日志时,最后恢复的数据很有可能不对。比如我们记录的是a++这样的日志, 那么重复一条日志,就会让a的值加1。反之如果我们记录是幂等的,比如一直是 a=5 这种操作,那么就对最后结果没有影响。很显然,设计幂等操作系统很麻烦。

  2. 设计一个支持snapshot的内存数据结构,也比较麻烦。

典型的是通过copy-on-write机制。和操作系统中的概念一样。当这个数据结构被修改,就创建一份真正的copy。老数据增加一份dirty flag。如果没有修改就继续使用之前的内存。这样在做snapshot的时候,保证我们的dump数据是begin check point这个时刻的数据。显然这个也比较麻烦。

还有一种支持snapshot的思路是begin check point后,不动老的数据。内存中的数据在新的地方,日志也写在新的地方。最后在end check point做一次merge。这个实现起来简单,但是内存消耗不小。

Redis是如何解决日志问题的

Redis 是一个基于内存的database,不同于memcached,他支持持久化。另外由于redis处理client request 和 response 都是在一个thread里面,也没有抢占式的调度系统,核心业务都是按照event loop顺序执行,而磁盘写日志又开销很大,所以redis实现日志功能做了很多优化。并且提供2种持久化方案。我们需要在不同的场景下,采用不同的方式配置。

snapshotting

某个时刻,redis会把内存中的所有数据snapshot到磁盘文件。更通俗的说法是fork一个child process,把内存中的数据序列化到临时文件,然后在main event loop 中原子的更换文件名。redis,利用了操作系统VM的copy-on-write机制,在不阻塞主线程的情况下,利用子进程和父进程共享的data segment实现snapshot。具体是代码实现在rdb.c, function at rdbSaveBackground

优点:

  1. 简单可靠,如果database 不大,执行的效果非常好。

缺点:

  1. 如果database size 很大,每一次snapshot时间非常长。不得不配置大的间隔,提高了宕机时数据丢失的风险。

为了解决上面的问题,redis增加了AOF。

Append Only File(AOF)

在database术语中,也被叫做WAL。如果开启的AOF的配置,redis会记录所有写操作到日志文件中。那么redis同样会遇到之前我们提到过的问题。

  1. 即便是追加写,磁盘的操作依然比内存慢好几个数量级,频繁的操作容易产生瓶颈。
  2. 如果数据量操作频繁,会产生大量的重复日志数据,导致恢复时间太长。比如记录一条微博的浏览量,会记录大量重复的+1日志。

那么redis是如何解决的呢?

  1. 文件写操作消耗的时间很长,redis会先把记录日志写在内存buffer中,在每一次event loop 结束之后,根据配置判断是否做写操作。每个buffer的大小有限制,这样每次写操作时间不会太长。
  2. 即便是调用write操作,OS并没有立即写入磁盘,redis 同样提供了一些方案决定刷新OS IO buffer的时机(1秒、从不、每次)。
  3. redis 提供一种AOF重写的方式rewriteAppendOnlyFile来处理AOF文件过大情况。

前面我们知道了,这种check point的机制还是比较麻烦的。那么redis是这么设计的。

image

  1. 为了避免加锁,redis 依然创建了一个child process,利用VM的copy-on-write,共享数据。同时保证主线程依然可以处理client请求。
  2. 根据KV的类型,先从内存读取数据,然后再写数据到磁盘,和之前的AOF文件无关。
  3. 那么当子进程rewrite AOF的过程中,main thread依然可以处理新的client request。新增的数据会被放在rewrite buffer中,而且写到原有的AOF文件中。
  4. child process完成后会通知主线程。主线程有一个定时任务,也就是会不断轮询child process是否已经完成(通过信号量)。
  5. 主线程会merge 变化的数据到temp file。
  6. 主线程原子的rename到一个新的AOF文件,之前的AOF就不起作用了。

优点:

  1. 除了merge 和 rename需要阻塞主线程,rewrite不会阻塞主线程。(前提是使用bgrewrite command)。

最后

这些都是性能和稳定性之间做的权衡,根据不同场景需要调整。

参考

缚心猿,锁六耗

| Comments

转载自Coursera 公开课 professionalism forums thread

【作者徐冰,1994年从中国成都到新加坡。目前修读中医学士学位,兼职电台主持】

无论是谁,有怎样的社会地位,在光鲜亮丽或毫不起眼的外表下,谁没有自己的故事?谁不是一路走来跌跌撞撞留了或深或浅的伤?在独自一人的时候,有多少人可以真正自在地和自己相处?

一次在“医学心理学”课上,老师暂停上课,将课室灯光调暗,让我们做个冥想的练习。

她让我们沉淀思绪,在尽可能放松的状态下,回溯过往。

我们一页页翻过我们各自的故事,重回生命中的重要时刻,走过一级级或快乐,或悲伤,或怨恨,或遗憾的台阶,回到原点。

想象,我们经由时光隧道回访旧时的家。走过熟悉的街道,看到家的门牌,走进去。家居陈设是否和过去一样?家里都有谁?他们的样子如何?他们之间的互动如何?然后,我们看到童年的自己。

这时,容许自己好好看看童年的自己。他是什么样子?他是什么表情?看进他的眼睛,他是否快乐?感受他的各种情绪,盼望和恐惧,愤怒和无助,自责和愧疚。容许自己,陪伴童年的自己片刻,听听他有什么话想说。

把他抱上膝头,轻轻地,拥抱他。

如果他哭,就让他哭。

容许自己,给童年的自己深深的理解和安慰,告诉他,他现在十分安全,没有任何人任何事可以再令他受伤害;告诉他,他只是一个孩子,没有做错什么,在能力许可内,他已经做得够好。跟童年的自己道歉,抱歉忽略和冷落他太久,然后,承诺你再也不会苛责他,嫌弃他,在任何时候,你只会爱他……

我不是第一次接触这样的练习。之前在台湾参加“萨提尔模式”课程时,老师也带着我们做过治疗性的冥想,我已经走过整个过程而且得到了很好的疗愈,因此对我来说,不再有强烈的心理冲击,但我仍感受到明显的情绪涌动。

而许多同学,包括四五十岁的大男人,在那一刻,都不能自已,泣不成声。

多数人,可能从未想到自己会有这样激烈的反应,从未意识到在内心深处,有这样深和痛的创伤。

我们每个人的心里,都有一个内在的小孩。他是长久以来被我们努力压抑的各种情绪,被刻意忽略的伤痛的累积。无论是谁,有怎样的社会地位,在光鲜亮丽或毫不起眼的外表下,谁没有自己的故事?谁不是一路走来跌跌撞撞留了或深或浅的伤?在独自一人的时候,有多少人可以真正自在地和自己相处?

在亲密关系中,又有多少人却感受到难以言说的寂寞?有多少人一直被困在过去走不出来?又有多少人把痛苦转嫁他人造成新的伤害?

那内在的小孩,一直在那里,以各种方式,也许是身体的不适,病痛,或精神上的失调,或人际关系的危机,来呼求我们的关注。我们却从未去理会和照顾他,更不知道那就是我们所有痛苦和不幸的根源。

让我们回到源头,与自己和解。

不要觉得这很荒谬,下次当你一个人的时候,让自己独处在一个不被打扰的环境里,让心静下来,走过岁月的小径,去探望幼年的你。跟他一起玩耍,陪他,注视他,抚摸他,温柔地待他。好像你是他的父母一般,无论他说什么做什么,你都不加评判地接纳。他跟任何一个你爱的人一样,值得你去爱。尤其是,如果那小小的人儿从未得到过足够的爱的话,你,只有你,能带给他补偿,改变他的命运。改变了他的命运,你就改变了自己的命运。