初识systemd-使用篇

        Linux操作系统的开机过程是这样的,即从BIOS开始,然后进入Boot Loader,再加载系统内核,然后内核进行初始化,最后启动初始化进程。初始化进程作为Linux系统的第一个进程,它需要完成Linux系统中相关的初始化工作,为用户提供合适的工作环境。RHEL 7、CentOS7等linux发行版系统已经替换掉了熟悉的初始化进程服务System V init,正式采用全新的systemd初始化进程服务。systemd初始化进程服务采用了并发启动机制,开机速度得到了不小的提升。

一、systemd概述

        systemd即为system daemon,是linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许可证下开源发布,开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低 Shell的系统开销的效果,最终代替现在常用的System V与BSD风格init程序。
        systemd是一个专用于 Linux 操作系统的系统与服务管理器。当作为启动进程(PID=1)运行时,它将作为初始化系统运行,也就是启动并维护各种用户空间的服务。
        为了与传统的 SysV 兼容,如果将 systemd 以 init 名称启动,并且"PID≠1",那么它将执行 telinit 命令并将所有命令行参数原封不动的传递过去。 这样对于普通的登录会话来说,无论是调用 init 还是调用 telinit 都是等价的。
当作为系统实例运行时,systemd将会按照system.conf配置文件以及system.conf.d配置目录中的指令工作;当作为用户实例运行时,systemd 将会按照user.conf配置文件 以及 user.conf.d配置目录中的指令工作。
        systemd 将各种系统启动和运行相关的对象,表示为各种不同类型的单元(unit),并提供了处理不同单元之间依赖关系的能力。大部分单元都静态的定义在单元文件中,但是有少部分单元则是动态自动生成的:其中一部分来自于其他传统的配置文件(为了兼容性),而另一部分则动态的来自于系统状态或可编程的运行时状态。 单元既可以处于活动(active)状态,也可以处于停止(inactive)状态,当然也可以处于启动中(activating)或停止中 (deactivating)的状态。还有一个特殊的失败(failed)状态,意思是单元以某种方式失败了(进程崩溃了、或者触碰启动频率限制、或者退 出时返回了错误代码、或者遇到了操作超时之类的故障)。当进入失败(failed)状态时,导致故障的原因将被记录到日志中以方便日后排查。需要注意的是,不同的单元可能还会有各自不同的"子状态",但它们都被映射到上述五种状态之一。
        systemd的优点是功能强大、使用方便,缺点是体系庞大、非常复杂。事实上,现在还有很多人反对使用systemd,理由就是它过于复杂,与操作系统的其他部分强耦合,违反"keep simple, keep stupid"的Unix哲学。

二、systemd与System V init的区别以及作用

        与以前的发行版使用的System V风格init相比,systemd采用了以下新技术:(1) 采用Socket激活式与总线激活式服务,以提高相互依赖的各服务的并行运行性能;(2)用Cgroups代替PID来追踪进程,以此即使是两次fork 之后生成的守护进程也不会脱离systemd的控制。
        无论怎样,RHEL 7系统选择systemd初始化进程服务已经是一个既定事实,因此也没有了“运行级别”这个概念,Linux系统在启动时要进行大量的初始化工作,比如挂载文件系统和交换分区启动各类进程服务,这些都可以看作是一个一个的单元(Unit),systemd用目标(target)代替了System V init中运行级别的概念,这两者的区别如下所示。
表1:System V init和systemd的区别

System V init运行级别systemd目标名称作用
0runlevel0.target -> poweroff.target关机
1runlevel1.target -> rescue.target单用户模式
2runlevel2.target -> multi-user.target等同于级别3
3runlevel3.target -> multi-user.target多用户的文本界面
4runlevel4.target -> multi-user.target等同于级别3
5runlevel5.target -> graphical.target多用户的图形界面
6runlevel6.target -> reboot.target重启
emergencyemergency.target紧急shell

