Archive for 二月, 2015

在《HBase:The definitvie Guide》里,Lars George首先从多个dimensions讨论了NoSQL与传统RDBMS的异同。那么HBase在这些纬度上,是如何选型和实现的呢?

Data Model

一般认为Hbase是column-oriented。但是同样在该书里,作者也强调了HBase的column-oriented与RDBMS里的column oriented又有所不同。HBase在存储时,确实是以column family为单位的,但在查询时是以key为基础的。

Note, though,that HBase is not a column-oriented database in the typical RDBMS sense, but utilizes an on-disk column storage format. This is also where the majority of similarities end, because although HBase store data on disk in a column-oriented format, it is distinctly different from traditional columnar databases: whereas columnar databases excel at providing real-time analytical access to data, HBase excels at providing key-based access to a specific cell of data, or a sequential range of cells.

Storage Model

HBase是persistent, 但它其实是一个memory与disk的结合体。增删改时数据先写WAL(可配),再写入memstore,在memtable满了之后,才会flush到持久化存储介质(一般是HDFS,3备份,都可配)的HFile里。而查询时,会merge memstore和多个HFiles的数据。而且,为了避免disk IO的延迟,对于hot data,Region server会把它以block为单位cache起来,也就是说,最好的情况下,有可能你的查询都在内存里完成。

Consistency model

这里的consistency,我理解是指CAP(consistency, availability,partition tolerance)里的一致性,包含了原子性和隔离性两个概念。HBase保证对于一个row-key里多个column families or columns的写入是原子的,但跨row-keys的写入就非原子了。这也意味着,如果有两个clients A和B在访问HBase,A批量写入一些跨row-keys的数据,B在此过程中读取会看到行与行的中间态,但不会有一行数据的中间态。

Physical model

HBase天生就是分布式的(当然为了学习目的,也可以把所有进程都配置在一台服务器上)。它依赖zookeeper协调各种进程角色,例如master与backup master之间的选主,region servers的存活性检查等,以及关键数据的保存,例如root,meta tables等。

在增加新region servers,对用户基本无感,master会在合适的时机(例如load balance 或者splitting),把流量分配到新服务器上。

Read/write performance

HBase支持random access, range scan, sequence access等,由于其数据是按key有序存储的,且存在block cache,所以比较高效的是range scan和sequence access。大量的Random access会使block cache无效,且造成频繁的disk IO,甚至Java heap问题,导致GC,性能会受到影响。

写入是先追加写WAL,再写memstore。后者是内存操作,耗时较小。WAL可以配置是实时flush,还是定期、阈值flush,由于其底层存储也是HDFS,故flush的代价较大,需要平衡持久性和写性能。

Secondary indexes

HBase不支持secondary indexes。但可以通过row-key,column family,version等的设计,模拟一些secondary indexes的场景。而且在HBase的设计理念里,也可以通过一些反范式的方式,例如冗余存储,提高查询性能,所以,是否一定要依赖secondary indexes也是一件值得商榷的事情。

Failure handling

首先,HBase架构里,master是一个单点,但由于client对数据的ACID不需要经过master,是直接与zookeeper和region servers通信的,所以master故障时,服务仍然可用。

但master的故障,在恢复或容错之前,会导致表结构、集群配置等无法修改。而且load balancing、splitting等操作也无法进行,如果再同时遇到region server的故障,那么也无法恢复等等。所以,master的故障还是非常需要重视的。

它的容错通过backup masters与zookeeper集群配合的选主完成,由于master上没有什么不可恢复的数据,例如region servers配置等是在zookeeper上,每个region server有哪些分片数据,是由region server自己维护并上报的,所以master的切换一般不会造成数据或元数据的丢失。

其次,如果region server故障,由于在逻辑层它是单点,所以短期内其上拥有的分片都是不可访问的(??),但数据不会丢失。

