0%

简单调研一圈,目前c语言可用的rpc框架有,Thrift,protobuf-c-rpc,rpcgen,这里只是简单记录一下调用demo,C语言用的还是不太熟悉,凑合看了。

Thrift

这里说一下Thrift的特点

  • 同步调用,相对比较容易处理
  • 需要修改request_service.c,也就是服务实现具体的方法,需要修改request_service.c文件(暂时没找到其他办法)
  • 依赖glibc

编译安装Thrift

1
2
3
4
5
6
# git clone https://github.com/apache/thrift.git
# cd thrift
# ./bootstrap.sh
# ./configure
# make
# make install

依赖

  • glibc-2.0

编写协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace cl shared


struct Param {
1: string key
2: i32 value
}

struct Result {
1: bool result
}

service RequestService {
Result sendMessage(1: Param param)
}

Server端

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
#include <glib-object.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>

#include <thrift/c_glib/thrift.h>
#include <thrift/c_glib/protocol/thrift_binary_protocol_factory.h>
#include <thrift/c_glib/protocol/thrift_protocol_factory.h>
#include <thrift/c_glib/server/thrift_server.h>
#include <thrift/c_glib/server/thrift_simple_server.h>
#include <thrift/c_glib/transport/thrift_buffered_transport_factory.h>
#include <thrift/c_glib/transport/thrift_server_socket.h>
#include <thrift/c_glib/transport/thrift_server_transport.h>

#include "gen-c_glib/request_service.h"




ThriftServer *server = NULL;
gboolean sigint_received = FALSE;

/* Handle SIGINT ("Ctrl-C") signals by gracefully stopping the
server */
static void
sigint_handler(int signal_number) {
THRIFT_UNUSED_VAR (signal_number);

/* Take note we were called */
sigint_received = TRUE;

/* Shut down the server gracefully */
if (server != NULL)
thrift_server_stop(server);
}

int main(void) {
RequestServiceHandler *handler;
RequestServiceProcessor *processor;
ThriftServerTransport *server_transport;
ThriftTransportFactory *transport_factory;
ThriftProtocolFactory *protocol_factory;

struct sigaction sigint_action;

GError *error = NULL;
int exit_status = 0;

handler = g_object_new(TYPE_REQUEST_SERVICE_HANDLER, NULL);
processor = g_object_new(TYPE_REQUEST_SERVICE_PROCESSOR, "handler", handler, NULL);
server_transport = g_object_new(THRIFT_TYPE_SERVER_SOCKET, "port", 9090, NULL);
transport_factory = g_object_new(THRIFT_TYPE_BUFFERED_TRANSPORT_FACTORY, NULL);
protocol_factory = g_object_new(THRIFT_TYPE_BINARY_PROTOCOL_FACTORY, NULL);
server = g_object_new(THRIFT_TYPE_SIMPLE_SERVER,
"processor", processor,
"server_transport", server_transport,
"input_transport_factory", transport_factory,
"output_transport_factory", transport_factory,
"input_protocol_factory", protocol_factory,
"output_protocol_factory", protocol_factory,
NULL);
memset(&sigint_action, 0, sizeof(sigint_action));
sigint_action.sa_handler = sigint_handler;
sigint_action.sa_flags = SA_RESETHAND;
sigaction(SIGINT, &sigint_action, NULL);
printf("Starting the server...\n");
thrift_server_serve(server, &error);
printf("server %p error %p\n",server,error);
if (!sigint_received) {
g_message ("thrift_server_serve: %s",
error != NULL ? error->message : "(null)");
g_clear_error(&error);
}

puts("done.");

g_object_unref(server);
g_object_unref(transport_factory);
g_object_unref(protocol_factory);
g_object_unref(server_transport);

g_object_unref(processor);
g_object_unref(handler);

return exit_status;
}

Server业务部分(request_service.c)

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
/**
* Autogenerated by Thrift Compiler (0.17.0)
*
* DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
* @generated
*/
#include <string.h>
#include <thrift/c_glib/thrift.h>
#include <thrift/c_glib/thrift_application_exception.h>
#include <stdio.h>
#include <stdbool.h>
#include "request_service.h"

gboolean
request_service_if_send_message(RequestServiceIf *iface, Result **_return, const Param *param, GError **error) {
return REQUEST_SERVICE_IF_GET_INTERFACE (iface)->send_message(iface, _return, param, error);
}

GType
request_service_if_get_type(void) {
static GType type = 0;
if (type == 0) {
static const GTypeInfo type_info =
{
sizeof(RequestServiceIfInterface),
NULL, /* base_init */
NULL, /* base_finalize */
NULL, /* class_init */
NULL, /* class_finalize */
NULL, /* class_data */
0, /* instance_size */
0, /* n_preallocs */
NULL, /* instance_init */
NULL /* value_table */
};
type = g_type_register_static(G_TYPE_INTERFACE,
"RequestServiceIf",
&type_info, 0);
}
return type;
}

static void
request_service_if_interface_init(RequestServiceIfInterface *iface);

G_DEFINE_TYPE_WITH_CODE (RequestServiceClient, request_service_client,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(TYPE_REQUEST_SERVICE_IF,
request_service_if_interface_init))

enum _RequestServiceClientProperties {
PROP_0,
PROP_REQUEST_SERVICE_CLIENT_INPUT_PROTOCOL,
PROP_REQUEST_SERVICE_CLIENT_OUTPUT_PROTOCOL
};

void
request_service_client_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
RequestServiceClient *client = REQUEST_SERVICE_CLIENT (object);

THRIFT_UNUSED_VAR (pspec);

switch (property_id) {
case PROP_REQUEST_SERVICE_CLIENT_INPUT_PROTOCOL:
client->input_protocol = g_value_get_object(value);
break;
case PROP_REQUEST_SERVICE_CLIENT_OUTPUT_PROTOCOL:
client->output_protocol = g_value_get_object(value);
break;
}
}

void
request_service_client_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
RequestServiceClient *client = REQUEST_SERVICE_CLIENT (object);

THRIFT_UNUSED_VAR (pspec);

switch (property_id) {
case PROP_REQUEST_SERVICE_CLIENT_INPUT_PROTOCOL:
g_value_set_object(value, client->input_protocol);
break;
case PROP_REQUEST_SERVICE_CLIENT_OUTPUT_PROTOCOL:
g_value_set_object(value, client->output_protocol);
break;
}
}

gboolean request_service_client_send_send_message(RequestServiceIf *iface, const Param *param, GError **error) {
gint32 cseqid = 0;
ThriftProtocol *protocol = REQUEST_SERVICE_CLIENT (iface)->output_protocol;

if (thrift_protocol_write_message_begin(protocol, "sendMessage", T_CALL, cseqid, error) < 0)
return FALSE;

{
gint32 ret;
gint32 xfer = 0;


if ((ret = thrift_protocol_write_struct_begin(protocol, "sendMessage_args", error)) < 0)
return 0;
xfer += ret;
if ((ret = thrift_protocol_write_field_begin(protocol, "param", T_STRUCT, 1, error)) < 0)
return 0;
xfer += ret;
if ((ret = thrift_struct_write(THRIFT_STRUCT (param), protocol, error)) < 0)
return 0;
xfer += ret;

if ((ret = thrift_protocol_write_field_end(protocol, error)) < 0)
return 0;
xfer += ret;
if ((ret = thrift_protocol_write_field_stop(protocol, error)) < 0)
return 0;
xfer += ret;
if ((ret = thrift_protocol_write_struct_end(protocol, error)) < 0)
return 0;
xfer += ret;

}

if (thrift_protocol_write_message_end(protocol, error) < 0)
return FALSE;
if (!thrift_transport_flush(protocol->transport, error))
return FALSE;
if (!thrift_transport_write_end(protocol->transport, error))
return FALSE;

return TRUE;
}

gboolean request_service_client_recv_send_message(RequestServiceIf *iface, Result **_return, GError **error) {
gint32 rseqid;
gchar *fname = NULL;
ThriftMessageType mtype;
ThriftProtocol *protocol = REQUEST_SERVICE_CLIENT (iface)->input_protocol;
ThriftApplicationException *xception;

if (thrift_protocol_read_message_begin(protocol, &fname, &mtype, &rseqid, error) < 0) {
if (fname) g_free(fname);
return FALSE;
}

if (mtype == T_EXCEPTION) {
if (fname) g_free(fname);
xception = g_object_new(THRIFT_TYPE_APPLICATION_EXCEPTION, NULL);
thrift_struct_read(THRIFT_STRUCT (xception), protocol, NULL);
thrift_protocol_read_message_end(protocol, NULL);
thrift_transport_read_end(protocol->transport, NULL);
g_set_error(error, THRIFT_APPLICATION_EXCEPTION_ERROR, xception->type, "application error: %s",
xception->message);
g_object_unref(xception);
return FALSE;
} else if (mtype != T_REPLY) {
if (fname) g_free(fname);
thrift_protocol_skip(protocol, T_STRUCT, NULL);
thrift_protocol_read_message_end(protocol, NULL);
thrift_transport_read_end(protocol->transport, NULL);
g_set_error(error, THRIFT_APPLICATION_EXCEPTION_ERROR, THRIFT_APPLICATION_EXCEPTION_ERROR_INVALID_MESSAGE_TYPE,
"invalid message type %d, expected T_REPLY", mtype);
return FALSE;
} else if (strncmp(fname, "sendMessage", 11) != 0) {
thrift_protocol_skip(protocol, T_STRUCT, NULL);
thrift_protocol_read_message_end(protocol, error);
thrift_transport_read_end(protocol->transport, error);
g_set_error(error, THRIFT_APPLICATION_EXCEPTION_ERROR, THRIFT_APPLICATION_EXCEPTION_ERROR_WRONG_METHOD_NAME,
"wrong method name %s, expected sendMessage", fname);
if (fname) g_free(fname);
return FALSE;
}
if (fname) g_free(fname);

{
gint32 ret;
gint32 xfer = 0;
gchar *name = NULL;
ThriftType ftype;
gint16 fid;
guint32 len = 0;
gpointer data = NULL;


/* satisfy -Wall in case these aren't used */
THRIFT_UNUSED_VAR (len);
THRIFT_UNUSED_VAR (data);

/* read the struct begin marker */
if ((ret = thrift_protocol_read_struct_begin(protocol, &name, error)) < 0) {
if (name) g_free(name);
return 0;
}
xfer += ret;
if (name) g_free(name);
name = NULL;

/* read the struct fields */
while (1) {
/* read the beginning of a field */
if ((ret = thrift_protocol_read_field_begin(protocol, &name, &ftype, &fid, error)) < 0) {
if (name) g_free(name);
return 0;
}
xfer += ret;
if (name) g_free(name);
name = NULL;

/* break if we get a STOP field */
if (ftype == T_STOP) {
break;
}

switch (fid) {
case 0:
if (ftype == T_STRUCT) {
if ((ret = thrift_struct_read(THRIFT_STRUCT (*_return), protocol, error)) < 0) {
return 0;
}
xfer += ret;
} else {
if ((ret = thrift_protocol_skip(protocol, ftype, error)) < 0)
return 0;
xfer += ret;
}
break;
default:
if ((ret = thrift_protocol_skip(protocol, ftype, error)) < 0)
return 0;
xfer += ret;
break;
}
if ((ret = thrift_protocol_read_field_end(protocol, error)) < 0)
return 0;
xfer += ret;
}

if ((ret = thrift_protocol_read_struct_end(protocol, error)) < 0)
return 0;
xfer += ret;

}

if (thrift_protocol_read_message_end(protocol, error) < 0)
return FALSE;

if (!thrift_transport_read_end(protocol->transport, error))
return FALSE;

return TRUE;
}

