听风的博客

行走,辨思,记录


  • 首页

  • 归档

扣库存的方案

发表于 2018-12-07

扣库存的场景及问题

扣库存是电商系统里一个非常基础的操作。

在业务上,它必须要能达到:

  • 不超卖
  • 重试时,不出现假卖

在非功能需求上,它需要做到:

  • 应对高并发的流量

单件商品扣库存方案及其优化

约定扣的库存数量为x

方案一 直接更新库存

A123459C-0ECF-41D8-BA06-63F4E45D929F

1
update sku_stock set stock = stock - x;

由于库存检查是在服务里做的,并发现情况下,容易出现超卖。
设原来的库存是5,用户a扣了库存4,b扣了库存2,流程如下:

  1. 用户a检查库存
  2. 用户b检查库存
  3. 用户a扣库存4
  4. 用户b扣库存2
  5. 最终库存为-1

方案一优化 加锁(事务)

为了解决并发修改的问题,可以通过增加锁(事务),把并发变成串行。
0DDDE9F0-EEEF-434B-B4D0-AE025095233D

这种方案可以解决并发上的问题,但是由于开启事务(锁)的时间较长,会导致严重的性能问题,同时,对于分库后的多个商品,还会存在多库事务的分布式事务问题,成本很高。

方案三 优化锁:减少锁的持有时间

为了优化锁,一个方式是减少锁的持有时间。可以考虑在扣库存时,增加一把乐观锁

1
update sku_stock set stock = stock - x where stock = x0;

方案四 高并发下的扣库存设计

方案三能解决大部分场景下的扣库存问题,但当碰到秒杀、大促等场景时,由于商品库存有限,扣库存失败的概率很高,这里如果重复尝试扣库存,会对系统造成较大的压力,减少吞吐。

因此需要对并发的流量进行限流。可以考虑对每个商品的并发数加队列或锁,以减少同时并发的数目失败的概率。对于特别热门的商品,还可以把一个商品拆成多个子商品,以增大并发数目。

扣库存的重试

当由于某种原因扣库存失败时,可能需要进行重试。由于扣库存不是一个幂等的操作,容易造成空卖。

可以通过给每个扣库存操作增加一个token来解决这个问题。当token已经使用过时,再次请求直接忽略。

多件商品扣库存方案及其优化

相比于单件商品的扣库存,同时扣多件商品的库存,需要考虑:

  1. 同时有多个商品,修改、回滚的数目比较多
  2. 操作商品的模式

最简单的方案是直接把多个商品放在一个事务来实现,但是这样有几个问题:

  1. 事务时间比较长,还容易造成死锁
  2. 商品越多,扣库存失败的可能性越大

对于第一个问题,可以把一个事务拆成多个事务,以加大系统吞吐量。这样做还有一个好处,就是这些商品可以不用在同一个库里了(mysql的事务是基于库实例实现的),从而避开了分布式事务的成本。

对于第二个问题,可以使用二阶段提交,先锁定所有商品的库存,然后统一提交。避免频繁回滚。

从扣库存看高并发

扣库存是一个典型的业务型系统,具有以下特点:

  1. 并发量高
  2. 数据量大
  3. 逻辑较轻

它的难点主要体现在:

  1. 大量高速交易
  2. 数据的一致性
  3. 应对各种业务、系统异常情况

Ouath 2.0 token申请时序图

发表于 2018-11-28 | 分类于 小功能

在看Ouath 2.0时,发现对于申请token的两种方式—授权码模式、简化模式的时序图不太清楚,于是自己画了一次时序图,以助自己理解。同时也放在这里供参考。

学习资料:
理解OAuth 2.0 - 阮一峰的网络日志
OAuth 2.0 官方文档

授权码模式2018-11-28-授权码模式时序图.png

授权码模式时序图

其中,第三方服务对于官方文档里的“client”

简化模式

简化模式时序图

其中,第三方服务对于官方文档里的“client”

记一次失败的mac平台下载工具探索

发表于 2018-11-27 | 分类于 工具

最近小伙伴分享了很多好照片给我,由于内容较多,是通过百度云盘分享的。而我电脑上百度云盘的下载速度平均只有50k/s, 全部文件下载完成需要上百小时,这时想起好像有工具可以加速下载,同时NAS也缺一个下载工具,正好可以把Arial用起来。

说做就做,打开官网开始研究,发现其使用十分简单。考虑到需要在不同的地方使用,最好用docker的方式使用,便于分享。

docker下的arial使用

