需求

如果访问网站中某些页面,例如某某用户发布的商品页面。。。需要统计浏览量,那么更新数据库就显得蛋疼,所以我想到一种方式,使用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资讯浏览量统计 redis实现阅读量_redis

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数据到数据库");
	}}

现在看最终成果~~~

redis资讯浏览量统计 redis实现阅读量_缓存_02

 

希望对你们有帮助。。

现在把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;
    }
}