百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

nginx源码剖析—nginx进程模型(nginx进阶)

ztj100 2025-03-26 19:20 8 浏览 0 评论

1.nginx进程模型

nginx采用的是多进程模型,典型的master-worker方式,采用一个master process(监控进程,也叫做主进程)和多个woker process(工作进程)的设计方式,此外,还有1个可选的chache manager和 1 个可选的cache loader进程。

这样设计的好处:

(1)利用多核系统的并发处理能力;

(2)负载均衡;

(3)管理进程会负责监控工作进程的状态,并负责管理其行为。


2. master-worker 启动流程

启动nginx的主进程将充当master进程,而由主进程fork()出来的子进程则充当工作进程。nginx也可以单进程模型执行,在这种进程模型下,主进程就是工作进程,没有监控进程。

Nginx的核心进程模型框图如下:

3.master进程是如何工作的


监控进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不需要处理网络事件,不负责业务的执行,只会通过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。

其工作流程如下图,它首先fork出worker进程,然后进入for循环,在每一帧循环中调用sigsupend挂起等待信号,如果收到信号,就处理信号,改变响应的标志位,然后根据标志位做出相应的操作。


master进程的标志位有如下7个:

   1: sig_atomic_t ngx_reap;
   2: sig_atomic_t ngx_terminate;
   3: sig_atomic_t ngx_quit;
   4: sig_atomic_t ngx_reconfigure;
   5: sig_atomic_t ngx_reopen;
   6: sig_atomic_t ngx_change_binary;
   7: sig_atomic_t ngx_noaccept;


每个标志与信号位的及作用的对应关系如下表:

这样,master进程的运行情况如下图:

【文章福利】:小编整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!正在跳转(需要自取)


下面看一下核心代码:

