Too many open files 的解决办法

在开发TCP网络应用的过程中,我们经常会遇到“Too many open files”这个问题。这说明你的程序以达到Linux所允许的打开文件数上限。你需要按照以下方式来提升:

每用户上限:

打开 /etc/security/limits.conf
在末尾添加:

1
2
3
4
* hard nofile 500000
* soft nofile 500000
root hard nofile 500000
root soft nofile 500000

修改后,你需要logout并重新login。

pam-limits

据说对于Daemon进程需要额外的步骤,但目前我并不需要。如果以上改动不能对你有所帮助,可能需要以下步骤。

打开 /etc/pam.d/common-session

添加以下内容:

1
session required pam_limits.so

系统级限制

这项设置应该大于没用户限制。

打开 /etc/sysctl.conf

添加以下内容:

1
fs.file-max = 2097152

运行:

1
sysctl -p

这会增加系统级的最大打开文件数。

验证效果

使用以下命令验证系统级最大打开文件数

1
cat /proc/sys/fs/file-max

Hard Limit

1
ulimit -Hn

Soft Limit

1
ulimit -Sn

检测用户限制

www-data替换为你希望检测的用户。

1
su - www-data -c 'ulimit -aHS' -s '/bin/bash'

检测运行中的进程限制

XXX替换为PID

1
cat /proc/XXX/limits

今日在一台新启动的服务器上,系统级、用户级配置都正常,唯独进程的limits仅为默认1024。进程使用的是supervisor守护启动,使用supervisor restart进程后,limits依然为1024,后重启了supervisor服务,limits恢复所设定数值。

开源静态网站生成工具

有博客订阅者反馈我的博客ATOM输出有问题,总有两篇已unpublish的博客出来诈尸。这个是个老问题,当时也是因为总是有这两篇博客捣乱所以unpublish了,但完全无效。

自己的博客一直没有认真维护,只是偶尔随手记录,更没有想到有人订阅了,闻之顿感责任重大。计划认真整理一下自己的博客。

那两篇诈尸的博客,或是github的bug,或是jekyll的bug,未能验证。细思之,将博客系统完全交予github管理,的确不够合理,于是考虑使用本地生成静态文件再行push的方式,代替先push再由github jekyll生成静态文件的方式。顺便也更新一下自己博客的样式。

关于jekyll,也有不少问题,比如不能生存tag page等,也考虑更换一下生成器。奈何目前的静态网站生成器着实太多,无从选择。幸有有心人整理了一个列表,staticgen.com,包含了github的关注数、项目使用的语言,模版语言等信息,选择方便了许多。

计算机科学经典书籍

在我学习编程的过程中,看过很多的书籍,大部分的书籍只能提供很有限的知识,有的甚至不能提供真正的“知识,充其量只是某个软件产品的使用手册而已,而且这类手册极易过时。现在,因为工作的需要,我依然会偶尔阅读这类“手册”。但我渐渐体会到了有些计算机知识是永恒的,如果从一开始我所阅读的,都是这些永恒的知识,而不是那些短暂的技能,我的知识体系会比现在更加的完整。

所以,我整理了这个列表。我希望他能涵盖目前已知的计算机科学体系,并且尽可能的在每个领域都提供一两本经典书籍。我会优先选取发布时间较早的书籍,因为那代表着它经得起时间的考验,同时我会参考Amazon和豆瓣的评分,以及维基百科等网站的引用和评价。我希望所提供的这个列表是“相对权威”的列表。

我会尽可能的包含中文译名和英文原版书名。

我会尽可能按照权威的学科分类对书籍进行划分,如果在分类上有错误,希望各位指出。

我也希望能够对学科间的依赖关系明确描述,默认是无依赖,当我发现了某个学科需要依赖于一些前置知识的时候,我会注明。如有遗漏,希望指出。

在列表中,也会包含部分非“计算机科学”方面的书籍,比如游戏开发,互联网相关的书籍。这些书籍入选的条件时,他是适合程序员阅读的,并且同样符合经典、权威的要求的。

