后端

Java响应式编程

Java响应式层级 #

Level 0: Thread & Runnable (Java 1+)
Level 1: ExecutorService, Callable, Future (Java 5+)
Level 2: ForkJoinPool (Java 7+)
Level 3: CompletableFuture (Java 8+)
Level 4: reactive streams, Flow (Java 9+)
Level 5: HTTP/2 client (Java 11+)
Level 6: Reactive libraries (RxJava, Reactor)
Level 7: Reactive services (Vert.x, Spring, Kafka)

Flow #

Flow.Publisher
Flow.Subscriber
Flow.Subscription
	# link publisher和subscriber
Flow.Processor
	# subscriber和publisher的act

ReactiveX #

Flux
Mono

RxJava #

模型	
	Observable
	Subscriber

Reactor #

Vert.x #

AKKA #

Nginx

基础 #

结构
    一个主进程(root权限运行)和多个工作进程(普通权限运行)
优点
    异步非阻塞
    非常稳定
    反向代理
        后端服务io能力不高,nginx buffer http请求直到完整,再发送到后端。同样buffer响应
    相对apache
        轻量
        apache阻塞
        占资源低
        模块化设计
        社区活跃, bug少
多进程模型
    使用epoll
    多worker处理,业务阻塞时切换调度, 结束阻塞时分配
模块
    handler
    filter
    upstream
    load-balance
功能
    http
       可以保持session, 相同的ip分配到同一个服务器上
       缓存静态页面到内存,建立索引与自动索引
       反向代理
       负载均衡
       模块化
           过滤器
               gzipping, byte ranges, chunked responses, SSI-filter
       支持SSL与TLS SNI
    imap/pop3代理
命令
    nginx -c /etc/nginx/nginx.conf
    nginx -s quit
    nginx -s stop
    nginx -s reload
            # 重载设置
            ## service nginx reload
    nginx -v
            # 查看版本
            ## -V
    nginx -t [-c nginx.conf]
            # 检查配置文件是否正确
    nginx -h
            # 查看帮助
            ## -?

    pkill -9 nginx
    kill -HUP `nginx.pid`
            # 平滑重启。尝试解析配置文件,成功时应用新配置(否则继续使用旧配置),运行新的工作进程并从容关闭旧工作进程
            ## 继续为当前连接客户提供服务
            # 支持 QUIT TERM INT USR1(重新打开日志文件,切割日志时用) USR2(平滑升级可执行程序) WINCH(从容关闭工作进程)

配置 #

http://nginx.org/en/docs/dirindex.html
域
        main http server location

worker_rlimit_nofile 51200;
        # worker最大打开文件数的限制, 不设时为系统限制
pid        /var/run/nginx.pid;
        # nginx.pid文件中存储当前nginx主进程的pid
