Winse Blog

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

Redis维护

在使用过程中,接触最多的就是它的commands。除了string/hashmap/set/sortedlist的基本使用方式外,下面总结平时会经常使用的命令:

启动,客户端连接

1
2
3
4
[root@docker redis-2.8.13]# nohup src/redis-server --port 6370 &

[root@docker redis-2.8.13]# src/redis-cli -p 6370
127.0.0.1:6370> 

获取redis的整体状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
127.0.0.1:6370> info
...
# Memory
used_memory:1415161160
used_memory_human:1.32G
used_memory_rss:0
used_memory_peak:1415161160
used_memory_peak_human:1.32G
used_memory_lua:33792
mem_fragmentation_ratio:0.00
mem_allocator:jemalloc-3.6.0
...
# CPU
used_cpu_sys:52.47
used_cpu_user:10.07
used_cpu_sys_children:0.00
used_cpu_user_children:0.00

# Keyspace
db0:keys=4253125,expires=0,avg_ttl=0

列出的信息,包括了版本、内存/CPU使用、请求数、键值对等信息。通过这些基本了解redis运行情况。

清空数据库

对于数据量少的情况下,可以使用flushall来清理记录。

1
2
3
4
5
6
7
8
127.0.0.1:6370> set abc 1234
OK
127.0.0.1:6370> keys *
1) "abc"
127.0.0.1:6370> flushall
OK
127.0.0.1:6370> keys *
(empty list or set)

数据量大的情况不建议使用flushall,可以直接把rdb数据文件干掉,然后重启redis服务就可以了(找不到数据文件后,就是一个新的库)。

随机获取一个键

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6370> mset a 1 b 2 c 3 d 4 e 5 f 6 
OK
127.0.0.1:6370> RANDOMKEY
"a"
127.0.0.1:6370> RANDOMKEY
"f"
127.0.0.1:6370> RANDOMKEY
"e"
127.0.0.1:6370> RANDOMKEY
"a"

遍历获取键值

一般情况下,我们会使用keys PATTERN来查找匹配的键值。但是,如果数据量很大,keys操作会很消耗系统资源,stop the world的事情不是我们想看到的!此时,可以通过scan/hscan/zscan/ssan命令依次获取。

  • 获取库中的键值
1
2
3
127.0.0.1:6370> eval "for i=1,100000 do redis.call('set', 'a' .. i, i) end" 0
(nil)
(0.98s)

正式环境我们无法预估匹配的键的数量,一根筋的使用keys命令可能并不明智。如果数据量很多,等不到结束应该就会ctrl+c了。这种情况下,可以使用scan命令:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6370> scan 0 match ismi:domain:*.upaiyun.com
1) "6553600"
2) 1) "ismi:domain:KunMing:1415646303170928524.test.b0.upaiyun.com"
   2) "ismi:domain:KunMing:1415392926002699280.test.b0.upaiyun.com"
   3) "ismi:domain:KunMing:141489373375899237.test.b0.upaiyun.com"
127.0.0.1:6370> scan 0 match ismi:domain:*.upaiyun.com count 10
1) "6553600"
2) 1) "ismi:domain:KunMing:1415646303170928524.test.b0.upaiyun.com"
   2) "ismi:domain:KunMing:1415392926002699280.test.b0.upaiyun.com"
   3) "ismi:domain:KunMing:141489373375899237.test.b0.upaiyun.com"

Basically with COUNT the user specified the amount of work that should be done at every call in order to retrieve elements from the collection. This is just an hint for the implementation, however generally speaking this is what you could expect most of the times from the implementation.

COUNT数值的意思应该是匹配操作的次数,而不是查询结果的个数。通过和scan 0对比可以得出来。

同理,对于set(smembers)可以使用sscan,sortedlist可以使用zcan等。

lua脚本

redis内置的脚本语言,直接使用脚本可以减少客户端和服务端连接(多次请求)的压力。例如要批量删除一些键值:

1
src/redis-cli keys 'v2:*' | awk '{print $1}' | while read line; do src/redis-cli del $line ; done

先获取匹配的key,然后使用shell再次调用redis的客户端进行删除。表面上看起来没啥问题,如果匹配的key很多,会产生很多的tcp连接,占用redis服务器的端口!最终端口不够用,请求报错。

此时,如果使用lua脚本的方式,就可以轻松处理。无需考虑端口等问题。

1
2
3
4
5
# 量少时可以使用
eval "local aks=redis.call('keys', 'v2:*'); if #aks >0 then redis.call('del', unpack(aks)) end" 0

# 优美
eval "local aks=redis.call('keys', 'v2:*'); for _,r in ipairs(aks) do redis.call('del', r) end" 0

当然,如果键不多,还可以使用一次性全部删除:

1
src/redis-cli -p $PORT del `~/redis-2.8.13/src/redis-cli -p $PORT 'keys' 'v2:*' | grep -v 'v2:ci:' | grep -v 'v2:ff' | grep -v "$(date +%Y-%m-%d)" | grep -v "$(date +%Y-%m-%d -d '-1 day')"`

计算打印 hash键 总共 的包括键值对数量

1
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

打印 每个 hash包括的键值对个数

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

–END

Hadoop查看作业状态Rest接口

hadoop yarn提供了web端查看任务状态,同时可以通过rest的方式获取任务的相关信息。rest接口和网页端的每个界面一一对应。

上面的5个图的链接为:

1
2
3
4
5
http://hadoop-master1:8088/cluster/apps/RUNNING
http://hadoop-master1:8088/cluster/app/application_1417676507722_1846
http://hadoop-master1:8088/proxy/application_1417676507722_1846/
http://hadoop-master1:8088/proxy/application_1417676507722_1846/mapreduce/job/job_1417676507722_1846
http://hadoop-master1:19888/jobhistory/job/job_1417676507722_1846/mapreduce/job/job_1417676507722_1846

查看正在运行的任务

1
2
3
4
5
6
7
8
9
10
11
12
13
curl http://hadoop-master1:8088/ws/v1/cluster/apps?states=RUNNING
...

curl http://hadoop-master1:8088/proxy/application_1417676507722_1867/ws/v1/mapreduce/info
...
curl http://hadoop-master1:8088/proxy/application_1417676507722_1867/ws/v1/mapreduce/jobs
...

curl http://hadoop-master1:8088/proxy/application_1417676507722_1867/ws/v1/mapreduce/jobs/job_1417676507722_1867
...
curl http://hadoop-master1:8088/proxy/application_1417676507722_1867/ws/v1/mapreduce/jobs/job_1417676507722_1867/counters
...
curl http://hadoop-master1:8088/proxy/application_1417676507722_1867/ws/v1/mapreduce/jobs/job_1417676507722_1867/conf

如果上面的任务是已经完成的,获取对应的信息时返回的值是空的。

1
curl http://hadoop-master1:8088/proxy/application_1417676507722_1867/ws/v1/mapreduce/jobs/job_1417676507722_1867/counters

查看执行完成的任务

1
2
3
4
5
6
7
8
9
10
curl http://hadoop-master1:19888/ws/v1/history
curl http://hadoop-master1:19888/ws/v1/history/info
...
curl http://hadoop-master1:19888/ws/v1/history/mapreduce/jobs?startedTimeBegin=$(date +%s -d '-1 hour')000
...
curl http://hadoop-master1:19888/ws/v1/history/mapreduce/jobs/job_1417676507722_1867
curl http://hadoop-master1:19888/ws/v1/history/mapreduce/jobs/job_1417676507722_1867/counters
curl http://hadoop-master1:19888/ws/v1/history/mapreduce/jobs/job_1417676507722_1867/conf

