失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > mysql查询显示注释 如何解决秒杀编程高并发问题 – 数据库 – 前端 mysql时间的比较大小写

mysql查询显示注释 如何解决秒杀编程高并发问题 – 数据库 – 前端 mysql时间的比较大小写

时间:2019-04-09 01:22:26

相关推荐

mysql查询显示注释 如何解决秒杀编程高并发问题 – 数据库 – 前端 mysql时间的比较大小写

就是指在同一个时间点,有大量用户同时访问URL地址,比如淘宝双11都会产生高并发。

高并发带来的后果

服务端导致站点服务器、DB服务器资源被占满崩溃。数据的存储和更新结果和理想的设计不一致。用户角度尼玛,网站这么卡,刷新了还这样,垃圾网站,不玩了

二:分析阻碍服务速度的原因1:事物行级锁的等待

java的事务管理机制会限制在一次commit之前,下一个用户线程是无法获得锁的,只能等待

2:网络延迟

3:JAVA的自动回收机制(GC)

三:处理高并发的常见方法

1:首先可以将静态资源放入CDN中,减少后端服务器的访问

2:访问数据使用Redis进行缓存

3:使用Negix实现负载均衡

4:数据库集群与库表散列

四:实战优化秒杀系统

1:分析原因

当用户在想秒杀时,秒杀时间未到,用户可能会一直刷新页面,获取系统时间和资源(A:此时会一直访问服务器),当时间到了,大量用户同时获取秒杀接口API(B),获取API之后执行秒杀(C),指令传输到各地服务器,服务器执行再将传递到中央数据库执行(D),服务器启用事务执行减库存操作,在服务器端JAVA执行过程中,可能因为JAVA的自动回收机制,还需要一部分时间回收内存(E)。

2:优化思路:

面对上面分析可能会影响的过程,大家可以进行如下优化

A:大家可以将一些静态的资源放到CDN上,这样可以减少对系统服务器的请求

B:对于暴露秒杀接口,这种动态的无法放到CDN上,大家可以采用Redis进行缓存

request——>Redis——>MySQL

C:数据库操作,对于MYSQL的执行速度大约可以达到1秒钟40000次,影响速度的还是因为行级锁,大家应尽可能减少行级锁持有时间。

DE:对于数据库来说操作可以说是相当快了,大家可以将指令放到MYSQL数据库上去执行,减少网络延迟以及服务器GC的时间。

3:具体实现

3.1:使用Redis进行缓存

引入redis访问客户端Jedis

1 <!– redis客户端:Jedis –>2 <dependency>3 <groupId>redis.clients</groupId>4 <artifactId>jedis</artifactId>5 <version>2.7.3</version>6</dependency>

优化暴露秒杀接口:对于SecviceImpl 中 exportSeckillUrl 方法的优化,伪代码如下

get from cache//首先大家要从Redis中获取需要暴露的URL

if null //如果从Redis中获取的为空

get db //那么大家就访问MYSQL数据库进行获取

put cache //获取到后放入Redis中

else locgoin //否则,则直接执行

大家一般不能直接访问Redis数据库,首先先建立数据访问层RedisDao,RedisDao中需要提供两个方法,一个是 getSeckill 和 putSeckill

在编写这两个方法时还需要注意一个问题,那就是序列化的问题,Redis并没有提供序列化和反序列化,大家需要自定义序列化,大家使用 protostuff 进行序列化与反序列化操作

引入 protostuff 依赖包

1 <!– protostuff序列化依赖 –> 2 <dependency> 3 <groupId>com.dyuproject.protostuff</groupId> 4 <artifactId>protostuff-core</artifactId> 5 <version>1.0.8</version> 6 </dependency> 7 <dependency> 8 <groupId>com.dyuproject.protostuff</groupId> 9 <artifactId>protostuff-runtime</artifactId>10 <version>1.0.8</version>11 </dependency>

编写数据访问层RedisDao

