精华内容
下载资源
问答
  • 幂等架构
    2018-01-07 10:03:08

    什么是幂等性

    抄用一段数学上的定义:f(f(x)) = f(x)。x被函数f作用一次和作用无限次的结果是一样的。幂等性应用在软件系统中,我把它简单定义为:某个函数或者某个接口使用相同参数调用一次或者无限次,其造成的后果是一样的,在实际应用中一般针对于接口进行幂等性设计。举个栗子,在系统中,调用方A调用系统B的接口进行用户的扣费操作时,由于网络不稳定,A重试了N次该请求,那么不管B是否接收到多少次请求,都应该保证只会扣除该用户一次费用。

    幂等性设计

    幂等性一般应用于协议设计,TCP协议支持幂等吗?答案是肯定的,在网络不稳定时,操作系统可以肆无忌惮的重发TCP报文片段。TCP协议能够保证幂等的核心在于sequence number字段,一个序列号的在较长的一段时间内均不会出现重复。对于应用层的协议设计,原理和TCP是类似的,我们需要一个不重复的序列号。再简单一点说,在一个业务流程的处理中,我们需要一个不重复的业务流水号,以保证幂等性。

    举个实际应用场景:用户A在网页上发起一笔游戏充值请求,浏览器引导用户去银行支付,支付成功后系统给用户进行充值。

    协议设计上,我们通过全局唯一的充值订单号贯穿整个业务流程,使该业务支持幂等。

    应用实现上,我们列举在银行支付成功后回调系统,进行充值的步骤进行说明。

    func pay_notify(orderid,value,state){//有问题的实现  
             order = db.query("select * from payorder where orderid=$orderid");  
             check(order,orderid,value,state);//判断支付金额是否与订单金额一致,判断是否是支付成功回调。  
             if(order.state=='未支付'){  
                db.update("update payorder set state='已支付' where orderid=$orderid");  
                charge(order.username,value);//执行充值  
             }else{  
             return result("订单已处理")//返回订单已处理,或者返回处理成功  
            }  
        }  

    上述实现的问题在于,当回调出现并发时,order.state已经是脏读了,有可能重复充值,该实现并不能100%保证幂等。

    列举三种改进方式:

    1. 悲观锁:select for update,整个执行过程中锁定该订单对应的记录;
    2. 乐观锁:affectrows = db.update(“update payorder set state=’已支付’ where orderid=$orderid and state=’未支付’ “),如果affectrows=1,执行充值,否则返回已处理;
    3. 定义notifylog表,orderid为unique key或者primary key,执行前,先insert,若insert成功则执行充值,否则返回已处理;

    以上简单例子用以说明幂等性常用应用实现,在SOA化系统中,可能很多原子功能都被拆分到不同的进程里,如charge充值这个函数,可能在另一个进程中,那么整个业务的链路就会更长,可能回调成功了,但是充值失败。同理,只要充值接口保证幂等性,对于已经回调过但是充值结果未返回的请求,回调接收程序,应当重复发起充值请求。更深入更复杂的应用场景,在数据一致性中再细讲。

    总结

    业务层设计协议时,要求请求方定义不重复的业务流水号。应用实现时,利用数据库乐观锁、插入unique key的日志等方式保证并发时的幂等。

    幂等性把关环节,在协议设计评审中,评审重要业务RPC或者http接口是否支持幂等,代码评审中,重点把关请求并发时,是否仍旧能够保证幂等性。设计人员和具体实现人员在实现过程中,也应该时刻自审幂等性的实现是否过关。

    更多相关内容
  • 我们先看两道互联网大厂的面试...幂等这个词本身源自数学,幂等性是数学中的一个概念,常见于抽象代数中,表达的是N次变换与1次变换的结果相同,在计算机的各个领域都借用了该概念。本课程带你聊聊接口幂等性的那些事。
  • 要想更好的理解分布式系统,并正确使用甚至构建分布式系统,需要理解其中的两个关键概念——分布式系统的数据一致性和分布式系统的幂等性。如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis...
  • 电商的很多业务,考虑更多的是 BASE(即Basically Available、Soft state、和Eventually consistent),而不是 ACID(Atomicity、Consistency、Isolation和 Durability)。即为了满足高负载的用户访问,我们可以容忍...
  • 微服务与幂等性 随着应用架构由单体架构到微服务架构进行演变,现如今市面上超过50%的应用都会基于分布式或微服务完成系统架构设计。在微服务架构体系内,就会存在若干个微服务,这些服务可能基于RPC或者HTTPS协议...

    微服务与幂等性

    随着应用架构由单体架构到微服务架构进行演变,现如今市面上超过50%的应用都会基于分布式或微服务完成系统架构设计。在微服务架构体系内,就会存在若干个微服务,这些服务可能基于RPC或者HTTPS等协议进行通讯。那么既然服务之间存在相互调用,那么必然存在服务调用延迟或者失败的情况,当出现这种问题,服务端会进行重试等操作或客户端有可能会进行多次点击提交。如果这样请求多次的话,那最终处理的数据结果就一定要保证统一,如支付场景。此时就需要通过保证业务幂等性方案来完成。

    幂等性简介

    幂等本身是一个数学概念。即 f(n) = 1^n ,无论n为多少,f(n)的值永远为1。在编程开发中,对于幂等的定义为:无论对某一个资源操作了多少次,其影响都应是相同的。 换句话说就是:在接口重复调用的情况下,对系统产生的影响是一样的,但是返回值允许不同,如查询。
    幂等性包括数据幂等、接口幂等、服务幂等、消息幂等。

    以SQL为例:

    • select * from table where id=1。此SQL无论执行多少次,虽然结果有可能出现不同,都不会对数据产生 改变,具备幂等性。
    • insert into table(id,name) values(1,'小莫') 。此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多次操作,产生的结果相同,具备幂等性。
      幂等性设计主要从两个维度进行考虑:空间、时间。
    • 空间:定义了幂等的范围,如生成订单的话,不允许出现重复下单。
    • 时间:定义幂等的有效期。有些业务需要永久性保证幂等,如下单、支付等。而部分业务只要保证一段时间幂等即可。
      同时对于幂等的使用一般都会伴随着出现锁的概念,用于解决并发安全问题。

    业务与幂等性

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

    • 当用户购物进行下单操作,用户操作多次,但订单系统对于本次操作只能产生一个订单。
    • 当用户对订单进行付款,支付系统不管出现什么问题,应该只对用户扣一次款。
    • 当支付成功对库存扣减时,库存系统对订单中商品的库存数量也只能扣减一次。
    • 当对商品进行发货时,也需保证物流系统有且只能发一次货。
      在电商系统中还有非常多的场景需要保证幂等性。但是一旦考虑幂等后,服务逻辑务必会变的更加复杂。因此是否要考虑幂等,需要根据具体业务场景具体分析。而且在实现幂等时,还会把并行执行的功能改为串行化,降低了执行效率。
      此处以下单减库存为例,当用户生成订单成功后,会对订单中商品进行扣减库存。 订单服务会调用库存服务 进行库存扣减。库存服务会完成具体扣减实现。
      现在对于功能调用的设计,有可能出现调用超时,因为出现如网络抖动,虽然库存服务执行成功了,但结果并没有在超时时间内返回,则订单服务也会进行重试。那就会出现问题,stock对于之前的执行已经成功了, 只是结果没有按时返回。而订单服务又重新发起请求对商品进行库存扣减。 此时出现库存扣减两次的问题。 对于这种问题,就需要通过幂等性进行结果。

    接口幂等

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

    前端防重

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

    PRG模式

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

    Token机制

    方案介绍

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

    1)服务端提供获取token接口,供客户端进行使用。服务端生成token后,如果当前为分布式架构,将token存放 于redis中,如果是单体架构,可以保存在jvm缓存中。
    2)当客户端获取到token后,会携带着token发起请求。
    3)服务端接收到客户端请求后,首先会判断该token在redis中是否存在。如果存在,则完成进行业务处理,业务 处理完成后,再删除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。但是,业务失败或超时,在生产环境下,一万个里最多也就十个左右会失败,那为了这十来个请求,让其他九千九百多个请求都产生额外请求,就有一些得不偿失了。虽然redis性能好,但是这也是一种资源的浪费。

    实现

    基于自定义业务流程实现


    这种实现方式省略,与传统实现无异。

    基于自定义注解实现

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

    1. 在token_common中新建自定义注解Idemptent
    public class IdemptentInterceptor implements HandlerInterceptor {
    
        @Override
        public 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;
        }
    
    
        @Autowired
        private 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("重复请求");
            }
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
        }
    }
    
    1. 修改token_service_order启动类,让其继承WebMvcConfigurerAdapter
    @Bean
    public IdemptentInterceptor idemptentInterceptor() {
        return new IdemptentInterceptor();
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //幂等拦截器
        registry.addInterceptor(idemptentInterceptor());
        super.addInterceptors(registry);
    }
    
    1. 更新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";
        }
    }
    
    展开全文
  • 安全架构-幂等性设计-SQL

    千次阅读 2020-12-20 22:35:06
    安全架构-幂等性设计 幂等性设计的文章有几篇了,虽然侧重点不一样分为了api幂等,http幂等,sql幂等,实际上是一回事。 本篇从侧重SQL语句操作入手介绍sql操作保证的幂等性。 文章目录安全架构-幂等性设计前言一、...

    安全架构-幂等性设计

    幂等性设计的文章有几篇了,虽然侧重点不一样分为了api幂等,http幂等,sql幂等,实际上是一回事。
    本篇从侧重SQL语句操作入手介绍sql操作保证的幂等性。



    前言

    一、SQL幂等场景

    一、查询,select * from user where xxx,不会对数据产生任何变化,具备幂等性。

    二、新增,insert into user(userid,name) values(1,'a'),

    如userid为唯一主键,即重复操作上面的业务,只会插入一条用户数据,具备幂等性。
    如userid不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性。
    三、修改,区分直接赋值和计算赋值。

    1、直接赋值,update user set point = 20 where
    userid=1,不管执行多少次,point都一样,具备幂等性。
    2、计算赋值,update user set point = point+ 20 where userid=1,每次操作point数据都不一样,不具备幂等性。

    四、删除,delete from user where userid=1,多次操作,结果一样,具备幂等性。

    上面场景中,我们发现新增没有唯一主键约束的数据,和修改计算赋值型操作都不具备幂等性。

    二、解决方案

    1.token机制

    跟api接口保持幂等性使用的token机制一样,此处参考知乎享学课堂的图
    在这里插入图片描述
    上图就是token+redis的幂等方案,适用绝大部分场景。主要思想:

    1、服务端提供了发送token的接口。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务前,先去获取token,服务器会把token保存到redis中。(微服务肯定是分布式了,如果单机就适用jvm缓存)
    2、然后调用业务接口请求时,把token携带过去,一般放在请求头部。
    3、服务器判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,执行业务完成后,最后需要把redis中的token删除。
    4、如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。
    这种方案是比较常用的方案,也是网上经常介绍的,但是有一点不同的地方:

    网上方案:检验token存在(表示第一次请求)后,就立刻删除token,再进行业务处理
    上面方案:检验token存在(表示第一次请求)后,先进行业务处理,再删除token
    关键点就是 先删除token,还是后删除token。

    一、网上方案缺点

    我们看下网上方案,先删除token,这是出现系统问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果,然后进行重试,但token已经删除掉了,服务端判断token不存在,认为是重复请求,就直接返回了,无法进行业务处理了。
    二、上面方案缺点

    后删除token也是会存在问题的,如果进行业务处理成功后,删除redis中的token失败了,这样就导致了有可能会发生重复请求,因为token没有被删除。
    小伙伴们有没有发现,其实上面的问题就是数据库和缓存redis数据不一致的问题。之前老顾分享了一篇文章,里面详细介绍了如何解决数据库和缓存redis数据不一致的问题。小伙伴们可自行查阅。

    其实根据这个场景的业务,可以有个简单的处理方式。老顾推荐是网上方案先删除token,先保证不会因为重复请求,业务数据出现问题。顶多再让用户处理一次。
    出现业务异常,可以让调用方配合处理一下,重新获取新的token,再次由业务调用方发起重试请求就ok了。

    token机制缺点
    小伙伴们有没有发现,业务请求每次请求,都会有额外的请求(一次获取token请求、判断token是否存在的业务)。其实真实的生产环境中,1万请求也许只会存在10个左右的请求会发生重试,为了这10个请求,我们让9990个请求都发生了额外的请求。(当然redis性能很好,耗时不会太明显)

    2.乐观锁机制

    sql语句增加版本号

    update user set point = point + 20, version = version + 1 
    where userid=1 and version=1
    

    加上了版本号后,就让此计算赋值型业务,具备了幂等性。

    乐观锁机制缺点
    就是在操作业务前,需要先查询出当前的version版本。

    3.唯一主键机制

    这个机制是利用了数据库的主键唯一约束的特性,解决了在insert场景时幂等问题。但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键,
    如果是分库分表场景下,路由规则要保证相同请求下,落地在同一个数据库和同一表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关。

    因为对主键有一定的要求,这个方案就跟业务有点耦合了,无法用自增主键了。

    4.去重表机制

    这个方案业务中要有唯一主键,这个去重表中只要一个字段就行,设置唯一主键约束,当然根据业务自行添加其他字段
    在这里插入图片描述
    上面的主要流程就是 把唯一主键插入去重表,再进行业务操作,且他们在同一个事务中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。

    这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。
    这个方案也是比较常用的,去重表是跟业务无关的,很多业务可以共用同一个去重表,只要规划好唯一主键就行了。

    去重表机制缺点
    多增加一个表的插入操作。

    总结

    幂等性相关的介绍有几篇文章了,可以看到,解决机制有多种,选择最适合自己的就可以。但是其中,我们能总结发现,有数据库操作的情况下,唯一主键和乐观锁比较简单方便,业务接口方面,token+redis机制和source+seq机制比较方便。

    参考资料:
    https://zhuanlan.zhihu.com/p/74046140
    https://www.cnblogs.com/javalyy/p/8882144.html

    展开全文
  • 微服务架构幂等

    2019-04-11 10:18:45
    微服务幂等性 微服务架构 微服务架构是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活...

    目录

    • 微服务架构
      • 单体应用的优缺点
      • 微服务的优缺点
      • 微服务网关+服务间通信
      • 服务间通信
      • 服务发现
      • 微服务幂等性
    •  

    微服务架构

    微服务架构是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。

    和 微服务 相对应的,这种方式一般被称为 单体式开发(Monolithic)。既所有的功能打包在一个 WAR 包里,基本没有外部依赖(除了容器),部署在一个 JavaEE 容器(Tomcat,JBoss,WebLogic)里,包含了 DO/DAO,Service,UI 等所有逻辑。

    单体应用的优缺点:

    img

    优点:

    • 开发简单,集中式管理
    • 基本不会重复开发
    • 功能都在本地,没有分布式的管理和调用消耗

    缺点:

    • 效率低:开发都在同一个项目改代码,相互等待,冲突不断
    • 维护难:代码功能耦合在一起,新人不知道何从下手
    • 不灵活:构建时间长,任何小修改都要重构整个项目,耗时
    • 稳定性差:一个微小的问题,都可能导致整个应用挂掉
    • 扩展性不够:无法满足高并发下的业务需

    微服务的优缺点:

    img

    优点:

    • 它解决了复杂问题。它把可能会变得庞大的单体应用程序分解成一套服务
    • 这种架构使得每个服务都可以由一个团队独立专注开发
    • 微服务架构模式可以实现每个微服务独立部署
    • 微服务架构模式使得每个服务能够独立扩展

    缺点(挑战):

    • 微服务是一个分布式系统,其使得整体变得复杂。开发人员需要基于RPC或者消息实现微服务之间的调用和通信,而这就使得服务之间的发现、服务调用链的跟踪和质量问题变得的相当棘手。
    • 分区的数据库体系和分布式事务。更新多个业务实体的业务交易相当普遍。这些类型的事务在单体应用中实现非常简单,因为单体应用往往只存在一个数据库。但在微服务架构下,不同服务可能拥有不同的数据库。CAP原理的约束,使得我们不得不放弃传统的强一致性,而转而追求最终一致性,这个对开发人员来说是一个挑战。
    • 跨越多服务变更。在单体应用程序中,您可以简单地修改相应的模块、整合变更并一次性部署他们。相反,在微服务中您需要仔细规划和协调出现的变更至每个服务。
    • 部署基于微服务的应用程序也是相当复杂的
    • 测试

    以上问题和挑战可大体概括为:

    • API Gateway
    • 服务间调用
    • 服务发现
    • 服务容错
    • 服务部署
    • 数据调用

    目前流行的两种微服务框架解决方案(可以解决以上问题)

    • SpringBoot + SpringCloud
      • SpringBoot是 Spring 的一套快速配置框架,可以基于spring boot 快速开发单个微服务
      • Spring Cloud基于Spring Boot,为微服务体系开发中的架构问题提供了一整套的解决方案——服务注册与发现,服务消费,服务保护与熔断,网关,分布式调用追踪,分布式配置管理等路由网关
    • Dubbo + ZooKeeper
      • Dubbo是一个阿里巴巴开源出来的一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
      • ZooKeeper用来实现服务的注册与发现和进行负载均衡

    基于微服务架构的应用是分布式系统,增加了系统设计和实现的难度,主要有以下方面:

    • 运行环境中,微服务实例的网络地址是动态分配的,微服务运行的实例是动态变化的,需要一套发现机制,使服务调用者可以获取正确的服务地址。
    • 服务之间存在着复杂的依赖关系,在高并发访问下,依赖的稳定性影响系统的可用性及性能,因此需要提供容错机制,当依赖的服务发生故障时,不会使调用方线程被长时间占用不释放,避免故障在系统中蔓延。
    • 需要一个高效的进程间通信机制支持微服务之间的交互
    • 服务实例的启停及流量的调度和分配需要一套负载监控和负载均衡组件来管理。

    微服务架构的基本组件

    • 可持续交付的平台
    • 服务注册与发现组件(ZooKeeper)
    • 服务网关

    一般微服务在系统内部,通常是无状态的,用户登入信息和权限管理最好有一个统一的地方维护管理(OAuth)。(类似SSO单点登入)

    API网关 + 服务间通信

    • 一般在后台 N 个服务和 UI 之间一般会一个代理或者叫 API Gateway
      • 提供统一服务入口,让微服务对前台透明
      • 聚合后台的服务,节省流量,提升性能(PC、Android/IOS端只需记住API网关的IP,API网关会有一个后台服务IP列表)
      • 提供安全,过滤,流控等API管理功能
      • 其实这个 API Gateway 可以有很多广义的实现办法,可以是一个软硬一体的盒子,也可以是一个简单的 MVC 框架,甚至是一个 Node.js 的服务端

    img

    所有的微服务都是独立的 Java 进程跑在独立的虚拟机上,所以服务间的通信就是 IPC(Inter Process Communication),已经有很多成熟的方案。现在基本最通用的有两种方式

    服务间通信

    同步调用(阻塞)

    服务间通信:网络中只有字符串可以穿透防火墙

    • REST(JAX-RS,Spring Boot):Http通信
    REST
        api
         String json = /users/list;
         USer user = new USer();
         user.setId(json.getId());
    • RPC(Thrift, Dubbo):远程过程调用 User user = new RPCUser();

    同步调用比较简单,一致性强,但是容易出调用问题,性能体验上也会差些,特别是调用层次多的时候。一般 REST 基于 HTTP,更容易实现,更容易被接受,服务端实现技术也更灵活些,各个语言都能支持,同时能跨客户端,对客户端没有特殊的要求,只要封装了 HTTP 的 SDK 就能调用,所以相对使用的广一些。RPC 也有自己的优点,传输协议更高效,安全更可控,特别在一个公司内部,如果有统一个的开发规范和统一的服务框架时,他的开发效率优势更明显些。就看各自的技术积累实际条件,自己的选择了。
    (对外REST,对内RPC)

    异步消息调用

    • Kafka
    • Notify
    • MessageQueue

    异步消息的方式在分布式系统中有特别广泛的应用,他既能减低调用服务之间的耦合,又能成为调用之间的缓冲,确保消息积压不会冲垮被调用方,同时能保证调用方的服务体验,继续干自己该干的活,不至于被后台性能拖慢。不过需要付出的代价是一致性的减弱,需要接受数据 最终一致性;还有就是后台服务一般要实现 幂等性,因为消息送出于性能的考虑一般会有重复(保证消息的被收到且仅收到一次对性能是很大的考验);最后就是必须引入一个独立的 Broker(消息队列的中间服务器)

    Broker.png

    服务发现

    在微服务架构中,一般每一个服务都是有多个拷贝,来做负载均衡。一个服务随时可能下线,也可能应对临时访问压力增加新的服务节点。服务之间如何相互感知?服务如何管理?

    这就是服务发现的问题了。一般有两类做法,也各有优缺点。基本都是通过 Zookeeper 等类似技术做服务注册信息的分布式管理。当服务上线时,服务提供者将自己的服务信息注册到 ZK(或类似框架),并通过心跳维持长链接,实时更新链接信息。服务调用者通过 ZK 寻址,根据可定制算法,找到一个服务,还可以将服务信息缓存在本地以提高性能。当服务下线时,ZK 会发通知给服务客户端。

    ZooKeeper实现了分布式锁,解决单点故障问题

    服务注册

    服务注册与发现.png

    Dubbo是一个RPC通信框架;ZooKeeper服务注册与发现。此处两者结合使用。

    • 基于客户端的服务注册与发现

    优点是架构简单,扩展灵活,只对服务注册器依赖。缺点是客户端要维护所有调用服务的地址,有技术难度,一般大公司都有成熟的内部框架支持,比如 Dubbo。
    此处调用可以是Dubbo,服务注册为ZooKeeper

    img

    • 基于服务端的服务注册与发现

    优点是简单,所有服务对于前台调用方透明,一般在小公司在云服务上部署的应用采用的比较多。

    LB为负载均衡服务器,由LB去查询注册中心,再去调用对应指定的服务

    img

    微服务需要考虑的问题:

    • API Gateway
    • 服务间调用
    • 服务发现
    • 服务容错
    • 服务部署
    • 数据调用

    微服务幂等性

    分布式系统中的幂等性概念:用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

    幂等场景:

    可能会发生重复请求或消费的场景,在微服务架构中是随处可见的。

    • 网络波动:因网络波动,可能会引起重复请求
    • 分布式消息消费:任务发布后,使用分布式消息服务来进行消费

    • 用户重复操作:用户在使用产品时,可能无意地触发多笔交易,甚至没有响应而有意触发多笔交易
    • 未关闭的重试机制:因开发人员、测试人员或运维人员没有检查出来,而开启的重试机制(如Nginx重试、RPC通信重试或业务层重试等)

    CRUD操作分析

    • 新增类请求:不具备幂等性

    • 查询类动作:重复查询不会产生或变更新的数据,查询具有天然幂等性

    • 更新类请求:
      • 基于主键的计算式Update,不具备幂等性,即UPDATE goods SET number=number-1 WHERE id=1
      • 基于主键的非计算式Update:具备幂等性,即UPDATE goods SET number=newNumber WHERE id=1
      • 基于条件查询的更新,不一定具备幂等性(需要根据实际情况进行分析判断)
    • 删除类请求:
      • 基于主键的Delete具备幂等性
      • 一般业务层面都是逻辑删除(即update操作),而基于主键的逻辑删除操作也是具有幂等性的

    幂等性的重要性

    针对一个微服务架构,如果不支持幂等操作,那将会出现以下情况:

    • 电商超卖现象
    • 重复转账、扣款或付款
    • 重复增加金币、积分或优惠券

    超卖现象
    ​ 比如某商品的库存为1,此时用户1和用户2并发购买该商品,用户1提交订单后该商品的库存被修改为0,而此时用户2并不知道的情况下提交订单,该商品的库存再次被修改为-1这就是超卖现象。

    ​ 究其深层原因,是因为数据库底层的写操作和读操作可以同时进行,虽然写操作默认带有隐式锁(即对同一数据不能同时进行写操作)但是读操作默认是不带锁的,所以当用户1去修改库存的时候,用户2依然可以都到库存为1,所以出现了超卖现象。

    ​ 解决方案A:可以对读操作加上显式锁(即在select …语句最后加上for update)这样一来用户1在进行读操作时用户2就需要排队等待了。但问题来了,如果该商品很热门并发量很高那么效率就会大大的下降,如何解决呢?(解决方案B)

    ​ 解决方案B:我们可以有条件有选择的在读操作上加锁,比如可以对库存做一个判断,当库存小于一个量时开始加锁,让购买者排队,这样一来就解决了超卖现象。

    解决方案

    • 全局唯一ID

    如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、Redis等。如果存在则表示该方法已经执行。

    使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。但是这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,但是实现起来比较简单。

    • 去重表

    这种方法适用于在业务中有唯一标识的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据写入去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。

    • 插入或更新

    这种方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。

    • 多版本控制

    这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等:boolean updateGoodsName(int id,String newName,int version);

    在实现时可以如下:update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}

    • 状态机控制

    这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100,付款失败为99。在做状态机更新时,我们就这可以这样控制:update goods_order set status=#{status} where id=#{id} and status<#{status}

    以上就是保证接口幂等性的一些方法。

    总结

    幂等性设计不能脱离业务来讨论,一般情况下,去重表同时也是业务数据表,而针对分布式的去重ID,可以参考以下几种方式:

    • UUID
    • Snowflake
    • 数据库自增ID
    • 业务本身的唯一约束
    • 业务字段+时间戳拼接
    展开全文
  • Python微信订餐小程序课程视频 ...Python实战量化交易理财系统 ...幂等一词来自于数学中的幂等,即f(f(x)) = f(x)。 需要保证幂等的场景 查询类的读操作,天然是幂等的,多次调用不会有副作用。需考虑以下几种写
  • 专为一线技术从业者学习互联网支撑亿级流量场景的三高架构师课程本课程授课内容为互联网支撑亿级流量场景的三高架构师训练营之有状态-幂等-分布式事务讲解大型互联网系统一般有如下特点:用户多,分布广:全球用户...
  • 如何用较低成本实现系统的高可用、易伸缩、可扩展目标就显得越发重要。为了解决这一系列问题,系统架构也在不断演进。传统的集中式系统已经逐渐无法满足要求,分布式系统被使用在更多的场景中。分布式系统由独立的...
  • 幂等性 如何解决类似的幂等性问题 幂等表方案 幂等性 发一次接口调用与发多次相同的接口消息都能得到与预期相符的结果 如何解决类似的幂等性问题 每重发一次请求1号工资就会+500,幂等性就被破坏了 ...
  • 线上故障之-redis锁处理幂等性失效和幂等性问题解决方案redis锁处理幂等性失效幂等性设计方法1. insert前先select2. 加悲观锁 redis锁处理幂等性失效 @Override @Transactional(rollbackFor = Exception.class) ...
  • 1:什么是幂等性? 幂等【idempotence】是一个数学与计算机学的概念,常见于抽象代数中。在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。 ps.幂等函数,或幂等方法是指可以使用...
  • 幂等实际是一个数学上的概念,在数学上如果函数满足 f(x) = f(f(x)),那么我们称函数f具备幂等性。举个栗子,假设f(x) =|x|,即函数f表示取x的绝对值,那么f(x) = f(f(x))也成立,即f(x) =||x| |。说完了幂等的原始...
  • 1.1 幂等性定义 数学定义 在数学里,幂等有两种主要的定义: 在某二元运算下,幂等元素是指自己重复运算(或对于函数是为复合)的结果等于它自己的元素。例如,乘法下唯一两个幂等实数为0和1,即s*s=s 某一元运算...
  • 分布式.幂等

    2022-07-30 15:39:30
    数学中在一次元运算为幂等时,其作用在任一元素两次后会和其作用一次的结果相同;在二次元运算为幂等时,自己重复运算的结果等于它自己的元素。计算机学中幂等指多次操作产生的影响只会跟一次执行的结果相同,通俗的...
  • 安全架构-api接口幂等性设计

    千次阅读 2020-12-19 12:37:30
    安全架构-api接口幂等性设计 今天开始研究API接口相关的安全性,api接口在当前互联网开发,分布式RPC,微服务架构,RESTful架构中,应用非常广泛,注意api接口的安全,也是架构师必须熟练且牢记的意识和技能。 api...
  • 1、幂等幂等性:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。 幂等性接口:是指可以使用相同参数重复执行,并能获得相同结果的接口。 数学中:在一次元运算为幂等时,其...
  • 接口设计--幂等

    千次阅读 2022-04-05 17:34:01
    1、什么是幂等性? 幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复...
  • 编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。就是说,一次和多次请求某一个资源会产生同样的作用影响。
  • 图灵学院java架构师学习路线-分布式架构为什么需要保证幂等性编程中的“幂等性”是指任意执行次数的效果,与一次执行的效果相同。具有幂等设计的接口可确保无论该接口被调用一次还是多次,都可以获得相同的结果。在...
  • 安全架构-HTTP协议幂等

    千次阅读 2020-12-19 13:15:39
    幂等性设计是架构师必须具备的技能之一,需要深入理解幂等性设计的相关原理和解决方法。 上一篇文章中介绍了api接口的幂等性,主要从业务逻辑处理的方面进行业务保障,做到实现幂等性。本文介绍HTTP协议本身在幂等...
  • Pre 我们来回顾下单体时代 ...分布式锁 能否解决幂等性问题? 分布式锁解决什么问题? 简言之: 解决跨进程间无法共享本地资源的问题 ,解决的是分布式场景下并发访问的问题。 为什么需要确保并发.
  • Java架构篇rest api 幂等

    千次阅读 2018-09-14 11:08:03
    为什么需要幂等性 在设计rest api的时候,调用方由于逻辑问题或者网络问题,超时导致的retry都可能对rest接口进行多次调用,所以rest api设计的时候注意幂等性。 哪些是幂等操作 安全:不改变资源的...
  • 目录1 幂等性介绍2 restful风格调用产生的问题3 接口幂等性问题解决3.1 前端防重3.2 PRG模式防止表单重复提交4 常用Token机制解决服务幂等性4.1 方案介绍5 服务幂等性5.1 并发不高和高并发的情况加锁解决 ...
  • Java中的幂等

    千次阅读 2019-12-18 18:19:44
    深入理解Java中的幂等性什么是幂等幂等性的使用场景幂等和防重保证幂等性的情况设计幂等性服务保证幂等策略防重复提交策略乐观锁防重表分布式锁token令牌支付缓冲区幂等的不足 什么是幂等幂等性定义: 一次和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 45,888
精华内容 18,355
热门标签
关键字:

幂等架构