curl -H "Accept: application/xml" "http://hadoop-master1:8088/ws/v1/cluster/apps?states=FINISHED&limit=1" | xmllint --format - 

后面的参数和运行任务一致,只是提供服务不同。

xml转csv

1
2
3
4
5
6
7
$ curl -H "Accept: application/xml" "http://hadoop-master1:8088/ws/v1/cluster/apps?startedTimeBegin=$(date +%s -d '-1 hour')000" 2>/dev/null | xsltproc yarn.xslt -  | sort -r

application_1417676507722_1973,AccessLogOnlyHiveJob,RUNNING,UNDEFINED,1417942144941,0,19416
application_1417676507722_1972,InfoSecurityLogJob,FINISHED,SUCCEEDED,1417942084278,1417942098184,13906
application_1417676507722_1971,InfoSecurityLogJob,FINISHED,SUCCEEDED,1417941603456,1417941617773,14317
application_1417676507722_1970,AccessLogOnlyHiveJob,FINISHED,SUCCEEDED,1417941581080,1417942142287,561207
application_1417676507722_1969,InfoSecurityLogJob,FINISHED,SUCCEEDED,1417941422664,1417941436456,13792

参考

–END

Mysql分区

Windows8 Mysql安装后数据默认放在C:\ProgramData\MySQL\MySQL Server 5.6\data下。

2、MyISAM数据库表文件: .MYD文件:即MY Data,表数据文件 .MYI文件:即MY Index,索引文件 .log文件:日志文件

3、InnoDB采用表空间(tablespace)来管理数据,存储表数据和索引, InnoDB数据库文件(即InnoDB文件集,ib-file set): ibdata1、ibdata2等:系统表空间文件,存储InnoDB系统信息和用户数据库表数据和索引,所有表共用 .ibd文件:单表表空间文件,每个表使用一个表空间文件(file per table),存放用户数据库表数据和索引 日志文件: ib_logfile1、ib_logfile2

1
2
3
4
5
6
7
8
9
10
create database hello;
use hello;
create table abc ( name varchar(1000), age int );
insert into abc values ("1", 1);

create table abc_myisam ( name varchar(100), age int ) engine=myisam;
insert into abc_myisam values ( '1', 1), ('2',2);
alter table abc_myisam partition by hash(age) partitions 4 ;

insert into abc_myisam values ( '11', 10), ('2',20), ( '1', 11), ('2',21), ( '1', 21), ('2',22), ( '1', 31), ('2',32), ( '1', 41), ('2',24), ( '1', 15), ('2',23) ;

最终库目录如下:

根据月份来进行分区:

1
2
3
4
5
6
7
8
9
10
11
12
--最好按照月份分区(date需要为日期类型)
alter table abc_myisam PARTITION BY RANGE (extract(YEAR_MONTH from date)) (  
    PARTITION p410 VALUES LESS THAN (201411),  
    PARTITION p411 VALUES LESS THAN (201412),  
    PARTITION p412 VALUES LESS THAN (201501),  
  PARTITION p501 VALUES LESS THAN (201502), 
  PARTITION p502 VALUES LESS THAN (201503), 
  PARTITION p503 VALUES LESS THAN (201504), 
  PARTITION p504 VALUES LESS THAN (201505), 
  PARTITION p505 VALUES LESS THAN (201506), 
    PARTITION p0 VALUES LESS THAN MAXVALUE  
)

根据日期来分区:

1
2
3
4
5
6
7
alter table t_dta_activeresources_ip PARTITION BY RANGE (to_days(day)) (  
    PARTITION p0 VALUES LESS THAN (735926),  
PARTITION p141124 VALUES LESS THAN (735927),
PARTITION p141125 VALUES LESS THAN (735928),
PARTITION p141126 VALUES LESS THAN (735929),
PARTITION p88 VALUES LESS THAN MAXVALUE  
)

