一文搞透接口层编码的那些事

关键词:编码、设计、Java、服务端、后端、接口、接口层、分层、需求、设计、系统、inerface、接口安全、接口性能、接口参数、接口异常、接口错误码、统一日志、接口返回值、接口定义、接口设计

1. 接口层编码准备

1.1 理解设计

一般而言,在开始编码前,已经完成了需求设计与评审、详细设计与评审。

在正式编码前,应充分理解设计要求,保持编码与设计一致:

  1. 需求理解

结合《需求文档》《交互设计》的需求描述,以及《详细设计》的需求分析部分,充分理解需求、评估风险。

  1. 设计理解
  • 理解《详细设计》中的物理架构变更,明确编码模块所处组件位置。
  • 理解《详细设计》中的领域模型变更,明确编码模块的领域设计,作为后续对模块、包、类的编码依据。
  • 理解《详细设计》中的技术路线变更,明确需引入、升级、部署的依赖组件,进行工程、环境搭建。
  1. 系统设计

充分理解系统设计的内容,作为后续编码的依据。

1.2 补充设计

有时候,因客观因素,导致详细设计不充分。这时,尽量不要无设计开发,容易引发较大问题。

最基本的,要进行以下动作:

  • 领域先行,数据是一切的基础。一般而言,数据是一个系统的基石。接口如果涉及底层数据表的改动,必须进行必要的评审。草率的改动,可能给会荼毒后续版本、甚至带来灾难性的后果。
  • 先对齐再编码,提前接口定义。现在基本上是前后端分离开发,提前进行接口定义,便于提前深入交流需求和设计,对齐前后端思想,提前发现问题。否则,将会有极大的重复返工的可能性,反而降低了整体效率。
  • 先思考再动手,捋清关键逻辑。对复杂的接口,提前梳理实现逻辑,按需绘制流程图、序列图、状态图等。与产品经理、开发组长、测试组长等关键角色同步,决策关键点。便于提前暴露风险,减少过程反复。
  • 识风险控进度,分析非功能指标。有时产品经理不容易暴露所有的非功能需求,研发提前分析识别,有助于避免方案一而再、再而三的改动,导致开发进度一延再延。

2. 接口层编码要求

2.1 接口分解

详细阅读《详细设计》中关于接口定义的部分,可以拆解为以下方面:

2.2 关注要点

基于上述拆解,在接口设计与代码实现上,需重点关注以下要求:

类型

位置

要求

描述

接口设计

设计原则

符合单一职责原则

只负责一个职责,推荐以对象+行为进行细粒度拆分,功能单一灵活才好维护和扩展。

复合接口隔离原则

不强迫依赖不需要的接口。

充分原则

接口不随意添加/删除/修订,必要性要充分。

统一原则

API要具备统一的命名规范、出入参规范、异常规范、错误码规范、版本规范等。

基本信息

接口命名

接口命名应符合命名规范

模块名、类名、接口名,以及参数和返回值名、变量与常量等接口命名应符合命名规范。

接口协议

接口协议明确统一

不同协议接口独立管理。对于HTTP接口,统一以 POST 为主,一致>>更优。

接口路径

命名规范

路径命名符合编码规范

出参入参

接口参数值

实体隔离

接口使用独立的VO,不与其他接口共用,更不使用下层代码的BO或PO。

字段最小化原则

接口参数只定义需要的字段,及时清理过期的字段,非必要不加入冗余字段。

参数校验

接口参数需要进行统一的参数校验,推荐使用validation。

参数安全

接口参数需要防XSS/CSRF/CORS攻击、防SQL注入等。

UGC安全

用户UGC数据接入内容审核平台。

接口返回值

返回值格式统一

接口的返回值使用统一的响应实体封装,格式统一。

错误码统一清晰

接口使用统一规范的错误码,涵盖错误消息、业务异常等。

字段最小化原则

接口返回值字段只包含需要的字段,非必要不返回冗余字段。

敏感信息脱敏

对敏感信息脱敏处理。

接口编码

接口层编码

接口层单一职责

接口层只应负责参数校验处理、请求转发等,不应参与实际业务操作。

统一异常处理

使用统一异常捕获处理,而非每个接口try-catch。

接口性能