每个Unit都有一个配置文件,告诉Systemd怎么启动这个Unit。Systemd默认从目录/etc/systemd/system/读取 配置文件,但是里面存放的大部分文件都是符号链接,指向目录/usr/lib/systemd/system/或者/lib/systemd /system/,Unit的配置文件都存放在这个目录里面。
例如:将系统默认的运行模式修改为“多用户,无图形”,可直接用ln命令把多用户模式目标文件链接到/etc/systemd/system/目录
ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target
初识systemd-使用篇
在RHEL6及之前的发行版系统是使用service、chkconfig等命令来管理系统服务,在RHEL7系统中是使用systemctl命令来管理服务的。我们来看一下它们之间的区别。
表2:服务的启动、重启、停止等常用命令对比

System V init命令systemctl命令作用
service foo startsystemctl start foo.service启动服务
service foo restartsystemctl restart foo.service重启服务
service foo stopsystemctl stop foo.service停止服务
service foo reloadsystemctl reload foo.service重新加载配置文件(不停止服务)
service foo statussystemctl status foo.service查看服务状态

表3:设置服务开机启动、不启动等常用命令对比

System V init命令systemctl命令作用
chkconfig foo onsystemctl enable foo.service开机自动启动
chkconfig foo offsystemctl disable foo.service开机不自动启动
chkconfig foosystemctl is-enabled foo.service查看服务是否为自动启动
chkconfig --listsystemctl list-unit-files查看各个服务的启动与禁用情况

三、系统管理相关命令

(1)systemctl是systemd的主命令,用于管理系统和服务。
重启系统
systemctl reboot
关闭系统,切断电源
systemctl poweroff
CPU停止工作
systemctl halt
暂停系统
systemctl suspend
让系统进入冬眠状态
systemctl hibernate
让系统进入交互式休眠状态
systemctl hybrid-sleep
启动进入救援状态(单用户状态)
systemctl rescue
(2)systemd-analyze命令用于查看启动耗时。
查看系统启动耗时
systemd-analyze
查看每个服务的启动耗时
systemd-analyze blame
显示瀑布状的系统启动过程流
systemd-analyze critical-chain
显示指定服务的启动流
systemd-analyze critical-chain atd.service
(3)hostnamectl命令用于查看当前主机的信息。
显示当前主机的信息
hostnamectl
设置主机名。
hostnamectl set-hostname xuad1
(4)localectl命令用于查看本地化设置。
查看本地化设置
localectl
设置本地化参数。

localectl set-locale LANG=zh_CN.UTF-8
localectl set-keymap zh_CN

(5)timedatectl命令用于查看当前系统时区设置。
查看当前时区设置
timedatectl
显示所有可用的时区
timedatectl list-timezones
设置当前时区

timedatectl set-timezone Asia/Shanghai
timedatectl set-time YYYY-MM-DD
timedatectl set-time HH:MM:SS

(6)loginctl命令用于查看当前登陆的用户。
列出当前session
loginctl list-sessions
列出当前登录用户
loginctl list-users
列出显示指定用户的信息
loginctl show-user root

四、Unit相关命令

Systemd可以管理所有系统资源。不同的资源统称为Unit(单位),Unit一共分成12种。

Service unit:系统服务
Target unit:多个Unit构成的一个组
Device Unit:硬件设备
Mount Unit:文件系统的挂载点
Automount Unit:自动挂载点
Path Unit:文件或路径
Scope Unit:不是由Systemd启动的外部进程
Slice Unit:进程组
Snapshot Unit:Systemd快照,可以切回某个快照
Socket Unit:进程间通信的socket
Swap Unit:swap文件
Timer Unit:定时器

