Redis 读书笔记

这篇文章主要介绍 Redis 的持久化机制,主从复制等等

持久化机制

通常情况下 Redis 会将数据存储于内存中,但 Redis 也支持持久化。Redis 支持两种持久化方式,RDB 方式 和 AOF 方式。RDB 通过快照方式,将内存数据写入磁盘。而 AOF 方式则是类似 MySQL 日志方式,记录每次更新的日志。前者性能高,但是可能引起一定的数据丢失,后者相反。

RDB 方式

RDB 通过快照 snapshotting 完成,也是 Redis 默认的持久化方式,当符合一定条件时 Redis 会自动将内存中的所有数据以快照方式保存一份副本到硬盘上 (dump.rdb),这个过程称为”快照”。

Redis 根据以下情况执行快照:

  • 根据配置规则进行自动快照
  • 用户执行 SAVE 或者 BGSAVE 命令
  • 执行 FLUSHALL 命令
  • 执行复制 replication
  1. 配置规则
  • save 900 1 表示在 15min(900s) 时间内,有一个或者一个以上键被更改则进行快照。
  • save 300 10 表示 300 秒内超过 10 个 key 被修改,则发起快照
  1. SAVE 或 BGSAVE 命令

SAVE 命令时, Redis 同步地进行快照操作,会阻塞所有来自客户端的请求。尽量避免在生产环境使用这一命令。

BGSAVE 命令,后台异步进行快照。查看快照是否成功,通过 LASTSAVE 命令获取最近一次成功执行快照时间,返回结果 Unix 时间戳。

  1. FLUSHALL ,Redis 清除数据库所有数据。只要定义了自动快照条件,则会进行快照。如果没有定义自动快照,则不会进行快照。

  2. 复制操作时,即使没有定义自动快照条件,也会生成 RDB 快照

Redis 默认将快照文件存储在工作目录中 dump.rdb 文件中,可以通过配置 dir 和 dbfilename 两个参数分别来指定快照文件的存储路径和文件名。

快照的保存过程:

  1. redis 调用 fork, fork 一份子进程
  2. 父进程处理 client 请求,子进程负责将内存内容写入临时文件,父进程处理写请求时 os 为父进程创建副本,子进程地址空间内的数据是 fork 时刻整个数据库的快照
  3. 当子进程将快照写入临时文件完毕之后,用临时文件替换原来的快照文件,子进程退出。

需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步变更数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘 io 操作,可能会严重影响性能。

AOF 方式

快照的方式是一定间隔备份一次,它的缺点就是如果 Redis 意外挂掉的话,就会丢失最后一次快照之后的所有修改。如果应用要求不能丢失任何修改可以采用 AOF 持久化方式。

AOF 将 Redis 执行的每一条写命令追加到硬盘文件中(默认为 appendonly.aof)。默认没有开启 AOF (append only file) ,可以通过 appendonly 参数启用:

appendonly yes

AOF 文件保存位置和 RDB 文件位置相同,通过 dir 参数设置,默认为 appendonly.aof ,通过 appendfilename 参数修改:

appendfilename appendonly.aof

Redis 在重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库。

操作系统会在内核中缓存写命令的修改,因此不是立即存盘,所以也可能会导致部分数据丢失。通过 fsync 函数强制系统写入磁盘的时机, 有三种方式如下(默认是:每秒 fsync 一次):

appendonly yes  # 启用 aof 持久化方式
# appendfsync always  # 收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
appendfsync everysec  # 每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
# appendfsync no  # 完全依赖 os, 性能最好,持久化没保证

AOF 方式的缺点:

  • 持久化文件会越来越大,为了解决这个问题 Redis 提供了 bgrewriteaof 命令。 收到此命令 Redis 将使用与快照类似的方式将内存中的数据以命令方式保存到临时文件中,最后替换原来的文件

集群

结构上,容易发生单点故障,分配不同服务器

容量上,内存容易成为存储瓶颈,需要对数据进行分片

集群的特点在于拥有和单机实例同样的性能,同时在网络分区后能够提供一定的可访问性以及对主数据库故障恢复的支持。

主从复制

复制多副本部署不同服务器,防止一台故障丢失数据。通过主从复制可以允许多个 slave server 拥有和 master server 相同的数据库副本。

主从复制特点:

  • master 可以拥有多 slave
  • 多 slave 可以连接到同一个 master 外也能连接到其他 slave
  • 主从复制不会阻塞 master, master 在同步数据时也可以继续处理 client 请求

