美文网首页Docker容器程序员微服务架构和实践
弱网络下,非幂等性操作的分布式微服务处理

弱网络下,非幂等性操作的分布式微服务处理

作者: 浑身演技 | 来源:发表于2017-10-13 20:10 被阅读179次

弱网络下,非幂等性操作的分布式微服务处理

背景

在浏览器访问服务器时,网速非常慢的情况下。为了获取结果,用户常常会进行重复点击操作。这将会使得一些非幂等性操作的操作结果变得非常不可靠。

举例而言,用户进行付款操作就是一个非幂等性操作。

非幂等性,简单而言,就是一个操作是不可重复的。

方案

在用户浏览器cookie中,加上idempotent.token ,然后在各个微服务中使用拦截器拦截,并且使用分布式锁 进行全局锁定。

由于微服务是分布式的,那么将会出现一种情况是,在某种负载均衡的策略,用户在访问仓库微服务(1),并且同时访问仓库微服务(2),并且同时对库存进行修改。这种情景是合乎情理的,并且他们将会带上同一个idempotent.token进行仓库的微服务操作。这个时候是必须要使用分布式锁进行加锁操作。

原理与实现

拦截器

拦截器运行结果示意图.png

在cookie中设置idempotent.token,每次业务访问的时候,同一URI且同一HttpServletRequest method的话,那么将会只允许进行有且仅有一次的访问。第二次访问的时候,将会返回401的错误。不被验证。

在大部分的若网络的情况下,已经能够拦截住多次访问,导致出现非幂等的情况。

具体的源代码如下:

/**
 * 幂等的拦截器,用于处理非幂等性操作。
 * 幂等性将不予处理,直接放行
 */
public class IdempotentTokenInterceptor extends HandlerInterceptorAdapter {
    private static final Logger log = LoggerFactory.getLogger(IdempotentTokenInterceptor.class);
    public static final String IDEMPOTENT_TOKEN = "idempotent.token";
    @Resource
    private IdempotentDb<IdempotentRo> defaultIdempotentDb;
    @Value("${spring.cloud.consul.host}")
    private String consulHost;
    @Value("${spring.cloud.consul.port}")
    private int consulPort;

    /**
     * 返回幂等错误的信息
     *
     * @param response http的响应
     * @param message  返回的http message
     * @return true -> 继续向下执行;false -> 不继续向下执行,将被拦截
     */
    private boolean with(HttpServletResponse response, String message) {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        CookieUtil.addCookie(response, IDEMPOTENT_TOKEN, UUID.randomUUID().toString().replaceAll("-", ""), 3600 * 2);
        try (PrintWriter writer = response.getWriter()) {
            writer.append(new Gson().toJson(Result.ResultBuilder.errorWith(message).build()));
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
            log.error("cannot close response print writer");
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return Optional.ofNullable(request.getCookies())
                .map(cookies -> Stream.of(cookies).filter(x -> IDEMPOTENT_TOKEN.equalsIgnoreCase(x.getName()))
                        .map(x -> {
                            String timeStamp = UUID.randomUUID().toString().replaceAll("-", "");
                            // 如果已经找到幂等token,判断是否幂等的值为空
                            return Optional.ofNullable(x.getValue()).map(v -> {
                                List<IdempotentRo> list = defaultIdempotentDb.findByAuthKey(v);
                                if (CollectionUtils.isEmpty(list))
                                    list = new ArrayList<>();

                                // 查找该url是否已经存在与幂等键值对之中
                                boolean hasRequested = list.stream().anyMatch(ir -> request.getMethod().equals(ir.getMethod())
                                        && request.getRequestURI().equals(ir.getAuthUrl()));
                                if (hasRequested) {
                                    log.error("already requested with idempotent token  from the URL of {} by {} method", request.getRequestURI(), request.getMethod());
                                    return with(response, "Please do not repeat the submission");
                                } else {
                                    defaultIdempotentDb.insert(IdempotentRo.IdempotentRoBuilder.build().set(v, request.getRequestURI(), request.getMethod()).create());
                                    CookieUtil.addCookie(response, IDEMPOTENT_TOKEN, UUID.randomUUID().toString().replaceAll("-", ""), 3600 * 2);
                                    return true;
                                }
                            }).orElseGet(() -> {
                                log.error("cannot find value of idempotent token  from the URL of {} by {} method", request.getRequestURI(), request.getMethod());
                                return with(response, "Please do not fake the idempotent token");
                            });

                        }).reduce((x, y) -> x && y).orElseGet(() -> {
                            log.error("cannot find idempotent token from the URL of {} by {} method", request.getRequestURI(), request.getMethod());
                            return with(response, "Please do request with idempotent token");
                        })).orElseGet(() -> {
                    log.error("cannot find cookies from the URL of {} by {} method", request.getRequestURI(), request.getMethod());
                    return with(response, "Please do not fake the request...");
                });

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        super.afterCompletion(request, response, handler, ex);

    }
}

幂等性分布式锁

在此系统中,我们的DistributedLock接口实现了Java的Lock,这对于大部分开发者而言,无学习难度的使用幂等性分布式锁。

在consul中,我们通过以sessionId为key创建了Consul的会话,并且session中key的值是不会更改的。

那么,相当明显的是,sessionId应该是全局唯一的。

一般而言,由于consul的k/v存储是相当有限的,推荐存储小于250kb的k/v。

我们的sessionId,一般会将其进行MD5的摘要算法来获取摘要。

当锁进行释放时,我们只需要destroy session,释放所拥有的资源就行。

具体代码如下:

public class IdempotentDistributedLock implements DistributedLock {
    private final Consul consul;
    private final Session value;
    private final SessionClient sessionClient;
    private final SessionCreatedResponse session;

    public static final String KEY = "consul_key";
    public static final Logger log= LoggerFactory.getLogger(IdempotentDistributedLock.class);

    public IdempotentDistributedLock(Consul consul, String sessionId) {
        this.consul = consul;
        // 获取摘要,作为session id,并创建会话
        this.value = ImmutableSession.builder().name(sessionId).build();
        this.sessionClient = consul.sessionClient();
        this.session = sessionClient.createSession(value);

    }

    @Override
    public void lock() {
        // 进行获取锁的操作,获取不到则将线程进行放入到缓冲队列中
        KeyValueClient keyValueClient = consul.keyValueClient();
        boolean hasAcquired=keyValueClient.acquireLock(KEY,this.value.getName().get(), this.session.getId());

        if(!hasAcquired)
            throw new AlreadyLockedException();
    }

    @Override
    @Deprecated
    public void lockInterruptibly() throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        KeyValueClient keyValueClient = consul.keyValueClient();
        keyValueClient.deleteKey(KEY);
        sessionClient.destroySession(session.getId());
    }

    @Override
    @Deprecated
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
}

性能测试

在Chrome的slow 3g下,用户访问同个操作,主要的延迟来自于业务的处理。

好处

  • 能够有效的防止用户重复点击
  • 分布式锁实现JVM的Lock接口,用户可无学习难度的使用,并且作为分布式锁进行资源锁定
  • 在以consul的作为一致性的基础服务情况下,用户也可以有效的进行调试排查,直接将所有会话列表查询出来

局限性

  • 用户对于单一资源的锁定将会出现有时难以决断
  • 用户只能进行一次性操作,对于其他想要进行资源的操作的话,将会直接熔断,不再进行等待

未来的趋势

  • 将会解决分布式锁中,复杂多资源的锁定

引用

相关文章

  • 弱网络下,非幂等性操作的分布式微服务处理

    弱网络下,非幂等性操作的分布式微服务处理 背景 在浏览器访问服务器时,网速非常慢的情况下。为了获取结果,用户常常会...

  • 幂等性

    1.幂等性 幂等性是分布式环境下常见的问题;幂等性指的是多次操作,结果是一致的。(多次操作数据库数据是一致的。)常...

  • 为什么我们想要业务主键,想要幂等

    为什么我们想要业务主键,想要幂等 ​ 在分布式微服务场景下,有太多的环节可以引发错误的处理(包括丢失或者重复处...

  • 高并发下接口幂等性解决方案

    一、幂等性概念 二、分布式系统 中的幂等性 在编程中,一个幂等操作的特点是其任意多次执行所产生的影响与一次执行的影...

  • 接口的幂等性的N种考虑

    分布式服务接口的幂等性如何设计 什么是幂等性 一个分布式系统中的某个接口,要保证幂等性,该如何保证?这个事儿其实是...

  • RabbitMQ消息中间件技术精讲10 高级篇三 幂等性保障不重

    利用幂等性保障消息不被重复消费 本文主要内容: 一:幂等性概念 什么是幂等性? 在网络超时等问题除外下,要求一次或...

  • 幂等性

    理解 HTTP 幂等性(15')系统幂等以及常用实现方式(10')分布式系统互斥性与幂等性问题的分析与解决(30')

  • post, put, patch三者区别

    1. post 非幂等操作 一般用于创建资源使用 2. put 幂等操作 更新已知资源() 3. patch 幂等...

  • 接口设计的幂等性考虑

    分布式系统接口幂等性 1.幂等性定义 1.1 数学定义 在数学里,幂等有两种主要的定义:- 在某二元运算下,幂等元...

  • JAVA开发知识点集合

    分布式 分布式系统学习资料(ing) 理解HTTP幂等性消费幂等 Dubbo Dubbo架构设计详解 跨域 关于跨...

网友评论

    本文标题:弱网络下,非幂等性操作的分布式微服务处理

    本文链接:https://www.haomeiwen.com/subject/vwltuxtx.html