[Spring MVC 8]高并发实战小Demo
创始人
2024-02-02 22:23:14

本项目基于Spring MVC进行关于点赞项目的开发,从传统的点赞到高并发缓存开发最后到消息队列异步开发,可谓是令人大开眼界。
本篇博客全部代码已经放出,本博客重点是后端操作,所以对于前端就十分简单的页面。讲述了关于Redis,Quartz定时器、ActiveMQ消息队列等相关内容,需要好好掌握。
关于Spring MVC基本上内容也都完结了,后期还继续深化Spring 体系,特别是源码系统,都需要好好掌握。

这里就高并发稍微说明一下,我们常见的高并发场景有:淘宝的双11、春运时的抢票、微博大V的热点新闻等。除了这些典型事情,每秒几十万请求的秒杀系统、每天千万级的订单系统、每天亿级日活的信息流系统等,都可以归为高并发。

而传统的项目大多直接操作数据库,这会造成数据库承受不住,所以引入了Redis进行缓存。

目录

  • 项目概述
    • 数据库表与持久化类
    • DAO层和Mapper映射文件
    • service层和DTO类
    • Controller层和前端页面
  • 传统点赞实现
  • 集成Redis 缓存
    • Redis使用
    • 集成Redis缓存
    • 设计Redis 数据结构
    • 集成 Quartz定时器
  • 集成ActiveMQ
    • 集成ActiveMQ
    • ActiveMQ异步消费

项目概述

在社交网站或者App中,点赞场景有很多,比如微信说说点赞、微博点赞等。普通人发的说说点赞数比较少,所以并发数少,而一些名人发的微博,由于粉丝多,可能短时间点赞数高达数百万。面对如此高并发的点赞,如果没有设计好项目那么会导致服务器和数据库压力过大出现异常。很多公司后端架构必会采取很多措施来解决这种高并发场景,比如引入缓存提升读的性能、使用MQ队列进行异步处理。
本章没有复杂的前端页面和业务,主要是部分功能,理解高并发的架构设计。

数据库表与持久化类

表的内容已经在ay_user数据库中
创建user表(用户),mood表(说说),user_mood_praise_rel 表(点赞关联表)
user和mood是一对多的关系,关联表主要记录用户和说说的关联关系,即哪些说说被哪些用户点赞。

create table user (id varchar(32) not null ,name varchar(20) default  null,account varchar(20) default  null,primary key (id)
#     key 'user_name_index' (name) using btree ,
#     key 'user_account_index' (account) using btree
) engine = InnoDB default charset = utf8;create table mood(id varchar(32) not null  ,content varchar(256) default null,user_id varchar(32) default null,publish_time datetime default  null,praise_num int(11) default null,primary key (id)
)engine = InnoDB default charset = utf8;create table user_mood_praise_rel (id bigint(32) not null  AUTO_INCREMENT,user_id varchar(32) default null,mood_id varchar(32) default  null,primary key (id)
)engine = InnoDB default charset = utf8;insert into user (id, name, account) values ('1','Jacin','ay');
insert into user (id, name, account) values ('2','ali','a1');insert into mood (id, content, user_id, publish_time, praise_num) values ('1','Good','1','2022-11-12','100');

在ay.model包创建user表的持久化对象,具体代码如下:

public class User implements Serializable {private String id;private String name;private String account;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAccount() {return account;}public void setAccount(String account) {this.account = account;}
}

同样的道理,建立mood,user_mood_praise_rel类,和数据库字段类似,这里就不再放代码了。

DAO层和Mapper映射文件

数据库表和持久化类创建完成后,继续创建对应的Dao类和Mapper映射文件。
在ay.dao创建对应的Dao类:
这里注意Repository(一般用于接口层):该注解的作用不只是将类识别为Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。 Spring本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的持久层框架抛出的异常,使得异常独立于底层的框架。

@Repository
public interface UserDao {User find(String id);
}

在mapper目录下创建user表对应的UserMapper.xml:



id,name,account

其中MoodDao.java:

@Repository
public interface MoodDao {List findAll();
}

以及UserMoodPraiseRelDao.java:

@Repository
public interface UserMoodPraiseRelDao {boolean save(@Param("userMoodPraiseRel")UserMoodPraiseRel userMoodPraiseRel);
}

相关的mapper.xml就不再重复写了。

service层和DTO类

DTO是数据传输对象,用于extends Model类。
通过DTO我们实现了表现层与Model之间的解耦,表现层不引用Model,DTO extends Model层。
ay.dto\MoodDTO.java:

