当前位置: 首页 > news >正文

银川网站建设哪家不错广州seo关键词优化外包

银川网站建设哪家不错,广州seo关键词优化外包,b2c网站建设开发,网站建设新闻秒杀高并发解决方案 1.秒杀/高并发方案-介绍 秒杀/高并发 其实主要解决两个问题,一个是并发读,一个是并发写并发读的核心优化理念是尽量减少用户到 DB 来"读"数据,或者让他们读更少的数据, 并 发写的处理原则也一样针对秒杀系统需…

秒杀高并发解决方案

1.秒杀/高并发方案-介绍

  1. 秒杀/高并发 其实主要解决两个问题,一个是并发读,一个是并发写
  2. 并发读的核心优化理念是尽量减少用户到 DB 来"读"数据,或者让他们读更少的数据, 并
    发写的处理原则也一样
  3. 针对秒杀系统需要做一些保护,针对意料之外的情况设计兜底方案,以防止最坏的情况
    发生。
  4. 系统架构要满足高可用: 流量符合预期时要稳定,要保证秒杀活动顺利完成,即秒杀商品
    顺利地卖出去,这个是最基本的前提
  5. 系统保证数据的一致性: 就是秒杀 10 个 商品 ,那就只能成交 10 个商品,多一个少一
    个都不行。一旦库存不对,就要承担损失
  6. 系统要满足高性能: 也就是系统的性能要足够高,需要支撑大流量, 不光是服务端要做极
    致的性能优化,而且在整个请求链路上都要做协同的优化,每个地方快一点, 整个系统就"快
    "了
  7. 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键, 对应的方案比如页
    面缓存方案、Redis 预减库存/内存标记与隔离、请求的削峰(RabbitMQ/异步请求)、分布式
    Session 共享等

image-20230226140153636

image-20230226140537788

2.秒杀场景模拟

image-20230226180110014

image-20230226180155154

image-20230226180138413

这里模拟每秒1000个请求,循环10次即总共发起10000次请求,请求/seckill/doSeckill接口

3.秒杀解决方案

3.1 v1.0-原始版本

SeckillController

