首页 文章 精选 留言 我的

精选列表

搜索[初体验],共236篇文章
优秀的个人博客,低调大师

Docker初体验,创建并导出第一个本地镜像

准备工作,创建一个目录docker-test,用来存放创建镜像所需的文件,同事完成相关文件的创建。 [root@ChatDevOps ~]# mkdir docker-test [root@ChatDevOps ~]# cd docker-test/ [root@ChatDevOps docker-test]# touch Dockerfile [root@ChatDevOps docker-test]# touch app.py [root@ChatDevOps docker-test]# touch requirements.txt 在Dockerfile中加入以下内容: # Use an official Python runtime as a parent image FROM python:2.7-slim # Set the working directory to /app WORKDIR /app # Copy the current directory contents into the container at /app ADD . /app # Install any needed packages specified in requirements.txt RUN pip install --trusted-host pypi.python.org -r requirements.txt # Make port 80 available to the world outside this container EXPOSE 80 # Define environment variable ENV NAME World # Run app.py when the container launches CMD ["python", "app.py"] 在app.py中增加以下内容: from flask import Flask from redis import Redis, RedisError import os import socket # Connect to Redis redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2) app = Flask(__name__) @app.route("/") def hello(): try: visits = redis.incr("counter") except RedisError: visits = "<i>cannot connect to Redis, counter disabled</i>" html = "<h3>Hello {name}!</h3>" \ "<b>Hostname:</b> {hostname}<br/>" \ "<b>Visits:</b> {visits}" return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits) if __name__ == "__main__": app.run(host='0.0.0.0', port=80) 在requirements.txt中添加以下内容: Flask Redis 安装pip,并使用pip安装Flask和Redis: [root@ChatDevOps docker-test]# curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py [root@ChatDevOps docker-test]# python get-pip.py [root@ChatDevOps docker-test]# pip install -r requirements.txt [root@ChatDevOps docker-test]# rm -rf get-pip.py [root@ChatDevOps docker-test]# ls app.py Dockerfile requirements.txt 执行构建命令,使用-t选项指定镜像的仓库、标签。注意,镜像名称必须小写。 [root@ChatDevOps docker-test]# docker build -t local/chatdevops . Sending build context to Docker daemon 4.608 kB Step 1/7 : FROM python:2.7-slim Trying to pull repository docker.io/library/python ... 2.7-slim: Pulling from docker.io/library/python ... ... [root@ChatDevOps docker-test]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE local/chatdevops latest fc26e265213a 21 minutes ago 151 MB docker.io/fedora latest cc510acfcd70 2 weeks ago 253 MB 使用刚刚创建的镜像构建一个容器,并将容器的80端口映射到本机的4000端口。 [root@ChatDevOps docker-test]# docker run -p 4000:80 local/chatdevops * Serving Flask app "app" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:80/ (Press CTRL+C to quit) 172.17.0.1 - - [23/May/2018 04:21:04] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [23/May/2018 04:21:04] "GET /favicon.ico HTTP/1.1" 404 - 172.17.0.1 - - [23/May/2018 04:22:24] "GET / HTTP/1.1" 200 - 在浏览器看一下效果:镜像制作完成,导出镜像,选项-o表示将导出内容写入一个文件,替代了标准输出。 [root@ChatDevOps docker-test]# docker save -o chatdevops.tar local/chatdevops [root@ChatDevOps docker-test]# ll 总用量 153716 -rw-r--r--. 1 root root 666 5月 23 09:52 app.py -rw-------. 1 root root 157392896 5月 23 11:47 chatdevops.tar -rw-r--r--. 1 root root 509 5月 23 11:12 Dockerfile -rw-r--r--. 1 root root 12 5月 23 09:51 requirements.txt 在异机上导入刚刚创建的镜像。 [root@ChatDevOps docker-test]# scp chatdevops.tar root@10.1.1.1.12:/root/ [root@ChatDevOps ~]# ll 总用量 153704 -rw-------. 1 root root 157392896 5月 23 14:30 chatdevops.tar [root@ChatDevOps ~]# docker load --input chatdevops.tar ba291263b085: Loading layer [==================================================>] 82.94 MB/82.94 MB 10dd6271862c: Loading layer [==================================================>] 7.487 MB/7.487 MB 4e1a46391216: Loading layer [==================================================>] 46.96 MB/46.96 MB a40d037570f2: Loading layer [==================================================>] 7.649 MB/7.649 MB d9bad830e350: Loading layer [==================================================>] 1.536 kB/1.536 kB 16b29278858d: Loading layer [==================================================>] 5.12 kB/5.12 kB b6e1c4419841: Loading layer [==================================================>] 12.32 MB/12.32 MB Loaded image: local/chatdevops:latest [root@ChatDevOps ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE local/chatdevops latest dea564c3cb05 3 hours ago 151 MB 本文是Docker官方文档的实例的实践与拓展,刚刚学习Docker,诸多问题还望大家海涵和指教。

优秀的个人博客,低调大师

JDK 17 + ZGC 初体验

1 前言 垃圾回收器的暂停问题一直是Java工程师关注的重点,特别是对实时响应要求较高的服务来说,CMS和G1等主流垃圾回收器的数十毫秒乃至上百毫秒的暂停时间相当致命。此外,调优门槛也相对较高,需要对垃圾回收器的内部机制有一定的了解,才能够进行有效的调优。 为了解决此类问题,JDK 11开始推出了一种低延迟垃圾回收器ZGC。ZGC使用了一些新技术和优化算法,可以将GC暂停时间控制在10毫秒以内,而在JDK 17的加持下,ZGC的暂停时间甚至可以控制在亚毫秒级别! 2 ZGC ZGC相关介绍、原理,网上已经有很多类似文章,这里只做简单介绍。 2.1 设计目标 ZGC 最初在 JDK 11 中作为实验性功能引入,并在 JDK 15 中宣布为生产就绪。作为一款低延迟垃圾收集器,旨在满足以下目标: 8MB到16TB的堆大小支持 10ms最大GC暂时 最糟糕的情况下吞吐量会降低15%(低延时换吞吐量很值,吞吐量扩容即可解决) 2.2 ZGC 内存分布 ZGC与传统的CMS、G1不同、它没有分代的概念,只有类似G1的Region概率,ZGC 的 Region可以具有如下图所示的大中下三类容量: 小型 Region(Small Region):容量固定为2MB,用于放置小于 256KB的小对象。 中型 Region(Medium Region):容量固定为 32MB,用于放置大于 256KB但是小于 4MB的对象。 大型 Region(Large Region):容量不固定,可以动态变化,但必须为 2MB的整数倍,用于放置 4MB或以上的大对象。每个大型 Region中会存放一个大对象,这也预示着虽然名字叫“大型 Region”,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。大型 Region在ZGC的实现中是不会被重分配的(重分配是ZGC的一种处理动作,用于复制对象的收集器阶段)因为复制大对象的代价非常高。 2.3 GC工作过程 与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC通过着色指针和读屏障技术,解决了转移过程中准确访问对象的问题,在标记、转移和重定位阶段几乎都是并发执行的,这是ZGC实现停顿时间小于10ms目标的最关键原因。 从上图中可以看出,ZGC只有三个STW阶段:初始标记,再标记,初始转移。 具体转移过程,网上有大量类似文章,这里不做详细介绍,大家有兴趣可以参考以下文章: 新一代垃圾回收器ZGC的探索与实践 ZGC 最新一代垃圾回收器 | 程序员进阶 3 为什么选择JDK17呢? JDK 17于9月14日发布,是一个长期支持(LTS)版本,这意味着它将在很多年内得到支持和更新。这也是第一个LTS版本,其中包含了一个可用于生产环境的ZGC版本。回顾一下,ZGC的实验版本已经包含在JDK 11(之前的LTS版本)中,而第一个可用于生产环境的ZGC版本出现在JDK 15(一个非LTS版本)中。 4 升级过程 从JDK8+G1升级到JDK17+ZGC,主要是在代码层面和JVM启动参数层面的做适配。 4.1 JDK下载 首先jdk17选择的是openjdk,下载地址:https://jdk.java.net/archive/,选择版本17 GA 4.2 代码适配 JDK11移除了 Java EE and CORBA 的模块 项目中如果用到javax.annotation.*、javax.xml.*等等开头的包,需要手动引入对应依赖 <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> </dependency> maven相关依赖版本升级 <!-- 仅供参考 --> <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version> <maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version> <maven-resources-plugin.version>3.2.0</maven-resources-plugin.version> <maven-jar-plugin.version>3.2.0</maven-jar-plugin.version> <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> <maven-deploy-plugin.version>3.0.0-M1</maven-deploy-plugin.version> <maven-release-plugin.version>3.0.0-M1</maven-release-plugin.version> <maven-site-plugin.version>3.9.1</maven-site-plugin.version> <maven-enforcer-plugin.version>3.0.0-M2</maven-enforcer-plugin.version> <maven-project-info-reports-plugin.version>3.1.0</maven-project-info-reports-plugin.version> <maven-plugin-plugin.version>3.6.1</maven-plugin-plugin.version> <maven-javadoc-plugin.version>3.3.0</maven-javadoc-plugin.version> <maven-source-plugin.version>3.2.1</maven-source-plugin.version> <maven-jxr-plugin.version>3.0.0</maven-jxr-plugin.version> Lombok版本升级https://projectlombok.org/changelog <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <!-- <version>1.16.20</version>--> <version>1.18.22</version> </dependency> Java9 模块化后,不允许应用程序查看来自JDK的所有类,会影响部分反射的运行,需要通过以下命令解决 --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED 本地使用了transmittable-thread-local-2.14.2.jar后启动报错 在agent后面加上日志输出即可解决,至于原因,猜测是跟类加载顺序有关系 -javaagent:/Users/admin/Documents/transmittable-thread-local-2.14.2.jar =ttl.agent.logger:STDOUT 以上内容仅针对彩虹桥项目升级遇到的问题,不同的业务代码适配的情况可能不一样,需要根据实际情况寻找解决方案。 4.3 JVM参数替换 下面是一些通用GC参数和ZGC特有参数以及ZGC的一些诊断选型,来自官网:Main - Main - OpenJDK Wiki 具体每个参数的含义,这里不做介绍,可参考官网文档The java Command,里面有详细说明。 JKD8+G1的启动参数: -server -Xms36600m -Xmx36600m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintReferenceGC -XX:+ParallelRefProcEnabled -XX:G1HeapRegionSize=16m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/apps/errorDump.hprof -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintGCApplicationConcurrentTime -verbose:gc -Xloggc:/opt/apps/logs/${app_name}-gc.log JDK17+ZGC的启动参数如下: -server -Xms36600m -Xmx36600m #开启ZGC -XX:+UseZGC #GC周期之间的最大间隔(单位秒) -XX:ZCollectionInterval=120 #官方的解释是 ZGC 的分配尖峰容忍度,数值越大越早触发GC -XX:ZAllocationSpikeTolerance=4 #关闭主动GC周期,在主动回收模式下,ZGC 会在系统空闲时自动执行垃圾回收,以减少垃圾回收在应用程序忙碌时所造成的影响。如果未指定此参数(默认情况),ZGC 会在需要时(即堆内存不足以满足分配请求时)执行垃圾回收。 -XX:-ZProactive #GC日志 -Xlog:safepoint=trace,classhisto*=trace,age*=info,gc*=info:file=/opt/logs/gc-%t.log:time,level,tid,tags:filesize=50M #发生OOM时dump内存日志 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/apps/errorDump.hprof 5 压测结果 直接上图 正如 ZGC 设计目标所描述,它将 GC 暂停时间从过去的几十毫秒降低到了令人惊叹的亚毫秒级别。然而,这种超低延迟表现也需要一定的代价,因为在实现低延迟的同时,ZGC 会占用一定的 CPU 资源。通常情况下,ZGC 占用的 CPU 比例不会超过 15%。在彩虹桥项目中,使用以上推荐的 JVM 参数后,ZGC 占用的 CPU 资源为 6% 左右。 6 ZGC日志 6.1 输出ZGC日志 GC日志中包含有关 GC 操作的详细信息,可以帮我们分析当前GC存在的问题。先来看一下上面JVM参数中关于GC日志的参数 -Xlog:safepoint=trace,classhisto*=trace,age*=info,gc*=info:file=/opt/logs/gc-%t.log:time,level,tid,tags:filesize=50M safepoint=trace:记录关于 safepoint 的 trace 级别日志。 Safepoint 是 JVM 中一个特殊的状态,它用于确保所有线程在特定操作(如垃圾回收、代码优化等)之前进入安全状态。 classhisto*=trace:记录与类的历史相关的 trace 级别日志。 age*=info:记录与对象年龄(在新生代中存在的时间)相关的 info 级别日志。 gc*=info:记录与垃圾回收相关的 info 级别日志。 file=/opt/logs/gc-%t.log:将日志写入到 /opt/logs/ 目录下的文件中,文件名为 gc-%t.log,其中 %t 是一个占位符,表示当前时间戳。 time,level,tid,tags:在每个日志记录中包含时间戳、日志级别、线程 ID 和标签。 filesize=50M:设置日志文件的大小限制为 50MB。当日志文件大小达到此限制时,JVM 将创建一个新的日志文件并继续记录。 更详细的gc日志配置可以参考:https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#enable-logging-with-the-jvm-unified-logging-framework 6.2 STW关键日志 其中我们重点关注的就是GC的STW情况,以下是一些关键字代表GC STW阶段 最基本的STW三阶段,初始标记:日志中Pause Mark Start,再标记:日志中Pause Mark End,初始转移:日志中Pause Relocate Start。 内存分配阻塞:这一般是因为垃圾生产速度大于回收速度,垃圾来不及回收,垃圾将堆占满时,线程会阻塞等待GC完成,关键字是Allocation Stall(被阻塞的线程名称) 如果出现此类日志,可以尝试如下方法解决: -XX:ZCollectionInterval 该配置含义:两个 GC 周期之间的最大间隔(单位秒)。默认情况下,此选项设置为 0(禁用),可以适当调小该配置,让GC周期缩短、提升垃圾回收速度,但这会提升应用CPU占用。 -XX:ZAllocationSpikeTolerance官方的解释是 ZGC 的分配尖峰容忍度。其实就是数值越大,越早触发回收。可以适当调大该配置,更早触发回收,提升垃圾回收速度,但这会提升应用CPU占用。 安全点:所有线程进入到安全点后才能进行GC,ZGC定期进入安全点判断是否需要GC。先进入安全点的线程需要等待后进入安全点的线程直到所有线程挂起。日志关键字safepoint ... stopped dump线程、内存:比如jstack、jmap命令,一般是手动dump导致,日志关键字HeapDumper 7 Linux大页内存 在openjdk的官网上也能看到,开启Linux大页内存后会提升应用的性能。 开启方式见官网文档https://wiki.openjdk.org/display/zgc/Main#Main-EnablingLargePagesOnLinux,注意除了修改系统配置外,还需要在进程JVM启动参数中新增-XX:+UseLargePages配置 经过几轮压测实际测试下来,发现在开启Linux大页后,CPU有8%左右的下降,但是由于大页面会提前预留指定大小的内存,会导致机器的内存使用率较高。而且目前生产环境没有其他应用开启此配置,稳定性有待考究,生产环境自行评估是否开启。 8 总结 在本篇文章中,我们探讨了如何升级到JDK 17,并使用最新一代垃圾回收器ZGC。经过实践和测试,我们发现升级后的系统在垃圾回收方面表现出色,暂停时间被有效控制在1毫秒内。尽管这一优化过程可能会消耗额外的CPU资源,但所获得的超低GC暂停时间显然是非常值得的。总之,相比其他垃圾回收器,ZGC 的性能和稳定性已经非常优秀,而且不需要太多的调优。在大多数情况下,使用 ZGC官方推荐的默认设置即可获得优秀的性能表现。对于那些RT敏感型应用,升级到JDK 17并采用ZGC是一个明智的选择。 文: 新一 本文属得物技术原创,来源于:得物技术官网 未经得物技术许可严禁转载,否则依法追究法律责任!