从数据库配置中:

slaveof 主数据库地址 主数据库端口

当配置好 slave 后,slave 与 master 建立连接,然后发送 sync 命令。无论是第一次连接还是重新连接,master 都会启动一个后台进程,将数据库快照保存到文件中,同时 master 主进程会开始收集新的写命令并缓存。后台进程完成写文件后,master 发送文件给 slave, slave 将文件保存到硬盘上,再加载到内存中,接着 master 就会把缓存的命令转发给 slave, 后续 master 将收到的写命令发送给 slave。如果 master 同时收到多个 slave 发来的同步连接命令,master 只会启动一个进程来写数据库镜像,然后发送给所有的 slave。

使用 info 命令来判断当前 redis 是主库还是从库, role 字段,还可以利用 master_link_status 来查看主从是否异步,up 为正常,down 为同步异步。

哨兵

监控 Redis 运行状况。

Redis 命令属性

Redis 不同命令拥有不同的属性,是否只读命令,是否是管理员命令,一个命令可以拥有多个属性。

  • REDIS_CMD_WRITE 属性,会修改 Redis 数据库数据
  • REDIS_CMD_DENYOOM 属性,可能增加 Redis 占用的存储空间,显然拥有该属性的命令都拥有 REDIS_CMD_WRITE 属性。
  • REDIS_CMD_NOSCRIPT 属性,无法在 Redis 脚本中执行
  • REDIS_CMD_RANDOM 脚本执行了该属性命令之后,不能执行拥有 REDIS_CMD_WRITE 属性命令
  • REDIS_CMD_SORT_FOR_SCRIPT 产生随机结果
  • REDIS_CMD_LOADING 当 Redis 启动时,只会执行拥有该属性的命令

Redis 事务

Redis 由单线程处理所有 client 请求,在接收到 client 发送的命令后会立即处理并返回结果,但是当 client 发出 multi 命令后,这个连接会进入事务上下文,连接后续命令并不会立即执行,而是先放到队列中,当收到 exec 命令后,redis 顺序执行队列中所有命令,并将结果一起返回。

get keydemo
multi
set keydemo 10
set keydemo 20
exec
get keydemo

在 multi 开始之后可以使用 discard 来取消事务。

Redis 的乐观锁

乐观锁,指的是每次拿数据时认为别人不会修改,不上锁,而在提交更新市判断期间是否有别人更新该数据。

乐观锁使用数据库版本记录实现,需要满足提交版本必须大于当前记录版本才能执行更新的乐观锁策略。

Redis 从 2.1 版本开始支持乐观锁,可以使用 watch 显式对 key 加锁。watch 命令会监视给定的 key,在 exec 结束时如果监视 key 从调用 watch 后发生过变化,则事务会失败。

其他

phpRedisAdmin 是一个 PHP 实现的 Redis 管理 Web 界面。地址:https://github.com/erikdubbelboer/phpRedisAdmin

reference


2017-08-01 redis , database , database , key-value , db

使用 openpyxl python lib 来读写 Excel

Openpyxl 是一个用来处理 Excel 格式文件的 Python 库,它能用来处理 Excel 2007 及以上版本的 excel 文件,也就是 .xlsx/.xlsm/.xltx/.xltm 格式的表格文件。

installation

使用 pip 安装

pip install openpyxl

usage

使用方法包括读和写,参考如下例子:

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

from openpyxl import Workbook, load_workbook


class ExcelWriter:
	def __init__(self, name):
		self.name = name
		self.wb = Workbook()

	def create_work_sheet(self, sheet_name, index):
		ws = self.wb.create_sheet(sheet_name, index)
		ws.sheet_properties.tabColor = "1072BA"

	def write_row(self):
		ws = self.wb.active
		for row in range(1, 10):  # 1 到 10 行
			ws.append(range(10))  # 10 列
		self.wb.save(self.name)

	def set_cell_value(self, row, col, value):
		ws = self.wb.active
		ws.cell(row=row, column=col).value = value

	def save(self):
		self.wb.save(self.name)


class ExcelReader:
	def __init__(self, path):
		self.path = path
		self.wb = load_workbook(path)

	def get_all_sheetnames(self):
		return self.wb.get_sheet_names

	def get_all_rows(self, sheet_name):
		"""
		按行进行迭代
		:param sheet_name:
		:return:
		"""
		ws = self.wb.get_sheet_by_name(sheet_name)  # 调用 get_sheet_by_name 如果 sheet name 不存在返回 None
		if ws is None:
			return
		rows = ws.rows
		for row in rows:
			if row is None:
				return
			yield [col.value for col in row]

	def get_cell_value(self, sheet_name, row, col):
		ws = self.wb.get_sheet_by_name(sheet_name)
		if ws is None:
			return
		return ws.cell(row=row, column=col).value


