Java程序的日志

Overview

一个在生产环境里运行的程序如果没有日志是很让维护者提心吊胆的,有太多杂乱又无意义的日志也是令人伤神。程序出现问题时候,从日志里如果发现不了问题可能的原因是很令人受挫的。本文想讨论的是如何在Java程序里写好日志。大多数的Web服务器(如Apache,Nginx)都有access日志和error日志,分别记录在不同的文件内;我们使用的服务器操作系统Linux有Syslog日志, /var/log目录下也有很多基础应用和服务的日志文件;桌面Windows有事件查看器, Mac有Console应用可以查看和管理日志;这些成熟的系统及工具方法都值得我们学习并在自己的项目中应用。

一般来说日志分为两种:业务日志和异常日志。使用日志我们希望能达到以下目标:

  1. 对程序运行情况的记录和监控;
  2. 在必要时可详细了解程序内部的运行状态;
  3. 对系统性能的影响尽量小;

日志规范

程序框架应该提供统一的日志记录接口,日志格式也需要有一定的规范,方便利用日志工具来分析日志。 首先我们有必要了解一下Linux普遍使用的Syslog标准协议,协议规定日志中应包含产生日志的模块(Facility),严重性(Severity Level),时间,主机名或IP,进程名,进程ID和日志内容,根据模块和严重性可以配置相应的动作:是否需要记录,日志存储路径(文件或网络)。

下面是部分常见的Syslog模块类型:

模块ID关键词描述
0kern内核消息
1user用户级别消息
2mail邮件系统
3daemon系统后台守护程序
4auth安全/鉴权消息
5syslogsyslogd内部产生的日志消息

以及Syslog严重程度划分:

代码严重程度关键词描述
0Emergencyemerg(panic)紧急,系统已经不稳定了
1Alertalert需要立刻采取措施
2Criticalcrit严重情况
3Errorerr (error)系统出错
4Warningwarning(warn)系统警告
5Noticenotice系统仍然正常,但值得注意
6Informationalinfo正常系统通告
7Debugdebug系统调试信息

在你的Java程序里日志也可以参考Syslog的设计,根据业务对程序的模块和日志级别做一定的规划和设计。

[Read More]

在线广告系统架构变迁

Overview

(未完)

广告按业务划分有以下几类:

  1. 展示广告(Banner广告)
  2. 搜索广告(关键词广告)

按计费模式有:

  1. CPT: 按有效广告展示天数计费,可定价或竞价;
  2. CPM: 按有效广告展现次数计费,可定价或竞价;
  3. CPC: 按有效点击次数计费;
  4. CPS: 按效果计费,如,按成交订单额,按流量带来的有效下载或安装次数计费;

广告系统按结构可划分为以下子系统:

  1. 广告投送服务器
  2. 广告数据库及业务系统
  3. 用户行为跟踪系统
  4. 日志传输系统
  5. 流式数据分析和计算平台
  6. 离线数据分析和计算平台

从展示广告到搜索广告再到社会化广告

早期的互联网广告以展示广告为主,业务模式和在报纸,期刊上刊登广告相似,广告主按其广告的展现次数或时长来付费。直到1998年有个叫GoTo的公司(后公司改名为Overture,2005年被Yahoo收购)开始提供关键词广告,这是一种简单高效的定向广告,广告主可以按与其业务相关的关键词购买搜索流量,按访客的点击计费,和早期的展示广告相比这无疑是一个巨大的进步,通过关键词将用户和广告关联起来,而不是在广告位上一直展现同样的广告或随机的展现。随后Google迅速的采用了这个方法将自身的搜索流量变现,并不断优化和壮大,最终形成了Google的Adword产品,早期Google并不为搜索用户建立个性化数据,因此可用于定向的数据比较有限,于是Google又推出了AdSense产品,将触角伸向了全网,通过AdSense的合作网站,Google可以采集到访客在这些网站的访问足迹,并利用这些数据提高其广告的相关性;随着以Facebook为代表的社交网络的兴起,这些网站有个特点就是自身就具备了比较完善的用户个性化数据,利用这些数据,网站可以将定向技术使用到展示广告,这使展示广告迎来了第二春,广告产业对数据的应用价值有了极大的兴趣,也催化了大数据的技术广泛应用。近年来一种新的广告业务模式–“实时竞价广告交易系统”也逐渐被网站主和广告主接受,其核心目的是让每次广告展现都创造出尽可能多的回报,以自动化的方式系统使用实时数据和竞价信息将广告空间分配给那一时刻出价最高的销售渠道。