优秀的个人博客,低调大师

云原生边缘设备解决方案Akri on k3s初体验

作者: 涂家英,SUSE 资深架构师,专注 Cloud-Native 相关产品和解决方案设计,在企业级云原生平台建设领域拥有丰富的经验。 写在前面 k3s 是 SUSE 推出的为物联网和边缘计算构建的经过认证的 Kubernetes 发行版,它可以帮助用户在资源受限的场景下使用 kubernetes,并结合 SUSE Rancher 实现云边协同。 将 k3s 与微软开源的 kubernetes 边缘项目 Akri 结合使用,用户就拥有了在边缘环境发现和使用各种 IOT 设备的能力。 架构 Akri 目前是 CNCF 的一个开源项目,其功能简单地说就是可以根据配置自动地寻找到相应的 iot 设备,为其创建关联的工作负载,并且通过不断地检测实现工作负载的动态变化。引用一句官方的描述为:You name it, Akri finds it, you use it. 架构如下: 包含了 Controller 服务、Agent 服务和 Discovery Handlers 服务以及两个 CRD 资源。 具体的组件解析可以查看官方文档:https://docs.akri.sh/architecture/architecture-overview 下面我们通过一个示例来更好地理解 Akri 的工作方式。 体验 示例使用了官方提供的一个 OPC UA 温度计 Demo,OPC UA 是现在使用比较广泛的工业自动化通信协议,我们可以通过 Akri 自动发现使用 OPU CA 协议的温度计设备,并为其创建工作负载,采集和使用其产生的温度数据。 示例中体现的大致工作流程如下: 首先需要模拟出两台 OPC UA 的服务设备,然后在 k3s 集群上部署 Akri 服务,基础工作完成后: 向集群配置用于发现 OPC UA 设备的 CRD(Configuration) CRD 下发后,Akri 的相应 Discovery 服务会按照规则寻找 OPC UA 设备 在找到 OPC UA 设备后,Agent 服务会生成设备对应的 CRD(Instance),Controller 服务查看到 Instance CRD 资源后,将创建对应的工作负载 OPC UA 设备模拟 使用了一个.NET 类型的开源项目模拟实现 OPU CA 设备,项目地址为:https://gitee.com/leotuss/opcua-donet 克隆到本地后,需要修改以下文件: # /opcua-dotnet/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml文件第76、77行 将<node-ip style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">替换为节点地址 #/opcua-dotnet/Applications/ReferenceServer/Quickstarts.ReferenceServer.Config.xml文件第77、78行 将<node-ip style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">替换为节点地址</node-ip></node-ip>` Linux 要运行这个程序,需要安装.NET core 的 SDK 和 runtime 可以使用以下命令运行此程序: dotnet run --project /opcua-dotnet/Applications/ConsoleReferenceServer/NetCoreReferenceServer.csproj 运行后,可以看到如下提示: root@edge-iot1:~/opcua-dotnet/Applications/ConsoleReferenceServer# dotnet run --project NetCoreReferenceServer.csproj .Net Core OPC UA Reference Server opc.tcp://192.168.6.151:62541/Quickstarts/ReferenceServer https://192.168.6.151:62540/Quickstarts/ReferenceServer/ Server started. Press Ctrl-C to exit... 至此程序运行成功,应用可以通过订阅opc.tcp://:62541/Quickstarts/ReferenceServer获得数据 安装 Akri 可以通过 Helm 安装 Akri: # 添加Akri repo helm repo add akri-helm-charts https://project-akri.github.io/akri/ # 部署Akri helm install akri akri-helm-charts/akri\ --setkubernetesDistro=k3s \ --setopcua.discovery.enabled=true 关于 kubernetesDistro 配置,Akri 依赖 crictl 跟踪 pod 信息,所以必须知道容器运行时 socket 的位置。目前 Akri 支持四种类型: 标准 kuberentes,对应配置为:--set kubernetesDistro=k8s k3s,对应配置为:--setkubernetesDistro=k3s microk8s,对应配置为:--set kubernetesDistro=microk8s 其它,对应配置为:--set agent.host.containerRuntimeSocket=/container/runtime.sock 关于 xxx.discovery.enabled 配置,Akri 目前支持三种设备发现: onvif:IP Cameras 的主流协议 opc ua:工业自动化通信协议 udev:linux 设备管理器 如我们需要 Akri 发现 onvif 设备,就可以配置--set onvif.discovery.enabled=true,配置后 Akri 会在集群中部署相应的 Discovery 服务,以 Daemonset 的方式运行,支持叠加部署,如需要发现上述三种类型设备,部署命令可以修改为: helm install akri akri-helm-charts/akri\ --setkubernetesDistro=k3s \ --setopcua.discovery.enabled=true\ --setonvif.discovery.enabled=true\ --setudev.discovery.enabled=true 部署完成后查看集群 Pods 可以看到: root@edge-k3s:~# kubectl get pods NAME READY STATUS RESTARTS AGE akri-controller-deployment-d4f7847b6-rlgrr 1/1 Running 11(25h ago) 4d2h akri-agent-daemonset-9s9m9 1/1 Running 10(25h ago) 3d23h akri-opcua-discovery-daemonset-tv84d 1/1 Running 8(25h ago) 3d17h 部署 CRD 使用 Akri 发现设备需要部署类型为 Configuration 的 CRD: apiVersion:akri.sh/v0 kind:Configuration metadata: name:akri-opcua-monitoring namespace:default spec: brokerProperties: IDENTIFIER:Thermometer_Temperature NAMESPACE_INDEX:"2" brokerSpec: brokerPodSpec: containers: - image:ghcr.io/project-akri/akri/opcua-monitoring-broker:latest name:akri-opcua-monitoring-broker resources: limits: '{{PLACEHOLDER}}':"1" cpu:30m memory:200Mi requests: '{{PLACEHOLDER}}':"1" cpu:9m memory:76Mi capacity:1 configurationServiceSpec: ports: - name:grpc port:80 protocol:TCP targetPort:8083 type:ClusterIP discoveryHandler: name:opcua discoveryDetails:|+ opcuaDiscoveryMethod: standard: discoveryUrls: -opc.tcp://192.168.6.151:62541/Quickstarts/ReferenceServer/ -opc.tcp://192.168.6.152:62541/Quickstarts/ReferenceServer/ applicationNames: action:Exclude items:[] name:opcua instanceServiceSpec: ports: - name:grpc port:80 protocol:TCP targetPort:8083 type:ClusterIP 需要关注的配置: spec.brokerProperties: 用于定义需要采集数据的 ID 信息,本演示中 opcua 程序中添加了IDENTIFIER:为Thermometer_Temperature和NAMESPACE_INDEX: "2"的数据输出,数据输出内容为 70-80 的随机数,并且在随机到 75 时,将 75 替换为 120 spec.brokerSpec.brokerPodSpec.containers.image: 设备对应工作负载的镜像,演示使用的是官方提供的镜像,作用是订阅 opcua 所产生的相应数据,此镜像是可以自定义的,实现更多可能 spec.capacity:针对设备的工作负载副本数量,用于实现工作负载高可用 spec.discoveryHandler: 这部分主要定义了发现 opuca 设备的规则,支持一些过滤规则 spec.configurationServiceSpec:Akri Controller 服务会为所有设备的工作负载创建一个总 svc,这段用于定义相应的 svc 的配置 spec.instanceServiceSpec: Akri Controller 服务会为每一个工作负载创建 svc,这段用于定义相应 svc 的配置 示例中可以看到 opuca 的发现规则是具体的服务地址,如果要支持批量的 opuca 设备发现,可以使用 Local discovery server(LDS),将 opcua 的设备注册到 LDS 中,然后在 discoveryUrls 配置中使用 LDS 的地址。采用 LDS 方式的话,可以实现过滤能力,如排除掉哪些 opcua 服务或者包含哪些 opcua 服务,示例如下: # 发现LDS中的所有opcua设备,除了<someserver style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">以外</someserver> discoveryDetails:|+ opcuaDiscoveryMethod: standard: discoveryUrls: -opc.tcp://<lds服务器地址 style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">:4840</lds服务器地址> applicationNames: action:Exclude items: - 将 akri-opcua-monitoring 的 crd 部署到集群后,可以通过kubectl get akric查看: root@edge-k3s:~/akric-crd# kubectl get akric NAME CAPACITY AGE akri-opcua-monitoring 1 2m13s 查看 Discovery 的服务日志可以看到,两个 opcua 设备已经被发现: [2022-11-10T08:26:26Z TRACE akri_opcua::discovery_impl] get_discovery_url_from_application - found server : Quickstart Reference Server [2022-11-10T08:26:26Z TRACE akri_opcua::discovery_impl] get_discovery_url_from_application - server has [UAString { value: Some("https://192.168.6.151:62540/Quickstarts/ReferenceServer/discovery") }, UAString { value: Some("opc.tcp://192.168.6.151:62541/Quickstarts/ReferenceServer") }] DiscoveryUrls [2022-11-10T08:26:26Z TRACE akri_opcua::discovery_impl] get_discovery_urls - Server at opc.tcp://192.168.6.152:62541/Quickstarts/ReferenceServer/ responded with 1 Applications [2022-11-10T08:26:26Z TRACE akri_opcua::discovery_impl] get_discovery_url_from_application - found server : Quickstart Reference Server [2022-11-10T08:26:26Z TRACE akri_opcua::discovery_impl] get_discovery_url_from_application - server has [UAString { value: Some("https://192.168.6.152:62540/Quickstarts/ReferenceServer/discovery") }, UAString { value: Some("opc.tcp://192.168.6.152:62541/Quickstarts/ReferenceServer") }] DiscoveryUrls [2022-11-10T08:26:26Z TRACE akri_opcua::discovery_handler] discover - found OPC UA server at DiscoveryURL opc.tcp://192.168.6.151:62541/Quickstarts/ReferenceServer [2022-11-10T08:26:26Z TRACE akri_opcua::discovery_handler] discover - found OPC UA server at DiscoveryURL opc.tcp://192.168.6.152:62541/Quickstarts/ReferenceServer 可以通过kubectl get akrii查看由 Agent 自动生成的 opcua 的 instance crd 资源: root@edge-k3s:~/akric-crd# kubectl get akrii NAME CONFIG SHARED NODES AGE akri-opcua-monitoring-7aa6fb akri-opcua-monitoring true ["edge-k3s"] 5m10s akri-opcua-monitoring-20f7e0 akri-opcua-monitoring true ["edge-k3s"] 5m9s` 于此同时,使用kubectl get pods可以查看到为设备自动创建的工作负载: NAME READY STATUS RESTARTS AGE akri-controller-deployment-d4f7847b6-rlgrr 1/1 Running 11 (27h ago) 4d4h akri-agent-daemonset-9s9m9 1/1 Running 10 (27h ago) 4d1h akri-opcua-discovery-daemonset-tv84d 1/1 Running 8 (27h ago) 3d19h edge-k3s-akri-opcua-monitoring-7aa6fb-pod 1/1 Running 0 6m44s <--- edge-k3s-akri-opcua-monitoring-20f7e0-pod 1/1 Running 0 6m43s <--- 部署数据展示服务 部署数据展示服务查看效果: kubectl apply -f https://raw.githubusercontent.com/project-akri/akri/main/deployment/samples/akri-anomaly-detection-app.yaml 部署完成后,查看一下展示服务 SVC 的 NodePort 端口: root@edge-k3s:~# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 5d6h akri-opcua-monitoring-7aa6fb-svc ClusterIP 10.43.152.214 <none> 80/TCP 13m akri-opcua-monitoring-svc ClusterIP 10.43.242.118 <none> 80/TCP 13m akri-opcua-monitoring-20f7e0-svc ClusterIP 10.43.22.196 <none> 80/TCP 13m akri-anomaly-detection-app NodePort 10.43.248.164 <none> 80:32007/TCP 7s <--- 访问 NodePort 端口,查看效果: 这个展示服务原理是通过连接工作负载的 SVC 获取工作负载采集到的设备数据,当值为 70-80 中任意数值时表示正常,用黑体展示;当值为 120 时表示异常,用红体展示 当设备下线时,Akri 会自动删除设备对应的工作负载,删除的时间大约为 5 分钟,以便应对可能出现的临时网络故障。 总 结 Akri 其实可以理解为是一种设备自动发现的框架,它可以通过云原生的方式帮助我们发现并使用 IOT 设备,目前支持 onvif、udev、opcua 三种类型。其它包括 Bluetooth、CoAP、IP、LoRaWAN、Zeroconf、acpid、MQTT 也正在开发中。 使用 k3s 可以帮助用户实现在边缘侧使用 kubernetes 的能力,通过 Akri 可以解决边缘场景下发现和使用设备的问题,这样用户就能将更多的精力专注在数据处理的应用上。