找到一个star最多的工具 https://hub.docker.com/r/xujinkai/aria2-with-webui/
开始以docker-compose使用,发现一直在报错

1
[ERROR] IPv6 RPC: failed to bind TCP port 6800

找到docker的官网,把ipv6打开 IPv6 with Docker | Docker Documentation

1
2
3
4
{
"ipv6": true,
"fixed-cidr-v6": "2001:db8:1::/64"
}

重启,发现还是不行。一直认为是自己环境的问题,于是找了台ubuntu的机器,运行命令:

1
2
3
4
5
6
docker run \
--name aria2-with-webui \
-p 6800:6800 \
-p 6880:80 \
-p 6888:8080 \
xujinkai/aria2-with-webui

可以正常访问。

怀疑是mac的问题,折腾了多处配置,均无果。开始以最简单的方式在mac下运行:

1
2
docker run \
xujinkai/aria2-with-webui

发现可以运行,最后发现只要用了docker-compose,就不能用,不用就可以。

反思与总结

对于工具,要多一些对其本身的怀疑,我就是一直觉得是镜像的问题,没有考虑过docker-compose,结果一直没有找到问题。

大里来说,要多思考,多分析,不要对觉得一些问题是理所当然,不可能出问题的。

《学习之道》笔记之外的感想

发表于 2018-06-18 | 分类于 工具

方法/规律的作用

今天整理了《学习之道》最后部分的读书笔记,花了三个小时,细细算了下,阅读3小时+笔记7小时=总共10小时,共234页,想起来年初时,看《如何阅读一本书》(297页),类似的内容,光阅读和理解前后就至少花了有30个小时(笔记至今还没整理好,估计是有生之年了…),效率相差近10倍(光阅读)。不能不感叹方法/工具的巨大效果。

更不用学习体验了。年初时,看《如何阅读一本书》的场景还历历在目,多翻几页,脑袋就像被塞满了东西,再也接受不了一个新的概念。等到《学习之道》时,心中满是作者会怎么聊这个话题的好奇,就像看电视剧会猜剧情走向,不时用自己的理解和作者要讲的内容以及讲述技巧作对比,明显有趣多了。

为什么一直没有意识到这些规律?

一直以来,都是勤劳、苦干被宣扬,类似于“劳动模范”、“战斗一百天”这种(背后的原因蛮好玩的,有兴趣的可以私聊讨论),而改进工作方法、效率却并不怎么突出,应该是一个社会原因。

当然,最重要的还是个人意识上原因。学习/阅读方法只是马恩毛列一个微不足道的应用。事实上,我也是在理解并应用了结构化思想之后,才用好这套学习方法的。

马恩毛列是指马克思、恩克思、毛泽东、列宁等一系列大神的思想,主要是唯物主义相关的,具体的我也搞不清楚,毕竟是一个只看过导读的人…

吐槽:
都在说要发扬传统文化,但是传统文化中的坑是真的多,随便举两个:

  1. 书读百遍,其意自现。你和一个医学生,说“书读百遍,其意自现”试试,他会教你做人的。一方面,这些知识太多,没有太多的时间去读百遍,另一方面,有些东西,没有正确的方式,永远也不可能学会。
  2. 头悬梁,锥刺股。从医学上来说,充分、适当的休息,对于学习和考试丟关重要。从我个人经历上来说,休息充分的一个小时,抵得上疲劳战术的10个小时不止。

好玩的规律

对于规律,我的认知是4个步骤:认识规律,学习规律,遵守规律,利用规律。以学习为例,学习成果可以就一个公式简单地表示:

1
2
s=vt
s指总的学习成果, v只学习速度,t指学习时间

平常学习,要得到最多的学习成果,那么有两种选择:

  1. 提高学习效率
  2. 增加学习时间
    学习时间受限于工作,以及精力,基本是个常数(固定的数),增长的空间非常小,所以,要学习,就只能提高学习效率了。

准备考试时,要学习的东西(s)一定,那么,有两个优化的选择:

  1. 提高学习效率
  2. 增加学习时间
    这里就能说明,为什么会有人选择脱产准备考试了。然后脱产后的时间也还是常数,再次说明了学习效率的重要性!!!

如果几天后就要考试了,那时间(t)就是个常数了,这里,有这些选择:

  1. 提高学习效率。然而大部分情况下,肯定是学不完这些知识的
  2. 少学一些内容,只学最重要的那部分,反正60分万岁嘛
  3. 把所有内容分成几部分,应该是有难有易的,分值也是有高有低,那么,把分值和难度(需要花的时间)一除,按单位价值从高到低去学就好了