例子
    user www-data;
    worker_processes 4;
    pid /run/nginx.pid;

    events {
        worker_connections 768;
        # multi_accept on;
    }

    http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;

        server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##
        gzip on;
        gzip_disable "msie6";

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # nginx-naxsi config
        ##
        # Uncomment it if you installed nginx-naxsi
        ##

        #include /etc/nginx/naxsi_core.rules;

        ##
        # nginx-passenger config
        ##
        # Uncomment it if you installed nginx-passenger
        ##

        #passenger_root /usr;
        #passenger_ruby /usr/bin/ruby;

        ##
        # Virtual Host Configs
        ##
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
    }

    #mail {
    #      # See sample authentication script at:
    #      # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
    #
    #      # auth_http localhost/auth.php;
    #      # pop3_capabilities "TOP" "USER";
    #      # imap_capabilities "IMAP4rev1" "UIDPLUS";
    #
    #      server {
    #              listen    localhost:110;
    #              protocol  pop3;
    #              proxy      on;
    #      }
    #
    #      server {
    #              listen    localhost:143;
    #              protocol  imap;
    #              proxy      on;
    #      }
    #}


    o-> app.zlycare.com
    server {
        listen 80;
        listen [::]:80;

        server_name app-test.zlycare.com www.app-test.zlycare.com;

        # access log file
        access_log /home/zlycare/data/app-zlycare-com.log;

        location / {
                gzip on;
                default_type text/plain;
                charset utf-8;
                root /home/zlycare/app/zlydoc-cloud/public;
                index index.html;
        }
    }

    o-> web.zlycare.com
    server {
        listen 80;
        listen [::]:80;

        server_name web-test.zlycare.com www.web-test.zlycare.com;

        # access log file
        access_log /home/zlycare/data/web.zlycare.log;

        location / {
            proxy_pass http://127.0.0.1:8082;
            #proxy_redirect off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

    o-> sdk.com
    server {
        listen 80;

        server_name 10.162.201.58;

        # access log file
        access_log /home/zlycare/data/app-zlycare-com.log;

        location / {
            gzip on;
            default_type text/plain;
            charset utf-8;
            root /opt/sdk/nginx;
            index index.html;
        }
    }

代理 #

nginx
    server{
        resolver x.x.x.x;
        listen 82;
        location / {
            proxy_pass http://$http_host$request_uri;
        }
    }

    不能有hostname, 必须有resolver, 即DNS服务器ip
    $http_host和$request_uri是nginx系统变量
用户机器
    export http_proxy=http://nginx-ip:82

反向代理 #

upstream backend {
    hash $consistent_key consistent
    server 192.168.61.1:9080 weight=1
    server 192.168.61.1:9090 weight=2
}
location / {
    proxy_pass http://backend
    set $consistent_key $arg_cat;                       # 从cat参数取值
    if ($consistent_key = "") {
        set $consistent_key $request_uri;
    }
}

上游服务器, 权重越高分配越多
请求/时,代理到backend配置的上游服务器
负载均衡算法
    round-robin(轮询)
    ip-hash
        ip_hash
    hash key/hash key consistent        # hash和一致性hash
        hash $uri
    least_conn                          # 最小连接数服务器
    least_time                          # 最小平均响应时间, 商业版

php #

conf/nginx.conf
    server{
        location / {
            proxy_pass http://127.0;
            proxy_redirect default;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $http_connection;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
        }
    }

default.conf
    server {
        listen      80;
        server_name  epinkr.com www.epinkr.com;
        #server_name  localhost;
        if ( $host != 'www.epinkr.com' )
        {
            rewrite ^/(.*)$ http://www.epinkr.com/$1 permanent;
        }
        #root    /home/qipin/deploy;
        index  index.php index.html index.htm;

        #charset koi8-r;
        #access_log  /var/log/nginx/log/host.access.log  main;

        location = / {
            root  /home/qipin/deploy;
        #    index  index.html index.htm;
            fastcgi_pass  127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $document_root/index.php;
            include        fastcgi_params;
        }

        location / {
            root  /home/qipin/deploy;
            index  index.html index.htm;
        }

        location /photo {
            root  /home/qipin/data;
        #    return 402;
        #    rewrite ^\/yuepin\/(.*) /$1 last;
        }

        location ~ ^\/(\w+)\/css\/ {
            root  /home/qipin/deploy;
            rewrite ^\/(\w+)\/css\/(.*)  /css/$2 last;
        }

        location ~ ^\/(\w+)\/img\/ {
            root  /home/qipin/deploy;
            rewrite ^\/(\w+)\/img\/(.*)  /img/$2 last;
        }

        location ~ ^\/(\w+)\/js\/ {
            root  /home/qipin/deploy;
            rewrite ^\/(\w+)\/js\/(.*)  /js/$2 last;
        }

        location ~ ^\/user\/(\w+)$ {
            root  /home/qipin/deploy;
        #    return 402;
            rewrite ^\/user\/(\w+)  /php/user/user_$1.php last;
        }

        location ~ ^\/company\/(\w+)$ {
            root  /home/qipin/deploy;
        #    return 402;
            rewrite ^\/company\/(\w+)  /php/company/company_$1.php last;
        }

        location ~ ^\/vendor\/(\w+)$ {
            root  /home/qipin/deploy;
        #    return 402;
            rewrite ^\/vendor\/(\w+)  /php/vendor/vendor_$1.php last;
        }

        location ~ ^\/person\/(\w+)$ {
            root  /home/qipin/deploy;
        #    return 402;
            rewrite ^\/person\/(\w+)  /php/person/person_$1.php last;
        }

        location ~ ^\/get\/(\w+)$ {
            root  /home/qipin/deploy;
        #    return 402;
            rewrite ^\/get\/(\w+)  /php/yp_$1.php last;
        }

        location ~ ^\/(\w+)$ {
            root  /home/qipin/deploy;
        #  return 402;
            rewrite ^\/(\w+)$  /php/$1.php last;
        }

        location ~ ^\/php\/(\w*\.php)$ {
            root  /home/qipin/deploy;
        #    return 403;
            fastcgi_pass  127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $document_root/php/$1;
            include        fastcgi_params;
        }

        location ~ ^\/php\/(\w+)\/(\w*\.php)$ {
            root  /home/qipin/deploy;
        #    return 403;
        #    try_files $uri =404;
            fastcgi_pass  127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param SCRIPT_FILENAME $document_root/php/$1/$2;
            include        fastcgi_params;
        }

        #location /qipin/ {
        #    root  /home/qipin/deploy;
        #    return 402;
        #    index  index.html;
        #    rewrite ^\/qipin\/(.*) /$1 last;
        #}

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page  500 502 503 504  /50x.html;
        location = /50x.html {
            root  /usr/share/nginx/html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass  http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root          html;
        #    fastcgi_pass  127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }

ssl.conf
    #HTTPS server
    #
    #server {
    #    listen      443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      /etc/nginx/cert.pem;
    #    ssl_certificate_key  /etc/nginx/cert.key;

    #    ssl_session_cache shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root  /usr/share/nginx/html;
    #        index  index.html index.htm;
    #    }
    #}

插件 #

HttpLimitReqModul
    介绍
        限制单个ip一段时间的连接数
    http{
        limit_req_zone $binary_remote_addr zone=allips:10m rate=20r/s;
        server {
            location {
                limit_req zone=allips burst=5 nodelay;
            }
        }
    }
HttpLimitConnModul
    介绍
        限制单个ip的并发连接数
HttpLimitZoneModul
    介绍
        限制ip连接内存大小

    http {
        limit_conn_zone $binary_remote_addr zone=namea:10m;
            # $binary_remote_addr 同一客户端ip地址
            # 1.1.18前是limit_zone
        limit_conn_zone $server_name zone=nameb:10m;
            # $server_name 同一server的名字
        server {
            location {
                limit_conn  namea 20;
                limit_conn nameb 20;
                    # 并发连接数
                limit_rate 100k;
                    # 下载速度
            }
        }
    }

Nodejs

基础 #

特点
    commonJS规范
    javascript书写(v8引擎)
        js设计之初就可以运行在后端
        v8
            成熟的事件驱动模式
            没有i/o库, 没有历史包袱
            v8性能好
    单线程
        不用在意多线程状态同步(没有死锁, 没有上下文切换)
        无法利用多核, 错误时应用退出,计算密集时无法调度   # child_process解决
    事件驱动(event-driven), 回调
        event loop
            [while(true)] -> watcher -> handles
            watcher产生事件后, event loop取到并执行其handle(回调函数)
            event loop每一周询问多个watcher是否有事件
            event loop中没有watcher时进程退出
        http模块就是启动了一个watcher,所以执行后进程不结束
            其它watcher有 timer, fs, udp/req, process
        不同操作系统中event driven的实现:
            windows: IOCP
            Linux: epoll
            Mac:kqueue
    非阻塞io(non-blocking i/o model)
        io与数据处理分离(所以必须异步)
        线程池结合event-driven实现
    异步io
        go语言有协程(coroutine)而node.js没有,协程可以同步式编程
            # 有第三方协程模块
        promise(commonJs的规范, 其实现有whenJs, Q)
            # 书写难度降低
        eventProxy      # 朴灵
        async/step
commonJS
    模块
        var math = require('math')  # 缓存优先,核心模块优先。依次找.js, .node, .json
        exports.add = function(){}
    二进制
    Buffer
    字符集编码
    I/O流
    进程环境
    文件
    套接字
    单元测试
    web网关
    包管理
        package.json
        bin
        lib
        doc
        test
实现技术
    libev的windows与linux接口实现
    c++扩展
事件循环    # 生产者消费者模型
    执行一次称为Tick
    询问观察者(文件、网络等)是否有待处理事件, 有关联回调执行回调
        # 观察者先idle, 再io, 再check
        通过请求对象实现,绑定回调、运行参数、执行标志
层次
    javascript
    v8
    node
    libuv
    *nix/ windows                # 分别编译
应用
    I/O密集服务
    cpu密集用c/c++扩展,用子进程

工具 #

node --v8-options | grep harmony
    # 查看支持的es6特性
npm
    介绍
        cnpm是一个alibaba开发维护的,提供私有npm注册服务
    -v      # 版本
    install     # 安装,会执行package.json中scripts的勾子命令
        -g
    uninstall
    config
        list    # 查看项目的默认设置。registry属性指向npm官方资源位置
        set registry http://192.168.1.20:7001
            # 设置源
    test        # package.json中scripts的test

    o-> 搭建cnpm服务器
        git clone https://github.com/cnpm/cnpmjs.org.git
        cd cnpmjs.org
        npm install npm -g
            # 升级npm的版本
        npm install
        创建mysql数据库,并在config/index.js中修改mysql数据库的用户名和密码
        config/index.js中注释bindingHost来对外网开放
        node --harmony_generators dispatch.js
            # 启动了两个端口, 7001用于npm注册服务, 7002用于web访问
    o-> 使用私有库
        npm install ape-algorithm --registry=http://192.168.1.20:7001
            # 如果私有库中没有,cnpm会到npm中同步一个到cnpm, 再传给客户端一份

    设置
         ~/.npmrc
            registry=http://192.168.1.20:7001
                # 淘宝翻墙库 https://registry.npm.taobao.org/
n
node-gyp    # 编译c/c++模块
nvm
cnpm
    介绍
        cnpm是一个alibaba开发维护的,提供私有npm注册服务
    安装
        npm install cnpm
            # 可以像使用npm一样使用
        cnpm sync gulp
            # npm 中发布的包在cnpm中有延时,可以用这个命令来手动同步
    搭建cnpm服务器
        git clone https://github.com/cnpm/cnpmjs.org.git
        cd cnpmjs.org
        npm install npm -g
            # 升级npm的版本
        npm install
        创建mysql数据库,并在config/index.js中修改mysql数据库的用户名和密码
        config/index.js中注释bindingHost来对外网开放
        node --harmony_generators dispatch.js
            # 启动了两个端口, 7001用于npm注册服务, 7002用于web访问
    使用私有库
        npm install ape-algorithm --registry=http://192.168.1.20:7001
            # 如果私有库中没有,cnpm会到npm中同步一个到cnpm, 再传给客户端一份
    项目设置私有库
        npm config list
            # 查看项目的默认设置。registry属性指向npm官方资源位置
        npm config set registry http://192.168.1.20:7001
    用户设置私有库
        // ~/.npmrc
        registry=http://192.168.1.20:7001
            # 另外,淘宝翻墙库 https://registry.npm.taobao.org/
调试
    o-> 代码中插入断点
        debugger;
    o-> 以debug模式运行
        # debug模式运行时, help查看可用命令
        node debug app.js

配置 #

package.json
    name                # 包名
    description         # 简介
    version             # 版本
    keywords            # 搜索关键词
    maintainers         # 维护者
    contributors        # 代码贡献者
    bugs                # 反馈bug的地址
    licenses            # 许可证
    repositories        # 托管代码地址
    dependencies        # 依赖包
    homepage            # 该包网站
    os                  # 操作系统支持列表
    cpu                 # cpu架构支持列表
    engine              # 支持的js引擎, 如ejs
    builtin             # 内建在底层系统的哪些组件上
    directories         # 目录说明
    implements          # 实现了commonJS哪些规范
    scripts             # 脚本命令
        preinstall
        install
        uninstall
        test
    author              # 包作者
    bin                 # 全局安装时,命令安装的位置
    main                # require()包时入口,默认找index
    devDependencies     # 开发时需要的依赖

    o->
    {
        "name": "test",
        "version": "0.1.0",
        "keywords": ["a", "b"],                     # npm search时用
        "description": "A testing package",
        "os": ["linux", "darwin"],
        "author": "outrun<outrun@mail.com>",
        "dependencies": {
            "express": "^1.0.0",
            "redis": ">= 0.6.7"
        },
        "devDependencies": {
            "grunt": "^0.4.5"
        },
        "main": "index",
        "bin": {
            "test": "./bin/test.js"
        },
        "scripts": {
            "start": "node server.js",
            "test": "vows test/*.js",               # "grunt test" "mocha test" "make test" "make test-all"
            "preinstall": "./configure",
            "install": "make && make install",
            "uninstall": ""
        },
        "engines": {
            "node": "5.0.0"
        }
    }

api #

异步api #

I/O操作api
setTimeout() setInterval()
    定时器插入观察者的红黑树中, tick时迭代定时器,如果超时就形成事件
        # 如果前一个tick耗时大,定时会拖后
        # 比较浪费性能
process.nextTick()
    回调放入队列,下tick全部执行, 位于idle观察者
setImmediate()
    回调放入链表, 每tick执行一个,位于check观察者,晚于idle

宿主对象 #

global      #全局对象
    root    # 指向自己
    BLOBAL # 指向自己

    setTimeout()
    setInterval()
    clearTimeout()
    clearInterval()
process     #当前进程
    argv    # 获得命令行参数数组
        title       # node
        version     # v0.12.2
    事件
        process.on("uncaughtException",function(e){
            console.log("error:"+e);
        });
buffer
    特点
        node中buffer不属于v8, 使用c++扩展编写。所以可以使用高于1.4g的内存
        一个元素一字节
        8kb之内为小对象,slab机制分配内存, 先申请后分配。大于8kb的创建SlowBuffer对象
        pool的实现方式
    字符编码类型
        # 默认编码UTF-8, 一个buffer只能有一个编码
        ascii
        utf-8
        utf-16le/ucs-2
        base64
        binary
        hex

    length

    write()
        # write(str, [offset], [length], [encoding])
    toString()
        # toString([encoding], [start], [end])
    isEncoding()
        # 指定编码是否支持转换
    copy()
        # 复制自身到另一个buffer的某位置
        buf.copy(buffer, 0)
    concat()  #静态方法
        concat(chunks, size)
            # chunks中为buffer数组, size为总大小
    使用
        new Buffer(100)
        new Buffer('a', 'UTF-8')
console     #控制台
    log('',obj)
        console.log('[%s] listening on http://127.0.0.1:%d', app.setting.env, port)
module
    Module.exports真正的接口,导出的是一个类型
    exports是Module.exports的包装,导出的是Object类型的对象
promise     # 0.11.x后加入的全局对象
    使用
        # 复制
        var jadeTemplate = new Promise(function(resolve, reject) {
        fs.readFile(path.join(__dirname, 'views/article.jade'), function(err, data) {
            if (err) {
            reject(err.message);
            } else {
            resolve(data.toString());
            }
        });
        });

        var localData = new Promise(function(resolve, reject) {
        fs.readFile(path.join(__dirname, 'static/shuffle.json'), function(err, data) {
            if (err) {
            reject(err.message);
            } else {
            resolve(JSON.parse(data.toString()));
            }
        });
        });

        Promise
        .all([jadeTemplate, localData])
        .then(function(value) {
            console.log(jade.compile(value[0])(value[1]));
        });

内置lib #

http #

介绍
    继承自net模块
    EventEmitter实例
事件
    服务端
        connection
        request
        close
        checkContinue
            # 发送较大数据时,先发送Expect: 100-continue请求头,此时触发
        connect
        upgrade
            # 要求升级连接协议时
        clientError
            # 客户端触发error事件时触发
    客户端
        response
        socket
            # 建立连接时触发
        connect
            # 响应200时触发
        upgrade
        continue

globalAgent     # 重用http长连接,实际是个连接池,默认有5个并发
    sockets
        # 连接数
    requests
        # 处于等待状态的请求数

createServer(onRequest).listen(8888, func);                # 创建服务器并启动
    request
        req.setEncoding("utf8")
        var postData = "";
        req.addListener("data", function(postDataChunk){
            postData += postDataChunk;
        });
        req.addListener("end", function(){
            route(handle, pathname, res, postData);
        });                                # 拼接post请求数据
        req.rawBody
            # post来的原生数据
        req.destroy()
            # 放弃请求,停止招收

    response
        res.setHeader('WWW-Authenticate', 'Basic realm=\"Tomcat Manager Application\"')
        res.writeHead(200, {"Content-Type": "text/html"});
            # 调用setHeader多次,调用writeHead后才写入
        res.write("");
        res.write(file,?"binary");?
        res.end();
request(options, fn)        # 发起客户端请求
    # options中有 host, hostname, port, localAddress(使用本地的哪个网卡), socketPath(本地套接字文件路径), method, path, headers, auth(被计算成请求头的Authorization部分), agent(并发连接数,默认5)

https #

介绍
    nodejs
    申请ca证书
    访问端口为443
使用
    express -e nodejs-https
    cd nodejs-https && npm install
    git --version
    openssl version -a
    openssl genrsa -out privatekey.pem 1024
        # 生成证书文件
    openssl req -new -key privatekey.pem -out certrequest.csr
        # 通过私钥生成CSR证书签名
    openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
        # 通过私钥和证书签名生成证书文件
        ## 这时生成了三个文件: certificate.pem, certrequest.csr, privatekey.pem
        ### 分别是: 证书文件, CSR证书签名, 私钥
        ## 由于证书是自己创建的,没有经过第三方机构验证,用户访问时会出现警告提示
    服务器
        var https = require('https')
            , fs = require('fs');
        var options = {
            key: fs.readFileSync('./privatekey.pem'),
            cert: fs.readFileSync('./certificate.pem')
        };
        https.createServer(options, app).listen(3011, function(){
            console.log('Https server listening on port: ' + 3011);
        });
    客户端
        var options = {
            hostname: 'localhost',
            port: 8000,
            path: '/',
            method: 'GET',
            key: fs.readFileSync('./keys/client.key'),
            cert: fs.readFileSync('./keys/client.crt'),
            ca: [fs.readFileSync('./keys/ca.crt')]
                # 设置rejectUnauthorized: false 来忽略ca验证
        }
        options.agent = new https.Agent(options)

        var req = https.request(options, function (res) {
            res.setEncoding('utf-8')
            res.on('data', function (d) {
                console.log(d)
            })
        })
        req.end()

        req.on('error', function(e){
            console.log(e)
        })

net #

介绍
    处理tcp请求
    socket是EventEmitter的Stream实例
注意
    默认开启Nagle, 会合并小数据成一个数据包延迟发送
        socket.setNoDelay(true)关闭Nagle
    并不是每次write都触发data事件, 关掉Nagle后,可能接收到多个小数据包后触发一次data

服务器事件
    listening
    connection
    close
    error
连接事件
    data
    end
    connect
    drain
        # 任意一端调用write时触发
    error
    close
    timeout

o-> 基本服务
# telnet来测试
var net = require('net')

var server = net.createServer(function (socket) {
    socket.on('data', function (data) {
        socket.write('a')
    })

    socket.on('end', function () {
        console.log('disconnected.')
    })

    socket.write('welcome')
})

server.listen(8124, function () {
    console.log('server bound')
})

o-> 基本服务2
var server = net.createServer()
server.on('connection', function (socket) {})
server.listen(8124)

o-> 监听
server.listen('/tmp/echo.sock')
    # nc -U /tmp/echo.sock 来测试

o-> 客户端
var client = net.connect({port: 8124}, function () {
    console.log('client connected')
    client.write('a')
})

client.on('data', function (data) {
    console.log(data.toString())
    client.end()
})

client.on('end', function() {
    console.log('disconnected.')
})

o-> 客户端
var client = net.connect({path: '/tmp/echo.sock'})

o-> 管道
var server = net.createServer(function (socket) {
    socket.write('a')
    socket.pipe(socket)
})

dgram #

介绍
    处理udp
    socket是EventEmitter实例

o-> 服务
var dgram = require('dgram')

var server = dgram.createSocket('udp4')

server.on('message', function (msg, rinfo) {
    console.log(msg + 'from' + rinfo.address + ':' +)
})

server.on('listening', function () {
    var address = server.address()
    console.log('listening ' + address.address + ':' + address.port)
})

server.bind(41234)

o-> 客户端
var message = new Buffer('a')
var client = dgram.createSocket('udp4')
client.send(message, 0, message.length, 41234, 'localhost', function (err, bytes) {
    client.close()
})

events #

介绍
    几乎所有对象的父类
使用
    var events = require('events')
        , util = require('util');
    function Obj(){events.EventEmitter.cal(this);}
    util.inherits(Obj, events.EventEmitter);
        # Obj.prototype.__proto__ = events.EventEmitter.prototype;
    Obj.prototype.write = function (data) {this.emit('data', data);};

    var obj = new Obj();
    obj.on('data', function (data) {console.log('Received data', data);})
        # obj.once
    obj.write('hello');

setMaxListeners(0)
    # 侦听器过多不警告

path #

os #

方法
    totalmem
    freemem

fs #

fs.readFile("tmp/test.png", "binary", function(error, file){
});
fs.writeFile('target.png', 'binary', function(err){
})
fs.exists(filePath, function(exists){
        if(exists){}
})
fs.unlink(filePath, function(err){
})
fs.renameSync(files.upload.path,?"/tmp/test.png");                # 写入文件(阻塞)

o-> 流读写
var reader = fs.createReadStream('in.txt')
        # 第二个参数为设置, highWaterMark: 每次读取的size, encoding: 编码
var writer = fs.createWriteStream('out.txt')
reader.on('data', function (chunk) {
        writer.write(chunk)
})
reader.on('end', function() {
        writer.end()
})

var reader = fs.createReadStream('in.txt')
var writer = fs.createWriteStream('out.txt')
reader.pipe(writer)

sys #

process #

argv
    # 启动时参数
pid
    # 当前进程的pid

once()
    once('SIGINT', function () {})
        # ctrl + c
memoryUsage()
    # 查看v8内存使用量
    # 其中rrs是resident set size, 是常驻内存的部分,其他在swap或文件系统中
kill()
    # process.kill(pid[, signal])
on()
    # 事件触发

o->
process.on('SIGTERM', function () {
    console.log('Got a SIGTERM, exiting...')
    process.exit(1)
})

o->
process.on('uncaughtException', function () {
    logger.error(err)
    process.send({act: 'suicide'})
        # 向主进程发送信号
    worker.close(function () {
        process.exit(1)
    })

    setTimeout(function () {
        # 长连接断开需要时间较久, 超时自动退出
        process.exit(1)
    }, 5000)
})

module #

exports
parent

stream #

介绍
    继承EventEmitter, 处理文件之类的流

tls #

介绍
    建立在tls/ssl上的加密tcp
    使用openssl来构建证书和测试

o-> 服务器
var tls = require('tls')
var fs = require('fs')

var options = {
        key: fs.readFileSync('./keys/server.key'),
        cert: fs.readFileSync('./keys/server.crt'),
        requestCert: true,
        ca: [fs.readFileSync('./keys/ca.crt')]
}

var server = tls.createServer(options, function (stream) {
        console.log('server connected', stream.authorized ? 'authorized' : 'unauthorized')
        stream.write('welcome!\n')
        stream.setEncoding('utf8')
        stream.pipe(stream)
})
server.listen(8000, function () {
        console.log('server bound')
})

o-> 客户端
var options = {
        key: fs.readFileSync('./keys/client.key'),
        cert: fs.readFileSync('./keys/client.crt'),
        ca: [fs.readFileSync('./keys/ca.crt')]
}

var stream = tls.connect(8000, options, function () {
        console.log('client connected', stream.authorized ? 'authorized' : 'unauthorized')
                # 证书是否通过
        process.stdin.pipe(stream)
})

stream.setEncoding('utf8')
stream.on('data', function (data) {
        console.log(data)
})
stream.on('end', function () {
        server.close()
})

child_process #

介绍
    可以创建新的node进程
spawn(command[, args][a options])
    # command执行的命令
    # args参数列表
    options 环境变量对象, 包括7个属性
        cwd 子进程当前工作目录
        env 环境变量键值对
        stdio 子进程stdio配置
        customFds 子进程stdio使用的文件标示符
        detached 进程组的主控制
        uid 用户进程id
        进程组id

    var du = child.spawn('du', ['-sh', '/disk1']);
    du.stdout.on('data', function(data){})
    du.stderr.on('data', function(data){})
    du.on('exit', function(code){})
exec('')
    # 对spawn的友好封装, 增加了shell命令解析
    child.exec('cat *.js | ws', function(error, stdout, stderr){})
execFile(command[, args])
    # 执行可执行文件,不解析args,防止了exec参数注入的风险
    child.execFile('/bin/ls', ['-l', '.'], function(err, result){})
fork()
    # 同spawn, 但建立ipc(进程通信, inter-process communication)
    var n = child.fork('./son.js');
    n.on('message', function(){
        console.log('Main listen: ', m);
    });
    n.send({hello: 'i am parent'});
    // son.js
    process.on('message', function(m){
        console.log('Son listen: ', m);
    });
    process.send({hello: 'i am child'});
子进程对象
    send()
        # 发送消息和句柄,句柄可以是
        ## net.Socket, net.Server, net.Native(c++层面的tcp套接字或IPC管道), dgram.Socket, dgram.Native
    kill()
        # 向子进程发送SIGTERM信号
    事件
        message
        error
        exit
        close
        disconnect

o-> cpu核数worker
    o-> master.js
    var fork = require('child_process').fork
    var cpus = require('os').cpus()
    for (var i = 0; i < cpus.length; i++){
        fork('./worker.js')
    }

    o-> worker.js
    var http = require('http')
    http.createServer(function(req, res){...}).listen(Math.round((1+Math.random()) * 1000), '127.0.0.1')

o-> spawn
var spawn = require('child_process').spawn
free = spawn('free', ['-m'])
free.stdout.on('data', function (data) {})
free.stderr.on('data', function (data) {})
free.on('exit', function (code, signal) {})

o-> fork
    # 需要至少30ms, 10M启动一个v8实例
var fork = require('child_process').fork
var cpus = require('os').cpus()
for (var i = 0; i < cpus.length; i++) {
        fork('./worker.js')
}

o-> 通信
    # 只有子进程是node进程时才可以通信
var cp = require('child_process')
var n = cp.fork(__dirname + '/sub.js')

n.on('message', function (m) {
    console.log('PARENT got message: ', m)
})
n.send({a: 1})

process.on('message', function (m) {
    console.log('CHILD got message:', m)
})
process.send({b: 2})

o-> 句柄通信
    # 节省了代理建立socket浪费的文件描述符
var child = require('child_process').fork('child.js')
var server = require('net').createServer()
server.on('connection', function (socket) {
    socket.end('handled by parent \n')
})
server.listen(1337, function () {
    child.send('server', server)
})
// child.js
process.on('message', function (m, server) {
    if (m === 'server') {
        server.on('connection', function (socket) {
            socket.end('handled by child \n')
        })
    }
})

o-> 句柄负载http
    # 对描述符是抢占式的
var cp = require('child_process')
var child1 = cp.fork('child.js')
var child2 = cp.fork('child.js')

var server = require('net').createServer()
server.listen(1337, function () {
    child1.send('server', server)
    child2.send('server', server)
    server.close()
})
// child.js
var http = require('http')
var server = http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'})
    res.end('handled by child, pid is ' + process.pid + '\n')
})
process.on('message', function (m, tcp) {
    if (m === 'server') {
        tcp.on('connection', function (socket) {
            server.emit('connection', socket)
        })
    }
})

cluster #

介绍
    child_process和net模块的组合
        cluster启动时,内部启动tcp服务器
        fork()时,将tcp服务器socket文件描述符发给worker, 实现共享端口
isWorker
    判断process.env是否值为NODE_UNIQUE_ID
isMaster
    判断cluster.isWorker
事件
    fork            # fork时
    online          # 工作进程创建好后
    listening       # 工作进程调listen()后
    disconnect      # 主进程和工作进程IPC通道断开后
    exit            # 所有工作进程退出后
    setup           # cluster.setupMaster()执行后

o-> cpu核数worker
var cluster = require('cluster')
cluster.setupMaster({
    exec: "worker.js"
})

var cpus = require('os').cpus()
for (var i = 0; i < cpus.length; i++) {
    cluster.fork()
}

domain #

介绍
    用于异步异常捕获
    绑定方式
        隐式绑定: 把domain上下文中定义的变量,自动绑定到domain对象
        显式绑定: 把不是domain上下文中定义的变量,以代码的方式绑定到domain对象
members     # 已加入domain对象的域定时器和事件发射器的数组

create()                # 返回一个domain对象
run(fn)                 # 在domain上下文中执行一个函数,并隐式绑定所有事件、定时器和低级请求
add(emitter)            # 显式的增加事件
remove(emitter)         # 删除事件
bind(callback)          # 以return为封闭callback函数 
intercept(callback)     # 同bind, 但返回第一个参数
enter()                 # 进入一个异步调用的上下文,绑定到domain
exit()                  # 退出当前的domain, 切换到不同的链的异步调用的上下文中,对应domain.enter()
dispose()               # 释放一个domain对象,让node进程回收这部分资源
使用
    var domain = require('domain');
    function async_error(){
        setTimeout(function(){
            var r = Math.random() * 10;
            console.log('random num is ' + r);
            if(r > 5)
                throw new Error('Error: random num ' + r + ' > 5');
        }, 10);
    }
    var d = domain.create();
    d.on('error', function(err){
        console.log(err);
    });
    setInterval(function(){
        d.run(async_err);
    }, 1000);
未绑定不捕获
    代码
        var domain = require('domain');
        var EventEmitter = require('events').EventEmitter;

        var e = new EventEmitter();

        var timer = setTimeout(function(){
            e.emit('data');
        }, 10);

        function next(){
            e.once('data', function(){
                throw new Error('Receive data error!');
            });
        }

        var d = domain.create();
        d.on('error', function(err){
            console.log(err);
        });
        d.run(next);
    原因
        timer和e两个关键对象在初始化时都没有在domain范围内。当next函数中抛出异常时, 没有处于domain的包裹中

    修改
        ...
        d.add(e);
        d.add(timer);
        d.run(next);

外部lib #

url #

parse()
    pathname

    url.parse(req.url)
    url.parse(req.url, true) 会parse出query对象

querystring #

parse()
    querystring.parse(url.parse(req.url).query)

crypto #

介绍
    加密并生成各种散列
    利用openssl库来实现,提供openssl中一系列哈希方法,包括hmac, cipher, decipher, 签名和验证等方法的封装
使用
    var crypto = require('crypto');
    console.log(crypto.getHashes());                                    # 打印支持的所有hasp算法

node-gyp #

编译C++模块的编译工具

util #

使用
    var util = require('util');
方法
    inherits(Sub, Base)                # 对象间原型继承,Sub 仅继承Base原型中定义 的函数
    inspect(obj)                # 任意对象转换为字符串
    log(string)                        # 带时间戳的log
    format('%s:%s', 'a', 'b', 'c')                // 'a:b c'
        # format('%s:%s', 'a')                // 'a:%s'
        # format(1, 2, 3)                        // '1 2 3'
    is系列
        isArray(obj)
        isRegExp(obj)
        isDate(obj)
        isError(obj)
        isBoolean(obj)
        isNull(obj)
        isNullOrUndefined(obj)
        isNumber(obj)
        isString(obj)
        isSymbol(obj)
        isUndefined(obj)
        isObject(obj)
        isFunction(obj)
        isPrimitive(obj)
            # 是否基本类型
        isBuffer(obj)
        deprecate(foo, 'foo() is deprecated, use bar() instead');
            # 标记为过时, 调用foo()时显示后面的话

zlib #

介绍
    提供压缩方法,如gzip

全局属性 #

介绍
    并非挂在global下的属性,但可以直接使用

__dirname
    # 在任何模块内获取当前模块文件的绝对路径
__filename
    # 当前在执行的js文件路径

方案 #

异常捕获
    process.on("uncaughtException",function(e){
        logger.error("error:"+e);
    });
    process.on('unhandledRejection', function (err, p) {
        console.error(err.stack)
    });

Spring Cloud

亿级流量 #

流量接入层 #

二级域名
    泛域名
    A记录
dns解析
    udp
        向网关请求dns解析
    httpDNS
        用ip请求http服务, 返回域名解析的ip
        因为用ip请求,适合app,不适合网页
lvs + keepalive             # 多lvs时用dns负载
nginx
    openresty
        kong
动静分离
    cdn
        dns动态域名解析
        cdn分发服务
            源服务拉取FastDFS
            CDN节点分发

WEB服务层 #

webflux
    不基于重量的servlet标准
    基于netty

Eureka #

使用
    @EnableEurekaServer
    application.properties
        eureka.client.register-with-eureka=false                # 是否注册自己
        eureka.client.fetch-registry=false                      # 是否拉取eureka
        eureka.client.service-url.defaultZone=http://localhost:7900/eureka/         # 设置注册中心的URL
        eureka.instance.hostname=euk1.com
        spring.application.name=EurekeServer                    # eureka集群中各节点要同名
行为
    register                    # 注册
    renew                       # 通过心跳, 默认30s。三次失败删除实例
    fetch registry              # 拉注册的信息
    cancel                      # 发取消请求,删除实例
    time lag                    # 同步时间延迟
    communication mechanism     # 通讯机制,默认jersey和jackson
功能
    唯一标识                        # service id
        主机名:application.name:端口
    提供RestAPI, 可多终端接入
问题
    一致性问题方案
        Eureka间不同步,client向多个Eureka提交
        Enreka间同步,Eureka强可用性弱一致性

基础 #

介绍
    spring boot基础上构建,快速构建分布式系统, 全家桶
    面向云环境架构(云原生)    # 适合在docker和paas部署
功能
    配置管理
    服务发现
    熔断
    智能路由
    微代理
    控制总线
    全局锁
    决策竞选
    分布式会话
    集群状态管理
子项目
    spring cloud netflix    # 对netflix oss套件整合
        eureka     # 服务治理(注册、发现)
        hystrix    # 容错管理
        ribbon     # 软负载均衡(客户端)
        feign      # 基于hystrix和ribbon,服务调用组件
        zuul       # 网关,智能路由、访问过滤
        archaius   # 外部化配置
    基础
        spring cloud starters       # 基础依赖, 高版本取消
        spring cloud commons
    服务
        spring cloud consul         # 封装consul(服务发现与配置, 与docker无缝)
        spring cloud cluster        # 抽象zookeeper, redis, hazelcast, consul的选举算法和通用状态模式实现接口
        spring cloud cloudfoundry   # 与pivotal cloudfoundry整合
        spring cloud aws            # 整合aws
        spring cloud zookeeper      # 整合zookeeper
        spring cloud cli            # groovy中快速创建应用
        spring cloud task           # 任务
    配置
        spring cloud config         # 应用配置外部化, 推送客户端配置, 支持git存储
    消息
        spring cloud bus            # 消息总线,传播集群状态变化来触发动作,如刷新配置
        spring cloud stream         # 声明式发送、接收消息
    监控
        spring cloud sleuth         # 跟踪
    安全
        spring cloud security       # 应用安全控制, zuul代理中OAuth2中继器
    测试
        spring cloud contract       # 契约测试, 可用groovy和yaml定义
版本
    用命名不用版本号,因为有多子项目版本,易混淆
    命名用伦敦地铁站用,字母表排序
缺点
    难于追查框架问题
    非二进制通信协议
    适合中小团队

配置 #

pom.xml
    <modules>
        <module>spring-cloud-common</module>
        <module>spring-cloud-provider-book</module>
        <module>spring-cloud-service-discovery</module>
        <module>spring-cloud-api-gateway</module>
        <module>spring-cloud-consumer-book</module>
        <module>spring-cloud-monitor-dashboard</module>
        <module>spring-cloud-aggregator</module>
        <module>spring-cloud-zipkin-server</module>
        <module>spring-cloud-admin-server</module>
        <module>spring-cloud-config-server</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <relativePath/>
    </parent>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
application.yml
    spring:
        profiles: peer1                         # bean的逻辑组

组件 #

dependencyManagement
    spring-cloud-dependencies

spring cloud spring boot #

spring cloud eureka #

原理
    生产者向eureka注册, 周期发送心跳(默认30s)
    eureka服务间同步注册信息
    消费者请求地址,缓存本地
    eurake接收生产者心跳超时, 设置为down, 推送状态到消费者
    eurake短期down过多生产者,进入自我保护不再down
组件
    spring-cloud-starter-[netflix-]eureka-server

application.yml
    eureka:
        instance:
            hostname: localhost                 # 实例主机名
        client:
            registerWithEureka: false           # 当前服务不注册
            fetchRegistry: false                # 不获取注册信息
            serviceUrl:                         # server地址
                defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
bootstrap.yml                                   # 基础配置, 待拉取config

注解
    @EnableEurekaServer                         # 修饰Application类

spring cloud ribbon #

application.yml
    ribbon:
        eureka:
            enabled: false                      # 禁止从eureka获得注册列表

spring cloud hystrix #

# 单个服务监控
hystrix dashboard
    路径
        /hystrix
        /hystrix.stream                         # 至少请求一次接口,才有数据

spring cloud turbine #

# 多服务监控
application.yml
    turbine:
        aggregator:
            clusterConfig: default                                  # 此监控器名
        appConfig: erp-consumer-metadb, erp-consumer                # 目标服务名
        clusterNameExpression: new String("default")                # 名称匹配表达式
路径
    /turbine.stream

spring cloud feign #

application.yml
    feign:
        hystrix:
            enabled: true

spring cloud zuul #

# 默认会用ribbon负载均衡
application.yml
    zuul:
        prefix: /v1
        routes:
            hiapi:
                path: /hiapi/**
                serviceId: erp-consumer-metadb
                # url: http://localhost:8762  # 这样写不会做负载均衡
                # serviceId: hiapi-v1

    ## 手动url负载均衡
    # ribbon: 
    #   eureka:
    #     enabled: false
    # hiapi-v1:
    #   ribbon:
    #     listOfServers: http://localhost:8762,http://localhost:8763
案例
    过滤
        import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
        @Component
        public class MyFilter extends ZuulFilter {

            private static Logger log = LoggerFactory.getLogger(MyFilter.class);

            @Override
            public String filterType() {
                return PRE_TYPE;
            }

            @Override
            public int filterOrder() {
                return 0;
            }

            @Override
            public boolean shouldFilter() {
                return true;
            }

            @Override
            public Object run() {
                RequestContext ctx = RequestContext.getCurrentContext();
                HttpServletRequest request = ctx.getRequest();
                log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
                Object accessToken = request.getParameter("token");
                if (accessToken == null) {
                    log.warn("token is empty");
        //
        //            ctx.setSendZuulResponse(false);
        //            ctx.setResponseStatusCode(401);
        //            try {
        //                ctx.getResponse().getWriter().write("token is empty");
        //            }catch (Exception e){
        //
        //            }
                    return null;
                }
                log.info("ok");
                return null;
            }
        }
    熔断
        @Component
        public class MyFallbackProvider implements ZuulFallbackProvider {
            @Override
            public String getRoute() {
                return "*";
            }

            @Override
            public ClientHttpResponse fallbackResponse() {
                return new ClientHttpResponse() {
                    @Override
                    public HttpStatus getStatusCode() throws IOException {
                        return HttpStatus.OK;
                    }

                    @Override
                    public int getRawStatusCode() throws IOException {
                        return 200;
                    }

                    @Override
                    public String getStatusText() throws IOException {
                        return "OK";
                    }

                    @Override
                    public void close() {

                    }

                    @Override
                    public InputStream getBody() throws IOException {
                        return new ByteArrayInputStream("error, I'm the fallback".getBytes());
                    }

                    @Override
                    public HttpHeaders getHeaders() {
                        HttpHeaders headers = new HttpHeaders();
                        headers.setContentType(MediaType.APPLICATION_JSON);
                        return headers;
                    }
                };
            }
        }

spring cloud config #

config-server
    application.yml
        server:
            port: 9012
        spring:
            application:
                name: erp-config-server
            cloud:
                config:
                server:
                    native:
                        search-locations: classpath:/shared             # 读取路径
            profiles:
                active: native                                          # 本地读取
    shared/config-client-dev.yml                                        # 文件名为 [客户端服务名]-[profile变量]
        server:
            port: 9013
        foo: foo version 1
    地址
        localhost:9012/config-client/dev                                # 查看分发给服务的配置
config-client
    spring:
        application:
            name: erp-config-client
        cloud:
            config:
                uri: http://localhost:9012
                fail-fast: true
        profiles:
            active: dev
注解
    @RefreshScope                           # 热更新

spring cloud bus #

application.yml
    spring:
        rabbitmq:
            host: localhost
            port: 5672
            username: outrun
            password: asdf
            publisher-confirms: true
            virtual-host: /
    management:
        security:
            enabled: false
路径
    POST /bus/refresh                       # 从新拉配置, 其它服务也触发同步
        ?destination=appName:*.*            # 指定刷新服务名下实例

spring cloud stream #

spring cloud sleuth #

application.yml
    zipkin:
        base-url: http://localhost:9014
    sleuth:
        sampler:
            percentage: 1.0