优秀的个人博客,低调大师

每日一博 | 一位 Rust 开发者的 Go 初体验

作者介绍:Nick Cameron,PingCAP 研发工程师,Rust 语言核心成员。 感谢 Rust 语言中文社区伙伴们的翻译和审校: 翻译:尚卓燃 审校:吴聪、张汉东 过去几周,我一直在用 Go 语言编写程序。这是我首次在大型且重要的项目中使用 Go。在研究 Rust 的特性时,我也看了很多关于 Go 的内容,包括体验示例和编写玩具程序。但真正用它编程又是一种完全不同的体验。 我觉得把这次体验写下来应该会很有趣。在这篇文章中,我会尽量避免将 Go 与 Rust 进行过多的比较,不过,由于我是从 Rust 转向 Go,难免也会包含一些比较。应该事先声明的是,我更偏袒 Rust ,但会尽力做到客观。 总体印象 用 Go 编程的感觉很棒。库程序里有我想要的一切,总体实现较为完善。学习体验也十分顺畅,不得不说,Go 是一种经过精心设计的实用性语言。举个例子:一旦你知悉了 Go 的语法,就能将其他语言中惯用法延续到 Go 中。只要你学会一些 Go,就可以相对轻易地推测 Go 语言的其他特性。凭借一些来自其他语言的知识,我能够阅读并理解 Go 代码,而不需要过多的搜索(Google)。 与 C/C++、Java、Python 等相比,Go 并没有那么多痛点,而且更具生产力。然而,它还是与这些语言处在同一个时代。尽管它从其他语言身上吸取了一些教训,甚至我个人认为它可能是那一代语言中最好的那个,但绝对还属于那一代语言。这是一种渐进式的改进,而不是推陈出新(需要明确的是,这不是意味着对其价值的批判,从软件工程的角度,渐进式改进通常会带来好的影响)。一个很好的例证是 nil:像 Rust 和 Swift 这样的语言已经去除了 null 的概念,并且消除了相关的一整类错误。Go 降低了一部分风险:没有空值(no null values),在 nil 和 0 之间进行区分。但其核心思想仍未改变,同样还会出现解空指针引用这种常见的运行时错误。 易学性 Go 非常易学。我知道人们经常吹捧这一点,但是我真的为自己生产力的飞速提高而感到震惊。多亏了 Go 语言以及它的文档和工具,我仅仅花了两天时间就可以写出「有价值」、可以提交的代码。 有助于易学性的几个因素是: Go 很精简。很多语言都试图让自己看起来小巧,但 Go 真正做到了这一点(这基本上是一件好事,我对这种自律精神印象深刻)。 标准库很出色(同样,也很小)。从生态系统中寻找并使用库程序非常容易。 几乎没有其他语言中所不具备的东西。Go 从其他既存语言中提取了很多内容,并进行完善,最后将它们很好地组合在一起。它在避免标新立异这一方面做了极大努力。 乏味的样板式代码 Go 代码很快就会变得非常重复。这是由于它缺乏宏或者泛型这种用于减少重复的机制(接口虽然有利于抽象,但在减少代码重复方面作用没有那么大)。最终我会写很多函数,而他们除了类型不同之外其他甚至完全一样。 错误处理也会导致重复。许多函数中像 if err != nil { return err } 这样的样板式代码甚至比那些真正有价值的代码还要多。 使用泛型或宏来减少样板式代码有时会受到批评,理由是不应为使代码易于编写而使其丧失可读性。我发现 Go 恰恰提供了一个反例,复制和粘贴代码往往既快速又简单,阅读代码却会令人灰心丧气,因为你不得不忽略大量的无关代码或者在大量的相同代码中找到细微的不同。 我喜欢的东西 编译时间:绝对快,可以确定要比 Rust 快得多。但实际上,它并没有我预期的那么快(对于中型到大型项目,我感觉它的速度只是与 C/C++ 相接近,或者稍微快一点。而我更加期待能够即时编译)。 协程(goroutine)和信道(channel):值得称赞的是,Go 为生成协程和使用信道提供了轻量级的语法。尽管只是一个小细节,却使 Go 的并发编程体验比其他语言更优越,它真正揭示了语法的力量。 接口:它们并不复杂,但是很容易理解和使用,并且在很多地方都很实用。 if ...; ... { } 语法:可以将变量的作用域限制在 if 语句真的很好。这与 Swift 及 Rust 中的 if let 起着相似的效果,但用途更为广泛(Go 没有像 Swift 和 Rust 那样的模式匹配,所以它无法使用 if let )。 测试和文档注释都很容易使用。 Go 工具链非常友好:将所有东西都放在一个地方,而不需要在命令行上使用多个工具。 拥有垃圾收集器(GC):不用考虑内存管理真的会使编程更加轻松。 可变参数。 我不喜欢的东西 以下内容没有特定的顺序。 nil 切片:要知道 nil、nil 切片和空切片三者都不相同,我敢保证我们只需要其中的两个,而不需要第三个。 枚举类型并不是第一公民:使用常量模拟枚举让人感觉是一种倒退。 不允许循环引用:这实际上限制了包在划分项目模块中的可用性,因为它变相鼓励了在一个包中堆积大量文件(或拥有大量零碎的小包,如果本该放在一起的文件四处分散,这也同样糟糕)。 switch 允许出现遗漏匹配的情况。 for ... range 语句会返回一对「索引/值」。要想只获取索引很容易(忽略值就好);但若要只获取值,则需要显式声明。在我看来,这种做法更应该颠倒过来,因为在大多数情况下,我更需要值而不是索引。 语法: 定义与用途存在不一致。 编译器有时会很挑剔(例如,要求或禁止尾随逗号);通过良好的工具可以缓解这种困扰,但是有时仍然会产生一些恼人的额外步骤。 使用多值返回类型时,类型上需要括号,但 return 语句中却不需要。 声明一个结构体需要两个关键字(type 和 struct)。 采用大写命名法来标记公共或私有变量,看起来就像匈牙利命名法那样,但更糟糕。 隐式接口。我知道它也出现在我喜欢的东西中,但有时候它确实很惹人烦——特别是当你试图找出所有实现该接口的类型,或者哪些接口是为给定类型而实现的时候。 你无法在不同的包中编写带有接收器的函数,所以即使接口是「鸭子类型」的,你也不能为其他包中的类型实现这个接口,这使得它们的用处大大降低。 还有我之前已经提过的,Go 缺少泛型和宏。 一致性 作为一名语言设计者和程序员,Go 最让我惊讶的地方也许是它的内置功能和用户可用功能之间频频出现不一致。许多语言的目标之一就是尽可能消除编译器魔法,让用户也能使用内置功能。运算符重载是一个简单但有争议的例子。但 Go 有很多魔法!你很容易就会遇到这样的问题:无法做那些内置功能可以做的事情。 一些让我印象深刻的地方: 返回多个值和信道的语法很棒,但是这两个无法一起使用,因为没有元组类型。 能够用 for ... range 语句对数组和切片进行迭代,但对其他集合就无能为力了,因为它缺乏迭代器的概念。 像 len 或者 append 这样的函数是全局函数,但你自己的函数却无法转变成全局函数。这些全局函数只能使用内置类型。即便 Go「没有泛型」,它们也可以变得通用。 没有运算符重载,那么 == 就会使人感到恼火。因为这意味着你不能在词典中使用自定义类型作为键,除非它们是可比较的。这一属性派生自类型结构,程序员无法重写该属性。 总结 Go 是一种简单、小巧、令人愉悦的语言。它也有一些犄角旮旯,但绝大部分是经过精心设计的。它的学习速度令人难以置信,并且规避了其他语言中一些不那么广为人知的特性。 Go 也是一种与 Rust 截然不同的语言。虽然两者都可以笼统地描述为「系统语言」或「C 语言的替代品」,但它们的设计目标、应用领域、语言风格和优先级不尽相同。垃圾收集确实带来了一个巨大的差异:使用 GC 使得 Go 变得更简单、更小,也更容易理解。而不使用 GC 使 Rust 奇快无比(特别是在您需要保证延迟,而不仅仅是高吞吐量的时候),并且得以支持 Go 中不可能实现的特性或编程模式(或者至少在不牺牲性能的前提下是无法实现的)。 Go 是一种编译型语言,其运行时得到了良好的实现,其速度毋庸置疑。Rust 也是编译型语言,但是运行时要小得多,它真的迅捷无比。在没有其他限制的情况下,我认为选择使用 Go 还是 Rust 其实意味着一种权衡: 一方面,Go 的学习曲线更短、程序更简单(这意味着更快的开发速度); 另一方面,Rust 真的性能卓越,并且类型系统更富有表现力(这使程序更安全,也意味着更快的调试和错误查找)。 附:英文原版文章 欢迎大家在下方留言,说说你的 Go & Rust 体验吧:从入门到放弃,从放弃到精通,踩过的坑,流过的泪……或者说说它们的必杀优势。 ⚠️理智探讨,Peace & Love.