if __name__ == '__main__':
	writer = ExcelWriter("sample.xlsx")

	writer.write_row()

	reader = ExcelReader('sample.xlsx')
	sn = reader.get_all_sheetnames()

	for data in reader.get_all_rows('Sheet'):
		print data

图片图标

openpyxl 还有很多重量级的功能,比如绘图等等,具体可参考文档。

其他

Python 其他处理 Excel 的库

reference


2017-07-31 python , excel , openpyxl

发送邮件服务收集整理

能够稳定发送邮件的服务,能够满足个人使用需求,能够通过 API 调用直接发送邮件的服务。

mailgun

免费服务每个月 10000 封 / 单个域名 限制,个人其实完全用不完

sendgrid

免费服务每天可以发送 100 封邮件

Sendcloud

搜狐的服务,免费账号 50 封邮件 / 天 20 条短信 / 天

  • https://sendcloud.sohu.com/price.html

Amazon SES

如果是通过 Amazon EC2 托管的程序,每个月前 62000 封邮件免费。


2017-07-30 email , collection , email-service

raspberry pi 折腾

关于树莓派的装机,配置,系统安装,网络配置等等网上有太多的叫教程,就不在一一介绍。这里主要想要整理一下在折腾过程中遇到的几个问题。一些细节很琐碎,记录下来备忘。我安装的是 Raspberry Pi 官方的系统,也就是 Debian 的衍生系统,所以绝大多数下面的内容在其他 Debian/Ubuntu/Linux Mint 系统上都可以操作。很多内容我在 Mint 下也都已经实现过。

raspberry_pi_2

树莓派的型号

树莓派到今天已经发布了很多代了,当时买的比较早,稍微和新一代比较一下

ras

安装风扇

一图胜过千言

raspberry_pi_fans

安装完成之后

raspberry_pi_fans_install

更新系统

在安装完成之后可以使用国内的 sources.list 源,比如说 清华大学的

deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ jessie main non-free contrib
deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ jessie main non-free contrib

然后更新软件包,一般 update 用来同步本地 package 和 源的 package 索引, update 一定要在 upgrade 或者 dist-upgrade 之前。update 只是用来同步 package 的状态,只是相当于检查更新,而需要手动触发更新。

sudo apt-get update

更新系统, upgrade 用来更新本地安装过的所有 package 的新版本。

sudo apt-get upgrade

更加智能的更新系统,会用更加智能的方式解决包冲突

sudo apt-get dist-upgrade

最后更新 kernel 和 firmware:

sudo rpi-update

中文支持

sudo apt-get instal ttf-wqy-microhei
sudo apt-get install scim-pinyin
sudo dpkg-reconfigure locales

挂载 NTFS

不同系统有不同的文件系统,在 Windows 下绝大部分的文件系统都是 NTFS,当然我一直在用的移动硬盘也格式化成了 NTFS,那么在 Linux 下挂载 NTFS 格式的磁盘,需要借助 ntfs-3g 这个 Package。

NTFS-3G 是微软 NTFS 文件系统的一个开源实现,包括读写支持。

使用如下命令安装 NTFS-3G

sudo apt-get install ntfs-3g

然后使用 sudo fdisk -l 来查看当前系统能够识别的磁盘及分区。一般来说额外的硬盘应该会是类似 /dev/sda1 这样的标示。

使用 mount 命令来挂载 NTFS 磁盘。

sudo mount -t ntfs-3g /dev/sda1 /media/sda1

mount 命令默认会调用 /sbin/mount.ntfs ,它在安装了 ntfs-3g 之后被符号连接到 /bin/ntfs-3g。 确保本地挂载点文件夹存在,否则自己手工新建 mkdir -p /media/sda1

或者可以直接使用 ntfs-3g 命令

ntfs-3g /dev/sda1 /media/sda1

具体参考: Arch Linux Wiki

外网连接

使用 frp , 在外网 VPS 上搞一个 server 。

共享 Samba

在之前的文章 就曾说到使用 Samba 来在局域网共享文件,搭配 Android 上 ES Explorer,电视盒子基本上可以做到类似家庭共享的作用,所有的一切都看路由器能够不能带动了。这里再说一下基本配置。

