Skip to content

Commit

Permalink
add seckill
Browse files Browse the repository at this point in the history
  • Loading branch information
hadyang committed Mar 16, 2020
1 parent f358c56 commit bcfed06
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 11 deletions.
42 changes: 42 additions & 0 deletions content/docs/architecture/design/seckill/index.md
@@ -0,0 +1,42 @@
---
title: 秒杀系统
date: 2020-03-16
draft: false
categories: architecture
---

## 问题场景

在进行系统设计的过程中,首先问题场景的特点。秒杀系统是十分典型的高并发场景,其特点也十分显著:高并发、低库存、高瞬时流量。再者分析整个系统的输入输出,即大概的 API 网关拥有的功能:查(用户查询商品信息)、改(用户购买商品)。将系统的特点和功能分析完毕后,就可以根据这些信息进行系统设计。一个常规的秒杀系统从前到后,依次有:

```log
 前端页面 -> 代理服务 -> 后端服务 -> 数据库
```

根据这个流程,一般优化设计思路:将 **请求拦截在系统上游,降低下游压力**。在一个并发量大,实际需求小的系统中,应当尽量在前端拦截无效流量,降低下游服务器和数据库的压力,不然很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时。

整体优化手段包含:**缓存****限流****削峰(MQ)****异步处理****降级****熔断****SET化****快速扩容**


## 前端页面

- 资源静态化:将活动页面上的所有可以静态的元素全部静态化,尽量减少动态元素;通过CDN缓存静态资源,来抗峰值。
- 禁止重复提交:用户提交之后按钮置灰,禁止重复提交
- URL动态化:防止恶意抓取

## 代理服务

利用负载均衡(例如 Nginx 等)使用多个服务器并发处理请求,减小服务器压力。

## 后端服务

- 用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取 IP 限流
- 业务拆分
- 利用 MQ 削峰
- 利用缓存应对大量查询请求
- 利用缓存应对写请求(注意数据一致性、持久性问题):缓存也是可以应对写请求的,可把数据库中的库存数据转移到 Redis 缓存中,所有减库存操作都在 Redis 中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。

## 数据库