systemctl list-units命令可以查看系统的所有Unit信息。
列出已启动的Unit
systemctl list-units
列出所有Unit,包括没有找到配置文件的或者启动失败的
systemctl list-units --all
列出所有没有启动的Unit
systemctl list-units --all --state=inactive
列出所有启动失败的Unit
systemctl list-units --failed
列出所有正在运行的、类型为service的Unit
systemctl list-units --type=service
systemctl status命令用于查看系统状态和单个Unit的状态。
显示系统状态
systemctl status
显示单个 Unit 的状态
sysystemctl status httpd.service
显示远程主机的nginx服务的状态
systemctl -H root@192.168.2.204 status nginx.service
除了status外,systemctl还提供了三个查询状态的简单方法,主要供脚本内部的判断语句使用。
显示某个 Unit 是否启动
systemctl is-active application.service
显示某个 Unit 是否启动失败
systemctl is-failed application.service
显示某个 Unit 服务是否开机启动
systemctl is-enabled application.service
systemctl常用命令
立即启动一个服务
systemctl start httpd.service
立即停止一个服务
systemctl stop httpd.service
重启一个服务
systemctl restart httpd.service
杀死一个服务的所有子进程
systemctl kill httpd.service
重新加载一个服务的配置文件
systemctl reload httpd.service
重载所有修改过的配置文件
systemctl daemon-reload
显示某个 Unit 的所有底层参数
systemctl show httpd.service
显示某个 Unit 的指定属性的值
systemctl show -p CPUShares httpd.service
设置某个 Unit 的指定属性
systemctl set-property httpd.service CPUShares=500
Unit的后缀名.service可以省略,例如启动ssh的命令systemctl start sshd.service可以写成systemctl start sshd。
Unit之间存在依赖关系,A依赖于B,就意味着systemd在启动A的时候,同时会去启动B。
systemctl list-dependencies命令列出一个Unit的所有依赖。
systemctl list-dependencies nginx.service
上面命令的输出结果之中,有些依赖是Target类型,默认不会展开显示。如果要展开Target,就需要使用--all参数。
systemctl list-dependencies --all nginx.service
systemctl list-unit-files命令用于列出所有Unit的状态。
列出所有Unit状态
systemctl list-unit-files
列出指定类型的Unit状态
systemctl list-unit-files --type=service
Unit的四种状态

enabled:开机启动
disabled:不开机启动
static:该Unit没有[Install]部分(无法执行),只能作为其他Unit的依赖
masked:该Unit被禁止开机启动

如果修改了某个服务的配置文件,就要重新加载配置,然后重新启动,否则修改不会生效。

systemctl daemon-reload
systemctl restart httpd.service

系统运行模式target相关命令
查看当前系统的所有Target
systemctl list-unit-files --type=target
查看一个 Target 包含的所有Unit
systemctl list-dependencies multi-user.target
查看启动时的默认Target
systemctl get-default
设置启动时的默认Target
systemctl set-default multi-user.target
切换Target时,默认不关闭前一个Target启动的进程,systemctl isolate命令改变这种行为,关闭前一个Target里面所有不属于后一个Target的进程
systemctl isolate multi-user.target

五、日志管理命令

systemd统一管理所有Unit的启动日志,可以只用journalctl一个命令查看所有日志(内核日志和应用日志),它的配置文件是/etc/systemd/journald.conf。
查看所有日志(默认情况下 ,只保存本次启动的日志)
journalctl
查看内核日志(不显示应用日志)
journalctl -k
查看系统本次启动的日志

journalctl -b
journalctl -b -0

查看上一次启动的日志(需更改设置)
journalctl -b -1
查看指定时间的日志

journalctl --since="2012-10-30 18:17:16"
journalctl --since "20 min ago"
journalctl --since yesterday
journalctl --since "2015-01-10" --until "2015-01-11 03:00"
journalctl --since 09:00 --until "1 hour ago"

显示尾部的最新10行日志
journalctl -n
显示尾部指定行数的日志
journalctl -n 20
实时滚动显示最新日志
journalctl -f
查看指定服务的日志
journalctl /usr/lib/systemd/systemd
查看指定进程的日志
journalctl _PID=1
查看某个路径的脚本的日志
journalctl /usr/bin/bash
查看指定用户的日志
journalctl _UID=33 --since today
查看某个 Unit 的日志

journalctl -u nginx.service
journalctl -u nginx.service --since today

实时滚动显示某个 Unit 的最新日志
journalctl -u nginx.service -f
合并显示多个 Unit 的日志
journalctl -u nginx.service -u php-fpm.service --since today
查看指定优先级(及其以上级别)的日志,共有8级

0: emerg
1: alert
2: crit
3: err
4: warning
5: notice
6: info
7: debug

