月度归档:2016年01月

网站网络架构调整

虽然站小并没有人访问,但是技术是极致的,尝试点新的东西总不是什么坏事。所以在折腾了一段时间后决定将网站原来的简单的三层网络架构进行调整,加快网站的访问速度并提高网站的并发。

之前的网络架构非常简单,就是一个简单的把Client请求DNS解析到Server,然后Server返回请求内容的过程,可以用下图描述:

network1

在这段时间内,我陆续对其中的一些部分进行了调整。

首先使用能够自适应移动端的主题,这样移动端也能很好的获取到网站内容。然后使用CDN替代DNS,并加入CDN缓存,这样已经缓存的内容就能够直接返回给用户,不用对Server进行请求。同时CDN对请求进行分发,能够找到最近的解析服务器对Client的请求进行解析。

在Server端,则采取分类型缓存手段,将静态内容(图片,JS,CSS等)分离,通过二级域名解析到云端的对象存储服务器上,这样请求中的静态内容将直接从云服务器上获取,无需向Server发送请求。另外,在Server上建立Cache服务,请求中的内容如果有之前存储在Cache中的,能直接通过读取Cache的方式获得,无需执行额外的代码罗技和数据库内容请求。

于是,新的网络架构可以用下图表示:

network

看起来相对之前的简单结构确实复杂很多,但是带来的提升也是很明显的。

20160130121635

一眼看去,并发2000毫无压力,而且网站也可以正常访问,好像有点太厉害了。当然,这是因为CDN缓存的原因,就当是娱乐一下就好,但至少证明了网站即使在2000并发下也不会出现宕机。

下面说下复杂架构带来的弊端。主要问题就是,静态内容更新的刷新十分复杂。因为今天内容同时在Cache,CDN Cache,Static Server上进行了缓存,其中任何一个没有更新最后请求到的资源也是旧资源,所以如果静态内容出现了更新就智能将三者全部清除并发送更新请求。不过毕竟这些内容并不会时常更新,所以新架构带来的负面影响也是可以接受的。

服务器安全的配置

之前自己维护个人网站时并没有太在意服务器安全问题,用服务器时权限随意毫不设防也并没有什么影响。结果到了公司终于获得了教训:公司的服务器在毫不设防的情况下(其实有iptables限制,但是由于规则漏洞,形同虚设)被黑客用Redis Crackit的方式入侵成功。

RedisCrackit

入侵方法类似于:Redis_Crackit入侵事件还原。简单来说就是利用了以下几点获取了服务器的root权限:

  1. 服务器没有iptables过滤或者规则有漏洞
  2. Redis没有关闭远程登录
  3. Redis以无密码或弱口令方式运行
  4. Redis以root权限运行
  5. Redis可以使用flushdb等具有危险性的命令

以上任何一点如果有所防备都将使入侵无法成功。正是由于运维上的缺失导致了这次公司服务器被入侵。总结这次教训,我们应该至少在服务器安全上做出以下应对:

  1. 设置iptables限制,关闭不需要开的端口,对开放访问的端口如80,22,21,3306等进行登录IP限制。
  2. 不轻易使用root权限运行服务
  3. 所有可执行文件不应具有写权限
  4. 可访问目录,例如www目录,的用户组设为只具有较低权限的用户组,例如www-data
  5. 避免弱口令和明文密码的使用
  6. 安装服务器安全监控软件

使用CDN加速网站静态内容访问

使用CDN缓存网站的静态内容能够很大程度上提高网站的访问速度。通过将图片、JS等内容存储在CDN服务器上当用户访问时这些内容通过CDN服务器分发到用户面前而不是直接从服务器获取图片,这样能够大大提高速度并降低网站IO,是十分有效的提高网站负载的方法。

本站使用七牛云存储提供的CDN方案来实现CDN缓存。在七牛上申请一个对象存储空间,在镜像源处填写自己的域名,之后七牛的服务器便可以获得网站的静态信息,并加以存储。

20160127195645

每个空间都对应了一个七牛提供的域名,绑定好空间后便可以通过这个域名访问到储存的内容。同时,七牛也提供了绑定自有域名的选项(需备案):

20160127195918

将自有域名以CNAME的方式解析到七牛提供的二级域名即可完成域名绑定。这样就可以通过自有的二级域名访问到自己的网站。