优秀的个人博客,低调大师

b2b2c社交电商--Spring Cloud GateWay初体验

Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。本文首先用官方的案例带领大家来体验下Spring Cloud的一些简单的功能,在后续文章我会使用详细的案例和源码解析来详细讲解Spring Cloud Gateway. 创建工程 本案例的的源码下载于官方案例,也可以在我的Github上下载。工程使用的Spring Boot版本为2.0.5.RELEASE,Spring Cloud版本为Finchley.SR1。 新建一个工程,取名为sc-f-gateway-first-sight在工程的pom文件引用工程所需的依赖,包括spring boot和spring cloud,以及gateway的起步依赖spring-cloud-starter-gateway,代码如下: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> 创建一个简单的路由 在spring cloud gateway中使用RouteLocator的Bean进行路由转发,将请求进行处理,最后转发到目标的下游服务。在本案例中,会将请求转发到http://httpbin.org:80这个地址上。代码如下: @SpringBootApplication @RestController public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public RouteLocator myRoutes(RouteLocatorBuilder builder) { return builder.routes() .route(p -> p .path("/get") .filters(f -> f.addRequestHeader("Hello", "World")) .uri("http://httpbin.org:80")) .build(); } } 在上面的myRoutes方法中,使用了一个RouteLocatorBuilder的bean去创建路由,除了创建路由RouteLocatorBuilder可以让你添加各种predicates和filters,predicates断言的意思,顾名思义就是根据具体的请求的规则,由具体的route去处理,filters是各种过滤器,用来对请求做各种判断和修改。 上面创建的route可以让请求“/get”请求都转发到“http://httpbin.org/get”。在route配置上,我们添加了一个filter,该filter会将请求添加一个header,key为hello,value为world。 启动springboot项目,在浏览器上http://localhost:8080/get,浏览器显示如下: { "args": {}, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Cache-Control": "max-age=0", "Connection": "close", "Cookie": "_ga=GA1.1.412536205.1526967566; JSESSIONID.667921df=node01oc1cdl4mcjdx1mku2ef1l440q1.node0; screenResolution=1920x1200", "Forwarded": "proto=http;host=\"localhost:8080\";for=\"0:0:0:0:0:0:0:1:60036\"", "Hello": "World", "Host": "httpbin.org", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", "X-Forwarded-Host": "localhost:8080" }, "origin": "0:0:0:0:0:0:0:1, 210.22.21.66", "url": "http://localhost:8080/get" } 可见当我们向gateway工程请求“/get”,gateway会将工程的请求转发到“http://httpbin.org/get”,并且在转发之前,加上一个filter,该filter会将请求添加一个header,key为hello,value为world。 注意HTTPBin展示了请求的header hello和值world。 使用Hystrix 在spring cloud gateway中可以使用Hystrix。Hystrix是 spring cloud中一个服务熔断降级的组件,在微服务系统有着十分重要的作用。Hystrix是 spring cloud gateway中是以filter的形式使用的,代码如下: @Bean public RouteLocator myRoutes(RouteLocatorBuilder builder) { String httpUri = "http://httpbin.org:80"; return builder.routes() .route(p -> p .path("/get") .filters(f -> f.addRequestHeader("Hello", "World")) .uri(httpUri)) .route(p -> p .host("*.hystrix.com") .filters(f -> f .hystrix(config -> config .setName("mycmd") .setFallbackUri("forward:/fallback"))) .uri(httpUri)) .build(); } 在上面的代码中,我们使用了另外一个router,该router使用host去断言请求是否进入该路由,当请求的host有“*.hystrix.com”,都会进入该router,该router中有一个hystrix的filter,该filter可以配置名称、和指向性fallback的逻辑的地址,比如本案例中重定向到了“/fallback”。 现在写的一个“/fallback”的l逻辑: @RequestMapping("/fallback") public Mono<String> fallback() { return Mono.just("fallback"); } Mono是一个Reactive stream,对外输出一个“fallback”字符串。 使用curl执行以下命令: curl --dump-header - --header 'Host: www.hystrix.com' http://localhost:8080/delay/3 返回的响应为: fallback 可见,带hostwww.hystrix.com的请求执行了hystrix的fallback的逻辑。 总结 本文通过官方的一个简单的案例,来讲解了spring cloud gateway的简单用法,在spring cloud gateway中有2个重要的概念predicates和filters,它们个将会在后续文章讲解。敬请期待。

优秀的个人博客,低调大师

Docker初体验,向Docker Hub推送第一个Docker镜像

一、注册Docker Hub账号 打开Docker Hub网站,找到注册选项,按照常规注册流程进行注册即可。需要注意的是,有时候可能需要进行人机识别验证,这需要调用Google的验证服务。众所周知Google在国内的情况,需要自己想办法搞定。如果人机验证这一步过不去,那么Docker Hub的账号是无法注册成功的。 二、通过Dockerfile构建镜像 这一步在之前的文章中有所涉及,操作如下: [root@ChatDevOps docker-test]# docker build -f /root/docker-test/Dockerfile -t chatdevops/myapp . Sending build context to Docker daemon 157.4 MB Step 1/7 : FROM python:2.7-slim ---> 46ba956c5967 Step 2/7 : WORKDIR /app ---> Using cache ---> 874ecbc1dfc0 Step 3/7 : ADD . /app ---> Using cache ---> fe2d0e196a64 Step 4/7 : RUN pip install --trusted-host pypi.python.org -r requirements.txt ---> Running in caf394f66822 ... ... 以上命令中,选项-f指定了Dockerfile的路径,选项-t指定了新的镜像的仓库名称及镜像名称,还可以指定镜像的标签。命令末尾的点代表构建新镜像的当前目录,也可以写成完整路径。 [root@ChatDevOps docker-test]# docker build -f /root/docker-test/Dockerfile -t chatdevops/myapp:1.01 /root/docker-test/ 三、登录Docker Hub 我在Docker Hub注册的账号为chatdevops,现在使用该账号进行登录。完整命令如下: [root@ChatDevOps docker-test]# docker login Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. Username (chatdevops): chatdevops Password: Login Succeeded 四、给新构建的本地镜像打标签 如果本地镜像的仓库名与你新注册的Docker Hub账号名称不一致,就需要使用docker tag进行重新打标签,具体命令格式为: docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG] 因为我注册的账号与我本地仓库的名称都是chatdevops,所以我在操作过程中省略了tag这一步。但是为了演示这个例子,我再次tag一下。 [root@ChatDevOps docker-test]# docker tag chatdevops/myapp:1.01 chatdevops/myapp:1.02 [root@ChatDevOps docker-test]# docker image ls chatdevops/myapp REPOSITORY TAG IMAGE ID CREATED SIZE chatdevops/myapp 1.01 c4bec2582bf3 47 minutes ago 309 MB chatdevops/myapp 1.02 c4bec2582bf3 47 minutes ago 309 MB chatdevops/myapp latest c4bec2582bf3 47 minutes ago 309 MB 五、将新构建的本地镜像推送到Docker Hub 通过docker push命令可以将一个或多个本地镜像推送到Docker Hub。 [root@ChatDevOps docker-test]# docker push chatdevops/myapp 该命令将镜像myapp的所有标签全部推送到Docker Hub。我们看一下效果:我们可以在其他机器上直接创建容器。例如:我们使用镜像chatdevops/myapp:1.02在10.1.1.12这台机器上创建一个名为test-app的容器。 [root@ChatDevOps ~]# docker run -i -t --name test-app chatdevops/myapp:1.02 Unable to find image 'chatdevops/myapp:1.02' locally Trying to pull repository docker.io/chatdevops/myapp ... 1.02: Pulling from docker.io/chatdevops/myapp 4d0d76e05f3c: Pull complete da828db4a2d5: Pull complete dae8f1abda34: Pull complete 7f80c063ca4d: Pull complete 4ad5318a2b9b: Pull complete 95a59aa8e00b: Pull complete 776fee21eb8e: Pull complete Digest: sha256:4de441303d87d392b36fc1218a1be18e3b2bf5b81c9e88eed8688c402f06a793 Status: Downloaded newer image for docker.io/chatdevops/myapp:1.02 [root@ChatDevOps ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 09fe7a428ecc chatdevops/myapp:1.02 "python app.py" About a minute ago Up About a minute 80/tcp test-app 在运行这个容器的过程中,docker会从Docker Hub拉取镜像chatdevops/myapp:1.02存放于本地,再创建容器。当然也可以先将Docker Hub的镜像拉取到本地再创建容器。 六、总结 通过以上步骤,我们将自己创建的容器上传的Docker Hub的仓库中,无论我们在哪里,只要网络能顺利与Docker Hub互联,我们就可以随时随地运行我们自己构建的镜像创建的容器,非常方便。此处我们使用的是公开的仓库,还可以将我们的镜像共享给其他需要的人呢,非常方便。可以通过关键字搜索一下我刚刚创建的镜像。

优秀的个人博客,低调大师

JDK 17 营销初体验 —— 亚毫秒停顿 ZGC 落地实践 | 京东云技术团队