@Controller
@RequestMapping(value = "/seckill")
public class SeckillController {@Resourceprivate GoodsService goodsService;@Resourceprivate SeckillOrderService seckillOrderService;@Resourceprivate OrderService orderService;@RequestMapping(value = "/doSeckill")public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V1.0--------");//===================秒杀 v1.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购LambdaQueryWrapper<SeckillOrder> seckillOrderLambdaQueryWrapper = new QueryWrapper<SeckillOrder>().lambda().eq(SeckillOrder::getGoodsId, goodsId).eq(SeckillOrder::getUserId, user.getId());SeckillOrder seckillOrder = seckillOrderService.getOne(seckillOrderLambdaQueryWrapper);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购Order order = orderService.seckill(user, goodsVo);if (order == null) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}model.addAttribute("order", order);model.addAttribute("goods", goodsVo);return "orderDetail";//===================秒杀 v1.0 end... =========================}}

OrderServiceImpl

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {@Resourceprivate SecKillGoodsService secKillGoodsService;@Resourceprivate OrderMapper orderMapper;@Resourceprivate SeckillOrderMapper seckillOrderMapper;/*** 秒杀商品,减少库存* @param user* @param goodsVo* @return*/@Overridepublic Order seckill(User user, GoodsVo goodsVo) {//1.根据商品id获取秒杀商品信息LambdaQueryWrapper<SecKillGoods> lambdaQueryWrapper = new QueryWrapper<SecKillGoods>().lambda().eq(SecKillGoods::getGoodsId,goodsVo.getId());SecKillGoods seckillGoods = secKillGoodsService.getOne(lambdaQueryWrapper);//2.秒杀商品库存-1seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);secKillGoodsService.updateById(seckillGoods);//3.生成普通订单Order order = new Order();order.setUserId(user.getId());order.setGoodsId(goodsVo.getId());order.setDeliveryAddrId(0L);order.setGoodsName(goodsVo.getGoodsName());order.setGoodsCount(1);order.setGoodsPrice(seckillGoods.getSeckillPrice());order.setOrderChannel(1);order.setStatus(0);order.setCreateDate(new Date());orderMapper.insert(order);//4.生成秒杀订单SeckillOrder seckillOrder = new SeckillOrder();seckillOrder.setGoodsId(goodsVo.getId());seckillOrder.setUserId(user.getId());seckillOrder.setOrderId(order.getId());seckillOrderMapper.insert(seckillOrder);return order;}
}

说明

非高并发情况下,程序执行流程分析

  1. 程序首先校验用户是否登录

  2. 然后判断商品库存是充足

  3. 在判断是否存在重复抢购

  4. 最后执行抢购生成订单及抢购订单数据

高并发情况下,程序执行流程分析

  1. 判断商品库存是充足、否存在重复抢购的程序是不具备原子性
  2. 秒杀商品的库存为负数,但具体的值无法确定,因为获取秒杀商品库存也不具备原子性
  3. 当有多少个请求冲破了前面库存和抢购的过滤,就会去生成多少个订单

3.2 v2.0-秒杀-复购、超卖处理

SeckillController

@RequestMapping(value = "/doSeckill")
public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V2.0--------");//===================秒杀 v2.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购Order order = orderService.seckill(user, goodsVo);if (order == null) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}model.addAttribute("order", order);model.addAttribute("goods", goodsVo);return "orderDetail";//===================秒杀 v2.0 end... =========================
}

OrderServiceImpl

/*** 秒杀商品,减少库存** @param user* @param goodsVo* @return*/
@Override
@Transactional(rollbackFor = {Exception.class})
public Order seckill(User user, GoodsVo goodsVo) {//1.根据商品id获取秒杀商品信息LambdaQueryWrapper<SecKillGoods> lambdaQueryWrapper = new QueryWrapper<SecKillGoods>().lambda().eq(SecKillGoods::getGoodsId, goodsVo.getId());SecKillGoods seckillGoods = secKillGoodsService.getOne(lambdaQueryWrapper);//2.秒杀商品库存-1// seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);// secKillGoodsService.updateById(seckillGoods);//1. Mysql在默认的事务隔离级别[REPEATABLE-READ]下//2. 执行update语句时,会在事务中锁定要更新的行//3. 这样可以防止其它会话在同一行执行update,deleteLambdaUpdateWrapper<SecKillGoods> updateWrapper = new UpdateWrapper<SecKillGoods>().lambda().setSql("stock_count=stock_count-1").eq(SecKillGoods::getGoodsId, goodsVo.getId()).gt(SecKillGoods::getStockCount, 0);boolean update = secKillGoodsService.update(updateWrapper);//如果更新失败说明已经没有库存了if (!update) {return null;}//3.生成普通订单Order order = new Order();order.setUserId(user.getId());order.setGoodsId(goodsVo.getId());order.setDeliveryAddrId(0L);order.setGoodsName(goodsVo.getGoodsName());order.setGoodsCount(1);order.setGoodsPrice(seckillGoods.getSeckillPrice());order.setOrderChannel(1);order.setStatus(0);order.setCreateDate(new Date());orderMapper.insert(order);//4.生成秒杀订单SeckillOrder seckillOrder = new SeckillOrder();seckillOrder.setGoodsId(goodsVo.getId());seckillOrder.setUserId(user.getId());seckillOrder.setOrderId(order.getId());seckillOrderMapper.insert(seckillOrder);//设置秒杀订单的key --> order:用户id:商品idredisTemplate.opsForValue().set("order:" + user.getId() + ":" + goodsVo.getId(), seckillOrder);return order;
}

说明