master监测zookeeper 临时节点的变化,感知到region server的故障(不论是硬件还是网络导致的),会选择新的region server来承担它之前的任务。在切换前,需要将故障region server的WAL日志splitting(由master和多个region servers配合完成)和replay(新region server完成,它之前需要从HDFS加载各个分片对应的已持久化的HFiles)。之后新的region server可用。

底层存储层,如果使用的是HDFS,则failure handling是由hdfs保证的。

Compression

如果不是大数据,用HBase就太复杂了。而既然是大数据,那么压缩就是一个重要的问题,动辄上T的数据啊!HBase支持多种压缩算法,例如gzip等,且支持扩展。这里指的HBase的压缩是kv层面的,底层的HDFS可能还会有压缩吧(??)。

Load balancing

HMaster定期会启动load balancing(默认5min),检查有没有负载不均衡的情况并予以处理。其理念比较简单,就是每个region servers平均分配regions。

元数据的修改,应该比较好办,耗时也较小。但假设A region server负载重,需要把一些regions移走,那么在此之前,它需要flush WAL,flush memstore,如果本身负载就重,再做这些事情,也比较麻烦吧?HBase有专门的config限制balancing的耗时,默认是2.5min。

由于没真实经历过,蛮好奇在实际运维中,是如何解决的呢?选择流量低峰期收工触发?

Atomic read-modify-write

在上面consistent里,已经介绍了HBase的row层级的事务。同时,HBase还提供Compare and set(CAS)操作。例如checkAndPut、checkAndDelete,针对相同的rowkey,先check是否等于指定值,然后再put or delete。

HBase的增删改操作,如前所述,都需要先写optional的WAL,再写memstore。猜测memstore是可以用row级别的锁来保证没有竞争,但WAL呢?如果涉及一个row下的多个column,且分成多个kv pair写入log,中途失败了怎么办呢?那么在replay的时候就破坏了原子性了!为了解决这个问题,HBase将一次写入的、同一rowkey的、多个column的数据,封装为一个WALEdit对象,一次性写入log,这样就保证了故障时的原子性。(异步flush这个waledit时如果失败会怎样?这时client端应该已经接收到true的返回值,认为成功。所以,就丢失数据了!这个topic应该在durability里讨论)

Locking, waits and deadlocks

HBase与数据相关的锁,应该大部分是行级锁。写入有row level lock;get/scan操作依赖multiversion concurrency control-style,所以不需要锁。

以put操作为例,默认情况下,锁是由server端自动管理的。也提供方法,在client端自己构造RowLock实例,并作为参数调用put方法。但这样会增加deadlock的几率,并降低HBase的吞吐!所以,除非万不得已,别这么玩。

get操作也同样提供了RowLock参赛,也同样由于deadlock和性能的原因,不推荐用。

关于锁,也有一些发散的疑问:

每个regionserver共享一个HLog实例,那锁加在哪里呢?HRegion是同步调用HLog写入的,所以会不会是互斥锁保证HLog实例在同一时间,只能被一个HRegion调用?

memstore是一个怎样的结构呢?有序数组?树形结构?数组插入数据的效率太差,应该不大可能。如果是树形结构,那么一旦发生节点关系的调整,会不会对整棵树、或path有影响呢?这依赖于其数据结构的选择。

当跨column families时,涉及多个store,即涉及其中的多个memstores,所以,这个锁是较为独立的?

Nginx与Lua

nginx与apache的区别

最常被提及的是,epoll与select的区别。

1. select仅会告知app有read、write or timeout事件发生,至于是哪个fd ready了,需要app遍历检查,一旦fd个数较多,性能就会比较低下。而epoll会直接告知app哪些fds ready了,直接操作即可。

2. select在每次调用时,都需要把fd列表从用户态copy至内核态,而epoll仅在初始化时copy一次。

 

在与php等脚本语言结合方式上也有所不同。

apache一般通过module的方式,加载php_mod.so,php运行在apache进程空间里,形成Process Per Connection or Thread Per Connection方式,而web请求一般都是IO阻塞型的,需要访问后端服务、DB、cache等读取、整合数据,在这个过程中,对应的httpd进程也就被占用住了,无法提供服务。一旦达到httpd进程数上限,就只能refuse了。

