Winse Blog

走走停停都是风景, 熙熙攘攘都向最好, 忙忙碌碌都为明朝, 何畏之.

批量下载163-open的视频

163的视频资源还是挺丰富的,默认情况下必须用客户端下载。感觉挺麻烦的,对于网络慢的情况可能要下很久,如果能用下载工具批量下载就好了。

首先从视频播放页面获取下载的地址(查看下载按钮的js):

1
appsrc : 'http://mov.bn.netease.com/open-movie/nos/mp4/2015/01/19/SAFDACJPD_sd.m3u8',
  • 然后从列表页获取列表所有视频的详情URL,
  • 然后从详情URL获取appsrc的地址,
  • 最后把后缀m3u8改成mp4。当然还可以把视频的名称优化下。

下面结合【chrome调试工具】、【notepad++】、【shell】来获取视频的下载地址:

  1. F12使用js获取列表详情URL
1
$("#list2").find("tr >td.u-ctitle a").each(function(i,node){console.log($(node).attr("href"));})

使用notepad++的列处理功能 处理chrome输出的信息只留下URL保存到url.txt,如 http://open.163.com/movie/2008/1/1/H/M6SGF6VB4_M6SGL3P1H.html

这里用同样的步骤也把视频列表的名称获取到,用于后面的视频重命名:

1
$("#list2").find("tr >td.u-ctitle a").each(function(i,node){console.log($(node).text());})
  1. 使用SHELL获取详情页面的URL
1
$ cat url.txt | while read line ; do if [ "$line" != '' ] ; then curl -s $line | grep -a appsrc ; fi ; done

获取到的数据同样包含一些不需要的信息,使用notepad++进行裁剪。提取 http://mov.bn.netease.com/open-movie/nos/mp4/2015/01/19/SAFDCDGNA_sd.m3u8 的视频地址,使用replace把m3u8替换成mp4,然后把URL复制到迅雷下载的下载框即可批量下载了。

  1. 重命名

给获取到的视频的名称加上顺序标识,然后弄成重命名的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mv SAFD8B131_sd.mp4  01机器学习的动机与应用.mp4
mv SAFD8D355_sd.mp4  02监督学习应用.梯度下降.mp4
mv SAFD8O947_sd.mp4  03欠拟合与过拟合的概念.mp4
mv SAFD8PQO8_sd.mp4  04牛顿方法.mp4
mv SAFD94US8_sd.mp4  05生成学习算法.mp4
mv SAFD97CGO_sd.mp4  06朴素贝叶斯算法.mp4
mv SAFD9I82D_sd.mp4  07最优间隔分类器问题.mp4
mv SAFD9L3B9_sd.mp4  08顺序最小优化算法.mp4
mv SAFD9VGEO_sd.mp4  09经验风险最小化.mp4
mv SAFDA37TS_sd.mp4  10特征选择.mp4
mv SAFDACJPD_sd.mp4  11贝叶斯统计正则化.mp4
mv SAFDAHG1C_sd.mp4  12K-means算法.mp4
mv SAFDAQ19J_sd.mp4  13高斯混合模型.mp4
mv SAFDB0UUS_sd.mp4  14主成分分析法.mp4
mv SAFDB7QLL_sd.mp4  15奇异值分解.mp4
mv SAFDBF188_sd.mp4  16马尔可夫决策过程.mp4
mv SAFDBM41T_sd.mp4  17离散与维数灾难.mp4
mv SAFDBTL6V_sd.mp4  18线性二次型调节控制.mp4
mv SAFDC4B84_sd.mp4  19微分动态规划.mp4
mv SAFDCDGNA_sd.mp4  20策略搜索.mp4

以上几个步骤就能完美的实现视频的下载。步骤比较多,但是还是很有成就感啊,程序员的逗逼方法。

几年前下载TED视频的脚本(全部用脚本来实现,其实拆分步骤或许是更好的选择简单些):