public class MoodDTO extends Mood implements Serializable {private String userName;private String userAccount;public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getUserAccount() {return userAccount;}public void setUserAccount(String userAccount) {this.userAccount = userAccount;}
}

ay.dto\UserDTO.java:

package com.ay.dto;import com.ay.model.User;
public class UserDTO extends User {
}

MoodDTO和UserDTO用于前端展示用的DTO对象,内容比较简单。
接着在service包下面:UserService.java:

public interface UserService {UserDTO find(String id);
}

MoodService.java:

public interface MoodService {//传统查询List findAll();
}

接着实现UserServiceImpl.java:


@Service
public class UserServiceImpl implements UserService {@Resourceprivate UserDao userDao;public UserDTO find(String id) {User user = userDao.find(id);return converModel2DTO(user);}private UserDTO converModel2DTO(User user) {UserDTO userDTO = new UserDTO();userDTO.setId(user.getId());userDTO.setAccount(user.getAccount());userDTO.setName(user.getName());return userDTO;}
}

MoodServiveImpl.java:

@Service
public class MoodServiveImpl implements MoodService {@Resourceprivate MoodDao moodDao;@Resourceprivate UserDao userDao;public List findAll() {List moodList = moodDao.findAll();return converModel2DTO(moodList);}private List converModel2DTO(List moodList) {if (CollectionUtils.isEmpty(moodList)) return Collections.EMPTY_LIST;List moodDTOList = new ArrayList();for (Mood mood : moodList) {MoodDTO moodDTO = new MoodDTO();moodDTO.setId(mood.getId());moodDTO.setContent(mood.getContent());moodDTO.setPraiseNum(mood.getPraiseNum());moodDTO.setPublishTime(mood.getPublishTime());moodDTO.setUserId(mood.getUserId());moodDTOList.add(moodDTO);//设置用户信息User user = userDao.find(mood.getUserId());moodDTO.setUserName(user.getName());moodDTO.setUserAccount(user.getAccount());}return moodDTOList;}
}

然后实现UserMoodPraiseRelService.java:

public interface UserMoodPraiseRelService {boolean save(UserMoodPraiseRel userMoodPraiseRel);
}

接着实现类:

@Service
public class UserMoodPraiseRelServiceImpl implements UserMoodPraiseRelService {@Resourceprivate UserMoodPraiseRelDao userMoodPraiseRelDao;public boolean save(UserMoodPraiseRel userMoodPraiseRel) {return userMoodPraiseRelDao.save(userMoodPraiseRel);}
}

Controller层和前端页面

ay.controller\UserController.java:

@RestController
@RequestMapping("/user")
public class UserController {@Resourceprivate UserService userService;
}

MoodController.java:

@Controller
@RequestMapping("/mood")
public class MoodController {@Resourceprivate MoodService moodService;@RequestMapping("/findAll")public String findAll(Model model) {List moodDTOList = moodService.findAll();model.addAttribute("moods",moodDTOList);return "mood";}
}

在views/mood.jsp:

<%@page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>


Getting Started: Serving Web Content

说说列表:
------------------------------------
用户:${mood.userName}
说说内容:${mood.content}
发表时间:${mood.publishTime}
点赞数:${mood.praiseNum}

进行测试后,浏览器输入80/mood/findAll:
在这里插入图片描述

传统点赞实现

设计思路示例图:
在这里插入图片描述
service层主要做两件事:1.保存点赞用户和被点赞的关联关系,关系保存在rel表中 2.更新说说点赞数。
service层处理过程中,请求数据库获取连接,执行相关的数据库操作归还数据库连接,最终返回数据给用户。
如果在高并发情况下,如果半小时点赞数高达20万,那么QPS高达111(QPS=每秒请求数/事务数量),也就是说后端服务每秒创建111个线程来处理点赞请求。数据库的连接数量有限的会导致响应时间长,处理慢,这就是传统实现的弊端。

代码实现:
MoodMapper.xml:

update moodcontent = #{mood.content},praise_num = #{mood.praiseNum},WHERE id = #{mood.id}

findById用来根据id查询说说实体 mood,update用来更新说说数据。
在MoodDao.java添加:

@Repository
public interface MoodDao {List findAll();boolean update(@Param("mood") Mood mood);Mood findById(String id);
}

在MoodService.java添加:

public interface MoodService {//传统查询List findAll();//传统点赞boolean praiseMood(String userId, String moodId);boolean update(@Param("mood") Mood mood);Mood findById(String id);}

MoodService 接口主要添加三个方法,praiseMood()处理用户点赞,update()更新说说内容,findById()查说说:
向MoodServiceImpl.java添加:

@Service
public class MoodServiveImpl implements MoodService {@Resourceprivate MoodDao moodDao;@Resourceprivate UserDao userDao;@Resourceprivate UserMoodPraiseRelDao userMoodPraiseRelDao;// DTO处理代码已经省略public boolean praiseMood(String userId, String moodId) {//保存关联关系UserMoodPraiseRel userMoodPraiseRel = new UserMoodPraiseRel();userMoodPraiseRel.setUserId(userId);userMoodPraiseRel.setMoodId(moodId);userMoodPraiseRelDao.save(userMoodPraiseRel);//更新说说的点赞数量Mood mood = this.findById(moodId);mood.setPraiseNum(mood.getPraiseNum() + 1);this.update(mood);return Boolean.TRUE;}public boolean update(Mood mood) {return moodDao.update(mood);}public Mood findById(String id) {return moodDao.findById(id);}}

MoodController.java添加如下代码:

@Controller
@RequestMapping("/mood")
public class MoodController {@Resourceprivate MoodService moodService;// 省略find代码@GetMapping(value = "/{moodId}/praise")public String praise(Model model, @PathVariable(value = "moodId") String moodId,@RequestParam(value = "userId") String userId) {boolean isPraise = moodService.praiseMood(userId, moodId);List moodDTOList = moodService.findAll();model.addAttribute("moods", moodDTOList);model.addAttribute("isPraise", isPraise);return "mood";}
}

mood.jsp代码不变。
下面进行测试:
输入80/mood/findAll:
点击 赞 会将点赞数+1
在这里插入图片描述
注意此时url已经改变。

集成Redis 缓存

传统的点赞功能出现的问题很多:1. 高并发请求下,服务器频繁创建线程 2. 高并发请求下,数据库连接池连接数有限 3.高并发请求下,点赞功能是同步处理
我们引入Redis缓存,每次点赞请求不是直接和MySQL数据库进行交互,而是和Redis进行交互,即把点赞相关的数据保存到Redis缓存,最后通过Quartz 创建定时计划,再把缓存的数据保存到数据库中。
在这里插入图片描述

Redis使用

启动redis服务器:redis-server.exe.打开redis-cli.exe 进入Redis客户端:
端口是6379,固定为Redis端口。
字符串类型的增删改查:

在这里插入图片描述

List 集合的增删改查:
在这里插入图片描述
Set集合的增删改查:
在这里插入图片描述
Hash 集合的增删改查:
在这里插入图片描述
可以在java写个程序测试redis连接性:
使用Java操作Redis需要jedis-2.1.0.jar,下载地址:http://files.cnblogs.com/liuling/jedis-2.1.0.jar.zip

import redis.clients.jedis.Jedis;public class TestRedis {public static void main(String[] args) {Jedis jedis = new Jedis("127.0.0.1");jedis.set("namc","a1");}
}

集成Redis缓存

首先在pom.xml引入所需要的依赖:

org.springframework.dataspring-data-redis${spring.redis.version}org.apache.commonscommons-pool2${commons.version}redis.clientsjedis${jedis.version}

接着在resources目录创建redis.properties:
redis密码默认为空

redis.maxIdle=300
redis.minIdle=100
redis.maxWaitMillis=3000
redis.testOnBorrow=true
redis.maxTotal=500
redis.host=127.0.0.1
redis.port=6379
redis.password=

resources创建spring-redis.xml配置文件:




在applicationContext.xml导入spring-redis.xml:


配置完成后,在test文件夹进行RedisTest.java进行测试:


public class RedisTest extends  BaseJunit4Test{@Resourceprivate RedisTemplate redisTemplate;@Testpublic void testRedis() {redisTemplate.opsForValue().set("name","ay1");String name =(String)redisTemplate.opsForValue().get("name");System.out.println(name);}
}

设计Redis 数据结构

Redis数据结构采用多个Set类型的结合来存放。用Set集合来存放所有被点赞说说的id,key可以是自己约定的唯一的key即可,而value为所有被点赞的说说id,用n个set集合来存放每条说说用户点赞的记录。如果需要获取某个说说被点赞的次数,只要统计set集合size()即可。
在这里插入图片描述
在MoodService.java添加:

	boolean praiseMoodForRedis(String userId, String moodId);List findAllForRedis();

在MoodServiceImpl.java:

	@Resourceprivate RedisTemplate redisTemplate;// key命名规范: 项目名称+模块名称+具体内容private static final String PRAISE_HASH_KEY = "springmv.mybatis.boot.mood.id.list.key";public boolean praiseMoodForRedis(String userId, String moodId) {//1.存放到hashset中redisTemplate.opsForSet().add(PRAISE_HASH_KEY , moodId);//2.存放到set中redisTemplate.opsForSet().add(moodId,userId);return false;}@Resourceprivate UserService userService;public List findAllForRedis() {List moodList = moodDao.findAll();if (CollectionUtils.isEmpty(moodList)) {return Collections.EMPTY_LIST;}List moodDTOList = new ArrayList();for (Mood mood : moodList) {MoodDTO moodDTO = new MoodDTO();moodDTO.setId(mood.getId());moodDTO.setUserId(mood.getUserId());//right = 总点赞数量 : 数据库的点赞数量 + redis的点赞数量moodDTO.setPraiseNum(mood.getPraiseNum() + redisTemplate.opsForSet().size(mood.getId()).intValue());moodDTO.setPublishTime(mood.getPublishTime());moodDTO.setContent(mood.getContent());//通过userID查询用户User user = userService.find(mood.getUserId());//用户名moodDTO.setUserName(user.getName());//账户moodDTO.setUserAccount(user.getAccount());moodDTOList.add(moodDTO);}return moodDTOList;}

MoodServiceImpl 类实现了MoodService 接口中的方法,处理逻辑较为简单:1.保存mood_id到Set集合 2.保存mood_id 和user_id 到Set集合中。
注意这里有多少说说被点赞在Redis缓存就有多少个Set集合。
在MoodController.java:
这里为了简单起见,user_id就直接随机生成了。

@GetMapping(value = "/{moodId}/praiseForRedis")public String praiseForRedis(Model model, @PathVariable(value = "moodId") String moodId,@RequestParam(value = "userId") String userId) {//方便使用,随机生成用户idRandom random = new Random();userId = random.nextInt(100) + "";boolean isPraise = moodService.praiseMoodForRedis(userId, moodId);//查询所有的说说数据List moodDTOList = moodService.findAllForRedis();model.addAttribute("moods", moodDTOList);model.addAttribute("isPraise", isPraise);return "mood";}

将mood.jsp的点赞路径改一下:


现在运行80/mood/findAll:
左边是相应的mysql:发现确实是不一样的数据(说明此时做到了缓存)
在这里插入图片描述
当你再次输入80/mood/findAll的时候,你会发现点赞数还是143,但是你点击点赞的时候就会变成156,说明数据在redis做到了缓存:
在这里插入图片描述
下面要做的是定时从redis读数据进入数据库保存
可以用Test进行测试:

 @Testpublic void testRedis() {//redisTemplate.opsForValue().set("htf", "ayccc");Set set = redisTemplate.opsForSet().members("springmv.mybatis.boot.mood.id.list.key");System.out.println("value of name is:" + set);}

集成 Quartz定时器

Quartz是一个Java 编写的开源任务调度的框架,通过触发器设置作业定时运行规则,控制作业的运行时间。定时器作用很多,比如,定时发送信息和定时生成报表等。
Quartz 框架主要核心组件包括调度器、触发器和作业。调度器作为作业的总指挥,触发器作为作业的操作者,作业为应用的功能模块。
在这里插入图片描述
Job 是一个接口,只有一个execute,被调度的作业需要实现该接口中execute()。
下面在ay\job创建PraiseDataSaveDBJob.java:


@Component@Configurable
// 相当于配置文件,被Spring 扫描初始化
@EnableScheduling
// 开启对计划任务的支持,在执行的任务上注解@Scheduled,声明是一个计划任务。
public class PraiseDataSaveDBJob {//每5秒执行一次@Scheduled(cron = "*/60 * *  * * * ")public void savePraiseDataToDB() {System.out.println("run .....");}@Resourceprivate RedisTemplate redisTemplate;private static final String PRAISE_HASH_KEY = "springmv.mybatis.boot.mood.id.list.key";@Resourceprivate UserMoodPraiseRelService userMoodPraiseRelService;@Resourceprivate MoodService moodService;//每10秒执行一次,真实项目当中,我们可以把定时器的执行计划时间设置长一点//比如说每天晚上凌晨2点跑一次。@Scheduled(cron = "*/10 * *  * * * ")public void savePraiseDataToDB2() {//获取所有被点赞的说说idSet moods = redisTemplate.opsForSet().members(PRAISE_HASH_KEY);if (CollectionUtils.isEmpty(moods)) {return;}for (String moodId : moods) {if (redisTemplate.opsForSet().members(moodId) == null) {continue;} else {//通过说说id获取所有点赞的用户id列表Set userIds = redisTemplate.opsForSet().members(moodId);if (CollectionUtils.isEmpty(userIds)) {continue;} else {for (String userId : userIds) {UserMoodPraiseRel userMoodPraiseRel = new UserMoodPraiseRel();userMoodPraiseRel.setMoodId(moodId);userMoodPraiseRel.setUserId(userId);//保存说说与用户关联关系userMoodPraiseRelService.save(userMoodPraiseRel);}Mood mood = moodService.findById(moodId);//更新说说点赞数量//说说的总点赞数量 = redis 点赞数量 + 数据库的点赞数量mood.setPraiseNum(mood.getPraiseNum() + redisTemplate.opsForSet().size(moodId).intValue());moodService.update(mood);//清除缓存数据redisTemplate.delete(moodId);}}}//清除缓存数据redisTemplate.delete(PRAISE_HASH_KEY);}
}

下面开始测试:输入80/mood/findAll,点击点赞按钮会发现10秒后数据库内容将进行更新,注意我开启定时的时候会将缓存数据删除。

集成ActiveMQ

本次解决的是对点赞功能同步处理。消息队列的特点:异步、削峰、解耦。解决方案:
把数据放到消息队列叫做生产者
从消息队列里边取数据叫做消费者
在这里插入图片描述
ActiveMQ安装
MQ:MessageQueue,消息队列,是一个消息的接收和转发的容器,用于消息推送。ActiveMQ是一个开源的消息系统,完全采用Java实现,所以很好支持JMS规范。
直接去官网下载:ActiveMQ
进入win64包打开activemq.bat:
在这里插入图片描述
浏览器输入http://localhost:8161/admin/ 账号密码均为admin
在这里插入图片描述

集成ActiveMQ

首先在pom.xml引入依赖:

org.springframeworkspring-jms${spring.version}org.apache.activemqactivemq-all5.11.2spring-contextorg.springframeworkorg.apache.geronimo.specsgeronimo-jms_1.1_specjavax.jmsjavax.jms-api2.0.1

在resources 目录下创建Active MQ配置文件spring-jms.xml:


JMS连接工厂队列模式模型

resources创建activemq.properties:

### active mq服务器地址
activemq_url=tcp://localhost:61616
### 服务器用户名
activemq_username=admin
### 服务器密码
activemq_password=admin

在applicationContext.xml 引入jms.xml:

    

ActiveMQ异步消费

在上面集成了ActiveMQ消息中间件,同时开发了相关的配置文件,这一节主要利用ActiveMQ实现点赞功能的异步处理。
在ay\mq目录下创建MoodProducer


@Component
public class MoodProducer {@Resourceprivate JmsTemplate jmsTemplate;private Logger log = Logger.getLogger(this.getClass());public void sendMessage(Destination destination, final MoodDTO mood) {log.info("生产者--->>>用户id:" + mood.getUserId() + " 给说说id:" + mood.getId() + " 点赞");//mood实体需要实现Serializable序列化接口jmsTemplate.convertAndSend(destination, mood);}
}

提供sendMessage 用来发送消息,方法的第一个参数是destination,主要用来指定队列的名称,第二个参数就是mood说说实体。需要注意的是,MoodDTO需要实现序列化接口。
在ay\mq创建MoodConsumer.java:


public class MoodConsumer implements MessageListener {private static final String PRAISE_HASH_KEY = "springmv.mybatis.boot.mood.id.list.key";@Resourceprivate RedisTemplate redisTemplate;private Logger log = Logger.getLogger(this.getClass());public void onMessage(Message message) {try {MoodDTO mood = (MoodDTO) ((ActiveMQObjectMessage) message).getObject();//1.存放到set中redisTemplate.opsForSet().add(PRAISE_HASH_KEY, mood.getId());//2.存放到set中redisTemplate.opsForSet().add(mood.getId(), mood.getUserId());log.info("消费者--->>>用户id:" + mood.getUserId() + " 给说说id:" + mood.getId() + " 点赞");} catch (Exception e) {System.out.println(e);}}
}

实现了MessageListener 接口,完成对消息的监听和接收,消息两种接收方法:同步接收和异步接收。
修改MoodServiceImpl 的praiseMoodForRedis方法:

public boolean praiseMoodForRedis(String userId, String moodId) {MoodDTO moodDTO = new MoodDTO();moodDTO.setUserId(userId);moodDTO.setId(moodId);moodProducer.sendMessage(destination, moodDTO);//        //1.存放到hashset中
//        redisTemplate.opsForSet().add(PRAISE_HASH_KEY , moodId);
//        //2.存放到set中
//        redisTemplate.opsForSet().add(moodId,userId);return false;}

下面进行测试:输入80/mood/findAll:
表明异步消费开发成功。
在这里插入图片描述

以上就是全部内容了。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...