nginx通过fastcgi协议,网络交互的方式与php_fpm进程通信。php处理事务的时候,nginx仍然可以接收请求、定位可用的php进程、排队。

 

扩展性方面,lua与nginx完美结合,而lua又可以方便的与c/c++ so包交互,使功能的扩展非常方便。

 

其实在多种细节角度,nginx也做的很好。(再翻翻nginx内核,然后补充吧)

nginx的防攻击实现(request limit等module的用法)

nginx的ngx_http_limit_req_module可以限制某一IP在一段时间内的访问频率,有burst模式和匀速模式。

其不适合我们的地方是:单机版,没有白名单机制,规则太简单,无法抗DDOS(主要指7层DDOS)。

所以,首先用lua对其做了一层封装,当limit module返回非200 code时,检查ip是否在白名单dict里,否则就返回403 forbidden。

随后,引入防攻击离线计算模块,规避单机版和规则问题。

lua exception时,如何不影响整体流程

采用pcall的方式,一旦出错不会导致进程退出,而是返回err code。

nginx封禁名单的过期机制

lua_shared_dict本身支持expire机制。

nginx dict的实现原理

利用shared memory实现,被当前nginx实例的worker进程共享。(如果一个服务器上运行多个nginx实例,相互间无法访问)

也利用LRU机制释放内存,利用set写入数据时,如果分配的内存空间满了,则会优先释放expired数据,如果还不够,则会根据LRU算法,flush掉一些not-yet-expired data。如果是使用safe_set,则不会flush掉一些not-yet-expired data,而是返回false。

nginx读取header、body时需注意的事情

nginx对body采取延迟读取的策略, 而我们在做灰度发布时,会希望在access phase就获取body内容。所以需要收工操作。

首先根据content_length检查是否存在http body(这里当chunked时,可能会有问题),由于nginx会对http body做延迟读,所以需要显示调用pcall(ngx.req.read_body)进行读取(若之前已读取过,该方法会立刻返回),然后通过ngx.req.get_post_args方法获取POST数组,并取出对应的POST参数(可能为空)。

ngx.req.read_body()   --- 触发读取到内存或文件
local args = ngx.req.get_post_args()  ---- 从内存中读取post为hash table

可以看到,简单调用get_post_args可能无法完整获取数据,如果post请求太大,被刷到文件里的话。具体参考:http://wiki.nginx.org/HttpLuaModule#ngx.req.read_body

接入层对nginx phase的扩展

1.启动阶段:

分配内存空间,lua所需dict,包括:

  • IP白名单,5M
  • router_rule_dict,路由规则,可用于灰度发布、流量隔离、封禁等,10M
  • 防攻击相关,inner_ip_dict, only_ip_dict, ip_ua_dict, ip_uri_dict, ip_refer_dict,10M

并初始化limit_req_zone,区分为one(1.0流量)、static(静态请求)、dynamic(动态请求)三种。

还需设置lua的cpath和path,分别用于加载so包和lua代码。

通过init_by_lua,从文件加载白名单到dict,从文件加载多个模块的router rule到dict。

2.请求执行阶段:

  •     NGX_HTTP_POST_READ_PHASE = 0,    /* 读取请求 */
  •     NGX_HTTP_SERVER_REWRITE_PHASE,   /* server级别的rewrite */
  •     NGX_HTTP_FIND_CONFIG_PHASE,      /* 根据uri查找location */
  •     NGX_HTTP_REWRITE_PHASE,          /* localtion级别的rewrite */
  •     NGX_HTTP_POST_REWRITE_PHASE,     /* server、location级别的rewrite都是在这个phase进行收尾工作的 */
  •     NGX_HTTP_PREACCESS_PHASE,        /* 粗粒度的access */
  •     NGX_HTTP_ACCESS_PHASE,           /* 细粒度的access,比如权限验证、存取控制 */
  •     NGX_HTTP_POST_ACCESS_PHASE,      /* 根据上述两个phase得到access code进行操作 */
  •     NGX_HTTP_TRY_FILES_PHASE,        /* 实现try_files指令 */
  •     NGX_HTTP_CONTENT_PHASE,          /* 生成http响应 */
  •     NGX_HTTP_LOG_PHASE               /* log模块 */