journalctl -p err -b
日志默认分页输出,--no-pager 改为正常的标准输出
journalctl --no-pager
以 JSON 格式(单行)输出
journalctl -b -u nginx.service -o json
以 JSON 格式(多行)输出,可读性更好
journalctl -b -u nginx.service -o json-pretty
显示日志占据的硬盘空间
journalctl --disk-usage
指定日志文件占据的最大空间
journalctl --vacuum-size=1G
指定日志文件保存多久
journalctl --vacuum-time=1years

六、Unit配置文件

前面已经讲过,配置一个服务开机启动的命令是systemctl enable <服务名>.service,实际上只是在/etc/systemd/system/目录下做了个文件链接而已。

systemctl enable httpd.service
等同于
ln -s '/usr/lib/systemd/system/httpd.service' '/etc/systemd/system/httpd.service'

Unit的配置文件的内容,一般会有以下三个区块,具体含义如下
[Unit]区块通常是配置文件的第一个区块,用来定义Unit的元数据,以及配置与其他Unit的关系,它的主要字段如下:

Description:简单描述
Documentation:服务的启动文件和配置文件
Requires:当前Unit依赖的其他Unit,如果它们没有运行,当前Unit会启动失败
Wants:与当前Unit配合的其他Unit,如果它们没有运行,不影响当前Unit的启动
BindsTo:与Requires类似,它指定的Unit如果退出,会导致当前Unit停止运行
Before:如果该字段指定的Unit也要启动,那么必须在当前Unit之后启动
After:如果该字段指定的Unit也要启动,那么必须在当前Unit之前启动
Conflicts:这里指定的Unit不能与当前Unit同时运行
Condition...:当前Unit运行必须满足的条件,否则不会运行
Assert...:当前Unit运行必须满足的条件,否则会报启动失败

[Service]区块配置,只有Service类型的Unit才有这个区块,它的主要字段如下:

Type:定义启动时的进程行为,它有以下几种值。
Type=simple:默认值,执行ExecStart指定的命令,启动主进程
Type=forking:以fork方式从父进程创建子进程,之后父进程会退出,子进程成为主进程
Type=oneshot:一次性进程,Systemd会等当前服务退出,再继续往下执行
Type=dbus:当前服务通过D-Bus启动
Type=notify:当前服务启动完毕,会通知Systemd,再继续往下执行
Type=idle:若有其他任务,则其他任务执行完毕,当前服务才会运行
ExecStart:启动当前服务的命令
ExecStartPre:启动当前服务之前执行的命令
ExecStartPost:启动当前服务之后执行的命令
ExecReload:重启当前服务时执行的命令
ExecStop:停止当前服务时执行的命令
ExecStopPost:停止当其服务之后执行的命令
RestartSec:自动重启当前服务间隔的秒数
Restart:定义何种情况Systemd会自动重启当前服务,可能的值包括always(总是重启)、on-success、on-failure、on-abnormal、on-abort、on-watchdog
TimeoutSec:定义Systemd停止当前服务之前等待的秒数
Environment:指定环境变量

[Install]通常是配置文件的最后一个区块,用来定义运行模式(Target)、Unit别名等设置,以及是否开机启动,它的主要字段如下:

WantedBy:它的值是一个或多个Target,当前Unit激活时(enable)时,符号链接会放入/etc/systemd/system目录下面以Target名+.wants后缀构成的子目录中
RequiredBy:它的值是一个或多个Target,当前Unit激活时,符号链接会放入/etc/systemd/system目录下面以Target名+.required后缀构成的子目录中
Alias:当前Unit可用于启动的别名
Also:当前Unit激活(enable)时,会被同时激活的其他Unit

Unit配置文件的完整字段清单,请参考官方文档。
systemctl cat命令可以用来查看配置文件,那么我们来看一下sshd配置文件的内容,分析下它每项配置的含义是什么。
systemctl cat sshd.service

[Unit]
Description=OpenSSH server daemon   # 当前服务的简单描述
Documentation=man:sshd(8) man:sshd_config(5)  # sshd是启动脚本,sshd_config是配置文件
After=network.target sshd-keygen.service  # 启动ssh服务之前会先启动这两个Unit
Wants=sshd-keygen.service  # 此Unit启动成功与否不影响ssh服务的正常启动

