Demo项目(统计接口访问次数的全技术总结)
数据库表的设计:
目的:我们需要统计出单个接口的在指定时间内被访问次数,所需要的变量有id,count次数,和date时间。需要满足,用户每进行一次访问,count次数加1,数据库记录一条新数据,该数据包含此用户的id,和时间,以及count次数加一。
id | count | date |
---|---|---|
1 | 1 | 2022/12/12 17: 36 GMT |
2 | 1 | 2022/12/12 17: 36 GMT |
3 | 1 | 2022/12/12 17: 36 GMT |
拓展:表的粒度问题,我们可以把12月12号这一天所有的访问次数整理成一条记录,这样就可以方便访问在12月12号的总访问次数。
id | count | date |
---|---|---|
1 | 5 | 2022/12/12 |
2 | 6 | 2022/12/13 |
3 | 7 | 2022/12/14 |
MYBATIS plus+部分与springboot整合:
目的:相对于mybatis,mybatis plus自动化了所有CRUD操作,让对数据库的操作更加简单。
pom.xml配置(引入pom依赖)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
创建mapper,该mapper继承了basemapper,basemapper中封装了基础的CRUD操作,无需自己再重新写xml来配置CRUD操作,可以直接调用。
@Mapper
//表明这是一个Mapper,也可以在启动类上加上包扫描
//Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
public interface UserMapper extends BaseMapper<User> {
}
Redis + Spring boot的整合
目的:redis在本项目中作为一个key-value结构的缓存,把访问用户id和访问次数存入redis中,然后再把redis中缓存的数据同步到mysql数据库中。
pom.xml配置(spring支持redis的集成,同时需要下载redis服务器,启动项目前先启动redis-server)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.7-SNAPSHOT</version>
</dependency>
application.properties中指定redis的host 和port(默认ip地址为localhost 127.0.0.1,默认port为6379)
对redis中的key和value进行序列化操作
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig2 {
/**
* 设置 redisTemplate 的序列化设置
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 1.创建 redisTemplate 模版
RedisTemplate<Object, Object> template = new RedisTemplate<>();
// 2.关联 redisConnectionFactory
template.setConnectionFactory(redisConnectionFactory);
// 3.创建 序列化类
GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
// 6.序列化类,对象映射设置
// 7.设置 value 的转化格式和 key 的转化格式
template.setValueSerializer(genericToStringSerializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
RabbitMq与springboot的整合:
目的:使用rabbitmq作为消息队列,异步地将redis缓存信息同步到mysql数据库中。
pom.xml配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring中amqp协议支持rabbitmq的集成
public class RabbitTest {
private static final String EXCHANGE_NAME = "exchange_demo";
private static final String ROUTING_KEY = "routingky_demo";
private static final String QUEUE_NAME = "queue_demo";
private static final String IP_ADDRESS = "127.0.0.1";
private static final int PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername("guest");
factory.setPassword("guest");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
// 创建一个 type=direct 持久化、非自动删除的交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, null);
// 创建一个:持久化、非排他的、非自动删除的队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 将交换器与队列通过 路由键 绑定
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
// String message = "Hello World!";
// channel.basicPublish(EXCHANGE_NAME,
// ROUTING_KEY,
// MessageProperties.PERSISTENT_TEXT_PLAIN,
// message.getBytes());
// 关闭资源
channel.close();
connection.close();
}
}
通过rabbitmq中的connectionfactory创建consumer类,定义channel和queue并对交换器和队列通过路由键进行一个绑定,创建完之后关闭资源。
整体业务逻辑实现:
1.初始化redis util,设定一个key,该key用来记录用户访问的id;
@PostConstruct
public void init(){
log.info("初始化");
int viewid = userMapper.selectById(1).getId();
redisUtil.set("pageid",viewid);
log.info("完成初始化");
}
2.每进行一次访问,就会新增一条用户访问id,该id通过redisUtil里面的incr方法进行递增,并把访问次数count设置为1。通过fastjson把对象转成JSONstring 并使用rebbittemplete中的convertandsend方法,把对象转为信息发送到消息队列中。
@GetMapping("/kay")
public String tokay(){
redisUtil.incr("pageid",1);
User user = new User(Integer.parseInt(redisUtil.get("pageid").toString()),1 ,date);
String s = JSON.toJSONString(user);
rabbitTemplate.convertAndSend("","queue_demo",s);
log.info("添加用户成功");
return "访问量次数加1"+ ": " + redisUtil.get("pageid");
}
3.设置一个rabbitmq 监听器,用于实时监听在consumer方发布的信息,并对此进行消费。使用log.info打印出接受到的消息,并把消息转换为User对象,再使用usermapper把User对象添加到数据库中,这样就实现了异步同步redis缓存到数据库中。
@RabbitListener(queues = "queue_demo")
@RabbitHandler
public void process(@Payload String msg){
User u1 = JSON.parseObject(msg, User.class);
log.info("Userinfo"+u1);
userMapper.insert(u1);
log.info("添加完毕");
}
4.最后使用定时任务,使用springboot中的@Scheduled方法,每隔一段时间对数据库中的访问次数进行一个统计。(未实现的点:传入指定的时间,并判断在该时间内的接口访问次数)
@Scheduled(cron = "*/20 * * * * ?")
public void count(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.gt("count",0);
long count = userMapper.selectCount(queryWrapper);
log.info("统计数为"+ count);
}
项目难点总结:
- rabbitMq 基本概念理解,需了解整个消息队列的运作过程。
- 时间格式化处理,如何在将input时间转成统一时间戳,并进行接口访问次数的判断。
- 表的粒度问题,并发问题,如何实现负载均衡。
- 代码逻辑,如何确定正确的代码逻辑。