- 多数据库:防止数据热点问题
- 优化 SQL 防止死锁
2 changes: 1 addition & 1 deletion content/docs/basic/net/tcp/index.md
Expand Up @@ -121,7 +121,7 @@ static inline struct sock *__inet_lookup(struct net *net,

> TCP采用四次挥手关闭连接如图所示为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
>
> 这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
> 这是因为服务端的 LISTEN 状态下的 SOCKET 当收到 SYN 报文的建连请求后,它可以把 ACK 和 SYN (ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。


Expand Down
11 changes: 5 additions & 6 deletions content/docs/java/concurrent/interrupt/index.md
Expand Up @@ -11,7 +11,7 @@ categories: java

关于中断状态,我们需要重点关注 `Thread` 类中的以下几个方法:

```
```java
// Thread 类中的实例方法,持有线程实例引用即可检测线程中断状态
public boolean isInterrupted() {}

Expand All @@ -26,7 +26,7 @@ public void interrupt() {}

我们说 **中断一个线程,其实就是设置了线程的 `interrupted status``true`**,至于说被中断的线程怎么处理这个状态,那是那个线程自己的事。如以下代码:

```
```java
while (!Thread.interrupted()) {
doWork();
System.out.println("我做完一件事了,准备做下一件,如果没有其他线程中断我的话");
Expand Down Expand Up @@ -61,9 +61,8 @@ while (!Thread.interrupted()) {
除了几个特殊类(如 `Object,Thread`等)外,**感知中断并提前返回是通过轮询中断状态来实现的**。我们自己需要写可中断的方法的时候,就是通过在合适的时机(通常在循环的开始处)去判断线程的中断状态,然后做相应的操作(通常是方法直接返回或者抛出异常)。当然,我们也要看到,如果我们一次循环花的时间比较长的话,那么就需要比较长的时间才能感知到线程中断了。

## 处理中断
一旦中断发生,我们接收到了这个信息,然后怎么去处理中断呢?本小节将简单分析这个问题。

我们经常会这么写代码:
一旦中断发生,我们接收到了这个信息,然后怎么去处理中断呢?本小节将简单分析这个问题。我们经常会这么写代码:

```java
try {
Expand Down Expand Up @@ -100,7 +99,7 @@ public final void acquire(int arg) {
}
```

而对于 `lockInterruptibly()` 方法,因为其方法上面有` throws InterruptedException` ,这个信号告诉我们,如果我们要取消线程抢锁,直接中断这个线程即可,它会立即返回,抛出 `InterruptedException` 异常。
而对于 `lockInterruptibly()` 方法,因为其方法上面有 `throws InterruptedException` ,这个信号告诉我们,如果我们要取消线程抢锁,直接中断这个线程即可,它会立即返回,抛出 `InterruptedException` 异常。

在并发包中,有非常多的这种处理中断的例子,提供两个方法,分别为响应中断和不响应中断,对于不响应中断的方法,记录中断而不是丢失这个信息。如 `Condition` 中的两个方法就是这样的:

Expand Down Expand Up @@ -129,6 +128,6 @@ synchronized (this) {
上面的代码会造成什么问题?仔细分析可以发现,代码中如果抛出 `InterruptedException`,就会陷入死循环中,导致异常日志打爆。为什么会这样呢?首先我们来看下这两个方法:

- `wait()`: if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
- `Thread.currentThread().interrupt()`:If none of the previous conditions hold then this thread's interrupt status will be set.
- `Thread.currentThread().interrupt()`: If none of the previous conditions hold then this thread's interrupt status will be set.

`wait()` 在当前线程有中断标志位时抛出中断异常;而 `interrupt()` 如果当前线程没有在`wait()`等阻塞操作,则标记中断。这样就陷入死循环,无限的打印 ERROR 日志。正确的处理 `InterruptedException` 是很重要的。
4 changes: 2 additions & 2 deletions content/docs/java/concurrent/synchronized/index.md
Expand Up @@ -57,11 +57,11 @@ Java中的每一个对象都可以作为锁。

同步块的作用范围应该尽可能小,仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩小,缩短阻塞时间,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。 

但是加锁解锁也需要消耗资源,如果存在一系列的连续加锁解锁操作,可能会导致不必要的性能损耗。 **锁粗化就是JVM将多个连续的加锁、解锁操作连接在一起**,扩展成一个范围更大的锁,避免频繁的加锁解锁操作。
但是加锁解锁也需要消耗资源,如果存在一系列的连续加锁解锁操作,可能会导致不必要的性能损耗。 **锁粗化就是 JVM 将多个连续的加锁、解锁操作连接在一起**,扩展成一个范围更大的锁,避免频繁的加锁解锁操作。

## 锁消除

Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,经过逃逸分析,**去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁**,可以节省毫无意义的请求锁时间
Java 虚拟机在 JIT 编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,经过逃逸分析,**去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁**,可以节省毫无意义的请求锁时间

## Synchronized vs ReentrantLock

Expand Down
4 changes: 2 additions & 2 deletions content/docs/java/generics/index.md
Expand Up @@ -21,7 +21,7 @@ Java中的泛型基本上都是在编译器这个层次来实现的,**在生

- **泛型的类型参数不能用在Java异常处理的catch语句中**。因为异常处理是由`JVM`在运行时刻来进行的。由于类型信息被擦除,`JVM`是无法区分两个异常类型`MyException<String>``MyException<Integer>`的。对于`JVM`来说,它们都是 `MyException`类型的。也就无法执行与异常对应的catch语句。

类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如`T get()`方法声明就变成了`Object get()``List<String>`就变成了`List`。接下来就可能需要生成一些桥接方法(bridge method)。这是由于擦除了类型之后的类可能缺少某些必须的方法。比如考虑下面的代码:
类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉 `<>` 的内容。比如`T get()`方法声明就变成了`Object get()``List<String>`就变成了`List`。接下来就可能需要生成一些桥接方法(bridge method)。这是由于擦除了类型之后的类可能缺少某些必须的方法。比如考虑下面的代码:


```
Expand All @@ -44,7 +44,7 @@ public void wildcard(List<?> list) {
}
```

>如上所示,试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。
> 如上所示,试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。
因为对于`List<?>`中的元素只能用`Object`来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。 如 **`List<? extends Number>`说明List中可能包含的元素类型是`Number`及其子类。而`List<? super Number>`则说明List中包含的是Number及其父类**。当引入了上界之后,在使用类型的时候就可以使用上界类中定义的方法。

Expand Down
1 change: 1 addition & 0 deletions content/docs/menu/index.md
Expand Up @@ -86,6 +86,7 @@ headless: true
- [流量控制]({{< relref "/docs/architecture/concurrent/flow-control/index.md" >}})
- [系统设计]({{< relref "/docs/architecture/design/_index.md" >}})
- [短链接系统]({{< relref "/docs/architecture/design/tinyURL/index.md" >}})
- [秒杀系统]({{< relref "/docs/architecture/design/seckill/index.md" >}})
- [分布式]({{< relref "/docs/architecture/distributed/_index.md" >}})
- [分布式一致性与共识算法]({{< relref "/docs/architecture/distributed/consensus/index.md" >}})
- [分布式缓存]({{< relref "/docs/architecture/distributed/cache/index.md" >}})
Expand Down

0 comments on commit bcfed06

Please sign in to comment.