这就是规律的一个简单应用,是不是很好玩?

为什么那么久都没有用起来结构化思考的方法?

了解一个东西分几个步骤:

  1. 了解:知道有这么个东西,但是细节不清楚。重点是知道
  2. 知识:能记住,能用,但是不会主动用。重点是理解细节
  3. 技能:需要时,能主动使用,但是不会迁移。在特定情景下,能用出来
  4. 本能:已经是意识的一部分,能用的时候,会自动地用出来。对其理解比较透彻,能用的时候可以随意使用

我们学到的大部分东西都停留在技能层面,从技能到本能的机会实在是太少了,很难跨过技能到本能的沟。我自己用的时候,主要是需求导向,即如何解决我碰到的问题,不断去思考尝试。

还有一个是主动的建立思维锚点,即多想想技能的背景、上下文、优缺点、条件与作用等。这样做应该是一个长期的工作,至少1年后再来谈效果吧。

买书

这阵子618,很多同事说,家里还有好几本书没看完,先不买了。我其实不太认同,因为人一直在成长,以前买的书,只是和那时的自己、环境相适应,现在需要新的书和资料来帮助我们,自然需要新的书。

而且书这种东西,很多时候,看起来是消费,但其实是投资的。投入大量的时间和少量的金钱,来谋取未来的回报。投资要的是高回报率以及高的整体回报,如果能通过少量的金钱(和时间相比,买书的钱可以忽略好吧)来优化书的质量,进而提高整体学习效率,最终抬高整体的回报,无疑是一件非常划算的事情。

命令模式推导及应用场景浅析

发表于 2018-05-24 | 分类于 设计模式
命令模式
阅读全文 »

Keyboard Maestro介绍

发表于 2018-05-19 | 分类于 工具
自动化你的工作
阅读全文 »

python 依赖管理简介

发表于 2018-03-20

python 虽然好用,但是项目一多,依赖的管理就是个大问题了,新项目已经全在用 python3,而老项目都是 pthon2,更蛋疼的是,不同项目之间还可能依赖了同一个库的不同版本!我们试着解决这个问题:

pip: 解决项目的全局依赖问题

最开始的时候,我们都是手动安装库,而库可能还有依赖的库,这样最终会形成一张树的依赖结构。解决这个问题,我们可以使用 pip。

它主要解决包的问题,包括安装、更新、删除等。
简介: https://pip.pypa.io/en/stable/quickstart/
安装: https://pip.pypa.io/en/stable/installing/

virtualenv: 解决不同项目间使用同一依赖不同版本的问题

pip 可以帮助我们方便地安装项目需要的依赖,不过随着项目的变多,我们可能会在不同的项目里使用同一个库的不同版本,甚至python 的不同版本,这时,需要一个新的工具来解决这个问题: virtualenv。

它可以帮助我们创建一个虚拟、独立的依赖环境,以保证每个项目可以使用指定的依赖,包括 python、第三方库等。

conda: 解决非python依赖的问题,以及简单统一的操作方式

pip 和 virtualenv的组合,可以解决绝大部分 python的依赖管理问题,但是,随着项目的发展,我们可能需要非 python 的依赖,来解决现有问题。

conda 作为一个打包工具和安装程序,可以帮助我们解决主流的开发环境和依赖的问题,除 python 外,它还支持:R, Ruby, Lua, Scala, Java, JavaScript, C/C++, FORTRAN等语言。一定程度上,可以把它看着 pip 和 virtualenv 的结合(实际上,pip 和 conda 是互补的关系,因为 pip 可以安装一部分 conda 不能安装的依赖)。

官网 (很卡,不推荐,建议使用下面的镜像)
国内镜像

总结

virtualenv和conda都是通过修改 shell 里的环境变量来达到修改环境的目的,基本原理类似。
不同的项目可以用不同的工具,不要求全,适合才是最好的。
Pycharm 对以上工具都有很好的支持,善用工具可以极大提高效率。

以下是参考资料:
Anaconda介绍、安装及使用教程
Anacodna之conda与 virtualenv对比使用教程,创建虚拟环境

java8里lambda里的 this 为什么会指向 lamdba 所在的外部类

发表于 2018-02-13 | 分类于 JAVA

这几天复习了 lambda ,发现有个细节,十分难以理解,那就是 lambda 里的 this指针。

Lambda 里的this指针指向其所属的内部类, 是怎么实现的呢?