接下来,只需要在自己的网站中,将所有静态内容调取都改为从CDN处获得就完成了CDN缓存的配置。在WordPress中可以通过WP-Super-Cache这个插件方便的完成这些配置。

20160127200240

现在打开网站,查看网站的代码就可以看见,所有的静态内容已经从www域名转到了static域名下:

20160127200418

Let’s Encrypt SSL证书试用

Let’s Encrypt (进入官网) 是一个开放的CA项目,旨在让每个网站都能使用HTTPS加密,该项目获得了思科、Mozilla、Akamai、IdenTrust和EFF等组织的支持,由Linux基金会托管。

Let’s Encrypt is a free, automated, and open certificate authority (CA), run for the public’s benefit. Let’s Encrypt is a service provided by the Internet Security Research Group (ISRG).

The key principles behind Let’s Encrypt are:

  • Free: Anyone who owns a domain name can use Let’s Encrypt to obtain a trusted certificate at zero cost.
  • Automatic: Software running on a web server can interact with Let’s Encrypt to painlessly obtain a certificate, securely configure it for use, and automatically take care of renewal.
  • Secure: Let’s Encrypt will serve as a platform for advancing TLS security best practices, both on the CA side and by helping site operators properly secure their servers.
  • Transparent: All certificates issued or revoked will be publicly recorded and available for anyone to inspect.
  • Open: The automatic issuance and renewal protocol will be published as an open standard that others can adopt.
  • Cooperative: Much like the underlying Internet protocols themselves, Let’s Encrypt is a joint effort to benefit the community, beyond the control of any one organization.

从15年初我便开始观察这个项目的进展,终于在11月,Let’s Encrypt颁发了第一张证书。从12月起,Let’s Encrypt开始了公测。我也在第一时间试用了这个所谓的免费SSL证书。

安装并不复杂,从GitHub上拉取项目代码,在本地生成SSL的Private Key,在Nginx中加入对443端口的监听,并加入ssl key即可。具体安装过程在Let’s Encrypt的官方Docs中有详细的介绍(Doc: https://letsencrypt.readthedocs.org/en/latest/index.html)。对于WordPress,还需要稍微修改下后台的链接,在设置中将链接修改成https方式即可使用。

总的来说,Let‘s Encrypt的部署过程十分容易且操作友好,有简单的图形界面(符号拼图),可以说在功能上已经较为完整。生成的证书也可以被主流浏览器识别,在一些无需要求太高的网站上已经十分适用。但是生成的SSL证书有效期只有三个月,需要每三个月重新生成一次,所以增加了维护成本。当然,写个脚本每三个月自动生成一次自然是更加聪明的做法。

站点服务器迁移,从Apache2到Nginx

在工作过程中逐渐体会到了Nginx在静态页面处理上的优势,决定将Server由之前的Apache转向Nginx。相对于Apache,Nginx使用更低的内存就能够实现更高的并发,这对于我使用的低配置服务器自然是非常合适的。

迁移过程十分简单,无非就是停止Apache服务,安装Nginx,将站点加入Nginx的Server中即可,网上相关资料很多,这里就不赘述了。(当然还有php-fpm的配置)

迁移后的结果还是十分满意的,下面配上压测的结果

20160127191809

上图是改成Nginx后的压测结果,RPS已经打到了58/s,要知道之前使用Apache服务器的时候,执行ab -c 1000 -n 100直接就宕机了。压测过程中的负载也极低,在0.01以下。

20160127192333

这个数据在之前使用Apache时可能超过20,这样的高值往往意味着服务器宕机。

总之,对于服务器配置不高,主要处理静态内容或者是追求高并发低负载这些要求的话,使用Nginx将是非常明智的选择。

Python学习笔记(二)

Python基础

控制台输入

print()函数可以接受多个字符串,用逗号“,”隔开。print()会依次打印每个字符串,遇到逗号“,”会输出一个空格,这样就可以连成一串输出:

>>> print('The quick brown fox', 'jumps over', 'the lazy dog')
The quick brown fox jumps over the lazy dog

python使用”%”作为替代符,与C类似:

>>> print('str: %s \nint: %d' % ('string', 100))
str: string 
int: 100

数据类型和运算

转义

使用”\”作为转义字符

# iamok.py
print('I\'m \"OK\"!')

$ python iamok.py
I'm "OK"

可以使用r’ ‘包含字符串进行整段文字的转义:

>>> print('abc\nde')
abc
de
>>> print(r'abc\nde')
abc\nde
>>> print('abc\\nde')
abc\nde

布尔值

布尔值包括True和False,需要区分大小写:

>>> True
True
>>> true
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
NameError: name 'true' is not defined

空值

Python中的空值用None表示,不是Null

>>> None
>>> Null
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
NameError: name 'Null' is not defined

除法

Python提供了“/”、“//”、“%”三种除法,三种除法结果如下:

>>> 10/3
3.3333333333333335
>>> 10//3
3
>>> 10%3
1

第一个结果结尾是5而不是3的原因可能和计算机二进制有关,没有深究。

编码

字符串编码规则十分复杂,这里不作深究。只需记住,通常在文件开头写上这两行:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;

第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。

申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码。

list

list是一个有序集合(有序集合不就是数组么,为啥还要叫集合?),基本用法如下:

>>> list = ['str1', 'str2', 'str3']
>>> list
['str1', 'str2', 'str3']
>>> len(list)
3
>>> list[0]
'str1'
>>> list[-1]
'str3'

确实就是集合嘛,可以用-1取最后的值,-n以此类推。

下面有一些对list的基本操作:

>>> list.append('str4')    # 在末尾加入元素
>>> list.insert(0,'str0')    # 在指定位置插入元素
>>> list
['str0', 'str1', 'str2', 'str3', 'str4']
>>> list.pop(3)    # pop出指定位置元素
'str3'
>>> list[2] = ['int0','int1']
>>> list
['str0', 'str1', ['int0', 'int1'], 'str4']    # 可以存在二维list
>>> list[2][1]
'int1'

tuple

和list唯一不同的地方在鱼tuple定义后不能修改。定义时使用”( )”而不是list的”[ ]”

需要注意的地方是只有一个元素时需要在结尾加上逗号

>>> t = (1)      # 错误,定义的是t=1,括号被解释成了数学上的小括号
>>> t = (1,)     # 正确

dict

字典,其实就是以Key-Value的方式存储的数据对:

>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95

要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:

>>> 'Thomas' in d
False

二是通过dict提供的get方法,如果key不存在,可以返回None,或者自己指定的value:

>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1

注意:返回None的时候Python的交互式命令行不显示结果。

要删除一个key,用pop(key)方法,对应的value也会从dict中删除:

>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。

在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key

set

set是数学意义上的集合,无序不重复,以list作为输入

>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}

