#64 SQLAlchemy

2014-08-21

数据库连接:Engine

创建 engine 相当于通过适配层对接了原生数据库接口。当 excute 方法和 connect 方法首次调用时,Engine 建立了一个正式的 DBAPI 连接到数据库,之后的所有数据库交互都是通过这个连接发出。
使用 ORM 时,Engine 是个幕后工作者,也就是说一旦创建之后,我们一般不会再直接接触到 Engine。

from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True)  # echo => logging

连接字符串(DB URL)

会话(Session)

http://sqlalchemy.readthedocs.org/en/latest/orm/session_basics.html

Session 是一个基于指定数据库连接的一个工作区。文档上有这么一个例子:如果将一个应用程序线程当作一场派对中的一个来宾,会话就是这个来宾手上的盘子,数据就是盘子中的食物,数据库就是厨房。

何时创建,何时提交,何时关闭?

Session = sessionmaker(bind=engine)
# 等同于:
# Session = sessionmaker()
# Session.configure(bind=engine)
session = Session()

定义模型(数据库映射)

基类

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

模型的定义

from sqlalchemy import Column, Integer, String


class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    password = Column(String)

    def __repr__(self):
        return "<User(name='%s', fullna

表结构(Schema)

又出来一个幕后工作者:Mapper,通过 模型类.__table__ 访问。

print type(User.__table__)  # <class 'sqlalchemy.sql.schema.Table'>
print repr(User.__table__)
# Table('users', MetaData(bind=None),
#       Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
#       Column('name', String(), table=<users>),
#       Column('fullname', String(), table=<users>),
#       Column('password', String(), table=<users>), schema=None)

数据库操作

1. 添加

u = User(name='ed', fullname='Ed Jones', password='password')
session.add(u)

# 一次添加多条记录
session.add_all([
    User(name='wendy', fullname='Wendy Williams', password='foobar'),
    User(name='mary', fullname='Mary Contrary', password='p@55vv0rd'),
    User(name='fred', fullname='Fred Green', password='123456')
])
print session.new
print session.query(User).all()

2. 查找

2.1 查询对象 Query

for name, fullname in session.query(User.name, User.fullname):
    print name.ljust(10), fullname

for row in session.query(User, User.name):
    print row.User, row.name

直接遍历 Query 对象就相当于按照默认顺序获取所有对象。

2.2 条件:filte_by

qs = session.query(User).filter_by(name='ed')
users = qs.all()
our_user = qs.first()

2.3 更加灵活的 filter

允许使用 Python 表达式和类属性!

query = session.query(User.name)
queryset = query.filter(User.fullname == 'Ed Jones')
for name, in queryset:
    print name

通用 Filter 操作符

  • Equal
    query.filter(User.name == 'ed')
    query.filter(User.name != 'ed')
  • LIKE
    query.filter(User.name.like('%ed%'))
  • IN / NOT IN
    query.filter(User.name.in_(['ed', 'wendy', 'jack']))
    query.filter(User.name.in_(session.query(User.name).filter(User.name.like('%ed%'))))
    query.filter(~User.name.in_(['ed', 'wendy', 'jack']))
  • NULL
    query.filter(User.name == None)
    query.filter(User.name.is_(None))
    query.filter(User.name != None)
    query.filter(User.name.isnot(None))
  • AND
    query.filter(User.name == 'ed').filter(User.fullname == 'Ed Jones')
    query.filter(and_(User.name == 'ed', User.fullname == 'Ed Jones')) # from sqlalchemy import and_
  • OR
    query.filter(User.name == 'ed', User.fullname == 'Ed Jones')
    query.filter(or_(User.name == 'ed', User.name == 'wendy')) # from sqlalchemy import or_
  • MATCH:
    query.filter(User.name.match('wendy'))
    注意:match 使用了数据库相关的内容,可能在不同数据库后端上有不同的效果,而且部分数据库不支持。

2.4 排序:order_by

for instance in session.query(User).order_by(User.id):
    print instance.name.ljust(10), instance.fullname

2.5 别名

label = User.name.label('name_label')
for row in session.query(label).all():
    print(row.name_label)

from sqlalchemy.orm import aliased
user_alias = aliased(User, name='user_alias')
for row in session.query(user_alias, user_alias.name).all():
    print row.user_alias

2.6 LIMIT 和 OFFSET

和 Django 一样,使用切片实现。

2.7 其他方法

  • .all() 返回查找到的所有纪录组成的列表。
  • .first() 返回查找到的第一条记录,没有找到返回 None。
  • .one() 只查找到一条记录时,返回该条记录。否则抛出异常:
    sqlalchemy.orm.exc.NoResultFound: No row was found for one()
    sqlalchemy.orm.exc.MultipleResultsFound: Multiple rows were found for one()
  • .scalar().one() 方法模拟出 .first() 方法的效果。不同的是,.first() 方法只获取那一条(LIMIT 1),而调用 .one() 方法却获取了所有记录,不过只返回了第一条。这是不一样的!

2.8 对原生 SQL 的支持

  • text
  • from_statement
  • params 为 Query 增加参数
