Simple HTTP router package - code review

xuanbao · · 420 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>There is a question on <a href="https://codereview.stackexchange.com/q/160489/133498" rel="nofollow">codereview.stackexchange</a></p> <p>I have created a <a href="https://github.com/vardius/goserver" rel="nofollow">router/micro-framework</a>. I have split my code into multiple files by responsibility. All files are in the same directory. Would you recommend any way of improving it ? The most important one for a performance is a <a href="https://github.com/vardius/goserver/blob/master/tree.go" rel="nofollow">tree.go</a> file.</p> <p>This is a <strong>benchmark results</strong> for <strong>4 cors CPU</strong>.</p> <pre><code>$ go test -bench=. -cpu=4 BenchmarkStrict1-4 2000000 923 ns/op BenchmarkStrict2-4 1000000 1138 ns/op BenchmarkStrict3-4 1000000 1419 ns/op BenchmarkStrict5-4 1000000 1767 ns/op BenchmarkStrict10-4 500000 2356 ns/op BenchmarkStrict100-4 100000 17943 ns/op BenchmarkStrictParallel1-4 3000000 438 ns/op BenchmarkStrictParallel2-4 2000000 501 ns/op BenchmarkStrictParallel3-4 3000000 444 ns/op BenchmarkStrictParallel5-4 2000000 639 ns/op BenchmarkStrictParallel10-4 2000000 823 ns/op BenchmarkStrictParallel100-4 200000 7039 ns/op BenchmarkRegexp1-4 1000000 1840 ns/op BenchmarkRegexp2-4 1000000 2899 ns/op BenchmarkRegexp3-4 500000 3625 ns/op BenchmarkRegexp5-4 300000 4777 ns/op BenchmarkRegexp10-4 200000 9648 ns/op BenchmarkRegexp100-4 10000 100741 ns/op BenchmarkRegexpParallel1-4 2000000 937 ns/op BenchmarkRegexpParallel2-4 1000000 1078 ns/op BenchmarkRegexpParallel3-4 1000000 1692 ns/op BenchmarkRegexpParallel5-4 500000 2414 ns/op BenchmarkRegexpParallel10-4 500000 3906 ns/op BenchmarkRegexpParallel100-4 50000 31972 ns/op </code></pre> <p>The output <code>BenchmarkStrict1-4 2000000 923 ns/op</code> means that the loop ran <code>2000000</code> times at a speed of <code>923 ns</code> per loop. Each benchmark name <code>BenchmarkStrict5-4</code> means that test used a <code>strict</code> or <code>regexp</code> route path for each node with a nested level <code>5</code>. Where 4 stands for CPU number.</p> <p>For more details take a look <a href="https://github.com/vardius/goserver/blob/master/benchmark_test.go" rel="nofollow">benchmark_test.go</a></p> <p>Do you know how can i make it faster ? I use mutex to make it thread safe for tree map access. But maybe there are some things along where i can improve my code/logic ?</p> <p><strong><em>tree.go</em></strong></p> <pre><code>package goserver import ( &#34;regexp&#34; &#34;strings&#34; &#34;sync&#34; ) type ( node struct { path string regexp *regexp.Regexp route *route parent *node children tree treeMu sync.RWMutex } tree map[string]*node ) func (n *node) isRoot() bool { return n.parent == nil } func (n *node) isLeaf() bool { return len(n.children) == 0 } func (n *node) regexpToString() string { if n.regexp == nil { return &#34;&#34; } return n.regexp.String() } func (n *node) setRegexp(exp string) { reg, err := regexp.Compile(exp) if err == nil { n.regexp = reg } } func (n *node) child(paths []string) (*node, map[string]string) { if len(paths) &gt; 0 &amp;&amp; paths[0] != &#34;&#34; { n.treeMu.RLock() defer n.treeMu.RUnlock() if child := n.children[paths[0]]; child != nil { return child.child(paths[1:]) } for path, child := range n.children { if len(path) &gt; 0 &amp;&amp; path[:1] == &#34;:&#34; { if child.regexp == nil || child.regexp.MatchString(paths[0]) { node, params := child.child(paths[1:]) params[strings.Split(path, &#34;:&#34;)[1]] = paths[0] return node, params } } } } else if len(paths) == 0 { return n, make(Params) } return nil, make(map[string]string) } func (n *node) addChild(paths []string) *node { if len(paths) &gt; 0 &amp;&amp; paths[0] != &#34;&#34; { n.treeMu.Lock() defer n.treeMu.Unlock() if n.children[paths[0]] == nil { n.children[paths[0]] = newNode(n, paths[0]) } return n.children[paths[0]].addChild(paths[1:]) } return n } func (n *node) setRoute(r *route) { n.route = r } func newNode(root *node, path string) *node { n := &amp;node{ path: path, parent: root, children: make(tree), } if parts := strings.Split(n.path, &#34;:&#34;); len(parts) == 3 { n.setRegexp(parts[2]) } return n } func newRoot(path string) *node { return newNode(nil, path) } </code></pre> <hr/>**评论:**<br/><br/>aboukirev: <pre><p>Keep in mind that many routers (Gorilla mux, for instance) don&#39;t bother with mutex on the router tree. The idea is that you construct the routes, build a tree, and then start serving, No modifications to the tree from that point on.</p> <p>I can see one scenario where a user adds a resource and gives it an arbitrary alias, which might be useful to have in the router tree as a static (no parameters) route as it does not fit any predefined pattern. But that&#39;s a special case.</p> <p>So one can get by without a mutex altogether.</p></pre>vardius-: <pre><p>This is true that on server run most of the routes are constructed and then served, but what if one specific app will need to create routes on the run ? dynamic routes of some kind that depends one a values from database or any other external source</p></pre>daveddev: <pre><p>Consider placing that logic within handlers.</p> <ul> <li>/api/something (static)</li> <li>/api/another (static)</li> <li>/blog/{all requests sent to special handler} (dynamic)</li> </ul> <p>This allows you to keep synchronization primitives closely associated with the need they fulfill.</p></pre>vardius-: <pre><p>i mean about case scenario where the route does not exist yet, like:</p> <ul> <li>/{username}/{user_property}</li> </ul> <p>and needs to be added when user is created. doesn&#39;t this or other more complex example needs thread safety ?</p></pre>daveddev: <pre><p>It appears that you&#39;re confusing dynamically added routes, and routes with parameters. At the moment, I&#39;m unsure how to help bring clarity to the matter (I&#39;m quite tired). If something comes to mind, I&#39;ll edit this post.</p> <p>Edited...</p> <p>Consider:</p> <ul> <li>/api/user/{id}/images -&gt; router sends to handler which retrieves images by user id</li> <li>/api/user/{id}/friends -&gt; router sends to handler which retrieves friends by user id</li> <li>/blog/{slug} -&gt; router sends to handler which retrieves blog posts by blog post slug</li> <li>/{unknown} -&gt; router sends to handler (sub-router) which sends to another handler which processes the request.</li> </ul> <p>This ensures that known routes avoid the overhead required by unknown routes. However, I do suggest that if the routing must be dynamic to that degree, that there may be unresolved design issues in play (or maybe simply poorly named routes).</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

420 次点击  
加入收藏 微博
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传