Gavin

  • 主页
  • 随笔
所有文章 web3 about me

Gavin

  • 主页
  • 随笔

软件设计模式之Reactor

2017-08-01

反应堆设计模式

是一种并行处理服务请求分发的事件模型。服务处理器分离收到的请求并调度相对用的请求处理器同步的处理各自的请求。

组成结构

Resources(可以被监听的事件)
任何可以向系统输入或者消耗输出的资源(linux 上对应的可以用epoll监听的事件)。
Synchronous Event Demultiplexer(同步的事件解复用器)
用一个EventLoop 阻塞所有的资源。解复用器触发调度器,同步的操作变为非阻塞的资源。(实例:

  • 同步系统调用read()函数 将会在没有数据可读时是阻塞的。
  • 解复用器用select()函数来监听该资源,直到该资源变为可读时。
  • read()函数不会被阻塞, 解复用器可以将该资源发送给调度器。)
    Dispatcher(调度器)
    处理请求处理程序的注册和注销。将资源从多路分解器调度到相关联的请求处理程序。
    Request Handler(请求处理程序)
    应用程序定义的与它自己相关联资源的请求处理程序。

    属性

    所有反应堆系统都是单线程的,但可以存在于多线程环境中。

    优点

    反应堆模式完全将应用程序特定的代码与反应堆实现分离,这意味着应用程序组件可以分为模块化的,可重复使用的部分。此外,由于请求处理程序的同步调用,反应器模式允许简单的粗粒度并发,而不会将多个线程的复杂性增加到系统。

    缺点

    由于反向控制流程,反应堆模式可能比由程序模式更难调试。而且,通过只同时调用请求处理程序,反应堆模式限制了最大的并发性,特别是在对称多处理硬件上。反应堆模式的可扩展性不仅受到同步调用请求处理程序的限制,还受到解复用器的限制。

tendermint 的reactor 模式实例

解复用器, EventLoop 循环, 将资源传送给调度器调用

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// recvRoutine reads msgPackets and reconstructs the message using the channels' "recving" buffer.
// After a whole message has been assembled, it's pushed to onReceive().
// Blocks depending on how the connection is throttled.
func (c *MConnection) recvRoutine() {
defer c._recover()
FOR_LOOP:
for {
// Block until .recvMonitor says we can read.
c.recvMonitor.Limit(maxMsgPacketTotalSize, atomic.LoadInt64(&c.config.RecvRate), true)
/*
// Peek into bufReader for debugging
if numBytes := c.bufReader.Buffered(); numBytes > 0 {
log.Info("Peek connection buffer", "numBytes", numBytes, "bytes", log15.Lazy{func() []byte {
bytes, err := c.bufReader.Peek(MinInt(numBytes, 100))
if err == nil {
return bytes
} else {
log.Warn("Error peeking connection buffer", "error", err)
return nil
}
}})
}
*/
// Read packet type
var n int
var err error
pktType := wire.ReadByte(c.bufReader, &n, &err)
c.recvMonitor.Update(int(n))
if err != nil {
if c.IsRunning() {
c.Logger.Error("Connection failed @ recvRoutine (reading byte)", "conn", c, "error", err)
c.stopForError(err)
}
break FOR_LOOP
}
// Read more depending on packet type.
switch pktType {
case packetTypePing:
// TODO: prevent abuse, as they cause flush()'s.
c.Logger.Debug("Receive Ping")
c.pong <- struct{}{}
case packetTypePong:
// do nothing
c.Logger.Debug("Receive Pong")
case packetTypeMsg:
pkt, n, err := msgPacket{}, int(0), error(nil)
wire.ReadBinaryPtr(&pkt, c.bufReader, maxMsgPacketTotalSize, &n, &err)
c.recvMonitor.Update(int(n))
if err != nil {
if c.IsRunning() {
c.Logger.Error("Connection failed @ recvRoutine", "conn", c, "error", err)
c.stopForError(err)
}
break FOR_LOOP
}
channel, ok := c.channelsIdx[pkt.ChannelID]
if !ok || channel == nil {
cmn.PanicQ(cmn.Fmt("Unknown channel %X", pkt.ChannelID))
}
msgBytes, err := channel.recvMsgPacket(pkt)
if err != nil {
if c.IsRunning() {
c.Logger.Error("Connection failed @ recvRoutine", "conn", c, "error", err)
c.stopForError(err)
}
break FOR_LOOP
}
if msgBytes != nil {
c.Logger.Debug("Received bytes", "chID", pkt.ChannelID, "msgBytes", msgBytes)
c.onReceive(pkt.ChannelID, msgBytes)
}
default:
cmn.PanicSanity(cmn.Fmt("Unknown message type %X", pktType))
}
// TODO: shouldn't this go in the sendRoutine?
// Better to send a ping packet when *we* haven't sent anything for a while.
c.pingTimer.Reset()
}
// Cleanup
close(c.pong)
for _ = range c.pong {
// Drain
}
}

调度器 负责注册资源,以及资源和请求程序的绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor {
// Validate the reactor.
// No two reactors can share the same channel.
reactorChannels := reactor.GetChannels()
for _, chDesc := range reactorChannels {
chID := chDesc.ID
if sw.reactorsByCh[chID] != nil {
cmn.PanicSanity(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chID, sw.reactorsByCh[chID], reactor))
}
sw.chDescs = append(sw.chDescs, chDesc)
sw.reactorsByCh[chID] = reactor
}
sw.reactors[name] = reactor
reactor.SetSwitch(sw)
return reactor
}

抽象出来的接口, 编程只要继承这些接口即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type BaseReactor struct {
cmn.BaseService // Provides Start, Stop, .Quit
Switch *Switch
}
func NewBaseReactor(name string, impl Reactor) *BaseReactor {
return &BaseReactor{
BaseService: *cmn.NewBaseService(nil, name, impl),
Switch: nil,
}
}
func (br *BaseReactor) SetSwitch(sw *Switch) {
br.Switch = sw
}
func (_ *BaseReactor) GetChannels() []*ChannelDescriptor { return nil }
func (_ *BaseReactor) AddPeer(peer *Peer) {}
func (_ *BaseReactor) RemovePeer(peer *Peer, reason interface{}) {}
func (_ *BaseReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {}

编写代码范本

1
2
3
4
5
6
7
8
9
10
11
12
type MyReactor struct{}
// 对应于资源
func (reactor MyReactor) GetChannels() []*ChannelDescriptor {
return []*ChannelDescriptor{ChannelDescriptor{ID:MyChannelID, Priority: 1}}
}
// 资源相对应的请求处理程序
func (reactor MyReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {
r, n, err := bytes.NewBuffer(msgBytes), new(int64), new(error)
msgString := ReadString(r, n, err)
fmt.Println(msgString)
}

赏

微信支付

微信

扫一扫,分享到微信

微信分享二维码
chainmint
Bytom's data structure.
© 2018 Gavin
Hexo Theme Yilia by Litten
  • 所有文章
  • web3
  • about me

tag:

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • polkadot

TRUST
Block Chain 是一种信仰。是一种价值观的转接。 从人类以神为信仰到以自己为信仰的一种转接。
Block Chain 是一种向往。向往着自由平等。
Block Chain 是一种追求。追求着诗和远方。