3.1 master进程住循环函数 ngx_master_process_cycle

   1: void
   2: ngx_master_process_cycle(ngx_cycle_t *cycle)
   3: {
   4:     char              *title;
   5:     u_char            *p;
   6:     size_t             size;
   7:     ngx_int_t          i;
   8:     ngx_uint_t         n, sigio;
   9:     sigset_t           set;
  10:     struct itimerval   itv;
  11:     ngx_uint_t         live;
  12:     ngx_msec_t         delay;
  13:     ngx_listening_t   *ls;
  14:     ngx_core_conf_t   *ccf;
  15:  
  16:     //设置屏蔽信号
  17:     sigemptyset(&set);
  18:     sigaddset(&set, SIGCHLD);
  19:     sigaddset(&set, SIGALRM);
  20:     sigaddset(&set, SIGIO);
  21:     sigaddset(&set, SIGINT);
  22:     sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
  23:     sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
  24:     sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
  25:     sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
  26:     sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
  27:     sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
  28:  
  29:     if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
  30:         ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  31:                       "sigprocmask() failed");
  32:     }
  33:  
  34:     sigemptyset(&set);
  35:  
  36:  
  37:     size = sizeof(master_process);
  38:  
  39:     for (i = 0; i < ngx_argc i 40: size 1 41: 42: 43: title='ngx_pnalloc(cycle-'>pool, size);
  44:  
  45:     p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
  46:     for (i = 0; i < ngx_argc i 47: p 48: p='ngx_cpystrn(p,' u_char ngx_argvi size 49: 50: 51: ngx_setproctitletitle 52: 53: 54: ccf='(ngx_core_conf_t' ngx_get_confcycle->conf_ctx, ngx_core_module);
  55:  
  56:     //其中包含了fork产生子进程的内容
  57:     ngx_start_worker_processes(cycle, ccf->worker_processes,
  58:                                NGX_PROCESS_RESPAWN);
  59:     //Cache管理进程与cache加载进程的主流程
  60:     ngx_start_cache_manager_processes(cycle, 0);
  61:  
  62:     ngx_new_binary = 0;
  63:     delay = 0;
  64:     sigio = 0;
  65:     live = 1;
  66:  
  67:     for ( ;; ) {//循环
  68:         if (delay) {
  69:             if (ngx_sigalrm) {
  70:                 sigio = 0;
  71:                 delay *= 2;
  72:                 ngx_sigalrm = 0;
  73:             }
  74:  
  75:             ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
  76:                            "termination cycle: %d", delay);
  77:  
  78:             itv.it_interval.tv_sec = 0;
  79:             itv.it_interval.tv_usec = 0;
  80:             itv.it_value.tv_sec = delay / 1000;
  81:             itv.it_value.tv_usec = (delay % 1000 ) * 1000;
  82:  
  83:             if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
  84:                 ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  85:                               "setitimer() failed");
  86:             }
  87:         }
  88:  
  89:         ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
  90:  
  91:         sigsuspend(&set);//master进程休眠,等待接受信号被激活
  92:  
  93:         ngx_time_update();
  94:  
  95:         ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
  96:                        "wake up, sigio %i", sigio);
  97:  
  98:         //标志位为1表示需要监控所有子进程
  99:         if (ngx_reap) {
 100:             ngx_reap = 0;
 101:             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
 102:  
 103:             live = ngx_reap_children(cycle);//管理子进程
 104:         }
 105:  
 106:         //当live标志位为0(表示所有子进程已经退出)、ngx_terminate标志位为1或者ngx_quit标志位为1表示要退出master进程
 107:         if (!live && (ngx_terminate || ngx_quit)) {
 108:             ngx_master_process_exit(cycle);//退出master进程
 109:         }
 110:  
 111:         //ngx_terminate标志位为1,强制关闭服务,发送TERM信号到所有子进程
 112:         if (ngx_terminate) {
 113:             if (delay == 0) {
 114:                 delay = 50;
 115:             }
 116:  
 117:             if (sigio) {
 118:                 sigio--;
 119:                 continue;
 120:             }
 121:  
 122:             sigio = ccf->worker_processes + 2 /* cache processes */;
 123:  
 124:             if (delay > 1000) {
 125:                 ngx_signal_worker_processes(cycle, SIGKILL);
 126:             } else {
 127:                 ngx_signal_worker_processes(cycle,
 128:                                        ngx_signal_value(NGX_TERMINATE_SIGNAL));
 129:             }
 130:  
 131:             continue;
 132:         }
 133:  
 134:         //ngx_quit标志位为1,优雅的关闭服务
 135:         if (ngx_quit) {
 136:             ngx_signal_worker_processes(cycle,
 137:                                         ngx_signal_value(NGX_SHUTDOWN_SIGNAL));//向所有子进程发送quit信号
 138:  
 139:             ls = cycle->listening.elts;
 140:             for (n = 0; n < cycle->listening.nelts; n++) {//关闭监听端口
 141:                 if (ngx_close_socket(ls[n].fd) == -1) {
 142:                     ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
 143:                                   ngx_close_socket_n " %V failed",
 144:                                   &ls[n].addr_text);
 145:                 }
 146:             }
 147:             cycle->listening.nelts = 0;
 148:  
 149:             continue;
 150:         }
 151:  
 152:         //ngx_reconfigure标志位为1,重新读取配置文件
 153:         //nginx不会让原来的worker子进程再重新读取配置文件,其策略是重新初始化ngx_cycle_t结构体,用它来读取新的额配置文件
 154:         //再创建新的额worker子进程,销毁旧的worker子进程
 155:         if (ngx_reconfigure) {
 156:             ngx_reconfigure = 0;
 157:  
 158:             //ngx_new_binary标志位为1,平滑升级Nginx
 159:             if (ngx_new_binary) {
 160:                 ngx_start_worker_processes(cycle, ccf->worker_processes,
 161:                                            NGX_PROCESS_RESPAWN);
 162:                 ngx_start_cache_manager_processes(cycle, 0);
 163:                 ngx_noaccepting = 0;
 164:  
 165:                 continue;
 166:             }
 167:  
 168:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
 169:  
 170:             //初始化ngx_cycle_t结构体
 171:             cycle = ngx_init_cycle(cycle);
 172:             if (cycle == NULL) {
 173:                 cycle = (ngx_cycle_t *) ngx_cycle;
 174:                 continue;
 175:             }
 176:  
 177:             ngx_cycle = cycle;
 178:             ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
 179:                                                    ngx_core_module);
 180:             //创建新的worker子进程
 181:             ngx_start_worker_processes(cycle, ccf->worker_processes,
 182:                                        NGX_PROCESS_JUST_RESPAWN);
 183:             ngx_start_cache_manager_processes(cycle, 1);
 184:  
 185:             /* allow new processes to start */
 186:             ngx_msleep(100);
 187:  
 188:             live = 1;
 189:             //向所有子进程发送QUIT信号
 190:             ngx_signal_worker_processes(cycle,
 191:                                         ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
 192:         }
 193:         //ngx_restart标志位在ngx_noaccepting(表示正在停止接受新的连接)为1的时候被设置为1.
 194:         //重启子进程
 195:         if (ngx_restart) {
 196:             ngx_restart = 0;
 197:             ngx_start_worker_processes(cycle, ccf->worker_processes,
 198:                                        NGX_PROCESS_RESPAWN);
 199:             ngx_start_cache_manager_processes(cycle, 0);
 200:             live = 1;
 201:         }
 202:  
 203:         //ngx_reopen标志位为1,重新打开所有文件
 204:         if (ngx_reopen) {
 205:             ngx_reopen = 0;
 206:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
 207:             ngx_reopen_files(cycle, ccf->user);
 208:             ngx_signal_worker_processes(cycle,
 209:                                         ngx_signal_value(NGX_REOPEN_SIGNAL));
 210:         }
 211:  
 212:         //平滑升级Nginx
 213:         if (ngx_change_binary) {
 214:             ngx_change_binary = 0;
 215:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
 216:             ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
 217:         }
 218:  
 219:         //ngx_noaccept为1,表示所有子进程不再处理新的连接
 220:         if (ngx_noaccept) {
 221:             ngx_noaccept = 0;
 222:             ngx_noaccepting = 1;
 223:             ngx_signal_worker_processes(cycle,
 224:                                         ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
 225:         }
 226:     }
 227: }