1 package com.xqc.seckill.dao.cache;2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import com.dyuproject.protostuff.LinkedBuffer; 7 import com.dyuproject.protostuff.ProtostuffIOUtil; 8 import com.dyuproject.protostuff.runtime.RuntimeSchema; 9 import com.xqc.seckill.entity.Seckill;10 11 import redis.clients.jedis.Jedis;12 import redis.clients.jedis.JedisPool;13 14 /**15 * Redis缓存优化16 * 17 * @author A Cang(xqc)18 *19 */20 public class RedisDao {21private final Logger logger = LoggerFactory.getLogger(this.getClass());22 23private final JedisPool jedisPool;24 25public RedisDao(String ip, int port) {26 jedisPool = new JedisPool(ip, port);27}28 29private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);30 31public Seckill getSeckill(long seckillId) {32 //redis操作逻辑33 try {34 Jedis jedis = jedisPool.getResource();35 try {36 String key = “seckill:” + seckillId;37 //并没有实现内部序列化操作38 // get-> byte[] -> 反序列化 ->Object(Seckill)39 // 采用自定义序列化40 //protostuff : pojo.41 byte[] bytes = jedis.get(key.getBytes());42 //缓存中获取到bytes43 if (bytes != null) {44 //空对象45 Seckill seckill = schema.newMessage();46 ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);47 //seckill 被反序列化48 return seckill;49 }50 } finally {51 jedis.close();52 }53 } catch (Exception e) {54 logger.error(e.getMessage(), e);55 }56 return null;57}58 59public String putSeckill(Seckill seckill) {60 // set Object(Seckill) -> 序列化 -> byte[]61 try {62 Jedis jedis = jedisPool.getResource();63 try {64 String key = “seckill:” + seckill.getSeckillId();65 byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema,66LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));67 //超时缓存68 int timeout = 60 * 60;//1小时69 String result = jedis.setex(key.getBytes(), timeout, bytes);70 return result;71 } finally {72 jedis.close();73 }74 } catch (Exception e) {75 logger.error(e.getMessage(), e);76 }77 78 return null;79}80 81 82 }

优化ServiceImpl的 exportSeckillUrl 的方法

1public Exposer exportSeckillUrl(long seckillId) { 2 // 优化点:缓存优化:超时的基础上维护一致性 3 //1:访问redis 4 Seckill seckill = redisDao.getSeckill(seckillId); 5 if (seckill == null) { 6 //2:访问数据库 7 seckill = seckillDao.queryById(seckillId); 8 if (seckill == null) { 9 return new Exposer(false, seckillId);10 } else {11 //3:放入redis12 redisDao.putSeckill(seckill);13 }14 }15 16 Date startTime = seckill.getStartTime();17 Date endTime = seckill.getEndTime();18 //系统当前时间19 Date nowTime = new Date();20 if (nowTime.getTime() < startTime.getTime()21 || nowTime.getTime() > endTime.getTime()) {22 return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(),23 endTime.getTime());24 }25 //转化特定字符串的过程,不可逆26 String md5 = getMD5(seckillId);27 return new Exposer(true, md5, seckillId);28}29 30private String getMD5(long seckillId) {31 String base = seckillId + “/” + salt;32 String md5 = DigestUtils.md5DigestAsHex(base.getBytes());33 return md5;34}

3.2 并发优化:

在执行秒杀操作死,正常的执行应该如下:先减库存,并且得到行级锁,再执行插入购买明细,然后再提交释放行级锁,这个时候行级锁锁住了其他一些操作,大家可以进行如下优化,这时只需要延迟一倍。

修改executeSeckill方法如下:

1@Transactional 2/** 3* 使用注解控制事务方法的优点: 4* 1:开发团队达成一致约定,明确标注事务方法的编程风格。 5* 2:保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部. 6* 3:不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制. 7*/ 8public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) 9 throws SeckillException, RepeatKillException, SeckillCloseException {10 if (md5 == null || !md5.equals(getMD5(seckillId))) {11 throw new SeckillException(“seckill data rewrite”);12 }13 //执行秒杀逻辑:减库存 + 记录购买行为14 Date nowTime = new Date();15 16 try {17 //记录购买行为18 int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);19 //唯一:seckillId,userPhone20 if (insertCount <= 0) {21 //重复秒杀22 throw new RepeatKillException(“seckill repeated”);23 } else {24 //减库存,热点商品竞争25 int updateCount = seckillDao.reduceNumber(seckillId, nowTime);26 if (updateCount <= 0) {27 //没有更新到记录,秒杀结束,rollback28 throw new SeckillCloseException(“seckill is closed”);29 } else {30 //秒杀成功 commit31 SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);32 return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);33 }34 }35 } catch (SeckillCloseException e1) {36 throw e1;37 } catch (RepeatKillException e2) {38 throw e2;39 } catch (Exception e) {40 logger.error(e.getMessage(), e);41 //所有编译期异常 转化为运行期异常42 throw new SeckillException(“seckill inner error:” + e.getMessage());43 }44}

3.3深度优化:(存储过程)

定义一个新的接口,使用存储过程执行秒杀操作

1/**2* 执行秒杀操作by 存储过程3* @param seckillId4* @param userPhone5* @param md56*/7SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5);

实现executeSeckillProcedure方法

1public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) { 2 if (md5 == null || !md5.equals(getMD5(seckillId))) { 3 return new SeckillExecution(seckillId, SeckillStatEnum.DATA_REWRITE); 4 } 5 Date killTime = new Date(); 6 Map<String, Object> map = new HashMap<String, Object>(); 7 map.put(“seckillId”, seckillId); 8 map.put(“phone”, userPhone); 9 map.put(“killTime”, killTime);10 map.put(“result”, null);11 //执行存储过程,result被复制12 try {13 seckillDao.killByProcedure(map);14 //获取result15 int result = MapUtils.getInteger(map, “result”, -2);16 if (result == 1) {17 SuccessKilled sk = successKilledDao.18queryByIdWithSeckill(seckillId, userPhone);19 return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, sk);20 } else {21 return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));22 }23 } catch (Exception e) {24 logger.error(e.getMessage(), e);25 return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);26 27 }28 29}

编写SeckillDao实现有存储过程执行秒杀的逻辑

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

在Mybatis中使用

1<!– mybatis调用存储过程 –>2<select id=”killByProcedure” statementType=”CALLABLE”>3 call execute_seckill(4 #{seckillId,jdbcType=BIGINT,mode=IN},5 #{phone,jdbcType=BIGINT,mode=IN},6 #{killTime,jdbcType=TIMESTAMP,mode=IN},7 #{result,jdbcType=INTEGER,mode=OUT}8 )9</select>

在Controller层使用

1@ResponseBody 2public SeckillResult<SeckillExecution> execute(@PathVariable(“seckillId”) Long seckillId, 3 @PathVariable(“md5”) String md5, 4 @CookieValue(value = “killPhone”, required = false) Long phone) { 5 //springmvc valid 6 if (phone == null) { 7 return new SeckillResult<SeckillExecution>(false, “未注册”); 8 } 9 SeckillResult<SeckillExecution> result;10 try {11 //存储过程调用.12 SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, phone, md5);13 return new SeckillResult<SeckillExecution>(true,execution);14 } catch (RepeatKillException e) {15 SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);16 return new SeckillResult<SeckillExecution>(true,execution);17 } catch (SeckillCloseException e) {18 SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.END);19 return new SeckillResult<SeckillExecution>(true,execution);20 } catch (Exception e) {21 logger.error(e.getMessage(), e);22 SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);23 return new SeckillResult<SeckillExecution>(true,execution);24 }25}

至此,此系统的代码优化工作基本完成。但是在部署时可以将其更加优化,大家一般会使用如下架构

如果觉得《mysql查询显示注释 如何解决秒杀编程高并发问题 – 数据库 – 前端 mysql时间的比较大小写》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。