[Service]
Type=notify  # ssh服务启动成功后会通知systemd,再启动其他依赖服务
EnvironmentFile=/etc/sysconfig/sshd  # 指定ssh服务的环境参数配置文件
ExecStart=/usr/sbin/sshd -D $OPTIONS  # 启动ssh服务执行的命令
ExecReload=/bin/kill -HUP $MAINPID  # 重启ssh服务执行的命令
KillMode=process  # process表示只停止主进程,不停止子进程
Restart=on-failure  # 进程非正常退出时,包括信号终止和超时,会重启服务
RestartSec=42s  # 上面Restart重启之前需要等待42秒再重启

[Install]
WantedBy=multi-user.target   # ssh服务所在的系统运行模式

[Unit]区块:启动顺序与依赖关系
After字段定义哪些服务在ssh服务启动之前启动,Before字段定义哪些服务在ssh服务启动之后启动,After和Before字段只涉及启动顺序,不涉及依赖关系。
举 例来说,某web应用需要postgresql数据库储存数据,在配置文件中只定义了postgresql在该web应用之前启动,而没有定义依赖关系, 由于某种原因postgresql需要重新启动,在postgresql停止期间,该web应用就会无法建立数据库连接。
设置依赖关系,需要使用Wants字段和Requires字段。
Wants字段表示sshd.service与sshd-keygen.service之间属于“弱依赖”关系,即sshd-keygen.service启动失败或者停止运行,不影响sshd.service的运行。
Requires字段则表示“强依赖”关系,即如果该服务启动失败或者停止运行,那么sshd.service也不会启动。
注意:Wants字段与Requires字段只涉及依赖关系,与启动顺序无关,默认情况下是同时启动的。
[Service]区块:启动、停止、重启行为
EnvironmentFile字段:指定ssh服务的环境参数配置文件,该文件内部的key=value键值对,可以用$key的形式,在当前配置文件中获取。
ExecStart字段定义了启动ssh服务的命令是/usr/sbin/sshd -D $OPTIONS,其中的变量$OPTIONS就来自EnvironmentFile字段定义的环境参数配置文件。
所有的启动设置前面都可以加上一个符号(-),表示“抑制错误”,即发生错误也不影响其他命令的执行。比如,EnvironmentFile=-/etc/sysconfig/sshd表示即使/etc/sysconfig/sshd文件不存在,也不会抛出错误。
Type字段定义启动类型,Type=notify表示ssh服务启动后会向Systemd发出通知信号,然后Systemd再启动其他服务。
举个例子,笔记本电脑启动时,要把触摸板关掉,配置文件可以这样写。