nginx lua module的一些指令只能在特定phase执行,这样对编码造成了影响。例如,在针对Hessian API做路由时,我们需要先读取body内容并decode后,才可以根据router rule,决定如何分流。

而这里有一个矛盾点,就是我们一般会在set_by_lua里根据分流规则,返回待路由的upstream分组。而set_by_lua里是不允许读取request body内容的。所以,很自然,我们决定使用rewrite_by_lua计算分流,但若根据rewrite_by_lua对某个全局变量,类似bUse2,进行赋值。再在后面用if判断bUse2的值,进行proxy或者fastcgi的话,由于if也是在rewrite阶段,且默认会在rewrite_by_lua前执行,那就意味着,执行顺序反了,rewrite永远都不会用到rewrite_by_lua赋值的结果!当然,我们可以用rewrite_by_lua_no_postpone on把rewrite_by_lua放到rewrite前执行,但又遇到一个问题,rewrite_by_lua里是不能给未初始化的ngx.var变量赋值的(未分配内存),而初始化需要用nginx的set指令,该指令又是在rewrite阶段执行……

最终的解决方案是,在access_by_lua里调用dispatcher.dispatch方法,读取Body、反序列化,并利用ngx.exec调用内部location方法进行分流。

一些限制总结:

access_by_lua在phase: access tail执行,set_by_lua在phase: rewrite执行,rewrite_by_lua在phase: rewrite tail执行。http://wiki.nginx.org/HttpLuaModule针对每个lua命令,会指明其运行phase。

那么nginx module或addones的指令分别在哪些phase执行呢?http://wiki.nginx.org/Phases, 这里有一张持续更新的表。(记住,set和if都是rewrite module提供的)

phase optional exits modules / directives
server selection* listenserver_name
post read HttpRealIpModule
server rewrite rewrite
location selection location
location rewrite location selectionfinalize request rewrite
preaccess degradationlimit_zonelimit reqHttpRealIpModule
access finalize request allowdenyauth_basic
try files location selection try_files
content autoindexCoreDAVEmptyGifFastCGIFLVgzip_staticindexmemcachedperlproxyrandom_indexscgi,stub_statusuwsgi
log access_log
post action* post_action

基于以上限制,我们的lua代码最终干预的主要是access和content phases。

具体说,首先在http rewrite阶段,nginx set指令初始化nginx var变量并设置一堆默认值。并if判断,但由于if body的指令,例如proxy、fastcgi都是content阶段执行的,所以当前并不会生效。

在access阶段,根据路由规则、流量异常标识,计算路由目标(proxy、fastcgi or forbidden)。并由lua代码通过ngx.exe,调用nginx internal location实现路由分发。

如果access phase执行失败,则请求还会继续执行下去,会执行content phase,执行默认设置的proxy或者fastcgi规则(与上面的if指令相呼应)。

 

HBase

部署情况

6台服务器,同一机房。1 master,1个secondary master。6台都作为zookeeper服务器了。6台也都运行region server了。在多台上搭建了thrift服务。

服务器都是1.3T普通硬盘,64G内存,12核cpu。

配置及可优化点

未进行特殊的配置优化。

还是默认的3version版本(可以精简),数据也未配置过期时间(其实可以配置),这样可以节省存储空间。

LRU cache size: 6台 * 4G * 0.25 * 0.99=6G。

典型查询与表结构设计

我们的查询,主要是根据userid、type、time range,进行scan查询。由于userid本身具备散列性质,故没有再进行特殊处理。

为了保证尽量顺序的读取,尽量采用上述顺序,保证相同userid、type的数据在同一个block上。