from sqlalchemy import text

# 例 1,简单用法
for user in session.query(User).filter(text("id<224")).order_by(text("id")).all():
    print user.name

# 例 2,定义参数
session.query(User).filter(text("id<:value and name=:name")).params(value=224, name='fred').order_by(User.id).one()

# 例 3,此时 Query 几乎只检测字段是否合法~
sql = text("SELECT * FROM users where name=:name")
session.query(User).from_statement(sql).params(name='ed').all()
session.query("id", "name").from_statement(sql).params(name='ed').all()

3. 更新

u = session.query(User).filter_by(name='fred')[0]
u.password = '12345678'
session.commit()
print u in session
u = session.query(User).filter_by(name='fred')[0]
print u.password

4. 删除

jack = session.query(User).filter_by(name='jack').one()
session.delete(jack)
session.query(User).filter_by(name='jack').count()

5. Count

session.query(User).filter(User.name.like('%ed')).count()
from sqlalchemy import func
session.query(func.count(User.name), User.name).group_by(User.name).all()
session.query(func.count('*')).select_from(User).scalar()
session.query(func.count(User.id)).scalar()

6. 事务:commit 和 rollback

commit

u = session.query(User).all()[-1]
u.password = '123456'
print session.dirty  # IdentitySet
# 此时 add_all 的新增操作实际上还没有提交
# session.commit()  # 提交到数据库

rollback

u = session.query(User).all()[-1]
u.password = '123456'
session.rollback()  # 撤销更新
print session.dirty
print 'After Rollback, PWD:'
print u.password  # 会先查找一遍~

关系

from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref

class Address(Base):
    __tablename__ = 'addresses'

    id = Column(Integer, primary_key=True)
    email_address = Column(String, nullable=False)
    user_id = Column(Integer, ForeignKey('users.id'))

    user = relationship("User", backref=backref('addresses', order_by=id))

    def __repr__(self):
        return "<Address(email_address='%s')>" % self.email_address

# 或者到 User 中声明,如下:
# class User(Base):
#     addresses = relationship("Address", order_by="Address.id", backref="user")

关联操作

jack = User(name='jack', fullname='Jack Bean', password='qwerty')
print jack.addresses
jack.addresses = [
    Address(email_address='jack@google.com'),
    Address(email_address='j25@yahoo.com'),
]
print jack.addresses[1]
print jack.addresses[1].user
session.add(jack)
print '-' * 70
session.commit()  # 也会插入 Address 信息
print '-' * 70
jack = session.query(User).filter_by(name='jack').one()
print jack
print '-' * 70
print jack.addresses   # lazy loading

JOIN

for u, a in session.query(User, Address).\
        filter(User.id==Address.user_id).\
        filter(Address.email_address=='jack@google.com').\
        all():
    print u
    print a
# <User(name='jack', fullname='Jack Bean', password='gjffdd')>
# <Address(email_address='jack@google.com')>

Join 也可以如下表示:session.query(User).join(Address)

如果两张表没有声明的关联,或者有多个关联,最好使用下面的形式:

query.join(Address, User.id==Address.user_id)  # explicit condition
query.join(User.addresses)  # specify relationship from left to right
query.join(Address, User.addresses)  # same, with explicit target
query.join('addresses')  # same, using a string

默认是 INNER JOIN:

session.query(User).join(Address). \
    filter(Address.email_address == 'jack@google.com'). \
    all()
# SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
# FROM users
# JOIN addresses ON users.id = addresses.user_id
# WHERE addresses.email_address = 'jack@google.com'

使用其他连接方式:

query.outerjoin(User.addresses)   # LEFT OUTER JOIN

或者使用 join 方法的其他参数,具体信息,在后面章节会涉及。

aliased 又出现了

from sqlalchemy.orm import aliased
adalias1 = aliased(Address)
adalias2 = aliased(Address)
for username, email1, email2 in \
    session.query(User.name, adalias1.email_address, adalias2.email_address).\
    join(adalias1, User.addresses).\
    join(adalias2, User.addresses).\
    filter(adalias1.email_address=='jack@google.com').\
    filter(adalias2.email_address=='j25@yahoo.com'):
    print username, email1, email2
# jack jack@google.com j25@yahoo.com

子查询

from sqlalchemy.sql import func
stmt = session.query(Address.user_id, func.count('*').label('address_count')).\
    group_by(Address.user_id).subquery()
for u, count in session.query(User, stmt.c.address_count).\
        outerjoin(stmt, User.id==stmt.c.user_id).\
        order_by(User.id):
    print u, count

使用子查询

stmt = session.query(Address).\
    filter(Address.email_address != 'j25@yahoo.com').subquery()
adalias = aliased(Address, stmt)
for user, address in session.query(User, adalias).\
        join(adalias, User.addresses):
    print user
    print address

EXIST

from sqlalchemy.sql import exists
stmt = exists().where(Address.user_id==User.id)
for name, in session.query(User.name).filter(stmt):
    print name