[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off

[Install]
WantedBy=multi-user.target

Type设置为oneshot时,就表示这个服务只要运行一次就够了,不需要长期运行。
如果关闭后,将来某个时候还想打开,配置文件修改如下。

[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off start
ExecStop=/usr/bin/touchpad-off stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

RemainAfterExit字段设置为yes,表示进程退出后,服务仍然保持执行。这样的话,一旦使用systemctl stop命令停止服务,ExecStop定义的命令就是执行,从而重新开启触摸板。
KillMode字段设为process表示只停止主进程,不停止任何子进程,即子进程打开的ssh session仍然保持连接。这个设置不太常见,但对ssh服务很重要,否则你停止服务的时候,会连自己打开的ssh session一起杀掉。
KillMode字段可以设置的值如下:

control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
process:只杀主进程
mixed:主进程将收到SIGTERM信号,子进程收到SIGKILL信号
none:没有进程会被杀掉,只是执行服务的stop命令。

Restart设为on-failure表示任何意外的失败,都将重启ssh服务。如果ssh服务正常停止(比如执行systemctl stop sshd.service命令),ssh服务就不会重启。
Restart字段可以设置的值如下:

no(默认值):退出后不会重启
on-success:只有正常退出时(退出状态码为0),才会重启
on-failure:非正常退出时(退出状态码非0),包括被信号终止和超时,才会重启
on-abnormal:只有被信号终止和超时,才会重启
on-abort:只有在收到没有捕捉到的信号终止时,才会重启
on-watchdog:超时退出,才会重启
always:不管是什么退出原因,总是重启

对于需要守护的进程,推荐设为on-failure;对于那些允许发生错误退出的服务,可以设为on-abnormal。
RestartSec表示Systemd重启服务之前,需要等待的秒数。
[Install]区块:定义服务启动的系统运行模式,即该服务在哪个运行模式下开机启动。
WantedBy定义服务所在的Target,WantedBy=multi-user.target指的是ssh服务所在的Target是multi-user.target(多用户命令行模式)。
当我们执行systemctl enable sshd.service命令时,sshd.service的一个符号链接,就会放在/etc/systemd/system目录下面的multi-user.target.wants子目录之中。
Systemd有默认启动的Target,multi-user.target(多用户命令行模式)。
初识systemd-使用篇
上图的结果表示,在multi-user.target.wants目录下面的所有服务,都将开机启动,这就是为什么systemctl enable命令能设置开机启动的原因。
查看 multi-user.target 包含的所有服务
systemctl list-dependencies multi-user.target
切换到另一个 target
shutdown.target 就是关机状态
systemctl isolate shutdown.target
前 面已经讲过systemd的系统运行目标有哪些,我们经常用到的有两个:一个是multi-user.target,表示多用户命令行状态;另一个是 graphical.target,表示图形用户状态,它依赖于multi-user.target。官方文档有一张非常清晰的Target依赖关系图。

七、Target的配置文件

Target的配置文件存放在/lib/systemd/system/目录下,我们来看一下multi-user.target的内容。
systemctl cat multi-user.target
初识systemd-使用篇
Requires=basic.target:要求basic.target一起运行。
Conflicts字段:冲突字段,如果rescue.service或rescue.target正在运行,multi-user.target就不能运行,反之亦然。
After:表示multi-user.target在basic.target、rescue.service、rescue.target之后启动,如果它们有启动的话。
AllowIsolate:允许使用systemctl isolate命令切换到multi-user.target。
注意:修改配置文件以后,需要重新加载配置文件,然后重新启动相关服务。
重新加载配置文件
systemctl daemon-reload
重启相关服务
systemctl restart foo

八、编写服务配置文件实例

我们首先编写一个xuad.service配置文件,功能为启动、重启、停止nginx服务,放在/usr/lib/systemd/system/目录下,内容如下:
vim /usr/lib/systemd/system/xuad.service

[Unit]
Description=xuad server

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
EnvironmentFile=/etc/sysconfig/xuad
ExecStart=/usr/sbin/nginx
ExecStartPost=/bin/echo $OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
ExecStopPost=/bin/echo stop

[Install]
WantedBy=multi-user.target

接下来再编写一个xuad文件,放在/etc/sysconfig/目录下,内容如下:
OPTIONS=Start
这样我们相当于定义了一个xuad服务。
首先确认nginx是否启动。
systemctl status nginx.service
初识systemd-使用篇
通过上图可以看到运行状态为inactive,很明显nginx未运行。
接下来我们来启动xuad.service这个服务,前面有讲过.service可以省略。

systemctl start xuad
systemctl status xuad  # 查看xuad服务状态

初识systemd-使用篇
很明显xuad服务已经成功启动,并且成功打印Start字符串,接下来我们确认一下nginx是否已经启动。
ps aux | grep nginx
初识systemd-使用篇
通过上图可以确认nginx已经成功启动。
接下来我们重启xuad.service这个服务,看看结果如何。

systemctl restart xuad
systemctl status xuad  # status只能看到启动成功后的状态
journalctl -u xuad.service -f  # 通过日志来看重启结果

初识systemd-使用篇
通过上图我们看到成功打印stop和Start两个字符串,说明重启nginx成功。
最后我们停止xuad.service,看看结果如何。

systemctl stop xuad
systemctl status xuad

初识systemd-使用篇
运行状态已经变成inactive(未运行),并且成功打印stop字符串,说明停止nginx成功。这时执行ps aux | grep nginx应该看不到nginx的进程了。
本文所有命令参考文献:
http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
在此感谢这位作者,谢谢!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值