使用如下命令安装

sudo apt-get install samba

修改配置文件smb.conf

[Public]
   comment = Public Storage  # 共享文件夹说明
   path = /home/pi/Public # 共享文件夹目录
   read only = no # 不只读
   create mask = 0777 # 创建文件的权限
   directory mask = 0777 # 创建文件夹的权限
   guest ok = yes # guest访问,无需密码
   browseable = yes # 可见

设置文件夹权限

sudo chmod -R 777 /home/pi/Public/

重启 Samba 服务

sudo samba restart

完成后局域网中的其他设备就可以通过,\\IP\ 来访问共享的内容

安装 Resilio Sync

很早之前,我也写过 Resilio Sync 的文章,那个时候还叫 BTSync。在笔记本上一直跑着,这些天我看树莓派负载也不高,跑一个 Resilio Sync 应该也还可以。

不过过程却有点繁复,国内似乎屏蔽了 Resilio Sync 的官网,连他的 Key 都无法下载下来,可以使用 proxychains 代理的方法(可以参考之前的文章),不过毕竟也稍微麻烦一点,不过后来发现,在官网下载一个可运行的 rslsync ,再配置一些 conf 文件就可以直接开跑。

在文件 /etc/apt/sources.list.d/resilio-sync.list 中写入:

deb [arch=armhf] http://linux-packages.resilio.com/resilio-sync/deb resilio-sync non-free

添加公钥

wget -qO - https://linux-packages.resilio.com/resilio-sync/key.asc | sudo apt-key add -

更新 index

sudo dpkg --add-architecture armhf
sudo apt-get update

安装

sudo apt-get install resilio-sync

安装完之后,如果想要修改 conf 文件,可以去相应的位置 /etc 下找,然后使用树莓派的IP:8888 来访问WEB 管理界面。

然后就是添加相应的 KEY 来同步文件了。具体可以参考我之前那篇文章。

raspi_2_b

reference


2017-07-29 RaspberryPi , Linux

flask admin

Flask Admin 是 Flask 的一个管理插件,类似于 Django Admin 一样的存在,可以很方便的帮助开发者实现管理界面,并且能够提供一套和数据库对应的界面。

Flask-Admin 提供一个现成的 SQLAlchemy 模型接口。它以类执行并接受 2 个参数:模型类和数据库会话。

初始化 Flask Admin

from flask import Flask
from flask.ext.admin import Admin

app = Flask(__name__)

admin = Admin(app)

Add administrative views here

app.run()

运行这个程序并访问 http://localhost:5000/admin/ ,可以看到基础模板。

增加一个管理视图

from flask import Flask
from flask.ext.admin import Admin, BaseView, expose

class MyView(BaseView):
	@expose('/')
	def index(self):
		return self.render('index.html')

app = Flask(__name__)

admin = Admin(app)
admin.add_view(MyView(name='Hello'))

app.run()

模型视图

模型视图允许你为数据库中的每个模型增加专用的管理页面。可以自动为数据库中每一张表生成一个管理页面。

from flask.ext.admin.contrib.sqla import ModelView

Flask and Flask-SQLAlchemy initialization here

admin = Admin(app)
admin.add_view(ModelView(User, db.session))

构造自己的 ModelView,要定制这些模型视图,有两个选择:一是覆盖 ModelView 类的公有属性,二是覆盖它的方法。

from flask.ext.admin.contrib.sqla import ModelView

# Flask and Flask-SQLAlchemy initialization here

class MyView(ModelView):
	# Disable model creation
	can_create = False

	# Override displayed fields
	column_list = ('login', 'email')

	def __init__(self, session, **kwargs):
		# You can pass name and other parameters if you want to
		super(MyView, self).__init__(User, session, **kwargs)

admin = Admin(app)
admin.add_view(MyView(db.session))

文件管理

from flask.ext.admin.contrib.fileadmin import FileAdmin

import os.path as op

# Flask setup here

admin = Admin(app)

path = op.join(op.dirname(__file__), 'static')
admin.add_view(FileAdmin(path, '/static/', name='Static Files'))

2017-07-28 flask , web , python , linux

Flask 上传文件

Flask 处理上传的文件非常简单,总结归纳可以分为三步:

  • 使用 <form> 标签被标记有 enctype=multipart/form-data ,并且在里面包含一个 <input type=file> 标签
  • 服务端通过请求对象上的 files 字典访问文件
  • 使用文件的 save() 方法将文件永久地保存在文件系统上的某处

