Java高并发秒杀API之高并发优化(四)

来源:互联网 发布:c语言程序 编辑:程序博客网 时间:2024/05/22 07:40

四 高并发优化

1.分析

这里写图片描述
1.详情页 部署到cdn上,这样用户访问的是cdn不是服务器了。
用户在上网时通过运营商访问最近的城域网,城域网访问主干网。
2.获取系统时间 不用优化
访问一次内存大概 10ns
无法使用cdn,适合服务器端缓存redis等(单台一秒10万qps,还可以做集群)
一致性维护非常低
3.秒杀地址优化
请求地址->访问redis–(超时穿透/主动刷新)->访问mysql
4.秒杀操作的优化分析
无法使用cdn
后端缓存困难,库存问题
一行数据竞争,热点商品
其他方案分析
这里写图片描述
运维成本高(nosql不稳定等),开发成本高(开发需要知道事务回滚等)
幂等性难保证,重复秒杀

mysql update同一条数据 ,4万qps

A :先update 后insert
B 先update 等待事务 ,A释放锁后update,insert

gc(新生代gc(暂停所有java代码,几十毫秒 )和老一代gc)
执行update –网络延迟/gc –>insert–网络延迟/gc –>commit/rollback
优化方向减少锁持有时间
同城机房(0.5~2ms)max(1000qps)
异地机房更长

如何判断update成功
1.自身没有报错2.客户端确认更新成功
优化思路
把客户端的业务逻辑放到mysql服务端

两种解决方案
1.定制sql方案update /*+[auto_commit]*/需要修改mysql源码
自动进行update为1 -> commit ,为0 -> rollback
2.存储过程

2.redis 后端优化

官网下载redis

https://redis.io/download 

安装完成后
redis-server服务器启动
redis-cli 客户端启动
引入jedis依赖

   <dependency>      <groupId>redis.clients</groupId>      <artifactId>jedis</artifactId>      <version>2.7.3</version>    </dependency>

redis指令 get key ,set key value

redisDao

package org.seckill.dao.cache;import org.seckill.entity.Seckill;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.dyuproject.protostuff.LinkedBuffer;import com.dyuproject.protostuff.ProtostuffIOUtil;import com.dyuproject.protostuff.runtime.RuntimeSchema;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;public class RedisDao {    private final JedisPool jedisPool;    private  final Logger logger = LoggerFactory.getLogger(RedisDao.class);    private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);    public RedisDao(String ip,int port){        jedisPool = new JedisPool(ip,port);    }    public Seckill getSeckill(long seckillId){        try {            Jedis jedis = jedisPool.getResource();            try {                String key = "seckill:"+seckillId;                //没有实现 内部序列化                //get -> byte[] -> 反序列化 ->Object(seckill)                //采用自定义序列化                // protostuff:pojo(有get,set方法)                byte [] bytes = jedis.get(key.getBytes());                if(bytes != null){                    //创建一个空对象                    Seckill seckill = schema.newMessage();                    ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);                    //被反序列                    return seckill;//比java 原生的压缩了1/10~1/5  压缩速度 差2个数量级                }            }finally{                jedis.close();            }        } catch (Exception e) {            logger.error(e.getMessage());        }        return null;    }    public String  putSeckill(Seckill seckill){        //set Object{seckill} -- 序列化  --byte[]        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());        }        return null;    }}

key存放seckill:id value存放序列化对象
所以需要protostuff(性能更好)serializable(性能较低)
添加依赖

    <dependency>      <groupId>com.dyuproject.protostuff</groupId>      <artifactId>protostuff-core</artifactId>      <version>1.0.8</version>    </dependency>    <dependency>      <groupId>com.dyuproject.protostuff</groupId>      <artifactId>protostuff-runtime</artifactId>      <version>1.0.8</version>    </dependency>

添加配置

 <!-- RedisDao -->    <!-- 构造方法注入 -->    <bean id ="redisDao" class = "org.seckill.dao.cache.RedisDao">         <constructor-arg index="0" value="localhost"> </constructor-arg>        <constructor-arg index="1" value="6379"> </constructor-arg>    </bean>

单元测试
RedisDaoTest

package org.seckill.dao;import org.junit.Test;import org.junit.runner.RunWith;import org.seckill.dao.cache.RedisDao;import org.seckill.entity.Seckill;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath:spring/spring-dao.xml"})public class RedisDaoTest {    @Autowired    private RedisDao redisDao;    private long id = 1002;    @Autowired    private SeckillDao seckillDao;    @Test    public void testSeckill() throws Exception{        //get and put         Seckill seckill  = redisDao.getSeckill(id);        if(seckill == null){            seckill = seckillDao.queryById(id);            if(seckill != null){                String result = redisDao.putSeckill(seckill);                System.out.println(result);                seckill  = redisDao.getSeckill(id);                System.out.println(seckill);            }        }    }    @Test    public void testGetSeckill() throws Exception{        Seckill seckill  = redisDao.getSeckill(id);        System.out.println(seckill);    }    @Test    public void testputSeckill() throws Exception{    }}

3.并发优化

原来的逻辑 update->insert->commit/rollback
修改为insert->update->commit/rollback
减少锁持有的时间
存储过程

--- 秒杀执行的存储过程--;DELIMITER $$  --console ; 转换为 $$CREATE PROCEDURE `SECKILL`.`execute_seckill`--参数 in 输入参数 out 输出参数-- row_count():返回上一条修改类型sql(d,i,u)的影响行数--row-count() = 0 未修改数据  >0 表示修改的行数 <0 sql错误/未执行(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,state,create_time)    values(v_seckill_id,v_phone,0,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 ;set @r_result=-3;call execute_seckill(1001,13934131331,now(),@r_result)select @r_result

SeckillDao添加方法

 /**  * 使用存储过程执行秒杀  * @param parammap  */  void  killByProcedure(Map<String,Object> paramMap);

SeckillDao.xml添加

    <select id="killByProcedure" statementType="CALLABLE">        call execute_seckill(        #{seckillId,jdbcType=BIGINT,mode=IN},        #{phone,jdbcType=BIGINT,mode=IN},        #{killTime,jdbcType=TIMESTAMP,mode=IN},        #{result,jdbcType=INTEGER,mode=OUT}        )    </select>

service添加

    //执行秒杀操作by存储过程        SeckillExecution excuteSeckillProcedure(long seckillId,long userPhone,String md5) throws RepeatKillException,seckillCloseException,SeckillException; 

实现类

public SeckillExecution excuteSeckillProcedure(long seckillId, long userPhone, String md5)            throws RepeatKillException, seckillCloseException, SeckillException {        if(md5==null||!md5.equals(getMD5(seckillId))){            return  new SeckillExecution(seckillId,SeckillStatEnum.DATA_REWRITE);        }        Date killTime = new Date();        Map <String, Object>map =new HashMap<String, Object>();        map.put("seckillId", seckillId);        map.put("phone", userPhone);        map.put("killTime", killTime);        map.put("result", null);    try {        seckilldao.killByProcedure(map);        int result = MapUtils.getInteger(map, "result",-2);        if(result==1){            SuccessKilled sk = successkilleddao.queryByIdWithSeckill(seckillId, userPhone);            return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,sk);        }else{            return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));        }    } catch (Exception e) {        logger.error(e.getMessage());        return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);    }    }

系统用到哪些服务
cdn
webserver : nginx(集群化,http服务器,给后端服务器做反向代理)+jetty
redis服务器端缓存
mysql mysql事务,保证数据的一致性和完整性

原创粉丝点击