remote_addr及X-Forwarded-For及用户真实IP及内网安全

先缕一下背景,计算机网络,连在网络中的机器都有一个(或多个)IP地址,提供Web服务的机器,常有一个便于人们记忆的域名,通过DNS解析到一个(或多个)IP地址。有的网站流量大,需要不止一台机器提供服务,但域名只有一个,只能指向一台机器,怎么办呢?

代理服务器负载均衡(简称LB),提升静态网页的访问速度,增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性

互联网产品做大了,再做它个开放平台,搞完用户,搞合作伙伴。对外提供内网服务,开放平台服务器(简称OP)实现认证,代理调用内网服务。
以下,OP、LB、Server均为Nginx

remote_addr_proxy.png

Web服务器,一些场景,根据来源IP地址,决定服务与否。若是内网接口,只服务内网机器,拒绝服务来自外网的请求。
遇到的问题是,当client c通过OP请求内网接口时,Server解析客户端IP为client c的IP,其为外网IP 遂拒绝了服务。此时,我们希望Server拿到的客户端IP为OP的内网IP,这样就能正常为OP代理的客户服务了。怎么做呢?

HTTP协议虽然定义了Client-Ip请求头,但不是安全的,服务器的做法是取TCP连接的对端IP作为client ip,在Nginx中,解析为变量$remote_addr,不架设代理、负载均衡时,直接拿来用即可。但当在Server前代理时,应利用Nginx的ngx_http_realip_module中的real_ip_header指令,指定用哪个请求header的值来替换默认的$remote_addr。

假设
client a IP:12.34.56.78
OP IP: 34.56.78.90 及内网IP 10.22.33.44
LB IP: 10.23.34.45
这时,我们考察 X-Forwarded-For 的定义及格式,对于上图Server接收来自client c的请求时,X-Forwarded-For首先由OP设置为client c的IP,即

proxy_set_header X-Forwarded-For $remote_addr;

LB接收此header时,应再次设置

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

此时,到达Server的信息应该是:
$remote_addr: 10.23.34.45
X-Forwarded-For: 12.34.56.78,10.22.33.44

注意,Server知道Load Balance的存在,所以不关心$remote_addr的默认值,关心的是真正的请求IP,此时可设置
real_ip_header

set_real_ip_from  10.0.0.0/8;
real_ip_header    X-Forwarded-For;
real_ip_recursive off;

关于real_ip_recursive指令,官方文档:

If recursive search is disabled, the original client address that
matches one of the trusted addresses is replaced by the last address
sent in the request header field defined by the real_ip_header
directive. If recursive search is enabled, the original client address
that matches one of the trusted addresses is replaced by the last
non-trusted address sent in the request header field.

设置off时,Server得到的$remote_addr为10.22.33.44 即开放平台的IP,正常服务!

注:RFC 7239 定义了一个新的 Forwarded header,更完备地组织这些信息。

位操作

位操作高效解决问题

其中,
Sum of Two Integers
Use ^ and & to add two integers

int getSum(int a, int b) {
    return b==0? a:getSum(a^b, (a&b)<<1); //be careful about the terminating condition;
}

用^及& 实现 加法
看了好一会儿,才看懂
两个位串相加,结果可由两部分组成:不同的位 与 相同的位
不同的位,用^取得
相同的位,相加并进位,先&运算,再左移1位
此两部分相加,即得结果,递归下去即可。

注意边界情况,整型通常4字节32位,不断左移终使b为0,结束递归。
对于python/php等脚本语言,实现了非常大的整型范围,这样写法则不成了,需用mask限制32位

class Solution(object):
    def getSum(self, a, b):
        """
        :type a: int
        :type b: int
        :rtype: int
        """
        # 32 bits integer max
        MAX = 0x7FFFFFFF
        # 32 bits interger min
        MIN = 0x80000000
        # mask to get last 32 bits
        mask = 0xFFFFFFFF
        while b != 0:
            # ^ get different bits and & gets double 1s, << moves carry
            a, b = (a ^ b) & mask, ((a & b) << 1) & mask
        # if a is negative, get a's 32 bits complement positive first
        # then get 32-bit positive's Python complement negative
        return a if a <= MAX else ~(a ^ mask)

如上,Python3整型是无界的(无限大),想限定整型上限,如何能让其如C/Java一样表示补码?
为了理解最后一行代码的含义,我们可以假设场景,然后确定目的是什么。
假设C语言int占4位,python语言int占8位,mask为0xF
举例,我们得到的a若为 0b0101,它被预期正常解释,算法结束;若a为0b1011,根据此算法的隐含条件,我们想让它被解释成负数,就得让它符号扩展到 0b11111011

~(a ^ mask)

0b1011 => 0b0100 => 0b11111011

应用程序语言(PHP)导出csv文件乱码问题及解决

给运营同学提供数据支持,需求是导出数据文件,通常就是Excel能打开的、通用的CSV逗号分隔符文件。

首先导出 无BOM头UTF-8编码的标准csv格式文件,但用Excel打开此csv乱码。
此时考虑,能否手动指定Excel打开文件的编码:
文件-导入-CSV,指定UTF8编码、 ,分隔符 及 " qualifier,不乱码了,但出现 字段内容的换行符以外起作用,也就是CSV的qualifier解析失效。

没能解决,遂换个思路,分析Excel本身导出的utf8编码的csv文件
csv-utf8-bom.png
赫然写着With BOM,丫生成的utf8文件带BOM头!(业界对于UTF8文件,不带BOM头的做法更为标准,微软仍在依赖BOM)
PHP生成csv时,先输出BOM头,导出的csv文件,用Excel直接打开,完美解决乱码问题!

具体代码(Stackoverflow,maybe需翻墙):
PHP导出csv
PHP输出BOM头:

echo "\xEF\xBB\xBF";

题外话:
解决这个问题时,遇到另一个小问题,PHP的指令结束标志 ?>,在include及require等文件引入时,产生额外的空白符见:Note,对一些场景导致bug。
比如此文,在输出BOM头前 输出了空白字符,将导致多数软件无法正常识别带BOM头的文件。

微博春晚流排序算法榜单

需求说明

原始需求说明

首页

实时流
白名单用户所发博文 + 关键词
阅读量+互动量排序,权重各占50%
发博时间降权

精彩段子

段子实时流
白名单用户所发博文 + 关键词
阅读量 + 互动量排序, 权重各占50%
发博时间降权
段子排行榜
白名单用户所发博文 + 关键词
阅读量 + 互动量排序, 权重各占50%
uid 去重,每个uid显示排名最高的博文

视频流

历年节目回顾
白名单用户所发博文+关键词
播放量+互动量排序,权重各占50%

明星实时流

白名单用户所发博文 + 关键词
阅读量 + 互动量排序,权重各占50%
发博时间降权

排序规则的抽象

是否区分视频微博

博文类:排序计数按 阅读量 + 转评赞
视频类:排序计数按 播放量 + 转评赞
视频的播放量从微博信息中获取,不同于 阅读、转评赞是同一个接口

是否时间降权

实时流: 计数按发布时间因子衰减

是否uid去重

段子手排行榜

抽象后的流排序

流分类

春晚实时博文 (阅读+转评赞+时间降权; 春晚主场、精彩段子、明星拜年)
春晚段子排行 (阅读+转评赞+uid去重,精彩段子)
春晚视频排行 (播放量+转评赞,历年回顾、视频排行)

算法抽象

//根据后台设置的计数权重计算 单体微博的原始得分
//如对于视频微博, $weight['read'] = 0 
//对于普通博文, $weight['play'] = 0
$raw_score = $weight['read'] * $read_count + $weight['interacts'] * $interacts_ccount  + $weight['play'] * $play_counts;
//根据时间衰减因子,计算衰减系数,
//$decay_hour_config 衰减到e^(-1) 约为0.3678需要的小时数
//如果需要实时性比较强,$decay_hour_config 可以设置的比较下,如0.01
$decay_weight = exp(-1 * ($count_time_in_seconds - $mid_create_time_in_seconds) /(3600 * $decay_hour_config));
$score = $score * $decay_weight;

流收录&&排序总体流程

使用redis的zset 实时收录feed流
区分视频微博和普通微博(redis_key_feed, redis_key_video, redis_key_spring2018)
每十分钟排序
前台展示使用redis_key_spring2018, 初始微博的score 为0

读《高性能MySQL》摘记

第三章 性能剖析
性能:更好的解释可以是,完成某任务所需的时间度量 秒/任务
优化:减少所需时间
吞吐量:每秒完成查询量 任务数/秒

正确步骤:测量时间花在哪(这是重点和前提),为什么花在那儿,优化那儿。
3.3 剖析MySQL查询
捕获MySQL查询
方法1 配置long_query_time=0 注意 由于全量查询,以免磁盘使用过大,最好部署log ratation工具
方法2 抓取TCP网络包,tcpdump保存到磁盘,用Percona Toolkit中的pt-query-degist分析
附加:MySQL还有“通用日志”,但只记录查询发生时间;MySQL Proxy代理层也可以记录所有查询
3.5.2 使用strace
strace -cfp $(pidof mysqld)
Percona Toolkit中的pt-ioprofile也是使用的strace来生成I/O活动的剖析报告