科普

  • 1985 计算机科学概论 Computer Science: An Overview
  • 1999 编码 Code

计算历史

  • The Computer from Pascal to von Neumann
  • A History of Computing in the Twentieth Century

Mathematics 数学

  • 300 BC 几何原本 Elements
  • 1687 自然哲学的数学原理 Philosophiae Naturalis Principia Mathematica
  • 1965 微积分和数学分析引论 Introduction to Calculus and Analysis (Richard Courant / Fritz John)
  • 1976 线性代数及其应用(侯自新 译)Linear Algebra and Its Applications (Gilbert Strang)
  • 1988 具体数学 Concrete Mathematics
  • 1995 线性代数应该这样学 Linear Algebra Done Right
  • (当下流行) 托马斯微积分 Thomas calculus
  • (当下流行) 初等概率论

Computability 计算理论

  • Alan Turing (1937) “On computable numbers, with an application to the Entscheidungsproblem”
  • (1979) 自动机理论、语言和计算导论 Introduction to Automata Theory, Languages, and Computation

本书建议看原版

  • 1996 计算理论导论 Introduction to the Theory of Computation

Information theory 信息论

  • Shannon, C.E. (1948) “A mathematical theory of communication”
  • Hamming, Richard (1950) “Error detecting and error correcting codes”
  • Huffman, D. (1952). “A Method for the Construction of Minimum-Redundancy Codes”
  • Ziv, J.; Lempel, A. (1977). “A universal algorithm for sequential data compression”
  • 1991 Elements of Information Theory

Computer Architecture 计算机架构

  • 1990 Computer Architecture: A Quantitative Approach

Operating System 操作系统

  • 1974 操作系统设计与实现 Operating Systems Design and Implementation
  • 1992 UNIX环境高级编程 Advanced Programming in the UNIX Environment
  • 1992 现代操作系统 Modern Operating Systems
  • 2002 深入理解计算机系统 Computer Systems:A Programmer’s Perspective

Compilers 编译原理

  • 1986 Compilers: Principles, Techniques and Tools

在封面变成一条龙后,被称为 dragon book.

img

Programming Languages

  • 1978 C程序设计语言 The C programming Language

Programming ?? (not science)

  • 1984 计算机程序的构造和解释 Structure and Interpretation of Computer Programs
  • 1986 编程珠玑 Programming pearls
  • 1993 代码大全 Code Complete

Algorithms 算法

  • 1968 The Art of Computer Programming
  • 1974 The Design and Analysis of Computer Algorithms
  • 1983 Data Structures and Algorithms
  • 1983 Algorithms
  • 1990 Introduction to Algorithms
  • 1996 Data Structures and Algorithm Analysis in C

Computational complexity theory 计算复杂度理论

  • 1979 Computers and Intractability: A Guide to the Theory of NP-Completeness
  • 1994 Computational Complexity

Networking 网络

  • TCP/IP详解 卷1:协议
  • UNIX网络编程

Database 数据库

  • 1980 Principles of Database & Knowledge-Base Systems, Vol. 1: Classical Database Systems
  • 1987 Database System Concepts
  • 2001 数据库系统全书 Database Systems: The Complete Book

Software engineering 软件工程

  • 1975 人月神话 The Mythical Man-Month: Essays on Software Engineering
  • 1994 设计模式 Design Patterns: Elements of Reusable Object-Oriented Software
  • 1999 程序员修炼之道 The Pragmatic Programmer: From Journeyman to Master

面向对象程序设计的经典。但当你使用动态语言时,会发现部分模式是不必要的

  • 2004 软件随想录 Joe on software
  • 重构:既有代码的改善 Refactoring: Improving the Design of Existing Code
  • 人件
  • Rework

Artificial Intelligence

  • 1994 人工智能:一种现代方法 Artificial Intelligence: A modern approach

机器学习

膜计算 Membrane Computing ??? 这是什么?

  • 2002 膜计算导论 Membrane Computing - An Introduction

