Oracle大批量高效地更新大数据表索引字段

更新索引字段会触发索引重建,如果是要大批量修改大数据表的索引字段,速度会很慢,时间大多花在重建索引上了。
高效的办法是将索引先unusable,待批次更新修改后再重建(rebuild)索引。
整理語法如下,需要注意:

1. 通过索引判断一个table是否大数据表(大于50M)。
2. 对于分区表的索引,需用对不同分区进行重建。
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
DECLARE
CURSOR c1 IS
SELECT index_name,PARTITIONED
FROM user_indexes
WHERE index_name IN (
SELECT DISTINCT INDEX_NAME
FROM user_ind_columns
WHERE TABLE_NAME = 'TABLE_NAME' --表名
AND column_name = 'COLUMN_NAME' --更新字段
AND TABLE_NAME IN (
SELECT segment_name
FROM (
SELECT segment_name
,SUM(siz_M) AS siz_M
FROM (
SELECT segment_name
,segment_type
,bytes / 1024 / 1024 AS siz_M
FROM user_segments
WHERE segment_type LIKE 'TABLE%'
AND segment_name = 'TABLE_NAME' --表名
)
GROUP BY segment_name
)
WHERE siz_M >= 50 --定义达到50M的表为大数据表
)
);
TYPE IDXINFOREC
IS TABLE OF c1%ROWTYPE INDEX BY BINARY_INTEGER;
idxinfo c1%ROWTYPE;
idxinfolist IDXINFOREC;
counter INTEGER;
BEGIN
counter := 0;
OPEN c1;
LOOP
FETCH c1 INTO idxinfo;
IF c1%FOUND THEN
counter := counter + 1;
END IF;
idxinfolist(counter) := idxinfo;
IF c1%NOTFOUND THEN
EXIT;
END IF;
END LOOP;
CLOSE c1;
--1.unusable索引
FOR i IN 1..counter LOOP
EXECUTE immediate 'ALTER INDEX ' || idxinfolist(i).index_name || ' unusable';
END LOOP;
--2.中间执行更新任务
-- update something
COMMIT;
--3.重建索引
FOR i IN 1..counter LOOP
IF idxinfolist(i).PARTITIONED = 'NO' THEN
EXECUTE immediate 'ALTER INDEX ' || idxinfolist(i).index_name || ' rebuild parallel nologging';
ELSE
IF idxinfolist(i).PARTITIONED = 'YES' THEN
FOR x IN (
SELECT PARTITION_NAME
FROM user_IND_PARTITIONS
WHERE index_name = idxinfolist(i).index_name
ORDER BY PARTITION_POSITION
) LOOP
EXECUTE immediate 'ALTER INDEX ' || idxinfolist(i).index_name || ' rebuild PARTITION ' || x.PARTITION_NAME || ' parallel nologging';
END LOOP;
END IF;
END IF;
EXECUTE immediate 'ALTER INDEX ' || idxinfolist(i).index_name || ' noparallel';
END LOOP;
END;

