需求
如果访问网站中某些页面,例如某某用户发布的商品页面。。。需要统计浏览量,那么更新数据库就显得蛋疼,所以我想到一种方式,使用redis+spring自带的scheduled定时任务来实现。
思路
使用redis的pub/sub 消息队列来统计一段时间内的网站浏览量。
使用scheduled定时任务 每隔一段时间批量更新数据库中的浏览量。
实现
开始之前请确保引入了正确的pom配置。。。
第一个是消息队列的实现。。消息队列是什么可以百度
redis配置文件:(配置文件中所引用的类需要自己实现,我会标出)
<!-- 配置JedisPoolConfig实例 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxTotal" value="${redis.maxActive}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<!-- 配置JedisConnectionFactory -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<!-- <property name="password" value="${redis.pass}" /> -->
<property name="database" value="${redis.dbIndex}" />
<property name="poolConfig" ref="poolConfig" />
</bean>
<!-- 配置RedisTemplate 这里不适用RedisTemplate 我们不存对象不需要序列化操作,有需要的可以自行寻找解决办法-->
<!-- <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" /> </bean> -->
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
<!-- 配置RedisCacheManager -->
<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="redisOperations" ref="stringRedisTemplate" />
<property name="defaultExpiration" value="${redis.expiration}" />
</bean>
<bean id="redisUtils" class="com.creator.redis.RedisUtils">
<property name="redisTemplate" ref="stringRedisTemplate" />
</bean>
# Redis settings
redis.host=127.0.0.1
redis.port=6379
#redis.pass=password
redis.dbIndex=0
redis.expiration=3000
redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true
RedisMessageReceiver 用来处理订阅之后 收到的消息 ,其中的RedisUtils工具类会在文末给大家。。只是用来操作redis数据库用的,没有逻辑,主要的逻辑都注释了,先试试做吧。
@Component
public class RedisMessageReceiver {
@Autowired
private RedisUtils redisUtils;
/**接收消息的方法*/
public void receiveMessage(String message){
// 输出发布的信息
System.out.println("有用户浏览一次 prodId为 : " + message+" 的页面");
//如果缓存中没有 浏览数据 则创建哈希表
if(redisUtils.hmget("prodGlance").isEmpty()){
//创建新的哈希表 用来储存 页面访问量数据
Map<String, String> newMap = new HashMap<String, String>();
//往map里边添加此次访问增加的访问量
newMap.put(message, "1");
redisUtils.hmset("prodGlance", newMap);
}else{ // 如果缓存中存在map 则寻找对应键值 加一
//如果 map中没有这个页面的访问数据缓存 则添加此页面Id
if(redisUtils.hget("prodGlance", message) == null){
redisUtils.hset("prodGlance", message, "1");
}else { // 如果找到 则直接+1
redisUtils.hincr("prodGlance", message, 1);
}
}
}
}
最后是SubscriberConfig 用来注册订阅信息,以及处理收到消息的方法
@Configuration
public class SubscriberConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 订阅了一个叫prodId 的通道
container.addMessageListener(listenerAdapter, new PatternTopic("prodId"));
// 这个container 可以添加多个 messageListener
return container;
}
@Bean
MessageListenerAdapter listenerAdapter(RedisMessageReceiver receiver) {
// 这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
return new MessageListenerAdapter(receiver, "receiveMessage");
}
}
现在我们写一个controller接收请求
@Controller
public class PublisherController {
@Autowired
private PublisherService publisherService;
@RequestMapping(value = "pub",method=RequestMethod.GET)
public String pubMsg(@Param(value="id") String id) {
publisherService.pubMsg(id);
return "index";
}
}
再来一个Service处理逻辑
@Service("publisherService")
public class PublisherServiceImp implements PublisherService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void pubMsg(String id) {
stringRedisTemplate.convertAndSend("prodId", id);
System.out.println("Publisher sendes Topic... ");
}
}
最后看一下效果~~
使用PostMan访问。
redis已经记下了浏览数量,接下来我们用定时任务来更新数据库 ,这边比较简单了。简单贴配置文件和代码。
之前一直使用的是@Scheduled注解来做的定时任务,今天不知道怎么了跑不起来,现在使用配置文件写一个。
配置文件中加入命名空间
xmlns:task="http://www.springframework.org/schema/task"xmlns:task="http:/
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
配置定时任务,红字是实现类 method是方法,cron是时间间隔,我们设为10秒一次,比较快查看效果
<!-- 配置定时任务 -->
<task:annotation-driven />
<bean id="Task" class="com.creator.redis.SaveRedisToMysql"/>
<task:scheduler id="task" pool-size="3" />
<task:scheduled-tasks scheduler="task">
<task:scheduled ref="Task" method="save" cron="10 * * * * ?" />
</task:scheduled-tasks>
现在是定时任务实现类,同样 其中的RedisUtils类会在文末给出
public class SaveRedisToMysql {
@Autowired
private RedisUtils redisUtils;
@Autowired
private ProductionMapper productionDao;
public void save() {
// 如果缓存中有页面访问量数据 则进行操作
Map<Object, Object> glanceMap = redisUtils.hmget("prodGlance");
if (!glanceMap.isEmpty()) {
Iterator keys = glanceMap.entrySet().iterator();
while (keys.hasNext()) {
// 获取键值
Map.Entry entry = (Map.Entry) keys.next();
Object prodId = entry.getKey();
Object glance = entry.getValue();
// 更新至数据库
int oldGlance = productionDao.selectByPrimaryKey(Integer.parseInt(prodId.toString())).getGlance();
productionDao.updateByPrimaryKeySelective(
new Production(Integer.parseInt(prodId.toString()), Integer.parseInt(glance.toString())+oldGlance));
// 重置缓存中的键值
redisUtils.hset("prodGlance", prodId.toString(), "0");
}
}
System.out.println("成功保存redis数据到数据库");
}}
现在看最终成果~~~
希望对你们有帮助。。
现在把RedisUtils贴出来。。
算了。。太长了,留下邮箱我发给大家