1
2
3
4
5
6
7
8
wget -qO- http://open.163.com/ted/ | iconv -f gbk -t utf-8 | awk '{if($0 ~ /<a href="http:\/\/v\.163\.com\/movie/ ){print}}' \  
 | sed -n 's/.*<a href="\([^"]*\)".*/\1/p' \  
 | while read url  
do   
        echo $url;  
        wget -qO- "$url" | iconv -f gbk -t utf-8 | awk '/appsrc: \047http:\/\//{if(match($0,/http:[^\047]*/))print substr($0,RSTART,RLENGTH);}' \  
 | sed -e s/-list\.m3u8/.mp4/ -e s/movie/movieMP4/  
done  

–END

Zookeeper节点切换

更新:下面的操作都是基于不停机不停服务的前提下的。如果可以停服务的话,你想怎么折腾就怎么折腾(只要zoo.cfg和myid一致就行)。

收告警邮件实在是收到烦了,zookeeper实例的机器挂掉了,机器一直没人处理。最后最终还是改了告警的脚本(呵呵,等到快出问题的时刻才告警)。

过程中也尝试了添加删除节点,下面是对本次体验的一些记载。

告警的检查脚本帖出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
msg=`$HADOOP_HOME/bin/hdfs getconf -confKey ha.zookeeper.quorum 2>/dev/null`

zks_total=`echo "$msg" | awk 'BEGIN{RS=","; } {print}' | grep -v '^$' `
total_count=`echo "$zks_total" | wc -l `

lost_zks=`echo "$zks_total" |  while read zk  ; do if ! echo mntr | nc ${zk//:/ } | grep zk_server_state >/dev/null ; then echo "$zk " ; fi ; done  `
lost_count=`echo "$lost_zks" | grep -v "^$" | wc -l ` 
lost_zks=`echo $lost_zks `

message="Zookeepers total: $total_count, dead: $lost_count"
if  [[ "$lost_count" != 0 ]]
then
  message="$message;  Dead: $lost_zks"
fi 

if (( $lost_count*2 > $total_count )) ; then
        echo "CRITICAL - $message"
        exit 2
elif (( $total_count/2 == $lost_count )) ; then
        echo "WARNING - $message"
        exit 1
else 
        echo "OK - $message"
        exit 0
fi

zookeeper3.5

zookeeper3.5的版本已经有动态增删节点的功能。

手动割接问题节点

生产的是3.4的,不支持reconfig的命令。这里使用 rolling restarts 手动切换的方式来进行割接,在测试环境通过不同的端口来模拟3台机器:

割接的时刻,最好一台台的加,不然可能会出现数据不一致的情况:https://www.slideshare.net/Hadoop_Summit/dynamic-reconfiguration-of-zookeeper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[hadoop@cu3 zktest]$ mv zoo_sample.cfg zoo1.cfg
[hadoop@cu3 zktest]$ sed -e 's/12181/22181/' -e 's/data1/data2/' zoo1.cfg >zoo2.cfg
[hadoop@cu3 zktest]$ sed -e 's/12181/32181/' -e 's/data1/data3/' zoo1.cfg >zoo3.cfg
[hadoop@cu3 zktest]$ cat zoo3.cfg 
tickTime=2000
initLimit=10
syncLimit=5
#maxClientCnxns=60

dataDir=/home/hadoop/zktest/data3
clientPort=32181

server.1=localhost:13888:13999
server.2=localhost:23888:23999
server.3=localhost:33888:33999

[hadoop@cu3 zktest]$ for i in {1..3} ; do echo $i >data$i/myid ; done 

# 添加两个便利的函数
[hadoop@cu3 zktest]$ function zkstat { 
> for i in {1..4} ; do ( echo "${i}2181 => `cat data$i/zookeeper_server.pid` : `echo mntr | nc localhost ${i}2181 | grep zk_server_state | awk '{print $2}' ` " ) ; done
> }

[hadoop@cu3 zktest]$ function zkstart { 
> for i in "$@" ; do (cd data$i ; ~/zookeeper-3.4.6/bin/zkServer.sh start /home/hadoop/zktest/zoo$i.cfg ) ; done
> }

