人的知识就好比一个圆圈,圆圈里面是已知的,圆圈外面是未知的。你知道得越多,圆圈也就越大,你不知道的也就越多。

0%

悠悠商城--web安全

架构原则

数据量尽量少

数据在网络上传输需要时间,且不管是请求数据还是返回数据都需要服务器做处理,而服务器在写网络时通常都要做压缩和字符编码,这些都非常消耗 CPU,所以减少传输的数据量可以显著减少 CPU 的使用。

  • 简化秒杀页面的大小,去掉不必要的页面装修效果
  • JS/CSS压缩,减少流量

请求数要尽量少

浏览器每发出一个请求都多少会有一些消耗,例如建立连接要做三次握手,有的时候有页面依赖或者连接数限制,一些请求(例如 JavaScript)还需要串行加载等。另外,如果不同请求的域名不一样的话,还涉及这些域名的 DNS 解析,可能会耗时更久。因此减少请求数可以显著减少以上这些因素导致的资源消耗。

  • 合并 CSS 和 JavaScript 文件,以减少请求数

路径要尽量短

所谓“路径”,就是用户发出请求到返回数据这个过程中,需求经过的中间的节点数。
每增加一个连接都会增加新的不确定性。从概率统计上来说,假如一次请求经过 5 个节点,每个节点的可用性是 99.9% 的的话,那么整个请求的可用性是:99.9% 的 5 次方,约等99.5%。
所以缩短请求路径不仅可以增加可用性,同样可以有效提升性能(减少中间节点可以减少数据的序列化与反序列化),并减少延时(可以减少网络传输耗时)。

  • 将多个相互强依赖的应用合并部署在一起,把远程过程调用(RPC)变成 JVM 内部之间的方法调用。

依赖要尽量少

所谓依赖,指的是要完成一次用户请求必须依赖的系统或者服务,这里的依赖指的是强依赖。
要减少依赖,我们可以给系统进行分级,比如 0 级系统、1 级级系统、2 级系统、3 级系统,0 级系统如果是最重要的系统,以此类推。
注意,0 级系统要尽量减少对 1 级系统的强依赖,防止重要的系统被不重要的系统拖垮。例如支付系统是 0 级系统,而优惠券是 1 级系统的话,在极端情况下可以把优惠券给降级,防止支付系统被优惠券这个 1 级系统给拖垮。

  • 次级系统服务降级

不要有单点

单点意味着没有备份,风险不可控,我们设计分布式系统最重要的原则就是“消除单点”。
要避免单点,关键点是避免将服务的状态和机器绑定,即把服务无状态化,这样服务就可以在机器中随意移动。

  • 部署配置中心,实现配置动态化

动静分离

所谓“动静分离”,其实就是把用户请求的数据(如 HTML 页面)划分为“动态数据”和“静态数据”。
简单来说,“动态数据”和“静态数据”的主要区别就是看页面中输出的数据是否和 URL、浏览者、时间、地域相关,以及是否含有Cookie 等私密数据。
强调一下,我们所说的静态数据,不能仅仅理解为传统意义上完全存在磁盘上的 HTML 页面,它也可能是经过 Java 系统产生的页面,但是它输出的页面本身不包含上面所说的那些因素。也就是所谓“动态”还是“静态”,并不是说数据本身是否动静,而是数据中是否含有和访问者相关的个性化数据。

  1. 把静态数据缓存到离用户最近的地方
  • 用户浏览器
  • 服务器缓存
  • CDN
  1. 静态化改造就是要直接缓存 HTTP 连接
    Web 代理服务器根据请求 URL,直接取出对应的 HTTP 连接而不是仅仅缓存数据。Web 代理服务器根据请求 URL,直接取出对应的 HTTP 响应头和响应体然后直接返回,这个响应过程简单得连 HTTP 协议都不用重新组装,甚至连 HTTP 请求头也不需要解析。

  2. 选择合适的缓存框架

  • Java
  • Nginx
  • Apache
  • Varnish

流量削峰

削峰的存在,一是可以让服务端处理变得更加平稳,二是可以节省服务器的资源成本。

  • 排队
  • 答题
  • 分层过滤

减库存

  1. 基于数据库行锁

    1
    update goods set inventory = inventory-1 where id = #{goodsId} and inventory > 0

    问题:大量锁竞争时,会影响数据库性能。

  2. 使用数据库乐观锁

    1
    update goods set inventory = inventory-1 where id = #{goodsId} and inventory > 0 and version = #{version}

    问题:库存100,且同时只有100人抢购商品时,实际卖出的商品可能少于100。

  3. 使用redis令牌桶
    预先创建n个令牌桶,n等于商品的个数。请求到来时,先获取令牌桶,只有获取了令牌桶的请求才能实际购买商品。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 创建令牌桶
    List<String> tokens = LongStream.range(0, number)
    .mapToObj(index -> "spike_" + UUID.randomUUID().toString().replace("-", ""))
    .collect(Collectors.toList());
    redisTemplate.opsForList().leftPushAll(String.valueOf(goodsId), tokens);

    // 获取令牌桶
    String token = redisTemplate.opsForList().leftPop(String.valueOf(goodsId));
    if (StringUtils.isEmpty(token)) {
    throw new RuntimeException(String.format("商品:%s已经售空", goodsId));
    }

    问题:实际卖出的商品可能少于100。

  4. 使用redis预减库存

    1
    2
    3
    4
    5
    6
    7
    // 判断redis中库存是否小于0
    Long stock = redisTemplate.opsForValue().decrement("spike_" + goodsId);
    if (stock == null || stock < 0) {
    // 内存标记商品已售空
    goodsOverMap.put(goodsId, true);
    throw new RuntimeException(String.format("商品已经售空,商品id:%s", goodsId));
    }
  5. 异步减库存/创建订单

    1
    2
    3
    4
    5
    6
    7
    String token = redisTemplate.opsForList().leftPop(String.valueOf(goodsId));
    if (StringUtils.isEmpty(token)) {
    throw new RuntimeException(String.format("商品:%s已经售空", goodsId));
    }

    // 异步发送秒杀消息
    sendSpikeMsg(goodsId, userId);

兜底方案

降级

所谓“降级”,就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。

限流

限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。

拒绝服务

当系统负载达到一定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2*CPU 核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的保护方式。

优化

静态资源优化

  • JS/CSS压缩,减少流量
  • CDN就近访问

缓存

  • 页面缓存
  • 对象缓存

其它

  • 隐藏秒杀地址
小礼物走一走,来 Github 关注我