性能

接口响应性能

提升性能可使用异步、缓存等方法。

(具体策略,详见下文《性能十八打》)

接口吞吐性能

接口并发性能

安全

认证授权

接口应统一进行用户身份认证和授权,包括接口权限、数据权限等。

请求安全

接口应对请求进行限制和过滤,应考虑使用防篡改、防重放策略。

内容安全

防XSS/CSRF/CORS攻击、防SQL注入,敏感信息需要脱敏、UGC信息需要内容审核。

传输安全

信息传输安全,通常需要使用 SSL/TLS 或其他加密协议,以确保请求和响应数据在传输过程中得到保护。

日志记录

关键接口、关键步骤一定要记录日志。

兼容性

接口版本

接口需要控制版本,修改老接口时,应重点关注接口的兼容性。

可靠性

接口容错

接口实现需考虑限流、熔断、降级,以及资源隔离、第三方容错。

幂等性

接口幂等

考虑接口的幂等性需要,防止重复处理。

可维护性

接口文档内容完整

接口数量和定义完整,推荐使用YApi、Swagger等工具。

接口文档更新及时高效

接口文档更新及时高效,推荐使用YApi、Swagger等工具。

接口注释完整

接口注释应充足,关键逻辑应详细注释、及时更新。

可测性

接口应可测试

单元测试、测试用例。

3. 接口层编码实践

3.1 接口命名应符合编码规范

接口命名:以动词短语命名,符合命名规范。

接口方法以动词短语命名,常见的命名方式推荐:

常见命名

含义

get***  

获取单个对象

list***

获取多个对象

count***

计数,获取统计值

add/insert***

新增

remove/delete***

删除

edit/update***

修改

find/search***

查询

do/execute***

执行某个过程或者流程

init***

初始化

check/validate***

校验合法性

3.2 统一接口参数校验

接口参数:每个接口单独定义VO,不与其他接口的VO共用,更不与其他BO或PO共同。

实体类定义:

@Setter
@Getter
public class UserAddQuery {
    /**
     * 用户名
     */
    @NotBlank
    private String username;
    /**
     * 昵称
     */
    @NotBlank
    @Length(message = "名称不能超过个 {max} 字符", max = 10)
    private String nickname;
    /**
     * 身份证号
     */
    @NotBlank
    private String idCard;
    /**
     * 年龄
     */
    @NotNull
    @Range(message = "年龄范围为 {min} 到 {max} 之间", min = 1, max = 200)
    private Integer age;
}

Java

接口参数:接口参数需要进行参数校验。推荐使用validation。

参数校验统一拦截器:

@RestControllerAdvice
public class CommonExceptionHandler {
    /**
     * 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Response<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        return Response.fail(ErrorCode.USER_ERROR_3132);
    }
    /**
     * 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
     */
    @ExceptionHandler({ConstraintViolationException.class})
    public Response<?> handleConstraintViolationException(ConstraintViolationException ex) {
        return Response.fail(ErrorCode.USER_ERROR_3132);
    }
}

Jav

3.3 统一接口返回值定义

接口响应:接口的返回值应使用统一的响应实体封装。

定义统一响应实体,例如:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Response<T> extends DTO {
    private static final long serialVersionUID = 1L;
    private boolean success;
    /**
     * @see ErrorCode
     */
    private String errCode;
    private String errMessage;
    private T data;

    public static Response<?> succeed() {
        return new Response<>(true, null, null, null);
    }
    public static <T> Response<T> succeed(T data) {
        Response<T> response = new Response<>();
        response.setSuccess(true);
        response.setData(data);
        return response;
    }
    public static Response<?> fail(ResponseCode errorCode) {
        return new Response<>(false, errorCode.getCode(), errorCode.getMessage(), null);
    }
}

Java

3.4 统一异常和错误码

接口响应:接口使用统一规范的错误码。

错误码示例:

/**
统一错误码
<p>
1xxx:高优问题:如数据库失败、支付失败、登录短信发送失败等
2xxx:中优问题:如推送短信发送失败、营销短信发送失败等
3xxx:低优问题:需要达到一定的频率才告警,例如5分钟内超过20次登录失败、5分钟内超过10次优惠券兑换失败等
*/
public enum AlertCode implements ResponseCode {
    /**
     * 一级宏观错误码:用户
     */
    USER_ERROR_3100("3100", "用户端错误"),
    /*
     * 二级宏观错误码:用户-注册
     */
    USER_ERROR_3101("3101", "用户名校验失败"),
    USER_ERROR_3102("3102", "用户名已存在"),
    USER_ERROR_3103("3103", "用户名包含敏感词"),
    USER_ERROR_3104("3104", "用户名包含特殊字符"),
    /*
     * 二级宏观错误码:用户-登录
     */
    USER_ERROR_3110("3110", "用户账户不存在"),
    USER_ERROR_3112("3112", "用户账户被冻结"),
    USER_ERROR_3113("3113", "用户密码错误"),
    USER_ERROR_3114("3114", "用户输入密码错误次数超限"),
    /*
     * 二级宏观错误码:用户-访问
     */
    USER_ERROR_3120("3120", "访问未授权"),
    USER_ERROR_3121("3121", "授权已过期"),
    USER_ERROR_3122("3122", "无权限使用 API"),
    USER_ERROR_3123("3123", "用户访问被拦截"),
    /*
     * 二级宏观错误码:用户-输入
     */
    USER_ERROR_3130("3130", "用户请求参数错误"),
    USER_ERROR_3131("3131", "无效的用户输入"),
    USER_ERROR_3132("3132", "请求必填参数为空"),
    USER_ERROR_3133("3133", "非法的时间戳参数"),
    USER_ERROR_3134("3134", "请求参数值超出允许的范围"),
    USER_ERROR_3135("3135", "参数格式不匹配"),
    /*
     * ... ...
     */
    
    ;
    private final String code;
    private final String message;
    
    AlertCode(String code, String message) {
        this.code = code;
        this.message = message;
    }
    
    @Override
    public String getCode() {
        return code;
    }
    
    @Override
    public String getMessage() {
        return message;
    }
}

Java

3.5 统一鉴权

统一鉴权

3.6 统一日志

统一日志

3.7 常见的接口编码实现

代码逻辑:接口层只应进行参数的校验、转换,以及请求转发。不建议有实际的业务操作。

以HTTP接口为例,根据详细设计中关于接口的定义,编写接口层代码:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
    
    @PostMapping("/add")
    @ResponseBody
    public Response<?> add(@Valid @RequestBody UserAddQuery query) {
        return userService.add(query);
    }
    
    @PostMapping("/edit")
    @ResponseBody
    public Response<?> edit(@Valid @RequestBody UserEditQuery query) {
        return userService.edit(query);
    }
    
    @PostMapping("/remove")
    @ResponseBody
    public Response<?> remove(@Valid @NotBlank String userId) {
        return userService.remove(userId);
    }
    
    @GetMapping("/{userId}")
    @ResponseBody
    public Response<GetUserResponse> get(@PathVariable @Valid @NotBlank String userId) {
        return userService.get(userId);
    }
}

Java

3.8 常见安全攻击和解决方案

3.8.1 常见的安全攻击

3.8.1.1 未授权的访问

未经授权的访问是 API 安全中最常见的问题之一。攻击者可以使用未经授权的凭据或者伪造请求,获取对受保护的资源的访问权限。这种攻击可能导致敏感信息泄露、恶意操作等风险。

以下是一个未经授权的访问示例,攻击者使用伪造的请求头部信息获取了对资源的访问权限:

GET /api/resources/1 HTTP/1.1Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

HTTP

3.8.1.2 SQL 注入和其他注入攻击

SQL 注入是一种常见的攻击方式,攻击者通过在请求参数中注入恶意的 SQL 语句,获取敏感信息或者修改数据库记录。其他注入攻击包括跨站点脚本攻击(XSS)等,攻击者可以在请求参数中注入恶意的脚本代码,获取敏感信息或者执行恶意操作。

以下是一个 SQL 注入攻击的示例,攻击者在请求参数中注入恶意的 SQL 语句,获取了数据库中的敏感信息:

GET /api/resources?id=1;SELECT * FROM users WHERE username='admin'-- HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

HTTP

3.8.1.3 跨站点请求伪造(CSRF)

跨站点请求伪造(CSRF)是一种攻击方式,攻击者通过在受信任网站上伪造请求,使用户在不知情的情况下执行恶意操作。例如,攻击者可以在电子邮件中包含一个恶意链接,用户点击链接后会在受信任的网站上执行恶意操作。

以下是一个 CSRF 攻击的示例,攻击者伪造了一个请求,向受信任的网站提交了恶意数据:

<html>
    <body>
        <form action="https://api.example.com/api/resources" method="POST">
            <input type="hidden" name="name" value="恶意数据">
            <input type="hidden" name="amount" value="1000000">
            <input type="submit" value="提交">
        </form>
    </body>
</html>

HTML

3.8.1.4 拒绝服务攻击(DoS)

拒绝服务攻击(DoS)是一种攻击方式,攻击者通过向 API 发送大量请求,使得 API 无法正常工作。这种攻击可能导致 API 无法响应正常的请求,影响服务的可用性和稳定性。

以下是一个 DoS 攻击的示例,攻击者向 API 发送了大量的请求,占用了大量的资源:

GET /api/resources?id=1 HTTP/1.1
Host: api.example.com

GET /api/resources?id=2 HTTP/1.1
Host: api.example.com

GET /api/resources?id=3 HTTP/1.1
Host: api.example.com

HTTP

3.8.2 常见安全解决方案

为了保护 API 免受恶意攻击和滥用,开发者可以采取以下几个方面的措施:

  1. 认证和授权:使用 OAuth2.0 或其他身份验证和授权协议,对请求进行身份验证和授权,确保只有授权用户才能访问受保护的资源。
  1. 加密和传输安全:使用 SSL/TLS 或其他加密协议,以确保请求和响应数据在传输过程中得到保护。对于敏感信息,可以使用对称加密或非对称加密进行加密处理。
  1. 输入验证和防止注入攻击:对输入数据进行验证和过滤,例如使用正则表达式或其他方法过滤掉非法字符或语句,防止 SQL 注入、XSS 等攻击。
  1. 防止拒绝服务攻击:对请求进行限制和过滤,例如限制每个用户的请求频率、限制请求的数据量和频率等,以防止恶意攻击者对 API 进行过度使用和占用资源。
  1. 日志记录和监控:对 API 的请求和响应进行日志记录和监控,及时发现异常情况和恶意攻击,并采取相应的措施进行处理。

3.9 常见接口性能优化方案

3.9.1 性能数字概念

在评估接口性能时,我们需要首先找出最耗时的部分,并优化它,这样优化效果才会立竿见影。

系统延时的各种数字概念:

跨地域的耗时计算:

我们已知光在真空中传播,折射率为 1,其光速约为 c=30 万公里/秒,当光在其他介质里来面传播,其介质折射自率为 n,光在其中的速度就降为 v=c/n,光纤的材料是二氧化硅,其折射率 n 为 1.44 左右,计算延迟的时候,可以近似认为 1.5,我们通过计算可以得出光纤中的光传输速度近似为 v=c/1.5= 20 万公里/秒。

以北京和深圳为例,直线距离 1920 公里,接近 2000 公里,传输介质如果使用光纤光缆,那么延迟时间 t=L/v = 0.2 万公里/20 万公里/秒=10ms ,也就是说从北京到深圳拉一根 2000 公里的光缆,单纯的距离延迟就要 10ms ,实际上是没有这么长的光缆的,中间是需要通过基站来进行中继,并且当光功率损耗到一定值以后,需要通过转换器加强功率以后继续传输,这个中转也是要消耗时间的。另外数据包在网络中长距离传输的时候是会经过多次的封包和拆包,这个也会消耗时间。

综合考虑各种情况以后,以北京到深圳为例,总的公网延迟大约在 40ms 左右,北京到上海的公网延迟大约在 30ms,如果数据出国的话,延迟会更大,比如中国到美国,延迟一般在 150ms ~ 200ms 左右,因为要经过太平洋的海底光缆过去的。

中间件的耗时和并发的基本数字:

对于机房内的访问,Redis缓存的访问耗时通常在1-5毫秒之间,而数据库的主键索引访问耗时在5-15毫秒之间。当然,这两者最大的区别不仅仅在于耗时,而更重要的是它们在承受高并发访问方面的能力。Redis单机可以承受10万并发(往往瓶颈在网络带宽和CPU),而MySQL要考虑主从读写分离和分库分表,才能稳定支持5千并发以上的访问。

3.9.2 前端层面优化

3.9.2.1 页面静态化

静态化加速方法:

  • 将访问频繁,但基本不变的页面拆分、并静态化为HTML文件。
  • 注入HTML文件到CDN,可以避免每次用户的请求都访问原始服务器,可以分担负载、提高响应和吞吐。
3.9.2.2 利用浏览器缓存

有两种较常用的应用场景值得使用缓存策略一试,当然更多应用场景都可根据项目需求制定。

  • 频繁变动资源:设置Cache-Control:no-cache,使浏览器每次都发送请求到服务器,配合Last-Modified/ETag验证资源是否有效
  • 不常变化资源:设置Cache-Control:max-age=31536000,对文件名哈希处理,当代码修改后生成新的文件名,当HTML文件引入文件名发生改变才会下载最新文件
3.9.2.3 渲染优化
  • CSS策略:基于CSS规则,避免多层嵌套,避免重复匹配重复定义,关注可继承属性等
  •  DOM策略:基于DOM操作,避免过多DOM
  •  阻塞策略:基于脚本加载,区分设置defer和async
  •  回流重绘策略:基于回流重绘
  •  异步更新策略:基于异步更新

3.9.3 数据库层面优化

3.9.3.1 SQL优化

通过查看线上日志或者监控报告,查到某个接口用到的某条sql语句耗时比较长。

索引优化判断:

  • 该sql语句加索引了没?
  • 加的索引生效了没?
  • 加的索引合理吗?

MySQL常见索引失效的场景:

  1. 查询表达式索引项上有函数,将无法使用索引
  1. 一次查询(简单查询,子查询不算)只能使用一个索引
  1. 不等于!= 无法使用索引
  1. 未遵循最左前缀匹配导致索引失效
  1. 类型转换导致索引失效,例如字符串类型指定为数字类型等
  1. like模糊匹配以通配符开头导致索引失效
  1. 索引字段使用is not null导致失效
  1. 查询条件存在 OR,且无法命中索引

SQL优化小技巧:

  • 通过执行计划优化索引
  • 控制索引数量,避免过大索引
  • 避免使用 select *,只返回所需数据
  • 选择合理的字段类型
  • 批量操作替代单个循环
  • 多用 limit
  • 避免 in 值过多
  • 用连接替代子查询
  • 使用小表驱动大表
  • 避免join过多表
3.9.3.2 分库分表

大动作,分库分表。

将数据拆分,将极大缓解性能压力,但分库分表也可能会带入很多问题:

  • 分库分表后,数据在分表内产生数据倾斜。
  • 如何创建全局性的唯一主键id。
  • 数据如何路由到哪一个分片。

每一个问题展开都要花费很长篇幅来讲解,这里就不展开细讲了。

关于分库分表,推荐使用 sharding-jdbc。

3.9.3.3 冗余数据

存储结构不仅要方便写入,还要方便查询。既然查询不方便,我们可以冗余一份数据,以便于查询。

通过冗余更多的数据,以减少关联查询的次数,可以提高查询性能。

冗余方式:

  • 在表中冗余其他表的字段
  • 引入新的聚合表

注意事项:

  • 冗余带来更新的麻烦
3.9.3.4 归档历史数据

MySQL并不适合存储大数据量,如果不对数据进行归档,数据库会持续膨胀,从而降低查询和写入的性能。为了满足大数据量的读写需求,需要定期对数据库进行归档。

在进行数据库设计时,需要事先考虑到对数据归档的需求。例如可以使用创建时间进行归档,例如归档一年前的数据。

3.9.3.5 读写分离

增加MySQL数据库的从节点来实现负载均衡,减轻主节点的查询压力,让主节点专注于处理写请求,保证读写操作的高性能。

当需要跨地域进行数据库的查询时,由于较高网络延迟等问题,接口性能可能变得很差。在数据实时性不太敏感的情况下,可以通过在多个地域增加从节点来提高这些地域的接口性能。

3.9.3.6 MySQL 换 ES/HBase等

MySQL并不适合大数据量存储,若不对数据进行归档,数据库会一直膨胀,从而降低查询和写入的性能。

  • 对于检索的场景,首推使用 Elasticseach。
  • 对海量数据读写场景,推荐将数据异构到HBase中。

3.9.4 接口优化

3.9.4.1 合理拆分接口

如果HTTP接口功能过于庞大,那么会导致核心数据和非核心数据杂糅在一起,耗时高和耗时低的数据耦合在一起。为了优化请求的耗时,可以通过拆分接口,将核心数据和非核心数据分别处理,从而提高接口的性能。

在RPC接口方面,也可以使用类似的思路进行优化。当上游需要调用多个RPC接口时,可以并行地调用这些接口。优先返回核心数据,如果处理非核心数据或者耗时高的数据超时,则直接降级,只返回核心数据。这种方式可以提高接口的响应速度和效率,减少不必要的等待时间。

3.9.4.2 裁剪冗余的接口参数和返回值

过大的参数或返回值会严重拉低接口响应时间,应遵循最小化原则。

3.9.4.3 预热低流量接口

对于访问量较低的接口来说,通常首次接口的响应时间较长。原因是JVM需要加载类、Spring Aop首次动态代理,以及新建连接等。这使得首次接口请求时间明显比后续请求耗时长。

不同的接口预热方式有所不同。

  • 对于读接口,可以考虑在服务启动时,自行调用一次接口。
  • 对于写接口,还可以尝试更新特定的一条数据。
  • 可以在服务启动时加载对应的类,以减少首次调用的耗时。

3.9.5 逻辑处理优化

3.9.5.1 提前过滤请求

在某些活动匹配的业务场景里,相当多的请求实际上是不满足条件的,如果能尽早的过滤掉这些请求,就能避免很多无效查询。例如用户匹配某个活动时,会有非常多的过滤条件,如果该活动的特点是仅少量用户可参加,那么可首先使用人群先过滤掉大部分不符合条件的用户。

3.9.5.2 合理使用缓存

合理利用多级缓存,包括本地缓存、分布式缓存等:

  • 本地缓存: Guava、Caffeine等
  • 分布式缓存:Redis、Memcached等

使用缓存提高性能的几种场景:

  • 缓存接口常用的热点数据
  • 缓存常用接口的不常变化数据
  • 缓存预先计算的数据

注意事项:

  • 选择合理的数据结构
  • 关注缓存命中率
  • 预估缓存容量空间,制定快速扩容策略
  • 缓存数据一致性问题
  • 缓存数据的淘汰策略
  • 缓存的QPS
  • 客户端连接数
3.9.5.3 传递上下文

接口内,避免重复的查询和写入操作,一般遵循:

  • 同样的表非必要只读取或写入一次,避免重复操作。
  • 已读取的信息,避免重复读取,有需要应向下传递。

在复杂的业务操作中,应定义一个Context 上下文对象,将一些中间信息存储并传递下来,会大大减轻后面流程的再次查询压力。

注意事项:

  • 应预估内存占用情况,注意及时释放,防止内存堆积。
3.9.5.4 使用异步策略

使用异步策略的几种方式:

  • 实时性要求不高的分支业务操作,使用异步线程处理。
  • 实时性要求不高的分支第三方操作,使用MQ异步处理。

注意事项:

  • 避免处理分布式事务
  • 需注意失败策略
  • 避免逻辑过于复杂
3.9.5.5 合理池化技术

使用池化技术,避免重复创建和销毁,提高性能:

  • HTTP连接池
  • 数据库连接池
  • 线程池

合理设置池参数同样重要:最小连接数、空闲连接数、最大连接数等。

3.9.5.6 使用并行策略

并行策略极大的提高了接口的复杂度,提高了扩展和维护成本。一定要慎重选择,非必要不使用。

梳理业务流程,画出时序图,分清楚哪些是串行?哪些是并行?充分利用多核 CPU 的并行化处理能力。如下图所示,存在上下文依赖的采用串行处理,否则采用并行处理。

JDK 的 CompletableFuture  提供了非常丰富的API,大约有50种 处理串行、并行、组合以及处理错误的方法,可以满足我们的场景需求。

3.9.5.7 最小化锁的颗粒度

原则上,锁是能不用就不用。如果非要使用,一定要合理控制锁的力度,过大会影响性能和稳定性。

3.9.5.8 避免一次性查询过多数据

在进行查询操作时,应尽量将单次调用改为批量查询或分页查询。不论是批量查询还是分页查询,都应注意避免一次性查询过多数据,比如每次加载10000条记录。因为过大的网络报文会降低查询性能,并且Java虚拟机(JVM)倾向于在老年代申请大对象。当访问量过高时,频繁申请大对象会增加Full GC(垃圾回收)的频率,从而降低服务的性能。

3.9.6 硬件提升

在软件提升空间不大的情况,必要时选择硬件提升,效果会非常显著:

  • 当瓶颈是计算时,使用更强、或更多的CPU/GPU。

  • 当瓶颈是磁盘读取速度时,使用更好的SSD盘,或分散到多机上读取。

  • 当瓶颈是内存时,使用更大的内存,或利用多机内存。

  • 当瓶颈是网络速度时,提升到更高的带宽速度。

3.9.7 产品及交互层面优化

大家往往容易死磕技术优化,忽视了产品和交互层面的改进。有时候,通过和产品沟通交互和业务逻辑,很容易就能解决很棘手的技术问题。

沟通的注意事项:

  • 需求不是完全一成不变的
  • 想要推动产品变更需求,必须理解原始客户诉求

  •  维持良好的沟通氛围,避免隔阂

可选的方式有:

  • 限制用户行为。限制单位时间内的用户操作,例如短信发送上限、账号关注上限、评论发言上限等。
  • 调整交互顺序
  • 调整产品逻辑

### Transformer编码器工作原理 #### 编码器架构概述 Transformer模型中的编码器由多个相同的层堆叠而成。每一层主要包含两个子层:一个多头自注意力机制(Multi-head Self-Attention Mechanism),以及一个全连接前馈网络(Feed Forward Network)。这些组件共同作用来处理输入数据并生成上下文感知的特征向量[^1]。 #### 多头自注意力机制 多头自注意力允许模型在不同的位置上关注输入的不同部分,从而捕捉到更丰富的语义信息。具体来说,在每个多头自注意单元内部,会计算查询键值三元组(Q,K,V),并通过矩阵运算得到加权求和的结果作为输出。此过程可以理解为对同一句话里的各个单词之间相互影响程度的一种量化描述。 为了增强表达能力,实际应用中通常采用多个平行运行的小型自注意力模块——即所谓的“头部”,最后再把这些头部产生的结果拼接起来形成最终输出。这种设计使得模型能够同时学习不同类型的关联模式[^2]。 ```python import torch.nn as nn class MultiHeadSelfAttention(nn.Module): def __init__(self, d_model, num_heads): super(MultiHeadSelfAttention, self).__init__() assert d_model % num_heads == 0 self.d_k = d_model // num_heads self.num_heads = num_heads # 定义线性变换参数WQ,WK,WV用于产生q,k,v ... def forward(self, Q, K, V, mask=None): ... ``` #### 前馈神经网络(FFN) 紧跟其后的是一系列两层的全连接层组成的前馈神经网络。它接收来自前面一层经过归一化之后的数据,并通过激活函数引入非线性特性。值得注意的是,这里的权重在整个序列长度维度上共享,意味着对于同一个时间步内的所有元素都施加相同的操作。 ```python class FeedForwardNetwork(nn.Module): def __init__(self, d_model, hidden_size): super(FeedForwardNetwork, self).__init__() self.linear_1 = nn.Linear(d_model, hidden_size) self.relu = nn.ReLU() self.linear_2 = nn.Linear(hidden_size, d_model) def forward(self, x): return self.linear_2(self.relu(self.linear_1(x))) ``` #### 层规范化与残差连接 除了上述核心部件外,还有一个非常重要的细节就是加入了Layer Normalization 和 Residual Connection 。前者有助于加速训练收敛速度;后者则能有效缓解深层网络可能出现的信息传递衰减问题,确保梯度稳定流动。 ![transformer_encoder](https://miro.medium.com/max/784/1*ZvYbJjzX9hLgUOaPmDyGGA.png)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值