0%

利用周末时间写了一个简单的后台推送服务,需要一直保持在后台跑。搜了网上的一些方案,比如
1.写一个sh脚本,在运行的命令最后加一个”&”,虽然这个方法可以保持后台运行,但是只要关掉终端,这个服务也会被kill。

2.使用nohup这个命令,nohup的意思是忽略SIGHUP信号。

  • SIGHUP信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 也就是关闭终端的时候发出的,这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。
  • 如果用nohup运行脚本的话,需要用kill命令来终止,比较麻烦。

最后发现用systemctl来管理更加方便,遵循以下步骤就能一劳永逸。

1.写一个service文件,总共分三个部分[Unit]、[Service]和[Install]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[Unit]部分主要是对这个服务的说明,内容包括Description和After,Description用于描述服务,After用于描述服务类别
[Unit]
Description=pusher
#After=network.target remote-fs.target nss-lookup.target

#[Service]部分是服务的关键,是服务的一些具体运行参数的设置
#Type=forking是后台运行的形式,
#PIDFile为存放PID的文件路径,
#ExecStart为服务的具体运行命令,ExecReload为重启命令,ExecStop为停止命令
#Restart指发生异常时是否重启
#PrivateTmp=True表示给服务分配独立的临时空间
#Environment=执行环境设置(绝对路径)
#注意:[Service]部分的启动、重启、停止命令全部要求使用绝对路径,使用相对路径则会报错!
[Service]
Type=forking
ExecStart=/www/wwwroot/servicelog.com/runtime/run_pusher.sh
KillMode=control-group
Restart=no
PrivateTmp=true

#[Install]部分是服务安装的相关设置,可设置为多用户的
[Install]
WantedBy=multi-user.target

2.将写好的service文件放到/usr/lib/systemd/system

3.记好以下几个命令就行了

1
2
3
4
5
6
7
8
9
10
#启动
systemctl start xxx.service
#重启
systemctl restart xxx.service
#停止
systemctl stop xxx.service
#重载
systemctl reload xxx.service
#状态
systemctl status xxx.service

4.还能实现开机自启动

1
2
3
4
#在开机启用服务
systemctl enable xxx.service
#禁止开机服务
systemctl disable xxx.service

参考链接:
1.Systemctl - ArchWiki
2.systemctl 命令详解及使用教程 | linux资讯

11个阶段

  1. POST_READ
    读取头部信息
  2. SERVER_REWRITE
    修改URI的阶段,用于重定向,在server内,location外
  3. FIND_CONFIG
    根据URL找到location块,根据重写后的uri来找对应的location
  4. REWRITE
    在location内进行URI重写
  5. POST_REWRITE
    防止重写URL后导致的死循环,location级别重写的后一阶段,用来检查上阶段是否有uri重写,并根据结果跳转到合适的阶段
  6. PREACCESS
    设置一些处理并发,限制速度,权限控制等
  7. ACCESS
    让HTTP模块判断是否进入NGINX服务器,比如基于ip黑名单饿权限控制
  8. POST_ACCESS
    根据权限控制阶段的执行结果进行相应处理,向用户发送拒绝服务的错误码,用来响应上一阶段的拒绝。
  9. PRECONTENT
    发给第三方服务,try_files
  10. CONTENT
    index,autoindex,反向代理
  11. LOG
    打印日志

学习完数据结构之后,就来看看利用其中列表的特性来实现任务队列。
列表可以存储一个有序的字符串列表,通常的命令分别是:

  • LPUSH(左边添加)
  • LPOP(从左边弹出)
  • RPUSH(右边添加)
  • RPOP(从右边弹出)

其实用上面四个命令就能实现一个任务队列,基本上就是生产者从一端不停的添加元素,而消费者从另一端不停的取出元素即可。

任务队列

用列表做任务队列有几个优点:

  1. 因为列表类型内部是用双向链表实现的,所以不管从哪一端添加元素都是非常快的。
  2. 当然生产者只管往列表里面添加任务,还可以由多个消费者来执行任务,这样就可以降低一个服务器的负载。
  3. 生产者和消费者之间不会产生耦合关系,只需要约定好内容格式即可,这样即使使用不同语言,一样能够实现

伪代码:

1
2
3
4
5
6
7
# 写一个无限循环函
while True:
task = RPOP queue
if task == True:
execute(task)
else:
time.sleep(1)

当然RPOP有一个问题就是需要隔一秒去取一次是否有任务,主动发起可能会浪费资源,Redis还提供了另外一个命令BRPOP(当然还有BLPOP),二者唯一的区别就是,当队列中没有任务时,会一直阻塞连接,直到有新的任务进来了,这样伪代码可以这么写:

1
2
3
4
5
while True:
# BRPOP 接受两个参数,第一个是键名,第二个是超时时间,为0则不超时
task = BRPOP queue, 0
# BRPOP 会返回一个数组,第二个就是我们的任务
execute(task[1])

优先级队列

BRPOP命令可以接受多个键值

1
BRPOP key1 [key2 ...] timeout

逻辑如下:

  • 如果所有键都没有元素则阻塞
  • 如果其中一个键有元素就会弹出
  • 如果多个键都有元素,则会按照从左到右的顺序依次取出

所以我们可以利用这一特性来实现优先级队列,将优先级高的键值放在左侧

1
2
3
4
while True:
# class1 为最高优先级,放最左侧
task = BRPOP queue:class1, queue:class2, queue:class3,0
execute(task[1])