需求
如果访问网站中某些页面,例如某某用户发布的商品页面。。。需要统计浏览量,那么更新数据库就显得蛋疼,所以我想到一种方式,使用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贴出来。。
package com.creator.redis;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.CollectionUtils;
/*
* @author 吴小龙
* 针对所有的hash 都是以h开头的方法
* 针对所有的Set 都是以s开头的方法
* 针对所有的List 都是以l开头的方法
*/
public class RedisUtils {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key,long time){
try {
if(time>0){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key){
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key){
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String ... key){
if(key!=null&&key.length>0){
if(key.length==1){
redisTemplate.delete(key[0]);
}else{
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key){
return key==null?null:redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key,String value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key,String value,long time){
try {
if(time>0){
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}else{
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param by 要增加几(大于0)
* @return
*/
public long incr(String key, long delta){
if(delta<0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param by 要减少几(小于0)
* @return
*/
public long decr(String key, long delta){
if(delta<0){
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key,String item){
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object,Object> hmget(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String,String> map){
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String,Object> map, long time){
try {
redisTemplate.opsForHash().putAll(key, map);
if(time>0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key,String item,String value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key,String item,String value,long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if(time>0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item){
redisTemplate.opsForHash().delete(key,item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item){
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item,double by){
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item,double by){
return redisTemplate.opsForHash().increment(key, item,-by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set<String> sGet(String key){
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key,String value){
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, String...values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key,long time,String...values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if(time>0) expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key){
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object ...values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<String> lGet(String key,long start, long end){
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key){
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key,long index){
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, String value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, String value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<String> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<String> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index,String value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key,long count,String value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
public void setRedisTemplate(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
}