Apache服务器可是使用prefork技术,启动多个独立的进程,每个进程独立的处理http请求,不需要担心线程安全的问题。
This Multi-Processing Module (MPM) implements a non-threaded, pre-forking web server that handles requests in a manner similar to Apache 1.3. It is appropriate for sites that need to avoid threading for compatibility with non-thread-safe libraries. It is also the best MPM for isolating each request, so that a problem with a single request will not affect any other.
尽管prefork在处理高并发的情况下并不高效,但是作为一个技术,倒是有启发我们的地方。我最近在调研Go服务器的性能看到一段代码,很优雅的实现了prefork和affinity的的功能,特地抄写在本文中,看看他是怎么实现的。
代码出处: WebFrameworkBenchmark go-fasthttp 。
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 package mainimport ( "flag" "fmt" "io" "log" "net" "os" "os/exec" "runtime" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/reuseport" ) var ( addr = flag.String("addr" , ":8080" , "TCP address to listen to" ) prefork = flag.Bool("prefork" , false , "use prefork" ) affinity = flag.Bool("affinity" , false , "use affinity for prefork" ) child = flag.Bool("child" , false , "is child proc" ) ) func main () { flag.Parse() ln := getListener() if err := fasthttp.Serve(ln, requestHandler); err != nil { log.Fatalf("Error in ListenAndServe: %s" , err) } } func requestHandler (ctx *fasthttp.RequestCtx) { io.WriteString(ctx, "Hello World" ) } func getListener () net.Listener { if !*prefork { ln, err := net.Listen("tcp4" , *addr) if err != nil { log.Fatal(err) } return ln } if !*child { children := make ([]*exec.Cmd, runtime.NumCPU()) for i := range children { if !*affinity { children[i] = exec.Command(os.Args[0 ], "-prefork" , "-child" ) } else { children[i] = exec.Command("taskset" , "-c" , fmt.Sprintf("%d" , i), os.Args[0 ], "-prefork" , "-child" ) } children[i].Stdout = os.Stdout children[i].Stderr = os.Stderr if err := children[i].Start(); err != nil { log.Fatal(err) } } for _, ch := range children { if err := ch.Wait(); err != nil { log.Print(err) } } os.Exit(0 ) panic ("unreachable" ) } runtime.GOMAXPROCS(1 ) ln, err := reuseport.Listen("tcp4" , *addr) if err != nil { log.Fatal(err) } return ln }
这个程序使用fast-http简单的实现了一个web服务器,简单的返回一个hello world。
如果程序启动的时候加上了-prefork参数,它会使用exec.Command启动多个子进程,子进程的数量和CPU的核数相同(第51行)。
如果程序启动的时候加上了-prefork参数和"-affinity"参数,它会将子进程绑定在其中的一个CPU核上,这样这个子进程只会被这个CPU执行。
子进程限定了使用的原生线程为1: runtime.GOMAXPROCS(1)。
因为程序使用了reuseport,所以不会导致多个IP地址和端口被占用的情况,多个子进程可以共用相同的IP地址+端口监听。
需要注意的事,reuseport并不是所有的操作系统都支持,比如目前windows就不支持,所以只可能在高版本的Linux中使用。