for name, in session.query(User.name).filter(User.addresses.any()):
    print name

for name, in session.query(User.name).\
    filter(User.addresses.any(Address.email_address.like('%google%'))):
    print name

session.query(Address).filter(~Address.user.has(User.name=='jack')).all()

其他方法

query.filter(User.addresses.contains(someaddress))
query.filter(User.addresses.any(Address.email_address == 'bar'))
query.filter(User.addresses.any(email_address='bar'))
query.filter(Address.user.has(name='ed'))
session.query(Address).with_parent(someuser, 'addresses')

预加载

Eager Loading 直译过来,表示立即载入。

http://sqlalchemy.readthedocs.org/en/latest/orm/loading_relationships.html

默认是 Lazy Loading。

1. subqueryload

from sqlalchemy.orm import subqueryload
jack = session.query(User).\
    options(subqueryload(User.addresses)).\
    filter_by(name='jack').one()

subqueryload() when used in conjunction with limiting such as Query.first(), Query.limit() or Query.offset() should also include Query.order_by() on a unique column in order to ensure correct results.

2. joinedload

默认 LEFT OUTER JOIN。

from sqlalchemy.orm import joinedload
jack = session.query(User).\
    options(joinedload(User.addresses)).\
    filter_by(name='jack').one()

joinedload() is not a replacement for join()
The join created by joinedload() is anonymously aliased such that it does not affect the query results. An Query.order_by() or Query.filter() call cannot reference these aliased tables - so-called “user space” joins are constructed using Query.join(). The rationale for this is that joinedload() is only applied in order to affect how related objects or collections are loaded as an optimizing detail - it can be added or removed with no impact on actual results. See the section The Zen of Eager Loading for a detailed description of how this is used.

3. Join + Eagerload

from sqlalchemy.orm import contains_eager
jacks_addresses = session.query(Address).\
    join(Address.user).\
    filter(User.name=='jack').\
    options(contains_eager(Address.user)).\
    all()

关联对象的删除

回到曾经的示例——删除 Jack 上来,如果 Jack 有几个地址,实际上 Jack 删除之后那几个地址的用户主键会被设置成 NULL,但是并没有删除。

#63 MySQL set 类型

2014-08-20

顾名思义,就是集合类型。

set('a', 'b', ...)

每个字段可以是指定选项中的若干个(包含 0 个)。

#61 RabbitMQ & AMQP

2014-07-11

AMQP

Advanced Message Queuing Protocol, 高级消息队列协议

久负盛名的投资公司摩根大通(JPMorgan Chase)在 2005 年前后设计了 AMQP,并和红帽一同采用 Java 实现了这个协议(没过多久就改用 C++ 重构了一遍),这就是后来的 Apache Qpid。后来的一些消息队列也都支持 AMQP 协议,比如 RabbitMQ(采用 Erlang 开发)、Apache ActiveMQ(Java)。

其他常见的 MQ 协议还有:STOMP 1, MQTT 2,有时也会和 XMPP 3 做对比。

  • 2006/06 版本 0-8
  • 2006/12 版本 0-9
  • 2008/11 版本 0-9-1 RabbitMQ实现
  • 2011/10 版本 1.0
  • AMQP 移交给 OASIS 组织之后发布的第一个版本
  • 2014/04 该版本成为 ISO 国际标准。

基本概念

AMQP Arch

  • Message
  • DeliveryTag 这个标记十分重要
  • Properties
  • Header
  • Body
  • Content Type
  • Content Encoding
  • Message Queue
  • Message Broker
  • Message-Oriented Middleware 消息中间件,有时简写 MOM
  • Connection TCP 连接, 服务器永远不会主动关闭连接
  • Channel 通道,或者叫信道,逻辑连接,不同通道之间是完全隔离的 (ChannelID)
  • 通道的打开关闭
  • 多线程可以使用各自的通道
  • Server 消息队列服务器,就是指 Broker
  • Virtual Host 虚拟主机,消息队列中的逻辑隔离
  • Publisher 消息生产者
  • Exchange 交换机
  • Queue 队列
  • Binding 绑定,指定了队列和交换机之间的关系
  • RoutingKey 路由键,Binding 的附加参数,对消息进行过滤
    注意:有些地方将绑定时指定的 RoutingKey 叫做 BindingKey
  • Comsumer 消息消费者

基本流程

AMQP 协议中,基本数据单位是帧。有 9 种帧结构用来开启、控制、关闭两点之间的信息传输链路:

  1. 打开(连接)open
  2. 开始(会话)begin
  3. 附加(链路)attach
  4. 传输 transfer
  5. 流量控制 flow
  6. 状态通信 disposition
  7. 分离(链路)detach
  8. 结束(会话)end
  9. 关闭(连接)close

连接 Conection,会话 Session,链路 Link。
链路是单向的数据传输通道,消息(Transfer 帧)就在链路上传输。

