我们不能失去信仰

我们在这个世界上不停地奔跑...

0%

Python中的类及元类metaclass

先来说说 Python 中最基本的类。

类也是对象

在 Python 中,类也是一种对象。在 Python 解释器执行的时候,就会创建一个对象。这个对象(类对象)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象所以,我们可以:

  • 将它赋值给一个变量
  • 拷贝它
  • 为它增加属性
  • 将它作为函数参数进行传递
    阅读全文 »

Tornado 对比 Django

Django 的 web 应用部署时采用 wsgi 协议与服务器对接,而这类服务器通常都是基于多线程的,也就是说每一个网络请求都会有一个对应的线程来进行处理。

Django 是走大而全的方向,注重的是高效开发,它最出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。

Django 提供的方便,也意味着 Django 内置的 ORM 跟框架内的其他模块耦合程度高,应用程序必须使用 Django内置的ORM,否则就不能享受到框架内提供的种种基于其 ORM 的便利。

  • session功能

  • 后台管理

  • ORM

阅读全文 »

小整数对象池

整数在程序中的使用非常广泛,Python 为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。

Python 对小整数的定义是 [-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收。 Python 程序中,所有位于这个范围内的整数使用的都是同一个对象。

同理,单个字母也是这样的。

大整数对象池

每个大整数,均创建一个新的对象。

阅读全文 »

Nginx 采用 Master-Worker 的多进程模型。

Nginx 启动后,Master 进程会去加载配置文件,建立好需要 ListenSocket(Listenfd) 之后,然后 Fork 出多个 Worker 进程,Master 进程负责管理Worker 进程以及接收来自外接的信号,向各个 Worker 进程发送信号,监控 Worker 进程的运行状态,如有 Worker 异常退出,Master 进程会重新启动新的 Worker 进程。请求的处理是放在 Worker 进程中进行。

采用 Master-Worker 多进程的模式有诸多好处。

  1. 如果程序有 bug 导致 Worker 异常退出,Master 会启动新的 Worker 补充上,这样就不会对其他没有异常的服务造成影响,就不至于造成整个服务都无法访问。
  2. Worker 个数是确定的,一般 CPU 有多少核,就设置成多少,这样就不会造成无意义的进程切换及进程竞争开销。

Nginx 是采用多路复用I/O模型(严谨点的话不能说是异步非阻塞I/O模型),所以它的并发量会比 Apache 高非常多,并且性能更好,更轻量。

Apache 中每个请求会独占一个工作线程,当并发数达到几千时,就同时有几千的线程在处理请求了。这对操作系统来说,是个不小的挑战,线程带来的内存占用非常大,线程的上下文切换带来的 CPU 开销很大,性能自然就上不去了,而且这些开销完全是没有意义的。阻塞的系统调用会导致 CPU 空闲,CPU 的利用率就会下降。

同步阻塞:线程会阻塞在那里等待返回,并让出 CPU

非阻塞:线程发现数据还没有可用的,就先直接先去做其他事情,定时自己来检查数据状态

异步非阻塞:线程发现数据没有可用的,然后把这个监控数据的任务交给操作系统,由操作系统来通知线程数据是否已经准备好。

有时候经常把异步非阻塞和非阻塞调用搞混,到底算不算异步,取决于进程向内核发起读取数据操作的时候,在内核把数据从内核态复制到用户态的这一时间内,进程是否处于阻塞状态。

Nginx 使用的是 epoll 模型,可以并发处理大量请求,但是同一时间能处理的请求当然也只有一个,只是在请求间不断地切换而已,切换是因为事件未准备好,而主动让出的。这里的切换是没有任何代价的,这跟 Apache 由于阻塞让出 CPU 是完全不同的。与多线程相比,Nginx 的处理机制有很大的优势,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,不会造成竞争 CPU,事件处理也非常的轻量级。并发数再多也不会导致无谓的资源浪费。更多的连接只会造成更多的内存占用,在 1 G 的机器上,可容纳的连接数可达 10 万左右。

Linux I/O 模型

select、poll、epoll 请看我很早写的另一篇文章:

Linux I/O 模型之select、poll、epoll

着重讲了什么阻塞、非阻塞、异步、同步 及 select、poll、epoll 解析

Nginx 如何热部署

当Nginx reload 发生的时候,比如执行 nginx -s reload

Master 进程收到信号后,会重新加载配置文件,然后向当前的 Worker 发出信号,并且告诉他们把当前手中的任务处理完你们就可以光荣退休了,不要再去竞争新的连接,然后 MasterFork 出新的 Worker,此时新的 Worker 就是具备新的配置文件属性,因此就完成了热重启。

Nginx 负载均衡

Nginx 负载均衡通过配置 upstream 模块来实现,内置了三种负载均衡策略。

  • s轮询(默认)/ 加权轮询

    轮询:Nginx 根据请求次数,将每次请求均匀分配到每台服务器,如果每台服务器的性能都一样,那么在一定程度上是可以使用这种模式的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    http {

    # ... 省略其它配置

    upstream web1 {
    server 192.168.0.100:8080;
    server 192.168.0.101:8080;
    server example.com:8080;
    }

    server {
    listen 80;

    location / {
    proxy_pass http://web1;
    }
    }

    # ... 省略其它配置
    }

    加权轮询:如果在 upstream 中的 server 参数最后面加上 weight=n ,就变成了加权轮询:

    1
    2
    3
    4
    5
    upstream web1 {
    server 192.168.1.2:8080 weight=1; # 分担 1/6 的请求
    server 192.168.1.3:8080 weight=2; # 分担 2/6 的请求
    server test.com weight=3; # 分担 3/6 的请求
    }

    除了 weight 还支持其他的一些参数比如下面这条语句:

    1
    2
    3
    4
    upstream web1{
    server 192.168.1.2:8080 weight=2 max_fails=3 fail_timeout=15;
    server 192.168.1.10:8080 backup;
    }

    第一条server 语句的意思是 192.168.1.2:8080 这台服务器的权重是2,如果出现了 3 次请求失败,就等待 15 秒后再重新检测节点健康状态。

    第二条 server 语句的意思是 192.168.1.10:8080 这台服务器作为备份机,所有服务器挂了之后才会生效。

    即使请求转发到了不可用的节点,此次请求也不会丢失,因为 Nginx 会把此次请求转发到另一个可用节点,如果节点全部不可用,Nginx 会返回最后一个节点返回的信息响应给客户端。

    请求失败的定义:与服务端建立连接、向服务端发送请求或者解析服务端响应头时,发生异常或超时被认为是通信失败,服务端返回的响应为空或不合法会被认为是通信失败,如果配置了 http_500、http_502、http_503、http_504 和 http_429,服务端返回这些状态码会被认为是通信失败,服务端返回 http_403、 http_404 永远不会被认为通信失败。

    upstream 存在一些问题:

    • 无法主动感知服务器状态
    • 配置不灵活,无法自定义通信失败判断头条(仅提供少数定义好的状态码可供使用)

    backup: 备份机,所有服务器挂了之后才会生效

    max_fails: 默认为1,某台 Server 允许请求失败的次数,超过最大次数后,在 fail_timeout 时间内,新的请求将不会分配给这台机器。如果设置为0,Nginx 会将这台 Server 置为永久无效状态,然后将请求发送到其他 handler 来处理这次的错误请求。

    fail_timeout: 默认10秒。某台 Server 达到 max_fails 次失败请求后,在 fail_timeout期间内, Nginx 会认为这台 Server 暂时不可用,不会将请求分配给它。

    max_conns: 限制分配给某台 Server 当前正在处理的最大连接数量,超过这个数量,将不会分配新的连接给它。默认为0,表示不限制。1.5.9 以后才有这个参数

    resolve: 将 server 指令配置的域名,指定域名解析服务器。需要在 http 模块下配置 resolver 指令,指定域名解析服务。

    1
    2
    3
    4
    5
    6
    7
    http {
    resolver 192.168.1.20; # dns 服务器地址

    upstream web1 {
    server abc.com resolve;
    }
    }
  • 最少连接

    将请求分配给连接数最少的服务器。 Nginx 会统计哪些服务器的连接数最少

  • IP Hash

    绑定处理请求的服务器。第一次请求时,根据客户端的 IP 算出一个 HASH 值,将请求分配到集群中的某一台服务器上。后面该客户端的所有请求,都将通过 HASH 算法,找到之前处理这台客户端请求的服务器,然后将请求交给它来处理。

  • URL_Hash

    在1.7.2版本之后,加入了 url_hash 策略,按照请求 url 的 hash 结果来分配请求,使每个 url 定向到同一个后端服务器,服务器做缓存时比较有效。

    1
    2
    3
    4
    5
    6
    upstream web1 {
    server 192.168.1.3:8080;
    server 192.168.1.4:8080;
    hash $request_uri;

    }

Nginx 官方配置参数

除了以上三种是官方自带的负载均衡策略,还有第三方的,比较常用的有一下几个

fair

根据服务器的响应时间来分配请求,响应时间短的优先分配,即负载压力小的优先会分配。

使用第三方模块时,我们需要在编译 Nginx 源码时,将其添加到 Nginx 模块中,具体方法可以查看第三方模块 README。

nginx-upstream-fair Github地址

Nginx 心跳检测

  • nginx_upstream_check_module

    这个模块时由淘宝开发,后端服务器健康检查状态都存在于共享内存中,共享内存的大小需要根据服务器数量进行设置

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
http {
check_shm_size 1M;

upstream springboot {
server 10.3.73.223:8080;
server 10.3.73.223:8090;
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_keepalive_requests 1;
check_http_send "GET /test HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx;
}

server {
listen 80;
server_name localhost;

location /test {
proxy_pass http://springboot;
}

location /status {
check_status;
access_log off;
}
}
}
  • check_shm_size: 共享内存大小
  • interval:向后端服务器发送健康检测的间隔时间
  • fall:连续失败的次数叨叨fall_count,服务器被认为是 down 状态
  • rise:连续成功次数达到 rise_count,服务器被认为是 up 状态
  • timeout:健康检查请求超时时间
  • default_down:设定初始服务器的状态,如果是 true,服务器默认是 down 状态,如果是 false,服务器默认是 up 状态,默认值是 true,也就是一开始服务器都被认为不可用,要等健康检查请求达到一定成功次数以后才会被认为是健康的。
  • type:健康检查请求协议类型,支持 TCP、HTTP、SSL_HELLO、MySQL 和 AJP
  • PORT:健康检查请求发送端口,可以与后端服务器服务端口不同
  • check_keepalive_requests:一个连接可发送的请求数,默认值为1,表示完成1次请求后即关闭连接
  • check_http_send:HTTP 接口健康检查发送的请求内容,为了减少传输数据量,推荐采用 HEAD 方法。默认值 “GET / HTTP/1.0 \r\n\r\n”
  • check_http_expect_alive:HTTP 接口健康检查成功状态码,默认值 http_2xx | http+3xx
  • 后端服务器状态查询页面,提供三种展示方式,语法: check_status [html |csv |json],默认值 html
  • check_http_send 发送到 TOMCAT服务器,可能需要配置 HOST,否则可能一直发送心跳检测,一直判定为失败
1
2
3
4
upstream xx{
check_http_send "GET /admin/health_check.htm HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n";

}

Nginx 正向代理和反向代理的区别

正向代理

请求到 Nginx 后, Nginx 帮你去请求你的目标地址,目标地址把请求响应发送给 Nginx,Nginx 再重新组装包返回给客户端。这样可以隐藏客户端的IP地址。

反向代理

请求到 Nginx 后, Nginx 将请求分发给后端服务器进行处理,后端服务器再将响应返回给Nginx,再由 Nginx 将响应做一些修改后,返回给客户端。在这个过程中, 后端真实服务器地址对客户端是不可见的。

国庆假期折腾了半天,把 NGINX 卸载了又装,一次又一次重新编译,来配置 HTTP2 和 基于 OPENSSL 的 TLS1.3 以及 HTTP2 的一些特性,如 HTTP2_PUSH 等等。总之就是很麻烦。于是就想到了 Docker。

拉取 NGINX 镜像

使用

# docker search nginx

可以看到以下内容:

image-20181008143929082

我们可以用第一个官方的就可以。

阅读全文 »

前些日子,面试官问我 HTTP 和 HTTPS 的区别的时候?

我总是说 HTTPS 就是在传输层和应用层之间加了一个基于TLS的加密协议。

image-20181003014931328

后来,反复审视自己的问题。觉得了解一个东西不要太表面。他也没深问,我也没往下说。

HTTP 存在的风险(切身经历)

为什么我的 js 代码偶尔无法运行

我自己是写了一个线上的 WEB 应用,偶尔就会接到反馈说我的网页怎么点击按钮没反应。

然后我反复纠结是否是我 js 代码写的有问题,无论怎么改,都会出现十次有两三次的现象,按钮无效,无法提交表单。

细心观察发现,那些无法执行的时候,页面上总是有些乱七八糟的广告,要么就是有个移动流量的圆球,要么就是什么吸引眼球的点击网页。

阅读全文 »

今天有一个文件,每次处理之后,发布之前总需要替换一些东西,本来想着写个脚本,但是后来又发现没必要,一行sed命令就可以解决的事情,于是写了写sed

然后发现 sed 的对于一些正则表达式的支持在 Mac 上面不一样。

事情是这样的,我处理完文件之后,把它处理的结果重定向到文件里面,然后我打开文件,发现,其实它是没有改变的,但是如果输出到 stdout 就是命令行,它是修改了的,然后就研究了一会。

在 Mac 上的 sed 正则表达式问题, 在 Linux 上面工作正常

我的解决方法是将原本的

➜ sed -i 's/\(^\s*!\[image.*\)\.\.\(.*\)/\1\2/g' tmp.md > a.md

\s 替换成空格

➜ sed -e 's/\(^ *!\[image.*\)\.\.\(.*\)/\1\2/g' tmp.md > a.md

然后就可以了。

很多概念就是在不断讨论中,不断查询中,不断修正。

先说几个概念

  • 用户空间和内核空间

    • 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为 4GB(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对 Linux 操作系统而言,将最高的 1GB 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF ),供内核使用,称为内核空间,而将较低的 3GB 字节(从虚拟地址 0x00000000 到0xBFFFFFFF ),供各个进程使用,称为用户空间。

      每个进程的 4GB 进程空间中,最高 1GB 都是一样的,即内核空间。只有剩余的 3GB 才归进程自己使用。

      换句话说就是、最高 1GB 的内核空间是被所有进程共享的!

      如图:

      image-20180930133015993

    详情请参考: Linux 内核空间与用户空间

阅读全文 »

Redis 简介

Redis 是一个开源的内存中的数据存储系统, 可以用作 : 数据库、 缓存和消息中间件。

常见的数据类型: String (字符串), Hash(散列),List(列表),Set(集合),有序集合(Sorted Set或者ZSet)与范围查询, Bitmaps, Hyperloglogs 和地理空间(Geospatial)索引半径查询。

数据库的工作模式按存储方式可以分为: 硬盘数据库和内存数据库。 Redis 将数据储存在内存里面,读写数据的时候都不会受到磁盘 I/O 速度的限制,所以速度极快。

磁盘数据库每次加载数据会存在 I/O速度的限制

  • 不同 I/O 类型及其对 CPU 的开销
I/O 类型 花费的 CPU 时钟周期
CUP一级缓存 3
CPU二级缓存 14
内存 250
硬盘 41000000
网络 240000000

Redis 采用的是基于内存的单进程单线程模型的 KV 数据库, 由 C 语言编写,官方提供的数据是可以达到 10万+ 的 QPS(每秒内查询次数)。 这个数据不比采用单进程多线程同样基于内存的 KV 数据库 Memcached 差,官方给的基准程序测试:

阅读全文 »

设有n个正整数,将他们连接成一排,组成一个最大的多位整数。
如:n=3时,3个整数13,312,343,连成的最大整数为34331213。
如:n=4时,4个整数7,13,4,246连接成的最大整数为7424613。
输入
2
12 123
输出
12312

代码:

1
2
3
4
import sys        
num = raw_input()
nums = sys.stdin.readline().strip('\n').split(' ')
print ''.join(sorted(nums,cmp = lambda x, y: int(str(x)+str(y))-int(str(y)+str(x)), reverse=True))

主要是实现一个比较器来决定每个数据的排序顺序。