3.2 master 产生master进程函数
ngx_start_worker_processes

   1: static void
   2: ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
   3: {
   4:     ngx_int_t      i;
   5:     ngx_channel_t  ch;
   6:  
   7:     ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");
   8:  
   9:     ch.command = NGX_CMD_OPEN_CHANNEL;
  10:  
  11:     //循环创建n个worker子进程
  12:     for (i = 0; i < n; i++) {
  13:         //完成fok新进程的具体工作
  14:         ngx_spawn_process(cycle, ngx_worker_process_cycle,
  15:                           (void *) (intptr_t) i, "worker process", type);
  16:  
  17:         //全局数组ngx_processes就是用来存储每个子进程的相关信息,如:pid,channel,进程做具体事情的接口指针等等,这些信息就是用结构体ngx_process_t来描述的。
  18:         ch.pid = ngx_processes[ngx_process_slot].pid;
  19:         ch.slot = ngx_process_slot;
  20:         ch.fd = ngx_processes[ngx_process_slot].channel[0];
  21:  
  22:         /*在ngx_spawn_process创建好一个worker进程返回后,master进程就将worker进程的pid、worker进程在ngx_processes数组中的位置及channel[0]传递给前面已经创建好的worker进程,然后继续循环开始创建下一个worker进程。刚提到一个channel[0],这里简单说明一下:channel就是一个能够存储2个整型元素的数组而已,这个channel数组就是用于socketpair函数创建一个进程间通道之用的。master和worker进程以及worker进程之间都可以通过这样的一个通道进行通信,这个通道就是在ngx_spawn_process函数中fork之前调用socketpair创建的。*/
  23:         ngx_pass_open_channel(cycle, &ch);
  24:     }

25: }