假设将上传的文件存放在 static/uploads 目录中。

werkzeug 库可以判断文件名是否安全,例如防止文件名是 /../test.png, 安装

pip install werkzeug

具体代码:

from flask import Flask, request
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)

app.config['UPLOAD_FOLDER'] = 'static/uploads/'
app.config['ALLOWED_EXTENSIONS'] = set(['png', 'jpg', 'jpeg', 'gif'])

# For a given file, return whether it's an allowed type or not
def allowed_file(filename):
	return '.' in filename and \
		   filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/upload', methods=['POST'])
def upload():
	upload_file = request.files['file']
	if upload_file and allowed_file(upload_file.filename):
		filename = secure_filename(upload_file.filename)
		upload_file.save(os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], filename))
		return 'hello, '+request.form.get('name', 'little apple')+'. success'
	else:
		return 'hello, '+request.form.get('name', 'little apple')+'. failed'

if __name__ == '__main__':
	app.run(debug=True)

app.config中的 config 是字典的子类,可以用来设置自有的配置信息,也可以设置自己的配置信息。函数 allowed_file(filename) 用来判断 filename 是否有后缀以及后缀是否在app.config['ALLOWED_EXTENSIONS']中。

过滤文件名

这里使用了 werkzeug 自带的 secure_filename 方法,该方法会过滤所有非 ASCII 码,对于中文文件名处理就不怎么友好了,所以我们可以定义自己的 secure_filename 方法

def secure_filename(filename):
    """
    确保文件名不包含 / -
    :param filename:
    :return:
    """
    filename = re.sub('[" "\/\--]+', '-', filename)
    filename = re.sub(r':-', ':', filename)
    filename = re.sub(r'^-|-$', '', filename)
    return filename

使用正则将文件名中的非法字符去除掉。

上传多个文件

Flask 提供了 getlist 方法

for upload in request.files.getlist("file"):
    filename = os.path.splitext(upload.filename)[0]
    filename = secure_filename(filename)
    save_name = hashlib.md5('video'+ str(time.time())).hexdigest()[:16]
    dest = '/'.join([dest_folder, save_name])
    print("Accept incoming file:", filename)
    upload.save(dest)

模拟上传文件

import requests
files = {'file': open('01.jpg', 'rb')}
user_info = {'name': 'einverne'}
r = requests.post("http://127.0.0.1:5000/upload", data=user_info, files=files)
print r.text

要控制上产文件的大小,可以设置请求实体的大小,例如:

app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 #16MB

如果要获取上传文件的内容可以:

file_content = request.files['image01'].stream.read()

使用 template

在 template 目录下新建 upload.html ,确保在 HTML 表单中设置 enctype=”multipart/form-data” 属性

<form action="\{\{ url_for('.upload_file') \}\}" method=post enctype=multipart/form-data>
	<div class="form-group">
	  <label for="exampleInputFile">上传文件</label>
	  <input type="file" name="file">
	  <p class="help-block">上传数据</p>
	  <button type="submit" class="btn btn-default">Submit</button>
	</div>
</form>

在 views.py 中写 upload_file 方法处理上传逻辑

@crawler_control.route('/upload', methods=['POST'])
def upload_file():
	if request.method == 'POST':
		f = request.files['file']
		if f and '.' in f.filename and f.filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']:
			filename = secure_filename(f.filename)
			path = op.join('/tmp', filename)
			f.save(path)
		return "Success"
	return render_template_string("only support POST")

2017-07-27 flask , web , python , linux

Flask restful

使用 JSON 作为交换格式

处理 JSON 时,请求和响应头的 Content-Type 设置为 application/json

from flask import Flask, request, Response
import json
app = Flask(__name__)

@app.route('/json', methods=['POST'])
def my_json():
	print request.headers
	print request.json
	rt = {'info':'hello '+request.json['name']}
	# add custom headers
	response.headers.add('Server', 'python flask')
	return Response(json.dumps(rt),  mimetype='application/json')

if __name__ == '__main__':
	app.run(debug=True)

模拟客户端请求

import requests, json

user_info = {'name': 'einverne'}
headers = {'content-type': 'application/json'}
r = requests.post("http://127.0.0.1:5000/json", data=json.dumps(user_info), headers=headers)
print r.headers
print r.json()

