算法描述
如果對于任意0<=a<p和0<=b<q(p和q皆是素數),那么當x<p*q時,存在一個唯一的x,使得x≡a mod p 且 x≡b mod q,則
x =(((a - b)*u) mod p)*q + b,其中u滿足u*q≡1 mod p。
算法證明
1.先推導x的解
因x≡a mod p 且 x≡b mod q
故令x = k1*p + a 且 x = k2*q + b (1)
即 k1*p + a = k2*q + b
=> a - b = k2*q - k1*p (2)
又因u*q≡1 mod p,故令u*q = 1 + k3*p (3)
由(2)和(3)式
=> a - b = k2 * (1+k3*p)/u - k1*p
兩邊同時乘u
=>(a - b) * u = k2*(1+k3*p) - k1*p*u
兩邊同時模p
=> ((a - b) * u) mod p = (k2 mod p) mod p (4)
又因x < p*q,故b + k2*q < p*q
=> b <(p - k2) * q
因0<b<q,故p > k2
=> (k2 mod p) mod p = k2
故(4)式即
((a - b) * u) mod p = k2 (5)
將(5)代入(1)式可得
x = (((a - b)*u) mod p)*q + b
2. 再證明x是唯一解
假設x1是另一解,即 x1≡a mod p 且 x1≡b mod q,得
x1 - x ≡ 0 mod p 即 p | x1 - x
x1 - x ≡ 0 mod q 即 q | x1 - x
又因p和q皆為素數,故p*q | x1 - x,得
x1 - x ≡ 0 mod (p*q)
故 x1 mod (p*q) = x mod (p*q) 證畢
posted @
2021-09-19 16:01 春秋十二月 閱讀(1041) |
評論 (0) |
編輯 收藏
主要思路
1. 首次連接時調用redisConnectWithTimeout或redisConnectUnixWithTimeout連接Redis服務端,若成功則保存返回的redisContext,假設為ctx
2. 發送命令數據后獲取響應,如果是pipeling模式則調用redisGetReply獲取響應,再檢查redisContext中的錯誤碼,如果為網絡出錯或關閉,則不置位ctx
REDIS_CONNECTED標志
3. 在下次發送數據時,先檢查ctx否置位了
REDIS_CONNECTED標志,若沒有則調用redisReconnect重連Redis服務端
實現代碼
自動連接
1 int redis_auto_connect(CBED_REDIS *redis)
2 {
3 if(NULL==redis->ctx){
4 redisContext *ctx;
5 if(redis->type == CONN_TCP)
6 ctx = redisConnectWithTimeout(redis->conn.tcp.ip, redis->conn.tcp.port, redis->timeout_conn);
7 else
8 ctx = redisConnectUnixWithTimeout(redis->conn.unix.path, redis->timeout_conn);
9
10 if(NULL==ctx){
11 zlog_fatal(c_redis, "redis allocate context fail");
12 return -1;
13
14 }else if(ctx->err){
15 zlog_fatal(c_redis, "redis connection %s:%d error: %s",
16 redis->type==CONN_TCP?redis->conn.tcp.ip:redis->conn.unix.path,
17 redis->type==CONN_TCP?redis->conn.tcp.port:0, ctx->errstr);
18 redisFree(ctx);
19 return -1;
20 }
21
22 if(REDIS_ERR==redisSetTimeout(ctx, redis->timeout_rw)){
23 zlog_fatal(c_redis, "redis set rw timeout error: %s", ctx->errstr);
24 redisFree(ctx);
25 return -1;
26 }
27
28 redis->ctx = ctx;
29 if(redis_auth(redis)){
30 redisFree(ctx);
31 redis->ctx = NULL;
32 return -1;
33 }
34
35 } else if(!(redis->ctx->flags & REDIS_CONNECTED)){
36 int retry = redis->reconn_max, n = 0;
37 do {
38 if(REDIS_OK==redisReconnect(redis->ctx)){
39 return redis_auth(redis);
40 }
41
42 zlog_warn(c_redis, "redis reconnect %d error: %s", ++n, redis->ctx->errstr);
43 sleep(redis->reconn_interval); //reconn_interval default is 3 seconds
44
45 }while(--retry > 0);
46
47 zlog_error(c_redis, "redis reconnect exceed max num %d", redis->reconn_max);
48 return -1;
49 }
50
51 return 0;
52 }
發送時檢查錯誤碼
1 static int redis_bulk_get_reply(CBED_REDIS *redis)
2 {
3 redisReply *r;
4 int i = 0;
5 int num = redis->cmd_num;
6 redis->cmd_num = 0;
7
8 for(; i<num; ++i){
9 if(REDIS_OK==redisGetReply(redis->ctx, (void**)&r)){
10 if(r->type == REDIS_REPLY_ERROR){
11 zlog_error(c_redis, "redis get reply error: %.*s", r->len, r->str);
12 freeReplyObject(r);
13 return -1;
14 }
15 freeReplyObject(r);
16
17 }else{
18 if(redis->ctx->err==REDIS_ERR_IO||redis->ctx->err==REDIS_ERR_EOF)
19 redis->ctx->flags &= ~REDIS_CONNECTED;
20 zlog_fatal(c_redis, "redis get reply fail: %s", redis->ctx->errstr);
21 return -1;
22 }
23 }
24
25 return 0;
26 }
27
28 int redis_send(CBED_REDIS *redis, unsigned char *data, unsigned int size, int force)
29 {
30 if(redis_auto_connect(redis))
31 return -1;
32
33 int i;
34
35 if(redis->max_cmd_num > 1){ //pipelining
36 for(i=0; i<redis->queue_num; ++i){
37 if(REDIS_ERR == redisAppendCommand(redis->ctx, "RPUSH %s %b", redis->queue[i], data, size)) {
38 zlog_fatal(c_redis, "redis append command rpush %s len %u fail: %s", redis->queue[i], size, redis->ctx->errstr);
39 return -1;
40 }
41
42 ++redis->cmd_num;
43 if((!force && redis->cmd_num==redis->max_cmd_num) || force){
44 if(redis_bulk_get_reply(redis))
45 return -1;
46 }
47 }
48
49 }else{
50 for(i=0; i<redis->queue_num; ++i){
51 redisReply *r = redisCommand(redis->ctx, "RPUSH %s %b", redis->queue[i], data, size);
52 if(NULL==r){
53 if(redis->ctx->err==REDIS_ERR_IO||redis->ctx->err==REDIS_ERR_EOF)
54 redis->ctx->flags &= ~REDIS_CONNECTED;
55 zlog_fatal(c_redis, "redis command rpush %s len %u fail: %s", redis->queue[i], size, redis->ctx->errstr);
56 return -1;
57 }
58
59 if(r->type == REDIS_REPLY_ERROR){
60 zlog_error(c_redis, "redis reply rpush %s len %u error: %.*s", redis->queue[i], size, r->len, r->str);
61 freeReplyObject(r);
62 return -1;
63 }
64
65 freeReplyObject(r);
66 }
67 }
68
69 return 0;
70 }
posted @
2021-02-25 15:51 春秋十二月 閱讀(6480) |
評論 (0) |
編輯 收藏
存儲格式
Oracle Number數據類型是變長的,占0~22字節,不像編程語言中的2/4字節整數或4/8字節浮點數,關于它的存儲格式與解析,DSI上有詳細的描述,如下所示
符號位/指數字節描述如下
數字字節描述如下
正數或零值的計算
負數值的計算
解析實現
由于Oracle Number的精度高達38位,遠超出了基本定長整數或浮點數表達的數值范圍,因此解析實際上是大整數/實數的四則運算,為避免造輪子,本文使用了
GMP開源庫(
https://gmplib.org/),用于任意精度的算術運算,操作有符號整數、有理數和浮點數,除了在GMP機器上運行的可用內存所暗示的精度之外,對精度沒有實際的限制。解析實現的核心函數是
orcl_raw2number
1 #include <stdio.h>
2 #include <assert.h>
3 #include <gmp.h>
4
5 #define MAX_PREC 256
6
7 static mpf_t s_base100;
8 static mpf_t s_one;
9
10 static void init_mpf_globals()
11 {
12 mpf_init_set_ui(s_base100, 100);
13 mpf_init_set_ui(s_one, 1);
14 }
15
16 static void clear_mpf_globals()
17 {
18 mpf_clear(s_base100);
19 mpf_clear(s_one);
20 }
21
22 static void orcl_raw2number(unsigned char *data, unsigned int len, mpf_t result)
23 {
24 unsigned int sign = *data, digit, i;
25 int exp = sign>=128 ? sign-193 : 62-sign;
26 int exp_val;
27 mpf_t tmp;
28
29 mpf_init2(tmp, MAX_PREC);
30 mpf_init2(result, MAX_PREC);
31
32 if(sign & 0x80){
33 for(i=1; i<len; ++i){
34 digit = data[i] - 1;
35 assert(0<=digit && digit<=99);
36
37 exp_val = exp - i + 1;
38 if(exp_val < 0){
39 mpf_pow_ui(tmp, s_base100, -exp_val);
40 mpf_div(tmp, s_one, tmp);
41 }else
42 mpf_pow_ui(tmp, s_base100, exp_val);
43
44 mpf_mul_ui(tmp, tmp, digit);
45 mpf_add(result, result, tmp);
46 }
47
48 }else{
49 --len; //ignore the last byte
50 for(i=1; i<len; ++i){
51 digit = 101 - data[i];
52 assert(0<=digit && digit<=99);
53
54 exp_val = exp - i + 1;
55 if(exp_val < 0){
56 mpf_pow_ui(tmp, s_base100, -exp_val);
57 mpf_div(tmp, s_one, tmp);
58 }else
59 mpf_pow_ui(tmp, s_base100, exp_val);
60
61 mpf_mul_ui(tmp, tmp, digit);
62 mpf_add(result, result, tmp);
63 }
64
65 mpf_neg(result, result);
66 }
67
68 mpf_clear(tmp);
69 }
測試用例
測試了123456.789、-123456.789、Oracle Number實際最大最小值、Oracle Number理論最大最小值
1 int main(int argc,
char *argv[])
2 {
3 int n = 19;
4 char buf[256];
5 mpf_t r;
6
7 init_mpf_globals();
8
9 //123456.789
10 unsigned
char data[] = {0xc3,0xd,0x23,0x39,0x4f,0x5b};
11 orcl_raw2number(data,
sizeof(data), r);
12 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
13 printf("result: %s\n", buf);
14 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
15 mpf_clear(r);
16
17 //-123456.789
18 unsigned
char data2[] = {0x3c,0x59,0x43,0x2d,0x17,0xb,0x66};
19 orcl_raw2number(data2,
sizeof(data2), r);
20 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
21 printf("result: %s\n", buf);
22 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
23 mpf_clear(r);
24
25 //0
26 unsigned
char zero[] = {0x80};
27 orcl_raw2number(zero,
sizeof(zero), r);
28 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
29 printf("result: %s\n", buf);
30 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
31 mpf_clear(r);
32
33 //test actual max value:9999
9(the number of 9 is 38)
34 unsigned
char max_data[] = {0xd3,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64};
35 orcl_raw2number(max_data,
sizeof(max_data), r);
36 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
37 printf("result: %s\n", buf);
38 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
39 mpf_clear(r);
40
41 //test actual min value:-9999
9(the number of 9 is 38)
42 unsigned
char min_data[] = {0x2c,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x66};
43 orcl_raw2number(min_data,
sizeof(min_data), r);
44 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
45 printf("result: %s\n", buf);
46 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
47 mpf_clear(r);
48
49 clear_mpf_globals();
50
51 //test max oracle number value
52 mpf_init2(r, 256);
53
54 mpf_set_str(r, "1e125", 10);
55 mpf_out_str(NULL, 10, 0, r); printf("\n");
56 gmp_printf("%Ff\n", r);
57
58 //test min oracle number value
59 mpf_set_str(r, "-1e125", 10);
60 mpf_out_str(NULL, 10, 0, r); printf("\n");
61 gmp_printf("%Ff\n", r);
62
63 mpf_clear(r);
64
65 return 0;
66 }
輸出如下
posted @
2020-05-08 12:23 春秋十二月 閱讀(955) |
評論 (0) |
編輯 收藏
場景說明
選擇ENet代替TCP用于弱網環境(通常丟包率高)的數據傳輸,提高可靠性及傳輸效率。為了說明怎樣正確有效地應用ENet,本文按照TCP C/S同步通信的流程作了對應的接口封裝實現,取庫名為
rudp。
接口對照
左邊為rudp庫的API,右邊為標準的Berkeley套接字API。rudp庫所有API前綴為rudp,rudp_listen和rudp_accept僅用于服務端,rudp_connect和rudp_disconnect僅用于客戶端;其它接口共用于兩端,其中rudp_send調用rudp_sendmsg實現,rudp_recv調用rudp_recvmsg實現。
具體實現
所有接口遵循Berkeley套接字接口的語義,為簡單起見,錯誤描述輸出到標準錯誤流。
◆
監聽,成功返回0,失敗返回-1
1 int rudp_listen(const char *ip, int port, ENetHost **host)
2 {
3 ENetAddress address;
4
5 if(!strcmp(ip, "*"))
6 ip = "0.0.0.0";
7
8 if(enet_address_set_host_ip(&address, ip)){
9 fprintf(stderr, "enet_address_set_host_ip %s fail", ip);
10 return -1;
11 }
12
13 address.port = port;
14
15 assert(host);
16 *host = enet_host_create(&address, 1, 1, 0, 0);
17 if(NULL==*host){
18 fprintf(stderr, "enet_host_create %s:%d fail", address.host, address.port);
19 return -1;
20 }
21
22 int size = 1024*1024*1024;
23 if(enet_socket_set_option((*host)->socket, ENET_SOCKOPT_RCVBUF, size)){
24 fprintf(stderr, "enet set server socket rcvbuf %d bytes fail", size);
25 }
26
27 return 0;
28 }
◆
接受連接,成功返回0,失敗返回-1
1 int rudp_accept(ENetHost *host, unsigned int timeout, ENetPeer **peer)
2 {
3 int ret;
4 ENetEvent event;
5
6 ret = enet_host_service(host, &event, timeout);
7 if(ret > 0){
8 if(event.type != ENET_EVENT_TYPE_CONNECT){
9 if(event.type == ENET_EVENT_TYPE_RECEIVE)
10 enet_packet_destroy(event.packet);
11 fprintf(stderr, "enet_host_service event type %d is not connect", event.type);
12 return -1;
13 }
14
15 assert(peer);
16 *peer = event.peer;
17
18 }else if(0==ret){
19 fprintf(stderr, "enet_host_service timeout %d", timeout);
20 return -1;
21
22 }else{
23 fprintf(stderr, "enet_host_service fail");
24 return -1;
25 }
26
27 return 0;
28 }
◆
建立連接,成功返回0,失敗返回-1,conn_timeout是連接超時,rw_timeout是收發超時,單位為毫秒
1 int rudp_connect(const char *srv_ip, int srv_port, unsigned int conn_timeout, unsigned int rw_timeout, ENetHost **host, ENetPeer **peer)
2 {
3 assert(host);
4 *host = enet_host_create(NULL, 1, 1, 0, 0);
5 if(NULL==*host){
6 fprintf(stderr, "enet_host_create fail");
7 goto fail;
8 }
9 if(enet_socket_set_option((*host)->socket, ENET_SOCKOPT_RCVBUF, 1024*1024*1024)){
10 fprintf(stderr, "enet set server socket rcvbuf 1M bytes fail");
11 }
12
13 ENetAddress srv_addr;
14 if(enet_address_set_host_ip(&srv_addr, srv_ip)){
15 fprintf(stderr, "enet_address_set_host_ip %s fail", srv_ip);
16 goto fail;
17 }
18 srv_addr.port = srv_port;
19
20 assert(peer);
21 *peer = enet_host_connect(*host, &srv_addr, 1, 0);
22 if(*peer==NULL){
23 fprintf(stderr, "enet_host_connect %s:%d fail", srv_ip, srv_port);
24 goto fail;
25 }
26
27 enet_peer_timeout(*peer, 0, rw_timeout, rw_timeout);
28
29 int cnt = 0;
30 ENetEvent event;
31
32 while(1){
33 ret = enet_host_service(*host, &event, 1);
34 if(ret == 0){
35 if(++cnt >= conn_timeout){
36 fprintf(stderr, "enet_host_service timeout %d", conn_timeout);
37 goto fail;
38 }
39
40 }else if(ret > 0){
41 if(event.type != ENET_EVENT_TYPE_CONNECT){
42 fprintf(stderr, "enet_host_service event type %d is not connect", event.type);
43 goto fail;
44 }
45 break; //connect successfully
46
47 }else{
48 fprintf(stderr, "enet_host_service fail");
49 goto fail;
50 }
51 }
52
53 #ifdef _DEBUG
54 char local_ip[16], foreign_ip[16];
55 ENetAddress local_addr;
56
57 enet_socket_get_address((*host)->socket, &local_addr);
58 enet_address_get_host_ip(&local_addr, local_ip, sizeof(local_ip));
59 enet_address_get_host_ip(&(*peer)->address, foreign_ip, sizeof(foreign_ip));
60
61 printf("%s:%d connected to %s:%d", local_ip, loca_addr.port, foreign_ip, (*peer)->address.port);
62 #endif
63
64 return 0;
65
66 fail:
67 if(*host) enet_host_destroy(*host);
68 return -1;
69 }
◆
斷開連接,若成功則返回0,超時返回1,出錯返回-1。先進行優雅關閉,如失敗再強制關閉
1 int rudp_disconnect(ENetHost *host, ENetPeer *peer)
2 {
3 int ret;
4
5 #ifdef _DEBUG
6 char local_ip[16], foreign_ip[16];
7 ENetAddress local_addr;
8
9 enet_socket_get_address(host->socket, &local_addr);
10 enet_address_get_host_ip(&local_addr, local_ip, sizeof(local_ip));
11 enet_address_get_host_ip(&peer->address, foreign_ip, sizeof(foreign_ip));
12
13 printf("%s:%d is disconnected from %s:%d", local_ip, local_addr.port, foreign_ip, peer->address.port);
14 #endif
15
16 ENetEvent event;
17 enet_peer_disconnect(peer, 0);
18
19 while((ret = enet_host_service(host, &event, peer->roundTripTime)) > 0){
20 switch (event.type){
21 case ENET_EVENT_TYPE_RECEIVE:
22 enet_packet_destroy (event.packet);
23 break;
24
25 case ENET_EVENT_TYPE_DISCONNECT:
26 ret = 0;
27 goto disconn_ok;
28 }
29 }
30
31 ret = 0==ret ? 1 : -1;
32
33 fprintf(stderr, "enet_host_service with timeout %d %s", peer->roundTripTime, 1==ret?"timeout":"failure");
34
35 enet_peer_reset(conn->peer);
36
37 disconn_ok:
38 enet_host_destroy(host);
39 return ret;
40 }
◆
發送數據,若成功則返回已發送數據的長度,否則返回-1
1 int rudp_sendmsg(ENetHost *host, ENetPeer *peer, ENetPacket *packet)
2 {
3 int ret;
4
5 if(enet_peer_send(peer, 0, packet)){
6 fprintf(stderr, "enet send packet %lu bytes to peer fail", packet->dataLength);
7 return -1;
8 }
9
10 ret = enet_host_service(host, NULL, peer->roundTripTime);
11 if(ret >= 0){
12 if(peer->state == ENET_PEER_STATE_ZOMBIE){
13 fprintf(stderr, "enet peer state is zombie");
14 return -1;
15 }
16 return packet->dataLength;
17
18 }else{
19 fprintf(stderr, "enet host service %u millsecond failure", peer->roundTripTime);
20 return -1;
21 }
22 }
23
24 int rudp_send(ENetHost *host, ENetPeer *peer, const void *buf, size_t len)
25 {
26 int ret;
27
28 ENetPacket *packet = enet_packet_create(buf, len, ENET_PACKET_FLAG_RELIABLE);
29 if(NULL==packet){
30 fprintf(stderr, "enet create packet %lu bytes fail", sizeof(int)+len);
31 return -1;
32 }
33
34 return rudp_sendmsg(host, peer, packet);
35 }
發送數據時需根據對端狀態判斷是否斷線,并且packet標志設為可靠
◆
接收數據,若成功則返回已接收數據的長度,否則返回-1
1 int rudp_recvmsg(ENetHost *host, ENetPeer *peer, ENetPacket **packet, unsigned int timeout)
2 {
3 int ret;
4 ENetEvent event;
5
6 ret = enet_host_service(host, &event, timeout);
7 if(ret > 0){
8 if(event.peer != peer){
9 fprintf(stderr, "enet receive peer is not matched");
10 goto fail;
11 }
12 if(event.type != ENET_EVENT_TYPE_RECEIVE){
13 fprintf(stderr, "enet receive event type %d is not ENET_EVENT_TYPE_RECEIVE", event.type);
14 goto fail;
15 }
16
17 *packet = event.packet;
18 return (*packet)->dataLength;
19
20 fail:
21 enet_packet_destroy(event.packet);
22 return -1;
23
24 }else {
25 fprintf(stderr, "enet receive %u millsecond %s", timeout, ret?"failure":"timeout");
26 return -1;
27 }
28 }
29
30 int rudp_recv(ENetHost *host, ENetPeer *peer, void *buf, size_t maxlen, unsigned int timeout)
31 {
32 ENetPacket *packet;
33
34 if(-1==rudp_recvmsg(host, peer, &packet, timeout))
35 return -1;
36
37 if(packet->dataLength > maxlen) {
38 fprintf(stderr, "enet packet data length %d is greater than maxlen %lu", packet->dataLength, maxlen);
39 return -1;
40 }
41
42 memcpy(buf, packet->data, packet->dataLength);
43 enet_packet_destroy(packet);
44
45 return packet->dataLength;
46 }
◆
等待所有確認,若成功返回0,超時返回1,失敗返回-1
1 int rudp_wait_allack(ENetHost *host, ENetPeer *peer, unsigned int timeout)
2 {
3 int ret, cnt = 0;
4
5 while((ret = enet_host_service(host, NULL, 1)) >= 0){
6 if(enet_peer_is_empty_sent_reliable_commands(peer, 0,
7 ENET_PROTOCOL_COMMAND_SEND_RELIABLE|ENET_PROTOCOL_COMMAND_SEND_FRAGMENT))
8 return 0;
9
10 if(peer->state == ENET_PEER_STATE_ZOMBIE){
11 fprintf(stderr, "enet peer state is zombie");
12 return -1;
13 }
14
15 if(0==ret && ++cnt>=timeout){
16 return 1;
17 }
18 }
19
20 fprintf(stderr, "enet host service fail");
21 return -1;
22 }
等待已發送數據的所有確認時,需根據對端狀態判斷是否斷線
示例流程
左邊為客戶端,壓縮并傳輸文件;右邊為服務端,接收并解壓存儲文件。

客戶端【讀文件塊并壓縮】這個環節,需要顯式創建可靠packet,并將壓縮后的塊拷貝到其中
posted @
2020-05-04 19:08 春秋十二月 閱讀(2428) |
評論 (0) |
編輯 收藏
為什么用VSS
VSS是Windows系統的卷影像拷貝服務,用于解決如下問題:
◆ 許多備份工具涉及打開文件
◆ 但是若一個應用程序已經以獨占方式打開文件并進行訪問時,備份工具則不能訪問該文件
◆ 即使備份工具能夠訪問已打開的文件,也可能造成備份文件的不一致性
在實際數據災備中,主流廠商實現SQL Server的熱備并不會使用數據庫自帶的
backup database/
backup log命令,因為這種方式在應急容災(此時源數據庫已宕機)掛載數據時要先還原,而還原要連接數據庫運行
restore database/
restore log命令,這樣就需要部署一臺機器裝上SQL Server專用于還原,不僅增大了成本而且延長了
RTO;而使用VSS,備份的就是SQL Server的數據文件及日志文件,在應急容災掛載時可直接打開并用于增刪改查,無須還原,免去了機器成本并降低了RTO(只存在數據庫掛載時的事務恢復時間)。
VSS架構
VSS包括Requestor、Writer、Provider和VSS核心模塊四部分,如下圖所示

Requestor在本文中表示熱備份應用程序;Writer主要功能是保證數據的一致性,使得那些能夠感知影像拷貝的應用程序能夠接收到凍結(freeze)和解凍(thaw)通知,以確保其文件的備份拷貝是內在一致的,在本文中即指SQL Server自帶的
SQL Writer;Provider主要功能是創建影像拷貝即打快照,允許將ISV特定的存儲方案與影像拷貝服務集成起來,在本文中即
volsnap.sys存儲過濾型驅動程序,位于文件系統和卷管理器之間;VSS核心模塊即圖中的卷影像拷貝服務,主要功能是協調各個模塊的協作運行,并提供創建及管理卷影像拷貝的API接口。
VSS原理示例
無論何時,當卷影像拷貝驅動程序看到一個針對原始卷的寫操作時,它把將要被修改的扇區的內容復制到一個與影像卷相關聯的、由頁面文件支持的內存區中
◆ 對于已修改扇區的影像卷讀操作,從該內存區中讀取數據
◆ 對于未修改扇區的影像卷讀操作,從原始卷中讀取
備份應用程序、Provider和SQL Writer的局限
◆ 只能備份Windows系統支持的本地文件系統上的文件,不支持遠程共享或交叉掛載的文件系統
◆ 對于系統提供者(Windows系統默認自帶的Provider,使用寫時拷貝技術),被拷貝的源卷不必是NTFS卷,但影像卷必須是NTFS卷
◆ SQL Writer支持全量備份及恢復、支持差異備份及恢復和Copy Only備份,但不支持備份連續事務日志、文件和文件組,不支持頁恢復
怎樣使用VSS
微軟官網提供的VSS SDK 7.2(
https://www.microsoft.com/en-us/download/details.aspx?id=23490)中自帶了
vshadow和
betest工具源碼,經過筆者修正一些bug(win 10 + vs2010),并為了備份配置方便將原來的文本換成xml格式,成功地實現了SQL Server的全量熱備及恢復、差量熱備及恢復
vshadow用法
以管理員身份在ms-dos窗口下執行vshadow.exe /?,可得到所有的幫助
示例
可用vshadow -wm獲取當前系統所有寫者的元數據,再從中查找SQL Server Writer的寫者ID及它下面COM組件的邏輯路徑和名稱
betest用法
以管理員身份在ms-dos窗口下執行betest.exe /?,可得到所有的幫助
示例
1. 全量備份SQL Server
betest.exe /v
/b /t
FULL /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/v -- 輸出詳細信息,可選的
/b -- 備份
/t -- 備份類型
/s -- 備份/恢復組件XML格式文檔,內含寫者及其下組件的元數據(非常重要)
/d -- 備份目錄
/c -- 相關寫者的配置文件,文件內含寫者ID及其下COM組件的邏輯全路徑名
全量恢復SQL Server
betest.exe /v
/r /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/r -- 恢復
其它選項說明同上,下同
2. 差異備份SQL Server
betest.exe /v /b /t
DIFFERENTIAL /s backupdiff.xml
/pre backupfull.xml /d f:\backupdiff /c SQLWriter.xml
/pre -- 表示前次基準的全量備份生成的組件XML格式文檔
差異恢復SQL Server
a) betest.exe /v /r
/AdditionalRestores /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/AdditionRestores -- 用于差異恢復的選項,表示全量后面需要緊跟差異恢復才能完成數據庫恢復
b) betest.exe /v /r /s backupdiff.xml /d f:\backupdiff /c SQLWriter.xml
注意,此時/s跟的是差異備份生成的backupdiff.xml文件,/d跟的是差異備份目錄
3. SQL Writer配置
xml格式說明
writer節點
id屬性 --- 寫者唯一ID
server_name屬性 --- SQLServer服務名
stop_restore_start屬性(可選) --- 表示恢復時是否先停止數據庫服務再啟動,yes表示先停再啟,no則反之,這個用于恢復系統數據庫master,因為master不支持在線恢復
component節點
pathname屬性 --- 邏輯路徑名
file節點
src_path節點 --- SQL Server文件所在路徑的匹配模式
alternate_path節點 --- 恢復時的備選路徑,用于合成差異增量
示例
<?xml version="1.0" encoding="utf-8"?>
<betest>
<writer id="{a65faa63-5ea8-4ebc-9dbd-a0c4db26912a}" service_name="MSSQLSERVER" stop_restore_start="no">
<component pathname="DESKTOP-JUP320L\master">
<file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file>
</component>
<component pathname="DESKTOP-JUP320L\model">
<!--file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file-->
</component>
<component pathname="DESKTOP-JUP320L\test">
<!--file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file-->
</component>
</writer>
</betest>
posted @
2020-05-02 16:31 春秋十二月 閱讀(1499) |
評論 (0) |
編輯 收藏
閱讀《MySQL Innodb無鎖化設計的日志系統》(
https://zhuanlan.zhihu.com/p/53037796)后的心得:
與oracle日志子系統異曲同工的差異
1. 空洞:對于并發會話copy重做日志造成的空洞,oracle是由lgwr判斷并等待持有redo copy閂鎖的會話釋放后,這時空洞已被填充,可以刷到磁盤了;mysql則是由log writer線程監測到空洞被填充后,再寫入一段連續最大lsn的日志到磁盤
2. io方式:oracle的lgwr是direct io;mysql的log writer是寫到os的page cache,后由獨立的log flusher線程刷盤,比oracle多了一個過程
3. 喚醒會話:oracle由lgwr掃描所有等待的會話,只喚醒滿足寫入條件(事務提交log已刷盤)的會話;mysql則由獨立的log flush notifier通過滿足條件對應的分片消息隊列來喚醒,比oracle多了一個過程
總結:mysql通過原子變量來管理全局log buffer的幾個內存位置來實現無鎖化,而原子操作在多核上仍不利于線性擴展。oracle的閂鎖也存在類似問題,但通過私有redo緩存和多個全局log buffer(相關閂鎖量與cpu核數正比),來提升了擴展性。故整體上oracle更優
閱讀《MySQL/InnoDB數據克隆插件(clone plugin)實現剖析》(
https://zhuanlan.zhihu.com/p/76255304)后的心得:
與oracle老式熱備異曲同工的差異
1. page追蹤:oracle老式熱備實際當每行更新時將整個關聯的page記錄在redo日志中;mysql熱備則是記錄變化page的id在單獨一個地方,用于page copy階段從buffer pool讀取并發送頁數據到備庫
2. redo歸檔:oracle老式熱備在拷貝數據文件的全過程,只要數據文件被修改就會有redo歸檔;mysql熱備則僅在page copy階段啟用redo歸檔,可看做是臨時的
3. 一致性恢復:oracle老式熱備存在數據塊分離現象,對此應用被凍結scn及日志序列號后的redo log來恢復;mysql則通過page copy及應用clone lsn后的redo log來恢復
總結:oracle老式熱備必須處于歸檔模式,由于記錄整塊而非行變化,因此重做日志寫放大而增加了cpu和io的開銷,由于可能判斷并修復分離的塊,因此延長了恢復時間;mysql通過page追蹤和臨時redo歸檔來減少應用redo的體量而縮短了恢復時間。故mysql熱備整體更優,但相對oracle的現代rman備份則并不更優
posted @
2020-04-21 11:19 春秋十二月 閱讀(5989) |
評論 (0) |
編輯 收藏
描述
nginx是一款著名的高性能開源Web與反向代理服務器,支持windows和linux操作系統,因為在windows系統上還不支持SCM(服務控制管理),所以只能以控制臺方式運行,但這樣并不是在后臺運行,也不能在系統登錄前啟動。針對這些問題,本方法通過改進源碼,使nginx良好地支持了SCM,方便了部署運行
特點
最大地復用了nginx源碼;支持SCM,并兼容控制臺運行方式;統一處理異常退出而報告服務停止
實現 變換原主函數
將原來的main函數更名為ngx_main,并增加第3個參數is_scm來標識運行方式,非0表示服務方式,0表示控制臺方式,流程如下

圖上紅色部分為插入的邏輯,其它部分為nginx原來的邏輯。由于服務初始化須將錯誤記錄在log(日志)中,所以應在初始化log模塊后調用
增加主函數
這個主函數也就是程序入口main,可被控制臺或SCM調用,當被SCM調用時,注冊服務以及啟動服務控制調度程序,流程如下

如果以命令行啟動nginx 也就是master進程(管理進程),或nginx產生worker進程(工作進程)時,那么以控制臺方式調用main,進而以is_scm為0調用ngx_main,當ngx_main返回時,就表示master或worker進程退出了
服務主函數
由SCM生成的一個邏輯線程調用,流程如下

這里的邏輯線程代替了nginx的master進程,到這里就表明已經以SCM方式運行了,所以以is_scm為1調用ngx_main,當ngx_main返回時,就表明master進程退出了,應該更新服務狀態為已停止,然后返回表明當前服務結束了
服務初始化 由ngx_main調用,見變換原主函數流程圖,流程如下
由于在nginx實現中,有多處出現異常錯誤而直接退出,因此首先注冊了進程退出處理器,在其內報告服務狀態為已停止,這樣只要當進程退出了,在SCM上就能看到已停止的狀態了
服務控制處理器 由SCM的主線程調用,流程如下
調用關系 下圖左邊為master進程調用模塊與函數,右邊為worker進程調用模塊與函數,委托主函數是
ngx_main
posted @
2019-11-20 19:45 春秋十二月 閱讀(878) |
評論 (0) |
編輯 收藏
部署圖 
傳統的vss備份架構由于備份應用部署在應用服務器內,因此比較耗應用服務器的CPU和IO,特別是拷貝大量的文件,為了降低對應用服務器的干擾,可采用server-free架構,將耗時的拷貝移到另一機器即備份服務器實現,而應用服務器只負責占用資源及耗時很少的打快照。這種架構運用了vss可傳輸卷影拷貝的特性,要求快照處于共享存儲中,適用于Windows Server 2003 sp1以上版本
協作流程圖 
VSS快照代理端的SetContext要求設置成
VSS_CTX_APP_BACKUP | VSS_VOLSNAP_ATTR_TRANSPORTABLE
posted @
2019-11-06 18:01 春秋十二月 閱讀(927) |
評論 (0) |
編輯 收藏
1. 綁定變量作為一種優化查詢處理的方法,在性能上有利有弊,是一把雙刃劍。它的優勢在于可以共享庫緩存中的父游標,從而避免了硬解析及相關的開銷;劣勢在于因綁定變量掃視增加了查詢優化器選擇(非常)低效執行計劃的風險,即使支持自適應游標共享,也引入了游標感知判斷和謂詞選擇率估算的代價,而且在生成高效的執行計劃前至少有一次是無效率的。因此,是否使用綁定變量,需要衡量實際字面值與處理數據量帶來的解析執行的收益與損害,當損害大于收益時就不應該使用,反之當處理較少數據硬解析耗時比執行多時,就可以使用了
2. 存儲快照一般有三種層次:物理卷、文件系統和應用程序
◆ 物理卷快照基于卷扇區映射表實現,宜采用CoFW法,因為它不必每次寫io都去遍歷映射表,比RoFW快
◆ 文件系統快照基于inode樹即元數據復制實現,每當寫io時更新快照或源inode的指向,必要時向上回溯至根inode。有的文件系統比如NetApp公司的WAFL則更優,只須復制根inode,因為每次寫io時它會變但其下所有的inode不會變
◆ 應用程序快照最典型的就是數據庫,原理本質與上述兩種一樣,基于頁改變位圖,當page首次(相對于快照創建時刻)改變時拷貝到快照文件(一種稀疏文件),另外當撤消未提交事務或回滾事務時也會發生拷貝(此時快照慢慢不再稀疏),這是為了保證快照的可用一致性
3.
數據塊的加鎖有單機和分布式兩種情景,前者是為了同步單實例事務的并發,后者是為了協調分布式事務的同步,并與緩存一致性協議緊密聯系。undo,redo,undo/redo三種日志對數據臟塊與提交日志記錄落盤的順序要求各不同,因此恢復方式不同。脫服務器備份架構比較好,具有不占用應用服務器資源的優勢,而微軟的vss可傳輸卷影拷貝提供了這一支持,足見其技術的先進前瞻性
4. Oracle的
實例恢復完全靠在線重做日志,介質恢復必須靠歸檔重做日志,以及在線重做日志。然而在線重做日志是有限數量的,那么Oracle是怎樣保證宕機經實例恢復后不丟數據?答案是檢查點。檢查點是數據庫中一個很重要的機制,被重做日志切換觸發,由DBWn執行刷新臟塊,并清除老的無用的在線重做日志,以允許被覆蓋
5. Linux內核的swap高速緩存和其它的緩存(比如page緩存)不太一樣,因為它存在的主要原因不是為了減少磁盤IO提高性能,而是解決換入換出共享匿名頁同步即并發swap的問題。那么它是唯一的方法嗎?不一定,可以遍歷所有的anon_vma鏈表,查找匿名頁對應的頁框是否已建立,但該方法沒有swap緩存快。當然,在換入操作很多的情景,swap緩存確實能提高系統性能
6. Linux內存回收的核心是LRU鏈表,Oracle的buffer cache也有個LRU,這兩種LRU的共同點是引用計數(標志)和非活躍鏈表,引用計數會影響一個對象是否移到非活躍鏈表,非活躍鏈表用于回收或覆蓋這個對象。對于Linux這個對象是頁框,移到非活躍鏈表取決于swap tendency;而Oracle則是數據塊buffer及其TCH
7. Linux內核中的反向映射讓我想起了Oracle中的反向鍵索引,它們的共同點都是為了高性能,前者是為了快速定位引用同一頁框的所有頁表項,從而方便共享內存的回收;后者是為了減少右側索引葉塊的競爭,從而降低緩沖區忙等待、提高并發量
8. mvcc與read uncommitted(簡稱RU)隔離級別的關系究竟如何?這取決于現代數據庫的實現。對于Oracle,RU和RC的讀實現都基于mvcc實現,換句話說Oracle其實沒有臟讀;對于MySQL innodb引擎,mvcc不適用于RU而只適用于RC/RR級別,因為RC/RR必須讀取修改已提交的數據,但基準點不同,前者查詢開始時、后者事務開始時,而RU則可讀取未提交的數據,當然用mvcc模擬實現RU應該也可以,只需要讀取當前新版本而非舊版本
9. 借助內核page cache的數據庫或者存儲引擎,一定程度上講,是粗暴懶惰的表現,這會導致系統負載比較重的情況下,io性能很差。所以為高性能,必須得處理好direct io,設計self cache,這樣一來,就避免了浪費在原先內核頁緩存的頁框,避免處理內核頁緩存和預讀的多余指令而提高了系統調用read和write的效率,同時減少了一次數據拷貝
10. SQL半連接的本質是在內連接的基礎上對內表去重,即使內表有符合多個連接條件的元組,也只匹配一條,從而減少了連接返回的結果集。一般地,簡單的in、exists和any子句,都采用半連接實現,但若內表本身保證了唯一性,則半連接可消除轉為內連接實現,或者內表數據量很小且外表存在索引,那么也會消除半連接,生成由內表驅動外表,外表走索引的執行計劃。由此一例看出,SQL優化器偏愛內連接,因為內連接帶來了驅動表選擇和謂詞下推的靈活,便于產生更優的執行計劃
11. 從Oracle數據庫內核角度講,游標代表SQL語句的句柄,包含了依賴對象及執行計劃等信息,它相當于linux的文件描述符和windows的句柄。打開或緩存的游標是指對應SQL語句所占的內存(父游標句柄、父堆0和子游標句柄的chunk)被加上kgl lock和pin鎖,意味著第三次后解析同樣的SQL不必再從library cache hash chain中加鎖查找而直接從PGA的子堆6地址中獲取并調用執行計劃,如此優化提高了并發度加快了查詢,這正是軟軟解析;軟軟解析前必須軟解析2次,目的是將library cache的執行計劃在PGA中做一份鏈接,軟解析前必須硬解析,目的是將執行計劃放在library cache中。然而,如果共享池空閑內存不足,或者依賴對象發生DDL操作導致執行計劃失效,那么執行計劃所占chunk可以被覆蓋釋放,這樣一來,軟(軟)解析時就需要重新生成執行計劃了
12. Oracle的內存管理粗略地類似于Linux內核,所不同的是內存分配單元,前者叫granule通常大小4M~16M,后者叫page通常4K;數據塊緩沖的分配類似伙伴算法,共享池(主要用于sql緩存)的chunk分配類似slab算法,共享池中的保留池類似基于slab的內存池
13. Oracle數據庫究竟是怎樣構建表數據塊的讀一致性版本?這是個比較復雜、細致和有趣的問題,核心流程如下
◆ 克隆數據塊,若不存在則先從磁盤讀,下面幾步以克隆塊為目標
◆ 根據ITL中的flag及lck,對所有已提交的事務做清除操作,即延遲塊清除。延遲塊清除為了獲取足夠精確的提交SCN填充到ITL,分2種情況,若事務表槽沒被覆蓋,則直接用其提交SCN;否則先從事務控制區獲取SCN,并判斷對于上界提交是否足夠精確,若不夠則需要回滾事務表一直找到合適的SCN或報錯ORA-01555
◆ 根據ITL中的uba,反向更改所有未提交的事務,也就是應用事務的undo記錄
◆ 根據ITL中的SCN,不斷反向更改大于目標SCN的已提交事務,直至遇見合適的已提交事務。這里也是應用undo記錄,但不同的是,除了應用行數據,還會從事務的第一個undo記錄找到先前即前一個已提交事務的ITL項拷貝回當前塊的對應ITL項
14. Oracle的多版本控制機制,為dml不僅提供了一致且正確的結果,還提高了并發性,可謂魚和熊掌兼得。那么它的缺點是什么?可能會導致熱表的IO增高,因為讀一致性需要不斷回滾多個事務對數據塊的修改,直到查詢開始時的數據。事務隔離級別read committed與read uncommitted的相同是不會臟讀,區別是前者會不可重復讀或幻讀
15. Sql*plus的ARRAYSIZE對查詢數據性能有重要的影響,這個值過大過小都不好,而是要接近一個數據塊所擁有的行數,如此僅一次邏輯IO就拿到了一批行。那么設置合適的ARRAYSIZE就一定能提高性能嗎?不一定,還要看所查詢的表使用了什么索引列及表數據在磁盤上的物理布局,若數據分散即聚簇因子低,則優化器會選用全表而非索引區間掃描,去執行這個查詢
16. IOT表如果為非主鍵列再建索引,那么就成二級索引。這時候查詢數據,需要兩次掃描,一是掃描二級索引得到IOT中的位置,二是掃描IOT本身匹配那個位置,之所以這樣是因為行記錄在IOT中的位置會變。而堆組織表,僅需一次掃描索引結構,得到rowid,再直接讀磁盤獲取行記錄。因此IOT上再建二級索引,并非明智的選擇
17. 相容性矩陣是封鎖調度的核心結構; 任意一個無環優先圖的封鎖調度都是沖突可串行化的; 基于樹協議的無環優先圖的封鎖調度,其整個事務集合的任意一個拓撲順序都是等價可串行化的
18. 總結解決數據庫丟失更新問題的方案
◆ 對于表不會被悲觀鎖鎖定的情景:使用基于select+update的樂觀鎖方法,查詢保存前映像,以便定位更新。前映像列可為全列,或新增一個時間戳列作為版本列
◆ 對于表可能會被悲觀鎖鎖定的情景:使用select…for update nowait+update的悲觀鎖方法,可以以全列的hash(虛擬列)來定位更新
19. 如果能夠在備庫上打開閃回,那么就可以做到既讓生產系統沒有承擔閃回的開銷,又能快速地為錯誤或故障恢復到以前某個時刻。一舉兩得比較完美,重做日志的創新使用真是太棒了
20. Oracle的索引聚簇表是個創新,它能將多個不同表的行按照索引列存儲在同一塊中,屬于物理上的join,這樣一來既可減少data buffer緩存的塊數而提高效率,又可提高多個相關表連接查詢的性能,比如通過外鍵約束的父子表。最典型的應用就是數據字典,數據字典對于查詢優化的成本估算很重要,由此可見oracle的設計之明智,mysql的innodb只有索引組織表,sql server有堆表和索引組織表,但它們都沒有索引聚簇表
21. 分布式事務處理是工程難題。Oracle的serializable串行隔離級別以樂觀鎖實現,所以并發度與非串行相當,需要注意的是:串行并不是說一個事務提交了才能處理下一個,而是多個事務間沒有沖突表現地像只有一個事務在運行,否則Oracle的serializable級別就不存在拋出ORA-08177錯誤了
22. 理清read uncommitted事務隔離級別的鎖策略:讀不加共享鎖,寫加排它鎖直至提交,這里的鎖是指lock;塊的緩沖區并發操作必須加鎖,這里的鎖是指latch,若不加,那臟讀讀到的數據可能是錯的。臟讀隔離級別允許讀修改但未提交的行記錄,這意味著讀不能被寫阻塞,也不能阻塞寫,所以不會申請共享鎖(顯式鎖定讀除外)
23. 與MySQL不同,Oracle的行鎖無需索引列的限制,是真正的行鎖,其實現為數據塊的屬性而非傳統的鎖管理器,但是它需要在事務commit或rollback時才釋放,如果存在慢sql,那么導致的阻塞會比較嚴重
24. 隔離是實現安全的一種辦法,其結果常被稱作“沙箱”。從這個意義上講Oracle很明智,因為它的事務沒有也不需要read uncommitted隔離級別,Oracle最低且默認的隔離級別是read committed,因為它有基于undo的多版本控制,天生非阻塞讀,根本不會臟讀。我想不出read uncommitted有什么好處,除了非阻塞讀及可能的高并發,要謹慎臟讀是危險不安全的
25. windows內存映射和linux內存映射的實現機制不太一樣,前者使用了內存區section的專用數據結構而不像后者重用了頁緩存,內存區的映射完全由內存管理器負責包括物理頁分配及臟頁面寫入器,與緩存管理器無關;緩存管理器基于內存管理器維護了文件塊數據的視圖,并提供了自己的延遲寫入器。這兩種寫入器即回刷,獨立并行地工作
posted @
2019-11-06 11:29 春秋十二月 閱讀(8109) |
評論 (0) |
編輯 收藏
腳本概述 由于某些sdk或軟件依賴眾多的第三方庫,而從官網下載到windows主機或從linux傳到windows時,所依賴的so庫往往丟失符號鏈接,給編譯運行帶來不便,因此編寫了
ctlsolink腳本,用于自動為單個so或某目錄下的眾多so或創建/刪除一級/二級符號鏈接。該腳本的用法如下:
● 第1參數為mk或rm子命令,mk表示創建,rm表示刪除
● 第2參數為文件或目錄
● 第3參數是可選的-r,且只能是-r,如果指定了,則表示不斷遞歸子目錄
腳本實現
考慮到so庫帶版本一般多為libx.so.1,libx.so.1.2,libx.so.1.2.3這三種形式(x為庫名),對于前一種創建/刪除一級符號鏈接即可,后兩種則創建/刪除二級符號鏈接。為了精確地抽出一級和二級鏈接名稱,這里使用awk來匹配,用shell變量的最短匹配模式從尾部逐步刪除點號及數字,核心代碼如下
1
if [ "$dir" != "$self_dir" ] || [ "$name" != "$self_name" ]; then
2
if echo $name | awk '{if($0~/\.so\.[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}$/) exit 0; else exit 1}'; then
3
link_name=${name%.[0-9]*}
4
link_name=${link_name%.[0-9]*}
5
link_name=${link_name%.[0-9]*}
6
link_name2=${name%.[0-9]*}
7
link_name2=${link_name2%.[0-9]*}
8
elif echo $name | awk '{if($0~/\.so\.[0-9]{1,}\.[0-9]{1,}$/) exit 0; else exit 1}'; then
9
link_name=${name%.[0-9]*}
10
link_name=${link_name%.[0-9]*}
11
link_name2=${name%.[0-9]*}
12
elif echo $name | awk '{if($0~/\.so\.[0-9]{1,}$/) exit 0; else exit 1}'; then
13
link_name=${name%.[0-9]*}
14
else
15
return
16
fi
17
18
if [ $do_mk = "yes" ]; then
19
#echo "name=$name, link_name=$link_name, link_name2=$link_name2"
20
if [ -n "$link_name2" ]; then
21
ln -sf $name $link_name2
22
ln -sf $link_name2 $link_name
23
else
24
ln -sf $name $link_name
25
fi
26
else
27
if [ -n $link_name2 ]; then
28
rm -f $link_name2
29
fi
30
rm -f $link_name
31
fi
32
fi 要注意的是,這兒不能使用%%刪除最長匹配的尾部來得到
link_name,因為它的模式是
.[0-9]*,這可能會錯誤地匹配了so前的部分,比如libx.1.so.2得到libx,而期望的是libx.1.so
完整腳本下載:
ctlsolink 運行效果 初始狀態

運行ctlsolink創建軟鏈接后

運行ctlsolink刪除軟鏈接后
posted @
2019-11-05 18:17 春秋十二月 閱讀(1984) |
評論 (0) |
編輯 收藏