[hadoop@cu3 zktest]$ zkstart {1..3}

切换时,Leader一直不变

模拟server.1进程down掉,用一个新的server.4代替: 切换的过程中不能停leader!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 配置server.4
[hadoop@cu3 zktest]$ sed -e 's/12181/42181/' -e 's/data1/data4/' zoo1.cfg >zoo4.cfg
[hadoop@cu3 zktest]$ mkdir data4
[hadoop@cu3 zktest]$ echo 4 > data4/myid

# 去掉server.1,添加server.4
[hadoop@cu3 zktest]$ vi zoo4.cfg 
...
server.4=localhost:43888:43999

[hadoop@cu3 zktest]$ zkstat
12181 => 20750 : follower 
22181 => 21037 : leader 
32181 => 21075 : follower 
42181 => 19757 :  

# 停server.1
[hadoop@cu3 zktest]$ kill 20750

# 启动server.4
[hadoop@cu3 zktest]$ zkstart {2..4}
[hadoop@cu3 zktest]$ zkstat 
12181 => 20750 :  
22181 => 21037 : leader 
32181 => 21075 : follower 
42181 => 21246 : follower 

此时server.4是新的配置,server.2和server.3是旧的配置。

# 停server.3,注意这里不能停leader!!
[hadoop@cu3 zktest]$ kill 21075
[hadoop@cu3 zktest]$ zkstat
12181 => 20750 :  
22181 => 21037 : leader 
32181 => 21075 :  
42181 => 21246 : follower 

# server.3的配置:server.1换成server.4
[hadoop@cu3 zktest]$ vi zoo3.cfg 
[hadoop@cu3 zktest]$ zkstart 3
JMX enabled by default
Using config: /home/hadoop/zktest/zoo3.cfg
Starting zookeeper ... STARTED
[hadoop@cu3 zktest]$ zkstat
12181 => 20750 :  
22181 => 21037 : leader 
32181 => 21791 : follower 
42181 => 21246 : follower 

3个server有两个已经是新的配置,现在停掉leader后重新选举也是ok的。

# 最后停leader,修改zoo2.cfg。集群down节点成功切换!!
[hadoop@cu3 zktest]$ zkstat
12181 => 20750 :  
22181 => 22044 : follower 
32181 => 21791 : follower 
42181 => 21246 : leader 

中间停Leader,重新选领导失败

现在再测试下中间过程停leader会是什么效果呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 先zoo4挂掉了,用zoo1来补充。
[hadoop@cu3 zktest]$ zkstat
12181 => 20750 :  
22181 => 22044 : leader 
32181 => 21791 : follower 
42181 => 21246 : 

配置 zoo1: 

# 修改zoo1的配置 和 myid,不能用原来的旧id: Have smaller server identifier, so dropping the connection: (3, 1)
server.5=localhost:13888:13999
server.2=localhost:23888:23999
server.3=localhost:33888:33999

# 此时zoo2,zoo3的配置为:
server.2=localhost:23888:23999
server.3=localhost:33888:33999
server.4=localhost:43888:43999

# 启动zoo1(id=5)
[hadoop@cu3 zktest]$ zkstart 1
[hadoop@cu3 zktest]$ zkstat
12181 => 22439 : follower 
22181 => 22044 : leader 
32181 => 21791 : follower 
42181 => 21246 :  

如果这里停的leader,zoo1收不到大于1/2的投票?
(觉得:只能在配置里面server才会被接受选票,
所以停了zoo2(leader)后,zoo3配置里面的server就只有自己了,zoo3也就拒接服务,然后接着zoo1(id=5)也拒接服务)

[hadoop@cu3 zktest]$ kill 22044
[hadoop@cu3 zktest]$ zkstat
12181 => 22439 :  
22181 => 22044 :  
32181 => 21791 :  
42181 => 21246 :  
[hadoop@cu3 zktest]$ jps -m | grep zktest
21791 QuorumPeerMain /home/hadoop/zktest/zoo3.cfg
22439 QuorumPeerMain /home/hadoop/zktest/zoo1.cfg

服务挂了!!

所以说5台zookeeper还是很有必要的,5台的话挂掉一台,Leader在切换的过程中停掉了其他三台机器也能正常选举出新的Leader。

正常切换后,应用不需要修改。只要zkserver中的一台zk服务器能连接就可以了。但可能监控的需要进行修改,因为原来是监控所有服务的,配置可能需要进行相应的修改。

–END

Puppet批量修改用户密码

  1. 先在一台机器修改成想要修改的密码,然后获取该用户的信息。
1
2
3
4
5
6
7
8
9
10
11
12
[root@hadoop-master1 ~]# puppet resource user root
user { 'root':
  ensure           => 'present',
  comment          => 'root',
  gid              => '0',
  home             => '/root',
  password         => '$6$C6EXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
  password_max_age => '99999',
  password_min_age => '0',
  shell            => '/bin/bash',
  uid              => '0',
}
  1. 保存到文件 user.pp ,使用 puppet apply user.pp 测试看看文件是否有问题。毕竟是生产,出了问题就要进机房的啊,谨慎点好。

  2. 把用户的资源信息写入的site.pp(不知道是啥的话,去看看puppet的文档先)。先搞几台机器测试下 puppet agent -t

  3. 然后使用 mco puppet runall 10 全部同步进行密码修改。或者 mco shell run -- /opt/puppetlabs/bin/puppet agent -t

–END

Maven压缩js/css功能实践

为了节约网络带宽,一般在发布项目时对资源(js/css)文件进行压缩(去掉空行、精简代码等)。但是要做到兼容开发与生产还是的下一番功夫才行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ls -l src/main/webapp/static/assets/js/ | head
total 3120
-rwxrwxr--+ 1 winse None  24804 Aug 10 17:40 bootbox.js
-rwxrwxr--+ 1 winse None  71315 Aug 10 17:40 bootstrap.js
-rwxrwxr--+ 1 winse None  13905 Aug 10 17:40 bootstrap-colorpicker.js
-rwxrwxr--+ 1 winse None  49319 Aug 10 17:40 bootstrap-multiselect.js
...

$ ls -l target/dist/js/ | head
total 1368
-rwxrwx---+ 1 winse None   8943 Aug 19 16:53 bootbox-min.js
-rwxrwx---+ 1 winse None   8057 Aug 19 16:53 bootstrap-colorpicker-min.js
-rwxrwx---+ 1 winse None  38061 Aug 19 16:53 bootstrap-min.js
-rwxrwx---+ 1 winse None  18232 Aug 19 16:53 bootstrap-multiselect-min.js
...

项目中原本使用dist(压缩)、assets目录放置js/css等资源,在部署的时刻替换dist为assets,有点麻烦。首先想到的用nginx进行url重写,但是需要增加一个服务有点麻烦,能不能直接用spring来实现呢?

  • 自定义一个handler类

查看Spring的 mvc:resources 实现,相当于注册了一个 location -> ResourceHttpRequestHandler 的映射。 第一种尝试自动化的方式就是自定义handler类来进行资源的定位。增加 StaticRequestHandler 的处理类,增加配置 location 和 compressLocation 的配置:首先去查找压缩文件([NAME]-min.js),找不到然后再找源文件([NAME].js)位置。

主要修改 getResource 方法,具体完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
## java
public class StaticRequestHandler extends ResourceHttpRequestHandler {