存储的data是经过protobuf序列化后的,由于读取时大多场景是同时读出,故没有采用hbase自身的column方式。

开启了gzip压缩,由于存储的大部分是日志,冗余度较高,压缩比约10:1。

kv len比约为:37:212,仍存在优化空间。

读写压力及优化思路

遇到过查询失败的情况,瓶颈在thrift server。据称hbase自带的thrift服务稳定性和性能都一般。

 

PHP扩展接口的声明

我们经常使用PHP_FUNCTION(count)之类,来声明对外提供的count接口,经过一系列的宏转换之后,会变成:

void zif_count(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)

php脚本的解析运行过程

  1. 使用lex进行词法解析,根据src/main/Zend/zend_language_scanner.l 将php代码解析为token
  2. 使用bison进行语法解析,根据src/main/Zend/zend_language_parser.y将token码,通过y文件里对应的zend compiler函数,填充其对应的struct opcode
  3. zend引擎 解析执行这些opcode

那么PHP语言层面的一些概念,如何实现呢?比如变量的作用域、弱类型?

PHP变量与内存使用

PHP虽然是弱类型语言,但底层基于C,故其底层的真正存储仍然需要区分类型。目前支持的类型有bool、long、double、string、array、object、resouce和NULL。存储对应的数据结构是zval,使用type标明变量的真实类型,使用union _zvalue_value存储变量的真实数据。

从zvalue的定义可以看到:

typedef union _zvalue_value {

        long lval;                                      /* long value */

        double dval;                            /* double value */

        struct {

                char *val;

                int len;

        } str;

        HashTable *ht;                          /* hash table value */

        zend_object_value obj;

} zvalue_value;

bool、long都是使用long类型来存储的,也包括看起来像是short、int、unsigned的数据,也就是说,你以为只需要一个byte的时候,光底层真实数据的存储,就会消耗掉8个byte!类似的情况也出现在float类型上。这也是PHP消耗内存过多的一个原因吧。

NULL型变量比较节省资源,zvalue_value是空指针,仅使用type标识即可。所以,在关注内存的情况下,对于不确定类型的初始化,使用NULL会比使用空字符串、0等要更节省空间。

与array、hash相关的实现是HashTable,其采用链表法解决hash冲突。这里存在的安全漏洞是用户可以通过构造特定的hash key,增大冲突率,从而是hash的O(1)操作退化为链表的O(n)操作,从而造成系统服务能力的退化。PHP目前仅是通过限制用户提交的数据个数的方式,来提高门槛,所以风险依然存在!

max_input_vars integer

How many input variables may be accepted (limit is applied to $_GET, $_POST and $_COOKIE superglobal separately). Use of this directive mitigates the possibility of denial of service attacks which use hash collisions. If there are more input variables than specified by this directive, an E_WARNING is issued, and further input variables are truncated from the request.

另外,在一个hash表里,数据量太大时,可能也会存在resize的情况,这也会在瞬间造成一些数据的copy。需要关注的是rehash函数zend_hash_rehash,在hash中存储的元素数 > hash bucket size的时候会被调用,因为元素过多,会导致冲突率增大。rehash以2x的速率进行,在此过程中确实会分配新的内存以增大buckets 大小,并调整两组双向指针,但不会涉及用户data的copy,所以一定程度上而言还是考虑了性能问题的。

变量赋值与引用计数

总体来说,PHP使用了基于引用计数的GC机制,和在内存使用方面使用写时复制。

在struct zval 里,有ref_count,和is_ref两个字段。前者记录该变量指向的内存,被引用的次数。后者是bool值,标记该内存是否是被引用的。

在不考虑其他变量的情况下,如果$b = $a,虽然这时候是copy by value,但由于此时b和a的值确实是一样的,所以基于写时复制的思想,a和b的refcount都是2,且两者的is_ref都是false。随后,如果修改b,例如$b=20,就需要将b和a指向的内存分裂开了,这时a和b的refcount都会变成1,is_ref维持为false。(修改a的道理是一样的)