gboolean
request_service_client_send_message(RequestServiceIf *iface, Result **_return, const Param *param, GError **error) {
if (!request_service_client_send_send_message(iface, param, error))
return FALSE;
if (!request_service_client_recv_send_message(iface, _return, error))
return FALSE;
return TRUE;
}

static void
request_service_if_interface_init(RequestServiceIfInterface *iface) {
iface->send_message = request_service_client_send_message;
}

static void
request_service_client_init(RequestServiceClient *client) {
client->input_protocol = NULL;
client->output_protocol = NULL;
}

static void
request_service_client_class_init(RequestServiceClientClass *cls) {
GObjectClass *gobject_class = G_OBJECT_CLASS (cls);
GParamSpec *param_spec;

gobject_class->set_property = request_service_client_set_property;
gobject_class->get_property = request_service_client_get_property;

param_spec = g_param_spec_object("input_protocol",
"input protocol (construct)",
"Set the client input protocol",
THRIFT_TYPE_PROTOCOL,
G_PARAM_READWRITE);
g_object_class_install_property(gobject_class,
PROP_REQUEST_SERVICE_CLIENT_INPUT_PROTOCOL, param_spec);

param_spec = g_param_spec_object("output_protocol",
"output protocol (construct)",
"Set the client output protocol",
THRIFT_TYPE_PROTOCOL,
G_PARAM_READWRITE);
g_object_class_install_property(gobject_class,
PROP_REQUEST_SERVICE_CLIENT_OUTPUT_PROTOCOL, param_spec);
}

static void
request_service_handler_request_service_if_interface_init(RequestServiceIfInterface *iface);

G_DEFINE_TYPE_WITH_CODE (RequestServiceHandler,
request_service_handler,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(TYPE_REQUEST_SERVICE_IF,
request_service_handler_request_service_if_interface_init))

gboolean
request_service_handler_send_message(RequestServiceIf *iface, Result **_return, const Param *param, GError **error) {
g_return_val_if_fail (IS_REQUEST_SERVICE_HANDLER(iface), FALSE);


printf("%p %p \n", param->key, error); // 这里写自己server的业务,有点头大。。。

return REQUEST_SERVICE_HANDLER_GET_CLASS (iface)->send_message(iface, _return, param, error);
}

static void
request_service_handler_request_service_if_interface_init(RequestServiceIfInterface *iface) {
iface->send_message = request_service_handler_send_message; //这里自己注册
}

static void
request_service_handler_init(RequestServiceHandler *self) {
THRIFT_UNUSED_VAR (self);
}

static gboolean
request_service_handler_sends_message(RequestServiceIf *iface, Result **_return, const Param *param, GError **error) {
THRIFT_UNUSED_VAR (iface);
THRIFT_UNUSED_VAR (error);
printf("service request_service_handler_sends_message run key-> %s, value -> %d \n",param->key,param->value);
puts("zip()");
Result *result = g_object_new(TYPE_RESULT, NULL);
result->result = true;
*_return = result;
return TRUE;
}

static void
request_service_handler_class_init(RequestServiceHandlerClass *cls) {
printf("request_service_handler_class_init in request\n");

cls->send_message = request_service_handler_sends_message;
}

enum _RequestServiceProcessorProperties {
PROP_REQUEST_SERVICE_PROCESSOR_0,
PROP_REQUEST_SERVICE_PROCESSOR_HANDLER
};

G_DEFINE_TYPE (RequestServiceProcessor,
request_service_processor,
THRIFT_TYPE_DISPATCH_PROCESSOR)

typedef gboolean (*RequestServiceProcessorProcessFunction)(RequestServiceProcessor *,
gint32,
ThriftProtocol *,
ThriftProtocol *,
GError **);

typedef struct {
gchar *name;
RequestServiceProcessorProcessFunction function;
} request_service_processor_process_function_def;

static gboolean
request_service_processor_process_send_message(RequestServiceProcessor *,
gint32,
ThriftProtocol *,
ThriftProtocol *,
GError **);

static request_service_processor_process_function_def
request_service_processor_process_function_defs[1] = {
{
"sendMessage",
request_service_processor_process_send_message
}
};

static gboolean
request_service_processor_process_send_message(RequestServiceProcessor *self,
gint32 sequence_id,
ThriftProtocol *input_protocol,
ThriftProtocol *output_protocol,
GError **error) {
gboolean result = TRUE;
ThriftTransport *transport;
ThriftApplicationException *xception;
RequestServiceSendMessageArgs *args =
g_object_new(TYPE_REQUEST_SERVICE_SEND_MESSAGE_ARGS, NULL);

printf("request_service_processor_process_send_message\n");
g_object_get(input_protocol, "transport", &transport, NULL);

if ((thrift_struct_read(THRIFT_STRUCT (args), input_protocol, error) != -1) &&
(thrift_protocol_read_message_end(input_protocol, error) != -1) &&
(thrift_transport_read_end(transport, error) != FALSE)) {
Param *param;
Result *return_value;
RequestServiceSendMessageResult *result_struct;

g_object_get(args,
"param", &param,
NULL);

g_object_unref(transport);
g_object_get(output_protocol, "transport", &transport, NULL);

result_struct = g_object_new(TYPE_REQUEST_SERVICE_SEND_MESSAGE_RESULT, NULL);
g_object_get(result_struct, "success", &return_value, NULL);

if (request_service_handler_send_message(REQUEST_SERVICE_IF (self->handler),
&return_value,
param,
error) == TRUE) {
g_object_set(result_struct, "success", return_value, NULL);

result =
((thrift_protocol_write_message_begin(output_protocol,
"sendMessage",
T_REPLY,
sequence_id,
error) != -1) &&
(thrift_struct_write(THRIFT_STRUCT (result_struct),
output_protocol,
error) != -1));
} else {
if (*error == NULL)
g_warning ("RequestService.sendMessage implementation returned FALSE "
"but did not set an error");

xception =
g_object_new(THRIFT_TYPE_APPLICATION_EXCEPTION,
"type", *error != NULL ? (*error)->code :
THRIFT_APPLICATION_EXCEPTION_ERROR_UNKNOWN,
"message", *error != NULL ? (*error)->message : NULL,
NULL);
g_clear_error(error);

result =
((thrift_protocol_write_message_begin(output_protocol,
"sendMessage",
T_EXCEPTION,
sequence_id,
error) != -1) &&
(thrift_struct_write(THRIFT_STRUCT (xception),
output_protocol,
error) != -1));

g_object_unref(xception);
}

if (param != NULL)
g_object_unref(param);
if (return_value != NULL)
g_object_unref(return_value);
g_object_unref(result_struct);

if (result == TRUE)
result =
((thrift_protocol_write_message_end(output_protocol, error) != -1) &&
(thrift_transport_write_end(transport, error) != FALSE) &&
(thrift_transport_flush(transport, error) != FALSE));
} else
result = FALSE;

g_object_unref(transport);
g_object_unref(args);

return result;
}

static gboolean
request_service_processor_dispatch_call(ThriftDispatchProcessor *dispatch_processor,
ThriftProtocol *input_protocol,
ThriftProtocol *output_protocol,
gchar *method_name,
gint32 sequence_id,
GError **error) {
request_service_processor_process_function_def *process_function_def;
gboolean dispatch_result = FALSE;

RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (dispatch_processor);
ThriftDispatchProcessorClass *parent_class =
g_type_class_peek_parent(REQUEST_SERVICE_PROCESSOR_GET_CLASS (self));

process_function_def = g_hash_table_lookup(self->process_map, method_name);
if (process_function_def != NULL) {
g_free(method_name);
dispatch_result = (*process_function_def->function)(self,
sequence_id,
input_protocol,
output_protocol,
error);
} else {
dispatch_result = parent_class->dispatch_call(dispatch_processor,
input_protocol,
output_protocol,
method_name,
sequence_id,
error);
}

return dispatch_result;
}