+ OpenConnection
|   + StartSession
|   |   + AttachLink
|   |   |     Transfer
|   |   |     Flow
|   |   |     Disposition
|   |   + DetachLink
|   + EndSession
+ CloseConnection
  1. 定义 Exchange、Queue、Binding
  2. 生产者将消息投递给 Exchange
  3. Exchange 根据实现定义的路由策略(Binding)将消息转发到 Queue
  4. 消费者从 Queue 中拿到消息

RabbitMQ

实现了 AMQP 0-9-1。

端口

ps -ef | grep rabbitmq | grep -v grep
rabbitmq  966640       1  5 11:09 ?        00:00:12 /usr/lib/erlang/erts-11.1.8/bin/beam.smp -W w -K true -A 64 -MBas ageffcbf -MHas ageffcbf -MBlmbcs 512 -MHlmbcs 512 -MMmcs 30 -P 1048576 -t 5000000 -stbt db -zdbbl 128000 -- -root /usr/lib/erlang -progname erl -- -home /var/lib/rabbitmq -- -pa  -noshell -noinput -s rabbit boot -boot start_sasl -lager crash_log false -lager handlers []
rabbitmq  966744  966640  0 11:09 ?        00:00:00 erl_child_setup 65536
rabbitmq  966775       1  0 11:09 ?        00:00:00 /usr/lib/erlang/erts-11.1.8/bin/epmd -daemon
rabbitmq  966802  966744  0 11:09 ?        00:00:00 inet_gethost 4
rabbitmq  966803  966802  0 11:09 ?        00:00:00 inet_gethost 4

sudo nmap -p 1-65535 localhost
4369/tcp  open  epmd
5672/tcp  open  amqp
15672/tcp open  unknown
25672/tcp open  unknown
  • 4369 Erlang 端口映射器守护程序 (epmd)
  • 5672 服务端口
  • 15672 Web 接口
  • 25672 不知道干嘛的,来自 inet_dist_listen_min - inet_dist_listen_max, 我本机默认配置

Exchange 类型

AMQP 中定义的 4 种 Exchange 类型:

  1. direct 需要符合 RoutingKey 的完全匹配
  2. topic 支持模糊匹配:# 代表任意个单词,* 代表一个单词
  3. RoutingKey 应该是小数点隔开的单词,另外,不可超过 255 字节。
  4. BindingKey 可以包含上面说的模糊匹配字符。
  5. fanout 将消息广播给所有绑定到该 Exchange 的 Queue,忽略 RoutingKey
  6. headers 采用消息的 Header 与 Binding 的属性来进行匹配,忽略 RoutingKey
  7. Binding 中 x- 开头的属性不会参与匹配。
  8. Binding 可以定义 x-match 属性,any (默认值) 表示匹配中一个字段就行,all 表示所有字段都匹配。
  9. 奇怪的是官网教程中没有这种类型的示例。

预置 Exchange:

  • (AMQP default) direct
  • amq.direct direct
  • amq.fanout fanout
  • amq.headers headers
  • amq.match headers
  • amq.rabbitmq.log topic
  • amq.rabbitmq.trace topic
  • amq.topic topic

默认 Exchange (AMQP default) 有点特殊,所有的队列都自动绑定在上面,然后又是 direct 类型,这样一来,生产者 publish 时如果将消息投递到名字为空字符串的这个 Exchange,RoutingKey 填写队列名字,就可以直接将消息投递到队列中。

确认机制

  1. 来自 TCP 的启发。
  2. 分成消费者确认和生产者确认两部分。
  3. DeliveryTag 对于确认机制至关重要。

具体下来就是:

  • Basic.Ack basic_ack(delivery_tag=0, multiple=False) 可以一次确认多个消息
  • Basic.Nack basic_nack(delivery_tag=None, multiple=False, requeue=True)
  • Basic.Reject basic.reject(delivery_tag=None, requeue=True)

Nack 是 RabbitMQ 对 AMQP 的拓展,和 Reject 不同的是,Nack 可以一次拒绝该通道所有没有 ACK 的消息。

关于自动确认模式

命令

Connection

  1. Connection.Start
  2. Connection.StartOk
  3. Connection.Secure
  4. Connection.SecureOk
  5. Connection.Tune
  6. Connection.TuneOk
  7. Connection.Open
  8. Connection.OpenOk
  9. Connection.Close
  10. Connection.CloseOk
  11. Connection.Blocked
  12. Connection.Unblocked

Channel

  1. Channel.Open
  2. Channel.OpenOk
  3. Channel.Flow
  4. Channel.FlowOk
  5. Channel.Close
  6. Channel.CloseOk

Access

  1. Access.Request
  2. Access.RequestOk

Exchange 交换器

  1. Exchange.Declare
  2. Exchange.DeclareOk
  3. Exchange.Delete
  4. Exchange.DeleteOk
  5. Exchange.Bind 交换器绑定 (RabbitMQ 拓展)
  6. Exchange.BindOk
  7. Exchange.Unbind 交换器解绑 (RabbitMQ 拓展)
  8. Exchange.UnbindOk