前言 自 2014 年发布以来, JDK 8 一直都是相当热门的 JDK 版本。其原因就是对底层数据结构、JVM 性能以及开发体验做了重大升级,得到了开发人员的认可。但距离 JDK 8 发布已经过去了 9 年,那么这 9 年的时间,JDK 做了哪些升级?是否有新的重大特性值得我们尝试?能否解决一些我们现在苦恼的问题?带着这份疑问,我们进行了 JDK 版本的调研与尝试。 新特性一览 现如今的 JDK 发布节奏变快,每次新出一个版本,我们就会感叹一下:我还在用 JDK 8,现在都 JDK 9、10、11 …… 21 了?然后就会瞅瞅又多了哪些新特性。有一些新特性很香,但考虑一番还是决定放弃升级。主要原因除了新增特性对我们来说改变不大以外,最重要的就是 JDK 9 带来的模块化(JEP 200),导致我们升级十分困难。 模块化的本意是将 JDK 划分为一组模块,这些模块可以在编译时、构建时和运行时组合成各种配置,主要目标是使实现更容易扩展到小型设备,提高安全性和可维护性,并提高应用程序性能。但付出的代价非常大,最直观的影响就是,一些 JDK 内部类不能访问了。 但是除此之外,并没有太多阻塞升级的问题,后续版本都是一些很香的特性: G1 (JEP 248、JEP 307、JEP 344、JEP 345、JEP 346),提供一个支持指定暂停时间、NUMA 感知内存分配的高性能垃圾回收器 ZGC (JEP 333、JEP 376、JEP 377),一个支持 NUMA,暂停时间不应超过 1ms 的垃圾回收器 并发 API 更新(JEP 266),提供 publish-subscribe 框架,支持响应式流发布 - 订阅框架的接口,以及 CompletableFuture 的进一步完善 集合工厂方法(JEP 269),类似 Guava,支持快速创建有初始元素的集合 新版 HTTP 客户端(JEP 321),一个现代化、支持异步、WebSocket、响应式流的 JDK 内置 API 空指针 NPE 直接给出异常方法位置(JEP 358),以前只给代码行数,不告诉哪个方法,一行多个方法的写法一但出现空指针,全靠程序员上下文分析推理 instanceof 的模式匹配(JEP 394),判断类型后再也不用强转了 数据记录类(JEP 395),一个标准的值聚合类,帮助程序员专注于对不可变数据进行建模,实现数据驱动 Switch 表达式语法改进(JEP 361),改变 Switch 又臭又长,易于出错的现状 文本块(JEP 378),支持二维文本块,而不是像现在一样通过 + 号自行拼接 密封类(JEP 409),提供一种限制进行扩展的语法,超类应该可以被广泛访问(因为它代表了用户的重要抽象),但不能广泛扩展(因为它的子类应该仅限于作者已知的子类) 以及一些未提到的底层数据结构优化,JVM 性能提升…… 这么多的优点,恰好能解决我们当前遇到的一些问题,因此我们决定进行 JDK 升级。 升级 升级应用评估 首先自然是要考虑要将哪些应用进行升级。我们根据以下条件进行应用筛选: 第一,也是最重要的一点,此系统可以通过升级,解决现有问题与瓶颈 第二,有完备的机制能够进行快速回归与验证,如完备的单元测试,自动化测试覆盖能力,便捷的生产压测能力等,底层的升级一定要做好完备的验证 第三,技术债务一定要少,不至于在升级过程中遇到一些必须解决的技术债,给升级增加难度 第四,负责升级的人对这个系统都很了解,除核心业务逻辑外,还能够了解引入了哪些中间件与依赖,使用了中间件的哪些功能,中间件升级后,大量不兼容的改动是否对现有系统造成影响 最终我们选取了一个结算页、收银台展示无券支付营销的应用进行升级。此应用特点如下: 作为核心链路的应用之一,接口响应时间要求很高,GC 是其耗时抖动的瓶颈之一 业务正在进行快速迭代发展,随着降本增效策略的落地,营销策略进一步精细化,营销种类、数量、范围进一步增加,给系统性能带来更大的挑战 日常流量不低,整点存在突发流量,并且需要承接大促流量 核心链路覆盖了单元测试,测试环境具备自动化回归能力,预发、生产支持常态化压测与生产流量回放 非 Web 应用,仅使用各个中间件的基础功能,升级出现不兼容的问题小 维护了 3 年,经历过多次重构,历史问题较少,几乎没有技术债务 针对以上特点,此应用很适合进行 JDK 17 升级。此应用基于 JDK 8,SpringBoot 2.0.8,除常见外部基础组件外,还使用以下公司内部中间件:UMP、SGM、DUCC、CDS、JMQ、JSF、R2M。 升级效果 可以先看下我们升级后压测的效果: 纯计算代码不再受 GC 影响 升级前 升级后 版本 吞吐量 平均耗时 最大耗时 JDK 8 G1 99.966% 35.7ms 120ms JDK 17 ZGC 99.999% 0.0254ms 0.106ms 升级后吞吐量几乎不受影响(甚至提升0.01%),GC 平均耗时下降1405 倍,GC 最大耗时下降1132 倍 升级步骤 升级 JDK 编译版本 首先自然是修改 maven 中指定的 JDK 版本,可以先升级到 JDK 11,同时修改 maven 编译插件 <java.version>11</java.version> <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version> <maven-source-plugin.version>3.2.1</maven-source-plugin.version> <maven-javadoc-plugin.version>3.3.2</maven-javadoc-plugin.version> <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin.version}</version> <configuration> <release>${java.version}</release> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> 引入缺少的依赖 然后就可以进行本地编译了,此时会暴露一些很简单的问题,比如找不到包、类等等。原因就是 JDK 11 移除了 Java EE and CORBA 的模块,需要手动引入。 <!-- JAVAX --> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.0.2</version> </dependency> 升级外部中间件 解决了编译找不到类的问题,接下来就该升级依赖的外部中间件了。对于我们的应用来说,也就是升级 SpringBoot 的版本。支持 JDK 17 的版本是 Spring 5.3,对应 SpringBoot 2.5。 在这里我建议升级至 SpringBoot 2.7,从 2.5 升级至 2.7 几乎没有需要改动的地方,同时高版本的 SprngBoot 所约定的依赖,对 JDK 17 的支持也更好。 建议进行大版本逐个升级,比如我们从 2.0 升级至 2.1。每升一个版本,就要仔细观察依赖版本的变化,掌握每个依赖升级的情况。SpringBoot 的升级其实意味着把所有开源组件约定版本进行大版本升级,接口弃用,破坏性兼容更新较多,需要一一鉴别。 下面以升级 Spring Boot 2.1 为例,说明我们升级的步骤: 首先阅读 Spring Boot 2.1 做了哪些和我们有关的配置改动 禁用了同 Bean 覆盖,开启需要指定spring.main.allow-bean-definition-overriding为true 然后阅读 Spring Boot 2.1 升级了哪些我们用到的依赖 Spring 升级至 5.1 首先阅读 Spring 5.1 做了哪些和我们有关的配置改动 无影响 然后阅读 Spring 5.1 升级了哪些我们用到的依赖 ASM 7.0 同理,阅读升级影响(这种底层依赖的底层依赖,如果仅 ASM 在使用,则无需关心) CGLIB 3.2 同理,阅读升级影响(这种底层依赖的底层依赖,如果仅 ASM 在使用,则无需关心) 最后阅读 Spring 5.1 弃用了哪些和我们有关的配置与依赖 无影响 Lombok 升级至 1.18 阅读改动影响,1.18 Lombok 默认情况下将不再生成私有无参构造函数。可以通过在lombok.config配置文件中设置lombok.noArgsConstructor.extraPrivate=true来启用它 Hibernate 升级至 5.3 阅读改动影响,对我们项目无影响 JUnit 升级至 5.2 阅读改动影响,需要 Surefire 插件升级至2.21.0及以上 最后阅读 Spring Boot 2.1 弃用了哪些和我们有关的配置与依赖 至此,Spring Boot 2.1 升级完毕。接下来分析一次依赖树变化,和升级前的依赖树进行比较,查看依赖变化范围是否全部已知可控。完成后进行 Spring Boot 2.2 的升级。 以下为我们需要注意的升级事项,仅供参考: 可以先升级到 JDK 11,一边启动一边验证。但不要在 JDK 11 使用 ZGC,ZGC 的堆预留与可用堆的比例太大,有时会导致OOM 代码中存在同 Bean,启动时 Springboot 2.0 会自动进行覆盖,高版本开启覆盖,需要指定spring.main.allow-bean-definition-overriding为true Spring Boot 2.2 默认的单元测试 Junit 升级至 5,Junit 4 的单元测试建议进行升级,改动不大 Spring Boot 2.4 不再支持 Junit 4 的单元测试,如果需要可以手动引入 Vintage 引擎 Spring Boot 2.4 配置文件处理逻辑变更,注意阅读更新日志 Spring Boot 2.6 默认禁用 Bean 循环依赖,可以通过将spring.main.allow-circular-references设置为true开启 Spring Boot 2.7 自动配置注册文件变更,spring.factories中的内容需要移动至META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件下 spring-boot-properties-migrator可以识别弃用的属性,可以考虑使用 Spring Framework 5.2 需要 Jackson 2.9.7+,注意阅读更新日志 Spring Framework 5.2 注解检索算法重构,所有自定义注释都必须使用@Retention(RetentionPolicy.RUNTIME)进行注释,以便 Spring 能够找到它们 Spring Framework 5.3 修改了很多东西,但都与我们的应用无关,请关注更新日志 ASM 仅单元测试 Mock 在使用,无需特殊关注,做好 JUnit 升级兼容即可 CGLIB 大版本升级以兼容字节码版本为主,关注好变更日志即可 Lombok 即使是小版本升级,也会有破坏性更新,需要仔细阅读每个版本的更新日志,建议少用 Lombok Hibernate 没有太大的破坏性更新,关注好变更日志即可 JUnit 升级主要关注大版本变更,如 4 升 5,小版本没有特别大的破坏性更新,并且是单元测试使用的依赖,可以放心升级或者不升级 Jackson 2.11,对java.util.Date和java.util.Calendar默认格式进行了更改,注意查看更新日志进行兼容 注意字节码增强相关依赖的升级 注意本地缓存升级 注意 Netty 升级,关注更新日志 升级内部中间件 内部中间件升级较为简单,主要是关注 JMQ、JSF 版本。其中 JSF 依赖的 Netty 和 Javassist 等都需要升级,Netty 版本较低会有内存泄漏问题。 我们使用的依赖版本 给大家参考下我们升级后的依赖版本 <properties> <!-- 基础组件版本 Start --> <java.version>17</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version> <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version> <jacoco-maven-plugin-version>0.8.10</jacoco-maven-plugin-version> <maven-assembly-plugin-version>2.4.1</maven-assembly-plugin-version> <maven-dependency-plugin-version>3.1.0</maven-dependency-plugin-version> <profiles.dir>src/main/profiles</profiles.dir> <springboot-version>2.7.13</springboot-version> <log4j2.version>2.18.0-jdsec.rc2</log4j2.version> <hibernate-validator.version>5.2.4.Final</hibernate-validator.version> <collections-version>3.2.2</collections-version> <collections4.version>4.4</collections4.version> <netty.old.version>3.9.0.Final</netty.old.version> <netty.version>4.1.36.Final</netty.version> <javassist-version>3.29.2-GA</javassist-version> <guava.version>23.0</guava.version> <mysql-connector-java.version>5.1.29</mysql-connector-java.version> <jmh-version>1.36</jmh-version> <caffeine-version>3.1.6</caffeine-version> <fastjson-version>1.2.83-jdsec.rc1</fastjson-version> <fastjson2-version>2.0.35</fastjson2-version> <roaringBitmap.version>0.9.44</roaringBitmap.version> <disruptor.version>3.4.4</disruptor.version> <jaxb-impl.version>2.3.8</jaxb-impl.version> <jaxb-core.version>2.3.0.1</jaxb-core.version> <activation.version>1.1.1</activation.version> <!-- 基础组件版本 End --> <!-- 京东中间件版本 Start --> <ump-version>20221231.1</ump-version> <ducc.version>1.0.20</ducc.version> <jdcds-driver-alg-version>2.21.1</jdcds-driver-alg-version> <jdcds-driver-version>3.8.3</jdcds-driver-version> <jmq.version>2.3.3-RC2</jmq.version> <jsf.version>1.7.6-HOTFIX-T2</jsf.version> <r2m.version>3.3.4</r2m.version> <!-- 京东中间件版本 End --> </properties> JVM 启动参数升级 远程 DEBUG 参数有所变化: JAVA_DEBUG_OPTS=" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000 " 打印 GC 日志参数的变化,我们在预发环境开启了日志进行观察: JAVA_GC_LOG_OPTS=" -Xlog:gc*:file=/export/logs/gc.log:time,tid,tags:filecount=10:filesize=10m " 使用了 ZGC 的部分 JVM 参数: JAVA_MEM_OPTS=" -server -Xmx12g -Xms12g -XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m -XX:MaxDirectMemorySize=2048m -XX:+UseZGC -XX:ZAllocationSpikeTolerance=3 -XX:ParallelGCThreads=8 -XX:CICompilerCount=3 -XX:-RestrictContended -XX:+AlwaysPreTouch -XX:+ExplicitGCInvokesConcurrent -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/logs " 内部依赖需要访问 JDK 模块,如 UMP、JSF、虫洞、MyBatis、DUCC、R2M、SGM: if [[ "$JAVA_VERSION" -ge 11 ]]; then SGM_OPTS="${SGM_OPTS} --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens java.management/sun.management=ALL-UNNAMED --add-opens java.management/java.lang.management=ALL-UNNAMED " UMP_OPT=" --add-opens java.base/sun.net.util=ALL-UNNAMED " JSF_OPTS=" --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED" WORMHOLE_OPT=" --add-opens java.base/sun.security.action=ALL-UNNAMED " MB_OPTS=" --add-opens java.base/java.lang=ALL-UNNAMED " DUC_OPT=" --add-opens java.base/java.net=ALL-UNNAMED " R2M_OPT=" --add-opens java.base/java.time=ALL-UNNAMED " fi 启动后完整的启动参数如下: -javaagent:/export/package/sgm-probe-java/sgm-probe-5.9.5-product/sgm-agent-5.9.5.jar -Dsgm.server.address=http://sgm.jdfin.local -Dsgm.app.name=market-reduction-center -Dsgm.agent.sink.http.connection.requestTimeout=2000 -Dsgm.agent.sink.http.connection.connectTimeout=2000 -Dsgm.agent.sink.http.minAlive=1 -Dsgm.agent.virgo.address=10.24.216.198:8999,10.223.182.52:8999,10.25.217.95:8999 -Dsgm.agent.zone=m6 -Dsgm.agent.group=m6-discount -Dsgm.agent.tenant=jdjr -Dsgm.deployment.platform=jdt-jdos --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED --add-opens=java.management/java.lang.management=ALL-UNNAMED -DJDOS_DATACENTER=JXQ -Ddeploy.app.name=jdos_kj_market-reduction-center -Ddeploy.app.id=30005051 -Ddeploy.instance.id=0 -Ddeploy.instance.name=server -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Djava.util.Arrays.useLegacyMergeSort=true -Dog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector -Dlog4j2.AsyncQueueFullPolicy=Discard -Xmx12g -Xms12g -XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m -XX:MaxDirectMemorySize=2048m -XX:+UseZGC -XX:ZAllocationSpikeTolerance=3 -XX:ParallelGCThreads=8 -XX:CICompilerCount=3 -XX:-RestrictContended -XX:+AlwaysPreTouch -XX:+ExplicitGCInvokesConcurrent -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/logs --add-opens=java.base/sun.net.util=ALL-UNNAMED --add-opens=java.base/sun.util.calendar=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.math=ALL-UNNAMED --add-opens=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -Dloader.path=/export/package/jdos_kj_market-reduction-center/conf 系统验证 系统可以成功启动后,就可以进行功能验证。有几个验证重点与方法: 首先可以通过单元测试快速进行系统全面回归,避免出现 JDK API、中间件 API 变更导致的业务异常 部署到测试环境,验证各个中间件是否正常,如 DUCC 开关下发,MQ 收发,JSF 接口调用等等,系统中所有用到的中间件都需要一一验证 然后可以开始进行核心业务的验证,这时候可以利用测试同学的测试自动化能力加人工补充场景,快速进行核心业务回归。其中研发需要观察系统被调用时的所有异常日志,包括警告,明确每条日志产生的原因 验证完成后,可以部署到联调环境,利用外部同事联调时的请求进一步进行验证 充分在测试环境观察后,部署至预发环境,利用外部同事联调时的请求进一步进行验证,并进行常态化压测,验证优化效果与瓶颈 经过预发长时间验证,没有问题后,部署一台生产,通过回放生产流量进一步进行验证 回放流量无异常后,开始承接生产流量,按接口开量,进行若干周的观察 逐步切量,直到全量上线 GC 调优 ZGC 介绍 如图所示,ZGC 的定位是一个最大暂停时间小于 1ms,且能够处理大小从 8MB 到 16TB 的堆,并且易于调优的垃圾回收器。ZGC 只有三个 STW 阶段,具体流程网上有大量类似文章,这里不做详细介绍。 优化方向 目前我们的应用日常使用 G1 约 30ms 的 GC 停顿时间,不到 1 分钟就会触发一次,大促时频率更高,暂停时间更长,导致接口性能波动较大。随着业务发展,为了优化系统我们大量应用了本地缓存,导致存活对象较多。ZGC 暂停时间不随堆、活动集或根集大小而增加,且极低的 GC 时间正是我们需要的特性,因此决定使用 ZGC。 ZGC 作为一个现代化 GC,没有必要做过多的优化,默认配置已经可以解决 99.9% 的场景。但是我们的应用会承接大促流量,根据观察,瞬时流量激增时 GC 时机较晚,因此应对突发流量是我们 ZGC 调优的一个目标,其他属性不做任何调整。 优化措施 ZGC 的一个优化措施就是足够大的堆,一般来说,给 ZGC 的内存越多越好,但我们也没必要浪费,通过压测观察 GC 日志,取得一个合适的值即可。我们只要保证: 堆可以容纳应用程序产生的实时垃圾 堆中有足够的空间,以便在 GC 运行时,为新的垃圾分配提供空间 因此,我们将机器升级成 8C 16G 配置,观察 GC 日志根据应用情况调整内存占用配置,最终设定为-Xmx12g -Xms12g -XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m -XX:MaxDirectMemorySize=2048m,提升 ZGC 的效果。 剩下的其他优化措施则视情况而定,可以调整触发 GC 的时机,也可以改为基于固定时间间隔触发 GC。 我们略微提升了触发时机,-XX:ZAllocationSpikeTolerance=3(默认为 2)应对突发流量。 CICompilerCount ParallelGCThreads一个是提升 JIT 编译速度,一个是垃圾收集器并行阶段使用的线程数,根据实际情况略微增加,牺牲一点点 CPU 使用率,提升下效率。 另外还可以开启Large Pages进一步提升性能。这一步我们没有做,因为现在部署方式为一台物理机 Docker 混部署。开启需要修改内核,影响宿主机的其他镜像。 总结 至此,调优完成,目前我们已在线上跑了一个多月,每周都有三次常态化压测,一切正常。 以上升级心得分享给大家,希望对各位有所帮助。 作者:京东科技 张天赐 来源:京东云开发者社区