  1. 超卖问题处理 前面出现超卖的主要原因在于,更新库存时不具备原子性,这里利用mysql默认的事务隔离级别

[REPEATABLE-READ]在事务中执行update语句获取锁定更新的行,这个机制进行处理

  1. 复购(生成了多个订单)问题处理,则在生成订单之前对库存更新进行判断,如果更新失败则不在生成订单
    //如果更新失败说明已经没有库存了if (!update) {return null;}
  1. 优化复购判断 在生成秒杀订单之后将订单存储到redis中,在判断复购时直接从redis中获取,避免频繁查询数据库,提升执行效率
    //解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}

测试结果

image-20230227224123469

image-20230227224157695

3.3 v3.0 优化秒杀,Redis 预减库存

  1. 前面我们防止超卖 是通过到数据库查询和到数据库抢购,来完成的, 代码如下:

image-20230306215302028

image-20230306215328274

  1. 如果在短时间内,大量抢购冲击 DB, 造成洪峰, 容易压垮数据库
  2. 解决方案: 使用 Redis 完成预减库存,如果没有库存了, 直接返回, 减小对 DB 的压力
  3. 示意图:

image-20230306215452562

代码实现

@Controller
@RequestMapping(value = "/seckill")
public class SeckillController implements InitializingBean {@Resourceprivate GoodsService goodsService;@Resourceprivate SeckillOrderService seckillOrderService;@Resourceprivate OrderService orderService;@Resourceprivate RedisTemplate redisTemplate;@RequestMapping(value = "/doSeckill")public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V3.0--------");//===================秒杀 v3.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//库存预减,如果在redis中预减库存,发现秒杀商品中已经没有库存了,直接返回//从而减少这个方法请求 orderService.seckill防止大量请求打到数据,优化秒杀//这个方法具有原子性!!!Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);//说明这个商品已经没有库存了if (decrement < 0) {//恢复redis库存为0,对业务没有影响只是为了好看redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购Order order = orderService.seckill(user, goodsVo);if (order == null) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}model.addAttribute("order", order);model.addAttribute("goods", goodsVo);return "orderDetail";//===================秒杀 v3.0 end... =========================}//该方法实在类的所有属性都初始化后,自动执行//这里将所有秒杀商品的库存量加载到redis中@Overridepublic void afterPropertiesSet() throws Exception {//1.查询所有的秒杀商品List<GoodsVo> list = goodsService.findGoodsVo();if (CollectionUtils.isEmpty(list)) {return;}//遍历list,将秒杀商品的库存量放入到redis中//秒杀商品库存量对应的key seckillGoods:商品idfor (GoodsVo goodsVo : list) {redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());}}}

大体思路:

  1. 项目启动时加载秒杀商品库存到redis中,库存量随着每次启动都会更新

  2. 在进行抢购之前先到redis中进行库存预减,利用redis的decrement具备原子性的特点

  3. 如果redis预减库存小于0则直接返回,避免了多余的请求打到数据库

3.4 v4.0 优化秒杀:加入内存标记,避免总到 Reids 查询库存

