抱歉,其實(shí)這篇應(yīng)該幾天前就出的,不過在這個項(xiàng)目中的lucene加載出bug了(雖然還沒解決,但緩存的先記錄下來,發(fā)出給大家)。
本系列:
(1)SSM框架構(gòu)建積分系統(tǒng)和基本商品檢索系統(tǒng)(Spring+SpringMVC+MyBatis+Lucene+Redis+MAVEN)(1)框架整合構(gòu)建
(2)SSM框架構(gòu)建積分系統(tǒng)和基本商品檢索系統(tǒng)(Spring+SpringMVC+MyBatis+Lucene+Redis+MAVEN)(2)建立商品數(shù)據(jù)庫和Lucene的搭建
(3)Redis系列(一)--安裝、helloworld以及讀懂配置文件
文章結(jié)構(gòu):(1)整表緩存;(2)排行榜緩存方案。
Redis文檔
一、整表緩存:(演示禁言表的整表緩存)
整表緩存核心思想:我們使用的是普通的key-value數(shù)據(jù)結(jié)構(gòu)。key對應(yīng)我們普通的禁言id。value則對應(yīng)這個id禁言過他人的列表的list。利用jsonarray和list之間的互換。形成這一整表緩存策略!!!
list里面則裝載著禁言表的記錄,每一行記錄對應(yīng)一個Gag。
(1)先編寫好我們的接口
public interface JedisClient {
public String get(String key);
public String set(String key, String value);
public String hget(String hkey, String key);
public long hset(String hkey, String key, String value);
public long incr(String key);
public long expire(String key, int second);
public long ttl(String key);
public long del(String key);
public long hdel(String hkey, String key);
public long zadd(String key,double score,User user);
public Set<String> zgetAll(String key, long start, long end);
//拿整個榜單
public long zaddList(String key,List<User> userList);
//拿去榜單最后一名,比如前50名,這里就拿到第50名的User。積分變化時就拿這個值去判斷,大于就丟user進(jìn)redis,小于則不管
public Set<String> getTopLast(String key,long start,long end);
}
(2)編寫業(yè)務(wù)接口實(shí)現(xiàn)類:
@Service
public class JedisClientSingle implements JedisClient {
@Autowired
private JedisPool jedisPool;
@Override
public String get(String key) {
Jedis jedis = jedisPool.getResource();
String string = jedis.get(key);
jedis.close();
return string;
}
@Override
public String set(String key, String value) {
Jedis jedis = jedisPool.getResource();
String string = jedis.set(key, value);
jedis.close();
return string;
}
//最基本的數(shù)據(jù)類型操作,key-value
@Override
public String hget(String hkey, String key) {
System.out.println("jedisPool " + jedisPool);
Jedis jedis = jedisPool.getResource();
System.out.println("jedis " + jedis);
String string = jedis.hget(hkey, key);
jedis.close();
return string;
}
@Override
public long hset(String hkey, String key, String value) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.hset(hkey, key, value);
jedis.close();
return result;
}
@Override
public long incr(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.incr(key);
jedis.close();
return result;
}
@Override
public long expire(String key, int second) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.expire(key, second);
jedis.close();
return result;
}
@Override
public long ttl(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.ttl(key);
jedis.close();
return result;
}
@Override
public long del(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.del(key);
jedis.close();
return result;
}
@Override
public long hdel(String hkey, String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.hdel(hkey, key);
jedis.close();
return result;
}
@Override
public long zaddList(String key,List<User> userList){
Jedis jedis = jedisPool.getResource();
Long result = null;
for (int i=0;i<userList.size();i++){
result = jedis.zadd(key, userList.get(i).getScore(), JsonUtils.objectToJson(userList.get(i)));
}
jedis.close();
return result;
}
@Override
public long zadd(String key,double score,User user){
Jedis jedis = jedisPool.getResource();
Long result = jedis.zadd(key, score, JsonUtils.objectToJson(user));
jedis.close();
return result;
}
@Override
public Set<String> zgetAll(String key,long start,long end){
Jedis jedis = jedisPool.getResource();
Set<String> userSet =jedis.zrange(key, start, end);
System.out.println("userSet :"+userSet);
jedis.close();
return userSet;
}
//拿最后一名的,所以start跟end必須標(biāo)記最后一名的位置索引
@Override
public Set<String> getTopLast(String key,long start,long end) {
Jedis jedis = jedisPool.getResource();
Set<String> userSet = jedis.zrange(key,start,end);
System.out.println("userSet :"+userSet);
jedis.close();
return userSet;
}
}
(3)我們寫進(jìn)緩存的string和讀出來的string必須使用統(tǒng)一的轉(zhuǎn)換方式:我這里使用的是fastjson這個庫
public class JsonUtils {
//轉(zhuǎn)成jsonarray
public static String objectToJson(Object data) {
String json = JSONArray.toJSONString(data);
return json;
}
//string轉(zhuǎn)java對象
public static User jsonObjectToUser(String userString) {
User user = JSONObject.parseObject(userString.toString(),User.class);
return user;
}
public static List<User> objectToUser(JSONArray array){
List<User> userList =new ArrayList<>();
for (int i=0;i<array.size();i++) {
System.out.print("array.get(i) "+array.get(i).toString());
//parseObject解析的是一個字符串,強(qiáng)轉(zhuǎn)成為String是不行的,必須打印成String語句
User user = JSONObject.parseObject(array.get(i).toString(),User.class);
userList.add(user);
}
return userList;
}
}
(4)整表緩存的controller調(diào)用:
@RestController
@RequestMapping("/gag")
public class GagController {
@Autowired
private GagService gagService;
@Autowired
private JedisClient jedisClient;
@RequestMapping(value = "/gagList",produces="text/html;charset=UTF-8", method = {RequestMethod.GET,RequestMethod.GET})
public String testRedis(Long id){
List<Gag> gagList= null;
try {
String resulthget = jedisClient.hget("禁言表", id + "");
if (resulthget != null) {
//字符串轉(zhuǎn)為list
System.out.println("有緩存啦啦啦!!!");
JSONArray array = JSONArray.parseArray(resulthget);
gagList = (List) array;
} else {
System.out.println("禁言表沒查過");//沒有查過就讀取整個表的list出來
gagList= gagService.findByUserId(id);
}
} catch (Exception e) {
e.printStackTrace();
}
try {
String cacheString = JsonUtils.objectToJson(gagList);//讀取出來后就加載緩存進(jìn)去,但是要轉(zhuǎn)化給json
jedisClient.hset("禁言表", id + "", cacheString);
} catch (Exception e) {
e.printStackTrace();
}
return JSON.toJSONString(gagList);
}
}
這里寫圖片描述
二、排行榜緩存方案(重頭戲!!!)
方案一:(優(yōu)雅方案):
1.第一次訪問的時候,查數(shù)據(jù)庫,查整個user表查出topN(使用sql排序),丟給redis(使用sorted set數(shù)據(jù)類型)。
2.排序在redis,redis自動排序。以后的用戶訪問:均訪問redis。
3.只要每次積分變化判斷的時候拿topN的最后一個判別,大于某個閾值則整個user丟進(jìn)redis排序。
效率性能再優(yōu)化:用戶積分變動的時候,(守護(hù)線程)服務(wù)器預(yù)存一下變化的數(shù)量。。到一定量再通知。
4.積分變換的時候都往redis拿個第50名的積分進(jìn)行判斷。再往下去設(shè)定一個小距離為閾值。比如現(xiàn)在第50名的積分是100,那80分一下的應(yīng)該就沒必要扔給redis了吧?
注意:這個排行榜的用戶是會不斷增加的,比如1億用戶,如果剛開始只有前50,后5千萬人的積分大于第50名,那么就會往redis加入這個用戶的信息。(雖然看起來要存很多,其實(shí)一億用戶怎么存也就1G左右的內(nèi)存,簡單暴力優(yōu)雅方案了)
實(shí)現(xiàn):
(1)繼續(xù)沿用JedisClient接口
//拿整個榜單
public Set<String> zgetAll(String key, long start, long end);
//批量加入用戶
public long zaddList(String key,List<User> userList);
//拿去榜單最后一名,比如前50名,這里就拿到第50名的User。積分變化時就拿這個值去判斷,大于就丟user進(jìn)redis,小于則不管
public Set<String> getTopLast(String key,long start,long end);
(2)繼續(xù)沿用JedisClientSingle實(shí)現(xiàn)類
//批量加入用戶到redis
@Override
public long zaddList(String key,List<User> userList){
Jedis jedis = jedisPool.getResource();
Long result = null;
for (int i=0;i<userList.size();i++){
result = jedis.zadd(key, userList.get(i).getScore(), JsonUtils.objectToJson(userList.get(i)));
}
jedis.close();
return result;
}
//單個用戶加入。而且可用于更新操作!!!!
@Override
public long zadd(String key,double score,User user){
Jedis jedis = jedisPool.getResource();
Long result = jedis.zadd(key, score, JsonUtils.objectToJson(user));
jedis.close();
return result;
}
//全拿榜單信息(拿多少就多少。)
@Override
public Set<String> zgetAll(String key,long start,long end){
Jedis jedis = jedisPool.getResource();
Set<String> userSet =jedis.zrange(key, start, end);
System.out.println("userSet :"+userSet);
jedis.close();
return userSet;
}
//拿最后一名的,所以start跟end必須標(biāo)記最后一名的位置索引
@Override
public Set<String> getTopLast(String key,long start,long end) {
Jedis jedis = jedisPool.getResource();
Set<String> userSet = jedis.zrange(key,start,end);
System.out.println("userSet :"+userSet);
jedis.close();
return userSet;
}
(3)緩存加入,查詢調(diào)用:(繼續(xù)沿用上面的JsonUtils)
@RequestMapping(value = "/queryTopN", produces = "text/html;charset=UTF-8", method = {RequestMethod.GET, RequestMethod.GET})
public String queryTopN() {
List<User> userList = null;
try {
//拿出redis的排行榜結(jié)果
Set<String> resultSet = jedisClient.zgetAll("Toptest", (long) 0, (long) 20);
System.out.println("resultSet " + resultSet.toString());
Iterator<String> iter = resultSet.iterator();//遍歷它
if (resultSet.size() > 0) {
//字符串轉(zhuǎn)為list
System.out.println("有緩存啦啦啦!!!");
userList = new ArrayList<>();
while (iter.hasNext()) {
//把一個個對象用fastjson格式轉(zhuǎn)換成jsonobject
User user = JsonUtils.jsonObjectToUser(iter.next());
System.out.println("user " + user.toString());
userList.add(user);
}
return JSON.toJSONString(userList);
} else {
System.out.println("Toptest沒查過");
userList = userService.queryTopN();//沒查過就查一次再丟給redis
}
} catch (Exception e) {
e.printStackTrace();
}
try {
//如果沒有緩存就批量加入到redis緩存中
jedisClient.zaddList("Toptest", userList);
} catch (Exception e) {
e.printStackTrace();
}
return JSON.toJSONString(userList);
}
這里寫圖片描述
(4)我們緩存策略核心:修改了用戶積分的時候就與緩存排行榜的我們限定的最后一名進(jìn)行比較,大于就丟進(jìn)redis排序,小于就不丟進(jìn)去。所以只有第一次訪問的時候是使用了數(shù)據(jù)庫的sql排序查詢。
@RequestMapping(value = "/updateScore", produces = "text/html;charset=UTF-8", method = {RequestMethod.GET, RequestMethod.GET})
public String updateScore(Long id,Integer scoreCount) {
User user = userService.queryById(id);
scoreService.updateScore(user,scoreCount);
return JSON.toJSONString(user);
}
注意的是:因?yàn)槊總€user序列化原因,每個user都有獨(dú)特的標(biāo)記,所以我們要從redis中拿user去判斷,而不是新建一個user。這樣的hash是不一樣的!!!
@Transactional
@Override
public void updateScore(User user, int scoreCount) {
User userFind = new User();
Score score = new Score();
score.setChangeType("做任務(wù)");
score.setScore(scoreCount);
score.setUser(user);
scoreDao.insertScore(score);
userDao.updateScore(user.getId(), scoreCount);
//從redis拿出排行榜的list先
Set<String> userSet = jedisClient.getTopLast("Toptest", (long) 0, (long) 5);
Iterator<String> iter = userSet.iterator();//遍歷 它,判斷這個用戶存不存在了
List<User> userList = new ArrayList<>();
while (iter.hasNext()) {
User userTemp = JsonUtils.jsonObjectToUser(iter.next());
System.out.println("user " + user.toString());
userList.add(userTemp);
if (userTemp.getAccount().equals(user.getAccount())) {//如果存在就用存在的
userFind = userTemp;
}
}
//如果原本有的就修改原有的。
if (userFind != null) {
jedisClient.zadd("Toptest", (double) user.getScore(), userFind);
return;
}
//如果沒有就加進(jìn)去嘛,根據(jù)排行榜來區(qū)分
if (userList.size() >= 20) {
if (user.getScore() + scoreCount > userList.get(19).getScore()) {
System.out.println("大于排名20名中的最后一名就丟進(jìn)緩存 :");
System.out.println("userList :" + userList.toString());
}
} else {
System.out.println("排行榜小于20人就直接丟進(jìn)緩存 :");
if (userFind != null) {
jedisClient.zadd("Toptest", (double) user.getScore(), user);
}
System.out.println("user " + userFind);
}
}
這里寫圖片描述