优秀的个人博客,低调大师

豆包MarsCode 初体验,用React创建一个最经典的贪吃蛇游戏

以下是「 豆包MarsCode 体验官」优秀文章,作者Find。 背景 在人工智能快速发展的时代,大模型(LLM)只要有足够的算力和数据就可以做到任何的事情,甚至可以模拟出另一个地球。LLM作为一个革命化的科技,可以取代很多岗位,甚至可以让人类达到"躺着领钱的时代"。Marscode作为一个新推出的IDE,紧跟时代的潮流,拥抱AI,顺应时代的潮流。 各种各样的语言,都会有一个贪吃蛇的游戏去作为一个里程碑,我也完完全全用这个IDE去编写贪吃蛇游戏这个小demo。 创建项目 在这个IDE创建一个贪吃蛇的项目 生成了一个这样的项目目录 因为我还没学ts,就再用npm init vite去初始化了一个JavaScript的React项目 输完项目名称,cd project-name和npm i切换到你的项目目录和下载依赖就完成了vite脚手架的安装 npm run dev把项目跑起来 很方便直接能网页预览 设计项目 提出自己的需求给AI assistant 设计这个游戏的组件 但是它好像不是很能理解上下文,我这两句话是放在一起写的。只能再给出一份更详细的设计要求 给出了四份组件,那就去项目中创建文件。 创建好对应的目录和文件。询问AI代码 以下过程一样,找AI去生成一份相关组件的代码 当然因为我是分开要的组件代码,所以就产生了一些错误。看到还有AI fix的功能 我果断尝试了 这里AI fix 左右两边分别表示之前的代码和修改后的代码,你可以去看它修改后的代码是否正确去选择是否接受 对于这个问题,AI fix似乎跟个人机一样,根本解决不了 最大的感受就是这个AI fix感觉就是给你改错,然后用错去改你的错,有一点拆东墙补西墙的感觉,毕竟还是AI,可能并不能很好去理解bug本质所在,只会通过bug存在的原因去机械的给你改bug。 解决完所有的bug 代码也是成功跑不出来 有些涉及AI生成的代码中有些属性并未被初始化等等问题,改来改去最后还是把页面跑出来了 代码 GameBoard.jsx import React, { useState, useEffect, useRef } from 'react'; import Snake from './Snake'; import Food from './Food'; import Score from './Score'; const GameBoard = () => { const [snakeBody, setSnakeBody] = useState([]); const [food, setFood] = useState({}); const [direction, setDirection] = useState('right'); const [isGameOver, setIsGameOver] = useState(false); const [score, setScore] = useState(0); const gameBoardRef = useRef(null); useEffect(() => { gameBoardRef.current.focus(); initializeGame(); }, []); useEffect(() => { if (snakeBody.length) { const timer = setInterval(moveSnake, 300); return () => clearInterval(timer); } }, [snakeBody, direction]); const initializeGame = () => { const initialSnake = [{ x: 200, y: 200 }]; setSnakeBody(initialSnake); setFood(getRandomPosition()); setDirection('right'); setIsGameOver(false); setScore(0); }; const getRandomPosition = () => { const x = Math.floor(Math.random() * 20) * 20; const y = Math.floor(Math.random() * 20) * 20; return { x, y }; }; const moveSnake = () => { let newHead; switch (direction) { case 'right': newHead = { x: snakeBody[0].x + 20, y: snakeBody[0].y }; break; case 'left': newHead = { x: snakeBody[0].x - 20, y: snakeBody[0].y }; break; case 'up': newHead = { x: snakeBody[0].x, y: snakeBody[0].y - 20 }; break; case 'down': newHead = { x: snakeBody[0].x, y: snakeBody[0].y + 20 }; break; default: return; } if (isCollisionWithWall(newHead) || isCollisionWithSelf(newHead)) { setIsGameOver(true); return; } const newSnakeBody = [newHead, ...snakeBody.slice(0, -1)]; if (isCollisionWithFood(newHead)) { setFood(getRandomPosition()); setScore(score + 1); newSnakeBody.push({}); } setSnakeBody(newSnakeBody); }; const isCollisionWithWall = (head) => { return head.x < 0 || head.x >= 400 || head.y < 0 || head.y >= 400; }; const isCollisionWithSelf = (head) => { return snakeBody.some((segment) => segment.x === head.x && segment.y === head.y); }; const isCollisionWithFood = (head) => { return head.x === food.x && head.y === food.y; }; const handleKeyDown = (event) => { if (event.keyCode === 37 && direction !== 'right') { setDirection('left'); } else if (event.keyCode === 38 && direction !== 'down') { setDirection('up'); } else if (event.keyCode === 39 && direction !== 'left') { setDirection('right'); } else if (event.keyCode === 40 && direction !== 'up') { setDirection('down'); } }; return ( <div> <div ref={gameBoardRef} tabIndex="0" onKeyDown={handleKeyDown} className="game-board" > {!isGameOver && <Snake snakeBody={snakeBody} />} {!isGameOver && <Food position={food} />} </div> <Score score={score} /> {isGameOver && <div>Game Over!</div>} </div> ); }; export default GameBoard; App.jsx和App.css import React from 'react'; import GameBoard from './components/GameBoard'; import './App.css'; function App() { return ( <div className="app"> <h1>贪吃蛇游戏</h1> <GameBoard /> </div> ); } export default App; .app { text-align: center; } ------------------css------------------ .game-container { display: flex; justify-content: center; align-items: flex-start; } .game-board { position: relative; width: 400px; height: 400px; border: 1px solid black; overflow: hidden; margin-right: 20px; } .score-board { margin-top: 20px; } Snake.jsx import React from 'react'; const Snake = ({ snakeBody }) => { return ( <> {snakeBody.map((segment, index) => ( <div key={index} style={{ width: '20px', height: '20px<p align=center>',</p> backgroundColor: index === 0 ? 'green' : 'black', position: 'absolute', left: `${segment.x}px`, top: `${segment.y}px`, }} /> ))} </> ); }; export default Snake; Food.jsx import React from 'react'; const Food = ({ position }) => { return ( <div style={{ width: '20px', height: '20px', backgroundColor: 'red', position: 'absolute', left: `${position.x}px`, top: `${position.y}px`, }} /> ); }; export default Food; Score.jsx import React from 'react'; const Score = ({ score }) => { return <div className="score-board">Score: {score}</div>; }; export default Score; 个人感受 做个这个小项目后,最大的感受就是如果你掌握了代码底层逻辑、编程思想等,通过这类有智能化AI的IDE一键生成会让你代码速度提升一个层次。可能有些人觉得AI只能搭建起一个简单的框架,事实上,在你搭建好了框架后,你还可以去按照你的需求去逐步完善你的项目,就拿这个贪吃蛇举例而言,如果我后续需要提升难度,根据我吃球后速度提升、设置障碍物等等,我都可以单独拎出这些需求告诉AI助手,就算它并不能完全帮你写完代码,但也能八九不离。CSS样式、页面切片这种,通过tailwindcss框架和语义化标签,AI已经有理解图像的能力,可以很好完成此类工作。当然随着大模型数据不断的喂养,慢慢等到资本的介入,算力不断提高,AI理解能力的提高,也会使这种AI IDE越来越厉害,也许真有一天普通人掌握编程思想不会编程语言也能编程。豆包Marscode正是有这种强大的功能的IDE,我相信豆包Marscode 会越来越好!!! 本文由博客一文多发平台 OpenWrite 发布!