模拟 RESTful 接口

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/user/<username>')
def user(username):
	print username
	print type(username)
	return 'hello world'

@app.route('/user/<username>/friends')
def user_friends(username):
	print username
	print type(username)
	return 'hello world'

if __name__ == '__main__':
	app.run(debug=True)

使用内置类型装换

@app.route('/page/<int:num>')
def page(num):
	print num
	print type(num)
	return 'hello world'

自动将 num 转换成 int 类型,目前 Flask 支持的转换有:

类别 解释
string 任何不带 slash(/) 的字符串
int 整数
float float
path 接受 slash(/)
any 所有
uuid uuid string

复杂的用法,比如定义一个范围

@app.route('/page/<int:num1>-<int:num2>')
def page(num1, num2):
	print num1
	print num2
	return 'hello world'

自定义转换器

自定义的转换器是一个继承 werkzeug.routing.BaseConverter 的类,修改 to_pythonto_url 方法即可。to_python 方法用于将url中的变量转换后供被 @app.route 包装的函数使用,to_url 方法用于 flask.url_for 中的参数转换。

下面是一个示例,将 HelloWorld/index.py 修改如下:

from flask import Flask, url_for
from werkzeug.routing import BaseConverter

class MyIntConverter(BaseConverter):
	def __init__(self, url_map):
		super(MyIntConverter, self).__init__(url_map)

	def to_python(self, value):
		return int(value)

	def to_url(self, value):
		return 'hi'
app = Flask(__name__)
app.url_map.converters['my_int'] = MyIntConverter


@app.route('/page/<my_int:num>')
def page(num):
	print num
	print url_for('page', num='123')
	return 'hello world'

if __name__ == '__main__':
	app.run(debug=True)

浏览器访问 http://127.0.0.1:5000/page/123 后,HelloWorld/index.py 的输出信息是:

123 
/page/hi

url for

上面例子只能提到的 url_for 方法用于构建URL,他的使用方法如下

from flask import Flask, url_for
app = Flask(__name__)

@app.route('/')
def hello_world():
	pass

@app.route('/user/<name>')
def user(name):
	pass

@app.route('/page/<int:num>')
def page(num):
	pass

@app.route('/test')
def test():
	print url_for('hello_world')
	print url_for('user', name='einverne')
	print url_for('page', num=1, q='hadoop mapreduce 10%3')
	print url_for('static', filename='uploads/01.jpg')
	return ''

if __name__ == '__main__':
	app.run(debug=True)

输出

/
/user/einverne
/page/1?q=hadoop+mapreduce+10%253
/static//uploads/01.jpg

url for 后接方法名


2017-07-27 linux , flask , json , restful , web , python

Flask 使用介绍

Flask 是一个轻量级的基于 python 的 web 框架。

安装运行

一般情况下,只要通过 pip 安装 Flask 即可:

pip install Flask

进入 python shell

>>> import flask
>>> print flask.__doc__
	flask
	~~~~~
	A microframework based on Werkzeug.  It's extensively documented
	and follows best practice patterns.
	:copyright: (c) 2015 by Armin Ronacher.
	:license: BSD, see LICENSE for more details.
>>> print flask.__version__
0.12.2

创建 index.py 文件,在文件中放入以下内容:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'Hello World!'

if __name__ == '__main__':
	app.run()

运行 python index.py

python index.py
 * Running on http://127.0.0.1:5000/

变量 app 是一个 Flask 实例,当客户端访问根目录 / 时,将响应 hello_world() 函数返回的内容。这不是返回 Hello World! 这么简单,Hello World! 只是 HTTP 响应报文的实体部分,状态码等信息既可以由 Flask 自动处理,也可以通过编程来制定。

Tips

更改静态资源地址

在创建 Flask 时使用额外的参数,具体可参考 __doc__

显示调试信息

添加 run 函数参数:

app.run(debug=True)

绑定网络接口和端口

默认情况下,Flask 使用 127.0.0.1 ,端口为 5000,通过如下方式更改:

app.run(host='0.0.0.0', port=80, debug=True)

绑定 80 端口需要 root 权限运行 index.py

获取请求参数

from flask import Flask, request
app = Flask(__name__)

@app.route('/')
def hello_world():
	params = request.args.items()
	return params.__str__()

if __name__ == '__main__':
	app.run()

另外可以通过 request.full_pathrequest.path 来获取客户端请求 url

