云原生之深入解析如何限制Kubernetes集群中文件描述符与线程数量
一、背景
- linux 中为了防止进程恶意使用资源,系统使用 ulimit 来限制进程的资源使用情况(包括文件描述符,线程数,内存大小等)。同样地在容器化场景中,需要限制其系统资源的使用量。
- ulimit: docker 默认支持 ulimit 设置,可以在 dockerd 中配置 default-ulimits 可为宿主机所有容器配置默认的 ulimit,docker 启动时可添加 –ulimit 为每个容器配置 ulimit 会覆盖默认的设置,目前 k8s 暂不支持 ulimit。
- cgroup: docker 默认支持 cgroup 中内存、cpu、pid 等的限制,对于线程限制可通过 –pids-limit 可限制每个容器的 pid 总数,dockerd 暂无默认的 pid limit 设置;k8s 限制线程数,可通过在 kubelet 中开启 SupportPodPidsLimit 特性,设置 pod 级别 pid limit。
- /etc/securiy/limits.conf,systcl.confg:通过 ulimit 命令设置只对当前登录用户有效,永久设置可通过 limits.conf 配置文件实现,以及系统级别限制可通过 systcl.confg 配置文件。
二、实验对比
① 环境
- 本地环境:os: Ubuntu 16.04.6 LTS 4.4.0-154-generic docker: 18.09.7 base-image: alpine:v3.9
- k8s 环境:kubelet: v1.10.11.1 docker: 18.09.6
② ulimit
- 用户级别资源限制,分为 soft 限制与 hard 限制;
-
- soft :用户可修改,但不能超过硬限制;
-
- hard:只有 root 用户可修改;
- 修改方式:ulimit 命令,临时修改;/etc/security/limits.conf,永久修改;
- 工作原理:根据 PAM ( Pluggable Authentication Modules 简称 PAM)机制,应用程序启动时,按 /etc/pam.d 配置加载 pam_xxxx.so 模块。/etc/pam.d 下包含了 login 、sshd 、su 、sudo 等程序的 PAM 配置文件, 因此用户重新登录时,将调用 pam_limits.so 加载 limits.conf 配置文件。
三、文件描述符限制
RLIMIT_NOFILEThis specifies a value one greater than the maximum filedescriptor number that can be opened by this process.Attempts (open(2), pipe(2), dup(2), etc.) to exceed thislimit yield the error EMFILE. (Historically, this limit wasnamed RLIMIT_OFILE on BSD.)Since Linux 4.5, this limit also defines the maximum number offile descriptors that an unprivileged process (one without theCAP_SYS_RESOURCE capability) may have "in flight" to otherprocesses, by being passed across UNIX domain sockets. Thislimit applies to the sendmsg(2) system call. For furtherdetails, see unix(7).
- 根据定义,nofile 限制进程所能最多打开的文件数量,作用范围进程:
-
- 设置 ulimit nofile 限制 soft 100/hard 200,默认启动为 root 用户;
$ docker run -d --ulimit nofile=100:200 cr.d.xiaomi.net/containercloud/alpine:webtool top
-
- 进入容器查看, fd soft 限制为 100 个:
/ # ulimit -a
-f: file size (blocks) unlimited
-t: cpu time (seconds) unlimited
-d: data seg size (kb) unlimited
-s: stack size (kb) 8192
-c: core file size (blocks) unlimited
-m: resident set size (kb) unlimited
-l: locked memory (kb) 64
-p: processes unlimited
-n: file descriptors 100
-v: address space (kb) unlimited
-w: locks unlimited
-e: scheduling priority 0
-r: real-time priority 0
-
- 使用 ab 测试,并发 90 个 http 请求,创建 90 个 socket,正常运行:
/ # ab -n 1000000 -c 90 http://61.135.169.125:80/ &
/ # lsof | wc -l
108
/ # lsof | grep -c ab
94
-
- 并发 100 个 http 请求,受到 ulimit 限制:
/ # ab -n 1000000 -c 100 http://61.135.169.125:80/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking 61.135.169.125 (be patient)
socket: No file descriptors available (24)
四、线程限制
RLIMIT_NPROCThis is a limit on the number of extant process (or, more pre‐cisely on Linux, threads) for the real user ID of the callingprocess. So long as the current number of processes belongingto this process's real user ID is greater than or equal tothis limit, fork(2) fails with the error EAGAIN.The RLIMIT_NPROC limit is not enforced for processes that haveeither the CAP_SYS_ADMIN or the CAP_SYS_RESOURCE capability.
- 由定义可知,nproc 进程限制的范围是对于每个 uid,并且对于 root 用户无效。
五、容器 uid
- 同一主机上运行的所有容器共享同一个内核(主机的内核),docker 通过 namspace 对 pid/utc/network 等进行了隔离,虽然 docker 中已经实现了 user namespace,但由于各种原因,默认没有开启,见 docker user namespace:
$ docker run -d cr.d.xiaomi.net/containercloud/alpine:webtool top
- 宿主机中查看 top 进程,显示 root 用户:
$ ps -ef |grep top
root 4096 4080 0 15:01 ? 00:00:01 top
- 容器中查看 id,uid 为 0 对应宿主机的 root 用户,虽然同为 root 用户,但 Linux Capabilities 不同,实际权限与宿主机 root 要少很多。
- 在容器中切换用户到 operator(uid 为 11),执行 sleep 命令,主机中查看对应进程用户为 app,对应 uid 同样为 11:
/ # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
/ # su operator
/ $ id
uid=11(operator) gid=0(root) groups=0(root)
/ $ sleep 100
$ ps -ef |grep 'sleep 100'
app 19302 19297 0 16:39 pts/0 00:00:00 sleep 100
$ cat /etc/passwd | grep app
app❌11:0::/home/app:
六、验证不同用户下 ulimit 的限制
- 设置 ulimit nproc 限制 soft 10/hard 20,默认启动为 root 用户:
$ docker run -d --ulimit nproc=10:20 cr.d.xiaomi.net/containercloud/alpine:webtool top
- 进入容器查看, fd soft 限制为 100 个:
/ # ulimit -a
-f: file size (blocks) unlimited
-t: cpu time (seconds) unlimited
-d: data seg size (kb) unlimited
-s: stack size (kb) 8192
-c: core file size (blocks) unlimited
-m: resident set size (kb) unlimited
-l: locked memory (kb) 64
-p: processes 10
-n: file descriptors 1048576
-v: address space (kb) unlimited
-w: locks unlimited
-e: scheduling priority 0
-r: real-time priority 0
- 启动 30 个进程:
/ # for i in `seq 30`;do sleep 100 &; done
/ # ps | wc -l
36
- 切换到 operator 用户:
/ # su operator# 启动多个进程,到第11个进程无法进行fork
/ $ for i in `seq 8`; do
> sleep 100 &
> done
/ $ sleep 100 &
/ $ sleep 100 &
sh: can't fork: Resource temporarily unavailable
- root 下查看:
/ # ps -ef | grep operator79 operator 0:00 sh99 operator 0:00 sleep 100100 operator 0:00 sleep 100101 operator 0:00 sleep 100102 operator 0:00 sleep 100103 operator 0:00 sleep 100104 operator 0:00 sleep 100105 operator 0:00 sleep 100106 operator 0:00 sleep 100107 operator 0:00 sleep 100109 root 0:00 grep operator
/ # ps -ef | grep operator| wc -l
10
七、验证 ulimit 在不同容器相同 uid 下的限制
- 设置 ulimit nproc 限制 soft 3/hard 3,默认启动为 operator 用户,起 4 个容器,第四个启动失败:
$ docker run -d --ulimit nproc=3:3 --name nproc1 -u operator cr.d.xiaomi.net/containercloud/alpine:webtool top
eeb1551bf757ad4f112c61cc48d7cbe959185f65109e4b44f28085f246043e65
$ docker run -d --ulimit nproc=3:3 --name nproc2 -u operator cr.d.xiaomi.net/containercloud/alpine:webtool top
42ff29844565a9cb3af2c8dd560308b1f31306041d3dbd929011d65f1848a262
$ docker run -d --ulimit nproc=3:3 --name nproc3 -u operator cr.d.xiaomi.net/containercloud/alpine:webtool top
b7c9b469e73f969d922841dd77265467959eda28ed06301af8bf83bcf18e8c23
$ docker run -d --ulimit nproc=3:3 --name nproc4 -u operator cr.d.xiaomi.net/containercloud/alpine:webtool top
b49d8bb58757c88f69903059af2ee7e2a6cc2fa5774bc531941194c52edfd763
$
$ docker ps -a |grep nproc
b49d8bb58757 cr.d.xiaomi.net/containercloud/alpine:webtool "top" 16 seconds ago Exited (1) 15 seconds ago nproc4
b7c9b469e73f cr.d.xiaomi.net/containercloud/alpine:webtool "top" 23 seconds ago Up 22 seconds nproc3
42ff29844565 cr.d.xiaomi.net/containercloud/alpine:webtool "top" 31 seconds ago Up 29 seconds nproc2
eeb1551bf757 cr.d.xiaomi.net/containercloud/alpine:webtool "top" 38 seconds ago Up 36 seconds nproc1
八、总结
① ulimit
- ulimit 限制 fd 总数,限制级别进程,可对所有用户生效;
- ulimit 限制线程总数,限制级别用户(uid),限制同一个 uid 下所有线程/进程数,对于 root 账号无效;
- 对于目前线上情况,有较小的概率因 ulimit 限制导致 fork 失败,如同一个宿主机中有多个 work 容器且基础镜像相同(即 uid 相同),若一个容器线程泄露,由于 ulimit 限制会影响其他容器正常运行。
② cgroup
- cgroup 中对 pid 进行了隔离,通过更改 docker/kubelet 配置,可以限制 pid 总数,从而达到限制线程总数的目的。线程数限制与系统中多处配置有关,取最小值,参考 stackoverflow 上线程数的设置:
-
- docker,容器启动时设置 –pids-limit 参数,限制容器级别 pid 总数;
-
- kubelet,开启 SupportPodPidsLimit 特性,设置 –pod-max-pids 参数,限制 node 每个 pod 的 pid 总数,以 kubelet 为例,开启 SupportPodPidsLimit,–feature-gates=SupportPodPidsLimit=true
- 配置 kubelet,每个 pod 允许最大 pid 数目为 150:
[root@node01 ~]# ps -ef |grep kubelet
root 18735 1 14 11:19 ? 00:53:28 ./kubelet --v=1 --address=0.0.0.0 --feature-gates=SupportPodPidsLimit=true --pod-max-pids=150 --allow-privileged=true --pod-infra-container-image=cr.d.xiaomi.net/kubernetes/pause-amd64:3.1 --root-dir=/home/kubelet --node-status-update-frequency=5s --kubeconfig=/home/xbox/kubelet/conf/kubelet-kubeconfig --fail-swap-on=false --max-pods=254 --runtime-cgroups=/systemd/system.slice/frigga.service --kubelet-cgroups=/systemd/system.slice/frigga.service --make-iptables-util-chains=false
- 在 pod 中起测试线程,root 下起 100 个线程:
/ # for i in `seq 100`; do
> sleep 1000 &
> done
/ # ps | wc -l
106
- operator 下,创建线程受到限制,系统最多只能创建 150 个:
/ # su operator
/ $
/ $ for i in `seq 100`; do
> sleep 1000 &
> done
sh: can't fork: Resource temporarily unavailable
/ $ ps | wc -l
150
- 在 cgroup 中查看,pids 达到最大限制:
[root@node01 ~]# cat /sys/fs/cgroup/pids/kubepods/besteffort/pod8b61d4de-a7ad-11e9-b5b9-246e96ad0900/pids.current
150
[root@node01 ~]# cat /sys/fs/cgroup/pids/kubepods/besteffort/pod8b61d4de-a7ad-11e9-b5b9-246e96ad0900/pids.max
150
- 总结 cgroup 对于 pid 的限制能够达到限制线程数目的,目前 docker 只支持对每个容器的限制,不支持全局配置;kubelet 只支持对于 node 所有 pod 的全局配置,不支持具体每个 pod 的配置。
③ limits.conf/sysctl.conf
- limits.conf 是 ulimit 的具体配置,目录项 /etc/security/limit.d/ 中的配置会覆盖 limits.conf。
- sysctl.conf 为机器级别的资源限制,root 用户可修改,目录项 /etc/security/sysctl.d/ 中的配置会覆盖 sysctl.conf,在 /etc/sysctl.conf 中添加对应配置(fd: fs.file-max = {}; pid: kernel.pid_max = {})
- 测试容器中修改 sysctl.conf 文件:
$ docker run -d --ulimit nofile=100:200 cr.d.xiaomi.net/containercloud/alpine:webtool top
cb1250c8fd217258da51c6818fa2ce2e2f6e35bf1d52648f1f432e6ce579cf0d
$ docker exec -it cb1250c sh/ # ulimit -a
-f: file size (blocks) unlimited
-t: cpu time (seconds) unlimited
-d: data seg size (kb) unlimited
-s: stack size (kb) 8192
-c: core file size (blocks) unlimited
-m: resident set size (kb) unlimited
-l: locked memory (kb) 64
-p: processes unlimited
-n: file descriptors 100
-v: address space (kb) unlimited
-w: locks unlimited
-e: scheduling priority 0
-r: real-time priority 0
/ #
/ # echo 10 > /proc/sys/kernel/pid_max
sh: can't create /proc/sys/kernel/pid_max: Read-only file system
/ # echo 10 > /proc/sys/kernel/pid_max
sh: can't create /proc/sys/kernel/pid_max: Read-only file system
/ # echo "fs.file-max=5" >> /etc/sysctl.conf
/ # sysctl -p
sysctl: error setting key 'fs.file-max': Read-only file system
- 以 priviledged 模式测试,谨慎测试:
$ cat /proc/sys/kernel/pid_max
32768
$ docker run -d -- --ulimit nofile=100:200 cr.d.xiaomi.net/containercloud/alpine:webtool top
$ docker exec -it pedantic_vaughan sh
/ # cat /proc/sys/kernel/pid_max
32768
/ # echo 50000 > /proc/sys/kernel/pid_max
/ # cat /proc/sys/kernel/pid_max
50000
/ # exit
$ cat /proc/sys/kernel/pid_max
50000 # 宿主机的文件也变成50000
- 由于 docker 隔离的不彻底,在 docker 中修改 sysctl 会覆盖主机中的配置,不能用来实现容器级别资源限制 limits.conf 可以在容器中设置,效果同 ulimit。
- 推荐方案如下:
-
- fd 限制:修改 dockerd 配置 default-ulimits,限制进程级别 fd;
-
- thread 限制:修改 kubelet 配置 --feature-gates=SupportPodPidsLimit=true - -pod-max-pids={},cgroup 级别限制 pid,从而限制线程数;
-
- 其他注意事项,调整节点 pid.max 参数;放开或者调大镜像中 ulimit 对非 root 账户 nproc 限制。
相关文章:

云原生之深入解析如何限制Kubernetes集群中文件描述符与线程数量
一、背景 linux 中为了防止进程恶意使用资源,系统使用 ulimit 来限制进程的资源使用情况(包括文件描述符,线程数,内存大小等)。同样地在容器化场景中,需要限制其系统资源的使用量。ulimit: docker 默认支持…...
Django的Auth模块
Auth模块 我们在创建好一个Django项目后执行数据库迁移命令会自动生成很多表 其中有auth_user等表 Django在启动之后就可以直接访问admin路由,需要输入用户名和密码,数据参考的就是auth_user表,并且必须是管理员才能进入 依赖于a…...

敏捷开发方法
理解: 极限编程(XP):敏捷开发的典型方法之一,是一种轻量级(敏捷)、高效,低风险、柔性、可预测的、科学的软件开发方法,它由价值观、原则、实践和行为4个部分组成。其中4大…...

vue 前端实现login页登陆 验证码
实现效果 // template <el-form :model"loginForm" :rules"fieldRules" ref"loginForm" label-position"left" label-width"0px" class"login-container"><span class"tool-bar"></sp…...

python 涉及opencv mediapipe知识,眨眼计数 供初学者参考
基本思路 我们知道正面侦测到人脸时,任意一只眼睛水平方向上的两个特征点构成水平距离,上下两个特征点构成垂直距离 当头像靠近或者远离摄像头时,垂直距离与水平距离的比值基本恒定 根据这一思路 当闭眼时 垂直距离变小 比值固定小于某一个…...

HTTP 和 HTTPS的区别
一、HTTP 1.明文传输,不安全 2.默认端口号:80 3.TCP三次握手即可 二、HTTPS 1.加密传输,更安全(在HTTP层与TCP层之间加上了SSL/TTL安全协议) SSL和TTL是在不同时期的两种叫法,含义相同。 2.默认端口号:443 3.TCP三…...

从零开始训练一个ChatGPT大模型(低资源,1B3)
macrogpt-prertrain 大模型全量预训练(1b3), 多卡deepspeed/单卡adafactor 源码地址:https://github.com/yongzhuo/MacroGPT-Pretrain.git 踩坑 1. 数据类型fp16不太行, 很容易就Nan了, 最好是fp32, tf32, 2. 单卡如果显存不够, 可以用优化器adafactor, 3. 如果…...

从文字到使用,一文读懂Kafka服务使用
🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论…...

什么是https加密协议?
前言: HTTPS(全称:Hypertext Transfer Protocol Secure) 是一个安全通信通道,它基于HTTP开发用于在客户计算机和服务器之间交换信息。它使用安全套接字层(SSL)进行信息交换,简单来说它是HTTP的安全版&…...

0012Java程序设计-ssm医院预约挂号及排队叫号系统
文章目录 **摘** **要**目 录系统实现5.2后端功能模块5.2.1管理员功能模块5.2.2医生功能模块 开发环境 摘 要 网络的广泛应用给生活带来了十分的便利。所以把医院预约挂号及排队叫号管理与现在网络相结合,利用java技术建设医院预约挂号及排队叫号系统,实…...

PaddleClas学习3——使用PPLCNet模型对车辆朝向进行识别(c++)
使用PPLCNet模型对车辆朝向进行识别 1 准备环境2 准备模型2.1 模型导出2.2 修改配置文件3 编译3.1 使用CMake生成项目文件3.2 编译3.3 执行3.4 添加后处理程序3.4.1 postprocess.h3.4.2 postprocess.cpp3.4.3 在cls.h中添加函数声明3.4.4 在cls.cpp中添加函数定义3.4.5 在main.…...

学习记录---kubernetes中备份和恢复etcd
一、简介 ETCD是kubernetes的重要组成部分,它主要用于存储kubernetes的所有元数据,我们在kubernetes中的所有资源(node、pod、deployment、service等),如果该组件出现问题,则可能会导致kubernetes无法使用、资源丢失等情况。因此…...
使用单例模式+观察者模式实现参数配置实时更新
使用vector存储观察者列表 #include <iostream> #include <vector> #include <functional> #include <algorithm>// 配置参数结构体 struct MyConfigStruct {int parameter1;std::string parameter2; };class Config { public:using Observer std::f…...

区块链实验室(28) - 拜占庭节点劫持区块链仿真
在以前的FISCO环境中仿真拜占庭节点攻击区块链网络。该环境共有100个节点,采用PBFT作为共识机制,节点编号分别为:Node0,Node,… ,Node99。这100个节点的前2010区块完全相同,自区块2011开始分叉。…...
聊聊AsyncHttpClient的ChannelPool
序 本文主要研究一下AsyncHttpClient的ChannelPool ChannelPool org/asynchttpclient/channel/ChannelPool.java public interface ChannelPool {/*** Add a channel to the pool** param channel an I/O channel* param partitionKey a key used to retrieve the cac…...

[MySQL] MySQL复合查询(多表查询、子查询)
前面我们学习了MySQL简单的单表查询。但是我们发现,在很多情况下单表查询并不能很好的满足我们的查询需求。本篇文章会重点讲解MySQL中的多表查询、子查询和一些复杂查询。希望本篇文章会对你有所帮助。 文章目录 一、基本查询回顾 二、多表查询 2、1 笛卡尔积 2、2…...

[架构之路-256]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 架构设计 - 软件系统不同层次的复用与软件系统向越来越复杂的方向聚合
目录 前言: 一、CPU寄存器级的复用:CPU寄存器 二、指令级复用:二进制指令 三、过程级复用:汇编语言 四、函数级复用:C语言 五、对象级复用:C, Java, Python 六、组件级复用 七、服务级复用 八、微…...
C++初学教程三
目录 一、运算符 一、自增自减运算符 二、位运算符 三、关系运算符...
雷达点云数据.pcd格式转.bin格式
雷达点云数据.pcd格式转.bin格式 注意,方法1原则上可行,但是本人没整好pypcd的环境 方法2是绝对可以的。 方法1 1 源码如下: def pcb2bin1(): # save as bin formatimport os# import pypcdfrom pypcd import pypcdimport numpy as np…...
Fiddler抓包测试
模拟弱网测试 操作:一、Rules - Customize Rules (快捷键CtrlR)弹出编辑器 二、接着CtrlF查找m_SimulateModem标志位 三、默认上传300ms,下载150ms 四、更改后,继续Rules - Performances - Simulate Modem Speeds勾上 …...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...