一文搞定接口幂等性架构设计方案

 幂等性介绍

现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计。那么在这一个系统中,就会存在若干个微服务,而且服务间也会产生相互通信调用。那么既然产生了服务调用,就必然会存在服务调用延迟或失败的问题。当出现这种问题,服务端会进行重试等操作或客户端有可能会进行多次点击提交。如果这样请求多次的话,那最终处理的数据结果就一定要保证统一,如支付场景。此时就需要通过保证业务幂等性方案来完成。

什么是幂等性

幂等是一个数学与计算机学概念,即 f(n) = 1^n,无论 n 为多少,f (n) 的值永远为 1,在数学中某一元运算为幂等时,其作用在任一元素两次后会和其作用一次的结果相同。

在编程开发中,对于幂等的定义为:无论对某一个资源操作了多少次,其影响都应是相同的。 换句话说就是:在接口重复调用的情况下,对系统产生的影响是一样的,但是返回值允许不同,如查询。

幂等函数或幂等方法是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

幂等性不仅仅只是一次或多次操作对资源没有产生影响,还包括第一次操作产生影响后,以后多次操作不会再产生影响。并且幂等关注的是是否对资源产生影响,而不关注结果。

幂等性维度

幂等性设计主要从两个维度进行考虑:空间、时间。

  • 空间:定义了幂等的范围,如生成订单的话,不允许出现重复下单。
  • 时间:定义幂等的有效期。有些业务需要永久性保证幂等,如下单、支付等。而部分业务只要保证一段时间幂等即可。

同时对于幂等的使用一般都会伴随着出现锁的概念,用于解决并发安全问题。

以 SQL 为例

  • select * from table where id=1。此 SQL 无论执行多少次,虽然结果有可能出现不同,都不会对数据产生改变,具备幂等性。
  • insert into table(id,name) values(1,'heima')。此 SQL 如果 id 或 name 有唯一性约束,多次操作只允许插入一条记录,则具备幂等性。如果不是,则不具备幂等性,多次操作会产生多条数据。
  • update table set score=100 where id = 1。此 SQL 无论执行多少次,对数据产生的影响都是相同的。具备幂等性。
  • update table set score=50+score where id = 1。此 SQL 涉及到了计算,每次操作对数据都会产生影响。不具备幂等性。
  • delete from table where id = 1。此 SQL 多次操作,产生的结果相同,具备幂等性。

什么是接口幂等性

在 HTTP/1.1 中,对幂等性进行了定义。

它描述了一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外),即第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。

这里的副作用是不会对结果产生破坏或者产生不可预料的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

为什么需要实现幂等性

使用幂等性最大的优势在于使接口保证任何幂等性操作,免去因重试等造成系统产生的未知的问题。

在接口调用时一般情况下都能正常返回信息不会重复提交,不过在遇见以下情况时可以就会出现问题:

前端重复提交表单

在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。

用户恶意进行刷单

例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。

接口超时重复提交

很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。

消息进行重复消费

当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。

引入幂等性后对系统有什么影响

幂等性是为了简化客户端逻辑处理,能放置重复提交等操作,但却增加了服务端的逻辑复杂性和成本,其主要是:

  1. 把并行执行的功能改为串行执行,降低了执行效率。
  2. 增加了额外控制幂等的业务逻辑,复杂化了业务功能;

所以在使用时候需要考虑是否引入幂等性的必要性,根据实际业务场景具体分析,除了业务上的特殊要求外,一般情况下不需要引入的接口幂等性。

Restful API 接口幂等

现在流行的 Restful 推荐的几种 HTTP 接口方法中,分别存在幂等行与不能保证幂等的方法,如下:

HTTP 协议语义幂等性

HTTP 协议有两种方式:RESTFUL、SOA。现在对于 WEB API,更多的会使用 RESTFUL 风格定义。为了更好的完成接口语义定义,HTTP 对于常用的四种请求方式也定义了幂等性的语义。

  • GET:用于获取资源,多次操作不会对数据产生影响,具有幂等性。注意不是结果。
  • POST:用于新增资源,对同一个 URI 进行两次 POST 操作会在服务端创建两个资源,不具有幂等性
  • PUT:用于修改资源,对同一个 URI 进行多次 PUT 操作,产生的影响和第一次相同,具备幂等性
  • DELETE:用于删除资源,对同一个 URI 进行多次 DELETE 操作,产生的影响和第一次相同,具备幂等性

综上所述,这些仅仅只是 HTTP 协议建议在基于 RESTFUL 风格定义 WEB API 时的语义,并非强制性。同时对于幂等性的实现,肯定是通过前端或服务端完成。

业务问题抛出

在业务开发与分布式系统设计中,幂等性是一个非常重要的概念,有非常多的场景需要考虑幂等性的问题,尤其对于现在的分布式系统,经常性的考虑重试、重发等操作,一旦产生这些操作,则必须要考虑幂等性问题。以交易系统、支付系统等尤其明显,如:

  • 当用户购物进行下单操作,用户操作多次,但订单系统对于本次操作只能产生一个订单。
  • 当用户对订单进行付款,支付系统不管出现什么问题,应该只对用户扣一次款。
  • 当支付成功对库存扣减时,库存系统对订单中商品的库存数量也只能扣减一次。
  • 当对商品进行发货时,也需保证物流系统有且只能发一次货。

在电商系统中还有非常多的场景需要保证幂等性。但是一旦考虑幂等后,服务逻辑务必会变的更加复杂。因此是否要考虑幂等,需要根据具体业务场景具体分析。而且在实现幂等时,还会把并行执行的功能改为串行化,降低了执行效率。

此处以下单减库存为例,当用户生成订单成功后,会对订单中商品进行扣减库存。 订单服务会调用库存服务进行库存扣减。库存服务会完成具体扣减实现。

现在对于功能调用的设计,有可能出现调用超时,因为出现如网络抖动,虽然库存服务执行成功了,但结果并没有在超时时间内返回,则订单服务也会进行重试。那就会出现问题,stock 对于之前的执行已经成功了,只是结果没有按时返回。而订单服务又重新发起请求对商品进行库存扣减。 此时出现库存扣减两次的问题。 对于这种问题,就需要通过幂等性进行结果。

解决方案

对于幂等的考虑,主要解决两点前后端交互与服务间交互。这两点有时都要考虑幂等性的实现。从前端的思路解决的话,主要有三种:前端防重、PRG 模式、Token 机制。

前端防重

通过前端防重保证幂等是最简单的实现方式,前端相关属性和 JS 代码即可完成设置。可靠性并不好,有经验的人员可以通过工具跳过页面仍能重复提交。主要适用于表单重复提交或按钮重复点击。

PRG 模式

PRG 模式即 POST-REDIRECT-GET。当用户进行表单提交时,会重定向到另外一个提交成功页面,而不是停留在原先的表单页面。这样就避免了用户刷新导致重复提交。同时防止了通过浏览器按钮前进 / 后退导致表单重复提交。是一种比较常见的前端防重策略。

Token 模式

通过 token 机制来保证幂等是一种非常常见的解决方案,同时也适合绝大部分场景。该方案需要前后端进行一定程度的交互来完成。

Token 防重实现

针对客户端连续点击或者调用方的超时重试等情况,例如提交订单,此种操作就可以用 Token 的机制实现防止重复提交。

简单的说就是调用方在调用接口的时候先向后端请求一个全局 ID(Token),请求的时候携带这个全局 ID 一起请求(Token 最好将其放到 Headers 中),后端需要对这个 Token 作为 Key,用户信息作为 Value 到 Redis 中进行键值内容校验,如果 Key 存在且 Value 匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的 Key 或 Value 不匹配就返回重复执行的错误信息,这样来保证幂等操作。

适用操作

  • 插入操作
  • 更新操作
  • 删除操作

使用限制

  • 需要生成全局唯一 Token 串
  • 需要使用第三方组件 Redis 进行数据效验

主要流程

  1. 服务端提供获取 Token 的接口,该 Token 可以是一个序列号,也可以是一个分布式 ID 或者 UUID 串。
  2. 客户端调用接口获取 Token,这时候服务端会生成一个 Token 串。
  3. 然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。
  4. 将 Token 返回到客户端,客户端拿到后应存到表单隐藏域中。
  5. 客户端在执行提交表单时,把 Token 存入到 Headers 中,执行业务请求带上该 Headers
  6. 服务端接收到请求后从 Headers 中拿到 Token,然后根据 Token 到 Redis 中查找该 key 是否存在。
  7. 服务端根据 Redis 中是否存该 key 进行判断,如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。

注意,在并发情况下,执行 Redis 查找数据与删除需要保证原子性,否则很可能在并发下无法保证幂等性。其实现方法可以使用分布式锁或者使用 Lua 表达式来注销查询与删除操作。

实现流程

通过 token 机制来保证幂等是一种非常常见的解决方案,同时也适合绝大部分场景。该方案需要前后端进行一定程度的交互来完成。

  1. 服务端提供获取 token 接口,供客户端进行使用。服务端生成 token 后,如果当前为分布式架构,将 token 存放于 redis 中,如果是单体架构,可以保存在 jvm 缓存中。
  2. 当客户端获取到 token 后,会携带着 token 发起请求。
  3. 服务端接收到客户端请求后,首先会判断该 token 在 redis 中是否存在。如果存在,则完成进行业务处理,业务处理完成后,再删除 token。如果不存在,代表当前请求是重复请求,直接向客户端返回对应标识。

业务执行时机

先执行业务再删除 token

但是现在有一个问题,当前是先执行业务再删除 token

在高并发下,很有可能出现第一次访问时 token 存在,完成具体业务操作。但在还没有删除 token 时,客户端又携带 token 发起请求,此时,因为 token 还存在,第二次请求也会验证通过,执行具体业务操作。

对于这个问题的解决方案的思想就是并行变串行。会造成一定性能损耗与吞吐量降低。

  • 第一种方案:对于业务代码执行和删除 token 整体加线程锁。当后续线程再来访问时,则阻塞排队。
  • 第二种方案:借助 redis 单线程和 incr 是原子性的特点。当第一次获取 token 时,以 token 作为 key,对其进行自增。然后将 token 进行返回,当客户端携带 token 访问执行业务代码时,对于判断 token 是否存在不用删除,而是对其继续 incr。如果 incr 后的返回值为 2。则是一个合法请求允许执行,如果是其他值,则代表是非法请求,直接返回。

先删除 token 再执行业务

那如果先删除 token 再执行业务呢?其实也会存在问题,假设具体业务代码执行超时或失败,没有向客户端返回明确结果,那客户端就很有可能会进行重试,但此时之前的 token 已经被删除了,则会被认为是重复请求,不再进行业务处理。

这种方案无需进行额外处理,一个 token 只能代表一次请求。一旦业务执行出现异常,则让客户端重新获取令牌,重新发起一次访问即可。推荐使用先删除 token 方案

但是无论先删 token 还是后删 token,都会有一个相同的问题。每次业务请求都回产生一个额外的请求去获取 token。但是,业务失败或超时,在生产环境下,一万个里最多也就十个左右会失败,那为了这十来个请求,让其他九千九百多个请求都产生额外请求,就有一些得不偿失了。虽然 redis 性能好,但是这也是一种资源的浪费。

基于业务实现

生成 Token

修改 token_service_order 工程中 OrderController,新增生成令牌方法 genToken

@Autowired
private IdWorker idWorker;@Autowired
private RedisTemplate redisTemplate;@GetMapping("/genToken")
public String genToken(){String token = String.valueOf(idWorker.nextId());redisTemplate.opsForValue().set(token,0,30, TimeUnit.MINUTES);return token;
}

新增接口

修改 token_service_api 工程,新增 OrderFeign 接口。

@FeignClient(name = "order")
@RequestMapping("/order")
public interface OrderFeign {@GetMapping("/genToken")public String genToken();
}

获取 token

修改 token_web_order 工程中 WebOrderController,新增获取 token 方法

@RestController
@RequestMapping("worder")
public class WebOrderController {@Autowiredprivate OrderFeign orderFeign;/*** 服务端生成token* @return*/@GetMapping("/genToken")public String genToken(){String token = orderFeign.genToken();return token;}}

拦截器

修改 token_common,新增 feign 拦截器

@Component
public class FeignInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate requestTemplate) {//传递令牌RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes != null){HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();if (request != null){Enumeration<String> headerNames = request.getHeaderNames();while (headerNames.hasMoreElements()){String headerName = headerNames.nextElement();if ("token".equals(headerName)){String headerValue = request.getHeader(headerName);//传递tokenrequestTemplate.header(headerName,headerValue);}}}}}
}

启动类

修改 token_web_order 启动类

@Bean
public FeignInterceptor feignInterceptor(){return new FeignInterceptor();
}

新增订单

修改 token_service_order 中 OrderController,新增添加订单方法

/*** 生成订单* @param order* @return*/
@PostMapping("/genOrder")
public String genOrder(@RequestBody Order order, HttpServletRequest request){//获取令牌String token = request.getHeader("token");//校验令牌try {if (redisTemplate.delete(token)){//令牌删除成功,代表不是重复请求,执行具体业务order.setId(String.valueOf(idWorker.nextId()));order.setCreateTime(new Date());order.setUpdateTime(new Date());int result = orderService.addOrder(order);if (result == 1){System.out.println("success");return "success";}else {System.out.println("fail");return "fail";}}else {//删除令牌失败,重复请求System.out.println("repeat request");return "repeat request";}}catch (Exception e){throw new RuntimeException("系统异常,请重试");}
}

修改 token_service_order_api 中 OrderFeign

@FeignClient(name = "order")
@RequestMapping("/order")
public interface OrderFeign {@PostMapping("/genOrder")public String genOrder(@RequestBody Order order);@GetMapping("/genToken")public String genToken();
}

修改 token_web_order 中 WebOrderController,新增添加订单方法

/*** 新增订单*/
@PostMapping("/addOrder")
public String addOrder(@RequestBody Order order){String result = orderFeign.genOrder(order);return result;
}

测试

通过 postman 获取令牌,将令牌放入请求头中。开启两个 postman tab 页面。同时添加订单,可以发现一个执行成功,另一个重复请求。

{"id":"123321","totalNum":1,"payMoney":1,"payType":"1","payTime":"2020-05-20","receiverContact":"heima","receiverMobile":"15666666666","receiverAddress":"beijing"}

基于自定义注解实现

直接把 token 实现嵌入到方法中会造成大量重复代码的出现。因此可以通过自定义注解将上述代码进行改造。在需要保证幂等的方法上,添加自定义注解即可。

自定义注解

在 token_common 中新建自定义注解 Idemptent

/*** 幂等性注解*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idemptent {
}

创建拦截器

在 token_common 中新建拦截器

public class IdemptentInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();Idemptent annotation = method.getAnnotation(Idemptent.class);if (annotation != null){//进行幂等性校验checkToken(request);}return true;}@Autowiredprivate RedisTemplate redisTemplate;//幂等性校验private void checkToken(HttpServletRequest request) {String token = request.getHeader("token");if (StringUtils.isEmpty(token)){throw new RuntimeException("非法参数");}boolean delResult = redisTemplate.delete(token);if (!delResult){//删除失败throw new RuntimeException("重复请求");}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

配置拦截器

修改 token_service_order 启动类,让其继承 WebMvcConfigurerAdapter

@Bean
public IdemptentInterceptor idemptentInterceptor() {return new IdemptentInterceptor();
}@Override
public void addInterceptors(InterceptorRegistry registry) {//幂等拦截器registry.addInterceptor(idemptentInterceptor());super.addInterceptors(registry);
}

添加注解

更新 token_service_order 与 token_service_order_api,新增添加订单方法,并且方法添加自定义幂等注解

@Idemptent
@PostMapping("/genOrder2")
public String genOrder2(@RequestBody Order order){order.setId(String.valueOf(idWorker.nextId()));order.setCreateTime(new Date());order.setUpdateTime(new Date());int result = orderService.addOrder(order);if (result == 1){System.out.println("success");return "success";}else {System.out.println("fail");return "fail";}
}

测试

获取令牌后,在 jemeter 中模拟高并发访问,设置 50 个并发访问

新增一个 http request,并设置相关信息

添加 HTTP Header Manager

测试执行,可以发现,只有一个请求是成功的,其他全部被判定为重复请求。 本文由传智教育博学谷狂野架构师教研团队发布。

如果本文对您有帮助,欢迎关注点赞;如果您有任何建议也可留言评论私信,您的支持是我坚持创作的动力。

转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/75188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系列四、Nginx的常用命令和配置文件

一、常用命令 1.1、查看nginx的版本号 ./nginx -v 1.2、启动nginx cd /usr/local/nginx/sbin./nginx 1.3、停止nginx cd /usr/local/nginx/sbin./nginx -s stop 1.4、重新加载nginx 说明&#xff1a;该命令用于修改配置文件后&#xff0c;在不重启nginx的情况下使配置文…

FPGA通信—千兆网(UDP)软件设计

一、PHY引脚功能描述 引脚功能描述1CLK25 CLK125:内部PLL生成的125MHz参考时钟&#xff0c;如MAC未使用125MHe时钟&#xff0c;则此引脚应保持浮动&#xff0c; 2 4 63 GND 接地3REG OUT开关压器&#xff0c;1.05V输出 5 6 8 9 11 12 14 15 MDI[0] MDI[0]- MDI[1] MDI[1…

学习笔记-BNF、EBNF、ABNF语法格式描述规范

目标是确认一些c/cpp的语法细节&#xff0c;需要看cpp语法定义文件。 考虑从c的语法定义文件开始确认。 考虑实现一个简化的语言定义和编译器&#xff0c;为后续的实际需求做自定义扩展。 参考网页&#xff1a; https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_f…

高可用Kuberbetes部署Prometheus + Grafana

概述 阅读官方文档部署部署Prometheus Grafana GitHub - prometheus-operator/kube-prometheus at release-0.10 环境 步骤 下周官方github仓库 git clone https://github.com/prometheus-operator/kube-prometheus.git git checkout release-0.10 进入工作目录 cd kube…

二、[mysql]之Explain讲解与实战

目录 一、了解Explain1.Explain介绍 二、Explain相关字段1.partitions2.filtered3.SHOW WARNINGS命令 三、Explain比较重要字段1.id2.select_type3.table4.type5.possible_keys6.key7.key_len8.ref9.rows10.Extra 四、索引优化实战&#xff08;遵循原则&#xff09;1.全值匹配2…

python关闭指定进程以excel为例

先说下环境&#xff1a; Excel版本&#xff1a; Python2.7.13和Python3.10.4并存。 2、打开两个excel工作簿 看进程是这样的&#xff1a; 3、用python编程kill进程 # -*- coding: utf-8 -*- import os proc_nameEXCEL.EXE if __name__ __main__:os.system(taskkill /im {} /…

【vue2第十六章】VueRouter 声明式导航(跳转传参)、路由重定向、页面未找到的提示页面404、vue路由模式设置

声明式导航(跳转传参) 在一些特定的需求中&#xff0c;跳转路径时我们是需要携带参数跳转的&#xff0c;比如有一个搜索框&#xff0c;点击搜索的按钮需要跳转到另外一个页面组件&#xff0c;此时需要把用户输入的input框的值也携带到那页面进行发送请求&#xff0c;请求数据。…

python 随机生成emoji表情

问答板块觉得比较有意思的问题 当时搜了些网上的发现基本都不能用&#xff0c;不知道是版本的问题还是咋的就开始自己研究 python随机生成emoji 问题的产生解决官网文档数据类型实现思路实现前提&#xff1a;具体实现&#xff1a; 其他常见用法插入 Emoji 表情&#xff1a;解析…

【ES6】Class中this指向

先上代码&#xff1a; 正常运行的代码&#xff1a; class Logger{printName(name kexuexiong){this.print(hello ${name});}print(text){console.log(text);} }const logger new Logger(); logger.printName("kexueixong xiong");输出&#xff1a; 单独调用函数p…

搭建自己的OCR服务,第二步:PaddleOCR环境安装

PaddleOCR环境安装&#xff0c;遇到了很多问题&#xff0c;根据系统不同问题也不同&#xff0c;不要盲目看别人的教程&#xff0c;有的教程也过时了&#xff0c;根据实际情况自己调整。 我这边目前是使用windows 10系统CPU python 3.7 搭建。 熟悉OCR的人应该知道&#xff0…

合宙Air724UG LuatOS-Air LVGL API控件-标签 (Label)

标签 (Label) 标签是 LVGL 用来显示文字的控件。 示例代码 label lvgl.label_create(lvgl.scr_act(), nil) lvgl.label_set_recolor(label, true) lvgl.label_set_text(label, "#0000ff Re-color# #ff00ff words# #ff0000 of\n# align the lines …

B站:AB test [下]

Focus在&#xff1a;AB Test结束后&#xff0c;如何进行显著性检验&#xff1f;&#xff08;以判断改动是否有效果&#xff09; 引入&#xff1a;Z检验和T检验 而T检验适用于 n<30 的小样本 值得注意的是&#xff1a;统计上显著并不意味着现实中显著&#xff01; e.g. 加速…

尚硅谷大数据项目《在线教育之离线数仓》笔记008

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 P123 P124 P125 P126 P127 P128 P129 P123 Apache Superset是一个现代的数据探索和可视化平台。它功能强大且十分易用&#xff0c;可对接各种数据源&#xff0c;包括很多现代的大数…

医院空调冷热源设计方案VR元宇宙模拟演练的独特之处

作为一个备受关注的技术-元宇宙&#xff0c;毋庸置疑的是&#xff0c;因为建筑本身契合了时尚、前卫、高端、虚拟、科幻、泛在、协作、互通等元素特征&#xff0c;因此在建筑行业更需要元宇宙&#xff0c;以居民建筑环境冷热源设计来说&#xff0c;元宇宙会打破既定的现实阻碍和…

LVS NAT模式负载均衡群集部署

目录 1 群集(集群) cluster 1.1 群集的类型 2 LVS的工作模式及其工作过程 2.1 NAT模式&#xff08;VS-NAT&#xff09; 2.2 直接路由模式&#xff08;VS-DR&#xff09; 2.3 IP隧道模式&#xff08;VS-TUN&#xff09; 3 LVS-NAt 模式配置步骤 3.1 部署共享存储 3.2 配…

SSL证书验签时要带www吗?

单域名证书&#xff1a;顶级域名如www.abc.com或abc.com 不管你提交订单的时候填写的域名是带www或不带www的域名&#xff0c;签发的证书均支持www和不带www的域名 单域名证书&#xff1a;子域名如mail.abc.com&#xff0c;签发的证书仅支持mail.abc.com 通配符证书&#xff…

MySQL触发器使用指南大全

一、介绍 触发器是与表有关的数据库对象&#xff0c;指在insert/update/delete之前或之后&#xff0c;触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性&#xff0c;日志记录&#xff0c;数据校验等操作。 使用别名OLD和NEW来引…

C++之生成key-value键值三种方式(一百九十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Web server failed to start. Port 8080 was already in use.之解决方法

问题&#xff1a; Web server failed to start. Port 8080 was already in use&#xff0c;这句错误描述意思是当前程序的端口号8080被占用了&#xff0c;需要将占用该端口的程序停止掉才行&#xff1b;错误如图所示&#xff1a; 解决方法&#xff1a; 按住winr&#xff0c;输入…

React 消息文本循环展示

需求 页面上有个小喇叭&#xff0c;循环展示消息内容 逻辑思路 设置定时器&#xff0c;修改translateX属性来实现滚动&#xff0c;判断滚动位置&#xff0c;修改list位置来实现无限滚动 实现效果 代码 /** Author: Do not edit* Date: 2023-09-07 11:11:45* LastEditors: …