3.3 真正的fork函数 ngx_spawn_process

   1: //参数解释:
   2: //cycle:nginx框架所围绕的核心结构体
   3: //proc:子进程中将要执行的工作循环
   4: //data:参数
   5: //name:子进程名字
   6: ngx_pid_t
   7: ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
   8:     char *name, ngx_int_t respawn)
   9: {
  10:     u_long     on;
  11:     ngx_pid_t  pid;
  12:     ngx_int_t  s;
  13:  
  14:     if (respawn >= 0) {
  15:         s = respawn;
  16:  
  17:     } else {
  18:         for (s = 0; s < ngx_last_process s 19: if ngx_processess.pid='= -1)' 20: break 21: 22: 23: 24: if s='= NGX_MAX_PROCESSES)' 25: ngx_log_errorngx_log_alert cycle->log, 0,
  26:                           "no more than %d processes can be spawned",
  27:                           NGX_MAX_PROCESSES);
  28:             return NGX_INVALID_PID;
  29:         }
  30:     }
  31:  
  32:  
  33:     if (respawn != NGX_PROCESS_DETACHED) {
  34:  
  35:         /* Solaris 9 still has no AF_LOCAL */
  36:         //创建父子进程间通信的套接字对(基于TCP)
  37:         if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
  38:         {
  39:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  40:                           "socketpair() failed while spawning \"%s\"", name);
  41:             return NGX_INVALID_PID;
  42:         }
  43:  
  44:         ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
  45:                        "channel %d:%d",
  46:                        ngx_processes[s].channel[0],
  47:                        ngx_processes[s].channel[1]);
  48:  
  49:         //设置为非阻塞模式
  50:         if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
  51:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  52:                           ngx_nonblocking_n " failed while spawning \"%s\"",
  53:                           name);
  54:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  55:             return NGX_INVALID_PID;
  56:         }
  57:  
  58:         if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
  59:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  60:                           ngx_nonblocking_n " failed while spawning \"%s\"",
  61:                           name);
  62:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  63:             return NGX_INVALID_PID;
  64:         }
  65:  
  66:         on = 1;
  67:         if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
  68:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  69:                           "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
  70:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  71:             return NGX_INVALID_PID;
  72:         }
  73:  
  74:         if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
  75:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  76:                           "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
  77:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  78:             return NGX_INVALID_PID;
  79:         }
  80:  
  81:         if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
  82:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  83:                           "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
  84:                            name);
  85:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  86:             return NGX_INVALID_PID;
  87:         }
  88:  
  89:         if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
  90:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  91:                           "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
  92:                            name);
  93:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  94:             return NGX_INVALID_PID;
  95:         }
  96:  
  97:         ngx_channel = ngx_processes[s].channel[1];
  98:  
  99:     } else {
 100:         ngx_processes[s].channel[0] = -1;
 101:         ngx_processes[s].channel[1] = -1;
 102:     }
 103:  
 104:     ngx_process_slot = s;
 105:  
 106:     //创建子进程
 107:     pid = fork();
 108:  
 109:     switch (pid) {
 110:  
 111:     case -1:
 112:         ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
 113:                       "fork() failed while spawning \"%s\"", name);
 114:         ngx_close_channel(ngx_processes[s].channel, cycle->log);
 115:         return NGX_INVALID_PID;
 116:  
 117:     case 0:
 118:         ngx_pid = ngx_getpid();
 119:         proc(cycle, data);
 120:         break;
 121:  
 122:     default:
 123:         break;
 124:     }
 125:  
 126:     ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);
 127:  
 128:     ngx_processes[s].pid = pid;
 129:     ngx_processes[s].exited = 0;
 130:  
 131:     if (respawn >= 0) {
 132:         return pid;
 133:     }
 134:  
 135:     ngx_processes[s].proc = proc;
 136:     ngx_processes[s].data = data;
 137:     ngx_processes[s].name = name;
 138:     ngx_processes[s].exiting = 0;
 139:  
 140:     switch (respawn) {
 141:  
 142:     case NGX_PROCESS_NORESPAWN:
 143:         ngx_processes[s].respawn = 0;
 144:         ngx_processes[s].just_spawn = 0;
 145:         ngx_processes[s].detached = 0;
 146:         break;
 147:  
 148:     case NGX_PROCESS_JUST_SPAWN:
 149:         ngx_processes[s].respawn = 0;
 150:         ngx_processes[s].just_spawn = 1;
 151:         ngx_processes[s].detached = 0;
 152:         break;
 153:  
 154:     case NGX_PROCESS_RESPAWN:
 155:         ngx_processes[s].respawn = 1;
 156:         ngx_processes[s].just_spawn = 0;
 157:         ngx_processes[s].detached = 0;
 158:         break;
 159:  
 160:     case NGX_PROCESS_JUST_RESPAWN:
 161:         ngx_processes[s].respawn = 1;
 162:         ngx_processes[s].just_spawn = 1;
 163:         ngx_processes[s].detached = 0;
 164:         break;
 165:  
 166:     case NGX_PROCESS_DETACHED:
 167:         ngx_processes[s].respawn = 0;
 168:         ngx_processes[s].just_spawn = 0;
 169:         ngx_processes[s].detached = 1;
 170:         break;
 171:     }
 172:  
 173:     if (s == ngx_last_process) {
 174:         ngx_last_process++;
 175:     }
 176:  
 177:     return pid;
 178: }