优秀的个人博客,低调大师

JDK17+ZGC初体验|得物技术

1 前言 垃圾回收器的暂停问题一直是Java工程师关注的重点,特别是对实时响应要求较高的服务来说,CMS和G1等主流垃圾回收器的数十毫秒乃至上百毫秒的暂停时间相当致命。此外,调优门槛也相对较高,需要对垃圾回收器的内部机制有一定的了解,才能够进行有效的调优。 为了解决此类问题,JDK 11开始推出了一种低延迟垃圾回收器ZGC。ZGC使用了一些新技术和优化算法,可以将GC暂停时间控制在10毫秒以内,而在JDK 17的加持下,ZGC的暂停时间甚至可以控制在亚毫秒级别! 2 ZGC ZGC相关介绍、原理,网上已经有很多类似文章,这里只做简单介绍。 2.1 设计目标 ZGC 最初在 JDK 11 中作为实验性功能引入,并在 JDK 15 中宣布为生产就绪。作为一款低延迟垃圾收集器,旨在满足以下目标: 8MB到16TB的堆大小支持 10ms最大GC暂时 最糟糕的情况下吞吐量会降低15%(低延时换吞吐量很值,吞吐量扩容即可解决) 2.2 ZGC 内存分布 ZGC与传统的CMS、G1不同、它没有分代的概念,只有类似G1的Region概率,ZGC 的 Region可以具有如下图所示的大中下三类容量: 小型 Region(Small Region):容量固定为2MB,用于放置小于 256KB的小对象。 中型 Region(Medium Region):容量固定为 32MB,用于放置大于 256KB但是小于 4MB的对象。 大型 Region(Large Region):容量不固定,可以动态变化,但必须为 2MB的整数倍,用于放置 4MB或以上的大对象。每个大型 Region中会存放一个大对象,这也预示着虽然名字叫“大型 Region”,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。大型 Region在ZGC的实现中是不会被重分配的(重分配是ZGC的一种处理动作,用于复制对象的收集器阶段)因为复制大对象的代价非常高。 2.3 GC工作过程 与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC通过着色指针和读屏障技术,解决了转移过程中准确访问对象的问题,在标记、转移和重定位阶段几乎都是并发执行的,这是ZGC实现停顿时间小于10ms目标的最关键原因。 从上图中可以看出,ZGC只有三个STW阶段:初始标记,再标记,初始转移。 具体转移过程,网上有大量类似文章,这里不做详细介绍,大家有兴趣可以参考以下文章: 新一代垃圾回收器ZGC的探索与实践 ZGC 最新一代垃圾回收器 | 程序员进阶 3 为什么选择JDK17呢? JDK 17于9月14日发布,是一个长期支持(LTS)版本,这意味着它将在很多年内得到支持和更新。这也是第一个LTS版本,其中包含了一个可用于生产环境的ZGC版本。回顾一下,ZGC的实验版本已经包含在JDK 11(之前的LTS版本)中,而第一个可用于生产环境的ZGC版本出现在JDK 15(一个非LTS版本)中。 4 升级过程 从JDK8+G1升级到JDK17+ZGC,主要是在代码层面和JVM启动参数层面的做适配。 4.1 JDK下载 首先jdk17选择的是openjdk,下载地址:https://jdk.java.net/archive/,选择版本17 GA 4.2 代码适配 JDK11移除了 Java EE and CORBA 的模块 项目中如果用到javax.annotation.*、javax.xml.*等等开头的包,需要手动引入对应依赖 <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> </dependency> maven相关依赖版本升级 <!-- 仅供参考 --> <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version> <maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version> <maven-resources-plugin.version>3.2.0</maven-resources-plugin.version> <maven-jar-plugin.version>3.2.0</maven-jar-plugin.version> <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> <maven-deploy-plugin.version>3.0.0-M1</maven-deploy-plugin.version> <maven-release-plugin.version>3.0.0-M1</maven-release-plugin.version> <maven-site-plugin.version>3.9.1</maven-site-plugin.version> <maven-enforcer-plugin.version>3.0.0-M2</maven-enforcer-plugin.version> <maven-project-info-reports-plugin.version>3.1.0</maven-project-info-reports-plugin.version> <maven-plugin-plugin.version>3.6.1</maven-plugin-plugin.version> <maven-javadoc-plugin.version>3.3.0</maven-javadoc-plugin.version> <maven-source-plugin.version>3.2.1</maven-source-plugin.version> <maven-jxr-plugin.version>3.0.0</maven-jxr-plugin.version> Lombok版本升级https://projectlombok.org/changelog <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <!-- <version>1.16.20</version>--> <version>1.18.22</version> </dependency> Java9 模块化后,不允许应用程序查看来自JDK的所有类,会影响部分反射的运行,需要通过以下命令解决 --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED 本地使用了transmittable-thread-local-2.14.2.jar后启动报错 在agent后面加上日志输出即可解决,至于原因,猜测是跟类加载顺序有关系 -javaagent:/Users/admin/Documents/transmittable-thread-local-2.14.2.jar =ttl.agent.logger:STDOUT 以上内容仅针对彩虹桥项目升级遇到的问题,不同的业务代码适配的情况可能不一样,需要根据实际情况寻找解决方案。 4.3 JVM参数替换 下面是一些通用GC参数和ZGC特有参数以及ZGC的一些诊断选型,来自官网:Main - Main - OpenJDK Wiki 具体每个参数的含义,这里不做介绍,可参考官网文档The java Command,里面有详细说明。 JKD8+G1的启动参数: -server -Xms36600m -Xmx36600m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintReferenceGC -XX:+ParallelRefProcEnabled -XX:G1HeapRegionSize=16m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/apps/errorDump.hprof -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintGCApplicationConcurrentTime -verbose:gc -Xloggc:/opt/apps/logs/${app_name}-gc.log JDK17+ZGC的启动参数如下: -server -Xms36600m -Xmx36600m #开启ZGC -XX:+UseZGC #GC周期之间的最大间隔(单位秒) -XX:ZCollectionInterval=120 #官方的解释是 ZGC 的分配尖峰容忍度,数值越大越早触发GC -XX:ZAllocationSpikeTolerance=4 #关闭主动GC周期,在主动回收模式下,ZGC 会在系统空闲时自动执行垃圾回收,以减少垃圾回收在应用程序忙碌时所造成的影响。如果未指定此参数(默认情况),ZGC 会在需要时(即堆内存不足以满足分配请求时)执行垃圾回收。 -XX:-ZProactive #GC日志 -Xlog:safepoint=trace,classhisto*=trace,age*=info,gc*=info:file=/opt/logs/gc-%t.log:time,level,tid,tags:filesize=50M #发生OOM时dump内存日志 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/apps/errorDump.hprof 5 压测结果 直接上图 正如 ZGC 设计目标所描述,它将 GC 暂停时间从过去的几十毫秒降低到了令人惊叹的亚毫秒级别。然而,这种超低延迟表现也需要一定的代价,因为在实现低延迟的同时,ZGC 会占用一定的 CPU 资源。通常情况下,ZGC 占用的 CPU 比例不会超过 15%。在彩虹桥项目中,使用以上推荐的 JVM 参数后,ZGC 占用的 CPU 资源为 6% 左右。 6 ZGC日志 6.1 输出ZGC日志 GC日志中包含有关 GC 操作的详细信息,可以帮我们分析当前GC存在的问题。先来看一下上面JVM参数中关于GC日志的参数 -Xlog:safepoint=trace,classhisto*=trace,age*=info,gc*=info:file=/opt/logs/gc-%t.log:time,level,tid,tags:filesize=50M safepoint=trace:记录关于 safepoint 的 trace 级别日志。 Safepoint 是 JVM 中一个特殊的状态,它用于确保所有线程在特定操作(如垃圾回收、代码优化等)之前进入安全状态。 classhisto*=trace:记录与类的历史相关的 trace 级别日志。 age*=info:记录与对象年龄(在新生代中存在的时间)相关的 info 级别日志。 gc*=info:记录与垃圾回收相关的 info 级别日志。 file=/opt/logs/gc-%t.log:将日志写入到 /opt/logs/ 目录下的文件中,文件名为 gc-%t.log,其中 %t 是一个占位符,表示当前时间戳。 time,level,tid,tags:在每个日志记录中包含时间戳、日志级别、线程 ID 和标签。 filesize=50M:设置日志文件的大小限制为 50MB。当日志文件大小达到此限制时,JVM 将创建一个新的日志文件并继续记录。 更详细的gc日志配置可以参考:https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#enable-logging-with-the-jvm-unified-logging-framework 6.2 STW关键日志 其中我们重点关注的就是GC的STW情况,以下是一些关键字代表GC STW阶段 最基本的STW三阶段,初始标记:日志中Pause Mark Start,再标记:日志中Pause Mark End,初始转移:日志中Pause Relocate Start。 内存分配阻塞:这一般是因为垃圾生产速度大于回收速度,垃圾来不及回收,垃圾将堆占满时,线程会阻塞等待GC完成,关键字是Allocation Stall(被阻塞的线程名称) 如果出现此类日志,可以尝试如下方法解决: -XX:ZCollectionInterval 该配置含义:两个 GC 周期之间的最大间隔(单位秒)。默认情况下,此选项设置为 0(禁用),可以适当调小该配置,让GC周期缩短、提升垃圾回收速度,但这会提升应用CPU占用。 -XX:ZAllocationSpikeTolerance官方的解释是 ZGC 的分配尖峰容忍度。其实就是数值越大,越早触发回收。可以适当调大该配置,更早触发回收,提升垃圾回收速度,但这会提升应用CPU占用。 安全点:所有线程进入到安全点后才能进行GC,ZGC定期进入安全点判断是否需要GC。先进入安全点的线程需要等待后进入安全点的线程直到所有线程挂起。日志关键字safepoint ... stopped dump线程、内存:比如jstack、jmap命令,一般是手动dump导致,日志关键字HeapDumper 7 Linux大页内存 在openjdk的官网上也能看到,开启Linux大页内存后会提升应用的性能。 开启方式见官网文档https://wiki.openjdk.org/display/zgc/Main#Main-EnablingLargePagesOnLinux,注意除了修改系统配置外,还需要在进程JVM启动参数中新增-XX:+UseLargePages配置 经过几轮压测实际测试下来,发现在开启Linux大页后,CPU有8%左右的下降,但是由于大页面会提前预留指定大小的内存,会导致机器的内存使用率较高。而且目前生产环境没有其他应用开启此配置,稳定性有待考究,生产环境自行评估是否开启。 8 总结 在本篇文章中,我们探讨了如何升级到JDK 17,并使用最新一代垃圾回收器ZGC。经过实践和测试,我们发现升级后的系统在垃圾回收方面表现出色,暂停时间被有效控制在1毫秒内。尽管这一优化过程可能会消耗额外的CPU资源,但所获得的超低GC暂停时间显然是非常值得的。总之,相比其他垃圾回收器,ZGC 的性能和稳定性已经非常优秀,而且不需要太多的调优。在大多数情况下,使用 ZGC官方推荐的默认设置即可获得优秀的性能表现。对于那些RT敏感型应用,升级到JDK 17并采用ZGC是一个明智的选择。 文: 新一 本文属得物技术原创,来源于:得物技术官网 未经得物技术许可严禁转载,否则依法追究法律责任!

优秀的个人博客,低调大师

从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十六 ║ Vue前篇:ES6初体验 & 模块化编程

缘起 昨天说到了《[从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十五 ║ Vue前篇:JS对象&字面量&this](https://www.cnblogs.com/laozhang-is-phi/p/9580807.html)》,通过总体来看,好像大家对这一块不是很感兴趣,嗯~~这一块确实挺枯燥的,不能直接拿来代码跑一下那种,不过还是得说下去,继续加油吧!如果大家对昨天的小demo练习的话,相信现在已经对JS的面向对象写法很熟悉了,如果嵌套字面量定义函数,如何使用this关键字指向。今天呢,主要说一下ES6中的一些特性技巧,然后简单说一下模块化的问题,好啦,开始今天的讲解~ 还是老规矩,一言不合就是上代码 str1 = 'Hello JS!'; function fun1() { var str1 = 'Hello C#!'; } fun1(); alert(str1); 大家猜猜,最后会弹出来哪一句话? 零、今天要完成浅紫色的部分 一、什么是传说中的ES6 这些定义网上一大堆,不过还是粘出来,大家可以统一看一下,简单了解了解: 1、定义 ECMAScript 6是JavaScript语言的下一代标准,在2015年6月正式发布。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。 标准的制定者有计划,以后每年发布一次标准,使用年份作为标准的版本。因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015。也就是说,ES6就是ES2015,下一年应该会发布小幅修订的ES2016。 2、有哪些新的变化 编程语言JavaScript是ECMAScript的实现和扩展,由ECMA(一个类似W3C的标准组织)参与进行标准化。ECMAScript定义了: 语言语法– 语法解析规则、关键字、语句、声明、运算符等。 类型– 布尔型、数字、字符串、对象等。 原型和继承 内建对象和函数的标准库–JSON、Math、数组方法、对象自省方法等。 ECMAScript标准不定义HTML或CSS的相关功能,也不定义类似DOM(文档对象模型)的Web API,这些都在独立的标准中进行定义。ECMAScript涵盖了各种环境中JS的使用场景,无论是浏览器环境还是类似node.js的非浏览器环境。 3、ECMAScript和JavaScript的关系 1996年11月,JavaScript的创造者Netscape公司,决定将JavaScript提交给国际标准化组织ECMA,希望这种语言能够成为国际标准。次年,ECMA发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为ECMAScript,这个版本就是1.0版。 该标准从一开始就是针对JavaScript语言制定的,但是之所以不叫JavaScript,有两个原因。一是商标,Java是Sun公司的商标,根据授权协议,只有Netscape公司可以合法地使用JavaScript这个名字,且JavaScript本身也已经被Netscape公司注册为商标。二是想体现这门语言的制定者是ECMA,不是Netscape,这样有利于保证这门语言的开放性和中立性。 因此,ECMAScript和JavaScript的关系是,前者是后者的规格,后者是前者的一种实现 二、var、let 与 const 块作用域 这里先说下,作用域的问题 1、ES6之前,JavaScript 并没有块级作用域,所谓的块,就是大括号里面的语句所组成的代码块,比如 function blog(bl) { if (bl) { var foo = "Blog"; } console.log(foo); } blog(true); //=> Blog 2、虽然变量变量foo位于if语句的代码块中,但是 JavaScript 并没有块级作用域的概念,因此被添加到了当前的执行环境 - 即函数中,在函数内都可以访问到。 因此:var 定义的变量是函数级作用域,作用范围是在函数开始阶段和函数执行完成之前内都是存在的; 并且如果该函数内部还存在匿名函数等特殊函数,这个 var 出的变量在匿名函数中任然可以用; 3、在ES出现后,定义了一个新的命名方式 let function Blog(bool) { if (bool) { let foo = "Blog"; } else { console.log(foo); } } Blog(false); //这里会报错 Uncaught ReferenceError: foo is not defined 因此,使用 let,上述问题完全解决,let出的变量作用域是块作用域,在离开某一代码块,该变量就会被销毁不存在 应当尽可能的避免用 var,用 let 来代替,除非你需要用到变量提升。 4、随着面向对象思维的出现,JS也出现了常量的定义 const const 与 let 的基本用法相同,定义的变量都具有块级作用域,也不会发生变量提升。不同的地方在于,const 定义的变量,只能赋值一次。 const foo='Blog'; function Blog(bool) { if (bool) { foo = "Vue"; } else { console.log(foo); } } Blog(true); //这里会报错 Identifier 'foo' has already been declared 因此const多用作不发生变化的变量定义,比如定义月份,或者,星期等:constmonths = []; 三、箭头函数 还记得昨天的那个小demo么,今天再说一个地方 var obj = { data: { books: "", price: 0, bookObj: null }, bind() {//**注意!**ES6 中,可以使用这种方法简写函数,等价于 bind: function () { var that = this; //普通函数 //$(".ok").click(function () { // console.log(this);//这个时候,this,就是 .ok 这个Html标签 // var bookItem = that.data.bookObj; // var _parice = $(bookItem).data("price"); // var _book = $(bookItem).data("book"); // that.data.books += _book + ","; // that.data.price += parseInt(_parice); // that.show(); //}); //箭头函数 $(".ok").click(() => { var bookItem = this.data.bookObj;//在箭头函数中,this指向的是定义函数时所在的对象 var _parice = $(bookItem).data("price"); var _book = $(bookItem).data("book"); this.data.books += _book + ","; this.data.price += parseInt(_parice); this.show(); $(".bg,.popupbox").hide(); }); }, } 在普通的click函数中 this 指向对象 $(".ok") ,因此,我们如果想要获取定义的对象中的数据(obj.data),那我们只能在 click 方法前,就去用一个 that 自定义变量来保存这个 this , 但是在箭头函数中就不一样了,this始终指向定义函数时所在的对象(就是 obj 对象); 是不是更方便些! 在Vue中,也经常使用 vue实例,或者this来获取相应的值 var vm = new Vue({ el:'#root', data:{ tasks:[] }, mounted(){ axios.get('/tasks') .then(function (response) { vm.tasks = response.data;//使用Vue实例 }) }, mounted2(){ axios.get('/tasks') .then(response => this.tasks = response.data);//箭头函数 this } }); 四、参数默认值 && rest参数 1、 在ES6中,可以像C#那样定义默认参数 function buyBook(price, count = 0.9){ return price * count; } buyBook(100); //甚至可以将方法的值赋给参数 function buyBook(price, count =GetCount()){ return price * count; } function GetCount(){ return 100; } buyBook(200); 2、不仅如此,还可以快速获取参数值 //ES6之前是这样的 function add(a,b,c){ let total = a + b + c; return total; } add(1, 2, 3); //ES6你可以这么操作,提供了 rest 参数来访问多余变量 function sum(...num) { let total = 0; for (let i = 0; i < num.length; i++) { total = total + num[i]; } return total; } sum(1, 2, 3, 4, 6); 五、ES6中的表达式 1、字符串表达式 在之前我们都是这样使用字符串表达式 var name = 'id is ' + bid+ ' ' + aid + '.' var url = 'http://localhost:5000/api/values/' + id 在ES6中我们有了新语法,在反引号包裹的字符串中,使用${NAME}语法来表示模板字符: var name = `id is ${aid} ${bid}` var url = `http://localhost:5000/api/values/${id}`//注意是反引号,英文输入下下的,Tab键上边的那个 2、还有就是多行表达式的写法 //之前我们都是这么写的 var roadPoem = '这个是一个段落' + '换了一行' + '增加了些内容' + 'dddddddddd' //但是在ES6中,可以使用反引号 var roadPoem = `这个是一个段落 换了一行 增加了些内容, dddddddddd 结尾,` 六、模块化定义 1、什么是模块化开发 模块化开发是基于一定的语法规范,通过代码书写设计,使代码耦合度降低,模块化的意义在于最大化的设计重用,以最少的模块、零部件,更快速的满足更多的个性化需求。因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。 用阮一峰大神的说法就是: 今天的Web网页越来越像桌面程序,网页上加载的javascript也越来越复杂,前端工程师不得不开始用软件工程的思维去管理自己的代码。Javascript模块化编程,已经成为一个非常迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。但是,Javascript不是一种模块化编程语言,它不支持"类"(class),更别提"模块"(module)了。(正在制定中的ECMAScript标准第六版将正式支持"类"和"模块",但还需要很长时间才能投入实用 就这样,Node.js 就出现了,一个用来开发服务器端的js框架,基于commonJs的模块化。当然中间还有CMD,AMD(这个东西我还需要慢慢研究下); 2、模块化在代码中是如何体现的呢 1、首先我们先看看普通的定义一个类是如何写的 新建一个index.js 文件 class Student { constructor(homework= []) { this.homework= homework; } study() { console.log(this.homework); } } const st = new Student ([ 'blog', 'api', 'vue' ]); st.study(); 然后新建一个index.html页面,引用该js文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script src="index.js"></script> </body> </html> 然后就可以得到结果: 这是一个很简单的,定义一个Student 类,然后定义一个方法,通过传递一个数组参数,来实例化。 这样虽然很简单,但是却无法复用,无法作为一个零件来使用。而且如果有一个地方要修改,多处都需要修改,这个面向对象的思想,没有发挥出来; 这个时候你可能会说,把这个拆成两个问题,就可以复用了,嗯~试试 2、我们把这两个文件分开 新建一个Student.js ,定义处理Student类;然后新建一个main.js方法,来调用实例化该类,就可以使用 然后在 index.html 页面里去引用这两个文件 <body> <script src="Student.js"></script> <script src="main.js"></script> </body> 当然结果是一样的,这样虽然实现了分隔开,也可以去不同的地方调用; 但是,从上文中你也看的出,如果不是自己写的代码,一般不太容易看的明白,到底是什么意思,直观性不是很好,我们将无法看到彼此间的关联(main.js 加载 Student.js), 3、我们用模块的写法设计这个调用 ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。 我们直接修改之前的代码 然后在 index.html 页面中,只需要引用 就行 4、因为浏览器现在还不能直接运行模块化代码,所以我们需要打包,打包工具有很多,比如webpack 注意:这里用到打包概念,之后会讲到,这里就先略过,以后会讲到,步骤是 首先安装npm,或者阿里镜像cnpm(npm其实是Node.js的包管理工具,这个在我们之后的Node.js环境配置中,自动随带安装)全局安装 rollup.js $ cnpm install --global rollup cd 当前文件夹$ rollup main.js --format iife --output bundle.js 然后只需要引用生成的 5、这里我因为测试,已经生成好了,打包出来的bundle.js 是这样的,是不是兜兜转转又回到了之前的写法,其实ES6的模块开发,就是导入的代码块儿 (function () { 'use strict'; class TaskCollection { constructor(tasks = []) { this.tasks = tasks; } dump() { console.log(this.tasks); } } const tc = new TaskCollection([ 'blog', 'api', 'vue' ]); tc.dump(); }()); 总结来说:模块化的好处和问题 可维护性 灵活架构,焦点分离 方便模块间组合、分解 方便单个模块功能调试、升级 多人协作互不干扰 可测试性,可分单元测试; 性能损耗 系统分层,调用链会很长 模块间通信,模块间发送消息会很耗性能 其实说白了,就是JS在作为一个开发语言来说,越来越靠近了后端服务器语言。 七、每天一个小Demo 这里是一个特别特别简单的关于ES6的留言板,大家可以看一看 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>简易留言板</h2> <input type="text" placeholder="请输入内容" size="30" id="msg"> <input type="button" value="留言" id="btn"> <div id="msg-div"></div> <script> //let 定义块级变量 let oBtn = document.getElementById('btn'); let msg = document.getElementById('msg'); let content = document.getElementById('msg-div'); oBtn.onclick = () => { let ovalue = msg.value; let ali = document.createElement('p'); //ES6模板字符串 //多行表达式 ali.innerHTML = `${ovalue}<span style="color:red;"> 删除</span>`; var aspan = content.getElementsByTagName('p'); if (aspan.length > 0) { content.insertBefore(ali, aspan[0]); } else { content.appendChild(ali); } msg.value = ''; var oSpan = content.getElementsByTagName('span'); for (let i = 0; i < oSpan.length; i++) { //ES6箭头函数 oSpan[i].onclick = function () { content.removeChild(this.parentNode);//注意this的指向 }; } }; </script> </body> </html> 八、结语 通过这两天的学习,大家了解到了,JS的一些特性和变化:嵌套字面量的定义,面向对象的封装,类和模块化的使用,ES6的日益成熟,通过打包进行发布等等,都能表现出JS在向一个服务器端语言快速迈进的冲动,也是极大的推动了,MVVM的到来,从而实现像Node.js 这种,可以脱离浏览器环境也能运行的不一样视角。好啦,关于JS高阶,这两讲已经差不多了,当然还有其他的,大家可以自行学习了解,其实这两篇都懂的化,已经差不多了,明天咱们就开始正式进入Vue入门篇,通过引用Vue.js 实现栗子。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册