  • 需求分析/图解
  1. 如果某个商品库存已经为空了, 我们仍然是到 Redis 去查询的, 还可以进行优化
  2. 解决方案: 给商品进行内存标记, 如果库存为空, 直接返回, 避免总是到 Redis 查询库存
  3. 分析思路-示意图

image-20230306220456230

@Controller
@RequestMapping(value = "/seckill")
public class SeckillController implements InitializingBean {@Resourceprivate GoodsService goodsService;@Resourceprivate SeckillOrderService seckillOrderService;@Resourceprivate OrderService orderService;@Resourceprivate RedisTemplate redisTemplate;@RequestMapping(value = "/doSeckill")public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V4.0--------");//===================秒杀 v4.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//如果库存为空,避免总是到 reids 去查询库存,给 redis 增加负担(内存标记)if (entryStockMap.get(goodsId)) {//如果当前这个秒杀商品已经是空库存,则直接返回.model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//库存预减,如果在redis中预减库存,发现秒杀商品中已经没有库存了,直接返回//从而减少这个方法请求 orderService.seckill防止大量请求打到数据,优化秒杀//这个方法具有原子性!!!Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);//说明这个商品已经没有库存了if (decrement < 0) {//这里使用内存标记,避免多次操作 redis, true 表示空库存了.entryStockMap.put(goodsId, true);//恢复redis库存为0,对业务没有影响只是为了好看redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购Order order = orderService.seckill(user, goodsVo);if (order == null) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}model.addAttribute("order", order);model.addAttribute("goods", goodsVo);return "orderDetail";//===================秒杀 v4.0 end... =========================}//该方法实在类的所有属性都初始化后,自动执行//这里将所有秒杀商品的库存量加载到redis中@Overridepublic void afterPropertiesSet() throws Exception {//1.查询所有的秒杀商品List<GoodsVo> list = goodsService.findGoodsVo();if (CollectionUtils.isEmpty(list)) {return;}//遍历list,将秒杀商品的库存量放入到redis中//秒杀商品库存量对应的key seckillGoods:商品idfor (GoodsVo goodsVo : list) {redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());//当有库存为 false,当无库存为 true。防止库存没有了,还会到 Redis 进行判断操作entryStockMap.put(goodsVo.getId(), false);}}}

3.4 v5.0 优化秒杀: 加入消息队列,实现秒杀的异步请求

需求分析/图解

  • 问题分析

前面秒杀, 没有实现异步机制, 是完成下订单后, 再返回的, 当有大并发请求下订单操作时, 数据库来不及响应, 容易造成线程堆积

  • 解决方案
  1. 加入消息队列,实现秒杀的异步请求

  2. 接收到客户端秒杀请求后,服务器立即返回 正在秒杀中…, 有利于流量削峰

  3. 客户端进行轮询秒杀结果, 接收到秒杀结果后,在客户端页面显示即可