参考:
ACM: 经典出版物
维基百科:计算机科学重要出版物
维基百科:理论计算机科学重要出版物
Goodreads: 必备计算机科学书籍列表
Goodreads: 游戏开发
维基百科:数学著作列表

Kingshard阅读笔记

KingShard 是 mysql 代理,可实现分库分表等功能。但因业务复杂,无法使用,故阅读其源码,以便修改。

代码版本:
2016-09-12
3c4a1db63226cb1384047a3f915d10e0594228d1

入口: cmd/

服务启动: proxy/server/server.go

Server.Run()
服务逻辑

Server.onConn(net.Conn)
客户端连接逻辑

  • Server.newClientConn(net.Conn)
    客户端连接初始化

客户端连接:proxy/server/conn.go

ClientConn.IsAllowConnect()
判断客户端IP是否合法

ClientConn.Handshake()
握手

ClientConn.Run()
客户端逻辑

ClientConn.readPacket()
PacketIO.ReadPacket()
拆包逻辑

ClientConn.dispatch(data)
包分发逻辑
data[0] mysql命令

  • QUIT c.handleRollback() c.Close()
  • QUERY c.handleQuery(string(data))
  • PING
  • INIT_DB c.handleUseDB 分配后端节点 backend.Node
  • FIELD_LIST
  • STMT_PREPARE
  • STMT_EXECUTE
  • STMT_CLOSE
  • STMT_SEND_LONG_DATA
  • STMT_RESET
  • SET_OPTION
    其它命令不支持,log

命令执行成功后,写结果给客户端,某些命令只需 ClientConn.writeOK

执行查询 ClientConn.handleQuery
ClientConn.preHanleShard 不确定什么时候会用
解析sql
handleSelect
handleExec
handleBegin
handleCommit
handleRollback

后端节点: backend/node.go backend.Node
GetSlaveConn
GetMasterConn

后端连接:
基类 backend/backend_conn.go backend.Conn
子类 backend/db.go backend.BackendConn

写入命令,读取返回
writeCommandStr
readOK

SQL 语法解析
sqlparser
需要执行 make,通过yacc编译

其它
Counter 计数器

雇主与雇员的关系

通常,我们认为雇主与雇员之间的关系是一种管理者和被管理者的关系,但在作者看来,雇主与雇员之间的关系并非如此,在很多时候雇员往往拥有更多的主动权。

在法制社会中,当我们成立一家公司时,股东之间通过契约联系在了一起,向同一个目标共同努力,同时实现股东个体的成功。可以说,契约精神是公司赖以存在的基础。

在公司与员工的关系上,契约精神同样是重要的基础。在公司与员工形成雇佣关系,这种契约关系就建立了。

在一个人成为管理者之前,应该对这一事实有充分的认识。你与下属是平等的,你只有一种权利那就是聘用和解聘的权利。如果公司没有赋予管理者这种权利,本质上,管理者只是个为员工提供服务的人员。员工会向你索取各种资源配合他们的工作,而你提供资源的过程和服务人员递上餐具和食物的过程并没有太大区别。

人事部门应该按市场规则与员工进行互动,本质上就和买菜时侃价行为类似。是一种交易行为。如果你要对其进行职位和薪资的调整,本质上是一种内部重新聘用的过程,即使是升职加薪,也需要征得员工的同意,然后才可执行。如果要进行合理的避税,这些事项必须在签订契约(也就是聘用)之前达成共识。对于辞退员工,各种处理方法亦必须要按照契约精神进行,无论是劝退或是辞退,本质上就是一种公司违约行为,理应给予赔偿。对于一些曾经有过突出贡献的员工,在辞退时不仅要给予法律内规定的赔偿,甚至要给予更多的“补偿”。这种补偿本质上也是基于一条根本契约:员工向公司提供劳动,公司为这种劳动支付报酬。违反这种契约,或许不会受到法律的惩罚,但会使某一方丧失在市场上的信用,无论是公司方还是员工方。

