秒杀项目优化

逻辑

  • 如何解决超卖问题
    (1)在sql加上判断防止数据边为负数
    (2)数据库加唯一索引防止用户重复购买
    (3)redis预减库存减少数据库访问 内存标记减少redis访问 请求先入队列缓冲,异步下单,增强用户体验

  • 为什么threadlocal存储user对象,原理?

并发编程中重要的问题就是数据共享,当你在一个线程中改变任意属性时,所有的线程都会因此受到影响,同时会看到第一个线程修改后的值。
有时我们希望如此,比如:多个线程增大或减小同一个计数器变量。
但是,有时我们希望确保每个线程,只能工作在它自己的线程实例的拷贝上,同时不会影响其他线程的数据。

举例: 举个例子,想象你在开发一个电子商务应用,你需要为每一个控制器处理的顾客请求,生成一个唯一的事务ID,同时将其传到管理器或DAO的业务方法中,
以便记录日志。一种方案是将事务ID作为一个参数,传到所有的业务方法中。但这并不是一个好的方案,它会使代码变得冗余。
你可以使用ThreadLocal类型的变量解决这个问题。首先在控制器或者任意一个预处理器拦截器中生成一个事务ID
然后在ThreadLocal中 设置事务ID,最后,不论这个控制器调用什么方法,都能从threadlocal中获取事务ID
而且这个应用的控制器可以同时处理多个请求,
同时在框架 层面,因为每一个请求都是在一个单独的线程中处理的,所以事务ID对于每一个线程都是唯一的,而且可以从所有线程的执行路径获取
运行结果可以看出每个线程都在维护自己的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 Starting Thread: 0 : Fri Sep 21 23:05:34 CST 2018<br>
Starting Thread: 2 : Fri Sep 21 23:05:34 CST 2018<br>
Starting Thread: 1 : Fri Jan 02 05:36:17 CST 1970<br>
Thread Finished: 1 : Fri Jan 02 05:36:17 CST 1970<br>
Thread Finished: 0 : Fri Sep 21 23:05:34 CST 2018<br>
Thread Finished: 2 : Fri Sep 21 23:05:34 CST 2018<br>
```
局部线程通常使用在这样的情况下,当你有一些对象并不满足线程安全,但是你想避免在使用synchronized关键字。
块时产生的同步访问,那么,让每个线程拥有它自己的对象实例
注意:局部变量是同步或局部线程的一个好的替代,它总是能够保证线程安全。唯一可能限制你这样做的是你的应用设计约束<br>
所以设计threadlocal存储user不会对对象产生影响,每次进来一个请求都会产生自身的线程变量来存储


### Redis

* Key的优化。

```java

关键伪代码
生成rediskey, objects包括ucid、用户输入入参、分页信息等等
public static String builder(String prefix, Object... objects) {
String input = JSONObject.toJSONString(Arrays.asList(objects));
String output = Util.md5_16(input);
return prefix+output;
}
cacheRedis.setex(key,EXPIRE_TIME_2S,info);

设计优点:借鉴spring-data-redis将入参通用为objects…序列化,然后将JsonString Md5压缩为16位,这里主要由于在秒杀开始时,redis数据会出现大量缓存列表数据,redis储存100w个value长度为32位,key长度为16位的数据时,需要使用个130MB内存,如果key的长度为32位时需要160MB左右的内存,所以压缩key的长度在这种场景很有必要。

  • 不要什么都放在redis中
    像列表页缓存,切勿为了减少redis的开销,将数据库每一列放到redis中,在redis中查询汇总,例如:每个秒杀资源都放在redis中,秒杀资源页需要10次redis链接才能完成一次列表页的组装。这样做会将服务器的qps成几何倍数的扩大到与redis的qps中造成系统获取不到redis连接资源

  • redis的库存如何与数据库的库存保持一致
    redis的数量不是库存,他的作用仅仅只是为了阻挡多余的请求透穿到DB,起到一个保护的作用
    因为秒杀的商品有限,比如10个,让1万个请求区访问DB是没有意义的,因为最多也就只能10个
    请求下单成功,所有这个是一个伪命题,我们是不需要保持一致的。

  • 为什么redis数量会减少为负数

    1
    2
    3
    4
    5
    6
    7
    8
       //预见库存
    long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock,""+goodsId) ;
    if(stock <0){
    localOverMap.put(goodsId, true);
    return Result.error(CodeMsg.MIAO_SHA_OVER);
    }
    假如redis的数量为1,这个时候同时过来100个请求,大家一起执行decr数量就会减少成-99这个是正常的
    进行优化后改变了sql写法和内存写法则不会出现上述问题
  • redis 分布式锁实现方法

