计算广告

名词速查

  • ROI: Return On Investment,投入产出比,即某次广告活动的总产出与总投入的比例。
  • EDM: E-mail Direct Marketing,邮件定向营销广告,即通过电子邮件的方式向目标用户传递推广信息的一种网络手段。
  • GD: Guaranteed Delivery,担保式投送,即媒体向广告主保证某个投放量,并在此基础上确定合同的总金额以及投放量未完成情况下的赔偿方案。
  • CPM: Cost Per Mille,千次展示付费。
  • GSP: Generalized Second Price,广义第二高价。
  • ADN: ad Network,广告网络。
  • CPC: Cost per Click,点击付费。
  • RPM: Revenue per Mille,千次展示收益。
  • TD: Trading Desk,交易终端,面向多个ADN或媒体按人群一站式采买广告屏并优化投入产出比的需求方产品。

广告定义

广告是由已确定的出资人通过各种媒介进行的有关产品(商品、服务和观点)的,通常是有偿的、有组织的、综合的、劝服性的非人员的信息传播活动。

广告的根本目的是广告主通过媒体达到低成本的用户接触(《当代广告学》),即按某种市场意图接触相应的人群,进而影响其中的潜在用户,使他们选择广告主产品的几率增加,或者对产品性价比的苛求程度降低。

对于互联网广告,一切付费的信息、产品或服务的传播渠道,都是广告。

广告目的

  • 品牌广告:希望借助媒体的力量来快速接触大量用户,以达到宣传品牌形象、提升中长期购买率与利润空间。
  • 直接效果广告:利用广告手段马上带来大量的购买或其他转化行为。

广告类型

  • 横幅广告
  • 文字链广告
  • 富媒体广告
  • 视频广告
    • 前插片
    • 暂停
    • 后插片
  • 社交广告
  • 移动广告
  • 邮件定向营销广告

广告发展史

展示广告

展示广告(display advertising),在互联网上展示广告创意。
采用合约广告(agreement-based advertising)的售卖模式,即采用合同约定的方式确定某一广告位在某一时间段为某特定广告主所独占,并且根据双方的要求,确定广告创意和投放策略。

定向广告

定向广告(targeted advertising),对不同的受众呈现不同的广告创意。
定向广告对技术系统的需求如下:

  • 受众定向(audience targeting),通过技术手段标定某个用户的标签。
  • 广告投放(ad serving),将广告投送由直接嵌入页面变为实时响应前端请求,并根据用户标签自动决策并返回合适的广告创意。

定向广告依然采用合约广告的售卖模式,使用GD的交易方式,主要面向品牌广告主,按CPM计费。
这种合约广告引发一个重要的计算问题——在线分配(online allocation)——满足各合约目标受众量要求的同时尽可能为所有广告商分配到质量更好的流量。这个问题有两个难点:

  • 有效地将流量分配到各个合约互相交叉的人群覆盖上。
  • 在在线的环境下实时地完成每一次展示决策。

如果将各合约的量看作约束条件,将某种度量下的质看作目标函数,可以利用带约束优化(constrained optimization)的数学框架来探索这一问题。
若一开始就提供非常精细的定向反而会造成售卖率的下降。最初的定向标签往往粒度较粗。典型的是一些人口属性标签。
受众定向产生后,市场向精细化运作的方向快速发展。主要有两方面的趋势:

  • 定向标签越来越精准。
  • 广告主数量不断膨胀。

在这种趋势下,按照合约方式售卖广告有很多麻烦:

  • 难以准确预估细粒度标签组合的流量。
  • 一次展示同时满足多个合约时,仅按照量约束下的在线分配策略进行决策可能浪费很多本来可以卖的更贵的流量。

按照合约售卖广告有许多麻烦,因此,抛弃量的保证而采用最唯利是图的策略进行广告决策的思路催生了竞价广告。

竞价广告

竞价广告(auction-based advertising),供给方只向广告主保证质即单位流量的成本,但不再以合约的方式给出量的保证,即对每一次展示都基本按照收益最高的原则来决策。

搜索广告

搜索广告(search ad),搜索引擎成为互联网新的入口后,搜索流量变现采用了与服务自然结合的付费搜索(paid search / sponsored search)模式。
可以将付费搜索看成根据用户的即时兴趣定向投送的广告,即时兴趣的标签是关键词。由于开始的时候便十分精准,一开始便采用竞价方式售卖。

上下文广告

上下文广告(contextual advertising),根据当前浏览页面的关键词作为用户兴趣标签,竞价形式售卖。

InnoDB I/O

InnoDB 事务日志