查询时执行计划带上partitions可以查看命中的是那个分区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> explain select * from t_dta_illegalweb where day='2015-01-04';
+----+-------------+------------------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table            | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+------------------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | t_dta_illegalweb | ALL  | NULL          | NULL | NULL    | NULL | 1335432 | Using where |
+----+-------------+------------------+------+---------------+------+---------+------+---------+-------------+
1 row in set

mysql> explain partitions
 select * from t_dta_illegalweb where day='2015-01-04';
+----+-------------+------------------+------------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table            | partitions | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+------------------+------------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | t_dta_illegalweb | p150104    | ALL  | NULL          | NULL | NULL    | NULL | 1335432 | Using where |
+----+-------------+------------------+------------+------+---------------+------+---------+------+---------+-------------+
1 row in set

如果清理掉分区的数据后,再查看执行计划:

1
2
3
4
5
6
7
8
9
10
mysql> alter table t_dta_illegalweb truncate partition p150104;
Query OK, 0 rows affected

mysql> explain partitions select * from t_dta_illegalweb where day='2015-01-04';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | Extra                                               |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+-----------------------------------------------------+
|  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+-----------------------------------------------------+
1 row in set

打开日志开关

默认mysql是没有打开记录一般日志的开关的,可以通过命令行修改参数。对于查看具体执行了那些sql语句,调试很有帮助。

1
2
3
4
mysql> set global general_log = 1;
Query OK, 0 rows affected

mysql> SHOW  GLOBAL VARIABLES LIKE '%log%';

参考

–END

Nginx服务配置

配置nginx作为网页快照的服务,需要理解好配置root的涵义!

安装、启动

首先安装,然后修改配置:

1
2
3
4
5
6
yum install nginx 

less /etc/nginx/nginx.conf
less /etc/nginx/conf.d/default.conf 

service nginx restart

实际操作中没有root,只能自己编译了:

1
2
3
4
5
6
7
8
9
下载nginx,pcre-8.36.zip,zlib-1.2.3.tar.gz解压到src下。
cd nginx-1.7.7
./configure --prefix=/home/omc/tools/nginx --with-pcre=src/pcre --with-zlib=src/zlib
make && make install

cd /home/omc/tools/nginx
vi conf/nginx.conf # 修改listen的端口,80要root才能起
sbin/nginx
sbin/nginx -s reload

如果编译的目录和真正存放程序的路径不一致时,可以使用-p参数来指定。

1
2
3
cd nginx
sbin/nginx -p $PWD
sbin/nginx -s reload -p $PWD

静态页面服务配置

下面具体说说配置的涵义:

  • root(不管在那个配置节点下)位置都对应 请求的根路径。
1
2
3
4
5
6
7
8
9
location /static {
    root  /usr/share/static/html;
    autoindex on;
}

location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
}
  • location的/static对应的是访问目录/usr/share/static/html/static下的内容,请求/static/hello.html对应到/usr/share/static/html/static/hello.html。也就是说节点下的root目录 对应 的是 访问地址的/
  • autoindex可以用于list列出目录内容。

配置了两个路径后,问题来了:如果/usr/share/nginx/html/也有目录static,那nginx会访问谁?nginx来先匹配配置,访问/static定位到/usr/share/static/html

1
2
3
4
location /static {
    root  /usr/share/static/html;
  try_files $uri /static/404.html;
}
  • try_files可以设置默认页面,如/usr/share/static/html/static目录下不存在abc.html,那么会内部重定向到/static/404.html。这里路径要/static下面。

try_files还可以返回状态值,跳转到对应状态的页面:

1
2
3
location / {
    try_files $uri $uri/ $uri.html =404;
}

如果try_files的所给出的地址不包括$uri时,请求会被重定向配置指向的新代理服务:

1
2
3
4
5
6
7
location / {
    try_files $uri $uri/ @backend;
}

location @backend {
    proxy_pass http://backend.example.com;
}

实践