获取 POST 请求参数

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/register', methods=['POST'])
def register():
	print request.headers
	print request.form
	print request.form['name']
	print request.form.get('name')
	print request.form.getlist('name')
	print request.form.get('nickname', default='little apple')
	return 'welcome'

if __name__ == '__main__':
	app.run(debug=True)

使用 requests 模拟客户端请求:

import requests
user_info = {'name': 'einverne', 'password': '123'}
r = requests.post("http://127.0.0.1:5000/register", data=user_info)
print r.text

唯一 URL

构造访问 URL 时可能会遇到如下两种情况:

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

解释:

  • 第一种类似文件系统中访问文件夹,访问一个结尾不带斜线的 URL 会被 Flask 重定向到带斜线的规范 URL 去
  • 第二种类似 Unix-like 系统中的路径名,访问结尾带斜线的 URL 会产生一个 404 “Not Found” 错误。

RESTful

Flask 下有 Flask-Restless 结合 SQLAlchemy 轻松实现 RESTful 。

reference


2017-07-27 python , web , flask , django

Flask cookie and session

因为 HTTP 协议是无状态的,即服务器不知道用户上一次做了什么,这严重阻碍了交互式 Web 应用程序的实现,所以引入了 Cookie 和 Session。

session

from flask import Flask, render_template_string, \
	session, request, redirect, url_for
app = Flask(__name__)
app.secret_key = 'F12Zr47j\3yX R~X@H!jLwf/T'

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/login')
def login():
	page = '''
	<form action="\{\{ url_for('do_login') \}\}" method="post">
		<p>name: <input type="text" name="user_name" /></p>
		<input type="submit" value="Submit" />
	</form>
	'''
	return render_template_string(page)

@app.route('/do_login', methods=['POST'])
def do_login():
	name = request.form.get('user_name')
	session['user_name'] = name
	return 'success'

@app.route('/show')
def show():
	return session['user_name']

@app.route('/logout')
def logout():
	session.pop('user_name', None)
	return redirect(url_for('login'))

if __name__ == '__main__':
	app.run(debug=True)

设置 session 过期时间

from datetime import timedelta
from flask import session, app
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=5)
from flask import Flask, request, Response, make_response
import time
app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/add')
def login():
	res = Response('add cookies')
	res.set_cookie(key='name', value='letian', expires=time.time()+6*60)
	return res

@app.route('/show')
def show():
	return request.cookies.__str__()

@app.route('/del')
def del_cookie():
	res = Response('delete cookies')
	res.set_cookie('name', '', expires=0)
	# print res.headers
	# print res.data
	return res
if __name__ == '__main__':
	app.run(host='0.0.0.0', debug=True)

reference


2017-07-27 flask , web , python

北京租房所思所想

毕业面临人生第一次租房,而到现在从学校搬出来也已经有近一个月时间,期间工作有些繁忙,而有一些租房相关的笔记一直躺在WizNote中,直到今天回到家,才想到应该把我的经历记下来,提醒自己以后租房过程中避坑。

信息来源

现在的社会是一个信息为主的社会,我们经常挂在嘴边说的一句话就是信息不对称能够产生利益,现在社会很多的金钱交易都是基于信息的不对称。而租房信息获取的途径也会直接导致以后一年的住房条件和心情。下面就讲讲我参考的几个主要站点和App。

自如

首先就是自如,也是我周围同学使用最多的,大多数周围的同学都通过自如找到了房子,我也看过一些自如的房子,自如有如下特点

  • 自如的房子装修一般还不错,该有的家具设施都有,一般也比较干净
  • 自如需要多交一个月的服务费,和中介多交一个月也无太大差别,另水电自理
  • 双周的保洁还是让公共区域非常的干净,至少我看过的两个房子进门都非常干净也没有异味

缺点:

  • 但是自如的房子一般都比较”贵”,相对来讲
  • 住自如的房子就要抱着和室友相处的”危险”,当然我相信这个世界是美好的,同在一个屋檐下,在同一个城市打拼,本就应该相互尊重相互谦让一下,然而事实就是我身边确实发生了非常不愉快的事情,甚至严重到了报警,所以如果租自如的房子,请做好合租的思想准备。

107间

在同学QQ群中第一次听说,立马下载安装注册之后,需要缴纳100元作为认证费用,只有缴纳费用之后才能看到发布者联系方式。可是缴纳认证费用之后连续联系的两家都已经将房屋出租出去。不及时更新房屋信息,并且在缴纳认证费用的界面没有任何提示。如果30天内没有签约,费用不退。