  private final static Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);

  private String location;
  private String compressLocation;

  private Resource locationResource;
  private Resource compressLocationResource;

  public void setLocation(String location) {
      this.location = location;
  }

  public void setCompressLocation(String compressLocation) {
      this.compressLocation = compressLocation;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
      super.afterPropertiesSet();

      this.locationResource = getWebApplicationContext().getResource(location);
      super.setLocations(Collections.singletonList(this.locationResource));

      this.compressLocationResource = getWebApplicationContext().getResource(compressLocation);
  }

  @Override
  protected Resource getResource(HttpServletRequest request) {
      String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
      if (path == null) {
          throw new IllegalStateException("Required request attribute '"
                  + HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set");
      }

      if (!StringUtils.hasText(path) || isInvalidPath(path)) {
          if (logger.isDebugEnabled()) {
              logger.debug("Ignoring invalid resource path [" + path + "]");
          }
          return null;
      }

      Resource res = null;
      if (path.endsWith(".css")) {
          res = findResource(compressLocationResource, path.substring(0, path.length() - 4) + ".min.css");
      } else if (path.endsWith(".js")) {
          res = findResource(compressLocationResource, path.substring(0, path.length() - 3) + ".min.js");
      }

      if (res == null) {
          res = findResource(locationResource, path);
      }

      return res;
  }

  private Resource findResource(Resource location, String path) {
      try {
          if (logger.isDebugEnabled()) {
              logger.debug("Trying relative path [" + path + "] against base location: " + location);
          }
          Resource resource = location.createRelative(path);
          if (resource.exists() && resource.isReadable()) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Found matching resource: " + resource);
              }
              return resource;
          } else if (logger.isTraceEnabled()) {
              logger.trace("Relative resource doesn't exist or isn't readable: " + resource);
          }
      } catch (IOException ex) {
          logger.debug("Failed to create relative resource - trying next resource location", ex);
      }

      return null;
  }

}

