SpringBoot整合Redis實現(xiàn)訪問量統(tǒng)計的示例代碼
前言
之前開發(fā)系統(tǒng)的時候客戶提到了一個需求:需要統(tǒng)計某些頁面的訪問量,記得當(dāng)時還糾結(jié)了一陣子,不知道怎么去實現(xiàn)這個功能,后來還是在大佬的帶領(lǐng)下借助 Redis 實現(xiàn)了這個功能。今天又回想起了這件事,正好和大家分享一下 Spring Boot 整合 Redis 實現(xiàn)訪問量統(tǒng)計的全過程。
首先先解釋一下為什么需要借助 Redis,其實原因也很簡單,就是因為它非??欤棵肟蓤?zhí)行大約110000次的 SET 操作,每秒大約可執(zhí)行81000次的 GET 操作),我們就可以把訪問量暫存在 Redis 中,當(dāng)有人訪問頁面的時候,就直接在 Redis 中執(zhí)行 +1 的操作,然后再每隔一段時間把 Redis 中的訪問量的數(shù)值寫入到數(shù)據(jù)庫中就搞定了~
肯定有小伙伴會想:如果我們不借助 Redis 而是直接操作數(shù)據(jù)庫的話會怎么樣呢?
訪問量的統(tǒng)計是需要頻繁讀寫的,如果不用 Redis 做緩存而是直接操作數(shù)據(jù)庫的話,就會對數(shù)據(jù)庫帶來巨大的壓力,試想一下如果此時有成千上萬個人同時訪問頁面的話,數(shù)據(jù)庫很可能在這一瞬間造成數(shù)據(jù)庫的崩潰。對于這種高讀寫的場景,就需要直接在 Redis 上讀寫,等到合適的時間,再將數(shù)據(jù)批量寫到數(shù)據(jù)庫中。所以通常來說,在必要的時候引入Redis,可以減少MySQL(或其他)數(shù)據(jù)庫的壓力。
Spring Boot 整合 Redis
怎么創(chuàng)建 Spring Boot 項目這里就不提了,直接上重點——整合 Redis
引入依賴、增加配置
首先還是需要引入 Redis 依賴
<!-- 集成Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
接下來就在配置文件中增加 Redis 的相關(guān)配置
# spring配置
spring:
# redis配置
redis:
host: 127.0.0.1
port: 6379
database: 0
jedis:
pool:
max-active: 200
max-idle: 500
min-idle: 8
max-wait: 10000
timeout: 5000P.S. 如果 Redis 設(shè)置了密碼,別忘了增加 password 配置哦 ~
翠花!上代碼
首先在 Utils 包內(nèi)新增一個 RedisUtil
package com.media.common.utils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @program: media
* @description: RedisUtil
* @author: 莊霸.liziye
* @create: 2021-12-15 10:02
**/
@Component
public final class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定緩存失效時間
* @param key 鍵
* @param time 時間(秒)
*/
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;
}
}
/**
* 根據(jù)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, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入并設(shè)置時間
* @param key 鍵
* @param value 值
* @param time 時間(秒) time要大于0 如果time小于等于0 將設(shè)置無限期
* @return true成功 false 失敗
*/
public boolean set(String key, Object 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 delta 要增加幾(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞增因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 遞減
* @param key 鍵
* @param delta 要減少幾(小于0)
*/
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
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 獲取hashKey對應(yīng)的所有鍵值
* @param key 鍵
* @return 對應(yīng)的多個鍵值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 鍵
* @param map 對應(yīng)多個鍵值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并設(shè)置時間
* @param key 鍵
* @param map 對應(yīng)多個鍵值
* @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表中放入數(shù)據(jù),如果不存在將創(chuàng)建
*
* @param key 鍵
* @param item 項
* @param value 值
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
*
* @param key 鍵
* @param item 項
* @param value 值
* @param time 時間(秒) 注意:如果已存在的hash表有時間,這里將會替換原有的時間
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object 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遞增 如果不存在,就會創(chuàng)建一個 并把新增后的值返回
*
* @param key 鍵
* @param item 項
* @param by 要增加幾(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash遞減
*
* @param key 鍵
* @param item 項
* @param by 要減少記(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根據(jù)key獲取Set中的所有值
* @param key 鍵
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根據(jù)value從一個set中查詢,是否存在
*
* @param key 鍵
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將數(shù)據(jù)放入set緩存
*
* @param key 鍵
* @param values 值 可以是多個
* @return 成功個數(shù)
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 將set數(shù)據(jù)放入緩存
*
* @param key 鍵
* @param time 時間(秒)
* @param values 值 可以是多個
* @return 成功個數(shù)
*/
public long sSetAndTime(String key, long time, Object... 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 鍵
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值為value的
*
* @param key 鍵
* @param values 值 可以是多個
* @return 移除的個數(shù)
*/
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緩存的內(nèi)容
*
* @param key 鍵
* @param start 開始
* @param end 結(jié)束 0 到 -1代表所有值
*/
public List<Object> 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 鍵
*/
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倒數(shù)第二個元素,依次類推
*/
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 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
* @param key 鍵
* @param value 值
* @param time 時間(秒)
*/
public boolean lSet(String key, Object 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 值
* @return
*/
public boolean lSet(String key, List<Object> 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<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據(jù)索引修改list中的某條數(shù)據(jù)
*
* @param key 鍵
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object 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 移除的個數(shù)
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}然后再新增一個 RedisConfig 類
package com.media.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
/**
* @program: media
* @description: RedisConfiguration
* @author: 莊霸.liziye
* @create: 2021-12-15 10:16
**/
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
/**
* 設(shè)置 redisTemplate 的序列化設(shè)置
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 1.創(chuàng)建 redisTemplate 模版
RedisTemplate<Object, Object> template = new RedisTemplate<>();
// 2.關(guān)聯(lián) redisConnectionFactory
template.setConnectionFactory(redisConnectionFactory);
// 3.創(chuàng)建 序列化類
GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
// 6.序列化類,對象映射設(shè)置
// 7.設(shè)置 value 的轉(zhuǎn)化格式和 key 的轉(zhuǎn)化格式
template.setValueSerializer(genericToStringSerializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}有些眼尖的小伙伴會發(fā)現(xiàn)在 RedisUtil 工具類中,我們在 private RedisTemplate<String, Object> redisTemplate 上增加的是 @Resource 注解,并非是 @Autowire 注解。

原因也很簡單,在源碼中我們可以看到 RedisTemplate 指定的是泛型,如果在注入 RedisTemplate 時,值的部分使用了 Object ,那么再使用@AutoWired 注解注入就會報空指針的錯誤,所以需要使用 @Resource 注解(二者的區(qū)別是前者是根據(jù)類型注入后者是根據(jù)名字注入,具體的這里就不詳細說,有興趣的小伙伴可自行百度查閱??)
Redis 的相關(guān)代碼到這里就寫完了, 接下來我們就以“記錄A頁面的訪問量”為需求,寫一個簡單的業(yè)務(wù)邏輯,代碼僅供參考哦 ~
首先我們新建一個數(shù)據(jù)庫表,表結(jié)構(gòu)很簡單,只有三個字段,分別是ID、訪問量、統(tǒng)計時間

我們再寫一下操作這個表的 CRUD 方法(這個也很簡單,相信各位小伙伴都可以腦補出來 (●'?'●) 所以在這里就不寫具體代碼了)
此處略去一萬個字....??
下面我們寫一個監(jiān)聽類:
package com.media.picture.handler;
import com.media.common.utils.DateUtils;
import com.media.common.utils.RedisUtil;
import com.media.picture.domain.MamPictureView;
import com.media.picture.service.IMamPictureViewService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* @program: media
* @description: ListenHandler
* @author: 莊霸.liziye
* @create: 2021-12-15 10:54
**/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ListenHandler {
@Autowired
private RedisUtil redisUtil;
@Autowired
private IMamPictureViewService iMamPictureViewService;
public ListenHandler(){
System.out.println("開始初始化");
}
@PostConstruct
public void init() {
System.out.println("Redis及數(shù)據(jù)庫開始初始化");
//插入一條空數(shù)據(jù)
MamPictureView mamPictureView = new MamPictureView();
mamPictureView.setViewNum(Long.valueOf(0));
int viewId = iMamPictureViewService.insertMamPictureView(mamPictureView);
redisUtil.set("pageA_id", viewId);
redisUtil.set("pageA_count", 0);
System.out.println("Redis及數(shù)據(jù)庫初始化完畢");
}
}監(jiān)聽器的作用就是當(dāng)項目啟動后,在數(shù)據(jù)庫表中插入一條空記錄,并且在 Redis 中存入這條空記錄的 id,并且將其訪問量初始化為0。
最后我們再寫一下跳轉(zhuǎn)A頁面的方法:
@Autowired
private RedisUtil redisUtil;
@GetMapping("/toPageA")
public String toPageA()
{
redisUtil.incr("pageA_count",1);
System.out.println("訪問量:"+redisUtil.get("pageA_count"));
return "/pageA";
}這時候代碼就全部搞定了,我們啟動一下項目,看看執(zhí)行效果??

我們每點跳轉(zhuǎn)一次頁面,Redis 中的訪問量就會執(zhí)行+1操作,實現(xiàn)了訪問量的記錄,最后一步就是把 Redis 中記錄的訪問量寫入數(shù)據(jù)庫就大功告成啦~
我這里選擇的是使用定時任務(wù)的方式寫入,每間隔一段時間寫入一次(為了能看到明顯的效果,就寫成了每間隔40秒執(zhí)行一次)??
@Scheduled(cron = "*/40 * * * * ?")
public void viewCount2DB(){
System.out.println("準(zhǔn)備從redis寫入mysql");
MamPictureView mamPictureView = new MamPictureView();
mamPictureView.setViewId(Long.valueOf((String) redisUtil.get("pageA_id")));
mamPictureView.setViewNum(Long.valueOf((String) redisUtil.get("pageA_count")));
iMamPictureViewService.updateMamPictureView(mamPictureView);
System.out.println("寫入完畢");
}
P.S. 寫入數(shù)據(jù)庫的過程就很簡單了,而且有很多辦法可以實現(xiàn)寫入的操作,這里的定時任務(wù)只作為參考哦~ o( ̄▽ ̄)ブ
到此這篇關(guān)于SpringBoot整合Redis實現(xiàn)訪問量統(tǒng)計的示例代碼的文章就介紹到這了,更多相關(guān)SpringBoot整合Redis訪問量統(tǒng)計內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot項目Redis統(tǒng)計在線用戶的實現(xiàn)示例
- SpringBoot+Redis Bitmap實現(xiàn)活躍用戶統(tǒng)計
- SpringBoot+Redis?BitMap實現(xiàn)簽到與統(tǒng)計的項目實踐
- 微服務(wù)Spring Boot 整合 Redis 實現(xiàn)UV 數(shù)據(jù)統(tǒng)計的詳細過程
- 微服務(wù)?Spring?Boot?整合?Redis?BitMap?實現(xiàn)?簽到與統(tǒng)計功能
- SpringBoot使用Redis的zset統(tǒng)計在線用戶信息
- SpringBoot運用Redis統(tǒng)計用戶在線數(shù)量的兩種方法實現(xiàn)
相關(guān)文章
Java實現(xiàn)拖拽文件上傳dropzone.js的簡單使用示例代碼
本篇文章主要介紹了Java實現(xiàn)拖拽文件上傳dropzone.js的簡單使用示例代碼,具有一定的參考價值,有興趣的可以了解一下2017-07-07