4.worker 进程是如何工作的

worker进程的主要任务是完成具体的任务逻辑。其主要关注点是与客户端或后端真实服务器(此时nginx作为中间代理)之间的数据可读/可写等I/O交互事件,所以工作进程的阻塞点是在像select()、epoll_wait()等这样的I/O多路复用函数调用处,以等待发生数据可读/写事件。当然也可能被新收到的进程信号中断。

master进程是信号来控制worker进程的。当收到信号时,信号处理函数ngx_signal_handler()就会执行。worker进程感兴趣的信号有4个,也是通过4个变量来控制,如下表:

其工作流程如下图所示:

其核心代码如下:

   1: static void
   2: ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
   3: {
   4:     ngx_int_t worker = (intptr_t) data;
   5:  
   6:     ngx_uint_t         i;
   7:     ngx_connection_t  *c;
   8:  
   9:     ngx_process = NGX_PROCESS_WORKER;
  10:  
  11:     //子进程初始化
  12:     ngx_worker_process_init(cycle, worker);
  13:  
  14:     ngx_setproctitle("worker process");
  15:  
  16: //这里有一段多线程条件下的代码。由于nginx并不支持多线程,因此删除掉了
  17:  
  18:     //循环
  19:     for ( ;; ) {
  20:         
  21:         //ngx_exiting标志位为1,进程退出
  22:         if (ngx_exiting) {
  23:             c = cycle->connections;
  24:             for (i = 0; i < cycle->connection_n; i++) {
  25:                 if (c[i].fd != -1 && c[i].idle) {
  26:                     c[i].close = 1;
  27:                     c[i].read->handler(c[i].read);
  28:                 }
  29:             }
  30:  
  31:             if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
  32:             {
  33:                 ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
  34:                 ngx_worker_process_exit(cycle);
  35:             }
  36:         }
  37:  
  38:         ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
  39:  
  40:         ngx_process_events_and_timers(cycle);//处理事件的方法
  41:  
  42:         //强制结束进程
  43:         if (ngx_terminate) {
  44:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
  45:             ngx_worker_process_exit(cycle);
  46:         }
  47:  
  48:         //优雅地退出进程
  49:         if (ngx_quit) {
  50:             ngx_quit = 0;
  51:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
  52:                           "gracefully shutting down");
  53:             ngx_setproctitle("worker process is shutting down");
  54:  
  55:             if (!ngx_exiting) {
  56:                 ngx_close_listening_sockets(cycle);
  57:                 //设置ngx_exiting 标志位
  58:                 ngx_exiting = 1;
  59:             }
  60:         }
  61:  
  62:         //重新打开所有文件
  63:         if (ngx_reopen) {
  64:             ngx_reopen = 0;
  65:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
  66:             ngx_reopen_files(cycle, -1);
  67:         }
  68:     }
  69: }

