秒杀系统-高并发的优化

秒杀系统面临着如下问题:
(1)无法使用cdn缓存,因为系统逻辑不可能放在cdn中。
(2)后端缓存困难:库存问题,因为运用到了mysql事务操作(设置联合主键)。
(3)一行数据竞争:热点商品,因为多个用户同时对数据库某条数据进行操作。
秒杀系统的优化方案:
(1)前端控制:暴露接口,按钮防重复提交。
(2)动静态数据分离:cdn缓存,后端Redis缓存。
(3)事务竞争优化:减少事务锁时间,把客户端逻辑放在mysql服务端,避免网络延迟和GC的影响。 GC(Garbage Collection)垃圾回收机制
使用Redis优化地址暴露接口,在dao包下新建一个cache包,然后创建一个RedisDao.java函数,在spring-dao.xml中配置redisdao构造器。
RedisDao.java:
public class RedisDao {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final JedisPool jedisPool;
    public RedisDao(String ip, int port) {
        jedisPool = new JedisPool(ip, port);
    }
private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);
    public Seckill getSeckill(long seckillId) {
        try {
            Jedis jedis = jedisPool.getResource();
            try {
                String key = "seckill:" + seckillId;
                byte[] bytes = jedis.get(key.getBytes());
                if (bytes != null) {
                    Seckill seckill = schema.newMessage();
                    ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);
                    return seckill;
                }
            } finally {
                jedis.close();
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }
    public String putSeckill(Seckill seckill) {
        try {
            Jedis jedis = jedisPool.getResource();
            try {
                String key = "seckill:" + seckill.getSeckillId();
                byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema,
                    LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                int timeout = 60 * 60; 
                String result = jedis.setex(key.getBytes(), timeout, bytes);
                return result;
            } finally {
                jedis.close();
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }
}
针对网络延迟和GC导致的并发问题,使用存储过程,将整个事务放在mysql端完成,秒杀存储过程实现如下。
DELIMITER $$ 
CREATE PROCEDURE `seckill`.`execute_seckill`
  (in v_seckill_id bigint,in v_phone bigint,
    in v_kill_time timestamp,out r_result int)
  BEGIN
    DECLARE insert_count int DEFAULT 0;
    START TRANSACTION;
    insert ignore into success_killed
      (seckill_id,user_phone,create_time)
      values (v_seckill_id,v_phone,v_kill_time);
    select row_count() into insert_count;
    IF (insert_count = 0) THEN
      ROLLBACK;
      set r_result = -1;
    ELSEIF(insert_count < 0) THEN
      ROLLBACK;
      SET R_RESULT = -2;
    ELSE
      update seckill
      set number = number-1
      where seckill_id = v_seckill_id
        and end_time > v_kill_time
        and start_time < v_kill_time
        and number > 0;
      select row_count() into insert_count;
      IF (insert_count = 0) THEN
        ROLLBACK;
        set r_result = 0;
      ELSEIF (insert_count < 0) THEN
        ROLLBACK;
        set r_result = -2;
      ELSE
        COMMIT;
        set r_result = 1;
      END IF;
    END IF;
  END;
$$
DELIMITER ;
最后,在ServiceService.java中建立存储执行秒杀操作的函数,在SeckillDao.java实现该接口,在SeckillDao.xml中使用mybatis调用存储过程,在服务端的SeckillServiceImpl.java完成配置即可。

猜你喜欢

转载自www.cnblogs.com/smuxiaolei/p/8979786.html