基于redis做打败全国用户排名

2013年11月11日 13:45

360引领了开机速度,打败全国百分之多少的用户,很多产品纷纷开始模仿。基于redis可以快速构建一个性能优良的排名系统,假设用户每次操作一个url后获得一个分数,计算得到该分数在所有url中的排名。由于所有url可能是非常庞大的量级,用树的结构进行插入,排序,查询是效率较高的操作,基于redis的zset可以快速构建原型。

redis.zadd("sitescore",url)           # O(1)
total = redis.zcount("sitescore")     # O(1)
rank = redis.zrank("sitescore",url)   # O(log(n))

puts "打败了全国#{(rank * 100.0 / total).round(2)}%的网站"

简单测试,在10万级别的样本中,0.2秒左右可以得到打败全国网站的百分比

windows虚拟机批量安装程序

2013年11月01日 11:28

需要在几百台虚拟机上部署开发好的监测软件,运维人员表示手工操作要崩溃的。我想应该有一种方法可以实现自动安装,调研一下,要做到自动安装,需要以下几个条件。
1. psTools工具包,可以远程执行命令
2. 安装包需要支持静默安装
3. 需要通过命令行下载安装包
 
psexec.exe可以远程执行命令,提供远程windows虚拟机的ip和具有管理员权限的用户名和密码,就可以远程执行本地命令。
 
psexec.exe \\ip_addr -u username -p password  -c -w C:\  runupdate.bat
 
使用nsis制作安装包,使用/S参数时,nsis安装包会静默安装,在脚本中安装完成的地方加入
Exec “第一个启动文件”
在静默安装完成后,就立即启动。
 
使用http下载文件,将安装包放置于linux主机的web目录下,使用网上分享的bat支持http下载脚本即可,需要注意windows不区分大小写,文件名全部用小写防止出错。
 
echo=1/*>nul&@cls
@echo off

call :http "http://xxx.com/download/installer.exe" C:\installer.exe



c:
C:\installer.exe /S

if %ERRORLEVEL% NEQ 0 goto failed
echo "installer updated DONE!"

cd ..
echo %computername% %date% %time% >> ComputerList.txt
echo "Updating of installer succeeded. "
goto end

:faile
echo $%computername% %date% %time% >> ComputerList.txt
echo "Updating of installer failed. "

:end
c:

pause
goto :eof

::-----------------下面是函数定义区域-----------------
:http
echo Source:      "%~1"
echo Destination: "%~f2"
echo Start downloading. . .
cscript -nologo -e:jscript "%~f0" "%~1" "%~2"
echo OK!
goto :eof

*/
var iLocal,iRemote,xPost,sGet;
iLocal =WScript.Arguments(1); 
iRemote = WScript.Arguments(0); 
iLocal=iLocal.toLowerCase();
iRemote=iRemote.toLowerCase();
xPost = new ActiveXObject("Microsoft"+String.fromCharCode(0x2e)+"XMLHTTP");
xPost.Open("GET",iRemote,0);
xPost.Send();
sGet = new ActiveXObject("ADODB"+String.fromCharCode(0x2e)+"Stream");
sGet.Mode = 3;
sGet.Type = 1; 
sGet.Open(); 
sGet.Write(xPost.responseBody);
sGet.SaveToFile(iLocal,2); 
 
本文参考:

http://blog.csdn.net/cneducation/article/details/3997166

下载脚本参考:

http://www.newlifex.com/showtopic-149.aspx

php守护进程daemon

2013年10月22日 19:30

项目中很多功能是php写的,其中一个数据解析模块需要一直运行,一开始是crontab方式批量执行,但是这样无法满足项目对于实时性的需求,需要将模块以服务方式运行。php可以像C一样创建守护进程。参考文章

//Run as daemon process.
function run()
{
    if(($pid1 = pcntl_fork()) === 0)
    {
        posix_setsid(); //Set first child process as the session leader.

        if(($pid2 = pcntl_fork()) === 0)
        {
            //Second child process, which run as daemon.
            speedbench_data_check(); 
        }
        else
        {
            //First child process exit;
            exit;
        }
    }
    else
    {
        //Wait for first child process exit;
        pcntl_wait($status);
    }
}

//Entry point.
run();

run函数负责将整个程序变为daemon process,方法和Unix环境下C的方法很类似,通过两次fork,第一次fork后调用setsid将子进程1变为session leader,这样就可以让子进程2与其祖先detach,即使祖先进程结束了它也会继续运行。

使用crontab监控并重启进程

#!/bin/bash
#check speedbench_data_daemon process and restart if down
DATE=`date -d "today" +"%Y-%m-%d-%H:%M"`
#用ps命令查看php speedbench_data_daemon.php进程
MM=`ps aux |grep "speedbench_data_daemon.php" | grep -v "grep" |wc -l`
#if语句判断进程是否存在,如果不存在,输出日志记录并重启speedbench_data_daemon
if [ "$MM" == "0" ]; then 
    echo "$DATE speedbench_data_daemon restart" >> path/to/speedbench_data_daemon.log
    path/to/php path/to/speedbench_data_daemon.php >> path/to/speedbench_data_daemon.log 2>>path/to/speedbench_data_daemon.log.wf
else
  echo "$DATE speedbench_data_daemon $MM ok"
fi 

ruby字符串eval

2013年10月17日 17:21

在任务分发系统中,任务的内容使固定的,但是ID是根据时间动态生成的,一开始任务是在发送之前拼装的。后面任务的种类增加了,有的任务ID需要出现2次,发送前拼装就显得捉襟见肘了。ruby的string eval正好可以解决这个问题。先生成pattern,在执行的时候,用相应的需要替换的内容填充。用模式匹配,然后替换也可以,但是这样对于任务种类增加的时候,需要随之进行修改。

direct_content = 'Test ID=#{test_id}\n' + 
                 'url=script://#{test_id}.pts\n' + 
                 ...


test_id = rand(1000)_Time.new.strftime("%Y-%m-%d-%H")

puts eval( '"' + direct_content  + '"')

直接使用eval存在安全隐患,但是我的应用中不会存在,因为这些字符串是我自己的程序构建的。

redis键复制

2013年10月11日 11:52

redis并没有提供key的复制命令,但是可以通过redis提供的lua脚本实现。

require 'rubygems'
require 'redis'

r = Redis.new

KeyCopyScript = <<EOF
    local max_key = tonumber(ARGV[1])
    local i = 0
    while(i <= max_key) do
      local key_1 = "account:" .. i .. ":" .. KEYS[1]
      local res = redis.call('exists',key_1)
      if( res )
      then
        local key_2 = "account:" .. i .. ":" .. KEYS[2]
        local val = redis.call('get',key_1)
        redis.call('set',key_2,val)
        i = i + 1
      end
    end
EOF

puts r.eval(KeyCopyScript,["value_src","value_dest"],[2])

ajax的jsonp使用gzip压缩

2013年9月29日 11:04

管理界面需要加载所有用户信息,jsonp返回数据量达到12M,加载体验很差。ajax支持对返回的json,jsonp的gzip压缩。需要前端和后端一起配合修改。

.ajax({
  url:url,
  dataType:'jsonp',
  headers : {'Accept-Encoding' : 'gzip'},
  type: 'get'
})

后端只要修改web服务器配置就可以,比如我是nginx+sinatra,只需要修改nginx配置文件

    gzip  on;
    gzip_min_length  102480;
    gzip_buffers     4 8k;
    gzip_types       text/plain application/x-javascript text/css application/xml application/json;

clockwork+sidekiq做耗时任务

2013年9月28日 10:29

sidekiq是一个基于colluloid的任务队列系统,在使用sidekiq之前,我自己用clockwork和redis构建了一个任务调度和分发系统,其中有一些统计的工作,需要遍历redis中的所有记录。随着记录的不断增长,遍历一次的时间越来越长,影响到clockwork的工作了,于是将这部分耗时的工作丢给sidekiq处理。

将原来的类变成sidekiq的worker,将所有这些类的rb文件在sidekiq_env.rb中require进来。

class HardJob
  include Sidekiq::Worker
  def perform opt=:take_snapshot
    case opt.to_sym
    when :take_snapshot
      HardJob.new.take_snapshot
    when
    ...
  end
  ...
end

clockwork的脚本中,require "sidekiq_env.rb"

HardJob.delay.perform_async :take_snapshot

在另一个终端中运行

sidekiq -r ./sidekiq_env.rb

从日志中可以看到sidekiq开始异步执行任务。

redis pipeline

2013年9月27日 18:50

 
在redis的一个集合中有上万的元素,这些元素对应对象属性有几个键值,原来通过逐个读取的方式访问,获取集合中所有元素的信息需要300多秒,严重影响性能,google后发现,redis提供pipeline来获取大量数据,改用pipeline实现后缩短到20秒
 
    $redis = Redis.new
    keys = $redis.smembers("userlist")
    res_ids = $redis.pipelined do
      keys.each do |client_id|
        $redis.get("account:#{client_id}:id")
      end 
    end 
    res = $redis.pipelined do
      res_ids.each do |id|
        x = "account:"+id
        lastlogin = $redis.hmget("#{x}:lastlogin","ip","time")
        firstlogin = $redis.hmget("#{x}:firstlogin","ip","time")
        multi_value = $redis.mget(x+":available",x+":network_type",x+":traffic_used",x+":traffic_wifi_used",
                   x+":tasks_today",x+":max_traffic",x+":tasks_wifi_today",x+":tasks_3g_today",
                   x+":tasks_wifi_finished",x+":tasks_3g_finished",x+":tasks_wifi_arrive",
                   x+":tasks_3g_arrive",x+":tasks_wifi_success",x+":tasks_3g_success",
                   x+":month_3g_traffic",x+":month_3g_traffic_yestoday",x+":month_2g_traffic",
                   x+":month_2g_traffic_yestoday",x+":month_wifi_traffic",
                   x+":month_wifi_traffic_yestoday",x+":system_info",x+":province_name",
                   x+":city_name",x+":isp_name",x+":software_version",
                   x+":task:send",x+":task:arrive",x+":task:finish",x+":task:pcapempty",
                   x+":tasks_3g_yestoday",x+":tasks_wifi_yestoday")
       [lastlogin , firstlogin , multi_value ]
      end 
    end 

 

eventmachine网络学习

2013年9月24日 21:10

EventMachine是Ruby一个基于Reactor设计模式的、用于网络编程和并发编程的事件驱动框架。

EventMachine的rdoc写的很详细,EventMachine可以处理任何协议,一些基本的协议已经实现,非常适合做服务器开发。

 

require 'eventmachine'
class Echo < EM::Connection
 def receive_data(data)
send_data(data)
 end
end
EM.run do
 EM.start_server("0.0.0.0", 10000, Echo)
end

主要有4个回调方法:

  • post_init 在实例初始化的时候调用
  • connection_completed 在连接建立后调用
  • receive_data(data) 当接收到客户端的数据时调用,数据以chunk的到达,需要自己负责处理
  • unbind 当客户端断开连接后调用

作为一个练习,掌握eventmachine的基本用法,这里是一个很好的例子。

快速的给小团队的开发人员架设一个git服务器

架设git服务器

添加git用户,并设置密码,无密码无法登录

# adduser git
# passwd git  
# su git
$ cd
$ mkdir .ssh

把开发者的 SSH 公钥添加到这个用户的 authorized_keys 文件中。然后在目录下创建空仓库:

mkdir project.git
$ cd project.git
$ git –bare init

这样就完成了git服务器的架设。

 

工程师提交

$ git init
$ git add .
$ git commit -m ‘initial commit’
$ git remote add origin git@gitserver:/home/git/project.git
$ git push origin master

这样就有一个小团体使用的git服务器,可以协作开发。