  4. 秒杀消息发送设计 SeckillMessage - String

image-20230307223112391

image-20230307223159835

代码实现

秒杀消息类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SeckillMessage {private User user;private Long goodsId;
}

定义消息队列、交换机

@Configuration
public class RabbitMQSecKillConfig {private static final String QUEUE = "seckillQueue";private static final String EXCHANGE = "seckillExchange";@Beanpublic Queue queue_seckill() {return new Queue(QUEUE);}@Beanpublic TopicExchange topicExchange_seckill() {return new TopicExchange(EXCHANGE);}@Beanpublic Binding binding_seckill() {return BindingBuilder.bind(queue_seckill()).to(topicExchange_seckill()).with("seckill.#");}}

发送秒杀消息

@Service
@Slf4j
public class MQSenderMessage {@Resourceprivate RabbitTemplate rabbitTemplate;//发送秒杀信息public void senderMessage(String message) {System.out.println("发送消息了=" + message);log.info("发送消息:" + message);rabbitTemplate.convertAndSend("seckillExchange", "seckill.message", message);}
}

消息接收者

@Service
@Slf4j
public class MQReceiverConsumer {@Resourceprivate GoodsService goodsService;@Resourceprivate OrderService orderService;//下单操作@RabbitListener(queues = "seckillQueue")public void queue(String message) {SeckillMessage seckillMessage = JSONUtil.toBean(message, SeckillMessage.class);Long goodId = seckillMessage.getGoodsId();User user = seckillMessage.getUser();//获取抢购的商品信息GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodId);//下单操作orderService.seckill(user, goodsVo);}}

SeckillController

@Controller
@RequestMapping(value = "/seckill")
public class SeckillController implements InitializingBean {@Resourceprivate GoodsService goodsService;@Resourceprivate RedisTemplate redisTemplate;@Resourceprivate MQSenderMessage mqSenderMessage;//如果某个商品库存已经为空, 则标记到 entryStockMapprivate HashMap<Long, Boolean> entryStockMap = new HashMap<>();@RequestMapping(value = "/doSeckill")public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V5.0--------");//===================秒杀 v5.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//如果库存为空,避免总是到 reids 去查询库存,给 redis 增加负担(内存标记)if (entryStockMap.get(goodsId)) {//如果当前这个秒杀商品已经是空库存,则直接返回.model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//库存预减,如果在redis中预减库存,发现秒杀商品中已经没有库存了,直接返回//从而减少这个方法请求 orderService.seckill防止大量请求打到数据,优化秒杀//这个方法具有原子性!!!Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);//说明这个商品已经没有库存了if (decrement < 0) {//这里使用内存标记,避免多次操作 redis, true 表示空库存了.entryStockMap.put(goodsId, true);//恢复redis库存为0,对业务没有影响只是为了好看redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购// Order order = orderService.seckill(user, goodsVo);// if (order == null) {//     model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());//     return "secKillFail";// }// model.addAttribute("order", order);// model.addAttribute("goods", goodsVo);//抢购, 向 MQ 发消息, 因为不知道是否成功, 客户端需要轮询//errmsg 为排队中.... , 暂时返回 "secKillFaill" 页面SeckillMessage seckillMessage =new SeckillMessage(user, goodsId);mqSenderMessage.senderMessage(JSONUtil.toJsonStr(seckillMessage));model.addAttribute("errmsg", "排队中.....");return "orderDetail";//===================秒杀 v5.0 end... =========================}//该方法实在类的所有属性都初始化后,自动执行//这里将所有秒杀商品的库存量加载到redis中@Overridepublic void afterPropertiesSet() throws Exception {//1.查询所有的秒杀商品List<GoodsVo> list = goodsService.findGoodsVo();if (CollectionUtils.isEmpty(list)) {return;}//遍历list,将秒杀商品的库存量放入到redis中//秒杀商品库存量对应的key seckillGoods:商品idfor (GoodsVo goodsVo : list) {redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());//当有库存为 false,当无库存为 true。防止库存没有了,还会到 Redis 进行判断操作entryStockMap.put(goodsVo.getId(), false);}}}
  • 客户端轮询秒杀结果-思路分析示意图

image-20230311122052931

image-20230311122517345

http://www.dinnco.com/news/17070.html

相关文章:

  • 网站建设APP的软件十大经典营销案例
  • 武汉市党风廉政建设网站网站优化课程
  • 做的比较好的分享网站检测网站是否安全
  • 我公司要网站建设福建seo搜索引擎优化
  • 中央人民政府网站网址pc端百度
  • 西安专业网站建设公司无屏蔽搜索引擎
  • 国外免备案域名浙江网站seo
  • 网站开发日志模版b2b网站平台
  • 成都软件公司前100强seo论坛站长交流
  • 设计网站的管理系统网站优化方法
  • 网店建设管理系统景德镇seo
  • 大屏网站做响应seo如何快速排名
  • 网站建设需要到哪些知识外贸海外推广
  • erlang做网站优势互联网运营
  • 公司做网站所上传的产品图片云服务器免费
  • wordpress生成静态网页seo内容优化是什么
  • 如何免费创建网站平台百度流量
  • 开源php网站开发找网站公司制作网站
  • 网络运维与安全就业方向百度seo排名主要看啥
  • 做一个代驾app需要多少钱杭州百度快照优化公司
  • 中山做外贸网站关键词推广
  • 营销公关名词解释九江seo
  • 为解析的域名做网站百度推广开户渠道公司
  • 思途旅游网站建设互联网营销策划案
  • 个人投资公司注册条件安卓优化大师下载安装
  • 做网站免费模板怎么上传到空间广州seo公司官网
  • 自己做的个人网站 csdn小红书搜索优化
  • 个人做网站靠什么盈利友链交换平台源码
  • wordpress站点是什么北京网站营销与推广
  • 学习网站模板网上的推广