而如果初始时是by reference的,即$b = &$a,则两者的refcount都是2,且is_ref为true。无论怎样修改a和b的,两个字段都不发生变化。

那么数组与对象的赋值是怎样的呢?

从表面看,跟标量是一样的,assign-by-value时,共享同样的zval array/object内存区。但需要注意的是,它们还含有子zval结构!

先来看array,数组变量和其内部字段,都遵循写时复制!所以,当$b = $a,这种赋值方式时,a和b的表现如同两个独立的变量,虽然PHP会尽量复用内存空间。

再来看object,可以将object的assign-by-value赋值视为指针,但其is_ref=false。PHP中对象是按引用传递的,即对象进行赋值和操作的时候是按引用(reference)传递的,而不是整 个对象的拷贝。

混合使用assign-by-value和assign-by-ref

ref_count和is_ref的设计,都是为了尽可能节省内存,并且正确处理变量的赋值和改变(基本跟C语言的传值、传引用差不多)。那么考虑下,当把assign-by-value和assign-by-ref混合使用会怎样呢?答案是,没法省内存了。

$a = ‘sample';

$b = $a;

$c = &$a;

上面这段代码,line2的时候,还遵循写时复制,a和b共享一块内存。到line3时,假设还共享内存,那么其对应的ref_count和is_ref怎样设置呢?如果ref_count=3,且is_ref=false,那修改c的时候,将导致开辟新内存给c,而a和b共享旧内存,而a的值这时是应该随之改变的;如果is_ref=true,那么a、b、c还共享这块内存,而b的值应该是不变的!所以,PHP的做法是,在line3时,就分配了新内存,a与c共享内存,ref_count=2,is_ref=true;b的ref_count=1,is_ref=false。颠倒line2和line3的情况差不多。

所以,最好切记,除非清楚知道自己在做什么,否则别把assign-by-value和assign-by-ref混合使用

另外,如果进行字符串连接,例如 $a = ‘a'; $b = ‘b'; $c = $a . $b; 那么c没有复用a和b的存储空间,所以它们的refcount都=0。

is_ref的用途

从上面可以推测出,is_ref如果为true,当值发生改变的时候,不发生写时复制导致的分裂,所有指向该内存的“指针”对应的值都自然随之改变。

另外,还需要注意的是,仅当refcount>1时,is_ref才可以为true。

When the reference count of a variable container reaches 1 and the is_ref value is set to 1, the is_ref value is reset to 0. The reason for this is that a variable container can only be marked as a referenced variable container when there is more than one variable pointing to the variable container.

用户函数调用与引用计数

用户自定义的php函数调用时,会初始化两个符号表(symbol tables),一个是存储函数参数的function stack,另一个是存储函数生命周期的变量的function symbol table。假设有如下方法:function foo($a),并使用foo($init)那么$a的ref_count=3,且is_ref=false。3次引用分别是init、function stack的arg0、function symbol table的变量a。之所以init和a这时会共享同样的内存,还是由于没有发生写时复制。

但需要注意的是,如果init被显示声明为global变量,那么init与arg0和a不会共享同样的内存。因为global会在建立一个指向local变量的引用!

global $intval;

$intval = 10;

xdebug_debug_zval(‘intval’); // refcount = 1, is_ref = 1

function test_pass_int($i)

{

xdebug_debug_zval(‘i’);  // refcount = 2, is_ref = 0  被function stack和function symbol table中的变量使用,没有与intval共享zval内存区,因为intval是is_ref=true的!

 

$i = 20;

xdebug_debug_zval(‘i’); // refcount = 1, is_ref = 1 发生了写时复制,function stack和function symbol table中的变量分裂了

}

test_pass_int($intval);

 

如果做一个小小的改变,改为pass-by-ref,function test_pass_int(&$i),则function stack、function symbol和intval会共享zval了, test_pass_init里print的结果都是refcount = 3, is_ref = 1。