redis分布式锁解决什么问题
1.一个进程中的多个线程,多个线程并发访问同一个资源的时候,如何解决线程安全问题。
2.一个分布式架构系统中的两个模块同时去访问一个文件对文件进行读写操作
3.多个应用对同一条数据做修改的时候,如何保证数据的安全性
在但一个进程中,我们可以用到synchronized、lock之类的同步操作去解决,但是对于分布式架构下多进程的情况下,
如何做到跨进程的锁。就需要借助一些第三方手段来完成

我用了四种方法 ,分别指出了不同版本的缺陷以及演进的过程 orderclosetask
V1—->>版本没有操作,在分布式系统中会造成同一时间,资源浪费而且很容易出现并发问题
V2—>>版本加了分布式redis锁,在访问核心方法前,加入redis锁可以阻塞其他线程访问,可以
很好的处理并发问题,但是缺陷就是如果机器突然宕机,或者线路波动等,就会造成死锁,一直
不释放等问题
V3版本–>>很好的解决了这个问题v2的问题,就是加入时间对比如果当前时间已经大与释放锁的时间
说明已经可以释放这个锁重新在获取锁,setget方法可以把之前的锁去掉在重新获取,旧值在于之前的
值比较,如果无变化说明这个期间没有人获取或者操作这个redis锁,则可以重新获取
V4—->>采用成熟的框架redisson,封装好的方法则可以直接处理,但是waittime记住要这只为0

Mysql

  • 需注意 因为秒杀,大促,打折等活动进行频繁,所以需要单独建立秒杀_….表来管理否则会经常进行回归

RabbitMQ

  • 订单处理队列rabbitmq
    请求先入队缓冲,异步下单,增强用户体验
    请求出队,生成订单,减少库存
    客户端定时轮询检查是否秒杀成功

  • rabbitmq如何做到消息不重复不丢失即使服务器重启
    -1.exchange持久化
    -2.queue持久化
    -3.发送消息设置MessageDeliveryMode.persisent这个也是默认的行为
    -4.手动确认

事务

RPC事务补偿
当集中式进行服务化RPC演进成分布式的时候,事务则成为了进行分布式的一个痛点,本项目的做法为:
1.进行流程初始化,当分别调用不用服务化接口的时候,成功则进行流程,失败则返回并进行状态更新
将订单状态变为回滚
2.使用定时任务不断的进行处理rollback的订单进行回滚

Maven

maven隔离就是在开发中,把各个环境的隔离开来,一般分为
本地(local)
开发(dev)
测试(test)
线上(prod)
在环境部署中为了防止人工修改的弊端!
spring.profiles.active=@activatedProperties@

架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
项目进行dubbo+ZK改造
├── miaosha-admin 登录模块
│ ├── pom.xml
│ └── miaosha-admin-api
│ └── miaosha-admin-service
│ └── miaosha-admin-web
│ └── miaosha-common



├── miaosha-order 订单秒杀模块
│ ├── pom.xml
│ └── miaosha-order-api
│ └── miaosha-order-service
│ └── miaosha-order-web
│ └── miaosha-order-common



├── miaosha-message 消息模块
│ ├── pom.xml
│ └── miaosha-message-api
│ └── miaosha-message-service
│ └── miaosha-message-web
│ └── miaosha-message-common

更进一步优化的地方

-------------本文结束感谢您的阅读-------------