InnoDB使用日志减少提交事务时候的开销,日志中记录了事务后就无须在每个事务提交的时候把缓冲池的脏块刷新到磁盘
InnoDB采用日志将随机I/O变为顺序I/O,日志可用来恢复已经提交的事务
日志采用环形方式写入:写到日志的尾部后重新跳转到开头继续写,不会覆盖还没应用到数据文件的日志记录
后台线程控制刷新变更到数据文件,可以批量写入
多个文件作为一组循环日志,通常不修改文件数量而修改文件大小。完全关闭MySQL,将旧的日志文件移到其他地方保存,然后重启
确定理想的日志文件大小,需要权衡正常数据变更的开销和崩溃恢复所需要的时间。

  • 日志太小:InnoDB将必须做更多检查点导致更多日志写。极端情况下必须等待变更应用到数据文件。
  • 日志太大:崩溃恢复时间极大增加。现在得到一定改善

数据大小和访问模式影响恢复时间。
例子:
均匀分布在1TB数据中的缓存池脏页和几百MB频繁变更的数据
恢复时间也依赖于普通修改操作大小。较短的行使得更多修改可以放在同一日志中,恢复时就必须重放更多操作。
在:

  • 缓冲满
  • 事务提交
  • 每一秒钟

三条件满足任意之一时InnoDB刷写缓冲区内容到磁盘日志文件
调大innodb_log_buffer_size增加日志缓冲区大小以帮助在大事务的情况下减少I/O
推荐日志缓冲区大小为1~8MB
大的日志缓冲区可帮助减少缓冲区空间分配的争用,32~128MB的日志缓冲区也可以考虑
通过检查SHOW INNODB STATUS输出中LOG部分监控InnoDB日志和日志缓冲区的I/O性能,通过观察Innodb_os_log_written变量来查看InnoDB对日志文件写出了多少数据。
经验法则:

  • 可以通过观察10~100秒间隔的数字然后记录峰值决定日志缓存的设置大小
  • 日志文件的全部大小应该足够容纳服务器一个小时的活动内容

当InnoDB把日志缓冲刷新到磁盘日志文件时,先会使用一个Mutex锁住缓冲区,刷新到所需要的位置,然后移动剩下的条目到缓冲区的前面。
Mutex释放的时候可能有多个事务准备好刷新日志记录
Group Commit 可以在一个I/O操作内提交多个事务,但是在MySQL 5.0后开启二进制日志时这个功能就不能使用
innodb_flush_log_at_trx_commit变量控制日志缓冲刷新的频繁程度

0

把日志缓冲写到日志文件并每秒钟刷新一次,事务提交时不做任何操作

1

默认。将日志缓冲写到日志文件并且每次事务提交都刷新到持久化存储。最安全的设置,保证不会丢失已提交的事务除非磁盘或者操作系统采用“伪”刷新

2

每次提交时把日志缓冲写到日志文件,并不刷新,InnoDB每秒钟做一次刷新。比0更适合(当MySQL进程挂了的时候2不会丢失任何事务)

大部分操作系统内,把缓存写到日志只是简单的把数据从InnoDB的内存缓冲移到操作系统的内存缓存。
MySQL崩溃/电源断电时,设置0或2通常导致最多一秒的数据丢失,除非刷新被推迟
把日志刷新到持久化存储是阻塞I/O的调用,因此设置1时会明显降低InnoDB每秒提交的事务数量。
磁盘缓存更快但是十分危险,不止可能丢失事务,更可能损坏数据,不推荐
高性能事务处理的最佳配置是设置1并把日志文件放到一个有电池保护的写缓存的RAID卷中
Percona Server将innodb_flush_log_at_trx_commit变量从全局变量扩展为会话级变量

InnoDB打开和刷新日志以及数据文件

innodb_flush_method选项配置InnoDB与文件系统交互
windows和非windows操作系统的选项值互斥
windows默认unbuffered,其他操作系统都是fdatasync

fdatasync

非windows系统默认值
InnoDB使用fsync()刷新数据和日志文件
缺点:操作系统至少会在自己的缓存中缓冲一些数据,理论上是浪费的。实际上有时有帮助,有时没用
文件系统可能会做更智能的I/O调度和批量操作,例如积累写操作后合并执行,重排I/O来提升效率,并发写入多个设备,预读优化(例如连续请求几个顺序块时通知磁盘预读下一个块)
innodb_file_per_table选项导致每个文件独立的fsync(),写多个表不能合并到一个I/O操作,可能导致InnoDB执行更多的fsync()操作

0_DIRECT

InnoDB对数据文件使用0_DIRECT标记或directio()函数
不影响日志文件且不是所有类unix系统都有效
GNU/Linux、FreeBSD、Solaris(5.0后新版本)支持
同时影响读写
大部分系统用fcntl()调用来设置文件描述符的0_DIRECT标记,Solaris采用directio()
只能关闭操作系统和文件系统的预读
通常需要带有写缓存的RAID卡并且设置为Write-Back策略。否则可能导致严重的性能下降。多个写线程可能缓解一定问题,MySQL5.5提供原生异步I/O,但通常无法解决
可能导致服务器预热时间变长,特别是操作系统缓存较大时。
也可能导致小容量的缓存池比缓冲I/O方式操作慢的多。如果需要的数据不在缓冲池时不得不直接从磁盘读取。
innodb_file_per_table不会产生任何额外性能损失。相反如果不用,可能由于顺序I/O遭受性能损失,因为某些文件系统(Linux所有的ext文件系统)每个inode都有一个Mutex。这些文件系统上使用0_DIRECT时推荐打开innodb_file_per_table

ALL_0_DIRECT

在Percona Server和MariaDB可用,让服务器在打开日志文件时采用0_DIRECT

0_DSYNC

日志文件调用open()函数时设置0_SYNC标记,使得所有写同步(数据写到磁盘后写操作才返回)
不影响数据文件
和0_DIRECT标记的区别:没有禁用操作系统层的缓存。因此并没有避免双重缓冲,也没使写操作直接操作到硬盘。0_SYNC标记在缓存中写数据然后发送到磁盘
和fsync()区别:用0_SYNC标记时,操作系统可能把“使用同步I/O”标记下传给硬件层,告诉设备不要使用缓存。fsync()告诉操作系统把修改过的缓冲数据刷写到设备商。如果设备支持,紧接着传递一个指令给设备刷新设备自身的缓存。另外,使用0_SYNC标记时,每个write()或者pwrite()操作都会在函数完成之前 把数据同步到磁盘,完成前函数调用是阻塞的。而fsync()允许写操作累积在缓存使得每个写更快,然后一次性刷新所有的数据

async_unbuffered

windows默认值
InnoDB大部分写使用没有缓存的I/O
例外是当innodb_flush_log_at_trx_commit设置为2的时候对日志文件使用缓冲I/O
windows2000以上使用操作系统的原生异步I/O,更老的版本InnoDB使用自己用多线程模拟的异步I/O

unbuffered

只对windows有效
类似于async_unbuffered,但不使用原生异步I/O

normal

只对windows有效
InnoDB不使用原生异步I/O或者无缓冲I/O

Nosync和littlesync

只为开发使用
无文档,对生产环境不安全
不应该使用

建议

使用类UNIX操作系统并且RAID控制器带有电池保护的写缓存,建议使用0_DIRECT。其他情况默认值或者0_DIRECT都可能是最好的选择

InnoDB表空间

InnoDB把数据保存在表空间内,本质是一个由一个或多个磁盘文件组成的虚拟文件系统
保存表、索引、插入缓冲、双写缓冲以及其他内部数据结构
innodb_data_file_path配置表空间
例:innodb_data_file_path = ibdata1:1G;ibdata2:1G;ibdata3:1G:autoextend:max:2G
上述配置在三个文件中一共创建了3GB的表空间,分散了驱动器的负载,然而通常并不能获得太多收益。InnoDB先填满第一个文件,第一个满了以后填满第二个。推荐使用RAID控制器
最后一个文件设置了自动扩展。默认的行为是创建单个10MB的自动扩展文件。设置了限制,自动扩展文件最多到2GB
建议关闭自动扩展功能,或者至少设置一个合理的空间范围
唯一的回收空间方式是导出数据,关闭MySQL,删除所有文件,修改配置,重启,让InnoDB创建新的数据文件,然后导入数据
不能简单的删除文件或者改变大小。如果表空间损坏了,InnoDB会拒绝启动
设置innodb_file_per_table选项为每张表使用一个文件,在MySQL4.1和之后的版本都支持
在数据字典存储为“表名.ibd”的数据
优点:删除一张表时回收空间简单,容易分散表到不同的磁盘上
缺点:

  • 数据放到多个文件导致更多的空间浪费,对于非常小的表问题更大(InnoDB的页大小是16KB)
  • 更差的DROP TABLE性能,可能导致显而易见的服务器端阻塞
    • 删除表需要从文件系统层删除文件,这可能在某些文件系统(重点批评ext3)上很慢。可以欺骗文件系统来缩短过程:把.ibd文件链接到一个0字节的文件然后手动删除文件
    • 每张表都在InnoDB中使用自己的表空间,移除表空间需要InnoDB锁定和扫描缓冲池,查找属于这个表空间的页面,对于大缓冲池的服务器十分缓慢。可以通过Percona Server的innodb_lazy_drop_table选项让服务器慢慢清理掉属于被删除表的页面

打开innodb_file_per_table选项时依然需要为回滚日志和其他系统数据创建共享表空间,最好关闭自动增长
可以通过查看文件的大小来确认表的大小,远远快于SHOW TABLE STATUS
建议:使用innodb_file_per_table并设置共享表空间的大小
不推荐:使用裸设备
例:一个没有格式化的分区。
可能可以提升几个百分点的性能,但是不能直接用文件管理数据

行的旧版本和表空间

写压力大的环境下表空间迅速增长。如果事务保持打开很久,并且使用默认的REPEATABLE READ事务隔离级别,InnoDB将不能删除旧的行版本
InnoDB把旧版本存在共享表空间,如果有很多数据在更新,共享表空间会持续增长
清理线程只有一个线程处理,直到最近的MySQL版本才改进
可以使用SHOW INNODB STATUS来帮助定位问题。历史链表的长度会显示了回滚日志的大小,以页为单位
TRANSACTIONS部分的第一行和第二行展示当前事务号和清理线程完成到了哪个点,差距很大时可能有大量没有清理的事务
例子:

------------
TRANSATIONS
------------
Trx id conter 0 80157601
Purge done for trx's n:o <0 80154573 undo n:o <0 0

事务标识是一个64bit的数字,由两个32bit的数字组成,两个事务标识做差计算未清理的事务数量
很多事务可能不改变数据,一个事务可能修改很多行,不能精确反应增长情况

如果有很大的回滚日志并且因此表空间增长的很快,可以强制MySQL减速使MySQL清理线程跟上。否则InnoDB将保持数据写入,填充磁盘直到磁盘空间爆满或大于表空间定义上限
设置innodb_max_purge_lag变量为大于0的值。这个值表示InnoDB开始延迟后面的语句更新数据之前,可以等待被清除的最大事务数量
innodb_max_purge_lag会降低性能,但是伤害会小于清理线程跟不上造成的性能下降

双写缓冲

避免页没写完整所导致的数据损坏,保证数据完整性
双写缓冲是表空间一个特殊的保留区域,在一个连续的块中保存100个页,本质上是一个最近写回的页面的备份拷贝
InnoDB从缓冲池刷新页面到磁盘时,首先把他们写/刷新到双写缓冲,然后再把他们写到所属的数据区域中。保证每个页面的写入都是原子且持久化
InnoDB写页面到双写缓冲是顺序的,并且只调用一次fsync()刷新到磁盘,所以写两次对性能的冲击很小
双写缓冲策略允许日志文件更高效,保证数据页不会损坏,InnoDB日志记录的时候就没必要包含整个页
InnoDB通过校验值判断页面是否损坏,从双写缓冲或者原始页面读取正确的内容
备库或者使用zfs等做了相同处理的文件系统时可以不用双写缓冲,可以配置innodb_doublewrite为0来关闭双写缓冲
Percona Server中,可以配置双写缓冲存到独立的文件中

其他配置项

sync_binlog控制MySQL刷新二进制日志到磁盘方式
默认值是0,即MySQL并不刷新,由操作系统决定什么时候刷新缓存到持久化设备
大于0的值指定两次刷新到磁盘的动作之间间隔多少次二进制写操作(若autocommit被设置,则每个独立语句是一次写,否则一个事务记一次写),0和1以外的值罕见
若未设置为1,则崩溃以后可能导致二进制文件没有同步事务数据,会导致复制中断并且无法及时恢复
设置为1时每次写二进制日志都会增加的二进制日志大小,需要更新元信息,性能损失极大
放到带电池保护的写缓存的RAID卷能极大提高性能
通过expire_logs_days选项清理旧的二进制文件
rm造成损坏后手动同步二进制日志

MySQL 查询缓存

命中判定

MySQL通过一个hash值引用来获取语句对应的缓存数据集。

hash值受到查询语句,需要查询的数据库和客户端协议版本等的影响。

MySQL在进行缓存命中判定的时候,不会解析语句生成执行计划,而是直接生成hash值并进行命中判定,大大缩短了查询语句的运行时间。查询缓存前只进行一个判断语句是否为SEL开头的大小写不敏感检查。

MySQL对于不确定的数据不会进行缓存,准确的描述是 如果查询语句中包含任何不确定函数,则查询缓存中不可能找到结果

一般来说,自定义函数、存储函数、用户变量、临时表、系统表、包含列级权限的表、子查询和存储过程不能缓存。

绑定变量在5.1版本前不能缓存。

某些数据可以通过提前计算避开限制。

开启缓存的开销有:

  • 读查询前检查是否命中缓存
  • 可以被缓存的读查询进行缓存操作
  • 写操作时设置所有对应缓存失效

缓存的内存使用

由一个40KB左右大小的管理维护数据结构和多个变长数据块组成。

管理维护数据结构记录可用内存空间、已使用内存空间、存储数据表和查询结果映射空间以及存储查询字符串和查询结果空间等内容。

数据块存储了自己的类型、大小、数据和指向前一个和后一个数据块的指针。

数据块的类型:

  • 存储查询结果
  • 存储查询和数据表的映射
  • 存储查询文本

等。

服务器启动时,先初始化查询需要的内存,生成一个完整的空闲块作为内存池,大小是配置的查询缓存大小减去维护元数据的数据结构消耗的空间。

当由查询结果需要缓存时,MySQL从空间块中申请一个大于query_cache_min_res_unit的数据块,同时一般保证这个内存块尽可能小(也可能选择大的)然后填入数据。
若数据块用完,则MySQL会依照上述原则申请一块新的数据块。

若完成查询后内存空间还有剩余,则空余的内存会被释放,并放回空闲内存部分。

缓存查询过程

过程如图

MySQL的查询缓存在写入时可能产生碎片问题。常见于并发。如图

碎片问题

缓存适用情况

只有缓存带来的资源节约大于其本身的资源消耗时才会带来性能提升,取决于具体的服务器压力模型。

理论上可以通过关闭和开启查询缓存并对比系统效率来决定是否开启缓存。

网络消耗为系统主要瓶颈时缓存的意义不大。

复杂的SELECT语句都可以使用查询缓存。如果某些关键的查询能够得到优化,那么降低其他的查询速度也是有意义的,可以通过SQL_CACHE选项进行控制来得到进一步优化。

UPDATE、DELETE和INSERT操作尽可能少。

查询缓存命中率计算公式: Qcache_hits/(Qcache_hits+Com_select)

缓存未命中的可能原因有:

  • 无法缓存(语句结果不确定/结果过大)
  • 从未处理的语句
  • 缓存失效

缓存失效的主要原因有:

  • 缓存碎片
  • 内存不足
  • 数据修改

可以通过Com_*查看数据修改的情况(Com_update,Com_delete等)

通过Qcache_lowmem_prunes来查看内存不足情况。这个参数代表内存不足时删除缓存的数量。

“命中和写入”的比率可以更好的反应查询缓存的效率,即 Qcache_hits/Qcache_inserts
根据经验,通常3:1以上缓存有效
推荐达到10:1。

配置和维护缓存

相关的参数有:

  • query_cache_type:是否打开查询缓存 OFF/ON/DEMAND
  • query_cache_size:缓存总内存空间 1024整数倍
  • query_cache_min_res_unit:分配内存块时的最小单位
  • query_cache_limit:能够缓存的最大查询结果
    • 注意:数据开始生成时就开始缓存,因此当结果返回完毕后MySQL才知道结果是否超出限制,若超出则进行删除缓存操作,严重拖慢性能
  • query_cache_wlock_invalidate:表有锁时是否返回结果

可以通过设置合理的query_cache_min_res_unit减少碎片。

可以通过单个查询的平均缓存大小 (query_cache_size-Qcache_free_memory)/Qcache_queries_in_cache 作为参考。需要考虑结果不均匀的情况。

通过参数Qcache_free_blocks来观察碎片,若Qcache_free_blocks大小接近Qcache_total_blocks/2则说明有严重的碎片问题。

可以使用 FLUSH QUERY CACHE完成碎片整理。

RESET QUERY CACHE是缓存清空,加以区别。

观察Qcache_lowmem_prunes值,若增长过快,则:

  • 很多空闲块:碎片
  • 少空闲块:缓存过小

也有可能是可能缓存不适合工作于目前的应用系统。

InnoDB与查询缓存

4.0版本前,事务处理中查询缓存是禁用的,4.1开始,InnoDB控制事务是否可以使用查询缓存。

事务是否可以访问缓存取决于当前事务ID和数据表上是否有锁。

每一个InnoDB内存数据字典中都保存一个事务ID号,若当前事务小于该事务ID则无法访问查询缓存。

如果表上有锁,则关于该表的任何查询无法访问缓存。

恒成立的事实:

  • 所有大于该表计数器的事务才能使用缓存
  • 对表加锁的事务ID不会被更新为该表的计数器,计数器会被更新成系统事务ID。该事务后续更新操作也无法读取和修改缓存。

通用查询缓存优化

使用多个小表代替一个大表。

采用批量写入。

控制缓存大小,放置服务器僵死,必要时禁用缓存。

通过SQL_CACHE和SQL_NO_CACHE控制单条查询语句缓存。也可以修改会话级的变量query_cache_type。

写密集型应用直接禁用缓存。

读密集型应用根据评估判断是否禁用缓存。

RESTful API 规范

约束

客户-服务器(Client-Server),提供服务的服务器和使用服务的客户需要被隔离对待。

无状态(Stateless),来自客户的每一个请求必须包含服务器处理该请求所需的所有信息。换句话说,服务器端不能存储来自某个客户的某个请求中的信息,并在该客户的其他请求中使用。

可缓存(Cachable),服务器必须让客户知道请求是否可以被缓存。

分层系统(Layered System),服务器和客户之间的通信必须被这样标准化:允许服务器和客户之间的中间层(Ross:代理,网关等)可以代替服务器对客户的请求进行回应,而且这些对客户来说不需要特别支持。

统一接口(Uniform Interface),客户和服务器之间通信的方法必须是统一化的。

  • 资源标识符。每个资源都有各自的标识符。客户端在请求时需要指定该标识符。在 REST 服务中,该标识符通常是 URI。客户端所获取的是资源的表达(representation),通常使用 XML 或 JSON 格式。

  • 通过资源的表达来操纵资源。客户端根据所得到的资源的表达中包含的信息来了解如何操纵资源,比如对资源进行修改或删除。

  • 自描述的消息。每条消息都包含足够的信息来描述如何处理该消息。

  • 超媒体作为应用状态的引擎(HATEOAS)。客户端通过服务器提供的超媒体内容中动态提供的动作来进行状态转换。

支持按需代码(Code-On-Demand,可选),服务器可以提供一些代码或者脚本(Javascrpt,flash,etc)并在客户的运行环境中执行。这条准则是这些准则中唯一不必必须满足的一条。(比如客户可以在客户端下载脚本生成密码访问服务器。)

协议

API与用户的通信协议,总是使用HTTPs协议,确保交互数据的传输安全。

域名

应该尽量将API部署在专用域名之下。

  • https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

  • https://example.org/api/

API版本控制

路径又称”终点”(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

  • https://api.example.com/v1/products
  • https://api.example.com/v1/users
  • https://api.example.com/v1/employees

HTTP请求方式

对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面四个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • DELETE(DELETE):从服务器删除资源。

GET /product:列出所有商品。

POST /product:新建一个商品。

GET /product/ID:获取某个指定商品的信息。

PUT /product/ID:更新某个指定商品的信息。

DELETE /product/ID:删除某个商品。

GET /product/ID/purchase:列出某个指定商品的所有投资者。

GET /product/ID/purchase/ID:获取某个指定商品的指定投资者信息。

HTTP头部&HTTP状态码

针对不同的需求和不同的响应,Request应该采用相应的HTTP头部,Response应采用相应的HTTP状态码,方便服务端和用户端作出不同的响应。

  • Authorization 认证报头
  • Cache-Control 缓存报头
  • Content-Type 消息体类型报头

  • 200 OK

  • 400 Bad Request
  • 500 Internal Server Error

黑盒测试

概述

黑盒测试也称功能测试或数据驱动测试,它是在已知产品所应具有的功能,通过测试来检测每个功能是否都能正常使用。

在测试时,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试。

适用范围

  • 是否有不正确或遗漏了的功能

  • 在接口上,能否正确地接受输入数据,能否产生正确地输出信息

  • 访问外部信息是否有错

  • 性能上是否满足要求

  • 界面是否错误,是否不美观

  • 初始化或终止错误

基本类型

通过测试

软件的基本功能能否正常工作

失败测试

为了破坏软件而设计和执行的测试案例

优点

  • 比较简单,不需要了解程序内部的代码及实现

  • 与软件的内部实现无关

  • 从用户角度出发,能很容易的知道用户会用到哪些功能,会遇到哪些问题

  • 基于软件开发文档,所以也能知道软件实现了文档中的哪些功能

  • 在做软件自动化测试时较为方便

缺点

  • 不可能覆盖所有的代码,覆盖率较低,大概只能达到总代码量的30%

  • 自动化测试的复用性较低

方法

等价类划方法

等价类划分是把所有可能的输入数据,即程序的输入域划分成若干部分(子集),然后从每一个子集中选取少数具有代表性的数据作为测试用例。

等价类

等价类是指某个输入域的子集合。在该子集合中,各个输入数据对于揭露程序中的错误都是等效的。

有效等价类

对于程序的规格说明来说是合理的,有意义的输入数据构成的集合。

无效等价类

与有效等价类的定义相反。

步骤

  1. 对每个输入或外部条件进行等价类划分,形成等价类表,为每一等价类规定一个唯一的编号

  2. 设计一测试用例,使其尽可能多地覆盖尚未覆盖的有效等价类,重复这一步骤,直到所有有效等价类均被测试用例所覆盖

  3. 设计一新测试用例,使其只覆盖一个无效等价类,重复这一步骤直到所有无效等价类均被覆盖

例子

设某公司要打印2001~2005年的报表,其中报表日期为6位数字组成,其中,前4位为年份,后两位为月份。

第一步——划分等价类

输入及外部条件 有效等价类 无效等价类
报表日期的类型及长度 6位数字字符① 有非数字字符④
少于6个数字字符⑤
多于6个数字字符⑥
年份范围 在2001~2005之间② 小于2001⑦
大于2005 ⑧
月份范围 在1~12之间③ 小于1⑨
大于12⑩

第二步——有效等价类划分测试用例

对表中编号为①②③的3个有效等价类用一个测试用例覆盖

测试数据 期望结果 覆盖范围
200105 输入有效 等价类①②③

第三步——为每一个无效等价类至少设计一个测试用例

测试数据 期望结果 覆盖范围
001MAY 输入无效 等价类④
20015 输入无效 等价类⑤
2001001 输入无效 等价类⑥
20000 输入无效 等价类⑦
20080 输入无效 等价类⑧
200100 输入无效 等价类⑨
200113 输入无效 等价类⑩

边界值分析法

边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。

通常边界值分析法是作为对等价类划分法的补充,这种情况下,其测试用例来自等价类的边界。

因果图方法

一种适合于描述对于多种条件的组合,相应产生多个动作的形式,适合设计测试用例

因果图方法最终生成的就是判定表,它适合于检查程序输入条件的各种组合情况。

因果图中使用了简单的逻辑符号,以直线联接左右结点。左结点表示输入状态(或称原因),右结点表示输出状态(或称结果)。

ci表示原因,通常置于图的左部;ei表示结果,通常在图的右部。ci和ei均可取值0或1,0表示某状态不出现,1表示某状态出现。

关系

4种符号分别表示了规格说明中向4种因果关系。

  1. 恒等:若ci是1,则ei也是1;否则ei为0

  2. 非:若ci是1,则ei是0;否则ei是1

  3. 或:若c1或c2或c3是1,则ei是1;否则ei为0。“或”可有任意个输入

  4. 与:若c1和c2都是1,则ei为1;否则ei为0。“与”也可有任意个输入

约束

输入状态相互之间还可能存在某些依赖关系,称为约束。

输入条件的约束有以下4类:

  1. E约束(异):a和b中至多有一个可能为1,即a和b不能同时为1

  2. I约束(或):a、b和c中至少有一个必须是1,即 a、b 和c不能同时为0

  3. O约束(唯一);a和b必须有一个,且仅有1个为1

  4. R约束(要求):a是1时,b必须是1,即不可能a是1时b是0

输出条件的约束只有M约束(强制):若结果a是1,则结果b强制为0。

步骤

  1. 分析软件规格说明描述中,那些是原因(即输入条件或输入条件的等价类),那些是结果(即输出条件), 并给每个原因和结果赋予一个标识符

  2. 分析软件规格说明描述中的语义。找出原因与结果之间,原因与原因之间对应的关系。根据这些关系,画出因果图

  3. 由于语法或环境限制,有些原因与原因之间,原因与结果之间的组合情况不不可能出现。 为表明这些特殊情况,在因果图上用一些记号表明约束或限制条件

  4. 把因果图转换为判定表

例子

某软件规格说明书包含这样的要求:第一列字符必须是A或B,第二列字符必须是一个数字,在此情况下进行文件的修改,但如果第一列字符不正确,则给出信息L;如果第二列字符不是数字,则给出信息M。

标识符

原因:

  • 1——第一列字符是A

  • 2——第一列字符是B

  • 3——第二列字符是一数字

结果:

  • 21——修改文件

  • 22 ——给出信息L

  • 23——给出信息M

因果图

11为中间节点;考虑到原因1和原因2不可能同时为1,因此在因果图上施加E约束。

根据因果图建立判定表

1 2 3 4 5 6 7 8
原因(条件) 1 1 1 1 1 0 0 0 0
2 1 1 0 0 1 1 0 0
3 1 0 1 0 1 0 1 0
11 1 1 1 1 0 0
动作(结果) 22 0 0 0 0 1 1
21 1 0 1 0 0 0
23 0 1 0 1 0 1

设计测试用例

1 2 3 4 5 6 7 8
原因(条件) 1 1 1 1 1 0 0 0 0
2 1 1 0 0 1 1 0 0
3 1 0 1 0 1 0 1 0
11 1 1 1 1 0 0
动作(结果) 22 0 0 0 0 1 1
21 1 0 1 0 0 0
23 0 1 0 1 0 1
测试用例 A6 Aa B9 BP C5 HY
A0 A@ B1 B* H4 E%

错误推测法

基于经验和直觉推测程序中所有可能存在的各种错误, 从而有针对性的设计测试用例的方法。

基本思想

列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据他们选择测试用例。

例子

输入数据和输出数据为0的情况;输入表格为空格或输入表格只有一行。

测试一个对线性表(比如数组)进行排序的程序,可推测列出以下几项需要特别测试的情况:

  • 输入的线性表为空表

  • 表中只含有一个元素

  • 输入表中所有元素已排好序

  • 输入表已按逆序排好

  • 输入表中部分或全部元素相同

异度之刃:黄金之国伊拉评价

三天通了黄金之国伊拉这个dlc。感觉实在是太棒了,强力推荐,可以说在我心中是本月最佳游戏了。(隔壁的某播片游戏,同样播片你咋就这么丢人
(以下剧透)
dlc拔高了整个游戏的体验。因为有了前传,异度之刃本篇不再是一个幸运小屁孩嘴炮救世的老套故事,而是有血有肉的一个完整的世界。我对莱克斯的好感简直是蹭蹭的涨。总有一种高桥为了救莱克斯口碑才出个dlc的感觉(笑
本篇的战斗和剧情的割裂,导致天之圣杯和他的主人看起来毫无威力,而全世界对天之圣杯的畏惧和排斥在玩家精分的体验下显得十分滑稽。莱克斯接纳光/焰并战胜反派的那一段就有一种一通嘴炮就能让她们觉醒的荒诞感觉。但是在前传中,天之圣杯造成的灾难和恐惧十分直观,村庄被直接抹除,伊拉王城在熊熊大火中被焚尽,世界最强国伊拉、希亚全境遁入云海,世界都感到震惊。因此,世界上的所有人都惧怕圣杯强大的力量这个设定很容易被玩家所接受。于是,本篇里看起来非常突兀的场景,在dlc里得到了良好的诠释。dlc成功构造了——全世界都排斥、畏惧光,哪怕是曾经持有者,前代英雄也是如此。长期的压抑导致了光/焰的对自身的恐惧和强烈的自毁倾向——这样的设定,因此莱克斯的“力量应当保护所爱之人”的观点和对光/焰力量的完全接纳与包容才显得如此珍贵。说到底,是莱克斯救赎了光/焰,莱克斯作为男主的正当性得到了体现。
除了解释了莱克斯行为的合理性,dlc还提供了一个强烈的对比——阿德尔。本篇里阿德尔只有为数不多的几句耍帅台词,刺客信条一般披风的剪影,无法接纳而将光/焰封印的既成事实,还有光对他的景仰。给我的印象就是一个高冷的酷酷英雄。莱克斯14岁小孩子的幼稚设定在这种对比之下就非常不讨喜。dlc却告诉我这是个二货王子?阿德尔在dlc的设定变成了一个无心政治,梦想是隐居又博爱、受爱戴的私生二货王子。因此阿德尔的结局有两重骂名:作为光的御刃者,阿德尔和其他普通人害怕光的力量带来毁灭,隐藏在心中的恐惧导致阿德尔无法完全发挥与控制光,导致了光的暴走和伊拉的毁灭,反而将过错怪罪于光的力量,将光封印在灵洞中,甚至害怕到将温柔的焰封印沉入海底,作为御刃者不合格;作为广受子民爱戴的王子,结局告诉我阿德尔把光封印以后仅仅收拢了反抗军残部,然后前往群岛隐居。你的子民呢?居然还是被奸诈的王叔收拢成立新国家的?作为政治领袖也不合格。他的英雄形象彻底崩坏了。反观莱克斯,调谐圣杯得到的是完全形态的绿毛,拯救了世界也拯救了绝望的光。开个玩笑,战斗中,阿德尔都是控制不住老婆的输出,打强敌怂的不行;莱克斯都是莽到老婆输出跟不上,打强敌可带劲儿了,“什么天之圣杯,老子打的就是天之圣杯,吼姆拉,你他娘的塞壬机甲呢,给我拉来”(划掉)。总之,通过阿德尔的衬托,莱克斯幼齿的身躯立马伟岸了起来(笑)。
关于新系统,dlc去掉了抽卡(坏文明)环节,主角队开场基本固定,配装很舒服。挂球系统简化,现在三段必杀技的属性都会被挂上属性球,打球更容易了。御刃者下场战斗,带俩异刃,和原来三个异刃相比切换更麻烦,补偿就是人和异刃切换的时候会额外发动一个技能,方便打御刃者素质四连,而且被打掉的血以虚血形式存在,切换人和异刃即可回复。御刃者在场时可以发动一个无限制的特殊技能,例如劳拉可以扣除当前一半的血量刷新所有武技,感觉整套系统就是在鼓励你莽。
最后是一点碎碎念,这样一看本篇的劳拉组太惨了,明明前传里都是大好人,结果本篇里劳拉牌冰棍在和教廷的战斗中连着战舰一起被摧毁,霞(就是后来的芳)被教皇重新调谐,还被做成了食刃种,被真亲手杀死,还只能留下尸体无法进入异刃的轮回,真没有复活劳拉,还在和教皇的决战中阵亡了。完全是bad end啊,我不能接受!还有就是本来想随手写一个空间吐一堆槽的,结果莫名的写出了一坨干巴巴的分析文字,完全不是我玩完之后的感想啊!哪有这么一本正经吐槽的啊!果然玩完当场就要顺手写出来啊,还是说我完全不适合写东西呢。总之感谢看到最后的各位,有感想可以尽情交流。

记一次内网访问外网使用代理出现的apt问题

背景

tx 内网的开发网不允许直接访问外网,而且内网中的 apt 源内容较少,所以使用 apt 等工具管理开源工具时,需要使用代理访问 archive.ubuntu.com 和 security.ubuntu.com 以获取内容和修改PPA源。

问题

  • 通过~/.bashrc设置 http 代理和 https 代理后,source 和重启 bash 都无法使 apt 连接至 archive.ubuntu.com 和 security.ubuntu.com。
  • sudo apt-add-repository 无法正常工作。

原因

  • apt 并不使用系统变量 http_proxy 和 https_proxy ,需修改 /etc/apt/apt.conf 或使用 -o 选项指定 http/https 代理。
  • sudo 命令使用 root 的环境设置,需使用 -E 选项保留当前用户的设置。

方法

  • 使用 sudo apt-get -o Acquire::http::proxy="xxxx"
  • 使用 sudo -E apt-add-repository