写了一个例子,作为测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.function.Supplier;
public class LambdaTest {
public static void main(String[] args) {
new LambdaTest().test();
}
public void test() {
String para = "abc";
String para2 = "abc";
System.out.println(this);
Supplier<String> supplier = () -> {
return para + para2;
};
System.out.println(supplier.get());
}
}

对应的jvm 机器码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static java.lang.String lambda$test$0(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: new #12 // class java/lang/StringBuilder
3: dup
4: invokespecial #13 // Method java/lang/StringBuilder."<init>":()V
7: aload_0
8: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
11: aload_1
12: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
18: areturn
LineNumberTable:
line 16: 0

通过方法签名可以知道,如果一个类没有带 this,被编译成了一个静态内部类方法。

带 this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.function.Supplier;
public class LambdaTest {
public static void main(String[] args) {
new LambdaTest().test();
}
public void test() {
String para = "abc";
String para2 = "abc";
System.out.println(this);
Supplier<String> supplier = () -> {
System.out.println(this);
return para + para2;
};
System.out.println(supplier.get());
}
}

对应的 jvm 机器码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private java.lang.String lambda$test$0(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PRIVATE, ACC_SYNTHETIC
Code:
stack=2, locals=3, args_size=3
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
7: new #12 // class java/lang/StringBuilder
10: dup
11: invokespecial #13 // Method java/lang/StringBuilder."<init>":()V
14: aload_1
15: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: aload_2
19: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: areturn
LineNumberTable:
line 15: 0
line 16: 7

lambda 被编译成了一种内部类!这就能说通了。

结论:

lambda一般情况下会被编译成静态匿名方法,引用的外部变量以参数的方式传递。
如果 lambda 里使用了this 指标,则被编译为匿名内部方法,以让 this 指针指向lambda 外部类。

Nas服务器的外网访问配置

发表于 2017-12-11 | 分类于 工具

之前使用花生壳的DDNS实现了Nas服务的外网访问,可以在公司访问家里的timeMachine.瞬间觉得自己的数据安全了很多。
可惜好景不长。没多久就发现家里的文件没有通过samba访问。于是开始折腾Nas系统,发现官方推荐上https,同时为了统一访问体验,决定上子域名。以下是折腾的记录。

子域名的两种方案

查找资料后发现,要实现子域名的DDNS,有两种方案:

  1. 购买花生壳的付费二级域名,然后在二级域名的基础上使用三级域名。优点是简单、方便、便宜。可以不用备案。
  2. 自己购买顶级的域名,然后通过脚本更新DNS记录。优点是掌控度高。

买花生壳的付费二级域名

比较过两种方案后,我决定还是买花生壳的付费二级域名。反正是给自己用的,输入域名的机会不多,长几个短几个字母问题不大。

首先购买

如何快速找到想要的信息

发表于 2017-10-31 | 分类于 信息化时代的个人行动指南

我们在一个信息爆炸的时代,互联网给我们带来丰富信息的同时,也造成了很大的负担,如何在网上快速准确的找到想要的信息,是一个很重要的技巧。

常见的信息来源有:

  • 搜索引擎等通用搜索
  • stack over flow等专业领域知识
  • 知乎、搜狗(用于查询微信公众号的内容)等封闭社区

下面两类,主要在于平时的积累,这里重点说说搜索引擎:

搜索引擎

搜索引擎处于互联网的中心,连接了各种信息。使用时,可以从一个点出发,然后不断缩小范围,最终找到想要的信息。

以查询“天龙八部”的出处背景为例:
直接输入“天龙八部”会是这样:

如果知道“天龙八部”与佛教相关,可以输入“天龙八部 佛教”:

如果不知道与佛教相关,则可以先了解它的背景:

常见的拓展信息

  • 本身的描述,如apple可能是水果,也可能是科技公司
  • 背景信息,如历史时间段、行业等
  • 相关信息,如一个人的学校、籍贯等

搜索引擎的功能

google支持以下功能的搜索:

  • 查询多个关键词,以空格分开
  • 查询短语,用英文的引号(“”)分开
  • 查询任意一个词,用 OR 分开
  • 排除某个词,用 - 分开
  • 数字范围,用 .. 表示

同时,还可以对搜索的结果做一些限制:

  • 时间范围
  • 特定网站
  • 在页面的特定位置,如title、页眉、正文等,甚至是url、链接
  • 资源的类型,如pdf、ppt、word等

其他技巧:

  • 通配符 “*”
  • 搜索缓存
    在相应网址前加上“cache:”
12…4

Shicheng Yuan

35 日志
12 分类
73 标签
RSS
© 2018 Shicheng Yuan
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4