## spring config
  <!-- 静态资源 -->
  <!-- <mvc:resources mapping="/static/**" location="/static/" /> -->

  <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
      <property name="mappings">
          <value>
              /static/assets/**=staticRequestHandler
          </value>
      </property>
  </bean>
  <bean id="staticRequestHandler" class="com.hotel.servlet.resource.StaticRequestHandler">
      <property name="location" value="/static/assets/" />
      <property name="compressLocation" value="/static/dist/" />
  </bean>

这种方式实现了自动定位压缩资源 min.js 的功能,但是压缩还是不能自动化而且不能实时的更新(min要单独压缩产生),并且调试和生产环境还是需要手动的修改配置来切换。

有没有更好的自动化的实现开发环境和生产环境分开呢?

  • Maven打包时压缩然后替换源文件

使用 yuicompressor-maven-plugin 插件压缩资源,然后把压缩资源打包放置到assets目录下。

注意: yuicomressor 插件的 nosuffix 配置为 true ! 这样压缩后的文件名和源文件名称才一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
## spring config
  <!-- 静态资源 -->
  <mvc:resources mapping="/static/**" location="/static/" />

## maven pom.xml
      <profile>
          <id>release</id>

          <build>
              <plugins>
                  <!-- http://alchim.sourceforge.net/yuicompressor-maven-plugin/compress-mojo.html -->
                  <plugin>
                      <groupId>net.alchim31.maven</groupId>
                      <artifactId>yuicompressor-maven-plugin</artifactId>
                      <version>1.3.2</version>
                      <executions>
                          <execution>
                              <id>compress_js_css</id>
                              <phase>process-resources</phase>
                              <goals>
                                  <goal>compress</goal>
                              </goals>
                          </execution>
                      </executions>
                      <configuration>
                          <encoding>UTF-8</encoding>
                          <nosuffix>true</nosuffix>
                          <skip>false</skip>

                          <jswarn>false</jswarn>
                          <nomunge>false</nomunge>
                          <preserveAllSemiColons>false</preserveAllSemiColons>

                          <sourceDirectory>src/main/webapp/static/assets</sourceDirectory>
                          <outputDirectory>${project.build.directory}/dist</outputDirectory>
                      </configuration>
                  </plugin>

                  <plugin>
                      <artifactId>maven-war-plugin</artifactId>
                      <version>2.6</version>
                      <configuration>
                          <webResources>
                              <resource>
                                  <directory>${project.build.directory}/dist</directory>
                                  <targetPath>static/assets</targetPath>
                                  <filtering>false</filtering>
                              </resource>
                          </webResources>
                      </configuration>
                  </plugin>
                  
              </plugins>
          </build>
      </profile>

war插件添加了自定义webResources资源,首先把压缩的文件拷贝到对应目录,maven发现文件已经存在就不会再拷贝同名的文件。这样源文件就相当于被替换成压缩的资源了。

总结

使用maven插件压缩打包,完美的解决js/css压缩导致的开发和生产不兼容问题。

后记

jsp使用了tag的地方总是会产生很多的空行,看着挺烦的。其实可以通过在jsp开头添加 trimDirectiveWhitespaces 属性来去掉空行:

1
<%@ page language="java" trimDirectiveWhitespaces="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

–END

Redis批量操作

紧接上一篇优化的文章,在生产进行shard分库/分片操作后,部分大量zsort键值对拆散到多个redis实例。原来统计总量的命令现在需要汇总后才行。

今天就来说说Redis里面的批量操作,批量操作其实就是循环,把大部分的工作让机器做了。逻辑比较复杂的用比较实现,功能简单的用脚本即可。

  • 单机用lua脚本
1
2
3
# redis-cli

eval "local aks=redis.call('keys', '*'); local res=0; for i,r in ipairs(aks) do res=res+redis.call('hlen', r) end; return res" 0

通过keys获取所有的键(都是hash类型),然后计算出匹配的hash包括的键值对总数。

同一个实例的小数据量的统计,lua脚本优势还是比较明显的:redis自带的还能编程。

  • shell脚本

看官网的文档:the Redis command line interface

1
2
3
4
5
6
7
8
9
10
11
12
13
## shell

cat commands | redis-cli --raw

## redis-cli

127.0.0.1:6379> CONFIG RESETSTAT
OK
...
127.0.0.1:6379> info stats
# Stats
total_connections_received:2
...

注意:不要用循环然后调用redis-cli COMMAND,这种方式会产生很多的TCP连接,如果要执行的命令太多,可能导致TCP端口不够用。

还有 redis-cli --statredis-cli --scanredis-cli monitor 这些命令挺有意思的。

  • 直接tcp连接管道操作
1
2
3
4
5
6
7
8
9
10
11
## shell

sed 's/$/^M/' commands > test.redis.cmd
cat test.redis.cmd | nc localhost 6379

## redis-cli

127.0.0.1:6379> info stats
# Stats
total_connections_received:1
...

通过tcp连接操作,一批次的操作就一个连接。但是,操作的时刻需要主要,linux的换行符是 \n,而redis需要的是 \r\n;还有通过tcp连接返回的结果是redis协议原始数据,没有经过处理,需要稍微看看协议规范Redis Protocol specification

  • pipelining

redis自带的管道功能,性能提升相当明显(官网数据)。

原来看到过觉得高大上,准备试试。但是返回结果却只有一个成功数而已!!

1
2
3
4
$ echo 119.84.100.68 | xargs -I{} echo "1:ips:2016-08-16:{}" | while read cmd ; do echo -e "*2\r\n\$5\r\nzcard\r\n\$${#cmd}\r\n$cmd\r\n" ; done  | redis-cli -p 26379 --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1

注意:redis-cli带的pipe就比较坑:最后只返回操作成功失败的统计数量,只适合用来做更新操作Redis Mass Insertion!!

要用pipeline来实现查询的话,用java/scala等语言来弄吧。

最后帖一下用shell脚本:

1
2
3
4
5
6
7
8
9
10
11
12
day=${day:-`date "+%Y-%m-%d"`}
key="1:ips:$day"

for h in hadoop-master{1..4} ; do 
  exists=$(redis-cli -h $h -p 6372 exists $key)
  if [ $exists -gt 0 ] ; then
    redis-cli -h $h -p 6372 zrange $key 0 -1 > activeresourceip$day.data
    for h in hadoop-master{1..4} ; do
      cat activeresourceip$day.data | while read ip ; do echo -e "zcard $key:$ip\r" ; done | redis-cli -h $h -p 6372
    done
  fi
done | awk '{s+=$1} END {print s}' 

–END