总结下:

  • 房源相对较少
  • 房屋信息更新不及时,打电话联系客服竟然还是人工和房东进行确认,这都什么时候还用人工确认
  • 签约流程不明确,107间确实过滤了大部分中介,可带来的却是中间确认流程的缺失

强烈建议将该App 拉入黑名单。白白让我交了100块的认证。

链家

房源多,整租单间价格和市场几乎就是市场均价,但是环境看机会。

我爱我家

类似链家。

安居客

同样也是一个找租房的应用,不过看官网和他应用的主要内容似乎是一家经营房产,买卖房屋为主业的公司,租房只是他们其中的一个副业,当然也可以在上面找找周围合适的房屋信息,个人感觉比那个鬼 107 间要靠谱一些,和自如相同也是有“房管”来管理租赁一个范围内的房屋,和自如类似。可以参考,毕竟最后没有在安居客租房因此也不能下结论。

内网

如果你公司内网有租房信息,请一定要利用好,租房那天在外面跑了很多地方,顶着大太阳看了一个有一个房子,却只有在公司内网同事分享的一些房子靠谱一些。个人最后也在同学的推荐下找到了相对比较合适的房子。

58

其他 58,链家 ,豆瓣 之类,只是寥寥看了一下,并没有真正却联系,在看房的时候也了解到不乏有之前的房东在 58 上找到很好的房子,进而想要帮房东更快的租出房子而内网发布信息的。当然如果个人贸然出租请还是注意防骗。

关于合同的一切

这个社会的复杂性都在一直合同中变的扁平和简洁,而着又同样带来了合同的复杂性,合同越复杂带来的后续处理细节就越方便。我们找到房东直租,但房东是一位上了年龄的阿姨,合同也让我们自己带,网上,同学问了一圈,最后在住房和城市建设委员会的网站上找了一份租房合同模板 。但网站上给出的版本还是 08 年的版本,我们又找了一份 14 年的修订版 —- 北京市房屋租赁合同(自行成交版2014)

有了合同模板就好很多,按照上面的填空,填完也能够整个合同流程有一个大概的印象,应该注意的地方合同中大多都有提到,当然除了合同中提到的内容,涉及到金钱的地方要额外的注意。

  • 押一付三,每月金额,押金金额,开始日期结束日期,交房租日期。
  • 家具、空调、洗衣机等等电器的使用情况和损坏状况。
  • 关键的一点,水电费的读数,取暖费谁交。

2017-07-25 租房 , 合租 , 生活 , living

电子书

最近文章

  • Vim 插件之全局搜索:ack.vim 这篇文章看开始陆陆续续记录一下用过的 Vim Plugin,虽然有些一直也在用但从没有好好整理过,正好这篇开一个计划吧。
  • 我可能要抛弃用了很多年的 Chrome 换用 Vivaldi 大概一两年前就听说了 Vivaldi 这样一款浏览器,它使用 Chromium 做内核,创始人是从 Opera 浏览器出来的,不满 Chrome 横扫浏览器市场,没有给 Opera 一点喘息的机会,很多 Opera 的特性在 Chrome 上都被摘掉了。所以他们就开始搞了这一个 Vivaldi 浏览器,我记得当时听到的第一个宣传语是,快,想来 Chrome 最初打开市场时的宣传语也是快,但是多少年过去了 Chrome 虽然自身保持非常干净,但是加上自定义的扩展,也变得越来越臃肿了,但倒是也在一个可以接受的范围内。两年前 Vivaldi 依靠这一条宣传没有吸引到我,但是我也下载尝试,并且这两年来一直存在我的电脑中。我记得之前在我看来最大的缺点便是不能同步数据,而这个功能也在之前的更新中被加上了。所以今天就非常值得拿出来讲一讲。
  • 使用 port knocking 隐藏 SSH daemon 端口 暴露在互联网上的服务器非常容易被恶意程序进行端口扫描,以前也整理过一篇 VPS 安全设置 的文章,但都是一些比较基础的设置,能够绕过一些简单的端口扫描,但是并不能从根本上解决端口扫描的问题。
  • 威联通折腾篇十四:迁移系统盘 当时安装系统的时候就直接插入了一块硬盘,安装在了第一块机械硬盘上面,虽然读写也没有遇到什么瓶颈,但是记录以做备份,可以用于将系统迁移到 SSD 上。
  • Intellij IDEA 支持 jQuery 在 设置中进行如下操作开启 jQuery 支持: