分布式下,远程接口调用的安全问题

初步介绍

目前很多服务都是分布式开发的,就算自己做项目,练手,也都是采用的分模块,切分端口号的操作,那么这样就必然涉及一个远程调用的问题。那么一但涉及远程调用,就需要考虑一个性能以及安全的问题,本文就是初步讲解安全的问题

本文的一个整体流程

  1. 常规业务流程分析
  2. 具体每一步的业务
  3. 针对每一步遇到问题的解决

常规业务流程分析

  1. 我们做电商平台都知道一个流程,订单与发货模块,都是分开处理的,端口号是不一样的,这样导致的也就是我们只能跨域请求
  2. 常规的,也就是客户下单,然后后台发送一个数据到发货模块,然后发货系统发出商品
  3. 中间涉及访问数据库之类的操作
  4. 具体流程图我就不作出演示,业务很简单

数据库表
在这里插入图片描述
这里的数据库表只是简单模拟,没有正式开发的数据要求,id(订单ID),name(订单描述之类),status(订单状态),version(订单版本)
实体类

 public class Order {
    private int id;
    private String name;
    private int status;
    private int version;
	//省略getset方法  
}

Mapper

public interface OrderMapper {

    @Select("select * from table_order where id=#{id}")
    Order querryById(int id);

    @Insert("insert into table_order values(#{id},#{name},#{status},#{version})")
    void insertOrder(Order order);

    @Update("update table_order set status=#{status} where id=#{id}")
    int updateOrder(Order order);

    @Update("update table_order set version=#{version} where id=#{id}")
    int updateOrderByVersion(Order order);
}

由于模仿项目,直接通过接口注解操作,方便
Service层

@Service
public class OrderServiceImpl implements OrderService {

    private Logger logger= LoggerFactory.getLogger(getClass());

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InvokeService invokeService;

    private final static String url="http://localhost:8080/deliver?id=";

    @Override
    public Order querryById(int id) {
        Order order=orderMapper.querryById(id);
        if (order!=null)
            return order;
        return null;
    }

    @Override
    public void insertOrder(Order order) {
        orderMapper.insertOrder(order);
    }

    @Override
    @Transactional
    public String sendOrder(Order order) {
        int id=order.getId();
        String status=invokeService.invoke(url,id);

        order=new Order();
        order.setStatus(1);
        order.setId(id);
        orderMapper.updateOrder(order);
        return status;
    }
}

由于是跨域请求,所以我们分离一个service出来,专门做跨域问题(invokeService)
invokeService

@Service
public class InvokeServiceImpl implements InvokeService {
    
    @Autowired
    private RestTemplate restTemplate;

    @Override
    public String invoke(String url, int id) {
        String body = restTemplate.getForEntity(url + id, String.class).getBody();
        return body;
    }
}

跨域处理,端口和之前不一样哦

@RestController
public class DeliveController {

    private Logger logger= LoggerFactory.getLogger(getClass());

    @RequestMapping("/deliver")
    public String deliver(int id){
        logger.info("===========接受订单指令,准备发货");
        try {
            TimeUnit.SECONDS.sleep(10);
        }catch (InterruptedException e){
            e.printStackTrace();
            return "-1";
        }
        return "1";
    }
}

当前服务器Controller的代码就不贴出来了,就是一个调用service的代码,这里可以很明显的看出一个业务逻辑了

  1. 首先获取到订单信息
  2. 远程调用接口,以做其余数据的处理,以及发货处理
  3. 接受返回值,判断出货是否成功,然后修改数据库信息
  4. 因为涉及修改,所以增加了事务管理

第一步分析

  1. 以上代码,看上去没有任何的问题,也符合业务逻辑以及对数据库的修改,也增加了事务管理,好像没啥问题,那么以下的问题可以思考一下
  2. 因为增加了事务管理,所以需要保持事务的特性,我们在做跨域请求的时候,当前请求持有这个链接,导致数据库无法操作,而导致一直等待10s,性能以及用户体验很受伤
  3. 这个对于多线程,高并发而言,是否会出现多发货的问题呢?是否会出现一个严重BUG呢?

解析

  1. 针对第二个问题,我们可以考虑使用编程式事务处理,可以将我们的数据库操作和跨域请求分开,这样就不会导致我们一个独占链接的问题了
  2. 针对第三个问题,后面继续分析

初步优化

	@Autowired
    private TransactionTemplate transactionTemplate;

    public String sendOrderByTemplate(Order order) {
        int id=order.getId();
       
        String status=invokeService.invoke(url,id);

        transactionTemplate.execute(new TransactionCallback<Object>() {

            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                Order order1=new Order();
                order1.setId(id);
                order1.setStatus(Integer.parseInt(status));
                orderMapper.updateOrder(order1);
                return null;
            }
        });
        return status;
    }

通过以上的这个代码,就可以明显的优化第二个问题给我们带来的影响了,因为我们的跨域请求和数据库访问是分开的,是两个独立的代码,不会导致数据库链接资源被浪费掉。

但是,我们这个还是不能解决我们多线程的问题,因为只要是高并发,就会同一时间怼到我们方法上面来,就会同一时间去请求跨域信息,已导致“发货系统”出现问题,那么如何解决呢?

最终优化

public String sendOrderBySingle(Order order) {
        int id=order.getId();
        boolean flag=(boolean)transactionTemplate.execute(new TransactionCallback<Object>() {

            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                Order order1=new Order();
                order1.setId(id);
                order1.setStatus(3);
                order1.setVersion(1);

                return 1==orderMapper.updateOrderByVersion(order1);
            }
        });

        String status="";

        if (flag){
            status=invokeService.invoke(url,id);

            final String  finalStatus = status;
            transactionTemplate.execute(new TransactionCallback<Object>() {

                @Override
                public Object doInTransaction(TransactionStatus transactionStatus) {
                    Order order1=new Order();
                    order1.setId(id);
                    order1.setStatus(Integer.parseInt(finalStatus));
                    orderMapper.updateOrder(order1);
                    return null;
                }
            });
        }else {
            logger.info("======================远程调用接口失败");
        }


        return status;
    }

我们通过以上的方法,就可以很好的避免这个问题了,因为你有一个标志位以及版本信息存在,所有的请求,都要先走上面的方法,然后标志位就更新了,这样就不会导致所有请求都访问跨域了。

是不是感觉这样的逻辑理解起来很容易呢?下面总体总结一下

总结

  1. 整体业务逻辑都很清晰,只是考虑一个数据库链接资源以及高并发的问题
  2. 然后优化的方法,就采用事务分离,以及标志位和版本控制
  3. 其实,这里对第三种优化做一个解释,为什么会这样去想

分析
我们都知道,数据库有乐观锁与悲观锁吧。悲观锁很好理解就是锁住每一次的请求,而乐观锁就不会。
然后乐观锁的实现方式就是CAS算法,这个算法就是比较并且替换。我们首先比较版本号,如果版本号一样就操作,否则不会做出操作,因为一旦版本号不一样,就说明线程不同步了,就说明数据的有效信息不对了。
这就是第三种优化的核心思想,就是一个版本的控制

疑问

  1. 那么以上的方法就真的 安全了吗?考虑以下的问题
  2. 如果真的是高并发,同时怼到服务器上,虽然不会怼到跨域方法上面,但是会直接怼到本地数据库呀,有可能瞬间你的数据库就瘫痪了
  3. 如果你考虑加缓存,将数据放在缓存上,每次读取都走内容读,就很快,那么你考虑过缓存雪崩吗?或者刚好遇到缓存时间失效,过期了呢?
  4. 那么,针对以上问题又如何解释,后面文章会继续分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值