Oracle Partition Common Usage

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
--create tablespaces
CREATE tablespace cus_area_1 datafile '/mnt/oracle/oradata/zuobian/cus_area_1.dbf' SIZE 5m autoextend ON NEXT 10m maxsize unlimited;
CREATE tablespace cus_area_2 datafile '/mnt/oracle/oradata/zuobian/cus_area_2.dbf' SIZE 5m autoextend ON NEXT 10m maxsize unlimited;
CREATE tablespace cus_area_3 datafile '/mnt/oracle/oradata/zuobian/cus_area_3.dbf' SIZE 5m autoextend ON NEXT 10m maxsize unlimited;
--create table with partition declaration
CREATE TABLE CUSTOMER
(
CUSTOMER_ID NUMBER NOT NULL PRIMARY KEY,
CUST_AREA INT NOT NULL,
FIRST_NAME VARCHAR2(30) NOT NULL,
LAST_NAME VARCHAR2(30) NOT NULL,
PHONE VARCHAR2(15) NOT NULL,
EMAIL VARCHAR2(80),
SEX VARCHAR2(10),
STATUS VARCHAR2(10),
CREATE_DATE DATE
)
PARTITION BY LIST (CUST_AREA) --partition declaration
(
PARTITION area_1 VALUES(1) TABLESPACE cus_area_1,
PARTITION area_2 VALUES(2) TABLESPACE cus_area_2,
PARTITION area_3 VALUES(3) TABLESPACE cus_area_3
);
-- test data
INSERT INTO CUSTOMER(CUSTOMER_ID,CUST_AREA,FIRST_NAME,LAST_NAME,PHONE,CREATE_DATE) VALUES(1,1,'geln','zhang','18912386146',sysdate);
INSERT INTO CUSTOMER(CUSTOMER_ID,CUST_AREA,FIRST_NAME,LAST_NAME,PHONE,CREATE_DATE) VALUES(2,2,'eric','zhang','18912386146',sysdate);
INSERT INTO CUSTOMER(CUSTOMER_ID,CUST_AREA,FIRST_NAME,LAST_NAME,PHONE,CREATE_DATE) VALUES(3,3,'tom','zhang','18912386146',sysdate);
INSERT INTO CUSTOMER(CUSTOMER_ID,CUST_AREA,FIRST_NAME,LAST_NAME,PHONE,CREATE_DATE) VALUES(4,3,'tony','zhang','18912386146',sysdate);
commit;
-- query by partition
SELECT CUSTOMER_ID,CUST_AREA,FIRST_NAME,LAST_NAME FROM CUSTOMER;
SELECT CUSTOMER_ID,CUST_AREA,FIRST_NAME,LAST_NAME FROM CUSTOMER partition (area_1) ;
SELECT /*+rowid(CUSTOMER)*/ ROWID,LAST_NAME,FIRST_NAME,PHONE FROM CUSTOMER partition (AREA_1) WHERE ROWID>='AAASrfAAIAAAACAAAA' AND ROWID<='AAASrfAAIAAAACHCcQ';
-- find all partitioin data for a table
SELECT e.owner,e.segment_name,e.partition_name,o.data_object_id,
e.RELATIVE_FNO,
e.BLOCK_ID MIN_BLOCK,
e.BLOCK_ID + e.BLOCKS - 1 MAX_BLOCK
FROM dba_extents e, dba_objects o
WHERE e.segment_name = 'CUSTOMER' -- table name
AND o.object_name = e.segment_name
AND e.owner = 'MASK' -- schema name
AND o.OWNER = e.owner
AND NVL (e.partition_name, 0) = NVL (o.SUBOBJECT_NAME, 0)
AND o.data_object_id IS NOT NULL;

jvm退出的原因,使用strace定位

今天遇到的一个tomcat启动过程中jvm退出的问题,不是jvm crash的情况,用户日志配置的不正确导致一些信息没有展现出来,只看到pandora执行了shutdownhook的信息。这可能是启动时的逻辑有触发System.exit,或被系统或人为kill掉了。

根据以往的经验,排除了oom killerulimit -t设置不当导致被内核给kill掉的情况,OS级别的signal通常不留机会给jvm执行shutdownhook的。如此一来singal的范围应该就是SIGTERM, SIGINT, SIGHUP这3种(参考这里)。

虽然singal范围缩小,但依然不能确定是因为代码里调用了System.exit还是人为(或被其他进程)kill引起的。直接上大招用systemtap需要安装kernal debuginfo,没有权限的话,还要找到对应的人去做;

如果现象较容易重现的话,可以先通过strace命令进一步缩小问题的范围,究竟是因为jvm内部执行了System.exit还是外界的kill引起的。

这里通过启动一个scala的repl来模拟java进程,通过strace attach到jvm进程上,然后观察,如果是外界的kill所致,可以看到下面的信息:

1
2
3
4
5
6
7
8
$ sudo strace -p 1947
Process 1947 attached - interrupt to quit
futex(0x7fb7635959d0, FUTEX_WAIT, 1948, NULL) = ? ERESTARTSYS (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
futex(0x7fb762762360, FUTEX_WAKE_PRIVATE, 1) = 1
rt_sigreturn(0x7fb762762360) = 202
futex(0x7fb7635959d0, FUTEX_WAIT, 1948, NULLPANIC: attached pid 1947 exited with 143
<unfinished ... exit status 143>

里面的关键信息是SIGTERM或exit status 143(即SIGTERM的code)

如果是kill -2ctrl-c终止repl,可以看到有关SIGINT的信息

1
2
3
4
5
6
7
8
$ sudo strace -p 1813
Process 1813 attached - interrupt to quit
futex(0x7fb24d15a9d0, FUTEX_WAIT, 1814, NULL) = ? ERESTARTSYS (To be restarted)
--- SIGINT (Interrupt) @ 0 (0) ---
futex(0x7fb24c327360, FUTEX_WAKE_PRIVATE, 1) = 1
rt_sigreturn(0x7fb24c327360) = 202
futex(0x7fb24d15a9d0, FUTEX_WAIT, 1814, NULLPANIC: attached pid 1813 exited with 130
<unfinished ... exit status 130>

如果是jvm自身执行了System.exit比如:

1
scala> System.exit(0)

那么在跟踪的信息里,是看不到signal的:

1
2
3
4
$ sudo strace -p 2131
Process 2131 attached - interrupt to quit
futex(0x7fc14adb49d0, FUTEX_WAIT, 2132, NULLPANIC: attached pid 2131 exited with 0
<unfinished ... exit status 0>

至此我们可以判断出到底是外部还是内部引起的了,如果是内部就不必麻烦Systemtap了,可以从源码去找。

负载均衡和过载保护

最近需要给一个现网server增加过载保护的功能,借此机会也思考了很多,简单谈谈我对这两个概念的理解和实现方法。

一.负载均衡
简单来说,就是按照目标server的参数进行合理分配,这个参数可以是失败率,也可以是响应时间,也可以是请求量,甚至是随机数。
我们来按照从简单到复杂逐个看一下几种实现。

1.轮询式
逻辑比较简单,直接看代码:

1
2
3
4
5
6
vector<Server*> vecServer;
while(1)
{
Server* server = vecServer[curIndex % vecServer.size()];
curIndex ++;
}

如上代码就是一个简单的轮询式分配方法,这种方法优点实现简单,cpu计算少,缺点就是无法动态判断server的状态,当后端有一台server挂掉的时候,会至少1/vecServer.size()的请求。(最为严重的情况是由于单台后端server的超时导致前段全部挂死)。而且这种分配方法有一个bug,就是当每次请求结束后就释放内存,那么curIndex永远都只会为0,即每次都请求第一个server。

2.定死权重式
这种方式适用于那种需要实现就规定后端server的权重,比如A比Bserver的响应速度快,我们希望A接受的请求比B多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//假设A,B, C server的权重分别为 10 5 2
typedef struct _serverinfo
{
//server 指针
Server* server;
//权重
int weight;
}ServerInfo;
vector<ServerInfo*> vecServer;
vecServer.push_back(A);
vecServer.push_back(B);
vecServer.push_back(C);
vector<int> vecWeight;
for (unsigned i = 0; i < vecServer.size(); i++)
{
vecWeight.insert(vecWeight.end(),vecServer[i]->weight,i);
}
while(1)
{
index = vecWeight[random() % vecWeight.size()];
Server* server = vecServer[index].server;
}

上面的代码也比较好理解,一共有两个数组,一个是server信息数组vecServer,一个是权重数组vecWeight。在分配server时,先通过权重数组vecWeight获取到server信息数组的下标,然后分配server。
这样的做法在我1年半写的一个项目是有使用的,经过统计效果是很不错的,基本是访问量是严格按照权重比分配的。这样做的cpu消耗也不高,但是缺点也是显而易见的,就是还是没有办法动态调整权重,需要人为去修改。所以我们接下来看第三种。

3.动态调整权重
要讨论这种方法前,我们先要明确几个希望使用他的原因:

  • 1.我们希望server能够自动按照运行状态进行按照权重的选择
  • 2.我们不希望手工去配置权重变化

然后是我们实现方法,很明显,我们需要一个基准来告诉我们这台server是否是正常的。这个基准是什么,是历史累计的平均值。比如如果是按照响应时间分配权重,那么就是所有后端server历史累计的平均响应时间,如果是错误率也是如此。
那么一旦调整了权重,我们什么时候来调整权重呢,调整比例是怎样呢?按照我的经验,一般是隔一段固定时间才进行调整,如果正常但是权重过低,那么就按照20%的比例恢复;如果server不正常,那么直接按照当前server响应时间/历史平均响应时间进行降权。这里的逻辑之所以不一样是有原因的,因为服务出现问题的时候,我们是能够知道这坏的程度有多少的,就是当前server响应时间/历史平均响应时间进行降权;但是要恢复的时候,你并不能保证server能够支撑到多大的访问量,所以只能按照20%放量来试。也避免滚雪球效应的发生。
我们来看一下代码。

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
typedef struct _serverinfo
{
unsigned _svr_ip; //目标主机
float _cfg_wt; //配置的权重
float _cur_wt; //当前实际权重
int _req_count; //请求数
float _rsp_time; //请求总响应时间
float _rsp_avg_time; //请求平均响应时间
int _rsp_error; //请求错误数
}ServerInfo;
vector<ServerInfo*> vecServer;
int total_rsp_time = 0;
int total_req_count = 0;
unsigned int comWeight = 100;
unsigned int MaxWeight = 1000;
while(1)
{
//按照文中第二种方式进行server分配
serverInfo._req_count++;
serverInfo._rsp_time+=rsp_time;//响应时间
total_req_count++;
total_rsp_time += rsp_time;
if(! 需要重建权重)
{
continue;
}
float total_rsp_avg_time = (float)total_rsp_time / (float)total_req_count;
for(vector<ServerInfo*>::iterator it = vecServer.begin();it!=vecServer.end();++it)
{
it->_rsp_avg_time = (float)it->_rsp_time / (float)it->_req_count;
if(it->_rsp_avg_time > total_rsp_avg_time)
{
it->_cur_wt = int(comWeight*total_rsp_avg_time/it->_rsp_avg_time);
}
else
{
it->_cur_wt *= 1.2;
}
it->_cur_wt = it->_cur_wt < MaxWeight ? it->_cur_wt : MaxWeight;
}
//按照文中第二种方式重建权重数组
}

以上基本展示了动态调整的过程,代码可能只是起演示作用,很多比如越界的检测都没有做,大家参照就好~

OK,到这里我们基本就结束了负载均衡的讨论了,但是还有一个话题:过载保护。

二.过载保护
关于过载保护其实经常适合负载均衡结合在一起使用的,但有两个问题:

  • 1.过载参照的基准是谁。
    是上面代码中的total_rsp_avg_time吗?
    不是,因为除非所有机器的正常性能完全一样,
    否则不可以拿total_rsp_avg_time来作为某台机器的负载基准。
    而能拿来做参照的,只有这台server自身的历史累计值。
  • 2.怎么实现过载保护。其实很简单,我们定义两个值_cur_max_queue_cnt和_queue_req_cnt,
    意义分别是这个server上在一段时间内允许分配的最多次数和当前已经排队的个数。
    _cur_max_queue_cnt值是通过当前时间段响应时间和历史累计时间算出来的。
    每次使用分配的server前,都要判断一下_queue_req_cnt是否达到了_cur_max_queue_cnt,
    如果达到了,分配失败。否则分配成功并且_queue_req_cnt++。

代码如下:

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
typedef struct _serverinfo
{
unsigned _svr_ip; //目标主机
float _cfg_wt; //配置的权重
float _cur_wt; //当前实际权重
int _req_count; //请求数
float _rsp_time; //请求总响应时间
float _rsp_avg_time; //请求平均响应时间
int _rsp_error; //请求错误数
int _total_req_count; //总的请求数
float _total_rsp_avg_time; //总的请求平均响应时间
int _total_rsp_error; //总的请求错误数
float _total_rsp_time; //总的请求时间
float _cur_max_queue_cnt; //当前实际允许的最大排队请求数
int _queue_req_cnt; //当前排队请求数
}ServerInfo;
float comxQueueSize = 1000;
float maxQueueSize = 10000;
while(1)
{
//按照文中第二种方式进行server分配
if(serverInfo._queue_req_cnt > serverInfo._cur_max_queue_cnt)
{
//分配失败
continue;
}
serverInfo._queue_req_cnt ++;
serverInfo._req_count++;
serverInfo._rsp_time+=rsp_time;//响应时间
serverInfo._total_req_count++;
serverInfo._total_rsp_time+=rsp_time;//响应时间
total_req_count++;
total_rsp_time += rsp_time;
if(! 需要重建权重)
{
continue;
}
//按照第三种方法重新分配权重
//按照文中第二种方式重建权重数组
//其实和上面的循环合并成一个
for(vector<ServerInfo*>::iterator it = vecServer.begin();it!=vecServer.end();++it)
{
it->_total_rsp_avg_time = (float)it->_total_rsp_time / (float)it->_total_req_count;
if(it->_rsp_avg_time > it->_total_rsp_avg_time)
{
it->_cur_max_queue_cnt = int(comxQueueSize*it->_total_rsp_avg_time/it->_rsp_avg_time);
}
else
{
it->_cur_max_queue_cnt *= 1.2;
}
it->_cur_max_queue_cnt = it->_cur_max_queue_cnt < maxQueueSize ? it->_cur_max_queue_cnt : maxQueueSize;
it->_queue_req_cnt = 0;
}
}

上面的代码为了演示方便,所以把两个for循环拆开了,实际上是应该合到一个里面写的。


秋意


暑尽秋意浓,
翘望满目空,
西子斜阳里,
游客独匆匆。


晚秋


秋风起,满地黄,
朝为晨露暮凝霜,
偏爱秋风抚落叶,
不喜叶落树上光。

RESTful Web Services中API的设计原则

RESTful既然作为一种软件架构风格,那就自然就会有它本身的设计原则,就像iOS、android以及metroUI都有自己的设计规范一样。遵守基本的设计规则可以帮助我们设计出更加简洁易懂、优雅的软件系统。在这个过程中,程序员们都是艺术家,设计着另外一种美。

关于API的设计原则,我所知道的大部分来自书本、网上的资料以及参考各个开放平台的API规范。

RESTful Web Services Cookbook
想要看书的话,可以买本来看,从名字就知道,这是一本手册,里面有设计API需要注意的所有方面。

RESTful API Design
Youtube上apigee关于API Desion的视频,讲的也很不错,请自备梯子。

豆瓣的API
另外就是可以参考一下现有的开放平台API的设计,我比较喜欢设计,条理清晰,也很漂亮。apigee的视频中举的例子比较多的是facebook、twitter、LinkedIn等网站,参考已经实现的设计也是一个很好的学习方法。

API Design Tips

每个实体对象仅需要两个URL

1
2
/books # for Collections
/books/2 # for Single Object

每个对象仅需要两个url,第一个是获取对象的集合,第二个是获取单个对象

使用名词代替动词

1
2
/books/2 # Good :)
/getBook?id=2 # Bad :(

正确使用HTTP方法

1
2
3
4
GET # Read
POST # Create
PUT # Update
DELETE # Delete

对象间的关联关系

1
2
/books/2/author # book author
/author/1/books # author's books

数据分页

1
/books?start=10&count=20 # return the books from 10 to 30

查询条件

1
/books?order=hot # return the books order by hot

返回需要的参数

1
/books/2?fields=author,isbn,price # only return the book's author, isbn and price

错误处理
依靠status code来给程序标识错误,常见的status code如下:

1
2
3
4
5
6
7
8
200 - OK # GET success
201 - CREATED # POST success
202 - ACCEPTED # PUT success
400 - BAD REQUEST # Wrong path or unsupported parameters
401 - UNAUTHORIZED # Need Authorize
403 - FORBIDDEN # forbidden to access
404 - NOT FOUND # Resource not exists
500 - INTERNAL ERROR # Server error

具体的错误信息和错误代码(自定义的错误代码)也要返回:

1
{"code": 1000, "message": "missing_args", "request": "GET /books/2"}

身份认证

1
Oauth 2.0

版本管理

1
2
GET /books/2 # version 1
GET /v2/books/2 # version 2

mysql多列索引详解

创建多列索引

在t_user表id,userName,email字段上创建多列索引(该表只有此索引):

1
alter table t_user add index USER_INDEX(id, userName, email);

能够利用该索引的查询

符合leftmost index prefixes原则的查询

1
2
3
4
5
6
select * from t_user where id = 40;
select * from t_user where id between 10 and 50;
select * from t_user where id in (30, 31, 32);
select * from t_user where id = 40 and userName = 'zxc';
select * from t_user where id = 40 and userName = 'zxc' and email = 'xiongcaizhang@yahoo.com';
select * from t_user where id > 40 and userName > 't';

不能利用以上索引的查询

不符合leftmost index prefixes原则的查询

1
2
select * from t_user where userName = 'zxc';
select * from t_user where userName = 'zxc' and email = 'xiongcaizhang@yahoo.com';

or查询

1
select * from t_user where id = 40 or userName = 'zxc';

不能使用索引的解决方案

在where语句后面的查询字段建立单个索引及多列索引,注意leftmost index prefixes原则,避免建立重复索引

or查询使用union来连接查询结果,并在对应的字段上建立索引

关于我


90 programmer,Blogger,base Shanghai.China,remote worker.

2012年毕业,本科数学,先后就职于 悦己网爱屋吉屋驴妈妈,现任 左边网 技术负责人.

简历: echo 'aHR0cDovL2xhd3JlbmNlLXp4Yy5naXRodWIuaW8vaW1nL015RGVlckNWLnBkZg=='|base64 -D

技术

PythonJavaElasticsearchMysql/MongoScalaLinux/UnixJavaScriptOLAPNLP

足迹

情怀

爱狗完美主义历史Apple控Google控骑行电影吃货写作豆瓣

咖啡

如果您觉得『Jeck_Zhang Blog』对您的工作和生活有价值,欢迎对『Jeck_Zhang』进行无负担小额赞助(1元即可)

  • 支付宝帐号 407815772@qq.com
  • 手机扫码



centos install oracle 11g

1
2
/mnt/database/runInstaller -silent -ignoreSysPrereqs -debug -force FROM_LOCATION=/mnt/database/stage/products.xml oracle.install.option=INSTALL_DB_SWONLY UNIX_GROUP_NAME=oinstall INVENTORY_LOCATION=/mnt/oraInventory ORACLE_HOME=/mnt/oracle/product/11.2.0/db_1 ORACLE_HOME_NAME="Oracle11" ORACLE_BASE=/mnt/oracle oracle.install.db.InstallEdition=EE oracle.install.db.isCustomInstall=false oracle.install.db.DBA_GROUP=oinstall oracle.install.db.OPER_GROUP=oinstall
DECLINE_SECURITY_UPDATES=true
1
dbca -silent -createDatabase -templateName General_Purpose.dbc -gdbname zuobian -sid zuobian -responseFile NO_VALUE -characterSet AL32UTF8 -memoryPercentage 30 -emConfiguration LOCAL
1
CREATE TABLESPACE zuobian_data datafile '/mnt/oracle/oradata/zuobian/zuobian_data01.bdf' size 5120M, '/mnt/oracle/oradata/zuobian/zuobian_data02.bdf' size 5120M EXTENT MANAGEMENT LOCAL;
1
alter tablespace zuobian_data add datafile '/mnt/oracle/oradata/zuobian/zuobian_data01.bdf' size 800M autoextend on next 50M maxsize 1000M;
1
2
3
4
5
6
7
CREATE TABLE TRAVEL_GIFTCLASS (
GC_ID NUMBER(10) NOT NULL,
GMT_CREATE TIMESTAMP NOT NULL,
GMT_MODIFIED TIMESTAMP NOT NULL,
GC_NAME VARCHAR2(200),
GC_PARENTID NUMBER(10)
);
1
create user zuobian identified by zuobian default tablespace zuobian_data;
1
2
3
4
grant connect,resource,dba to zuobian;
grant connect,resource to zuobian;
grant create view to zuobian;
grant read,write on directory dump_dir to zuobian;

Nginx+Lua+GraphicsMagick实现动态生成指定尺寸的图片

面临的问题
网站需求变更,需要更多不同尺寸的缩略图

有些图片的缩略图很少使用到,但还是存在了硬盘上,造成空间浪费

解决方法
Nginx搭配Lua模块,如果访问的图片不存在,则调用GraphicsMagick的命令行实时生成指定尺寸的图片。

-集成了Lua模块的Nginx项目OpenResty

-GraphicsMagick的安装和使用

-具体使用方法

原始图片地址:

1
/images/f47aa98b47b4b7bd.jpg

自定义图片尺寸:

1
2
3
/images/f47aa98b47b4b7bd=40x40.jpg
/images/f47aa98b47b4b7bd=300x300.jpg

配置文件中可以写成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location ~* ^/tmp/(.+)(=|=C)(\d+)x(\d+).(jpg|jpeg|gif|png)$ {
root /data/static/style;
set $image_root /data/static/style/tmp;
set $image_path /data/static/style;
set $fileName $1.$5;
set $width $3;
set $height $4;
set $suffix $5;
set $origin $image_root/$fileName;
set $file $image_path$uri;
if (!-f $file) {
rewrite_by_lua '
local command = "/usr/local/GraphicsMagick/bin/gm convert "..ngx.var.origin.." -thumbnail "..ngx.var.width.."x" ..ngx.var.height.." "..ngx.var.file;
os.execute(command);
ngx.req.set_uri(ngx.var.request_uri, true);
';
}
}

这样就能简单的生成图片指定尺寸的缩略图了。