No Blame Culture,它的基础在于,人和人之间是平等。人们通过不同的分工在一起协作,人们不应因理念、方法的差别而受到羞辱或讽刺。

Go语言的GC优化技巧

GC只要不出问题,就不会有人关心GC的问题,但如果GC出了问题,想要优化它却不是一件容易的事情。我最近就遇到了Golang GC问题,经过一系列的尝试终于将应用性能优化了300%的。你一定会觉得性能优化300%,那之前的代码得写的多烂啊。坦白的说,之前的代码虽然未做优化,但并没有大的问题。

我们的IM服务器,使用golang开发,自定义的协议,运行在一台4核8G的云主机上,同时在线大约一万多人。一直以来,运行的还不错,但近期我们发现当在线人数达到2万人的时候,CPU占用就会达到100%,它就彻底的失去了响应。我的内心是奔溃的,因为根据我们曾经做过的压力测试推测,这点连接数完全不应该有问题的。

定位CPU问题

Golang定位CPU问题还是比较方便的,因为golang提供了非常便捷的profile工具。官方的文档看这里

首先需要在程序内部启动一个http服务,并引入net/http/pprof模块,就会自动增加 /debug/pprof/* 相关的Handler。其中 /debug/pprof/profile为CPU profile,/debug/pprof/heap为heap信息, 也是我们主要关注的两个信息。

1
2
3
4
5
6
import _ "net/http/pprof"
import "net/http"
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()

添加好上述代码,并部署执行后,即可使用如下go tool pprof命令进行监测

go tool pprof http://localhost:6060/debug/pprof/profile

默认会搜集30s的profile,这里会等待30s。然后就可以查看profile的结果了。

> png > profile.png

我们发现最耗时的几个点 runtime.scanobject, runtime.pcvalue, runtime.memclr

runtime.scanobject 7.82s(18.16%) of 10.64s(24.74%)

runtime.pcvalue 4.43s(10.29%) of 9.50s(22.07%)

runtime.memclr 3.18s(7.39%)

而它们大部分都来自于 runtime.gcDrain

runtime.gcDrain 0.02s of 23.66s(54.96%)

而这些都是和GC相关的操作,至此,我们可以得出一个初步结论,GC占用了大部分的CPU。

第一次实验

既然是GC问题,那么我们看一下heap吧

go tool pprof http://localhost:6060/debug/pprof/heap
> png > heap.png

通过Heap我们可以发现内存占用最大的是Connection,每一个IM用户的连接都会有一个独立的Connection,而这个Connection上还会保存一些用户ID、昵称等基本信息。同时我们要把每个Connection放入一个map中,让我们称他cmap,cmap的key是用户ID,这样我们才能定位用户对应的Connection。

我们并不能确定问题在cmap上,或者是Connection结构上。但是我们完全没有其他的线索,既然它的heap占用最大,就先拿它开刀吧。

为了验证我们的假设,我们在测试环境中,不断的创建连接,关闭连接,同时运行profile。profile的结果显示,GC完全没问题,甚至GC都没出现在profile中。

其实这时测试实验已经可以基本确定GC问题和Connection和cmap无关了。但是我们依然希望对Connection的创建与销毁进行一个优化,然后再来对比GC是否有改善。

对象池模式

我们决定使用对象池模式来优化Connection的创建销毁。所谓对象池模式,就是在对象创建时我们通过对象池进行获取而不是分配内存创建新对象,当我们要释放一个对象时也不是直接丢给GC而是放入对象池。

Golang已经提供了一个sync.Pool实现。`sync.Pool是线程安全的,你可以在多个goroutine中安全的使用它。Pool可能在任何时候回收Pool中的对象,所以你不用关心Pool`的GC问题。

优化的调整也很简单:

优化前代码:

1
2
3
4
5
6
7
8
func NewConnection(conn net.Conn) *Connection{
c := &Connection{conn: conn}
// init stuffs...
}
func (c *Connection) Close() error{
c.conn.Close()
}

优化后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 对象池:全局对象池
var cPool = sync.Pool{
// 当对象池中无对象时,如何创建新对象
New:func() interface{}{return new(Connection)}
}
func NewConnection(conn net.Conn) *Connection{
c := cPool.Get() // 从对象池中获取
c.conn = conn //初始化
// init stuffs...
}
func (c *Connection) Close() error{
c.conn.Close()
c.conn = nil // Relaese
// release stuffs ...
cPool.Put(c) // 放回对象池
}

测试了没问题就发布了。事实再次证明,GC和Connection、connection map完全没有关系,GC的问题在生产环境上依然如常。而且还产生了一些bug,因为原本的释放由GC完成,所以不需要做临时属性的reset工作,但使用了对象池后,这些未释放的临时属性会一直存在,当你以为你是用的是一个NewConnection的时候,其他它并不是一个新的对象。

我的结论是,在与业务逻辑相关的地方,尽量不要使用对象池,以免引出新的bug。对象池应该用在那些边界明确、高内聚的模块上。

slice, append 的妙用

GC更多还是与内存的分配与释放频次有关,我们把目光从内存占用量最大的模块上移开,关注了一下其他琐碎的小对象。通过profile与heap的对比往往能比较好的发现问题,profile与频率相关,heap与占用相关,如果两样都占了,那么就是很可疑的。

通过对比,我决定优化一下日志模块,这是自己写的一个小的日志模块,为了能更方便记录每个Connection上的基本信息、时间、行号等,但写的比较直接,基本靠 Time.Format fmt.Sprintf 完成了每行日志文本的拼接,这两个函数比较易用,但是性能并不是最佳。我参照了原生的log模块,对自己的日期格式化和日志文本的拼接进行了优化,值得一提的是一个原生log代码中对append的妙用。

优化前代码示意:

1
2
3
4
5
6
7
8
9
type Logger struct {
}
func (l *Logger) Log(str string) {
...
fmt.Fprintln(writer, fmt.Sprintf("%s %s:%d %s %s",
"[INFO]",filename, line, str
))
}

优化后代码示意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Logger struct {
buf []byte
}
func (l *Logger) formatHeader(buf *[]byte) {
...
*buf = append(*buf, "[INFO]"...)
*buf = append(*buf, filename...)
*buf = append(*buf, ':')
*buf = append(*buf, itoa(line)...)
}
func (l *Logger) Log(str string) {
l.buf = l.buf[:0] // Reset slice
l.formatHeader(&l.buf)
l.buf = append(l.buf, str...)
l.writer.Write(*buf)
}

以上的示例代码中,大家可以仔细体会一下。Logger对象有一个bufl.buf:=l.buf[:0]这句本质上只是把buf这个slice的length修改了,但是capacity并没有改变,所以不会发生内存的分配。而*buf=append(*buf, xxx) 只有在length > capacity 时才会导致内存重新分配。而length > capacity 是很少发生的,也就意味着很少发生buf内存的分配,这也就是优化了GC。

受此启发,我们每次读取网络数据包的位置,也做了类似的优化,但这里无法使用append,只能直接make。

优化前的代码类似这样:

1
2
3
4
5
func (c *Connection) ReadBuffer(length) []byte{
buff := make([]byte, length)
io.ReadFull(c.reader, buff)
return buff
}

我在Connection中增加了一个可重用的buf缓冲区,优化后的代码类似这样:

1
2
3
4
5
6
7
8
func (c *Connection) ReadBuffer(length int) []byte{
if length > len(c.buf) {
c.buf = make([]byte, length)
}
buff := c.buf[:length]
io.ReadFull(reader, buff)
return buff
}

这可以大大减少读数据时的内存分配。

这里我们可以总结出一条GC优化原则:
* 成员变量代替临时变量,减少内存分配。

注意:文中代码仅为示例,未考虑线程安全因素,实际使用中需要对成员变量的访问加锁。

return还是传址

成员变量可以解决方法内部的GC问题,但是当需要与外部交互时,我们还需要其他的技巧。比如这个函数:

1
2
3
4
5
6
func (c *Connection) ReadPacket() *Packet {
buff := c.ReadBuffer()
packet := new(Packet)
packet.Init(buff)
return packet
}

这里我们不可避免的创建了一个Packet对象。而如果我们将函数进行如下改造,则可以为其他的代码创造出优化的空间。

1
2
3
4
5
6
7
8
9
10
func (c *Connection) ReadPacket() *Packet {
packet := new(Packet)
c.Read(packet)
return packet
}
func (c *Connection) Read(packet *Packet) {
buff := c.ReadBuffer()
packet.Init(buff)
}

我们保留了ReadPacket方法以保持向下兼容,同时增加了一个新方法Read,它没有return,但是需要你传入一个Packet指针,这使它的消费者有条件重用这个Packet。

原消费者代码:

1
2
3
4
for {
packet := connection.ReadPacket()
handle(packet)
}

这里每次会创建一个新的packet。

优化后的消费者代码:

1
2
3
4
5
packet = new(Packet)
for {
connection.Read(packet)
handle(packet)
}

这里始终重用一个packet。

类型转换,[]byte to string

在网络应用中,不可避免的要使用[]byte,并且需要将[]byte转换为最终要使用的数据,因此我们经常需要做 []byte到string的转换。但是我们发现string(bytes) 这一转换将引起一次内存copy,这使得转换会产生一些重复数据。有一些黑科技可以让它不复制内存,但是这样获得的string没有imutable特性,会导致不可预料的后果以及一些内部优化也将失效。

1
2
3
4
5
6
7
func unsafeToString(bytes []byte) *string {
hdr := &reflect.StringHeader{
Data: uintptr(unsafe.Pointer(&bytes[0])),
Len: len(bytes),
}
return (*string)(unsafe.Pointer(hdr))
}

我尚不能确定是否有其他类型的相互转换也会有同样的问题。

GOGC

使用GOGC环境变量或者SetGCPercent,可以用来调整GC的触发频率,默认 GOGC=100,我们将这个数值调到了GOGC=200。对于这个参数,我还没有深入的研究,不过实验证明调高这个数值可以减少GC的CPU占用。

GC日志

有时在做一些实验的时候我们可以通过观察GC日志来发现检验效果。你需要使用GODEBUG=gctrace=1 作为程序启动的环境变量,即可看到GC相关信息。

GODEBUG=gctrace=1 yourprogram

禁术

Well,做了这么多,我们的CPU占用情况和GC情况到底改善了多少呢?18000连接左右时,CPU从80%占用降低到了50%占用。GC占比依然很高,仅从54%下降到了50%。这说明,我们并没有找到影响GC真正的原因。我必须寻找下一个优化点,否则一切好无头绪。于是我再次对比CPU profile和Heap dump的图,我想也许是业务逻辑处理模块的问题,但我还不能确定是哪个业务模块的问题,那就赌那个CPU占用最高的业务模块吧。

但已经花费了太多的时间优化GC,我不想再浪费时间不断的猜测,于是我只能使出我从不轻易使用的禁术(请勿模仿,使用不当可能会对你的职业生涯造成严重的伤害)————直接禁用了那个功能,并以最快的时间跑出了profile,然后立刻恢复了这个功能。

这次猜中了,当禁用了这个功能后,GC情况大大改善。这下心里有底了,顺藤摸瓜就发现了这么一段代码:

1
2
3
4
5
6
7
8
9
10
11
func Compress(p []byte) ([]byte, error) {
buf := new(bytes.Buffer)
zipWriter := zlib.NewWriter(buf)
if _, err := zipWriter.Write(p); err != nil {
return nil, err
}
if err := zipWriter.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

这就是照着golang官方zlib Example写的。看来Golang的zlib封装有很大的问题,导致了这个对象很重。

既然已经找到了问题的原因,就很好办了。解决的方法就是最开始提到的对象池模式。这次并没有使用sync.Pool,而是自己实现了一个简单的对象池。完整代码就不贴了

1
2
3
4
5
6
7
8
9
10
11
12
func (w *zlibWorker) compress(p []byte) ([]byte, error) {
buf := new(bytes.Buffer)
zipWriter := w.zipWriter // 重用zipWriter
zipWriter.Reset(buf) // <- 主要在这里
if _, err := zipWriter.Write(p); err != nil {
return nil, err
}
if err := zipWriter.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

优化后的同时段的CPU占用率从50%下降到了20%,GC占用量可以忽略。而CPU占用最高的三个函数分别是 runtime.memmovecompress/flate.(*compressor).reset 还有 syscall.Syscall,前两个都源自zlib.Writer.Reset,看来还有进一步的优化空间,比如用CGO,不过这已经超出了本文的讨论范围。syscall.Syscall是网络应用不可避免的的部分,无法优化。可以将syscall.Syscall视为性能优化的参照标准,这一函数的CPU占比越大,通常程序是越健康的。

总结

这些技巧在其他有自动垃圾回收机制的语言中也同样适用

  • Profile与Heap对照,往往能更有效的定位问题
  • 通过实验来验证猜测
  • 使用成员变量代替临时变量
  • 使用传址代替返回
  • 对比较重的对象应用对象池模式
  • 利用语言特性进行优化
  • 调整GC相关参数进行优化

React相关坑记录

空组件

这个错误可以用最简单的方式重现

1
2
var Example = null
ReactDOM.render(<Example/>, document.getElementById('container'))

会报以下错误:

1
2
3
Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components).
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

babel@6 export default

1
export default Example

babel使用了 presets ['es2015', 'react', 'stage-2'] 转换为了

1
exports.default = Example

请使用 module.exports = Exmaple 代替。

以上两个问题结合起来后产生的问题,让我抓狂了数日。

vim现代js设置

Install vim plugin: yajs editorconfig-vim

1
2
let g:jsx_ext_required = 0 " Allow JSX in normal JS files
let g:syntastic_javascript_checkers = ['eslint']

安装相关工具

1
npm install -g eslint babel-eslint eslint-plugin-react

代码检查开关:

1
2
3
4
5
6
/* eslint-disable no-console */
//suppress all warnings between comments
console.log('foo');
/* eslint-enable */