在实际操作遇到的不能访问的问题,配置本机的其他JavaWeb应用,但是在登录后,点其他链接总是跳转到登陆页。可以查看下真正请求的地址。

1
2
3
4
location /omc {
      proxy_pass http://REAL-IP:9000/omc;
      #proxy_pass http://localhost:9000/omc;
}

填写localhost不能访问,但是填具体的外网IP时是可以访问的。查看后,在页面定义了<base href="${basedir}/>导致请求都跳转到localhost了。在客户端肯定就访问失败了。这个需要特别注意下。

在特定的情况下,文件不一定是html后缀的(如:txt),如果要在浏览器解析html,需要配置content-type标题头。同时访问的url和真实存放的文件的路径有出处时,可以通过rewrite指令来进行适配。

1
2
3
4
5
6
7
8
server {
  ...
    location /snapshot {
        root   /home/ud/html-snapshot;
        add_header content-type "text/html";
        rewrite ^/snapshot/.*/(.*)$  /snapshot/$1   last;
        try_files $uri $uri.html $uri.htm =404;
    }

主备配置

1
2
3
4
5
6
7
8
9
10
upstream backend {
    server backend1.example.com 
    server backup1.example.com  backup;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

防火墙跳转情况下nginx配置

如在防火墙做了11111端口映射到9000端口,如果按照的配置,应用的redirect会被nginx转换为9000端口发给用户,而不是原始的用户访问的11111端口。导致不一致甚至不能访问。

1
2
3
4
5
6
7
8
9
10
11
12
  location ~ \.do$ {
    proxy_pass              http://localhost:8080;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        Host $http_host;
  }                                                                                                       
  location ~ \.jsp$ {
    proxy_pass              http://localhost:8080;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        Host $http_host;
  }

rewrite

flag有两个last和break参数。last和break最大的不同在于

  • break是终止当前location的rewrite检测,而且不再进行location匹配 – last是终止当前location的rewrite检测,但会继续重试location匹配并处理区块中的rewrite规则

结果rewrite的结果重新命中了location /download/ 虽然这次并没有命中rewrite规则的正则表达式,但因为缺少终止rewrite的标志,其仍会不停重试download中rewrite规则直到达到10次上限返回HTTP 500。

配置:

1
2
3
4
5
6
    location / {
       root   html;

rewrite ^/snapshot/[^\/]*/(.*)$  /snapshot/$1 last;
       index  index.html index.htm;
    }

日志:

1
2
3
2015/03/13 11:53:42 [error] 32395#0: *17 rewrite or internal redirection cycle while processing "/snapshot/45/c7/2f/45c72f9a926d2b72b0c705a125d2764a.txt", client: 132.122.237.189, server: localhost, request: "GET //snapshot/1/2/3/4/5/6/7/8/9/10/11/45/c7/2f/45c72f9a926d2b72b0c705a125d2764a.txt HTTP/1.1", host: "umcc97-44:8888"

2015/03/13 11:54:14 [error] 32395#0: *20 open() "/home/hadoop/nginx/html/snapshot/45c72f9a926d2b72b0c705a125d2764a.txt" failed (2: No such file or directory), client: 132.122.237.189, server: localhost, request: "GET //snapshot/1/2/3/4/5/45c72f9a926d2b72b0c705a125d2764a.txt HTTP/1.1", host: "umcc97-44:8888"

10次以上就报错,少于10次是ok的。

参考

–END

为github Pages页面设置自定义域名

  1. 注册个域名(net.cn)
  2. 添加CNAME文件(github.com)
  3. 添加解析记录(net.cn)

如果是使用子域名的话非常简单。在(pages)CNAME文件中填写www.winseliu.com,然后在(net.cn)解析页添加CNAME指向winse.github.io即可。

如果想默认顶级域名也能访问,需要添加的两个ip指向,参见上图。同时(pages)CNAME中使用winseliu.com。

参考

–END