广告系统的架构变迁

早期的展示广告系统因为业务数据量不大,广告位按时长售卖。系统架构上比较简单。系统核心为关系数据库,广告主通过业务系统管理广告数据:账户设置,广告计划,预算,推广单元,广告创意等;通过审核后可以投放的数据进入广告投放服务器。简单的广告系统可以完全围绕数据库来实现。投放服务器直接访问数据库获取需要投放的广告数据,并通过缓存来提高性能,减少对数据库的压力。

图[简单的广告系统架构]

当广告系统里的广告主越来越多,广告位的流量越来越大,直接访问数据库的做法不能满足性能和扩展性的要求,主要原因是传统的关系数据库的设计目标是通用的需求。这种情况下广告系统可以定时将广告数据从数据库中导出,生成的数据文件通过网络传到投放服务器,投放服务器将数据文件载入内存供快速访问,当用户打开网站页面时候,部署在广告位的代码(可部署在客户端或服务器端)将参数传给广告投放服务器,投放服务器按一定策略和逻辑取出需要展现给用户的广告数据,按接口规范直接返回给用户浏览器渲染,或返回给调用广告引擎的服务器端程序,再由服务器端程序将广告嵌入内容页面。

图[改进后的简单的广告系统架构]

对于大多数中小网站,广告是其运营收入重要的一部分,在网站上部署广告一般有下面几种方法:

接入大型广告网络

这是最简单的一种方式,不占用网站的服务器资源,只需要在网站广告位嵌入广告网络提供的广告代码即可。

Google AdSense

Google AdSense 是一种获取收入的快速简便的方法,适合于各种规模的网站发布商。它可以在网站的内容网页上展示相关性较高的 Google广告,并且这些广告不会过分夸张醒目。由于这些所展示的广告内容同用户在您的网站上查找的内容相关,因此,最终您的内容网页不仅仅会为您带来很好的经济效益,还能够得以充实网站内容页面。google根据网站上显示的广告被点击的次数支付佣金,当某个月底佣金累计达到100美元时即可向发布商支付广告佣金。

Google AdSense开创了一个新的广告模式吧。以前的很多广告商,主要靠在各种页面,高频率的轰炸网友的眼球,而不管这个网友对广告内容是不是感兴趣。但是AdSense却通过分析网页内容后,提供一些和内容相关的文字广告(也会有个别图片广告),因为内容相关的广告更容易引起读者的兴趣,使广告成为一种真正有用的信息,而且也不会给网友太多的骚扰。

阿里妈妈

阿里妈妈是阿里巴巴集团旗下的网站联盟,将网站主与淘宝卖家之间搭起了一座桥梁,依托自身强大的技术研发能力,对商品实现今精准投放,不断改进投放内容与形式,让双方利益都得到最大化,营造了一个良性循环的生态圈。

百度联盟

百度联盟隶属于全球最大的中文搜索引擎百度。一直致力于帮助合作伙伴挖掘互联网流量的媒体价值,帮助推广客户拓展精准有效的投放通路,是现今国内颇具规模的广告联盟。

使用第三方广告托管系统

使用第三方广告托管系统相对于接入一个广告网络相比还可以展现自身的广告。

百度广告管家

百度广告管家是在线媒体广告管理系统,提供了广告投放管理、收益优化、定向投放、广告托管、广告数据统计报告等功能,几乎涵盖了目前所有网站站长的广告管理需求。登录百度广告管家,看到广告投放管理可以支持对固定、弹窗、漂浮三种类型的广告位投放的管理操作,并支持提供按地域、接入方式、日期、浏览器、浏览器语言、操作系统、来源域、分辨率、被访URL等定向投放方式的管理。广告计费方式提供按日、按展现、按点击三大主流计费方式。

部署开源广告系统

通过部署开源的广告系统可以对广告投放有较大的控制权,但为此需要消耗带宽和服务器成本,以及广告系统二次开发和维护成本。

OpenX

OpenX是一个用PHP开发的开源广告管理与跟踪系统,适合各类网站使用,能够管理每个广告主拥有的多种任何尺寸横幅广告,按天查看,详细和概要统计并通过电子邮件发送报表给广告主。

自主开发

大型的互联网公司一般会选择自主开发广告系统与自身业务相结合,如百度,淘宝。也有一些独立的广告联盟或新创的广告公司(如在无线广告领域)会选择自主开发。

广告系统模块

广告投放服务器

广告投放服务器的规模是和流量以及广告数据量成正比的。当广告数据规模较小时,一台服务器的内存就能放下所有数据,如果同时这台服务器也能承担所有广告请求流量,那投放系统就能简化到一台机器;如果一台服务器不能承担所有流量,那么可以复制出同样的多台服务器共同承担广告请求流量,并在这个集群前使用负载均衡设备将流量均匀的分配给集群内的服务器;如果数据量太大以致于单机存不下所有数据,那么可以将广告数据按一定维度分区,存在一组服务器上,这组服务器提供不同分区的访问,对外则通过统一的接口供访问,调用者首先根据分区维度的参数值确定该分区的服务器,再向该服务器直接调用。

随着软硬件的不断升级和优化,现代的普通商用服务器单台每秒可处理几千次的广告请求。在实现上选用高性能的Web服务器,如Nginx或Lighttpd,可以支撑大量并发连接,降低处理每个广告请求的系统消耗。如果对性能的有极端的要求,有些广告系统甚至会自主开发Web服务器(如采用Erlang,C语言等),或裁剪通用开源Web服务器不需要的功能,而只保留核心功能。

K/V内存数据库

广告投放服务器对性能要求非常高,从收到广告请求到返回广告数据只有十几毫秒的时间完成决策和数据封装,而数据在内存中的读取速度相对最快,所以在广告投放服务器中,需要投放的广告数据都会保存在基于K/V的内存数据库中供快速读取。内存数据库和关系数据库中的数据通过消息队列保持同步,开源的K/V数据库有Memcache和Redis等。

关系数据库

广告系统需要采用关系数据库来存储结构化的数据,如:广告计划,推广单元,创意等。这些是至关重要的核心数据,对数据安全性要求很高,要求即使在出现硬件损坏的情况下,客户的数据仍然能保证完整。业界一般采用两种方法存储这些核心数据:采用商业关系数据库如:Oracle,或采用开源数据库如:Mysql或Postgresql。商业数据库功能完善,但价格往往不菲,当广告系统规模变大后,成比例上升的软件成本不可小觑,所以会逐渐转向采用开源的Mysql,其高可用性则通过多个集群及实时复制技术来保证。

在广告系统中对广告数据的管理会通过一个或多个基于Web的应用来实现。通常采用J2EE或LAMP开发框架来开发。随着互联网广告的广泛和深入使用,广告的业务系统功能也越来越多且复杂,从架构上广告业务系统也朝着服务化和开放方向发展。

消息中间件

广告数据在关系数据库的更新需要反应到投放服务器中的内存数据库,从而更新访客看到的广告。一般来说广告主对数据同步的延迟是有一定要求的,现代的广告系统也越来越往实时同步方向发展。

CDN

对于带有图片或视频等富媒体内容的广告创意,为了让访客的浏览器能较快的下载广告内容,需要将这些内容文件放在离访客地理位置较近的服务器上,这正是CDN系统的优势,广告系统往往需要租用或自己搭建CDN系统。

RPC框架

大型广告系统在实现服务化后,需要进行分布式通信的服务器越来越多,采用较好的RPC框架,可使得系统各个模块之间的通信方法标准化, 简化业务开发。

介绍一下Facebook开源的Thrift框架。Thrift是一种高效的、支持多种编程语言的远程服务调用的框架。互联网上比较流行的服务调用方式有很多种,例如基于 SOAP 消息格式的 Web Service,基于 JSON 消息格式的 RESTful 服务等。其中所用到的数据传输方式包括 XML,JSON 等,然而 XML 相对体积太大,传输效率低,JSON 体积较小,新颖,但还不够完善。Thrift则采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。

[Read More]

使用Github合作开发项目

本文大部分内容来自: https://help.github.com/categories/63/articles

Github上合作开发最好的方式是“Fork + Pull Request”。比如我最近需要一个静态Blog生成器,市面上有很多Ruby实现的,但我想要一个Go语言的实现,Github上找到了一个 https://github.com/wendal/gor ,测试了一下已有的功能基本能满足了,就用上了。

但实际使用过程中发现了一个问题,如果在URL中有中文,生成的URL如下没有做URL安全编码:

http://hugozhu.myalert.info/2013/02/27/在Pi和Github上搭建自己的个人博客.html

还有一种情况是如果URL中有空格,如:

http://hugozhu.myalert.info//2013//02/25/Java properties to enviorment variables.html

浏览器遇到这种URL时,会主动进行编码,但这里有两个问题:

  1. 遇到中文时,浏览器是用GBK还是UTF-8还是其它字符集编码后再发送给服务器呢?
  2. 遇到空格时,编码成+还是%20呢? 不同浏览器实现可能不一样,在不同操作系统上也可能不一样(可能和用户设置的缺省语言有关),这样有些用户可能会遭遇404错误了,实际上我在服务器的错误日志上的确看到这样的错误

日志:

2013/02/27 20:41:33 [error] 7791#0: *3285 open() ".../2013/02/25/Java+properties+to+enviorment+variables.html" failed (2: No such file or directory), client: 221.179.193.78, server: hugozhu.myalert.info, request: "GET /2013/02/25/Java+properties+to+enviorment+variables.html HTTP/1.1", host: "hugozhu.myalert.info"

于是我需要动手修改代码:

  1. 首先需要做的就是Fork一下原项目到自己的代码仓库: https://github.com/hugozhu/gor
  2. 修改好代码并提交到自己的仓库: https://github.com/hugozhu/gor/commit/db2784623d9df4d0652436efdbfbb9caccdc1e1d
  3. 在你的代码仓库页面上点Pull Request:
  4. 选择好你刚提交好的Commits,然后点发送;
  5. 原项目的维护者就会收到这个Pull Request: https://github.com/wendal/gor/pull/14
  6. 如果你提交的代码足够好,维护者可以合并到项目主干上;
  7. 记住下一次本地修改代码前要先Merge一下原作者新提交的改动;

如下:

git remote add upstream https://github.com/wendal/gor
git fetch upstream
git checkout master
git merge upstream/master 

到此为止就完成了一次合作开发。

[Read More]

在Pi和Github上搭建自己的个人博客

方法如下:

本站同时托管在家里的Raspberry PiGithub Pages上,并同步保持更新,海外用户会访问Github,国内用户则会访问Pi,不同线路解析域名hugozhu.myalert.info到不同的服务器是通过DnsPod的服务实现的,这么好的服务还是免费的,这里推荐一下。

因为Github Pages只能支持静态网页,你需要一个能生成静态网页的博客生成引擎。我使用的是gor , 也可以使用ruhohGoogle一下还有很多

静态页面博客的好处:

  1. 性能是最好的,很合适用Raspberry Pi来做服务器,节省资源;
  2. 文章可以用Markdown格式来编写,采用Github来做版本控制,我的Blog仓库在 http://github.com/hugozhu/blog ,数据安全很好,误删除也不担心了;
  3. 很容易找到托管环境,方便迁移;
  4. 用Gor在Pi上生成速度很快;再用Nginx提供Web服务,可以直接在Pi上写Blog;
  5. 大繁至简

Github设置

  1. 在你的仓库里增加一个your_github_id.github.com,比如我的github ID是hugozhu,相应的仓库名就是hugozhu.github.com,这个仓库也就是网站的根目录了,在这里放生成好的静态文件
  2. 如果你需要用自己的域名,而不是Github提供的,可以在根目录下增加一个CNAME文件,文件内容则是你的域名,在DnsPod上需要建一个CNAME记录,将你的域名指向your_github_id.github.com. 也就是github原来分配给你的,完成这个设置后,访问your_github_id.github.com会跳转到你的域名;
  3. 每次更新后,Github会在10分钟内生效。

更新博客

  1. Gor的使用详细说明可见 https://github.com/wendal/gor
  2. 我的整个网站的内容也通过Github开源了: https://github.com/hugozhu/blog

以我的网站为例:

git clone https://github.com/hugozhu/blog
    Cloning into 'blog'...
    remote: Counting objects: 190, done.
    remote: Compressing objects: 100% (146/146), done.
    remote: Total 190 (delta 81), reused 132 (delta 23)
    Receiving objects: 100% (190/190), 155.48 KiB | 171 KiB/s, done.
    Resolving deltas: 100% (81/81), done.
cd blog
gor compile
    2013/02/27 13:17:19 gor.go:21: gor ver 2.1
    2013/02/27 13:17:19 payload.go:572: Load Layout : default
    2013/02/27 13:17:19 payload.go:572: Load Layout : page
    2013/02/27 13:17:19 payload.go:572: Load Layout : post
    2013/02/27 13:17:19 config.go:61: Look lile a Json, try it
    2013/02/27 13:17:19 config.go:64: It is Json Map
    2013/02/27 13:17:19 widgets.go:111: Load widget from  widgets/analytics/config.yml
    2013/02/27 13:17:19 widgets.go:111: Load widget from  widgets/comments/config.yml
    2013/02/27 13:17:19 widgets.go:111: Load widget from  widgets/google_prettify/config.yml
    2013/02/27 13:17:19 compile.go:125: Done
cd compiled
git init
git add -A 
git commit -m "update website" .
git remote add origin hugozhu@github.com:hugozhu/hugozhu.github.com
git push -u origin master

最后等待10分钟,再打开 http://hugozhu.github.com 就好了。。。

[Read More]

动态DNS程序

动态根据宽带public ip更新dnspod登记的域名

按照 https://gist.github.com/833369 逻辑重新用Go实现了,用更少的内存开销在Raspberry Pi上跑。

替换上你的Email,密码,域名ID,记录ID等参数,就可以运行了。 会在后台一直运行,每隔30秒检查一遍IP,如果修改了就更新IP。

获得domain_id可以用:

curl curl -k https://dnsapi.cn/Domain.List -d "login_email=xxx&login_password=xxx" 

获得record_id:

curl -k https://dnsapi.cn/Record.List -d "login_email=xxx&login_password=xxx&domain_id=xxx"

将Java的Properties文件转换成环境变量

Overview

在Java程序中使用properties文件很方便,但有时候需要和脚本配合使用时,需要把properties文件内的多个变量转换成环境变量,本文提供一个转换脚本示范:

比如env.properties如下(=附近可以有空格,也可以有空行)

MYSQL_URL = jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=gbk
MYSQL_USER = root
MYSQL_PASS = 

执行下面的脚本后就相当于

export MYSQL_URL="//localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=gbk"
export MYSQL_USER="root"
export MYSQL_PASS="" 

env.sh脚本代码

#!/bin/bash

property_file=env.properties

get_prop(){
    propfile=$1
    key=$2
    grep  "^${2}=" ${1}| sed "s%${2}=\(.*\)%\1%"
}

trim() {
    trimmed=$1
    trimmed=${trimmed%% }
    trimmed=${trimmed## }
    echo "$trimmed"
}

`grep -v "^#" $property_file | sed -e '/^$/d' | while read line
do
    key=$(echo $line | awk -F "=" '{print $1}')
    trimmed_key=$(trim $key)
    trimmed_val=$(trim $(get_prop $property_file "$key")
    echo "export $trimmed_key=\"$trimmed_val\")"
done`