开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

升级到Java 17没这么简单

发表于 2021-11-19

前言

最近在给公司新架构做技术选型,刚好 Java 17 也正式发布一段日子了,而且是LTS长期支持版本,就想着直接用起来吧,里面有些特性还是非常好用的,比如:

  • JEP 378:文本块支持
  • JEP 395:Record 类型
  • JEP 286:变量类型推导
  • More…

遇到的问题

其中最主要的原因就是 Java 模块化之后,有些 jdk 内部的类不能被访问了,但是在 Java 16 之前都只是警告,而在 Java 16 之后则会直接报错,目前依赖了cglib和javassist的框架可能都会因此导致项目无法启动,抛出如下异常:

1
2
3
4
5
6
7
8
9
10
11
log复制代码Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @39aeed2f
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at net.sf.cglib.core.ReflectUtils$1.run(ReflectUtils.java:61)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:554)
at net.sf.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:52)
at net.sf.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:243)
at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:332)

从 Java 16 开始,JEP 396会默认把--illegal-access参数设置为deny,即默认禁用访问封装的包以及反射其他模块,这样就会导致上面的异常,在此之前该参数默认值一直都是--illegal-access=permit,只会产生警告,而不会报错,所以如果是 Java 16 的话需要在执行 Java 程序时把--illegal-access设置为permit,这样就可以解决问题,示例:

1
sh复制代码java -jar --illegal-access=permit app.jar

从 Java 17 开始就更狠了,JEP 403直接把--illegal-access参数移除了,如果需要启用访问封装的包,需要在执行 Java 程序时加上--add-opens java.base/java.lang=ALL-UNNAMED选型,示例:

1
sh复制代码java -jar --add-opens java.base/java.lang=ALL-UNNAMED app.jar

如果是在 IDEA 中运行需要配置对应的 VM 参数,示例:

虽然说加完参数之后是可以跑起来,但是我认为这是一个破坏性的改动,因为这样的话,如果有一天 Java 版本变化了,参数又失效了,那么所有的项目都需要更新,这样会导致项目的维护成本大大增加,所以这里不建议使用。

开源框架升级进度跟踪

那么有没有办法不加启动参数就能正常运行呢,答案是肯定的,只不过需要等开源框架全换算 Java 17 的新 API,目前我跟踪到的两个项目都还没有适配 Java 17。

Spring

SpringBoot 2.5.0 开始支持 Java 17,没啥问题。

apollo 配置中心

apollo 目前的 master 分支代码是已经适配好了,但是还没有正式发版,比较奇怪的事是, apollo 之前升级了底层依赖包来适配 Java 17,但是后来又回滚回来了,不知道是出于什么原因。

dubbo

dubbo 有个issue 7593四月份就提出来了,但是一直没人跟进。

总结

一顿操作下来发现不行,最终还是先换成了 Java 15,待时机成熟的时候再升级到 Java 17。

本文首发于我的博客:monkeywie.cn,欢迎收藏!不定期分享JAVA、Golang、前端、docker、k8s等干货知识。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

首次!统一调度系统规模化落地,全面支撑阿里巴巴双 11 全业

发表于 2021-11-19

简介: 今年双 11 首次规模化亮相的统一调度,通过一套调度协议、一套系统架构,统一管理底层的计算、存储、网络资源,超大规模、高效率、自动化的资源弹性,实现了业界新的突破。在离线混部、离在线混部、新的快上快下技术,减少数万台服务器采购,带来数亿计的资源成本优化和大促效率提升。

01 背景

统一调度项目 1.0 成功支持 2021 年双 11 大促,统一调度方案实现了从容器调度到快上快下全流程的全面升级和优化。项目组 100 多位核心成员,成功走过了立项、POC、方案评审设计、封闭开发测试、大促冲刺各个阶段,历经考验成功上线。

作为阿里巴巴的核心项目,阿里云(容器团队和大数据团队)联合阿里巴巴资源效能团队、蚂蚁容器编排团队,历时一年多研发和技术攻坚,实现了从**“混部技术”到今天“统一调度技术”的全面升级。**

今天,统一调度已实现阿里巴巴电商、搜推广、MaxCompute 大数据和蚂蚁业务的调度全面统一,实现了 pod 调度和 task 高性能调度的统一,实现了完整的资源视图统一和调度协同,实现了多种复杂业务形态的混部和利用率提升,全面支撑了全球数十个数据中心、数百万容器、数千万核的大规模资源调度。

云原生产品家族

02 统一调度技术全面升级

云计算的本质,就是把小的计算碎片变成更大的资源池,充分削峰填谷,提供极致的能效比。对数据中心低碳节能、绿色环保、科技发展、更高效运转的追求下,阿里巴巴对技术的探索永无止境。阿里的技术人有一个理想,让数据中心的算力成为水、电、气一样的基础设施,开箱即用。

为了让业务间峰谷互补的优势发挥到最大,过去我们构建了混部技术,打破多资源池的割裂,不同计算领域的多调度大脑协同共用资源;老一代的混部技术带来了资源的统一和利用率的巨大提升,但多调度器的本质让我们的追求受限。

阿里巴巴持续追求构建可支撑更多复杂任务无差别混部、极致弹性互补、领先的新一代调度技术,实现极致的全局最优调度,提供更高质量的算力。今年我们在技术上到达一个新的临界点,容器服务 ACK 牵头并协同众多团队,启动了基于 ACK 的新一代统一调度项目。

容器产品家族

今年双 11 首次规模化亮相的统一调度,通过一套调度协议、一套系统架构,统一管理底层的计算、存储、网络资源,超大规模、高效率、自动化的资源弹性,实现了业界新的突破。在离线混部、离在线混部、新的快上快下技术,减少数万台服务器采购,带来数亿计的资源成本优化和大促效率提升。

今年首次引入大规模数据智能来进一步丰富调度能力,提供了包括实时的负载感知,自动规格推荐(VPA),差异化 SLO 工作负载编排,CPU 归一化,支持周期性预测的 HPA,分时复用等,提供了更多维度的成本优化技术和高可靠的容器运行时保障。

围绕着新一代的统一调度,阿里巴巴电商、搜索、大数据等众多平台、不同类型的复杂计算资源都以一致的方式申请资源,统筹的额度管理和资源规划,数十万核资源借用秒级即可完成。基于统一调度,阿里云与蚂蚁也实现了调度技术融合,蚂蚁生态全面升级为统一调度。调度平台为未来带来更多想象空间,例如,我们可以通过众多手段,例如价格杠杆等经济因素,驱动阿里内部的业务更合理使用各个数据中心的资源,确保数据中心全局资源水位尽可能平衡,以改进数据中心的能效比。

阿里云容器服务 ACK 对标准 Kubernetes 进一步增强,更高性能吞吐和更低的响应延迟构建稳定可靠的超大规模单集群能力,平稳支撑了 1.2 万节点超 100 万核的超大规模集群、为统一调度大资源池化的生产运行提供了坚实的基座。阿里巴巴众多类型的复杂资源也实现了基于容器服务底座 ACK 的全面融合升级。

除电商、搜索、大数据等阿里经典场景外,统一调度也极大的赋能了新型的技术创新。以直播电商场景为例,决策对实时计算的需求很高,比如薇娅双 11 直播间 9 千多万在线观看人数的产生的浏览、交易等实时数据的秒级数据分析。今年阿里将实时计算引擎 Blink 升级为基于统一调度的新一代引擎,在成本、性能、稳定性以及用户体验上获得大幅提高,大规模作业拉起性能相比 Yarn 提速 40%,错误恢复效率提升 100%,通过统一调度技术在双 11 大促备战接节省数十万 CPU,在集群 CPU 水位超过 65% 时,实现全局零热点,保障了各直播推流的时效性。

在 Serverless 方面,函数服务首次在集团内得到大规模落地,并应用于双 11 支撑了淘宝搜索推荐、数据处理、前端 SSR 等 10 多个业务场景。借助统一调度技术,函数计算可以和阿里资源池内实现大规模混跑,充分利用集群的碎片资源,彻底解决了 Serverless 场景在流量低峰期的资源闲置成本问题。基于 ACK 镜像按需加载和网络栈优化,函数实例的冷启动时间小于 150ms,并结合池化技术保证了函数计算容器的冷启动率小于 5%,这是保证双 11 大促成功的关键。

03 未来展望

未来,容器服务 ACK 将阿里巴巴统一调度的经验输出到整个行业,支撑更多新型计算负载生态、新型技术形态的架构演进,实现云计算无处不在,全面赋能更多的企业,释放更大的低碳价值红利。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

面对DNS劫持,只能坐以待毙吗? 知己知彼,什么是 DNS?

发表于 2021-11-19

简介: 借助 ARMS-云拨测,我们可实时对网站进行监控,实现分钟级别的监控,及时发现 DNS 劫持以及页面篡改。

作者:白玙

DNS 劫持作为最常见的网络攻击方式,是每个站长或者运维团队最为头疼的事情。苦心经营的网站受到 DNS 劫持后,不仅会影响网站流量、权重,还会让用户置身于危险之中,泄露隐私造成财产损失。

就是这样一个简单到不能再简单的攻击方式,在 2009 年制造了轰动全球的“银行劫持案”,导致巴西最大银行 Banco Bradesco 银行近 1% 客户受到攻击而导致账户被盗。黑客利用宽带路由器缺陷对用户 DNS 进行篡改——用户浏览黑客所制作的 Web 页面,其宽带路由器 DNS 就会被黑客篡改,由于该 Web 页面设有巧妙设计的恶意代码,成功躲过安全软件检测,导致大量用户被 DNS 钓鱼诈骗。

网站被黑、被歹意镜像、被植入垃圾代码,现象屡见不鲜,其危害还包括:

  • 钓鱼诈骗网上购物,网上支付有可能会被恶意指向别的网站,更加加大了个人账户泄密的风险;
  • 网站内出现恶意广告;
  • 轻则影响网速,重则不能上网。

但面对DNS劫持时,只能束手就擒吗?

知己知彼,什么是 DNS?

DNS 即 Domain Name System 的缩写,域名系统以分布式数据库的形式将域名和 IP 地址相互映射。简单的说,DNS 是用来解析域名的,在正常环境下,用户的每一个上网请求会通过 DNS 解析指向到与之相匹配的 IP 地址,从而完成一次上网行为。DNS 作为应用层协议,主要是为其他应用层协议工作的,包括不限于 HTTP、SMTP、FTP,用于将用户提供的主机名解析为 IP 地址,具体过程如下:

(1)用户主机(PC 端或手机端)上运行着 DNS 的客户端;

(2)浏览器将接收到的 URL 中抽取出域名字段,即访问的主机名,比如 www.aliyun.com/ , 并将这个主机名传送给 DNS 应用的客户端;

(3)DNS 客户机端向 DNS 服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式 DNS 集群的工作);

(4)该 DNS 客户机最终会收到一份回答报文,其中包含有该主机名对应的 IP 地址;

(5)一旦该浏览器收到来自 DNS 的 IP 地址,就可以向该 IP 地址定位的 HTTP 服务器发起 TCP 连接。

(图片源自网络,仅作示意)

可以看到想要获取目标网站 IP,除了在本机中查找行为,还需要第三方服务器(DNS)参与。但只要经过第三方服务,网络就不属于可控制范围,那么就有可能产生 DNS 挟持,比如获取的 IP 并不是实际想要的 IP,从而打开非目标网站。网站在经过本地 DNS 解析时,黑客将本地 DNS 缓存中的目标网站替换成其他网站的 IP 返回,而客户端并不知情,依旧按照正常流程寻址建并立连接。如果一些黑客想要盗取用户账号及密码时,黑客可以做跟目标网站一模一样的木马页面,让用户登录,当用户输入完密码提交的时候就中招了。

常见 DNS 劫持手段又有哪些?

(1)利用 DNS 服务器进行 DDoS 攻击

正常 DNS 服务器递归询问过程被利用,变成 DDoS 攻击。假设黑客知晓被攻击机器 IP 地址,攻击者使用该地址作为发送解析命令的源地址。当使用 DNS 服务器递归查询后会响应给最初用户。如果黑客控制了足够规模的肉鸡进行上述操作。那么,这个最初用户就会受到来自于 DNS 服务器的响应信息 DDoS 攻击,成为被攻击者。

(2)DNS 缓存感染

黑客使用 DNS 请求将数据注入具有漏洞的 DNS 服务器缓存中。这些缓存信息会在客户进行 DNS 访问时返回给用户,把用户对正常域名的访问引导到入侵者所设置挂马、钓鱼等页面上,或通过伪造邮件和其他服务获取用户口令信息,导致客户遭遇进一步侵害。

(3)DNS 信息劫持

原则上 TCP/IP 体系通过序列号等多种方式避免仿冒数据插入,但黑客通过监听客户端和 DNS 服务器对话,就可以解析服务器响应给客户端的 DNS 查询 ID。每个 DNS 报文包括一个相关联的 16 位 ID,DNS 服务器根据这个 ID 获取请求源位置。黑客在 DNS 服务器之前将虚假响应交给用户,欺骗客户端去访问恶意网站。假设当提交给某个域名服务器域名解析请求的数据包被截获,然后按黑客的意图将虚假 IP 地址作为应答信息返回给请求者。这时,原始请求者就会把这个虚假 IP 地址作为它所要请求的域名而进行连接,显然它被引导到了别处而根本连接不上自己想要连接的那个域名。

(4)ARP 欺骗

通过伪造 IP 地址和 MAC 地址实现 ARP 欺骗,在网络中产生大量 ARP 通信量使网络阻塞,黑客只要持续不断发出伪造的 ARP 响应包就能更改目标主机 ARP 缓存中的 IP-MAC 条目,造成网络中断或中间人攻击。ARP 攻击主要是存在于局域网网络中,局域网中若有一台计算机感染 ARP 木马,则感染该 ARP 木马的系统将会试图通过”ARP 欺骗”手段截获所在网络内其它计算机的通信信息,并因此造成网内其它计算机的通信故障。ARP 欺骗通常是在用户局网中,造成用户访问域名的错误指向,但在 IDC 机房被入侵后,则也可能出现攻击者采用 ARP 包压制正常主机、或者压制 DNS 服务器,以使访问导向错误指向。

DNS 劫持对业务造成哪些影响?

一旦被劫持,相关用户查询就没办法获取到正确 IP 解析,这就很容易造成:

(1)很多用户习惯依赖书签或者易记域名进入,一旦被劫持会使这类用户无法打开网站,更换域名又没办法及时告知变更情况,导致用户大量流失。

(2)用户流量主要是通过搜索引擎 SEO 进入,DNS 被劫持后会导致搜索引擎蜘蛛抓取不到正确 IP,网站就可能会被百度 ban 掉。

(3)一些域名使用在手机应用 APP 调度上,这些域名不需要可以给客户访问,但这些域名的解析关系到应用 APP 访问,如果解析出现劫持就会导致应用 APP 无法访问。这时候更换域名就可能会导致 APP 的下架,重新上架需要审核并且不一定可以重新上架。这就会导致应用 APP 会有用户无法访问或者下载的空窗期。

可以看到,DNS 劫持对业务有着巨大影响,不仅仅是用户体验的损失,更是对用户资产安全、数据安全的造成潜在的巨大风险。

我们该如何监测网站是否被 DNS 劫持?

借助 ARMS-云拨测,我们实时对网站进行监控,实现分钟级别的监控,及时发现 DNS 劫持以及页面篡改。

劫持检测

  • DNS 劫持监测

利用域名白名单、元素白名单,有效探测域名劫持以及元素篡改情况。在建立拨测任务时,我们可以设置 DNS 劫持白名单。比如,我们配置 DNS 劫持格式的文件内容为 www.aliyun.com:201.1.1.22|250.3.44.67。这代表 www.aliyun.com 域名下,除了 201.1.1.22 和 250.3.44.67 之外的都是被劫持的。

  • 页面篡改监测

我们把原始页面的元素类型加入页面篡改白名单,在进行拨测时将加载元素与白名单对比,判断页面是否被篡改。比如,我们配置页面篡改的文件内容为 www.aliyun.com:|/cc/bb/a.gif… www.aliyun.com 域名下,除了基础文档 、/cc/bb/a.gif 和 /vv/bb/cc.jpg 之外的元素都属于页面被篡改。再比如,我们配置页面篡改的文件内容为 www.aliyun.com:\*,代表:www.aliyuyn.com 域名下所有的元素都不认为是被篡改。

劫持告警

在持续监测的同时,及时告警也至关重要。通过灵活配置劫持告警比例,当任务的劫持比例大于阈值,即迅速通知相关运维团队,对网站进行维护,确保用户的数据安全以及网站的正常浏览。

在提升用户体验的同时,确保网站以及用户资产安全对于企业而言同样至关重要。云拨测为你的网站安全与用户体验保驾护航!

关于云拨测

云拨测作为面向业务的非侵入式云原生监测产品,成为最佳的选择。通过阿里云遍布全球的服务网络,模拟真实用户行为,全天候持续监测网站及其网络、服务、API 端口可用性与性能。实现页面元素级、网络请求级、网络链路级细颗粒度问题定位。丰富的监测关联项与分析模型,帮助企业及时发现与定位性能瓶颈与体验暗点,压降运营风险,提升服务体验与效能。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

springboot异步任务与定时任务 1实现Async异

发表于 2021-11-19

「这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战」

1.实现Async异步任务

1.1 环境准备

在 Spring Boot 入口类上配置 @EnableAsync 注解开启异步处理。创建任务抽象类 AbstractTask,并分别配置三个任务方法 doTaskOne(),doTaskTwo(),doTaskThree()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码public abstract class AbstractTask {
   private static Random random = new Random();
​
   public void doTaskOne() throws Exception {
       System.out.println("开始做任务一");
       long start = currentTimeMillis();
       sleep(random.nextInt(10000));
       long end = currentTimeMillis();
       System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
  }
​
   public void doTaskTwo() throws Exception {
       System.out.println("开始做任务二");
       long start = currentTimeMillis();
       sleep(random.nextInt(10000));
       long end = currentTimeMillis();
       System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
  }
​
   public void doTaskThree() throws Exception {
       System.out.println("开始做任务三");
       long start = currentTimeMillis();
       sleep(random.nextInt(10000));
       long end = currentTimeMillis();
       System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
  }
}

1.2 同步调用

下面通过一个简单示例来直观的理解什么是同步调用:

  • 定义 Task 类,继承 AbstractTask,三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10 秒内)。
1
2
3
java复制代码@Component
public class SyncTask extends AbstractTask {
}
  • 在 单元测试 用例中,注入 SyncTask 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@RunWith(SpringRunner.class)
@SpringBootTest
public class TaskTest {
   @Autowired
   private SyncTask task;
​
   @Test
   public void testSyncTasks() throws Exception {
       task.doTaskOne();
       task.doTaskTwo();
       task.doTaskThree();
  }
}
  • 执行单元测试,可以看到类似如下输出:
1
2
3
4
5
6
复制代码开始做任务一
完成任务一,耗时:6720毫秒
开始做任务二
完成任务二,耗时:6604毫秒
开始做任务三
完成任务三,耗时:9448毫秒

任务一、任务二、任务三顺序的执行完了,换言之 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法顺序的执行完成。

1.3 异步调用

上述的 同步调用 虽然顺利的执行完了三个任务,但是可以看到 执行时间比较长,若这三个任务本身之间 不存在依赖关系,可以 并发执行 的话,同步调用在 执行效率 方面就比较差,可以考虑通过 异步调用 的方式来 并发执行。

  • 创建 AsyncTask类,分别在方法上配置 @Async 注解,将原来的 同步方法 变为 异步方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@Component
public class AsyncTask extends AbstractTask {
   @Async
   public void doTaskOne() throws Exception {
       super.doTaskOne();
  }
​
   @Async
   public void doTaskTwo() throws Exception {
       super.doTaskTwo();
  }
​
   @Async
   public void doTaskThree() throws Exception {
       super.doTaskThree();
  }
}
  • 在 单元测试 用例中,注入 AsyncTask 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。
1
2
3
4
5
6
7
8
9
java复制代码@Autowired
private AsyncTask asyncTask;
​
@Test
public void testAsyncTasks() throws Exception {
   asyncTask.doTaskOne();
   asyncTask.doTaskTwo();
   asyncTask.doTaskThree();
}
  • 执行单元测试,可以看到类似如下输出:
1
2
3
复制代码开始做任务三
开始做任务一
开始做任务二

如果反复执行单元测试,可能会遇到各种不同的结果,比如:

  1. 没有任何任务相关的输出
  2. 有部分任务相关的输出
  3. 乱序的任务相关的输出

原因是目前 doTaskOne(),doTaskTwo(),doTaskThree() 这三个方法已经 异步执行 了。主程序在 异步调用 之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就 自动结束 了,导致了 不完整 或是 没有输出任务 相关内容的情况。

注意:@Async所修饰的函数不要定义为static类型,这样异步调用不会生效。

1.4 异步回调

为了让 doTaskOne(),doTaskTwo(),doTaskThree() 能正常结束,假设我们需要统计一下三个任务 并发执行 共耗时多少,这就需要等到上述三个函数都完成动用之后记录时间,并计算结果。

那么我们如何判断上述三个 异步调用 是否已经执行完成呢?我们需要使用 Future 来返回 异步调用 的 结果。

  • 创建 AsyncCallBackTask 类,声明 doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback() 三个方法,对原有的三个方法进行包装。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码@Component
public class AsyncCallBackTask extends AbstractTask {
   @Async
   public Future<String> doTaskOneCallback() throws Exception {
       super.doTaskOne();
       return new AsyncResult<>("任务一完成");
  }
​
   @Async
   public Future<String> doTaskTwoCallback() throws Exception {
       super.doTaskTwo();
       return new AsyncResult<>("任务二完成");
  }
​
   @Async
   public Future<String> doTaskThreeCallback() throws Exception {
       super.doTaskThree();
       return new AsyncResult<>("任务三完成");
  }
}
  • 在 单元测试 用例中,注入 AsyncCallBackTask 对象,并在测试用例中执行 doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback() 三个方法。循环调用 Future 的 isDone() 方法等待三个 并发任务 执行完成,记录最终执行时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码@Autowired
private AsyncCallBackTask asyncCallBackTask;
​
@Test
public void testAsyncCallbackTask() throws Exception {
   long start = currentTimeMillis();
   Future<String> task1 = asyncCallBackTask.doTaskOneCallback();
   Future<String> task2 = asyncCallBackTask.doTaskTwoCallback();
   Future<String> task3 = asyncCallBackTask.doTaskThreeCallback();
​
   // 三个任务都调用完成,退出循环等待
   while (!task1.isDone() || !task2.isDone() || !task3.isDone()) {
       sleep(1000);
  }
​
   long end = currentTimeMillis();
   System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}

看看都做了哪些改变:

  • 在测试用例一开始记录开始时间;
  • 在调用三个异步函数的时候,返回Future类型的结果对象;
  • 在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
  • 跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。

执行一下上述的单元测试,可以看到如下结果:

1
2
3
4
5
6
7
复制代码开始做任务三
开始做任务一
开始做任务二
完成任务二,耗时:2572毫秒
完成任务一,耗时:7333毫秒
完成任务三,耗时:7647毫秒
任务全部完成,总耗时:8013毫秒

可以看到,通过 异步调用,让任务一、任务二、任务三 并发执行,有效的 减少 了程序的 运行总时间。

2.为异步任务规划线程池

2.1线程池的作用

  1. 防止资源占用无限的扩张
  2. 调用过程省去资源的创建和销毁所占用的时间

在上一节中,我们的一个异步任务打开了一个线程,完成后销毁。在高并发环境下,不断的分配新资源,可能导致系统资源耗尽。所以为了避免这个问题,我们为异步任务规划一个线程池。

2.2定义线程池

在上述操作中,创建一个 线程池配置类TaskConfiguration ,并配置一个 任务线程池对象taskExecutor。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@Configuration
public class TaskConfiguration {
   @Bean("taskExecutor")
   public Executor taskExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(10);
       executor.setMaxPoolSize(20);
       executor.setQueueCapacity(200);
       executor.setKeepAliveSeconds(60);
       executor.setThreadNamePrefix("taskExecutor-");
       executor.setRejectedExecutionHandler(new CallerRunsPolicy());
       return executor;
  }
}

上面我们通过使用 ThreadPoolTaskExecutor 创建了一个 线程池,同时设置了以下这些参数:

线程池属性 属性的作用 设置初始值
核心线程数 线程池创建时候初始化的线程数 10
最大线程数 线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程 20
缓冲队列 用来缓冲执行任务的队列 200
允许线程的空闲时间 当超过了核心线程之外的线程,在空闲时间到达之后会被销毁 60秒
线程池名的前缀 可以用于定位处理任务所在的线程池 taskExecutor-
线程池对拒绝任务的处理策略 这里采用CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务 CallerRunsPolicy
  • 创建 AsyncExecutorTask类,三个任务的配置和 AsyncTask 一样,不同的是 @Async 注解需要指定前面配置的 线程池的名称taskExecutor。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码@Component
public class AsyncExecutorTask extends AbstractTask {
   @Async("taskExecutor")
   public Future<String> doTaskOneCallback() throws Exception {
       super.doTaskOne();
       System.out.println("任务一,当前线程:" + Thread.currentThread().getName());
       return new AsyncResult<>("任务一完成");
  }
​
   @Async("taskExecutor")
   public Future<String> doTaskTwoCallback() throws Exception {
       super.doTaskTwo();
       System.out.println("任务二,当前线程:" + Thread.currentThread().getName());
       return new AsyncResult<>("任务二完成");
  }
​
   @Async("taskExecutor")
   public Future<String> doTaskThreeCallback() throws Exception {
       super.doTaskThree();
       System.out.println("任务三,当前线程:" + Thread.currentThread().getName());
       return new AsyncResult<>("任务三完成");
  }
}
  • 在 单元测试 用例中,注入 AsyncExecutorTask 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncExecutorTaskTest {
   @Autowired
   private AsyncExecutorTask task;
​
   @Test
   public void testAsyncExecutorTask() throws Exception {
       task.doTaskOneCallback();
       task.doTaskTwoCallback();
       task.doTaskThreeCallback();
​
       sleep(30 * 1000L);
  }
}

执行一下上述的 单元测试,可以看到如下结果:

1
2
3
4
5
6
7
8
9
复制代码开始做任务一
开始做任务三
开始做任务二
完成任务二,耗时:3905毫秒
任务二,当前线程:taskExecutor-2
完成任务一,耗时:6184毫秒
任务一,当前线程:taskExecutor-1
完成任务三,耗时:9737毫秒
任务三,当前线程:taskExecutor-3

执行上面的单元测试,观察到 任务线程池 的 线程池名的前缀 被打印,说明 线程池 成功执行 异步任务!

2.3优雅地关闭线程池

由于在应用关闭的时候异步任务还在执行,导致类似 数据库连接池 这样的对象一并被 销毁了,当 异步任务 中对 数据库 进行操作就会出错。

解决方案如下,重新设置线程池配置对象,新增线程池 setWaitForTasksToCompleteOnShutdown() 和 setAwaitTerminationSeconds() 配置:

1
2
3
4
5
6
7
8
9
java复制代码@Bean("taskExecutor")
public Executor taskExecutor() {
   ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
   executor.setPoolSize(20);
   executor.setThreadNamePrefix("taskExecutor-");
   executor.setWaitForTasksToCompleteOnShutdown(true);
   executor.setAwaitTerminationSeconds(60);
   return executor;
}
  • setWaitForTasksToCompleteOnShutdown(true): 该方法用来设置 线程池关闭 的时候 等待 所有任务都完成后,再继续 销毁 其他的 Bean,这样这些 异步任务 的 销毁 就会先于 数据库连接池对象 的销毁。
  • setAwaitTerminationSeconds(60): 该方法用来设置线程池中 任务的等待时间,如果超过这个时间还没有销毁就 强制销毁,以确保应用最后能够被关闭,而不是阻塞住。

3.quartz动态定时任务(数据库持久化)

3.1前言

在项目开发过程当中,某些定时任务,可能在运行一段时间之后,就不需要了,或者需要修改下定时任务的执行时间等等。需要在代码当中进行修改然后重新打包发布,很麻烦。使用Quartz来实现的话不需要重新修改代码而达到要求。

3.2原理

  1. 使用quartz提供的API完成配置任务的增删改查
  2. 将任务的配置保存在数据库中

3.3配置

application.yml在上一节中已经引入了maven依赖包,这里不再重复。直接spring属性下面加入quartz配置信息

1
2
3
4
5
6
7
8
9
10
yml复制代码spring:
datasource:
  url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
  username: root
  password: 123456
  driver-class-name: com.mysql.cj.jdbc.Driver
quartz:
  job-store-type: JDBC #数据库存储quartz任务配置
  jdbc:
    initialize-schema: NEVER #自动初始化表结构,第一次启动的时候这里写always

但可能是版本bug,有的时候自动建表不会生效,自己去quartz-scheduler-x.x.x.jar里面找一下建表sql脚本:classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql,然后执行。

3.4动态配置代码实现

第一步 创建一个定时任务相关实体类用于保存定时任务相关信息到数据库当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@Data
public class QuartzBean {
   /** 任务id */
   private String id;
​
   /** 任务名称 */
   private String jobName;
​
   /** 任务执行类 */
   private String jobClass;
​
   /** 任务状态 启动还是暂停*/
   private Integer status;
​
   /** 任务运行时间表达式 */
   private String cronExpression;
}

第二步 创建定时任务暂停,修改,启动,单次启动工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
java复制代码public class QuartzUtils {
​
  /**
    * 创建定时任务 定时任务创建之后默认启动状态
    * @param scheduler 调度器
    * @param quartzBean 定时任务信息类
    */
  @SuppressWarnings("unchecked")
  public static void createScheduleJob(Scheduler scheduler, QuartzBean quartzBean) throws ClassNotFoundException, SchedulerException {
          //获取到定时任务的执行类 必须是类的绝对路径名称
          //定时任务类需要是job类的具体实现 QuartzJobBean是job的抽象类。
          Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(quartzBean.getJobClass());
          // 构建定时任务信息
          JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(quartzBean.getJobName()).build();
          // 设置定时任务执行方式
          CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
          // 构建触发器trigger
          CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzBean.getJobName()).withSchedule(scheduleBuilder).build();
          scheduler.scheduleJob(jobDetail, trigger);
  }
​
  /**
    * 根据任务名称暂停定时任务
    * @param scheduler 调度器
    * @param jobName 定时任务名称
    */
  public static void pauseScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {
      JobKey jobKey = JobKey.jobKey(jobName);
      scheduler.pauseJob(jobKey);
  }
​
  /**
    * 根据任务名称恢复定时任务
    * @param scheduler 调度器
    * @param jobName 定时任务名称
    */
  public static void resumeScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {
      JobKey jobKey = JobKey.jobKey(jobName);
      scheduler.resumeJob(jobKey);
  }
​
  /**
    * 根据任务名称立即运行一次定时任务
    * @param scheduler 调度器
    * @param jobName 定时任务名称
    */
  public static void runOnce(Scheduler scheduler, String jobName) throws SchedulerException {
      JobKey jobKey = JobKey.jobKey(jobName);
      scheduler.triggerJob(jobKey);
  }
​
  /**
    * 更新定时任务
    * @param scheduler 调度器
    * @param quartzBean 定时任务信息类
    */
  public static void updateScheduleJob(Scheduler scheduler, QuartzBean quartzBean) throws SchedulerException {
​
          //获取到对应任务的触发器
          TriggerKey triggerKey = TriggerKey.triggerKey(quartzBean.getJobName());
          //设置定时任务执行方式
          CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
          //重新构建任务的触发器trigger
          CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
          trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
          //重置对应的job
          scheduler.rescheduleJob(triggerKey, trigger);
  }
​
  /**
    * 根据定时任务名称从调度器当中删除定时任务
    * @param scheduler 调度器
    * @param jobName 定时任务名称
    */
  public static void deleteScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {
      JobKey jobKey = JobKey.jobKey(jobName);
      scheduler.deleteJob(jobKey);
  }
}
@Controller
@RequestMapping("/quartz/job/")
public class QuartzController {
  //注入任务调度
  @Resource
  private Scheduler scheduler;
​
  @PostMapping("/create")
  @ResponseBody
  public String createJob(@RequestBody QuartzBean quartzBean) throws SchedulerException, ClassNotFoundException {
      QuartzUtils.createScheduleJob(scheduler,quartzBean);
      return "已创建任务";//这里return不是生产级别代码,测试简单写一下
  }
​
  @PostMapping("/pause")
  @ResponseBody
  public String pauseJob(String jobName) throws SchedulerException {
      QuartzUtils.pauseScheduleJob (scheduler,jobName);
      return "已暂停成功";//这里return不是生产级别代码,测试简单写一下
  }
​
  @PostMapping("/run")
  @ResponseBody
  public String runOnce(String jobName) throws SchedulerException {
      QuartzUtils.runOnce (scheduler,jobName);
      return "运行任务" + jobName + "成功";//这里return不是生产级别代码,测试简单写一下
  }
​
  @PostMapping("/resume")
  @ResponseBody
  public String resume(String jobName) throws SchedulerException {
      QuartzUtils.resumeScheduleJob(scheduler,jobName);
      return "恢复定时任务成功:" + jobName;
  }
​
  @PostMapping("/update")
  @ResponseBody
  public String update(@RequestBody QuartzBean quartzBean) throws SchedulerException {
      QuartzUtils.updateScheduleJob(scheduler,quartzBean);
      return "更新定时任务调度信息成功";
  }
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

图解!三个问题带你彻底理解内存管理! 地址空间不隔离 地址空

发表于 2021-11-19

你好,我是 Cone

面试的时候是不是经常面试官被问到一个问题:有了解过虚拟内存吗?那你详细讲讲你了解的虚拟内存吧。我在去年秋招的时候也经常被问题这个问题,那么今天好好来和你一起研究探究这个操作系统的内存管理。

下面就通过一个例子,带你进入操作系统内存管理的世界。

首先,假设我们的内存空间有 64MB,现在我需要运行三个程序,其中程序 A 运行时需要占用的大小为 32MB,程序 B 运行时占用的大小为 30MB,程序 C 运行时占用大小 4MB,那么很显然如果三个程序一起运行,肯定会有地址冲突,因为装不下啊,这只是其中一个问题,还有另外一种问题,程序 A 和 B 一起运行时,如何确保 AB 的程序地址不冲突呢,你可能会想到说,在代码里面写好,让他们不冲突即可,但是那我假如要运行程序 C 呢? 是让 B 退出运行还是让 A 退出运行,那程序 C 的地址如何确定,那是不是就得在代码中变化了,虽然 AB两个程序没有出现相互访问的情况,还可以控制,但是程序一旦多起来,就要为了内存地址”打架了”。这个例子中所有的空间地址都硬编码在了程序里,所以每次要改地址的时候就得改程序,这种最原始的地址使用就出现了很多问题。

通过上面的例子想必你已经发现问题了。

也就是如题,三个问题带你彻底解决内存管理,那么是哪三个问题呢?如下图。

针对以上三个问题,我们逐一解决,首先是地址空间不隔离问题。

地址空间不隔离

在计算机学科里有一句名言:

计算机科学领域的任何一个问题都可以增加一个中间层来解决。

对于这类隔离问题也是这样,不如我们在真实的内存地址上增加一层试试看,我们就叫它虚拟地址吧,既然是虚拟地址,那就可以随意让我们来定值了,只要在运行的时候找到一个与之对应的物理地址去运行就可以了。

通过添加虚拟地址,两个运行的程序就无法知道对方的地址(这里暂且不讲进程间通信)。

所以这个问题就这样得到解决,那么新增加了这个虚拟地址到底该如何与真实地址对应上呢?

所以得提供一个 y = f(x) 这样一个转换 关系,这个转换关系在计算机系统设计的时候存在于真实物理地址中,而操作这个转换关系的物价叫做内存管理单元(MMU,Memory Management Unit),具体详细情况,请看图。

MMU 常见情况存在于 CPU 芯片中,也有一些独立于 CPU 的,通过虚拟内存解决了第一个问题,下面来看看第二问题。

地址空间不确定

地址空间不确定的原因就是不知道程序到底应该运行在哪里,有了上面的虚拟内存,其实地址空间也可以确定下来了,不过比较笨,两个程序的虚拟内存是一样的,对应到物理内存的话不一样也可以,那就得全靠MMU了,为了改变这种比较笨的局面以及随着 CPU 的发展,就出现了一种内存管理技术——内存分段。

16 位 CPU 的内存分段

内存分段模型最早出现在 8086CPU 中,8086 的段寄存器只有 16 位,而地址总线有 20 位,为了解决这一不匹配,就引出了代码段寄存器和 IP 指令寄存器,段寄存器左移四位+IP 寄存器的值就是内存地址了,而程序又会分为数据段、代码段、堆等一个个段,每个段都有响应的寄存器对应,比如需要代码段的时候 CS 寄存器就开始工作了、需要数据段的时候 DS 寄存器就开始工作了等等。这也就是最早内存分段模型,计算例子,如下图。

在深入浅出 CPU 的两种工作模式一文中有着详细的介绍 8086 的历史起源、寻址原理等,你可以跳转到那篇文章仔细阅读。

上面提到了程序有会分为很多段,比如数据段、代码段等等,那么这些段是如何来的呢?

这个时候就得引出我们的编译技术了,编译技术其实在没有内存分段的时候已经有了,比如写了那么多汇编指令,得转换成二进制格式的代码,就得用到汇编器了,它的作用你肯定也猜到了,就是将汇编代码转成二进制格式目标文件代码,也就是我们所说的机器语言,汇编器也是最早的编译技术,当然汇编完成之后,还要有链接的过程,链接器的作用就是开始对程序进行分段合并处理,编译汇编的时候会对程序分段也会做一些处理,不过最主要的是在链接过程具体过程如下图,后续会有跟你专题分析编译技术。

没有内存分段的时候汇编器也比较粗糙,就是无脑对应一条条指令翻译汇编代码就好了,因为这个时候的空间地址完全由汇编代码决定,那么内存分段之后,将一个可执行的二进制文件,分成了很多段,这个时候得益于我们编译技术,它帮我们处理了分段模型,最后的二进制文件就是一段一段的。主题与篇幅原因,这里不做展开,牢记二进制是一段一段的就可以了,后续也会专门分享这个二进制文件到底是个什么样子的。

程序既然是一段一段的了,那么 CPU 读取程序指令的时候,不也得有分工,所谓术业有专攻

那么就是我们开始提到的 CS 段读取指令,DS 段读取数据了,还有很多 CPU 的寄存器去做不同的事情,一些常用的寄存器用处,如下图。

我相信你读到这里,肯定豁然开朗,原来内存分段得这么多技术一起配合啊,没错就是需要这么多计算机体系知识一起配合完成,操作系统也是类似,它不仅仅在操作系统层面有难度,编译技术也得更上,所以你也明白自研操作系统的难度了吧,16 位处理还是比较简单的,到 64 位处理起来更加复杂,而计算机就是由国外 16 位发展而来的,甚至 16 位之前还有很多研究,而到国内应用起来就是 32 位、64 位的,没有基础理论的研究,上来就肝 32、64 位的话太难,做一款成熟的操作系统真的太难,好在有华为的鸿蒙,你会说不喜欢用、不好用,但看到这里相信你也会慢慢原谅当前的鸿蒙(我专门体验了很久鸿蒙,感觉还行~),给它时间吧。

扯了一段爱国情怀,继续回到本文主题,16 位的内存分段,已经讲解完了,接下来我们看看 32 位的内存分段。

在进入 32 位内存分段模型前,先简单来看一个问题:

上面我们知道,16 位的寻址方式最大能寻址 cs=0xffff,ip=0xffff 也就是 0x10ffef 这么大了。这个地址算下来也就 1M 多,1M 多是什么概念?你现在打开相机随意拍一张照片可能都比这个大了,实在是太小了,稍有多的东西就放不下了。那怎么办?当然是扩展 16 位到 32 位了。到了 32 位之后,为了兼容原来的 ax,bx 等寄存器,就在高位扩展了 16 位,而低 16 位依然不变,由高位 8AH 和低 8 位 AL 组成,具体为什么要这么做,而不是直接换成两个 16 位寄存器,,那得问 Intel 的工程师们了。
扩展成 32 位之后,系统能运行更多的程序,而表示一个程序的内存地址的代价也会增加,表示一个程序内存地址的数据已经不在适合放在 32 位的寄存器中了,因为太大了,放不下,具体是如何放不下的,请接着往下阅读,既然放不下就得寻求新的方式了,这就是 32 位 CPU 内存分段的知识了。

下面进入 32 位 CPU 内存分段一起解决上面提到的问题。

32 位 CPU 的内存分段

话不多说,上图,寻址原理一起看图说话。

上面提到的寄存器放不下这么多大量的信息,所以就得寻求改变,最终就是如上图一样进行了改变,别怕,我带你一起解析这幅图。

这时候虚拟地址由两部分组成,段选择子和偏移量,在CPU 工作模式一文有详细提及。

  • 段选择子里面有三个信息:段描述符的索引、TL 位以及请求特权级位。
  • 偏移量就是从段描述符表中取到地址后需要去寻找真正的物理地址的一个索引定位。

上图的主要工作流程为: 首先虚拟地址中的段选择子会根据自身信息与 GDTR 寄存器配合,在段描述符表中找到需要的段描述符位置,从段描述符的信息中拿到段的起始地址,然后根据虚拟地址中的偏移量进行精确定位到物理地址,这就是分段机制下,从虚拟地址找对于的物理地址方法。

然而是分段类型,具体的分段机制大概是分成:数据段、代码段、堆段、栈段、BSS 段等等,如下图,在映射到物理地址的时候,就是一段一段映射的,这个段就比较大块。

讲解完 32 位 CPU 内存分段模型,你肯定发现了,在程序切换的时候会出现大块内存交换的情况,所谓内存交换就是如果要运行 A 程序要 100MB,但是现在发现连续内存不够了,但是空闲的不联系的内存之和大于 100MB,这个时候操作系统会把这些空闲的内存通过交换的方式得到一片连续的空间,这样 A 就能运行了,具体的话,请看图。

如上图,将中间 40MB 空闲内存和 C 程序运行的 70MB 进行交换,得到 90MB 空闲内存,这时候 A 就行加载进去运行了。20MB、40MB、50MB 这没有运行程序的空闲地址,但又不能运行程序,这就是内存碎片了。

大块内存交换不用想肯定效率低,切换块太大的话还会容易产生内存碎片,下面我们就来讲解如何解决这些问题的。

内存使用效率过低

内存使用效率过低表现为两个方面:

  • 内存使用率太低、内存碎片的出现;
  • 内存交换速度太慢,大块交换容易卡顿。

所以就出现了内存分页技术,所谓分页技术就是把虚拟内存和物理内存分成固定的大小,每个大小相同的东西叫做页,也就是每一页都是相同大小的,而这个页的大小一般由硬件规格决定,一般可支持多种大小,这个时候操作系统来选择其中一种大小,这就是分页。下面具体来看看分页时的一些寻址原理等知识。

内存分页

这里我们选取 4KB 大小为一页进行讲解 ,这个大小是无所谓的,重点在于原理。

一个例子探究分页

现在,计算机有 32KB 内存大小,假设每页 4KB,也就是从 VP1 到 VP8,我们有 2 个程序 A 和 B,A 运行时被映射到 VP1、VP3、VP5,B 程序运行时需要 VP1、VP2、VP3。开始运行的时候,B 程序的 VP2 没有被映射到物理内存。看图

当程序 B 运行时,需要读取 VP2 时,发现物理内存中,并没有与之对应的内容,这个时候就会触发页错误(Page Fault),就需要进行一次换入(Page In)操作,你肯定也会想到换出(Page Out),这是从内存中保存到磁盘里的操作,了解这一原理,你可能会想到,一次换入操作肯定会有消耗,那打开一个应用或者启动一个程序时,是不是可以减少这类操作从而优化启动耗时呢。你看掌握原理,你的思维有多发散,原理的重要性不言而喻。

上面就是基本的分页两个操作,你会发现,分页完之后,都是一页一页的换入换出操作,非常简便,没有一大段那么沉重了,自然就解决了交换速度太慢的问题,其实不仅于此,是不是还解决了内存碎片的问题呢,把物理内存分成一页一页,用到的时候就一个个换入,这样下来就没有碎片了。

寻址原理

分页时解决了这些问题,那么它是如何寻址的呢?请看图

分页之后,虚拟地址包含页号和偏移量,页号作为页表的索引,页表包含物理页每页所在物理内存的基地址。这个基地址与页内偏移的组合就形成了物理内存地址。

内存分页之后每个页都是很小块,在进行内存交换的时候,使用效率过低的问就解决了,也不会出现大块交换卡顿,大块内存碎片的情况。

总结

本文从没有内存管理的三个问题,带你进入内存管理的世界,通过一个个例子,解决了内存管理的空间地址不隔离、不确定、使用效率过低的三个问题,期待更多的图解知识,请长期关注作者。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringloC容器的依赖注入源码解析(4)—— Bean

发表于 2021-11-19

createBean方法的执行流程如下:
请添加图片描述
在AbstractBeanFactory的doGetBean的创建bean的方法里打上断点:
请添加图片描述
多放行几次看到了自定义的bean:
请添加图片描述
step into之后来到AbstractAutowireCapableBeanFactor的createBean中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
java复制代码protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {

if (logger.isTraceEnabled()) {
logger.trace("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;

// Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}

// Prepare method overrides.
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}

try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}

try {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}

最开始是BeanDefinition的定义,由于之前以及定义了BeanDefinition,所以要根据Definition的属性创建出来bean实例,这里用RootBeanDefinition接收容器中获取到的BeanDefinition实例。

1
java复制代码RootBeanDefinition mbdToUse = mbd;

接下来尝试会用类加载器加载出class对象

1
java复制代码Class<?> resolvedClass = resolveBeanClass(mbd, beanName);

进入到resolveBeanClass:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码protected Class<?> resolveBeanClass(final RootBeanDefinition mbd, String beanName, final Class<?>... typesToMatch)
throws CannotLoadBeanClassException {

try {
if (mbd.hasBeanClass()) {
return mbd.getBeanClass();
}
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged((PrivilegedExceptionAction<Class<?>>) () ->
doResolveBeanClass(mbd, typesToMatch), getAccessControlContext());
}
else {
return doResolveBeanClass(mbd, typesToMatch);
}
}
catch (PrivilegedActionException pae) {
ClassNotFoundException ex = (ClassNotFoundException) pae.getException();
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
}
catch (ClassNotFoundException ex) {
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
}
catch (LinkageError err) {
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);
}
}

如果是注解方式定义的话,会执行

1
2
3
java复制代码if (mbd.hasBeanClass()) {
return mbd.getBeanClass();
}

如果是xml方式定义的话,会执行来做解析的工作

1
java复制代码doResolveBeanClass(mbd, typesToMatch);

doResolveBeanClass方法里有很多classLoader,即调用事先保存的各种各样的类加载器去尝试加载class对象,由于class对象和类加载器一一对应,所以class对象会存在于其中的一个类加载器中,通过该加载器找到了对应的class对象之后,就会用对应的classLoader加载出对象来


回到createBean,下面如果获取到的class对象不为空,并且当前的BeanDefinition在解析之前没有class对象,但是却有className时(对应xml方式),此时就会拷贝一个RootBeanDefinition的副本,然后给这个副本设置上先前解析出来的class对象实例。

这样做的目的是不希望将解析的class绑定到缓存里的BeanDefinition,因为class有可能是每次都需要动态解析出来的。

1
2
3
4
java复制代码if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}

注解方式不会执行上面那个if,所以会来到下面这个逻辑

1
java复制代码mbdToUse.prepareMethodOverrides();

该方法判断BeanDefinition是否有定义方法的覆盖

1
2
3
4
5
6
java复制代码public void prepareMethodOverrides() throws BeanDefinitionValidationException {
// Check that lookup methods exist and determine their overloaded status.
if (hasMethodOverrides()) {
getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);
}
}

prepareMethodOverride先获取需要覆盖的方法数量,如果count==1则不存在重载,在使用CGLIB增强阶段就不需要进行校验了,直接找到某个方法进行增强即可,否则在增强阶段还需要做特殊的处理

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
if (count == 0) {
throw new BeanDefinitionValidationException(
"Invalid method override: no method with name '" + mo.getMethodName() +
"' on class [" + getBeanClassName() + "]");
}
else if (count == 1) {
// Mark override as not overloaded, to avoid the overhead of arg type checking.
mo.setOverloaded(false);
}
}

样例:

1
2
3
4
5
6
7
8
xml复制代码<bean id="my TestBean" class="io.spring.test.MyTestBean">
<lookup-method name="getUserBean" bean="teacher"/>
<reblaced-method name="changedMethod" replacer="replacer"/>
</bean>

<bean id="teacher" class="io.spring.test.Teacher"/>
<bean id="student" class="io.spring.test.Student"/>
<bean id="replacer" class="io.spring.test.Replacer"/>

对于replaced-method,会去验证一下MyTestBean里是否有changedMethod这个即将被替换的方法,对于lookup-method,如果该属性存在,则会去判断一下Teacher这个bean里是否有getUserBean这个方法,没有则报错。


回到createBean,通过方法覆盖验证之后来到

1
java复制代码Object bean = resolveBeforeInstantiation(beanName, mbdToUse);

主要是执行某些类型的后置处理器的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码@Nullable
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
// 如果beforeInstantiationResolved还没有设置或者是false(说明还没有进行需要在实例化前执行的操作)
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
// Make sure bean class is actually resolved at this point.
// mbd.isSyntheticO默认是false
// 如果注册了InstantiationAwareBeanPostProcessors类型的BeanPostProcessor
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}

Spring在AOP过程中产生的中间代理类就是isSynthetic的,此时的targetType是自己定义的WelcomeController,进入到applyBeanPostProcessorsBeforeInstantiation方法里:

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
return result;
}
}
}
return null;
}

这里使用了责任链模式(Spring中的后置器基本都会使用责任链模式来处理),依次遍历实现了InstantiationAwareBeanPostProcessor接口的BeanPostProcessor实现类,并调用类里面的postProcessBeforeInstantiation看看谁来负责对bean实例的创建,如果去处理了并有了结果,就会立即返回,只要某一个后置处理器返回了结果,就会阻止后面后置处理器的执行。

只要

1
java复制代码Object bean = resolveBeforeInstantiation(beanName, mbdToUse);

可以获取到bean实例,则会终止后续创建bean的流程,此时就表明bean实例的创建被用户接管了,Spring容器可以不用去创建了。

继续resolveBeforeInstantiation,如果bean实例已经被创建出来的话,就会执行

1
java复制代码bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);

前面按理说已经执行了初始化,所以这里就应该执行初始化之后的操作,进入到applyBeanPostProcessorsAfterInitialization:

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}

同样使用了责任链模式,但相比先前这里不局限于InstantiationAwareBeanPostProcessor,而是所有的BeanPostProcessor,只要前一个执行返回结果,当前就可以拿到上一次执行后的结果,直到获取不到结果就返回。

Spring中的拦截器都是用到了后置处理器,使用责任链的模式来层层处理。

resolveBeforeInstantiation最后如果bean不会空则就打上已经处理过的标记。


回到createBean,此时会来到doCreateBean来让Spring创建Bean容器了。
请添加图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

demo3 创建一个定时器任务+awaitility 1

发表于 2021-11-19

参考文档:# Scheduling Tasks

  1. demo说明

在上一篇中,我们已经自己亲自实践体验了怎么做定时任务,无外乎就是@EnableScheduling和@Scheduled,但是存在一个问题:把application.run和我们的任务内容混入同一个类中运行,完全不够优雅清晰。

写完之后发现,springboot官方还特意做了一个scheduling task的快速教程,那么我们这一篇中就跟随教程步骤,也建一个官方版本的定时任务来体验一下~

  1. 创建并运行

2.1 添加awaitility依赖

看到awaitility突然的懵逼😳,我们上一篇代码中压根没有这货啊~

果断飞速开始搜索这是啥:

image.png

来自其他大佬blog:

在编写自动化测试用例过程中,往往会遇见被测代码有异步或者队列处理的中间过程;如果需要校验这部分结果,必须等待异步操作结束或队列消费完,而这个中间等待的时间是不确定的,常常是根据经验值设定,通过 Thread.sleep(经验值) ,而这个时间通常会设置成最长的那次时间,但是可能99%次这个异步操作都低于这个最长的时间,这就造成了每次执行这个测试用例都花费了异步任务最长的那次时间。

现介绍一款开源工具awaitility: github.com/awaitility/… ,该工具提供轮询的方式,判断操作是否完成,以最短的时间获取异步任务结果。

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

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

原文链接:blog.csdn.net/hj7jay/arti…

来自官方github:

Testing asynchronous systems is hard. Not only does it require handling threads, timeouts and concurrency issues, but the intent of the test code can be obscured by all these details. Awaitility is a DSL that allows you to express expectations of an asynchronous system in a concise and easy to read manner. For example:

1
2
3
4
5
6
7
8
9
10
> java复制代码@Test
> public void updatesCustomerStatus() {
> // Publish an asynchronous message to a broker (e.g. RabbitMQ):
> messageBroker.publishMessage(updateCustomerStatusMessage);
> // Awaitility lets you wait until the asynchronous operation completes:
> await().atMost(5, SECONDS).until(customerStatusIsUpdated());
> ...
> }
>
>

简单粗暴的理解一下,大致就是这是一个用来测试异步系统的DSL库,因为异步系统里面存在着各种线程、延时等问题,比如我们即将要写的定时器就是一个单线程的延时操作。

那么要对这种进行测试的话,那么我们的测试程序也应该有一定的延时才能对定时任务执行结果进行检测。

如果再重复写一个定时器来执行这个测试程序,是不是就不够优雅呢~

更何况那样设置的话,一般就会把测试的程序的任务循环时间设置成最大值,才能保证测试能够执行

so~ 这个Awaitility就诞生了

正如官方文档的example里面所说,它自带的await()这些方法,可以在检测到目标程序执行成功后,再测试!还可以设置这个测试程序的最大等待时长

那么,既然我们要写的定时器任务,是一个典型的异步程序,那么我们就按照官方说法,给maven添加Awaitility的依赖(pom.xml文件中添加):

1
2
3
4
5
6
maven复制代码<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>3.1.2</version>
<scope>test</scope>
</dependency>

2.2 创建定时器任务java

image.png

如图所示,和前几篇文一样,创建一个SchedulingTask的package,然后创建一个ScheduledTask的类(java文件),一些特殊注意点我标注在了下面代码的注释中了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
java复制代码package com.example.springbootDemos.SchedulingTask;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

// @Component:提供Bean注入;当这个类不属于service、controller等时,用Component进行标注
@Component
public class ScheduledTask {

private static final Logger log = LoggerFactory.getLogger(ScheduledTask.class);

// 获取当前时间戳
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

// 用fixedRate指明每隔5秒执行一次
// 也可以用cron=""来使用cron表达式指定定时任务
// @Scheduled只能放在没有传参的method前面
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {

// 把当前时间戳放在log里面显示
log.info("The time is now {}", dateFormat.format(new Date()));
}
}

上面代码就可以看出,我们要做一个定时任务,就是要在需要定时循环的方法前面,添加Scheduled注解,并指定循环周期,循环周期可以用fixedRate,也可以用cron。

但要注意,这个注解只能用于无参method

2.3 创建启动定时器的application类

image.png

要启动定时器,那么就要在class前面使用@EnableScheduling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码package com.example.springbootDemos.SchedulingTask;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

/*
@SpringBootApplication这个注解包含了以下几个内容:
@Configuration: Tags the class as a source of bean definitions for the application context.
@EnableAutoConfiguration: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings
@ComponentScan: Tells Spring to look for other components, configurations, and services in the com/example package, letting it find the controllers.

@EnableScheduling:ensures that a background task executor is created. Without it, nothing gets scheduled.
*/
@SpringBootApplication
@EnableScheduling
public class ScheduledTaskApplication {

public static void main(String[] args) {
SpringApplication.run(ScheduledTaskApplication.class, args);
}
}

2.4 运行结果

看下面红框显示的时间戳部分,就可以清楚的看到,这是一个定时5秒执行的任务了!
image.png

官方文档到跑完定时任务,就那么突然地结束了!!!!!!!

所以,awaitility去哪里了?????

介于这么虎头蛇尾的官方操作,那么我们就自行尝试拓展一把!!!!!

  1. 拓展:添加awaitility测试

由于常规的输出当前时间戳,一般是没有什么异常可以抛出的,所以这里决定调整一下要测试的内容:检测自动自增数据是否为偶数

首先,我们创建一个私有变量:

1
java复制代码private int count = 0;

然后,给它写一个定时的自增方法,让count每隔3秒加1:

1
2
3
4
5
java复制代码@Scheduled(fixedRate = 3000)
public void addCount() {
count += 5;
log.info("count num: {}", count);
}

同时,我们准备一个public方法获取当前最新的count值:

1
2
3
java复制代码public int getCount() {
return count;
}

最后,我们就需要写一段awaitility的方法,去检测异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@Test
@Scheduled(fixedRate = 5000)
public void taskTesting() {
try {
await().atMost(3, SECONDS).until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
if(count%2 == 0)
log.info("SUCCESS:count={},是偶数", getCount());
return count%2 == 0;
}
});
} catch (Exception e) {
log.info("ERROR:count={},不为偶数", getCount());
}
}

放上完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
java复制代码package com.example.springbootDemos.SchedulingTask;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.*;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;

// @Component:
// 提供Bean注入;当这个类不属于service、controller等时,用Component进行标注
@Component
public class ScheduledTask {

private static final Logger log = LoggerFactory.getLogger(ScheduledTask.class);

private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

private int count = 0;

// 用fixedRate指明每隔5秒执行一次
// 也可以用cron=""来使用cron表达式指定定时任务
// @Scheduled只能放在没有传参的method前面
@Scheduled(fixedRate = 3000)
public void addCount() {
count += 5;
log.info("count num: {}", count);
}

public int getCount() {
return count;
}

// 测试程序 - 如果测试的时候count是偶数则success,反之error
@Test
@Scheduled(fixedRate = 5000)
public void taskTesting() {
try {
await().atMost(3, SECONDS).until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
if(count%2 == 0)
log.info("SUCCESS:count={},是偶数", getCount());
return count%2 == 0;
}
});
} catch (Exception e) {
log.info("ERROR:count={},不为偶数", getCount());
}
}
}

运行结果:

image.png

当然,很多都不会和我这样,这么简单粗糙的给test写个定时器来定时测试。关于await,本身就有很多非常边界的方法。具体可以参考这篇大佬的文:
异步校验工具awaitility快速入门

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java缓缓连接Oracle,对方向你吐了块红色

发表于 2021-11-19

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

前言

  • 在线音乐戳我呀!
  • 音乐博客源码上线啦!
  • 前几篇讲了Docker的安装部署,操作算是比较简单的。(入门级)
  • 最近Oracle异常崩溃(原因不详,排查之后会跟朋友们说哦),导致Java程序被牵连,但今天的主题不是Oracle,而是Java。
  • 接下来将分享Java连接Oracle报错,一五一十盘出。
  • Are you ready ?

欢迎来到我瑶族大部落 ~

3.jpg

Java连接Oracle

  • java连接oracle失败
  • 部署到Docker报错,timezone region not found?

一、java连接oracle失败

今天Oracle安装好之后,开始用springboot连接oracle数据库,结果报错了,我们不慌不张的看看报错信息:

java.sql.SQLException: Listener refused the connection with the following error: ORA-12505, TNS:listener does not currently know of SID given in connect descriptor]

5.png

经检查,说由于连接的URL写法错误导致。

原URL:”jdbc:oracle:thin:@localhost:1521:helowin”

更改后:”jdbc:oracle:thin:@localhost:1521/helowin”

将符号:修改为/即可。

二、部署到Docker报错,timezone region not found?

2.1 timezone region not found

于是我们开开心心的将jar包丢到Docker上,run的时候,日志也学java程序,向我吐了块

ORA-01882:timezone region not found

6.png

2.2 错误原因

是因为docker 容器内时区不是 CST 导致。

2.3 解决办法

在dockerfile 文件中增加一下命令。

1
2
3
4
bash复制代码#设置时区

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

7.png

设置一下时区,在重新run一下,程序走起success!

三、小知识

3.1 缘由

因为我容器启动的时候没有指定指定时区,所以默认是慢了八个小时;

8.png

退出容器,然后执行。

1
bash复制代码docker cp /etc/localtime 容器id:/etc/localtime

然后再docker restart 容器id。

3.2 关于容器与时区。

通过date命令查看时间。

查看主机时间:

1
2
csharp复制代码[root@localhost ~]# date
2016年 07月 27日 星期三 22:42:44 CST

查看容器时间:

1
2
ruby复制代码root@b43340ecf5ef:/#date                                                                                                                          
Wed Jul 27 14:43:31 UTC 2016

可以发现,他们相隔了8小时。

  • CST 应该是指(China Shanghai Time,东八区时间)
  • UTC 应该是指(Coordinated Universal Time,标准时间)

后记

接触Java的时候,还是在上家前后端都做的日子。其实对于新人前后端都接触的话,对个人生涯我觉得是很有帮助的。

因为前后端都懂的话,有很多好处,就比如:有一个需求来了,可能这个前端做是可以实现的,但后端做,可能会更好(可能对于性能等等来说)

更能知道哪端做会更合适、更优 ~

👍 如果对您有帮助,您的点赞是我前进的润滑剂。

相关文献

java.sql.SQLException: Listener refused the connection with the following error: ORA-12505, TNS:listener does not currently know of SID given in connect descriptor

ORA-01882 timezone region not found

解决netcore在docker容器中连接oracle报错(timezone region not found)

docker容器内时区和宿主机时区不一致

Docker 解决容器时间与主机时间不一致的问题三种解决方案

以往推荐

前端仔,快把dist部署到Nginx上

多图详解,一次性啃懂原型链(上万字)

老湿说的万物皆对象,你也信?

Vue-Cli3搭建组件库

Vue实现动态路由(和面试官吹项目亮点)

项目中你不知道的Axios骚操作(手写核心原理、兼容性)

VuePress搭建项目组件文档

vue-typescript-admin-template后台管理系统

原文链接

juejin.cn/post/703209…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SSIS学习使用十二:高级日志(Advanced Loggi

发表于 2021-11-19

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

翻译参考

本文主要参考翻译自 The Stairway to Integration Services 系列文章的 Advanced Logging - Level 12 of the Stairway to Integration Services,目的在于对 SSIS 有一个全面清晰的认识,所有内容在原文的基础上进行实操,由于版本差异、个人疑问等多种原因,未采用完全翻译的原则,同时也会对文中内容进行适当修改,希望最终可以更有利于学习和了解 SSIS,

感谢支持!

高级日志

在之前的介绍中我们配置了SSIS的内建日志。演示了简单和高级日志配置、导出导入日志配置,以及使用脚本任务(Script Tasks)和Dts.Events对象生成自定义日志消息。在SSIS学习使用四:控制流任务错误处理中,我们讨论了冒泡事件的行为、分享了操作事件冒泡的默认行为及介绍了父子模式。

本篇我们将结合学到的关于事件冒泡、日志和父子模式来创建一个自定义的SSIS包日志模型(模式)。

前提条件

禁用Precedence.dtsx事件处理

点击 Precedence.dtsx SSIS包控制流的任何空白处,按F4打开属性。修改 DisableEventHandlers 属性为True。

禁用内建日志(Disable Built-In Logging)

我们要做的第一件事是从SSIS包中移除存在的日志配置。

点击顶部的SSIS下拉菜单,然后点击”日志记录”(Logging...)。

当 配置SSIS日志(Configure SSIS Logs) 窗口显示后,点击 “删除”(Delete) 按钮,删除存在的 文本日志文件(text log file) 配置。

技术上讲,这将停止日志记录 Precedence.dtsx SSIS包。同时推荐清理一下SSIS包,因此建议在 “配置SSIS日志” 窗口中取消选中Precedence包,如下:

并建议删除日志文件连接管理器(log file connection manager):

这样就清除了上一部分配置的内建日志。

推荐复习下第10部分高级事件行为中的事件冒泡…

准备父子SSIS设计模式日志记录(Preparing for Parent-Child SSIS Design Pattern Logging)

首先,我们需要一个数据库和表用于记录日志。让我们创建一个名为 SSISStairwayConfig 的数据库。

使用下面的脚本创建数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码Use master
go
/* SSISStairwayConfig database */
If Not Exists(Select name
From sys.databases
Where name = 'SSISStairwayConfig')
begin
print 'Creating SSISStairwayConfig database'
Create Database SSISStairwayConfig
print 'SSISStairwayConfig database created'
end
Else
print 'SSISStairwayConfig database already exists.'
go

接下来,使用下面的脚本创建 lg 架构(schema) 和 SSISErrors表 用于日志记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
sql复制代码Use SSISStairwayConfig
go
/* log schema */
If Not Exists(Select name
From sys.schemas
Where name = 'lg')
begin
print 'Creating lg schema'
declare @sql varchar(100) = 'Create Schema lg'
exec(@sql)
print 'Lg schema created'
end
Else
print 'Lg schema already exists.'

/* lg.SSISErrors table */
If Not Exists(Select s.name + '.' + t.name
From sys.tables t
Join sys.schemas s
On s.schema_id = t.schema_id
Where s.name = 'lg'
And t.name = 'SSISErrors')
begin
print 'Creating lg.SSISErrors table'
Create Table lg.SSISErrors
(
ID int identity(1,1) Constraint PK_SSISErrors Primary Key Clustered,
ErrorDateTime datetime Not Null Constraint DF_logSSISErrors_ErrorDateTime Default(GetDate()),
ErrorDescription varchar(max) Null,
ErrorCode int Null,
SourceName varchar(255) Null
)
print 'Lg.SSISErrors created'
end
Else
print 'Lg.SSISErrors table already exists.'

我们将防止来自SSIS包的error数据到SSISStairwayConfig数据库的lg.SSISErrors表。

将父子SSIS设计模式应用于日志记录(Applying the Parent-Child SSIS Design Pattern to Logging)

打开 Parent.dtsx SSIS包的 “事件处理程序”(Event Handlers) 标签页,导航到 “Parent” 可执行程序 和 “OnError”事件处理器。

拖拽一个”执行SQL任务”(Execute SQL Task)到OnError事件处理器界面,从”脚本任务”连接一个成功优先约束到新的”执行SQL任务”

打开”执行SQL任务”编辑器,修改 ConnectionType 属性为 ADO.NET。

点击 Connection 属性的值下拉列表,然后点击 “新建连接”(New connection):

“新建连接”选项可以为我们做三件事。

首先,它选择正确的连接管理器类型:ADO.NET连接。

其次,它在SSDT编辑器底部的 “连接管理器”(Connection Managers) 选项卡中创建一个新的 ADO.NET连接管理器,

第三,新建连接选项打开 配置ADO.NET连接管理器编辑器(Configure ADO.NET Connection Manager editor)

点击”新建”按钮,配置一个新的数据连接。

在“服务器名”(Server Name)下拉框中,选择或输入之前创建SSISStairwayConfig数据库的 SQL Server 实例的服务器和实例名。从“选择或输入一个数据库名”(Select or enter a database name)的下拉列表中选择或输入SSISStairwayConfig。

单击“测试连接”按钮,以确保具有访问 SSISStairwayConfig 数据库的权限。如下为测试连接成功:

点击”确定”,关闭连接管理器编辑器,返回 “配置ADO.NET连接管理器” 窗口。

“数据连接”(Data connections)存储在工作站的Windows配置文件中。”数据连接”信息将会在工作站上保持可用,并在将来开发SSIS包中可访问。点击”确定”(OK),关闭该窗口。

SSIS已将连接管理器命名为 “WIN-FR5GRQSCDPO.SSISStairwayConfig”,并将该名称放置在 “执行SQL任务” 的 “Connection” 属性中。 实际的连接管理器 “WIN-FR5GRQSCDPO.SSISStairwayConfig” 还可以在 “连接管理器” 窗口中找到。

下面,点击 “SQLStatement” 属性的值文本框的内部,然后点击”省略号”,打开 “输入SQL查询”(Enter SQL Query) 窗口。输入下面的语句

1
2
3
4
5
6
7
8
sql复制代码Insert Into lg.SSISErrors
(ErrorCode
,ErrorDescription
,SourceName)
Values
(@ErrorCode
,@ErrorDescription
,@SourceName)

输入SQL查询 的窗口如下:

点击”确定”,关闭该窗口。

创建的 Insert 语句包含三个参数:ErrorCode, ErrorDescription和SourceName。这些参数和之前在 Parent.dtsx SSIS包的 OnError事件处理程序 的 “脚本任务”(之前在SSIS学习使用四:控制流任务错误处理中创建) 读取到的SSIS变量相似。

点击 “执行SQL任务”编辑器 左侧列表中的 “参数映射”(Parameter Mapping),打开参数映射页,在此处连接 SQL查询参数 和 OnError事件处理程序 变量。

点击 Add 按钮,点击 “变量名”(Variable Name)下拉框 修改变量名为 “System::ErrorCode”。修改 “参数名称”(Parameter Name) 为 “ErrorCode”。

System::ErrorCodeSSIS变量是一个 Int32(integer) 数据类型,它的值会代替 Insert 语句中的 @ErrorCode 参数。

“方向”(Direction)选项是:Input, Output 和 ReturnValue,数据类型(Data Type)列包含一个 ADO.Net 数据类型的列表。为什么是 ADO.Net 数据类型,因为在开始配置当前 “执行SQL任务”(“常规页”) 时我们选择的 Connection Type。如果 Connection Type 属性选择的是 OLEDB,此处的数据类型将会不同。参数大小(Parameter Size)不用修改。

ADO.Net 和 OLEDB 连接类型之前的另一个不同是:可以在SQL语句和 ADO.NET 下的”参数名称”列中使用参数的名字。当使用OLEDB时,在SQL语句中必须提供问号标记(question marks),并使用数字引用参数名称(第一个问号为0,第二个问号为1,依此类推)。对于 Insert(和其他) 语句,ADO.Net的语法更清晰。

点击”添加”按钮,添加另一个参数:ErrorDescription。System::ErrorDescription SSIS变量是String数据类型,其值将代替 Insert 语句中的 @ErrorDescription 参数。

最后再添加SourceName参数的映射。System::SourceNameSSIS变量是String数据类型,其值将代替 Insert 语句中的@SourceName参数。

注意“参数映射”中的数据类型,如果类型不正确可能导致执行失败。

在SSDT的调试器中执行 Parent.dtsx 包,观察OnError事件处理程序。

询问 “Script Task 4” 成功时点击 “否”,Parent.dtsx SSIS包的 OnError事件处理程序 响应,之后 “执行SQL任务” 同样执行。

通过打开 SQL Server Management Studio (SSMS) 可以观察结果,执行语句如下:

1
2
3
sql复制代码Use SSISStairwayConfig
go
Select * From lg.SSISErrors;

结果显示如下:

弹出测验(Pop quiz):在”父子模式”中,子包中需要多少行代码或对象来记录错误? (答案:0)

我们还获得了 一致性 和 收集 的额外好处:每个错误消息都以相同的格式和位置记录。

We get the additional benefit of consistency and collection: Each error message is logged in the same format and location.

总结

在本文中,我们结合了先前了解的事件冒泡,日志记录 和 Parent-Child 模式的知识,创建自定义SSIS包日志记录模式。

我们使用 Parent.dtsx SSIS包的 OnError事件处理程序 和 Execute SQL Task 来捕获和存储有关子包(Precedence.dtsx)中引发的错误信息。无需在子包中添加其他逻辑即可完成此操作,并且实现了存储SSIS错误信息的一致格式和定位。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

从更高到更好 揭秘2021阿里双11背后的技术亮点 绿色低碳

发表于 2021-11-19

简介: 今年,是阿里巴巴第13个双11。今年双11,阿里巴巴大促峰值的计算成本相比去年下降50%;截至11日,小蛮驴无人车在双11期间累计配送快递已超过100万件。更多的阿里自研技术投入到双11,在芯片、服务器等硬核技术攻坚的同时,技术也在推动全链路的降本增效。除了深耕自立自强的高科技,阿里还在践行“人人受益、责任担当、开放共享”的好科技,希望用技术给客户创造更好的价值,为社会创造更大的贡献。从更高到更好,今年双11,阿里巴巴的技术有什么不一样?

作者 | Alibaba Tech

来源 | 阿里技术公众号

今年,是阿里巴巴第13个双11。

今年双11,阿里巴巴大促峰值的计算成本相比去年下降50%;

截至11日,小蛮驴无人车在双11期间累计配送快递已超过100万件。

更多的阿里自研技术投入到双11,在芯片、服务器等硬核技术攻坚的同时,技术也在推动全链路的降本增效。

除了深耕自立自强的高科技,阿里还在践行“人人受益、责任担当、开放共享”的好科技,希望用技术给客户创造更好的价值,为社会创造更大的贡献。

从更高到更好,今年双11,阿里巴巴的技术有什么不一样?

绿色低碳

1 绿色科技 助力每笔订单碳排放量持续下降

交易背后的碳成本主要有三部分:一是商品交易/数字交易本身的碳成本;二是物流运输中的碳成本;三是商品生产和消费中的碳成本。

低碳双11,必须解答好三个考题:首先,如何让数据中心变得更“绿”、更加节能;其次,如何通过算力共享,提升资源统一调度效率;再次,如何让单位算力的效率和价值最大化。

在绿色数据中心方面,阿里云以低碳选址、清洁能源、液冷技术等融合AI应用,加速绿色节能技术创新及迭代升级,做到“少用电、用好电、用绿电”;在绿色计算方面,通过技术创新实现资源利用率显著提升、助力自身节能减排,今年每一笔订单在交易环节的碳排放进一步减少;在绿色算力方面,云提供了算力充分复用、高效利用的技术架构与经济模式,让算力本身更绿色;在绿色数字供应链方面,以算法驱动,采用预测+决策的算法体系,从机器学习向深度学习技术栈演进,实现数据驱动的预测决策一体化优化,持续优化、创新供应链计划和运配等环节,降低货损、提升运配效率,帮助商家降低碳排放。

2 技术驱动,建设阿里巴巴绿色数字供应链

今年双11,数字供应链采用预测+决策的算法体系,实现数据驱动的预测决策一体化优化,降低货损,统一调度减少浪费;在运配场景,提升车辆装载率及运输效率;通过建设一盘货、全链路可售能力,帮助商家库存共享,更精准地进行补货备货,助力商家多端销售,降低滞销。

3 阿里云数据中心大规模使用绿电,为“双11减碳计划”提供绿色“后盾”

为助力数据中心低碳绿色发展,近年来,阿里巴巴积极响应国家“双碳”战略要求,开展清洁能源电力交易。2018年,张北数据中心加入张家口“四方协作机制”风电交易,率先在全国数据中心行业开展非水可再生能源电力交易。今年9月,作为首批全国绿色电力交易主体,达成1亿千瓦时光伏电力交易,成为本次交易中互联网行业最大的绿色电力购买主体,践行了绿色发展的理念,探索了通过新交易品种获得绿色电力的路径。双11期间,预计阿里张北数据中心将使用绿电近3000万千瓦时,减排二氧化碳2.6万吨。公开数据显示,2021年1-9月,阿里巴巴共交易绿电2.48亿千瓦时。自2018年至2021年9月,仅张北数据中心就累计交易约6亿千瓦时新能源电量,累计实现二氧化碳减排近52.3万吨。

值得一提的是,阿里巴巴张北数据中心成为行业内首个碳普惠试点项目,获评2020年国家绿色数据中心,入选2021年国家生态环境部低碳绿色典型案例。

4 统一调度、统一资源池稳定、高效、低成本支撑双 11

在应用侧,针对高并发场景,在保障消费体验的同时,阿里技术大幅优化交易链路,应对脉冲式流量。在软件侧,通过统一资源池进行统一调度,支撑大规模的离在线混部,实现在线业务优先调度。在云方面,基于阿里自研的神龙云服务器、自研的数据库PolarDB等,大大提升了服务器资源使用效率,可搭建百万级规模的容器集群,大规模释放云原生的技术红利。

5 绿色物流全面升级,通过智能切箱、绿色回箱、碳账单等让绿色心智充分触达消费者,打造人人都能参与的绿色双11

通过优化纸箱型号和推荐合理的装箱方案,菜鸟智能切箱算法支持菜鸟全行业仓内包材推荐,平均减少使用15%包材,仅在菜鸟仓一年可为5.3亿个包裹“瘦身”。

通过碳排放测算和绿色物流各环节的信息化盘查,在2021年天猫双11期间推出了物流行业首个“个人减碳账单”,并在6万个菜鸟驿站发起“快递包装回收 全面换鸡蛋”、1万个菜鸟驿站试行旧包装循环寄件项目,打通消费者可感知、可参与的快递包装循环使用闭环。

技术创新

1 平头哥芯片含光800规模化支持搜索推荐等场景

2021双11期间,含光800通过阿里云平台支持了淘宝搜索、推荐等业务,其中淘宝主搜100%的AI算力由该芯片提供。实际应用情况显示,含光800有效发挥了芯片与云计算环境融合的优势,既提升了系统的性能又降低了整体能耗,以搜索场景为例,相比传统GPU,使用含光800运行的算法效率最高可提升近2倍,单位算力能耗降低58%。

2 自研磐久服务器新一代硬件技术全新上阵

磐久服务器新一代硬件和技术全新应用于双11,提供行业领先的计算及存储生产力,场景化性能提升30%以上。今年云栖大会上,阿里巴巴发布自研磐久服务器系列,包括高性能计算系列、高性能存储系列、大容量存储系列。采用统一的方升架构,实现从芯片、部件到整机的技术及架构创新和自研,使用灵活模块化与全栈软硬件融合技术,提供多种通用和异构计算、多层级存储解决方案,为客户带来行业领先的、更高可靠性和更极致的性能体验。

3 双11云原生技术实现自研、开源、商业化三位一体

中间件迈向产品三位一体架构进行演进,后端BaaS化,运行时Mesh化,业务Serverless化,做到开箱即用,按量付费,全面提升研发运维效率。其中基础中间件进行BaaS改造,微服务升级到MSE,消息升级到MQ,可观测升级到ARMS,高可用升级到AHAS,全链路压测升级到PTS,升级成公有云产品,极致弹性,大促分钟级建站和扩容,大大提升建站效率。Mesh化在国际化大规模落地,把非业务逻辑下沉到Sidecar,让业务聚焦业务开发。业务Serverless化,淘宝导购前端100%上FC函数计算云产品,实现快速扩容、快速响应,帮助业务提升运维效率。未来预计将有更多企业采用阿里双十一同款中间件支撑业务峰值,快速共享中间件沉淀的技术和能力。

4 多模态大模型M6首次支持双11 落地40多个业务场景

超大规模模型成为人工智能领域的焦点。近期,阿里达摩院实现了全球首个10万亿参数的多模态大模型M6,并发布了对外平台,涵盖各项单模态和跨模态的理解及生成任务。作为国内首个商业化落地的多模态大模型,M6已在阿里巴巴超40个业务场景中应用,日调用量超5亿。

今年,大模型首次支持双11。依靠多模态理解能力,M6正在增进淘宝、支付宝等平台的搜索及内容认知精度;凭借流畅的写作能力,M6正为天猫虚拟主播等创作剧本和文案;M6已在犀牛智造上岗,其高清图像生成能力让设计师工作效率提升5倍,新款服饰开发周期从过去的1-3个月降低到最快2周,首批AI设计服装已在淘宝上线。

尤其值得一提的是,M6做到了业内极致的低碳高效,相比去年发布的大模型GPT-3,M6实现同等参数规模,能耗仅为其1%,极大降低了大模型实现门槛,并推动了普惠AI的发展。

5 IPv6大规模应用于双11

在过去三年多时间,阿里巴巴在技术生态中积极推动IPv6超大规模部署和应用,截止2021年10月,集团应用月活IPv6用户已经超过6亿。双11期间电商APP淘宝,天猫,闲鱼,考拉的移动互联网IPv6流量占比超过76%。淘宝的核心业务功能 “交易订单”链路已经完成IPv6改造并有1/3 流量通过IPv6承载,首次支撑双11大促。此外,钉钉已完成IPv6全链路改造,是国内首款端到端全链路支持IPv6-only的试点应用。

6 全栈式数字供应链技术创新,助力提升商业效率

数字供应链为行业与商家提供端到端的数字供应链解决方案,实现从采购到流通、从商家到消费者的全链路场景的覆盖。

今年双11数字供应链不断技术创新,自研深度时序预测算法Falcon,帮助商家进行智能的销量预测及补货入库,并打造了智能时序预测机器人产品DChain Forecast,让商家也具备高准确度的时序预测能力;持续建设供应链仿真平台,实现供应链全链路高保真仿真,支持品仓部署、网络规划等沙盘推演,优化库存管理,提升在架;提供智能履约决策能力,实现对仓配路由、订单集批、配送调度等作业场景的覆盖,保障远近场、标品生鲜等不同时效要求的服务目标达成。

本次大促,数字供应链驱动了超过数以亿件商品与订单的流动,以数字化技术助力提升商业效率与经济活力。

7 阿里灵杰产品全面拥抱云原生,性能和可用性全面提升

阿里灵杰全面拥抱云原生,包括MaxCompute上线统一调度、实时计算Flink版 VVP平台升级Serverless、Hologres 全面上线到云原生平台,带来集群稳定性和资源弹性能力的提升。MaxCompute统一调度集群承担了交易量的40%,性能也是混部集群中最高的,实时计算Flink版 引擎优化、Hologres高可用能力建设等帮助核心业务营销活动分析、生意参谋等节省大促资源的同时,可用性也有大的提升。

8 云原生架构在国际化全面铺开,结合业务架构创新进一步实践云原生价值

双11期间国际化所有核心应用100%云原生,100%ASI开启Mesh数据面能力,Mesh控制面下沉覆盖核心交易链路大部分核心系统,云原生去中心化网关架构成功在AE规模化落地,承接大促流量,并经受双11峰值流量考验;

业务架构:为提升架构持续演进能力,将网关类组件实现容器化剥离,基于容器编排部署,实现去中心化网关架构,为云原生业务架构演进树立新的标杆。

基础架构:以服务网格为底座,实现企业级流量治理标准与产品架构,支持了双十一数千应用服务级别流量管控与治理,实现了流量治理规则上线由以天为单位应用维度升级到秒级上线生效,人均效率提升1倍以上。

体验升级

1 淘宝3D沉浸式直播间、虚拟主播 打造沉浸式消费新体验

双11期间,淘宝直播融合3D沉浸式直播间、3D化商品与虚拟主播(AI数字人)为消费者带来全新的沉浸式消费新体验。

淘宝直播3D沉浸式直播间基于全球首个影视级扩展现实的云拍摄技术,突破了以往影视级XR直播拍摄中云渲染、AI、复杂光影模拟、云3D资产库等关键技术,支持主播仅用普通绿幕就可虚拟出上千平米的直播会场、近千盏舞台灯光、特种摄像设备的直播效果,成为首个具有低成本、可复用、低碳环保等优势的商家自播影视级拍摄解决方案。

与此同时,淘宝直播不仅利用先进的3D建模技术,将海量线下商品在线上实现3D化呈现,更在大数据与多模态技术的支持下,让直播间里的虚拟主播根据商家全量多模态商品介绍剧本,在直播过程中图文并貌地进行商品的讲解、对话互动、问题解答,为消费者带来数字空间消费新交互体验。

2 多语言技术创新促进跨境电商商家销售

创译技术融合改写和翻译能力,缓解低质量源语言商品信息的影响,显著提升了用户语言体验,目前已支持AliExpress、ICBU在十多个国家、数千万跨境商品信息的国际化;图像翻译技术自动定位、识别翻译区域,将中文图片自动转化为英文图片,将创意轻松国际化,实现了Lazada百万商品图片信息的国际化,图片翻译及编辑工具已赋能上千头部跨境商家;多语言生成技术还能整合碎片、零散的信息,生成富有吸引力的素材,为消费者提供及时丰富的讯息。

3 业务中台助力双11更加开放与保障

业务中台通过中台能力升级,为消费者和商家提供更好的购物体验。

消费者侧,不断打磨消费者购买链路细节来提升购买体验。在联想词推荐、自适应索引选择的加持下,用户可以快速搜索到已购买目标商品,加购好物且可以购物车一键分享,直播间多样商品也能组合下单;同时,业务中台通过电商&支付能力开放,助力多支付多优惠渠道打通,缩短用户整体支付路径,降低交易时长5.5s,提升用户支付体验。

商家侧,通过中台智能算法,包括商品库存预警、尖货爆品提前关注,0秒退款等能力,助力商家高效合理备货;同时针对直播间的权益投放,促进下单转化率最高提升6%,直播间主播战绩再攀高峰。

4 首创跨境B2B直播 优化音视频体验

2021下半年,新冠疫情反复、线下展会和跨境物流遇阻、汇率和上游原材料价格波动,外贸行业面临多重不确定性。阿里巴巴国际站去年首创跨境B2B直播,至今已累计进行30多万场。

为给客户更好的跨境直播和客服体验,达摩院XG实验室与视频云、企业智能等团队合作,通过音视频应用与网络传输优化跨层融合,实现极端弱网对抗能力,优化面向QoE的带宽估计、抗丢包技术,实现70%丢包对抗能力;通过深耕带宽估计、SVC、抗丢包等二十余项传输技术,保障了多种弱网场景下的音视频体验,音视频清晰度、卡顿率等核心体验指标均有所提升,视频卡顿率下降达80%,在极端弱网场景下表现尤为突出。

5 云原生数据仓库AnalyticDB:大幅提升查询分析实时性

阿里云数据库已连续多年稳定支撑双11,历经极端流量场景淬炼。除了保障稳定顺滑的基本盘,今年大促期间数据库通过全面云原生化,大幅提升用户体验,让技术帮助业务产生更有价值的消费者体验,持续通过技术创新赋能用户,引领技术发展路径。

今年,阿里集团通过引入阿里云原生关系型数据库PolarDB+云原生数据仓库AnalyticDB(ADB)+一站式在线数据管理平台DMS,赋能交易业务中台实现对全量订单进行便捷搜索,实现个性化的联想词推荐,让检索效率大大提升,有效提升了手淘用户的搜索体验。

此外,AnalyticDB今年还新增了数百个系统核心指标。通过丰富的工具以及不断沉淀的能力,提前发现风险,保障双11大促丝般顺滑,写入峰值百万级TPS无延时,同时大幅减少工程师工作量。

PolarDB+AnalyticDB+DMS还支撑了实时买卖家库同步、历史订单搜索等双11核心业务场景。

6 云原生内存数据库Tair:确保购物车实时显示用户到手价

阿里云原生内存数据库Tair作为历年双11大促承载流量洪峰的利器,不仅在数十亿QPS的峰值下保持着亚毫秒级别的顺滑延迟,同时在电商交易核心体验场景上也做出了技术创新。

通过原生持久内存存储结构,Tair助力购物车场景实现了高并发流量下、实时显示动态优惠和券后到手价,让用户对商品实付价一目了然,显著提升消费者购买体验。

据悉,阿里自研内存数据库Tair诞生于2010年,是一种支持高并发低延迟访问的云原生内存数据库,完全兼容Redis,已历经多年双11大促考验,提供核心在线访问加速能力,显著提升系统吞吐量。

7 科技适老,让数字服务不产生数字鸿沟

目前,阿里巴巴12款APP加入了无障碍行动,让每个人都平等享受技术带来的红利。淘宝“长辈模式”,通过引入字体大小适配,个性化推荐,ASR语音转化等技术,消除数字鸿沟,帮助老年朋友看得清,看得懂,看得放心;

淘菜菜针对老年群体提供“银发版”,字号更大,操作更简便,同时优化低端机用户体验,小程序打开速度提升50%,加购性能提升30%;

饿了么持续优化银发用户和残障人士的使用体验,覆盖导购、搜索、下单全流程。在研发长辈模式过程中,优化了字体大小和银发人群常使用的功能模块。此外,饿了么还专门为无声蓝骑士上线了无障碍沟通系统,以及通过智能调度系统,为残障骑手匹配接单,过去一年,智能调度系统让这些骑手单均跑单距离减少7%,让数十万障碍和银发用户更好地享受本地生活服务。

产业智能

1 AI图像生成技术助力快反时装设计

在时装行业,如何高效、准确地量产符合市场趋势的创新设计款式,缩短新款培育周期与库存风险,一直是商家的痛点难题。

通过AI图像生成技术,阿里技术研发了一套智能趋势设计解决方案,可根据趋势描述,实时产出高分辨率款式设计图,支持不同层次的编辑操作,并通过个性化分发降低品牌同质化,从而提高了新品的研发速度与精准度,让品牌与消费者的需求对接更顺畅。

在今年的“双11”应用中,多家鞋靴商家应用并上架了几千款AI设计商品。

2 小蛮驴无人车实现大规模配送

今年双11,350多辆阿里“小蛮驴”携手菜鸟驿站,开进全国高校,每天配送快递5万多件,预计配送快递超100万件。“小蛮驴”将自动驾驶技术与菜鸟末端物流场景、即时配送场景相结合,实现了产品驱动的自动驾驶规模化落地。在物流产业中,末端物流一直是整个物流体系中成本最高、效率最低的环节,无人车开在非结构化的末端场景,需要识别处理大量的交通参与者,比如机动车、非机动车、行人、动物等,以及其他的突发状况。100万单的背后,是小蛮驴的算法技术、运营技术和安全保障能力在做支撑,以算法为例,小蛮驴目前每天单车日均识别障碍物约4000万,日均处理与其他交通参与者的交互约5000次。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…282283284…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%