static void
request_service_processor_set_property(GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec) {
RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (object);

switch (property_id) {
case PROP_REQUEST_SERVICE_PROCESSOR_HANDLER:
if (self->handler != NULL)
g_object_unref(self->handler);
self->handler = g_value_get_object(value);
g_object_ref (self->handler);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

static void
request_service_processor_get_property(GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec) {
RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (object);

switch (property_id) {
case PROP_REQUEST_SERVICE_PROCESSOR_HANDLER:
g_value_set_object(value, self->handler);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

static void
request_service_processor_dispose(GObject *gobject) {
RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (gobject);

if (self->handler != NULL) {
g_object_unref(self->handler);
self->handler = NULL;
}

G_OBJECT_CLASS (request_service_processor_parent_class)->dispose(gobject);
}

static void
request_service_processor_finalize(GObject *gobject) {
RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (gobject);

thrift_safe_hash_table_destroy(self->process_map);

G_OBJECT_CLASS (request_service_processor_parent_class)->finalize(gobject);
}

static void
request_service_processor_init(RequestServiceProcessor *self) {
guint index;

self->handler = NULL;
self->process_map = g_hash_table_new(g_str_hash, g_str_equal);

for (index = 0; index < 1; index += 1)
g_hash_table_insert(self->process_map,
request_service_processor_process_function_defs[index].name,
&request_service_processor_process_function_defs[index]);
}

static void
request_service_processor_class_init(RequestServiceProcessorClass *cls) {
GObjectClass *gobject_class = G_OBJECT_CLASS (cls);
ThriftDispatchProcessorClass *dispatch_processor_class =
THRIFT_DISPATCH_PROCESSOR_CLASS (cls);
GParamSpec *param_spec;

gobject_class->dispose = request_service_processor_dispose;
gobject_class->finalize = request_service_processor_finalize;
gobject_class->set_property = request_service_processor_set_property;
gobject_class->get_property = request_service_processor_get_property;

dispatch_processor_class->dispatch_call = request_service_processor_dispatch_call;
cls->dispatch_call = request_service_processor_dispatch_call;

param_spec = g_param_spec_object("handler",
"Service handler implementation",
"The service handler implementation "
"to which method calls are dispatched.",
TYPE_REQUEST_SERVICE_HANDLER,
G_PARAM_READWRITE);
g_object_class_install_property(gobject_class,
PROP_REQUEST_SERVICE_PROCESSOR_HANDLER,
param_spec);
}

Client段

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
#include <stdio.h>
#include <glib-object.h>

#include <thrift/c_glib/protocol/thrift_binary_protocol.h>
#include <thrift/c_glib/transport/thrift_buffered_transport.h>
#include <thrift/c_glib/transport/thrift_socket.h>

#include "gen-c_glib/request_service.h"

int main() {
ThriftSocket *socket;
ThriftTransport *transport;
ThriftProtocol *protocol;
RequestServiceIf *client;
socket = g_object_new(THRIFT_TYPE_SOCKET,
"hostname", "localhost",
"port", 9090,
NULL);
transport = g_object_new(THRIFT_TYPE_BUFFERED_TRANSPORT,
"transport", socket,
NULL);
protocol = g_object_new(THRIFT_TYPE_BINARY_PROTOCOL,
"transport", transport,
NULL);
GError *error = NULL;


thrift_transport_open(transport, &error);
printf("open client err %p \n", error);
client = g_object_new(TYPE_REQUEST_SERVICE_CLIENT,
"input_protocol", protocol,
"output_protocol", protocol,
NULL);
printf("client init finish %p \n", client);
Result *result = g_object_new(TYPE_RESULT, NULL);
Param *param = g_object_new(TYPE_PARAM, NULL);
param->key = "test";
param->value = 2;
printf("param %s %d \n", param->key,param->value);
gboolean ret = request_service_if_send_message(client, &result, param, &error);
printf("ret %b \n", ret);
printf("result %d \n", result->result);
printf("error %s \n", error->message);

}

CMakeFile.txt

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
cmake_minimum_required(VERSION 3.24)
project(thrift_rpc C)

set(CMAKE_C_STANDARD 11)
include_directories(/usr/include/glib-2.0)
include_directories(/usr/lib/x86_64-linux-gnu/glib-2.0/include)
add_executable(thrift_rpc main.c gen-c_glib/request_service.c gen-c_glib/model_types.c)
target_link_libraries(thrift_rpc /usr/local/lib/libthrift_c_glib.so)
target_link_libraries(thrift_rpc /usr/lib/x86_64-linux-gnu/libgobject-2.0.so)
target_link_libraries(thrift_rpc /usr/lib/x86_64-linux-gnu/libglib-2.0.so)



project(thrift_rpc_server C)
set(CMAKE_C_STANDARD 11)

add_executable(thrift_rpc_server server.c gen-c_glib/request_service.c gen-c_glib/model_types.c)

include_directories(/usr/include/glib-2.0)
include_directories(/usr/lib/x86_64-linux-gnu/glib-2.0/include)

target_link_libraries(thrift_rpc_server /usr/local/lib/libthrift_c_glib.so)
target_link_libraries(thrift_rpc_server /usr/lib/x86_64-linux-gnu/libgobject-2.0.so)
target_link_libraries(thrift_rpc_server /usr/lib/x86_64-linux-gnu/libglib-2.0.so)



protobuf-c-rpc

这里说一下protobuf-c-rpc的特点

  • rpc的函数注册是基于前缀的,示例中的example__就是。
  • Client只能通过异步回调的方式(至少我没发现有方法可以同步获取结果)
  • 支持多种通信方式,tcp,sock等

编译安装protofbuf

1
2
3
4
5
6
7
# wget https://github.com/protocolbuffers/protobuf/releases/download/v22.0/protobuf-22.0.tar.gz
# tar -zxvf protobuf-22.0.tar.gz
# cd protobuf-22.0
# ./autogen.sh
# ./configure
# make
# make install

编译安装protofbuf-c

1
2
3
4
5
6
7
# wget https://github.com/protobuf-c/protobuf-c/releases/download/v1.4.1/protobuf-c-1.4.1.tar.gz
# tar -zxvf protobuf-c-1.4.1.tar.gz
# cd protobuf-c
# ./autogen.sh
# ./configure
# make
# make install

编译安装protofbuf-c-rpc

1
2
3
4
5
6
# git clone https://github.com/protobuf-c/protobuf-c-rpc.git
# cd protobuf-c-rpc
# ./autogen.sh
# ./configure
# make
# make install

编写协议

1
2
3
4
5
6
7
8
9
10
11
12
13
syntax = "proto3";

message Param {
string message = 1;
}
message Result {
bool result = 1;
}


service RpcSendMsg {
rpc Send (Param) returns (Result);
}

Server端

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
#include "stdio.h"
#include "model.pb-c.h"
#include <string.h>
#include <protobuf-c-rpc/protobuf-c-rpc.h>
#include <stdbool.h>

static void example__send(RpcSendMsg_Service *service, const Param *param, Result_Closure closure, void *closure_data) {
printf("example__send run message:%s \n",param->message);
(void) service;
Result result = RESULT__INIT;
if (strcmp(param->message, "hello") != 0) {
result.result = true;
} else {
result.result = false;
}
closure(&result, closure_data);
}

static RpcSendMsg_Service rpc_send_msg_service = RPC_SEND_MSG__INIT(example__);

int main(void) {
printf("service start \n");
ProtobufC_RPC_Server *server;
ProtobufC_RPC_AddressType address_type = PROTOBUF_C_RPC_ADDRESS_TCP;
server = protobuf_c_rpc_server_new(address_type, "12345", (ProtobufCService *) &rpc_send_msg_service, NULL);
printf("service start at 12345 port\n");
for (;;)
protobuf_c_rpc_dispatch_run(protobuf_c_rpc_dispatch_default());
}

Client段

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
//
// Created by Eviltuzki on 23-2-11.
//
#include "stdio.h"
#include <string.h>
#include "model.pb-c.h"
#include <protobuf-c-rpc//protobuf-c-rpc.h>


static void
handler_result(const Result *result, void *closure_data) {
printf("rpc result %d\n", result->result);
}

int main(void) {
ProtobufCService *service;
ProtobufC_RPC_Client *client;
ProtobufC_RPC_AddressType address_type = PROTOBUF_C_RPC_ADDRESS_TCP;
const char *name = "localhost:12345";
service = protobuf_c_rpc_client_new(address_type, name, &rpc_send_msg__descriptor, NULL);
printf("connenct to server service is %p \n",service);
if (service == NULL) {
printf("error creating client \n");
return 0;
}
client = (ProtobufC_RPC_Client *) service;
while (!protobuf_c_rpc_client_is_connected(client))
protobuf_c_rpc_dispatch_run(protobuf_c_rpc_dispatch_default());
protobuf_c_boolean is_done = 0;
Param param = PARAM__INIT;
param.message = "hello1";
rpc_send_msg__send(service, &param, handler_result, &is_done);
while (!is_done)
protobuf_c_rpc_dispatch_run(protobuf_c_rpc_dispatch_default());
return 0;
}

CMakeFile.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cmake_minimum_required(VERSION 3.24)
project(rpc C)

set(CMAKE_C_STANDARD 17)

add_executable(rpc main.c model.pb-c.h model.pb-c.c)
target_link_libraries(rpc /usr/local/lib//libprotobuf-c-rpc.so)
target_link_libraries(rpc /usr/local/lib/libprotobuf-c.so)
target_link_libraries(rpc /usr/local/lib/libprotoc.so)
target_link_libraries(rpc /usr/local/lib/libprotobuf-lite.so)
target_link_libraries(rpc /usr/local/lib/libprotobuf.so)


project(rpc-s C)
add_executable(rpc-s server.c model.pb-c.h model.pb-c.c)
target_link_libraries(rpc-s /usr/local/lib//libprotobuf-c-rpc.so)
target_link_libraries(rpc-s /usr/local/lib/libprotobuf-c.so)
target_link_libraries(rpc-s /usr/local/lib/libprotoc.so)
target_link_libraries(rpc-s /usr/local/lib/libprotobuf-lite.so)
target_link_libraries(rpc-s /usr/local/lib/libprotobuf.so)

rpcgen

网上例子比较多,这里就不列举了,需要注意的是
long long类型对应的是hyper
参考:
https://docs.oracle.com/cd/E26502_01/html/E35597/xdrproto-31244.html#xdrproto-18
https://docs.oracle.com/cd/E19683-01/816-1435/6m7rrfn7f/index.html

内存热插拔是容器在部署中动态分配内存的关键特性,在 x86 上,可以使用直接启用 ACPI 的 QEMU 启动 VM,因为它会隐式启动固件。但是对于 arm64,则需要明确指定固件。也就是说,如果准备在 arm64 上运行一个普通的 Kata Container,你需要额外做的是在使用内存热插拔功能之前安装 UEFI ROM。

安装 UEFI ROM

修改脚本适配系统

需要执行的脚本

1
$GOPATH/src/github.com/kata-containers/tests/.ci/aarch64/install_rom_aarch64.sh

该脚本默认适配的系统是Ubuntu,使用apt源安装相关依赖,适配麒麟系统需要修改为dnf源进行安装,将脚本安装依赖部分修改为

1
dnf install -y python python3 python3-distutils-extra uuid-devel bison flex

其他edk2相关git项,根据网络情况进行适配

执行脚本安装

1
sudo .ci/aarch64/install_rom_aarch64.sh

脚本执行安装完成后,默认kata的img路径下(/usr/share/kata-containers)生成2个文件kata-flash0.img和 kata-flash1.img

内核适配

内核默认不支持nvdimm,需要打社区提供的patch才能开启

目前适配的kernel版本是5.4.160,社区提供了5.4.x版本kernel相应的patch,路径如下:

1
$GOPATH/src/github.com/kata-containers/kata-containers/tools/packaging/kernel/patches/5.4.x/0006-arm64-mm-Enable-memory-hot-remove.patch

将该文件拷贝至kernel源码下,执行patch

1
2
3
cd {kernel_path}
cp $GOPATH/src/github.com/kata-containers/kata-containers/tools/packaging/kernel/patches/5.4.x/0006-arm64-mm-Enable-memory-hot-remove.patch ..
patch -p1 <../0006-arm64-mm-Enable-memory-hot-remove.patch

编译内核注意nvdimm相关的config要开启

1
2
3
4
5
6
CONFIG_ACPI_NFIT=y
CONFIG_ARCH_ENABLE_MEMORY_HOTREMOVE=y
CONFIG_MEMORY_HOTREMOVE=y
CONFIG_ZONE_DEVICE=y
CONFIG_NVDIMM_PFN=y
CONFIG_NVDIMM_DAX=y

重新编译后,替换编译后的内核文件

配置文件修改

1
2
3
4
5
6
7
8
# If false and nvdimm is supported, use nvdimm device to plug guest image.
# Otherwise virtio-block device is used.
# Default is false
#disable_image_nvdimm = true //这里不要打开

# -pflash can add image file to VM. The arguments of it should be in format
# of ["/path/to/flash0.img", "/path/to/flash1.img"]
pflashes = ["/usr/share/kata-containers/kata-flash0.img","/usr/share/kata-containers/kata-flash1.img"] //这里指定uefi的rom

注意事项

如果基于之前的kernel编译了驱动之类的内容,kernel打了patch需要重新编译驱动,否则出现mod无法加载的情况

Kata rootfs 适配昇腾GPU驱动(Arm平台)

虚拟机

选择

  • 系统:驱动适配比较好的系统是CentOS 7.6和8.2,本次制作使用CentOS 8.2.2004 arm 镜像
  • 虚拟机:驱动依赖硬件设备,所以选择在宿主机通过qemu启动虚拟机,在虚拟机内部适配驱动
  • 其他:开启kvm加速,实测arm架构下开启kvm,需要宿主机是L0(不能是虚拟机),且bios使用edk2
  • 宿主机:宿主机系统为银河麒麟v10 arm版

准备工作

制作虚拟机(宿主机)

  • 制作qcow2文件(相当于是硬盘, 文件名hd.qcow2 , 大小100G)
    • qemu-img create -f qcow2 hd.qcow2 100G
  • 安装操作系统
    • qemu启动命令:qemu-system-aarch64 -m 24G -cpu host -smp 16 -M virt,accel=kvm -bios QEMU_EFI.fd -nographic -drive if=none,file=hd.qcow2,id=hd0 -device virtio-blk-device,drive=hd0 -drive if=none,file=CentOS-8.2.2004-aarch64-dvd1.iso,id=cdrom,media=cdrom -device virtio-scsi-device -device scsi-cd,drive=cdrom -net user,hostfwd=tcp::2222-:22 -net nic -device vfio-pci,host=0000:83:00.0
    • qemu参数说明:
      • -m 24G 指定内存大小
      • -cpu host 指定CPU类型和宿主机一致,注意host类型需要开启kvm加速
      • -M virt,accel=kvm machineType类型为virt(arm均使用virt),加速器使用kvm
      • -smp 16 cpu 16核
      • -bios QEMU_EFI.fd 指定edk2的efi文件
      • -nographic 因为不是在本地,所以直接通过控制台以基础文本的形式显示
      • -drive if=none,file=hd.qcow2,id=hd0 -device virtio-blk-device,drive=hd0 指定硬盘设备及驱动
      • -drive if=none,file=CentOS-8.2.2004-aarch64-dvd1.iso,id=cdrom,media=cdrom -device virtio-scsi-device -device scsi-cd,drive=cdrom 指定操作系统安装文件,以cdrom方式加载
      • -net user,hostfwd=tcp::2222-:22 -net nic 开启与宿主机的共享网络,同时将虚拟机的22端口映射到宿主机的2222端口
      • -device vfio-pci,host=0000:83:00.0 将宿主机的设备vfio直通到虚拟机
    • 如果在宿主机无法正常安装,可以考虑本地启动qemu安装,然后将qcow2文件拷贝至服务器
    • 安装完成后重启系统即可,以后启动qemu cdrom参数不再需要。
  • 注意事项:
    • 虚拟机启动后,需要开启sshd,否则无法ssh连接到虚拟机
    • QEMU_EFI.fd一定用edk2的,不要使用https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/QEMU_EFI.fd 这个版本,这个无法开启kvm加速
    • 全虚拟化的虚拟机慢的要死,开机就要15分钟,一定要开启kvm加速,后面驱动适配在全虚拟化下也会失败(全虚拟化运行太慢,导致驱动执行失败)
    • vfio宿主机一定要开启,否则虚拟机无法获取设备
    • 设备号可以通过lspci看到

内核制作

修改内核magversion

  • 进入内核源码目录,修改 include/linux/vermagic.h
    • 修改#define MODULE_VERMAGIC_MODULE_UNLOAD ""
    • 为:#define MODULE_VERMAGIC_MODULE_UNLOAD "mod_unload"
    • 修改#define MODULE_VERMAGIC_MODVERSIONS ""
    • 为:#define MODULE_VERMAGIC_MODVERSIONS "modversions"

编译内核获取bzImage(宿主机或虚拟机都可)

  • 直接执行make就行,注意配置和之前保持一致
  • 编译完成后,将arch/aarch64/boot/bzImage拷贝出来,作为kata启动的kernel

制作内核rpm安装包(宿主机或虚拟机都可)

  • 保持配置不变,执行make rpm-pkg
  • 然后将kernel,kernel-devel,kernel-headers三个rpm拷贝出来

虚拟机安装新内核(虚拟机)

  • 将编译的rpm安装包拷贝至虚拟机(开启ssh后scp -P 2222 xxx xxx@localhost:~即可)
  • 进入虚拟机后,执行rpm -ivh kernel-xxxxx.rpm 安装内核,然后重启虚拟机,用编译版本的内核启动虚拟机,准备后续rootfs制作

rootfs制作

制作rootfs(宿主机或虚拟机都可)

  • 进入kata源码kata-containers/tools/osbuilder/目录下,执行make镜像命令
  • make DISTRO=centos OS_VERSION=8.2.2004 SECCOMP=no DEBUG=true USE_DOCKER=true AGENT_INIT=yes EXTRA_PKGS='net-tools pciutils udev e2fsprogs tar gcc' rootfs
  • net-tools pciutils udev e2fsprogs tar gcc 这几样工具是驱动安装过程中需要的,需要提前打包至rootfs

拷贝rootfs至虚拟机

  • 上一步操作完成后,当前目录下会出来centos_rootfs文件夹,将该目录打包,拷贝至虚拟机
  • tar czvf centos_rootfs.tgz centos_rootfs
  • scp -P 2222 centos_rootfs.tgz root@localhost:~

驱动适配

补充缺失文件(虚拟机,其实这一步再打包之前操作也可以)

  • 解压rootfs
  • /usr/share/zoneinfo文件夹拷贝至 rootfs对应的目录下(centos默认rootfs缺失时区信息,无法安装驱动)
  • 拷贝驱动文件,kernel-devel,kernel-headers到rootfs的某目录下(我拷贝到home目录下),用于后续chroot安装
  • 创建hook脚本
    • mkdir -p /usr/share/oci/hooks/prestart
    • cd /usr/share/oci/hooks/prestart
    • 创建mount_tmpfs.sh脚本,解决/tmp,/var/log 只读问题,内容如下:
1
2
3
#!/bin/sh
mount -t tmpfs -o size=8192m tmpfs /tmp
mount -t tmpfs -o size=8192m tmpfs /var/log

挂载目录(虚拟机)

  • export ROOTFS_DIR=/root/centos_rootfs
  • mount -t sysfs -o ro none ${ROOTFS_DIR}/sys
  • mount -t proc -o ro none ${ROOTFS_DIR}/proc
  • mount -o bind,ro /dev ${ROOTFS_DIR}/dev
  • mount -t devpts none ${ROOTFS_DIR}/dev/pts
  • mount -t tmpfs none ${ROOTFS_DIR}/tmp

chroot并安装驱动(虚拟机)

  • chroot $ROOTFS_DIR
  • 进入home目录(驱动和kernel目录)
  • 安装kernel-devel和kernel-headers (rpm -ivh kernel*.rpm)
  • 创建华为用户组HwHiAiUser 和 用户
    • groupadd HwHiAiUser
    • useradd -g HwHiAiUser -d /home/HwHiAiUser -m HwHiAiUser -s /bin/bash
  • 安装驱动 ./A300-3000-npu-driver_21.0.3.3_linux-aarch64.run –full
  • 驱动安装成功后,执行 /usr/local/sbin/npu-smi info 如果能正常回显,则继续安装驱动,不能正常回显,则重启虚拟机,先exit退出chroot
  • 解除挂载 umount ${ROOTFS_DIR}/sys ${ROOTFS_DIR}/proc ${ROOTFS_DIR}/dev/pts ${ROOTFS_DIR}/tmp ${ROOTFS_DIR}/dev
  • 然后reboot重启虚拟机,重启后重新挂载目录,然后执行 /usr/local/sbin/npu-smi info 看是否能正常回显
  • 正常回显后,安装固件 ./A300-3000-npu-firmware_1.81.22.2.220.run –full
  • 然后删除内核源码(/usr/src/kernels下,内核版本的源码删除),删除kernel-devel,kernel-headers,驱动等文件(减少rootfs镜像大小)
  • exit退出chroot
  • 解除挂载 umount ${ROOTFS_DIR}/sys ${ROOTFS_DIR}/proc ${ROOTFS_DIR}/dev/pts ${ROOTFS_DIR}/tmp ${ROOTFS_DIR}/dev
  • 将rootfs打包(如果rootfs在虚拟机中制作,则忽略该步骤),并拷贝到宿主机上

制作镜像

  • 解压虚拟机中的rootfs文件
  • 切换到kata的image-builder目录(kata-containers/tools/osbuilder/image-builder)
  • 制作镜像./image_builder.sh rootfsDir
  • 得到kata-containers.img

其他适配

配置文件

  • kata的config需要进行以下几个修改
    1. guest_hook_path = "/usr/share/oci/hooks"
    2. kernel_modules=["drv_devdrv_host","drv_davinci_intf_host","drv_tsdrv_platform_host","drv_pcie_vnic_host","drv_pcie_hdc_host","drv_devmm_host"]
    3. hotplug_vfio_on_root_bus = true
    4. pcie_root_port = 2
  • 说明:
    1. guest_hook_path 执行hook,路径不需要更改,驱动需要挂载tmpfs,通过hook执行
    2. kernel_modules 驱动的modules默认不会加载,需要手动加载,配置到这里,vm启动会自动加载
    3. hotplug_vfio_on_root_bus 开启root bus,arm平台需要通过pcie_root_port进行设备热插拔
    4. pcie_root_port 单个虚拟机最多可以挂载GPU设备数

Kata源码

  • 主要修改如下:
    • virt模式支持pcieRootPort设备,通过配置进行加载
    • qmp添加设备指定bus为pcieRootPort设备,arm下只有pcieRootPort支持热插拔

Q&A

  1. 启动后,kata-runtime exec 进入vm后,/dev/loop设备只有1个

    • 修改内核配置
      • CONFIG_BLK_DEV_LOOP=y
      • CONFIG_BLK_DEV_LOOP_MIN_COUNT=4
  2. qemu 使用 -cpu host -M virt,accel=kvm 启动虚拟机黑屏

    • bios的efi启动文件不正确,使用edk2的efi文件即可
  3. 安装驱动npu-smi info执行后提示 dcmi module initialize failed. ret is -8005

    • 提示-8005的原因有很多,下面列出来几个适配中遇到过的
      • 显卡设备被其他驱动占用,去/sys/bus/pci/drivers/devdrv_device_driver目录下重新bind设备即可。
      • 内核模块加载不全,lsmod对比kernel_modules中的配置,是否已经加载全。
      • 同时提示sh: /tmp/pci_get_info_234.tmp: Read-only file system检查Hook脚本是否执行,可以df看下挂载情况。
      • 其他原因,可以通过dmesg查看kernel日志,具体排查
  4. chroot后,安装驱动,提示缺少xxxx组件

    • EXTRA_PKGS后面追加缺少的命令的安装包即可,无依赖的,可直接拷贝到rootfs对应目录下
  5. kernel-devel和kernel-headers是否可以不安装?

    • 需要安装,驱动安装过程中,有部分模块依赖内核,需要通过内核源码进行重编译,安装完成后,删除对应src目录即可。
    • 通过dkms安装会快点,但是依赖过多,不建议
  6. 内核日志提示:drv_seclib_host: version magic '5.4.160-1.el7.aarch64 SMP mod_unload modversions aarch64' should be '5.4.160-1.el7.aarch64 SMP aarch64'

    • 内核的version magic和编译的驱动的version magic不一致,参考内核部分修改version magic,重新编译即可
  7. qemu启动时加上-net user提示 -net user: Parameter 'type' expects a net backend type (maybe it is not compiled into this binary)

    • 编译qemu缺少slirp,重新config qemu,加上slirp后重新make qemu即可

Rust小试牛刀

Rust的书已经看完2本了,看程序基本上能看懂一些了(生命周期这块还是迷迷糊糊),但是觉得多少还是要自己写一下程序,实际应用一下才能学会。正好今天找了一个练手的小项目,初步尝试一下

背景

N年之前给媳妇写过一个小程序,处理Excel数据的,大致就是从A.xlsx读取数据,分组合并并计算,然后追加写入到B.xlsx,但是其中有个字典关系的映射,以前是维护在代码中的,每次修改字典都要重新编译。
加上当时程序是.Net写的,WinForm程序,依赖Windows和VisualStudio这个组合,最近今年工作基本就没用过Windows,电脑也换成了M1的Mac,给她处理这个程序要安装个arm版Windows不说,还要安装VisualStudio,本就不富裕的硬盘,又被拖走了几十个G。
想着既然学了Rust,加上为了处理这个程序,索性就用Rust重写一份吧。

准备工作

  1. Rust的安装就不多说什么了,按照官网的脚本执行一下就搞定了
  2. 编辑器我用的是VisualCode,配合rust-analyzer插件,debug就是直接用CodeLLDB。
  3. 因为要解析xlsx文件,在https://lib.rs/search?q=xlsx查了一圈,最后决定用umya-spreadsheet="0.8.3"
  4. 因为有个需要配置的字典,考虑json和toml,最后决定用toml,因为对我媳妇而已,这个更直接一些,最终选择toml="0.5.0"

开工

cargo new一个项目,添加好依赖,开整,只是逻辑的话,其实很简单,按照.Net的逻辑直接迁移过来就行了,不过有一说一,写的过程中还是挺多麻烦的,光看书以为自己掌握的东西,coding的时候却干着急想不起来怎么写,最后还是依赖搜索引擎解决。
另外还有一点就是关于mod,直到程序写完了,也没太get这个玩意和目录的关系怎么搞。所以最终的项目还是基于一个main.rs实现的。
逻辑啥的,就不具体写这里了,记录一下自己开发过程中记得住记不住的东西吧。

结构体

读取数据之后肯定不能直接用个元祖或者Map传来传去,怎么也得有个结构体来组装数据,为了隔离开,我这里还是用mod来进行一下简单的区分。

1
2
3
4
5
6
7
8
9
pub mod model {
#[derive(Debug, Clone)]
pub struct ProductModel {
pub field1: String,
pub field2: String,
pub field3: i32,
pub type_: String,
}
}

别看就上面几个字段,就这个type_还折腾我一会,因为type是关键字,如果确实要用type做这个字段名称,就要用r#type, 还有就是从mod到struce到field,都要pub,否则访问不了(开始在不同目录,后来放到一个文件了,也没调整了,这块后面再补习一下吧)。

xlsx处理lib-umya_spreadsheet

rust处理Excel的lib本来就不是很多,还有很多是只支持读取不支持写入或者只支持写入不支持读取的,好不容易找到了这么一个既支持读又支持写入的,搜了一圈,除了官方文档,几乎没有任何资料。我处理xlsx的时候基本上是按照行进行处理,一行封装成一个model,所以需要知道当前xlsx的当前sheet有多少行数据,然后for循环遍历,组合数据。然而就找一共有多少行这个api就一个一个尝试,官方文档并没有说这块内容,网上也没有这个使用教程,饶了一大圈,最终找到了这个api sheet.get_highest_row() , 不管怎么说,兜了一大圈,这个总算是解决了。
然后就是下一个问题,写入api需要传一个book对象,因为第二个xlsx实际上是我要追加数据,不是新建一个excel,凭着感觉走,是先用read的api创建mut对象,修改之后吧read的book放到write中,执行回写,验证了一下,hmmm没猜错,这样基本上这个lib的使用问题得到了解决。

for循环

for循环应该是一门语言中最基本的部分了,依靠代码提示,这个没出太大问题,vec和map的循环,都顺利进行,但是拿到一个sheet有多少行之后,我要遍历的时候发现不会了,然后又是搜索引擎走一圈,找到了这个最简单的语法

1
2
3
for i in 1..sheet.get_highest_row()+1 {
....
}

唉,就如实说,光看书是真的不行,还是得coding才能掌握的说。

集合

本来以为集合是个简单的东西,没想到这上面也废了不少劲。简单总结一下:

  • Vec 的添加元素用push
  • HashSet的添加元素用insert
  • HashMap的添加键值对也用insert
  • HashMap的遍历调用iter然后for循环,得到的是一个元素,key是.0,value是.1
  • 迭代得到的元素是引用,如果要放入其他集合,我选择的是clone一份,直接解引用,经常出现所有权问题,暂时也没想到更好的办法。

字符串 (String和&str)

说真的,字符串这玩意还挺绕的,String和&str,就这两个来来回回的转换,我这也是偷懒了,自己接收也好,使用也罢,基本都用String了,得到了&str基本都转换成String,然后在传递了,最起码没有所有权问题了,偷个懒,后面在慢慢找更好的适配方法。

字符串 (字符串拼接)

我是怎么也没有想到,一个字符串拼接竟然愁住了我。
我从HashSet的iter遍历得到的item,类型是&String,我就想直接后面拼接个字符串,然后判断在另外一个集合中是否存在,然后这个追加愁住了我。

1
2
3
4
let name =element+"流";
报错:
cannot add `&str` to `&std::string::String`
string concatenation requires an owned `String` on the leftrustcClick for full compiler diagnostic
1
2
3
4
 let name = element.add("流");
报错:
cannot move out of `* element ` which is behind a shared reference
move occurs because `* element ` has type `std::string::String`, which does not implement the `Copy` traitrustcClick for full compiler diagnostic
1
2
3
4
5
6
let name =*element.add("流");
报错:
the size for values of type `str` cannot be known at compilation time
the trait `Sized` is not implemented for `str`
all local variables must have a statically known size
unsized locals are gated as an unstable feature

我自己尝试出来正确方法

1
let name = String::from(element).add("流");

再来看另外一个, 对于元组的引用,也是通过clone来解决的。。

1
2
3
4
5
6
7
 let mut map: HashMap<String, Vec<Model>> = HashMap::new();
...
for entry in map.iter() {
let sheet_name = entry.0.clone().add("流");
let sheet = book.get_sheet_by_name_mut(&sheet_name).unwrap();
...
}

全局配置

这个程序因为有个配置文件,运行过程中需要使用,开始想着声明一个全局变量就行了,准备放到dict这个mod里面,存储当然就用HashMap,然后就出现了问题

1
2
3
4
5
6
pub mod dict {
static mut map:HashMap<String,String> = HashMap::new();
}
报错:
cannot call non-const fn `HashMap::<std::string::String, std::string::String>::new` in statics
calls in statics are limited to constant functions, tuple structs and tuple variantsrustcClick for full compiler diagnostic

既然需要HashMap必须是const,我就试一下

1
2
3
const mut map:HashMap<String,String> = HashMap::new();
报错:
const globals cannot be mutable

不可变?那我怎么insert配置文件啊?先试试看非mut的

1
2
3
4
const map:HashMap<String,String> = HashMap::new();
报错:
cannot call non-const fn `HashMap::<std::string::String, std::string::String>::new` in constants
calls in constants are limited to constant functions, tuple structs and tuple variantsrustcClick for full compiler diagnostic

给我整不会了,看来HashMap不能全局使用了,从网上又查了一圈,hmm找到方法了,用lazy_static,具体原理还没看,但是尝试了一下,确实解决了我的问题。最终代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pub mod dict {
use std::{collections::HashMap, fs::File, io::Read, sync::Mutex};

pub fn get_product_name(product_name: String) -> String {
if let Some(value) = CONFIG_MAP.lock().unwrap().get(&product_name) {
return value.to_owned();
}
return product_name;
}
lazy_static! {
static ref CONFIG_MAP: Mutex<HashMap<String, String>> = {
let mut file = File::open("./data/cfg.toml").unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
let obj: HashMap<String, HashMap<String, String>> = toml::from_str(&data).unwrap();
let dict_map = obj.get("product_dict").unwrap();
Mutex::new(dict_map.clone())
};
}
}

数据类型转换

  • f64-> i32 (引申一下,应该数字类型转换都可以这么搞)
    • let a = num as i32
  • String -> i32 (引申一下,字符串到数字类型)
    • let a = String::from("13").parse::<i32>().unwrap();

总结

这个程序算是第一次正式用rust实现一个完整的功能的程序,说简单吧,也用到不少东西,说复杂吧,实际上就是数据格式转换+xlsx基本操作,后面还会继续用rust实现一些小的功能之类的,主要还是方便一下自己吧,这次代码量也就150+行,不过嘞,写了快一天。
编程这东西,还是不能停留在看,要实际coding才能掌握,后面如果要写一些系统底层的东西,相比现在可要复杂太多了,自己给自己加加油打打气吧~

前置条件检查是否支持虚拟化

  • 通过dmesg查看是否支持虚拟化,arm不同于x86,lscpu看不出来
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # dmesg |grep kvm
    [ 0.499391] kvm [1]: Hisi ncsnp: enabled
    [ 0.499605] kvm [1]: 16-bit VMID
    [ 0.499606] kvm [1]: IPA Size Limit: 48bits
    [ 0.499644] kvm [1]: GICv4 support disabled
    [ 0.499645] kvm [1]: vgic-v2@9b020000
    [ 0.499647] kvm [1]: GIC system register CPU interface enabled
    [ 0.500407] kvm [1]: vgic interrupt IRQ1
    [ 0.501131] kvm [1]: VHE mode initialized successfully
  • 如果是下面这样,就不用继续了,浪费时间
    1
    2
    # dmesg |grep kvm
    [ 0.136111] kvm [1]: HYP mode not available
  • 注意:Arm架构不支持嵌套虚拟化,也就是说只能在物理机下运行kata,无法在虚拟机下运行

下载&编译&安装

gcc (yum源7以上无需编译)

  • 编译qemu需要
  • 下载源码
    1
    wget https://github.com/gcc-mirror/gcc/archive/refs/tags/releases/gcc-8.5.0.zip
  • 解压后,检查依赖项
    1
    ./contrib/download_prerequisites
  • yum安装依赖
    1
    # yum -y install bzip2 gcc gcc-c++ gmp-devel mpfr-devel libmpc-devel make zlib-devel flex bison-devel
  • 进入gcc8.5.0目录进行编译安装, make -j 后面的数字是编译的并行数,可适当调整
    1
    2
    3
    # mkdir build && cd build
    # ../configure --prefix=/opt/gcc-8.5.0 --enable-languages=c,c++ --disable-multilib
    # make -j8 && sudo make install
  • 进行软链接,软链前删除/usr/bin/下的cc和c++
    1
    2
    # ln -s  /opt/gcc-8.5.0/bin/gcc /usr/bin/cc
    # ln -s /opt/gcc-8.5.0/bin/c++ /usr/bin/c++

rust

  • 编译kata需要
  • 直接在线安装
    1
    curl --proto '=https' --tlsv1.2 -sSf sh.rustup.rs | sh

golang

  • 编译kata需要
  • 直接下载二进制
    1
    wget https://studygolang.com/dl/golang/go1.19.3.linux-arm64.tar.gz
  • 解压后将bin目录添加至PATH即可

kata

  • 下载源码
    1
    git clone https://github.com/kata-containers/kata-containers.git
  • 在kata-containers目录编译安装
    1
    2
    3
    4
    5
    $ pushd kata-containers/src/runtime
    $ make && sudo -E "PATH=$PATH" make install
    $ sudo mkdir -p /etc/kata-containers/
    $ sudo install -o root -g root -m 0640 /usr/share/defaults/kata-containers/configuration.toml /etc/kata-containers
    $ popd

containerd

  • 直接下载对应的rpm包
    1
    2
    # yum install 	http://mirror.centos.org/altarch/7/extras/aarch64/Packages/container-selinux-2.107-1.el7_6.noarch.rpm
    # yum install https://download.docker.com/linux/centos/7/aarch64/stable/Packages/containerd.io-1.5.11-3.1.el7.aarch64.rpm

python3.7 (yum源3.7以上无需编译)

  • 编译re2c需要

  • 下载源码

    1
    # wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tgz
  • 安装依赖

    1
    # yum install libffi-devel 
  • 解压并编译

    1
    2
    3
    4
    5
    6
    7
    # tar -zxvf Python-3.7.0.tgz
    # mv Python-3.7.0 /usr/local
    # rm -rf /usr/bin/python
    # cd /usr/local/Python-3.7.0/
    # ./configure
    # make
    # make install

re2c

  • 编译ninja需要
  • 下载源码
    1
    git clone https://github.com/skvadrik/re2c.git
  • 安装依赖
    1
    # yum install automake libtool gcc gcc-c++
  • 编译安装,进入re2c目录
    1
    2
    # autoreconf -i -W all
    # ./configure && make && make install

ninja

  • 编译qemu需要
  • 下载源码
    1
    git clone https://github.com/ninja-build/ninja.git
  • 编译,进入ninja目录
    1
    ./configure.py --bootstrap
  • 将ninja拷贝至PATH
    1
    mv ninja /usr/bin/

yq

  • 直接下载二进制就行
    1
    2
    https://github.com/mikefarah/yq/releases
    https://github.com/mikefarah/yq/releases/download/v4.30.4/yq_darwin_arm64

qemu

  • 可以通过tests脚本下载(是网络情况决定)
  • 提前下载源码,后续直接进行编译
    1
    2
    3
    4
    5
    git clone https://gitlab.com/qemu-project/qemu.git
    git clone https://gitlab.com/qemu-project/dtc.git
    git clone https://gitlab.com/qemu-project/meson.git
    https://gitlab.com/qemu-project/keycodemapdb.git

  • 这里采用kata-tests的脚本进行编译安装,具体见[kata-tests]

kata-tests

  • 进入kata-containers目录,下载源码tests

    1
    git clone https://github.com/kata-containers/tests.git
  • 调整目录结构(方便执行install_qemu脚本)

    1
    2
    3
    4
    5
    6
    7
    将kata-containers移动到/opt/kata/src/github.com/kata-containers

    /opt/kata/src/github.com/kata-containers/
    - tests
    - kata-containers (软连接至当前目录ln -s /opt/kata/src/github.com/kata-containers kata-containers)
    - 其他kata-containsers文件

    1
    将上面下载的qemu目录mv到/opt/kata/src/github.com/qemu
    1
    2
    /opt/kata (下面的GOPATH)路径下增加bin目录
    将yq二进制文件拷贝至bin目录下(跳过install_yq)
  • 修改脚本(必须)

    • 修改./kata-containers/tests/.ci/lib.sh, 增加如下
    • export GOPATH=上面调整的目录,我这里是/opt/kata
  • 修改脚本(可选,网速不好参考)

    • ./kata-containers/tests/.ci/install_qemu.sh
      • 方便调试增加set -x
    • ./kata-containers/tests/.ci/lib.sh
    • ./kata-containers/tests/.ci/aarch64/lib_install_qemu_aarch64.sh
      • 如果上面已经移动了qemu目录,执行下面的注释,否则不用执行
      • 注释掉clone_qemu_repo
      • 注释掉 sudo -E git fetch
    • ./kata-containers/tools/packaging/scripts/configure-hypervisor.sh
      • 如果提前clone了目录,增加如下,这样可以在config qemu的时候忽略子模块校验
        1
        qemu_options+=' --with-git-submodules=ignore'
  • 执行安装

    1
    # ./kata-containers/tests/.ci/install_qemu.sh

kata-agent(可选)

kernel (除非你有arm内核,否则还是需要编译)

  • 下载对应版本的内核并解压
  • 进入内核目录执行
    make -j 8
  • 编译完成后拷贝./arch/arm64/boot/Image至对应目录
    • 可软链,默认/usr/share/kata-containers/vmlinux.container
  • 编译模块部分(需要内核开启模块,自己根据版本调整,如果guestImage需要使用则需要编译)
    1
    2
    3
    4
    修改makefile的EXTRAVERSION 适配自己的版本,然后执行编译
    mkdir -p ../build/lib/modules/5.4.160-1.el7.aarch64
    make modules -j64
    make modules_install INSTALL_MOD_PATH=../build

guest Image (同kernel,如果有则无需编译)

  • 进入kata-containers/tools/osbuilder/rootfs-builder/centos, 根据自己rootfs选择

  • copy kernel modules (按需)

    1
    cp -r -d ${kernel}/../build/lib/modules/5.4.160-1.el7.aarch64/ lib/modules/5.4.160-1.el7.aarch64
  • 修改 config.sh(主要将yum或者dnf源修改为适配aarch64的,如果使用官方无需修改)

    1
    BASE_URL="https://mirrors.aliyun.com/centos/8-stream/BaseOS/aarch64/os/"
  • 增加rust加速config, 打到Docker镜像中

    1
    2
    3
    4
    5
    [source.crates-io]
    registry = "https://github.com/rust-lang/crates.io-index"
    replace-with = 'ustc'
    [source.ustc]
    registry = "git://mirrors.ustc.edu.cn/crates.io-index"
  • 修改Dockerfile(我这里rust加速,copy了config)

    1
    2
    增加
    COPY config /root/.cargo/config
  • 回到进入kata-containers/tools/osbuilder,执行编译(如果哪一步有超时,自己修改dns或者修改代理)

    1
    2
    make DISTRO=centos OS_VERSION=stream8 SECCOMP=no DEBUG=true USE_DOCKER=true AGENT_INIT=yes rootfs
    make USE_DOCKER=true image-centos -j 16
  • 拷贝编译好的Image(我这里是kata-containers-image-centos.img)

配置

kata

  • /etc/kata-containers/configuration.toml //过滤空行和注释后,我这里开启了debug模式
    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
    [hypervisor.qemu]
    path = "/usr/bin/qemu-system-aarch64"
    kernel = "/usr/share/kata-containers/vmlinux.container"
    image = "/usr/share/kata-containers/kata-containers.img"
    machine_type = "virt"
    enable_annotations = []
    valid_hypervisor_paths = ["/usr/bin/qemu-system-aarch64"]
    kernel_params = " initcall_debug"
    firmware = ""
    machine_accelerators=""
    cpu_features="pmu=off"
    default_vcpus = 1
    default_maxvcpus = 1
    default_bridges = 1
    default_memory = 2048
    disable_block_device_use = false
    shared_fs = "virtio-9p"
    virtio_fs_daemon = "/usr/libexec/kata-qemu/virtiofsd"
    valid_virtio_fs_daemon_paths = ["/usr/libexec/kata-qemu/virtiofsd"]
    virtio_fs_cache_size = 0
    virtio_fs_extra_args = ["--thread-pool-size=1"]
    virtio_fs_cache = "auto"
    block_device_driver = "virtio-blk"
    enable_iothreads = false
    enable_vhost_user_store = false
    vhost_user_store_path = "/var/run/kata-containers/vhost-user"
    valid_vhost_user_store_paths = ["/var/run/kata-containers/vhost-user"]
    valid_file_mem_backends = [""]
    pflashes = []
    enable_debug = true
    disable_image_nvdimm = true
    valid_entropy_sources = ["/dev/urandom","/dev/random",""]
    [factory]
    [agent.kata]
    enable_debug = true
    enable_tracing = true
    kernel_modules=[]
    debug_console_enabled = true
    [netmon]
    path = "/usr/libexec/kata-containers/kata-netmon"
    enable_debug = true
    [runtime]
    enable_debug = true
    internetworking_model="tcfilter"
    disable_guest_seccomp=true
    disable_selinux=false
    sandbox_cgroup_only=false
    sandbox_bind_mounts=[]
    vfio_mode="guest-kernel"
    experimental=[]
    [image]

containerd

  • /etc/containerd/config.toml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    disabled_plugins = []

    [debug]
    # address = "/run/containerd/debug.sock"
    # uid = 0
    # gid = 0
    level = "debug" # 我这里为了debug观察用

    [plugins]
    [plugins.cri.cni]
    conf_dir = "/etc/cni/net.d"
    [plugins.linux]
    shim_debug = true
    [plugins.cri]
    [plugins.cri.containerd]
    [plugins.cri.containerd.runtimes]
    [plugins.cri.containerd.runtimes.kata]
    runtime_type = "io.containerd.kata.v2"
    privileged_without_host_devices = true
    [plugins.cri.containerd.runtimes.kata.options]
    ConfigPath = "/etc/kata-containers/configuration.toml" # 指定kata配置文件

cni

  • /etc/containerd/config.toml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    "cniVersion": "0.2.0",
    "name": "mynet",
    "type": "bridge",
    "bridge": "cni0",
    "isGateway": true,
    "ipMasq": true,
    "ipam": {
    "type": "host-local",
    "subnet": "172.19.0.0/24",
    "routes": [
    { "dst": "0.0.0.0/0" }
    ]
    }
    }

验证

check

1
2
3
4
# kata-runtime check
INFO[0000] IOMMUPlatform is disabled by default.
System is capable of running Kata Containers
System can currently create Kata Containers

启动容器

1
2
$ sudo ctr image pull docker.io/library/busybox:latest
$ sudo ctr run --cni --runtime io.containerd.run.kata.v2 -t --rm docker.io/library/busybox:latest hello sh

Q&A 编译阶段

python

  1. ModuleNotFoundError: No module named ‘_ctypes’
    1
    2
    yum install libffi-devel 
    然后重新configure make make install

gcc

  1. g++: 错误:gengtype-lex.c:没有那个文件或目录
    1
    yum install flex

qemu

  1. ERROR: glib-2.56 gthread-2.0 is required to compile QEMU
    1
    yum install glib2-devel
  2. ERROR: Dependency “pixman-1” not found, tried pkgconfig
    1
    yum install pixman-devel
  3. ERROR: Dependency “libseccomp” not found, tried pkgconfig
    1
    yum install libseccomp-devel
  4. ERROR: C header ‘cap-ng.h’ not found
    1
    yum install libcap-ng-devel
  5. ERROR: C shared or static library ‘rados’ not found
    1
    yum install libcephfs-devel librbd-devel librados-devel

Q&A 运行阶段

  1. ctr: failed to create shim: failed to launch qemu: exit status 1, error messages from qemu log: qemu-system-aarch64: -device nvdimm,id=nv0,memdev=mem0: memory hotplug is not enabled: missing acpi-ged device : unknown

    1
    2
    修改kata-container的configuration.toml
    disable_image_nvdimm = true
    1
    2
    或者qemu应用下面补丁
    https://patchwork.kernel.org/project/qemu-devel/cover/20181018143042.29588-1-eric.auger@redhat.com/
  2. ctr: failed to create shim: Failed to Check if grpc server is working: rpc error: code = DeadlineExceeded desc = timed out connecting to vsock 2680247850:1024: unknown

    1
    内核文件问题,参考上面内核文件编译
  3. Err:Could not create the sandbox resource controller cgroups: cgroup mountpoint does not exist

    1
    2
    sudo mkdir /sys/fs/cgroup/systemd
    sudo mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd

回顾

不知不觉三年过去了,也是这疫情闹了三年,也是高德呆着的三年。我的工作经历还比较简单,研究生毕业之后,传统企业2年,京东1年,高德3年,算是2年传统企业+4年互联网经验吧。自己上次写Blog还是三年前,那个时候应该还是我再京东那边各种准备面试的时候。在京东那段时间工作说忙吧,也能划水,天天比较多的事情就是开会,当时的领导一言不合拉我们到会议室一开会就是1-2小时,也确实挺磨人的。不过在京东的那一年,也算是初次接触了互联网行业吧,毕竟以前做的还是toB项目,也算是从这里进行了入门,Es也是这一年用的最多的时候了,Java的知识,jvm什么的,也是这个时候逐渐开始尝试在项目中使用吧,然后就是在这里真真正正的体会到数据结构的作用。我们的一个搜索项目,调整数据结构之后搜索的rt从800+ms降低到50ms,真真正正的就是靠数据结构的优化。

离开了京东,来到了高德,也可以说是阿里吧,算是真真正正的在互联网呆了3年,也真的是成长最快的3年了。在这边很幸运,开始遇到了好老大,对我也比较认可,项目什么的,我也一点点逐步自己扛起来。在这边切切实实的感觉到Java那些参数,数据结构的选择,超时,索引等各种设计的重要性,当你的应用只有几十QPS的时候,其实只要能用就行,也不会出什么大问题,但是如果你的应用动不动就是成千上万的请求量,那就是对数据结构对算法的一个真正的考核了,也是到了这里我才明白大学让我们学习数据结构的重要性。

高德3年,在经历了一次晋升失败之后,今年成功晋升到技术专家。但是自己的身体状况也是每况愈下,明显感觉到自己的身体一天不如一天,加上老婆也怀孕了,自己也就选择了离开,为了照顾老婆以及即将出生的孩子,我选择了去国企,主要是为了稳定。经历京东的毕业,阿里的滚动式裁员,虽然都没有轮到自己,但是还是很后怕,互联网就是青春饭,熬上个几年能扯出来就撤出来吧。

工作及规划

当过技术负责人,当过业务组组长,觉得自己还是不喜欢做管理,后来找工作的时候,还是找偏向技术一点的国企,就是为了不放下技术,而且这次也是去了一个全新的领域,基本上全都是从0开始学习,刚开始的时候确实有压力,尤其是最近弄容器适配这块,屡屡没有成效,让自己的信心收到了打击,自己都感觉有点抑郁了,不过还好,后面各种尝试后,问题算是解决了。

技术栈这块想想自己每次换工作也是换一个领域,从做数据ETL到做搜索项目,到做业务项目,到做Iaas层应用,每次换完工作基本都是从0开始,不过也觉得这一路走来收获颇丰,后面还是继续保持学习。

学习

说到学习,回想一下最近3年,好像看过的书,都不到10本吧,还多是什么半小时漫画之类的,想想自己在京东那一年下去,自己看过的书都有几十本了,后面还是继续保持看书的习惯吧,毕竟自己现在工作不像在互联网那会了,多少有点自己的时间了。

以后

稀里糊涂写了一堆,大概就是自己这几年互联网到逃离互联网的感想吧,也没什么思路什么的,就想到什么写什么,后面还是继续更新一些自己学习总结什么的。
现在业务上自己主方向换了,java基本上逐渐用不上了,后面语言主要应该是go+rust吧,自己瞎捣鼓可能还会用python之类的,其他的可能主要研究虚拟化或者容器相关内容了,唉,随着工作业务方向走吧,就先写这么多。

概述

Java中线程的状态(也可以理解为生命周期),主要有以下几种:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED 。
这些状态存在于Thread类中的一个枚举中如下:

1
2
3
4
5
6
7
8
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}

具体状态

NEW

  • JDK原生的注释说的是一个还没有开始(started)的线程, 也就是说当new了一个线程之后,并没有调用start方法的时候,线程的状态就是NEW。

RUNNABLE

  • 这个状态代表线程处于一个可运行的状态,但是不一定会执行,因为要考虑cpu核心数等影响,直到CPU时间分片给到当前线程,才会真正的执行。

BLOCKED

  • 阻塞状态,通常都是在等待获取某个监视器的锁的时候会处于当前状态。
  • 已知:
    • 等待synchronized获取监视器对象锁的时候,线程的状态为:BLOCKED (on object monitor)
    • 线程从WAITING/TIMED_WAITING(object.wait()方法)状态唤醒后,因为需要重新获取synchronized监视器对象,会先进入BLOCKED状态,待获取了监视器对象锁后,变为RUNNABLE状态。

WAITING

  • 等待状态,一般是由于调用了如下方法而进入等待状态:
    • Object#wait()
    • Thread#join()
    • LockSupport#park()
  • 线程进入WAITING状态后,等待其他线程调用对应的通知对象才会唤醒。比如:
    • Object.notify()
    • Object.notifyAll()

TIMED_WAITING

  • 一样是等待状态,但是区别是带有一个超时时间,超过这个时间后,线程会被自动唤醒。以下几个方法会进入该状态:
    • Thread.sleep(long)
    • Object#wait(long)
    • Thread#join(long)
    • LockSupport#parkNanos(long)
    • LockSupport#parkUntil(long)

TERMINATED

  • 线程的终止状态,也就是当前线程已经执行完成。

状态转换

关于释放资源方面

锁的释放

  • 通过synchronized获取监视器对象锁之后,有如下几个方式会释放锁资源:
    • 方法或者代码块正常执行完成
    • 方法或者代码块抛出异常,代码终止执行
    • 调用监视器对象的wait方法,会释放锁资源
  • 补充:
    • 调用Thread.sleep方法不会释放锁资源

CPU资源的释放

- Thread.sleep(),会释放CPU资源,但是不会释放锁。
- Thread.yield(),会尝试放弃CPU资源,但是不会释放锁。(可能放弃后又立即获取到)
- 还有suspend()方法,由于已经过时,不再解释。

其他

obj.notify/notifyAll区别

  • notify会随机唤醒监视obj的一个线程,具体是哪个无法指定,由JVM确定。
  • notifyAll会唤醒所有监视obj的线程,然后重新去竞争,只有一个可以获取到资源。

wait/sleep/yield区别

  • sleep()方法会释放CPU资源但是不会释放锁资源。
  • wait()方法会释放CPU资源和锁资源。
  • yield()方法仅释放CPU执行权,锁仍然占用,线程会被放入就绪队列,会在短时间内再次执行。

虚假唤醒

  • 就是在没有调用obj.notify/notifyAll的前提下,obj.wait被唤醒了。
  • 等待线程即使没有收到正确的信号,也能够执行后续的操作,这就可能影响程序执行的正常逻辑。
  • 为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。
  • 示例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Sign {
    private final Object obj = new Object();
    private boolean flag = false;

    public void doWait() throws InterruptedException {
    synchronized (obj) {
    while (!flag) {
    obj.wait();
    }
    flag = false;
    }
    }
    public void doNotify() {
    synchronized (obj) {
    flag = true;
    obj.notifyAll();
    }
    }
    }

RUNNABLE状态

  • Java中没有线程所谓的RUNNING和READY状态,这两个状态合并到一起,为RUNNABLE状态。
  • 也就是说即使线程处于RUNNABLE状态,也不一定就正在执行,可能正在等待CPU时间分片。

概述

Redis持久化主要有2种方式,一种是基于内存快照的RDB格式,另外一种是基于操作日志的AOF格式,下面简单整理一下相关知识点。

RDB

  • RDB是基于内存快照的方式对Redis数据进行持久化,方式有两种,一种是自动触发,一种是手动触发。
  • 对于生成的RDB文件,Redis默认采用LZF算法进行压缩,然后进行网络传输。

手动触发

  • 方式1:执行save命令
    • 优点:整体执行时间快
    • 缺点:执行过程中,阻塞Redis相关操作
  • 方式2:执行bgsave命令
    • fork一个子进程,RDB持久化过程由子进程负责,整个过程中只有fork阶段是阻塞的。
    • 优点:阻塞Redis相关操作时间较短
    • 缺点:整体执行时间较长

自动触发

  • 配置文件中配置了:m秒内执行的n次修改,则触发bgsave
    1
    save m n
  • 新加入从节点,需要复制主节点全部数据,则触发bgsave生成RDB文件,提供给从节点恢复数据
  • 执行shutdown命令时,如果没有配置AOF,则触发bgsave生成RDB文件,下次启动时进行恢复。

优缺点

  • 优点
    • RDB文件是一个经过压缩的二进制文件,非常适合全量备份,全量复制等场景。
    • Redis加载并恢复RDB文件速度非常快,远超过AOF方式。
  • 缺点
    • 创建RDB文件多少需要导致Redis停顿(无论save还是bgsave),所以不适合实时生成,无法实时备份
    • 不同版本的Redis可能无法互相兼容
    • 如果最后一次备份RDB时候down机,数据可能丢失,数据完整性无法得到保障。

AOF

  • AOF实际上是Append Only File的缩写,该方式会生成一个独立的日志文件,记录每次的写入的命令。恢复的时候则重新执行AOF文件中的每一条命令。

  • 如果需要开启AOF格式,需要修改Redis配置文件,这个配置默认是不开启的:

    1
    appendonly yes
  • 重写触发配置如下:

    1
    2
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 500mb
  • AOF执行分4个阶段:

    • 命令写入(append):将所有命令追加到缓冲区中
    • 文件同步(sync):将缓冲区中的数据同步落盘
    • 文件重写(rewrite):定期对AOF文件进行重写操作,主要是进行压缩(比如插入后又删除,这种数据直接从aof中移除)。
    • 重启加载(load):读取aof文件,并重写加载数据

优缺点

  • 优点
    • 提供多种同步策略:
      • 每秒同步(一般推荐使用这个)
      • 每次修改同步(效率较低,但是数据可靠)
      • 不同步
    • 适合数据完整性要求较高场景
  • 缺点
    • 随着追加数据越来越多,AOF越来越大。
    • 数据恢复过程较慢。
    • 通常同等情况下,AOF文件大于RDB文件大小。

其他

  • 其实目前生产环境中很少有只是使用其中一种模式的,往往都是两种模式开启,作为互补使用。
  • 对于一些特殊场景,比如只是用来做缓存,则可以关闭持久化,以提升性能。

监控信息

QPS&TPS

并发量&CPU使用率

磁盘IO

大促销

问题

  • 超高的QPS和TPS

    • 风险:低下效率的查询
  • 大量的并发和超高的CPU使用率

    • 大量的并发

      • 数据库连接数被占满
    • 超高的CPU使用率

      • 因为CPU资源耗尽出现宕机
  • 磁盘IO

    • 磁盘IO性能突然下降(使用更快的磁盘设备)
    • 其他大量消耗磁盘性能的计划任务
  • 网卡IO

    • 网卡IO被沾满

      • 减少从服务器数量
      • 进行分级缓存
      • 避免使用select * 查询
      • 分离业务网络和服务器网络

影响数据库性能因素

  • SQL查询速度
  • 网卡浏览
  • 服务器应急
  • 磁盘IO

大表带来的问题

超过千万行或者10G

查询影响

  • 慢查询:很难在一定时间内过滤出所需要的数据

DDL影响

  • 建立索引需要很长的时间

    • 5.5版本,引起主从延迟

  • 修改表结构需要长时间锁表

    • 造成长时间的主从延迟
    • 影响正常的数据操作

处理大表

  • 分库分表

    • 分表主键选择
    • 分表跨分区数据查询统计
  • 历史数据归档

    • 归档时间点的选择
    • 如何进行归档操作

大事务带来的问题

什么是事务

  • 原子性

  • 一致性

  • 隔离性

    • 未提交读
    • 已提交读
    • 可重读
    • 串行化
  • 持久性

大事务

  • 运行时间比较长,操作的数据比较多的事务

    • 锁定数据太多,大量阻塞和锁超时
    • 回滚所需时间较长
    • 执行时间长,容易造成主从延迟

如何处理

  • 避免一次处理太多数据
  • 移除不必要在事务中的查询

附Xmind

MySQL大促销实战

基准测试

基准测试:直接简单易于比较,用于评估服务器的处理能力

压力测试:对真实的业务数据进行测试,活得真实系统所能承受的压力

目的

建立MySQL服务器的性能基准线

模拟比当前系统更高的负载,以找出系统的扩展瓶颈

测试不同的软件硬件和操作系统配置

证明新的硬件设备是否配置正确

如何

对整个系统进行基准测试

  • 优点

    • 能够测试整个系统的性能,包括Web服务器缓存。数据库等
    • 能反映出系统中各个组件接口间的性能问题,体现真是性能状况
  • 缺点

    • 测试设计复杂,消耗时间长

单独针对MySQL进行基准测试

  • 优点

    • 测试设计简单,所需耗费时间短
  • 缺点

    • 无法全面了解整个系统的性能基线

常见指标

TPS

  • 单位时间内处理事务数

QPS

  • 单位时间内查询数量

响应时间

  • 平均响应时间
  • 最小响应时间
  • 最大响应时间
  • 各时间所占百分比

并发量

  • 同时处理查询请求的数量

附Xmind

MySQL基准测试