Queue 队列

  1. Queue.Declare 队列声明
  2. Queue.DeclareOk
  3. Queue.Bind 队列绑定 (到交换器)
  4. Queue.BindOk
  5. Queue.Purge 队列清空 (没分配的,也就是 unack 不会被清空)
  6. Queue.PurgeOk
  7. Queue.Delete 队列删除
  8. Queue.DeleteOk
  9. Queue.Unbind 队列解绑
  10. Queue.UnbindOk

Basic

  1. Basic.Qos Quality of Service 设置:
  2. prefetch_size
  3. prefetch_count
  4. Basic.QosOk
  5. Basic.Consume 消费者开始消费
  6. Basic.ConsumeOk
  7. Basic.Cancel 取消消费者订阅
  8. Basic.CancelOk
  9. Basic.Publish 发布消息
  10. Basic.Return
  11. Basic.Deliver
  12. Basic.Get 直接从指定队列获取消息
  13. Basic.GetOk
  14. Basic.GetEmpty
  15. Basic.Ack 确认
  16. Basic.Reject
  17. Basic.RecoverAsync
  18. Basic.Recover
  19. Basic.RecoverOk
  20. Basic.Nack 负确认 (RabbitMQ 拓展)

Tx 事务

  1. Tx.Select
  2. Tx.SelectOk
  3. Tx.Commit
  4. Tx.CommitOk
  5. Tx.Rollback
  6. Tx.RollbackOk

Confirm

  1. Confirm.Select
  2. Confirm.SelectOk

话题 1:持久化

  1. 交换机持久化 durable=true
  2. 队列持久化 durable=true
  3. 绑定持久化
  4. 消息持久化 properties=pika.BasicProperties(delivery_mode = 2)
    basic_publish(exchange, routing_key, body, properties=None,
                  mandatory=False, immediate=False)
    

话题 2:可靠性的保障

  1. 事务
  2. 持久化
  3. 确认模式

参考资料与拓展阅读


  1. Streaming Text Oriented Messaging Protocol 

  2. 曾经是 Message Queuing Telemetry Transport 的简称, 后来不代表任何意义了
    img 

  3. Extensible Messaging and Presence Protocol, 一种通讯协议 

#60 OSI 参考模型

2014-04-25

OSI: Open System Interconnect,开放系统互联

OSI

OSI vs TCP/IP

img

应用层 Application Layer

表示层 Presentation Layer

按照 OSI 的设计, 应用层的数据可以在这一层经过编码转换,压缩,加密。

会话层 Session Layer

传输层 Transport Layer

端口 Port。

  • Packet segmentation (数据包分段)
  • Datagram (数据报)

网络层 Network Layer

IP 地址。

PDU (协议数据单元): Packet (包)

数据链路层 Data link layer

PDU (协议数据单元): Frame (帧)

子层:逻辑链路控制层 LLC

Logical Link Control

子层:媒体访问控制层 MAC

Media Access Control

  1. CSMA/CD
    Carrier sense multiple access with collision detection
  2. 数据校验
  3. 打包成帧

物理层 Physical layer

PDU (协议数据单元): Bit

比特转换为物理层的信号, 比如无线电信号、光纤信号、载波信号等。

子层:物理信号层 Physical signaling

#59 MySQL 时间

2014-03-06

几种形式

  1. 字符串,比如:
  2. varchar(14),存:yyyymmddHHMMSS 格式
  3. varchar(19),存:yyyy-mm-dd HH:MM:SS 格式
  4. varchar(23),存:yyyy-mm-dd HH:MM:SS.fff 格式
  5. varchar(26),存:yyyy-mm-dd HH:MM:SS.ffffff 格式
  6. varchar(24),存:yyyy-mm-ddTHH:MM:SS+0800yyyy-mm-dd HH:MM:SS 0800 格式
  7. 整形数
  8. int 1970 - 2038
    time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime((1 << 31) - 1))
    # 2038-01-19 03:14:07
    
    - int unsigned 1970 - 2106
    time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime((1 << 32) - 1))
    # 2106-02-07 06:28:15
    
    - bigint 可以用到世界毁灭
  9. 注意:也可以任意指定一个时间为起点,比如 2020-01-01 00:00:00
  10. TIMESTAMP 类型
  11. 相当于上面的 int 类型,2038 问题
  12. DATETIME 类型

PS: DATE, TIME, YEAR 三种类型:

  • DATE
  • TIME
  • YEAR

比较

1. 存储效率

2. 时间比较

3. 是否方便索引

#58 MAC 地址

2014-03-05

MAC 地址的正式名称应该是 Media Access Control Address, 翻译过来就是 “媒体接入控制层地址”。

#57 MySQL Timestamp

2014-03-03

优点

Timestamp 是按 UTC 时间进行存储。

2038 问题

img

MySQL 的 timestamp 类型挺好的,不过有个严重的 2038 问题,我不知道到时候这个类型会如何处理。

The TIMESTAMP data type is used for values that contain both date and time parts. TIMESTAMP has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.

(1 << 31) / (3600 * 24 * 365)

根据时间范围,这应该是用的一个有符号的 32 位整型数实现的,为什么不用无符号数呢,不是又可以往后续 68 年么?

我预测,MySQL 的某一个新版本,将会增加一个 TIMESTAMP64 类型,采用 64 位有符号整形数存时间戳。
或直接将 TIMESTAMP 拓展到 64 位,先支持配置和命令行参数开启这个特性,然后后续版本将其视作默认设置。

参考资料与拓展阅读

#56 WSGI

2014-03-01
  • https://peps.python.org/pep-3333/
  • http://wsgi.tutorial.codepoint.net/
  • https://wsgi.readthedocs.io/en/latest/index.html
  • https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface

The Web Server Gateway Interface (WSGI, pronounced whiskey or WIZ-ghee) is a simple calling convention for web servers to forward requests to web applications or frameworks written in the Python programming language.
The current version of WSGI, version 1.0.1, is specified in Python Enhancement Proposal (PEP) 3333.
Web 服务器网关接口(WSGI,发音为威士忌或 WIZ-ghee)是一种简单的调用约定,用于将请求转发到用 Python 编写的 Web 应用程序或框架的 Web 服务器。
当前版本的 WSGI,即 1.0.1 版本,由 Python 增强提案(PEP)3333 指定。

WSGI was originally specified as PEP-333 in 2003.
PEP-3333, published in 2010, updates the specification for Python 3.
WSGI 最初是在 2003 年的 PEP-333 中指定的。
2010 年发布的 PEP-3333 更新了 Python 3 的规范。

Background

In 2003, Python web frameworks were typically written against only CGI, FastCGI, mod_python, or some other custom API of a specific web server. To quote PEP 333:
2003 年,Python Web 框架通常只针对 CGI、FastCGI、mod_python 或某些特定 Web 服务器的其他自定义 API 编写。引用 PEP 333 的话:

Python currently boasts a wide variety of web application frameworks, such as Zope, Quixote, Webware, SkunkWeb, PSO, and Twisted Web -- to name just a few. This wide variety of choices can be a problem for new Python users, because generally speaking, their choice of web framework will limit their choice of usable web servers, and vice versa... By contrast, although Java has just as many web application frameworks available, Java's "servlet" API makes it possible for applications written with any Java web application framework to run in any web server that supports the servlet API.
Python 目前拥有众多 Web 应用程序框架,例如 Zope、Quixote、Webware、SkunkWeb、PSO 和 Twisted Web 等等。这种广泛的选择可能会成为新 Python 用户的问题,因为一般来说,他们选择的 Web 框架将限制他们可用的 Web 服务器的选择,反之亦然……相比之下,尽管 Java 也拥有同样多的 Web 应用程序框架,但 Java 的“servlet”API 使得使用任何 Java Web 应用程序框架编写的应用程序都能在支持 servlet API 的任何 Web 服务器中运行。

WSGI was thus created as an implementation-neutral interface between web servers and web applications or frameworks to promote common ground for portable web application development.
因此,WSGI 被创建为实现中立的 Web 服务器和 Web 应用程序或框架之间的接口,以促进可移植 Web 应用程序的开发和共同基础。

Specification overview

The WSGI has two sides:
WSGI 有两个方面:

  • the server/gateway side. This is often running full web server software such as Apache or Nginx, or is a lightweight application server that can communicate with a webserver, such as flup.
    服务器/网关方面。这通常运行完整的 Web 服务器软件,例如 Apache 或 Nginx,或者是可以与 Web 服务器通信的轻量级应用程序服务器,例如 flup。
  • the application/framework side. This is a Python callable, supplied by the Python program or framework.
    应用程序/框架方面。这是由 Python 程序或框架提供的 Python 可调用对象。

Between the server and the application, there may be one or more WSGI middleware components, which implement both sides of the API, typically in Python code.
在服务器和应用程序之间,可能会有一个或多个WSGI 中间件组件,它们在 Python 代码中实现了 API 的两个方面。

WSGI does not specify how the Python interpreter should be started, nor how the application object should be loaded or configured, and different frameworks and webservers achieve this in different ways.
WSGI 不指定 Python 解释器应如何启动,也不指定应用程序对象如何加载或配置,不同的框架和 Web 服务器以不同的方式实现这些功能。

WSGI middleware

A WSGI middleware component is a Python callable that is itself a WSGI application, but may handle requests by delegating to other WSGI applications. These applications can themselves be WSGI middleware components.
一个 WSGI 中间件组件是一个 Python 可调用对象,它本身是一个 WSGI 应用程序,但可以通过委托给其他 WSGI 应用程序来处理请求。这些应用程序本身可以是 WSGI 中间件组件。

A middleware component can perform such functions as:
中间件组件可以执行以下功能:

  • Routing a request to different application objects based on the target URL, after changing the environment variables accordingly.
    根据目标 URL 将请求路由到不同的应用程序对象,同时相应地更改环境变量。
  • Allowing multiple applications or frameworks to run side-by-side in the same process
    允许多个应用程序或框架在同一进程中并行运行。
  • Load balancing and remote processing, by forwarding requests and responses over a network
    负载均衡和远程处理,通过网络转发请求和响应。
  • Performing content post-processing, such as applying XSLT stylesheets
    执行内容后处理,例如应用 XSLT 样式表。

Examples

Example application

A WSGI-compatible "Hello, World!" application written in Python:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield b'Hello, World!\n'

Where:

  • Line 1 defines a function named application, which takes two parameters, environ and start_response. environ is a dictionary containing CGI environment variables as well as other request parameters and metadata under well-defined keys. start_response is a callable itself, taking two positional parameters, status and response_headers.
    第 1 行定义了一个名为 application 的函数,它接受两个参数 environ 和 start_response。environ 是一个包含 CGI 环境变量以及其他请求参数和元数据的字典,这些参数和元数据在定义良好的键下。start_response 本身是一个可调用对象,接受两个位置参数,status 和 response_headers。
  • Line 2 calls start_response, specifying "200 OK" as the HTTP status and a "Content-Type" response header.
    第 2 行调用 start_response,指定 "200 OK" 作为 HTTP 状态和 "Content-Type" 响应头。
  • Line 3 makes the function into a generator. The body of the response is returned as an iterable of byte strings.
    第 3 行将函数转换为生成器。响应正文被返回为一个字节串的可迭代对象。

Example of calling an application

A full example of a WSGI network server is outside the scope of this article. Below is a sketch of how one would call a WSGI application and retrieve its HTTP status line, response headers, and response body, as Python objects. Details of how to construct the environ dict have been omitted.
完整的 WSGI 网络服务器示例不再本文范围之内。以下只是一个 Demo,说明如何调用 WSGI 应用程序并检索其 HTTP 状态行、响应头和响应正文,作为 Python 对象。如何构造 environ 字典的详细信息已被省略。

from io import BytesIO

def call_application(app, environ):
    status = None
    headers = None
    body = BytesIO()

    def start_response(rstatus, rheaders):
        nonlocal status, headers
        status, headers = rstatus, rheaders

    app_iter = app(environ, start_response)
    try:
        for data in app_iter:
            assert status is not None and headers is not None, \
                "start_response() was not called"
            body.write(data)
    finally:
        if hasattr(app_iter, 'close'):
            app_iter.close()
    return status, headers, body.getvalue()

environ = {...}  # "environ" dict
status, headers, body = call_application(app, environ)

PEP 3333

PEP 3333 引入了一些新特性和更严格的规范,以提高 Python Web 应用程序的可移植性和互操作性。其中一些变化包括:

  • 强制要求 WSGI 服务器和应用程序使用相同的字符编码。
  • 确保应用程序能够正确处理 HTTP 头部中的 Unicode 值。
  • 对异常处理进行了更好的规范,以允许更好的错误处理和调试。
  • 更好地定义了 WSGI 环境变量,以提供更一致的行为。

此外,PEP 3333 还引入了一些新的建议,包括使用可选的服务器和客户端请求信息,提供更好的错误处理和日志记录,以及更好地处理请求和响应的二进制数据。

#55 Linux 权限管理总结

2014-02-27

文件类型

  • -, 普通文件
  • l, 链接文件
  • d, 目录
  • c, 字符设备文件
  • b, 块设备文件
  • p, 管道文件
  • s, Socket
  • t, 特殊文件

基础

  • 权限类型:
  • r,读 4
  • w,写 2
  • x,可执行 1
  • 用户类型:
  • user, 文件所有者
  • group, 文件所有组的所有用户
  • other,其他用户

目录的权限

  • r: 列出目录下的文件(ls
  • w: 在目录中,创建、删除、重命名文件或子目录
  • x: 进入目录(cd

示例

touch /tmp/testPerm

chmod 7777 /tmp/testPerm

ll /tmp/testPerm
-rwsrwsrwt 1 markjour markjour 0 2014-02-27 17:41:06 /tmp/testPerm

Linux 权限的两种表示方法:

  • 数字:
  • 字母:
    第一位表示文件类型
    第 2 - 4 位表示文件所有者的权限
    第 5 - 7 位表示文件所有组的权限
    第 8 - 10 位表示其他用户的权限

特殊权限

Linux 中的权限一共用 12 位来表示,除了上面 u g o 各 3 位(rwx)一共 9 位之外,还有 3 位:

  • setuid / suid, 4,显示为 S(显示在所有者权限中的第三位,如果有 x 的话,就显示 s)
  • setgid / sgid,2,显示为 S(显示在所有组权限中的第三位,如果有 x 的话,就显示 s)
  • sticky bit ,1,显示为 T

  • suid, sgid 是用来给文件设置临时提权用的。如果设置, 所有用户都会继承文件所有者或所有组的权限。

  • 如果目录设置了 sgid,那么该目录中新创建的文件和子目录会继承目录的所在组。
  • sticky bit 一般翻译为粘滞位,设置在目录上, 仅允许文件所有者(或 root)才能删除改目录下的文件。
    Linux 或 FreeBSD 都会忽略文件上的 sticky bit。
  suid sgid sbit
文件
目录
$ ls -alh /bin/ | grep rws
-rwsr-sr-x  1 daemon  daemon      55K 2022-04-14 08:23:36 at
-rwsr-xr-x  1 root    root        72K 2022-11-24 20:05:18 chfn
-rwsr-xr-x  1 root    root        44K 2022-11-24 20:05:18 chsh
-rwsr-xr-x  1 root    root        35K 2022-03-23 21:53:14 fusermount3
-rwsr-xr-x  1 root    root        71K 2022-11-24 20:05:18 gpasswd
-rwsr-xr-x  1 root    root        47K 2023-04-07 06:21:06 ksu
-rwsr-xr-x  1 root    root        47K 2022-02-21 09:49:57 mount
-rwsr-xr-x  1 root    root        28K 2022-11-24 20:05:18 newgidmap
-rwsr-xr-x  1 root    root        40K 2022-11-24 20:05:18 newgrp
-rwsr-xr-x  1 root    root        28K 2022-11-24 20:05:18 newuidmap
-rwsr-xr-x  1 root    root        31K 2022-02-07 21:05:06 nvidia-modprobe
-rwsr-xr-x  1 root    root        59K 2022-11-24 20:05:18 passwd
-rwsr-xr-x  1 root    root        31K 2022-02-26 19:11:57 pkexec
-rwsr-xr-x  1 root    root        55K 2022-02-21 09:49:57 su
-rwsr-xr-x  1 root    root       227K 2023-04-04 02:00:44 sudo
-rwsr-xr-x  1 root    root        35K 2022-02-21 09:49:57 umount
$ find /bin/ -type f -perm /4000 -ls

  7342874     36 -rwsr-xr-x   1 root     root        35200 Mar 23  2022 /bin/fusermount3
  7352563     28 -rwsr-xr-x   1 root     root        28136 Nov 24  2022 /bin/newuidmap
  7341794     32 -rwsr-xr-x   1 root     root        30872 Feb 26  2022 /bin/pkexec
  7347918     36 -rwsr-xr-x   1 root     root        35192 Feb 21  2022 /bin/umount
  7347747     48 -rwsr-xr-x   1 root     root        47480 Feb 21  2022 /bin/mount
  7344663     28 -rwsr-xr-x   1 root     root        28136 Nov 24  2022 /bin/newgidmap
  7349993     72 -rwsr-xr-x   1 root     root        72072 Nov 24  2022 /bin/gpasswd
  7350008     60 -rwsr-xr-x   1 root     root        59976 Nov 24  2022 /bin/passwd
  7347439    228 -rwsr-xr-x   1 root     root       232416 Apr  4 02:00 /bin/sudo
  7344105     32 -rwsr-xr-x   1 root     root        30936 Feb  7  2022 /bin/nvidia-modprobe
  7345135     56 -rwsr-xr-x   1 root     root        55672 Feb 21  2022 /bin/su
  7347241     72 -rwsr-xr-x   1 root     root        72712 Nov 24  2022 /bin/chfn
  7352704     40 -rwsr-xr-x   1 root     root        40496 Nov 24  2022 /bin/newgrp
  7347300     44 -rwsr-xr-x   1 root     root        44808 Nov 24  2022 /bin/chsh
  7382902     48 -rwsr-xr-x   1 root     root        47416 Apr  7 06:21 /bin/ksu
  7343017     56 -rwsr-sr-x   1 daemon   daemon      55624 Apr 14  2022 /bin/at

$ find /bin/ -type f -perm /2000 -ls

  7342246     16 -rwxr-sr-x   1 root     root        15504 Jan 12  2022 /bin/dotlock.mailutils
  7347219     72 -rwxr-sr-x   1 root     shadow      72184 Nov 24  2022 /bin/chage
  7346616     24 -rwxr-sr-x   1 root     tty         22912 Feb 21  2022 /bin/write.ul
  7340649    308 -rwxr-sr-x   1 root     plocate    313904 Feb 17  2022 /bin/plocate
  7341420     40 -rwxr-sr-x   1 root     crontab     39568 Mar 23  2022 /bin/crontab
  7349934     24 -rwxr-sr-x   1 root     shadow      23136 Nov 24  2022 /bin/expiry
  7347279     24 -rwxr-sr-x   1 root     tty         22904 Feb 21  2022 /bin/wall
  7345047    288 -rwxr-sr-x   1 root     _ssh       293304 Nov 23  2022 /bin/ssh-agent
  7343017     56 -rwsr-sr-x   1 daemon   daemon      55624 Apr 14  2022 /bin/at

https://en.wikipedia.org/wiki/Setuid
https://en.wikipedia.org/wiki/Sticky_bit
https://web.archive.org/web/20130204053849/http://content.hccfl.edu/pollock/aunix1/filepermissions.htm

操作

  • chown 更改文件所有者和文件所有组
  • chgrp 更改文件所有组
  • chmod 更改权限
chmod 755 filePath
chmod 644 filePath
chmod u+x filePath
chmod g+r filePath
chmod o+w filePath
chmod a+r filePath