文章归档

编写可读代码的艺术

可读性的基本定理:代码的写法应当使别人理解它所需的时间最小化

»» 继续阅读全文

memory allocator and coredump analysis

这几天彻底被一个线上BUG搞跨了,我们准备上线一个新的模块,灰度的时候发现系统极其不稳定,几乎每过几十分钟就core一次,而且coredump的地方比较有规律。现在来回顾一下这个core的分析过程

coredump现象

我们上线的流程是 稳定性测试 + 小流量灰度 + 全量上线 稳定性测试的机器环境是centos 6,线上自己的环境是 centos 5.4,稳定性测试的时候从线上拷贝流量,启动所有模块,持续跑了两天没有发现问题,然后接着小流量灰度。(后来的事实说明,环境也是导致coredump触发的一个很重要因素)

小流量灰度的时候问题出现了,时不时的core一下,core的地方比较稳定,每次都差不多都是那几个地方。既然core的比较稳定,说明应该是附近的代码出现了问题。

信息收集

从coredump的栈帧信息来看,现在能确定的是:

  • core在stl内部,说明不是明显的NULL引用等引发的coredump
  • 用户态极少使用malloc/free等来分配释放内存
  • coredump比较稳定,附近代码有问题的可能性比较大

静态代码分析

由于初步分析coredump处附近

  • 分析coredump代码附近有可疑的地方,例如数组越界,多线程race condition,不正确的malloc/free等等,没有发现问题。
  • 分析新上线的代码,例如有可能引入或者触发coredump的地方,没有发现问题。
  • 检查线程安全,race condition的地方,例如多线程读写map等

排查

初步分析没有发现明显的问题后,我们开始一步一步的排查和定位问题。通过配置线上可运行的模块,定位到其中某个模块可能有问题。由于静态代码分析的 时候看过这部分代码,很难定位到出问题的地方。加上在排查过程中即使卸载可疑的模块,仍然没法彻底解决问题,只是降低了coredump之间的时间间隔

coredump很可能不是在crash的地方附近,而是在之前别的地方就已经被破坏掉了。

Valgrind

为了证实这个问题,我们对所有模块都做了一次长时间的Valgrind测试,最终发现一个已经长时间运行的模块存在一个BUG,这个BUG非常难捕捉到,主要是由于一个Json的不正确使用导致的,走了一条很少有的执行路径,触发了此BUG

这个问题为什么之前没有爆发出来呢?double free这种错误会由于内存管理器的实现不用而表现出不稳定性,原因是假设你轻微地破坏了一块内存,而且被破坏的地方不影响别的内存块,那么如果这块内存 释放后并没有被重复利用,这个BUG就难以复现出来。所以我们看到coredump的地方未必是问题发现的地方。

谈软件质量保障

这真是一个非常广阔的,难以囊括清楚的话题。我所理解的软件质量保证主要包括研发流程管理,质量控制和软件维护。稍微列一下提纲:

  • 研发流程
    • 为什么选择git而不是svn
    • 如何构建企业级的基础系统
  • 质量控制
    • 对象设计:SOLID原则和GOF模式实践
    • 模块化编程:解耦、解耦、还是解耦
    • 模块的可测试性和单元测试
    • 系统量化,让trace bug变得简明
  • 软件维护
    • 文档:wiki/PPT/man pages/docs
    • 自动化部署
    • 监控系统

一、研发流程 1)为什么选择git而不是svn

对于研发流程的选择,我比较偏向于开源社区的风格,例如linux内核。而在分布式协作的工具支持方面,git比svn更易用。

  • 每个人都应该熟悉整个系统(全栈工程师?):不用了解每一个细节,但应该知道它
  • 每个人都是code reviewer
  • 严格的代码提交和合并流程:git做了这一切事情

2)如何构建企业级的基础系统

这里有一个很重要的原则就是:动静分离。所谓动,就是代码中易变的部分,静,就是不易变的部分。将不易变化的代码剖离出来,简化系统架构。ok,举个例子,假如我们要实现一个web service,代码架构通常是这样的:

  • common/
  • core/
  • service/

当然,这是我常用的做法,首先是要完备基础库,接着实现核心模块,而service负责将这些模块组织架构起来,最终编译成一个可执行文件。也许service里就一个main.cc源文件 如果能理解这种模式的好处,我们就可以推广到整个企业系统里面去。例如,这个基础系统看起来可能是这个样子的:

c语言可变参数类型

使用可变参数时,va_arg宏的第2个参数不能被指定为char、short或者float类型。因为在可变参数函数传递时,char和short会被提升为int类型,而float会被提升到double类型 。

例如,以下的代码是错误的

a = va_arg(ap, char);

因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:

a = va_arg(ap, int);

coredump记

造成程序coredump的成因很多:

  1. index类的索引造成的数组越界
  2. 多线程环境下使用了非线程安全的函数
  3. 多线程与释构
  4. 多线程环境下的数据互斥
  5. 空指针
  6. 对象delete后仍然被访问