可以通过传引用调用(Passing References to Functions)或返回引用(Returning by Reference)来模拟C里的指针,从而使函数体内可以修改传入的值。在需要交互大数据array或对象时,也可以提高效率。

function &find_node($key, &$tree)

{

$item = &$tree[$key];

return $item;
}

$tree = array(…);

$node = &find_node(3, $tree);

$node = ‘new value';

 

类静态方法与成员方法

有时会看到 采用静态调用的形式,调用成员方法,例如:

class A

{

function foo() {}

public $var = 1;  // should change to : public static $var = 1;

}

A::foo();

var_dump(A::$var); // error

而这时居然可以调用foo成功 ,且PHP不会报error或warn(只在E_STRICT时有提示)。除了语言设计的疏漏和兼容性之外,也是因为静态方法和成员方法都是存储在struct _zend_class_entry 的HashTable function_table字段里的。但想以类似的方式访问成员变量var就不行了,因为静态变量存储在HashTable default_static_members 里,而成员变量存储在default_properties里。

内存管理

参考资料

http://nikic.github.io/2012/03/28/Understanding-PHPs-internal-array-implementation.html

http://derickrethans.nl/talks/phparch-php-variables-article.pdf

http://php.net/language.references

http://php.net/manual/en/internals2.php

 

 

相信长期浸泡在终端和代码的小伙伴们都有一套自己喜爱的配色方案。以前一直在用简单、适合阅读的 Terminal.app 配色方案换到 MacBook Pro with Retina display 后发现这个配色时间看长了眼睛有点累。不断有人推荐 Solarized,看了一些截图,感觉还不错,决定试一下。

Solarized 是目前最完整的 Terminal/Editor/IDE 配色项目,几乎覆盖所有主流操作系统(Mac OS X, Linux, Windows)、编辑器和 IDE(Vim, Emacs, Xcode, TextMate, NetBeans, Visual Studio 等),终端(iTerm2, Terminal.app, Putty 等)。类似的项目还有 Tomorrow Theme.

要在 Mac OS X 终端里舒服的使用命令行(至少)需要给3个工具配色,terminal、vim 和 ls. 首先下载 Solarized:

$ git clone git://github.com/altercation/solarized.git

Terminal/iTerm2

Mac OS X 自带的 Terminal 和免费的 iTerm2 都是很好用的工具,iTerm2 可以切分成多窗口,更方便一些。

如果你使用的是 Terminal 的话,在 solarized/osx-terminal.app-colors-solarized 下双击 Solarized Dark ansi.terminal 和 Solarized Light ansi.terminal 就会自动导入两种配色方案 Dark 和 Light 到 Terminal.app 里。

如果你使用的是 iTerm2 的话,到 solarized/iterm2-colors-solarized 下双击 Solarized Dark.itermcolors 和 Solarized Light.itermcolors 两个文件就可以把配置文件导入到 iTerm 里。

Vim

Vim 的配色最好和终端的配色保持一致,不然在 Terminal/iTerm2 里使用命令行 Vim 会很别扭:

$ cd solarized
$ cd vim-colors-solarized/colors
$ mkdir -p ~/.vim/colors
$ cp solarized.vim ~/.vim/colors/

$ vi ~/.vimrc
syntax enable
set background=dark
colorscheme solarized

iterm2 and solarized

 

ls

Mac OS X 是基于 FreeBSD 的,所以一些工具 ls, top 等都是 BSD 那一套,ls 不是 GNU ls,所以即使 Terminal/iTerm2 配置了颜色,但是在 Mac 上敲入 ls 命令也不会显示高亮,可以通过安装 coreutils 来解决(brew install coreutils),不过如果对 ls 颜色不挑剔的话有个简单办法就是在 .bash_profile 里输出 CLICOLOR=1:

$ vi ~/.bash_profile
export CLICOLOR=1

 

http://www.vpsee.com/2013/09/use-the-solarized-color-theme-on-mac-os-x-terminal/