一种声形输入法设计

总体规则

1码声母,2码主部,3码次部,4码取次次码或字形码, … 结束字型码。

特点:

音形合用,书写顺序无关,完全从汉字结构出发。

声母编码

声母的,zh用v,ch用i,sh用u。第一码无需思考。

部首编码

主部指主偏旁,不在于上下左右,比如桂,主部取木。剑,主部取立刀。这样规则虽然有时不严谨,但大部分情况易于辨识。基本可以在不需要思考的情况下输入第二码。如无主次之分,则从左到右,从上到下,如吉,取士。左右皆为部首,首先取含义最匹配的为部首,其次取不需拆解的。如果不易分辨,左右结构优先取笔画少的为部首,否则遵循从上到下,从左到右规则。

次部取剩余部分的主码。依次类推。

没有剩余部分,但依然需要进一步区分的,取自身所包含的次要部件。

字型编码

结束字型码只会出现在结尾。你可以跳过中间的码直接输入结束字型码。结束字型码有3个,左右字,独体字和上下字,其他。你可以直接输入声母和结束字型码。

三个码表,相互独立。用户可以选择性的配置是否包含“声码”,“韵码”,“部首码”,“字型码”。主要难度在于字根部首分布的设计和字符集的编码完善。

工作方式可以采用分布式工作方式,将字符集分割为200个文件,开放的人工的方式对200个文件进行修改提交。

为了避免字根表设计不合理,导致码表分布不均匀的情况。工作应采用,先进行部首拆解,再进行字根合并编码的过程。
比如:型,土,刀,开。避,之(辟左)辛。