————————————————

版权声明:本文为CSDN博主「chen19870707」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:
https://blog.csdn.net/chen19870707/article/details/41245067

相关推荐

从IDEA开始,迈进GO语言之门(idea got)

前言笔者在学习GO语言编程的时候,GO语言在国内还没有像JAVA/Php/Python那样普及,绕了不少的弯路,要开始入门学习一门编程语言,最好就先从选择一个好的编程语言的开发环境开始,有了这个开发环...

基于SpringBoot+MyBatis的私人影院java网上购票jsp源代码Mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于SpringBoot...

基于springboot的个人服装管理系统java网上商城jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于springboot...

基于springboot的美食网站Java食品销售jsp源代码Mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于springboot...

贸易管理进销存springboot云管货管账分析java jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目描述贸易管理进销存spring...

SpringBoot+VUE员工信息管理系统Java人员管理jsp源代码Mysql

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍SpringBoot+V...

目前见过最牛的一个SpringBoot商城项目(附源码)还有人没用过吗

帮粉丝找了一个基于SpringBoot的天猫商城项目,快速部署运行,所用技术:MySQL,Druid,Log4j2,Maven,Echarts,Bootstrap...免费给大家分享出来前台演示...

SpringBoot+Mysql实现的手机商城附带源码演示导入视频

今天为大家带来的是基于SpringBoot+JPA+Thymeleaf框架的手机商城管理系统,商城系统分为前台和后台、前台用的是Bootstrap框架后台用的是SpringBoot+JPA都是现在主...

全网首发!马士兵内部共享—1658页《Java面试突击核心讲》

又是一年一度的“金九银十”秋招大热门,为助力广大程序员朋友“面试造火箭”,小编今天给大家分享的便是这份马士兵内部的面试神技——1658页《Java面试突击核心讲》!...

SpringBoot数据库操作的应用(springboot与数据库交互)

1.JDBC+HikariDataSource...

SpringBoot 整合 Flink 实时同步 MySQL

1、需求在Flink发布SpringBoot打包的jar包能够实时同步MySQL表,做到原表进行新增、修改、删除的时候目标表都能对应同步。...

SpringBoot + Mybatis + Shiro + mysql + redis智能平台源码分享

后端技术栈基于SpringBoot+Mybatis+Shiro+mysql+redis构建的智慧云智能教育平台基于数据驱动视图的理念封装element-ui,即使没有vue的使...

Springboot+Mysql舞蹈课程在线预约系统源码附带视频运行教程

今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的Springboot+Mysql舞蹈课程在线预约系统,系统项目源代码在【猿来入此】获取!https://www.yuan...

SpringBoot+Mysql在线众筹系统源码+讲解视频+开发文档(参考论文

今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的在线众筹管理系统,主要实现了普通用户在线参与众筹基本操作流程的全部功能,系统分普通用户、超级管理员等角色,除基础脚手架外...

Docker一键部署 SpringBoot 应用的方法,贼快贼好用

这两天发现个Gradle插件,支持一键打包、推送Docker镜像。今天我们来讲讲这个插件,希望对大家有所帮助!GradleDockerPlugin简介...

取消回复欢迎 发表评论: