首页 文章 精选 留言 我的

精选列表

搜索[MSA技术],共10000篇文章
优秀的个人博客,低调大师

如何优雅的处理异常 | 京东云技术团队

作者:京东零售 秦浩然 一、什么是异常 Java 语言按照错误严重性,从 throwale 根类衍生出 Error 和 Exception 两大派系。 Error(错误): 程序在执行过程中所遇到的硬件或操作系统的错误。错误对程序而言是致命的,将导致程序无法运行。常见的错误有内存溢出,jvm 虚拟机自身的非正常运行,calss 文件没有主方法。程序本生是不能处理错误的,只能依靠外界干预。Error 是系统内部的错误,由 jvm 抛出,交给系统来处理。 Exception(异常): 程序正常运行中,可以预料的意外情况。比如数据库连接中断,空指针,数组下标越界。异常出现可以导致程序非正常终止,也可以预先检测,被捕获处理掉,使程序继续运行。Exception(异常)按照性质,又分为编译异常(受检异常)和运行时异常(非受检异常)。 ◦ 编译异常: 又叫可检查异常,通常时由语法错和环境因素(外部资源)造成的异常。比如输入输出异常 IOException,数据库操作 SQLException。其特点是,Java 语言强制要求捕获和处理所有非运行时异常。通过行为规范,强化程序的健壮性和安全性。 ◦ 运行时异常: 又叫不检查异常 RuntimeException,这些异常一般是由程序逻辑错误引起的,即语义错。比如算术异常,空指针异常 NullPointerException,下标越界 IndexOutOfBoundsException。运行时异常应该在程序测试期间被暴露出来,由程序员去调试,而避免捕获。 二、处理异常方式 代码中,我们最常见到的处理异常的方式就是:try-catch try { // 业务逻辑 } catch (Exception e) { // 捕获到异常的逻辑 } 或者是再进一步区分下异常类型: try { // 业务逻辑 } catch (IOException ie) { // 捕获到IO异常的逻辑 } catch (Exception e) { // 捕获到其他异常的逻辑 } 三、如何抛出异常 我们通常可以用抛出异常的方式来控制代码流程,然后在网关处统一catch异常来返回错误code。这在一定程度上可以简化代码流程控制,如下所示: @Override public UserVO queryUser(Long id) { UserDO userDO = userMapper.queryUserById(id); if (Objects.isNull(userDO)) { throw new RuntimeException("用户不存在"); //用户不存在抛出异常 } return userDO.toVo(); } 上面这种抛出异常的方式,虽然简化了代码流程,但是在存在多种错误场景时,没有办法细分具体的错误类型。如:用户不存在的错误、用户没有权限的错误; 聪明如你,一定想到了自定义异常,如下: @Override public UserVO queryUser(Long id) { UserDO userDO = userMapper.queryUserById(id); if (Objects.isNull(userDO)) { throw new UserNotFoundException(); //用户不存在抛出对应异常 } if(!checkLicence(userDO)) { throw new BadLicenceException(); //用户无权限抛出对应异常 } return userDO.toVo(); } 确实,自定义异常可以解决错误场景细分的问题。进一步的,我们可以对系统流程不同阶段、不同业务类型分别自定义异常,但这需要自定义大量的异常; 四、如何优雅的抛出异常 上面的方式,可以区分出错误场景了,但是还存在一些缺点。如:可读性差、需要定义大量的自定义异常; 那我们下面就去优化上面的问题; 用断言增加代码的可读性; @Override public UserVO queryUser(Long id) { UserDO userDO = userMapper.queryUserById(id); Assert.notNull(userDO, "用户不存在"); //用断言进行参数的非空校验 return userDO.toVo(); } 断言虽然代码简洁、可读性好,但是缺乏像上述自定义异常一样可以明确区分错误场景,这就引出我们的究极方案:自定义断言; 自定义断言; 我们用自定义断言的方式,综合上面自定义异常和断言的优点,在断言失败后,抛出我们制定好的异常。代码如下: • 自定义异常基本类 @Getter @Setter public class BaseException extends RuntimeException { // 响应码 private IResponseEnum responseEnum; // 参数信息 private Object[] objs; public BaseException(String message, IResponseEnum responseEnum, Object[] objs) { super(message); this.responseEnum = responseEnum; this.objs = objs; } public BaseException(String message, Throwable cause, IResponseEnum responseEnum, Object[] objs) { super(message, cause); this.responseEnum = responseEnum; this.objs = objs; } } • 自定义断言接口 public interface MyAssert { /** * 创建自定义异常 * * @param objs 参数信息 * @return 自定义异常 */ BaseException newException(Object... objs); /** * 创建自定义异常 * * @param msg 描述信息 * @param objs 参数信息 * @return 自定义异常 */ BaseException newException(String msg, Object... objs); /** * 创建自定义异常 * * @param t 接收验证异常 * @param msg 描述信息 * @param objs 参数信息 * @return 自定义异常 */ BaseException newException(Throwable t, String msg, Object... objs); /** * 校验非空 * * @param obj 被验证对象 */ default void assertNotNull(Object obj, Object... objs) { if (obj == null) { throw newException(objs); } } /** * 校验非空 * * @param obj 被验证对象 */ default void assertNotNull(Object obj, String msg, Object... objs) { if (obj == null) { throw newException(msg, objs); } } } 上述代码我们可以看出基本设计,就是在我们自定义断言失败后抛出我们自定义异常。 下面是具体的实现案例: • 自定义业务异常类,继承自异常基本类 public class BusinessException extends BaseException { public BusinessException(IResponseEnum responseEnum, Object[] args, String msg) { super(msg, responseEnum, args); } public BusinessException(IResponseEnum responseEnum, Object[] args, String msg, Throwable t) { super(msg, t, responseEnum, args); } } • 响应code枚举接口定义 public interface IResponseEnum { /** * 返回code码 * * @return code码 */ String getCode(); /** * 返回描述信息 * * @return 描述信息 */ String getMsg(); } • 自定义业务异常类断言定义,实现自定义断言失败后对应的自定义异常的定义; public interface BusinessExceptionAssert extends IResponseEnum, MyAssert { @Override default BaseException newException(Object... args) { return new BusinessException(this, args, this.getMsg()); //断言失败后,抛出自定义异常 } @Override default BaseException newException(String msg, Object... args) { return new BusinessException(this, args, msg); //断言失败后,抛出自定义异常 } @Override default BaseException newException(Throwable t, String msg, Object... args) { return new BusinessException(this, args, msg, t); //断言失败后,抛出自定义异常 } } • 用枚举的方式,代替BadLicenceException、UserNotFoundException自定义异常。 public enum ResponseEnum implements IResponseEnum, BusinessExceptionAssert { BAD_LICENCE("0001", "无权访问"), USER_NOT_FOUND("1001", "用户不存在"), ; private final String code, msg; ResponseEnum(String code, String msg) { this.code = code; this.msg = msg; } @Override public String getCode() { return code; } @Override public String getMsg() { return msg; } } 使用实例 自定义断言失败抛出自定义异常 @Override public UserVO queryUser(Long id) { UserDO userDO = userMapper.queryUserById(id); ResponseEnum.USER_NOT_FOUND.assertNotNull(userDO); //自定义断言失败抛出自定义异常 return userDO.toVo(); } 网关处统一catch异常,识别异常场景 public static void main(String[] args) { UserService userService = new UserServiceImpl(new UserMapperImpl()); UserController userController = new UserController(userService); try { UserVO vo = userController.queryUser(2L); //执行业务逻辑 } catch (BusinessException e) { System.out.println(e.getResponseEnum().getCode()); //出现异常,错误code:1001 System.out.println(e.getMessage()); //出现异常,错误msg:用户不存在 } } 五、如何优雅的处理异常 网关处统一处理异常,这属于常规操作,这里不再赘述,简单举例如下: @ControllerAdvice public class BusinessExceptionHandler { @ExceptionHandler(value = BusinessException.class) @ResponseBody public Response handBusinessException(BaseException e) { return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg()); //统一处理异常 } } 综上,我们采用自定义断言的方式,结合了断言的可读性高的优势和自定义异常区分错误场景的优势。并且,有新增的错误场景,我们只需要在错误码枚举中新增对应枚举即可。

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

KaiwuDB 时序引擎数据存储内存对齐技术解读

一、理论 1、什么是内存对齐 现代计算机中内存空间都是按照 byte 划分的,在计算机中访问一个变量需要访问它的内存地址,从理论上看,似乎对任何类型的变量的访问都可以从任何地址开始。 但在实际情况中,通常在特定的内存地址才能访问特定类型变量,这就需要对数据在内存中存放的位置有限制。各种类型不是按照顺序排放,它们需要根据一定的规则在空间上排列,这就是对齐。 2、为什么需要内存对齐 (1)移植原因: 不是所有的硬件平台都能访问任意地址上的任意数据的,各个硬件平台对存储空间的处理上有很大的不同,部分平台对某些特定类型的数据只能从某些特定地址开始存取。 比如,市面上有些架构的 CPU 在访问一个没有进行对齐的变量时会发生错误,这时 CPU 会进入异常处理状态并且通知程序不能继续执行。 举个例子,在 ARM 硬件平台上,当操作系统被要求存取一个未对齐数据时会默认给应用程序抛出硬件异常。所以,如果不进行内存对齐,代码就不具有移植性,而且难以开展在很多平台上的开发工作。 (2)性能原因: 尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的。它一般会以双字节、4 字节、8 字节、16 字节甚至 32 字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度。 如果变量的地址没有对齐,可能需要多次访问才能完整读取到变量内容,而对齐后可能就只需要一次内存访问。因此,内存对齐可以减少 CPU 访问内存的次数,提高 CPU 访问内存的吞吐量。 举个例子,考虑 4 字节存取粒度的处理器访问 int 类型变量,该处理器只能从地址为 4 的倍数的内存地址开始读取数据。如果未经过内存对齐,获取该 int 类型的数据需要进行两次内存访问,最后再进行数据整理得到完整数据: 如果经过内存对齐,一次内存访问就能得到完整数据,减少了一次内存访问: CPU 读取内存是高耗时的指令,内存对齐是在内存的使用量和 CPU 计算间的居中的优化策略。这种策略是由编译器和 CPU 共同决定,并且程序员可以设置对齐的长度。 通过上述介绍的内存对齐的必要性,我们可以知道,如果不理解内存对齐,在编程时就可能产生下面的问题: (1) 程序运行速度变慢; (2) 应用程序产生死锁; (3) 操作系统崩溃; (4) 程序会毫无征兆的出错,产生错误的结果。 但是,我们在写程序时一般无需考虑对齐,通常是依赖编译器来为我们选择适合的对齐策略。 3、内存对齐规则 在学习内存对齐规则前,我们先一起了解下四个重要的基本概念: 指定对齐值: #pragma pack (n) 时指定的对齐值 n; 基本数据类型的自身对齐值: 基本数据类型自身占用的存储空间大小,如 char 类型为 1,short 类型为 2,int 类型为 4,double 类型为 8 等; 结构体或类类型的自身对齐值: 结构体或类的成员中自身对齐值最大的值,如 struct a 中有 char、int 和 double 共 3 个类型的数据成员,那么 struct a 的自身对齐值是 8 字节; 数据成员、结构体和类的有效对齐值: 自身对齐值或指定对齐值中的较小值。 了解上述概念后,我们一起来了解具体的内存对齐规则。内存地址对齐包含了两种相互独立又相互关联的部分:基本数据对齐和结构体数据对齐。 基本数据对齐比较简单,其自身对齐值就等于自身占用的存储空间大小,可以通过 alignof 获取一个类型的对齐值。 结构体数据对齐需要保证结构体的数据成员对齐以及结构体的整体对齐: (1)数据成员对齐规则: 第一个数据成员放在 offset 为 0 的地方,也就是结构体变量本身的起始地址,以后每个数据成员的偏移为其有效对齐值的整数倍; (2)结构体整体对齐规则: 在数据成员完成各自对齐后,结构体本身也要进行对齐,对齐会将结构体的大小增加为该结构体有效对齐值的整数倍,如有需要编译器会在最末成员后加上填充字节。 二、应用 1、前提介绍 在 KaiwuDB 时序引擎中,一条时序数据由不同的列数据组成,其中每列都对应一种数据类型。在存储的代码实现中,每条时序数据都存放在一块连续的内存空间里,不同列的数据按照列的顺序连续紧邻的排放,如下图所示: 值得一提的是,上图示例中的char类型,并非是一个字符,而是代表一个不定长的字符数组,也就是说这条数据的第二列,存放的是一个长度为 3 的 char 数组。另外,TIMESTAMP 的类型是 uint64_t,长度为 8 字节;Bitmap 的类型也是 char 数组,长度不定。 显然,这种存放方式并不满足内存对齐的要求,会对我们的程序产生两种可能的影响: (1)在不同硬件平台上的程序 crash; (2)降低存取效率。 由于我们的程序需要在 ARM 平台上运行,而且会有高密集地进行内存访问,所以时序数据存放满足内存对齐要求是十分必要的。 2、使用内存对齐生成存储格式 存储记录的数据类型可以分类为两种: (1)定长: TIMESTAMP、SMALLINT、INT、BIGINT、FLOAT、DOUBLE、BOOL、BINARY,长度包括 8、4、2、1 字节,都属于基本数据类型。前文介绍过,基本数据类型的对齐系数等于自身的类型长度。 (2)不定长: char、Bitmap,这两种类型都是不定长的 char 数组,基本组成元素都是 char 类型,因此它们的对齐系数都为 1。 由上可知,目前存储中存放的都是一些基本数据类型记录,不存在结构体或类类型。在这个条件下,一条时序数据的格式满足内存对齐的要求就相对而言比较简单了,存储格式生成方案如下: 对于定长的数据类型对应的列,按类型长度降序排列,先存放 8 字节的列、再存放 4 字节的列、再存放 2 字节和 1 字节的列,这样就可以满足内存对齐的要求。 存放完定长的列后,char和 Bitmap 都是 char 数组,对齐系数都为 1,所以可以直接存放在定长的列之后,先存放 char 类型,最后存放 Bitmap。 通过上述内存对齐的存储格式生成方案生成的一条时序数据的格式如下: 3、测试 通过编写一个简单的测试,验证一下内存对齐对存取效率的影响。 测试场景: 内存对齐系数为 8 字节,申请 10K 内存空间,在内存对齐和非内存对齐的情况下,分别写入数据 1G 次并且读取数据 1G 次。写入的数据类型均为 uint64_t,长度 8 字节,内存写满后循环使用。分别统计其耗时并进行对比,测试代码和测试结果如下: 测试结果显示,在只进行读写操作的情况下,进行 1G 次读写操作,非内存对齐要比内存对齐的耗时多 24.3% 左右。

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

Ray 2.0 发布,OpenAI 背后的机器学习技术

Ray 是一个统一的框架,用于扩展人工智能和 Python 应用程序。Ray 由一个核心的分布式运行时和一个用于加速 ML 工作负载的工具包(Ray AIR)组成。 Ray 2.0 是 Ray 的大幅度更新版本,对 Ray 生态中的所有库都进行了增强。通过这个重要的版本,Ray 朝着使分布式计算可扩展、统一和开放的目标大步迈进。 为了实现这些目标,Ray 2.0 具有统一机器学习(ML)生态的新功能、改善了 Ray 的生产支持,并使 ML 从业者比以往更容易使用 Ray 的库。 此次更新值得关注的重要内容包括: Ray AIR:是一个可扩展的、统一的 ML 应用工具包,现在是 Beta 版 简化了 ML 应用程序开发,提高了开发人员的速度,可与其他框架(如 Tensorflow、PyTorch、Hugging Face 等)进行互操作。 Ray 现在支持使用 Ray Datasets 库对 100TB 或更多的数据进行本地 shuffle KubeRay:是一个用于在 Kubernetes 上运行 Ray 的工具包,现在是 Beta 版。它取代了传统的基于 Python 的 Ray operator。 Ray Serve 的 Deployment Graph API 是建立、测试和部署部署推理图的一种新的、更简单的方法,在 2.0 中作为 Beta 版发布。 更多详情可查看:https://github.com/ray-project/ray/releases/tag/ray-2.0.0

资源下载

更多资源
腾讯云软件源

腾讯云软件源

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

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

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

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册