为了避免这些情况,需要谨慎处理所有相关的资源:

  1. 对象必须全部初始化后才能使用
  2. 尽可能的不要缓存对象,使用统一的内存管理来避免内存碎片化的问题。除非你确定对象被重用时,相关的资源被正确的初始化。
  3. 对象该由谁创建,由谁来释放,delete需要全局感知,避免游离的对象指针
  4. 对于共享的资源,注意互斥和死锁
  5. 全局对象使用指针,统一new和delete
  6. 特别注意资源进入临界区的时机,例如初始化工作确认完毕。

数据+结构

数据是业务流密切相关的东西,容易发生变化,而结构通常都是大同小异。很多时候我们不得不为了各种各样的需求而设计复杂的数据结构。而且一旦它们之间呈现出复杂的依赖关系时,你会发现这就是一个噩梦:

  • 结构的逻辑变的相当复杂,而且难以分离
  • code review变得困难,让质量无法保证,代码难以维护

能不能让这个事情变的更简单一些呢?看看内核的红黑树和链表:

  • 链表
    • http://lxr.free-electrons.com/source/include/linux/list.h
  • 红黑数
    • http://lxr.free-electrons.com/source/include/linux/rbtree.h
    • http://lxr.free-electrons.com/source/lib/rbtree_test.c

一旦你习惯了这种设计模式,会发现它很有好处,而且可以轻松巧妙的解决很多问题。例如,在网络编程中,定时器是频繁被用到的一个组件,你会怎么设计它?

nginx slab管理

今天在看一个开源的内存管理,https://github.com/flygoast/flib/blob/master/src/slab.c#L296

有几个地方比较难理解,记录一下,代码在 void *slab_alloc_locked(slab_pool_t *pool, size_t size) 函数里

if (page) { if (shift < slab_exact_shift) { p = (page - pool->pages) << pagesize_shift; bitmap = (uintptr_t*)(pool->start + p); s = 1 << shift; n = (1 << (pagesize_shift - shift)) / 8 / s; if (n == 0) { n = 1; } bitmap[0] = (2 << n) - 1; map = (1 << (pagesize_shift - shift)) / (sizeof(uintptr_t) * 8); for (i = 1; i < map; ++i) { bitmap[i] = 0; } page->slab = shift; page->next = &slots[slot]; page->prev = (uintptr_t) &slots[slot] | SLAB_SMALL; slots[slot].next =

»» 继续阅读全文

Every Programmer Should Know

Latency Numbers http://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html, it will be less change in the future

What Every Programmer Should Know About Memory

  1. Part 1
  2. Part 2: CPU caches
  3. Part 3 (Virtual memory)
  4. Part 4 (NUMA systems)
  5. Part 5 (What programmers can do - cache optimization)
  6. Part 6 (What programmers can do - multi-threaded optimizations)
  7. Part 7 (Memory performance tools)
  8. Part 8 (Future technologies)
  9. »» 继续阅读全文

常用数学符号的LaTeX表示方法

原文:http://www.mohu.org/info/symbols/symbols.htm

1、指数和下标可以用^和_后加相应字符来实现。比如:

2、平方根(square root)的输入命令为:\sqrt,n 次方根相应地为: \sqrt[n]。方根符号的大小由LATEX自动加以调整。也可用\surd 仅给出 符号。比如:

3、命令\overline 和\underline 在表达式的上、下方画出水平线。比如:

4、命令\overbrace 和\underbrace 在表达式的上、下方给出一水平的大括号。

5、向量(Vectors)通常用上方有小箭头(arrow symbols)的变量表示。这可由\vec 得到。另两个命令\overrightarrow 和\overleftarrow在定义从A 到B 的向量时非常有用。

6、分数(fraction)使用\frac{...}{...} 排版。一般来说,1/2 这种形式更受欢迎,因为对于少量的分式,它看起来更好些。

7、积分运算符(integral operator)用\int 来生成。求和运算符(sum operator)由\sum 生成。乘积运算符(product operator)由\prod 生成。上限和下限用^ 和_来生成,类似于上标和下标。

以下提供一些常用符号的表示方法

»» 继续阅读全文

Profiling Go Program

- http://blog.golang.org/2011/06/profiling-go-programs.html

At Scala Days 2011 a few weeks ago, Robert Hundt presented a paper titled “Loop Recognition in C++/Java/Go/Scala.” The paper implemented a specific loop finding algorithm, such as you might use in a flow analysis pass of a compiler, in C++, Go, Java, Scala, and then used those programs to draw conclusions about typical performance concerns in these languages. The Go program presented in that paper runs quite slowly, making it an excellent opportunity to demonstrate how to use Go's profiling tools to take a slow program and make it faster.

By using Go's profiling

»» 继续阅读全文

第 1 页,共 4 页1234