使用add和remove方法增减元素

>>> s.add(4)
>>> s
{1, 2, 3, 4}

两个set可以做数学意义上的交集、并集等操作:

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

代码逻辑

判断逻辑

使用if、elif、else三个保留字

age = 3
if age >= 18:
    print('adult')
elif age >= 6:
    print('teenager')
else:
    print('kid')

循环逻辑

使用for关键字

names = ['Michael', 'Bob', 'Tracy']
for name in names:
    print(name)

range(101)就可以生成0-100的整数序列,计算如下:

sum = 0
for x in range(101):
    sum = sum + x
print(sum)

 

Python学习笔记(一)

Python是非常流行的脚本语言,能够用非常简洁的代码完成复杂的逻辑。出于一下几个原因我决定开始学习Python:

  1. 直接原因是公司的网站用Django框架
  2. 间接原因是由于同时还肩负着公司的运维,经常需要使用脚本
  3. 个人原因也需要掌握一门脚本语言。之前只用shell写脚本有些吃力

于是就这样开始了~

安装Python3

Python有两个主要版本:2.x和3.x。既然有新的版本那就从新版本入手好了,于是决定学习Python3。

Ubuntu14.04下自带了Python3.4,但是直接使用python命令调用的是Python2.7:

root@localhost:~# python
Python 2.7.6 (default, Jun 22 2015, 17:58:13) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

使用python3命令时才会调用3.4版本的python:

root@localhost:/usr/bin# python3
Python 3.4.3 (default, Oct 14 2015, 20:28:29) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

使用以下步骤将默认的python版本切换为python3。

在用户目录下新建.bash_alias文件:

cd ~
vi .bash_alias

向其中写入以下内容:

alias python=python3

执行下面命令完成切换:

source ~/.bash_aliases

再使用python命令时版本已经切换成了python3:

root@localhost:~# python
Python 3.4.3 (default, Oct 14 2015, 20:28:29) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Hello World

创建.py文件,写入一下内容:

print('hello, world')

执行

# python helloworld.py
hello, world