Redis應用---Redis實現點贊點踩功能
開發者頭條是一個資源豐富的程式猿學習網站。當網站釋出新的內容的時候,內容優秀的總是會被點贊
為什麼使用Redis而不是MySQL實現?
Redis 和 MySQL應用場景不同。
- 從效率來說:
Redis的資料存放在記憶體,所以速度快但是會受到記憶體空間限制。
MySQL存放在硬碟,在速度上肯定沒有Redis快,但是存放的資料量要多的多。
- 從功能來說:
Redis是一個K-V資料庫,同時還支援List/Hash/Set/Sorted Set等幾個簡單資料結構,所以只能以這些資料結構為基礎實現功能。
Redis效能好,快,併發高,但不能處理邏輯,而且不支援事務,看具體的場合,主要做資料快取,減少MySQL資料庫的壓力。最擅長的是結構化資料的cache,計數器場景,輕量級的訊息佇列,Top排行榜等網際網路應用場景。在點贊過後要立即重新整理顯示在頁面,所以推薦使用Redis。
Redis幾種資料結構的適用場景
- List: 雙向列表,適用於最新列表,關注列表;
- Set: 適用於無順序的集合,點贊點踩,抽獎,已讀,共同好友;
- SortedSet : 具有排序加成功能,適用於排行榜,優先佇列的實現;
- Hash:物件屬性,不定長屬性數;
- KV : 單一數值,適用於驗證碼,快取等實現。
實現思路
使用Redis的Set資料結構儲存資料。
Like和dislike對應不同的key,格式是:LIKE/DISLIKE:entityType:entityID;例如like的一個key:like:1:1234(1代表news業務,1234代表的是指定的newsId);可以通過RedisKeyUtil類中方法獲取相應的key值;
public class RedisKeyUtil { private static String SPLIT = ":"; private static String BIZ_LIKE = "LIKE"; private static String BIZ_DISLIKE = "DISLIKE"; /** * 返回Like的redis key * key:業務+引數 * @param entityId * @param entityType * @return */ public static String getLikeKey(int entityId, int entityType) { return BIZ_LIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); } public static String getDisLikeKey(int entityId, int entityType) { return BIZ_DISLIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); } }
- 點贊:將當前使用者userid作為value,存入到對應like集合當中,同時判斷點dislike集合中是否有此id值,有的話就移除;
- 點踩:與上面操作相反。
- 檢視使用者點贊點踩狀態:通過like和dislike對應的key,以及userid,查詢userid是否在對應的集合中;
程式碼實現
接下來看看如何去實現。
首先必須要安裝好Redis並且啟動Redis服務,瞭解Redis的5種資料結構型別的介紹和基本命令的操作,瞭解Redis的Java客戶端Jedis的操作。這些內容在此不一一詳述,詳情參見博文:http://blog.csdn.net/noaman_wgs/article/details/59501400。
瞭解了以上內容以後,接下來進入實戰演練。
(注:本文只提供後臺實現點讚的一種實現的思路與介面的實現,不提供前臺頁面的顯示與操作)。
實現思路:
使用Redis的Set資料結構儲存資料。
當前使用者點讚的話,就將當前使用者id存入到對應點贊集合當中,同時判斷點反對集合中是否有此id值,有的話就移除;
當前使用者點反對的話,與上面操作相反。
頁面顯示的時候就根據當前使用者id在點贊集合和反對集合中查詢,若id值在點贊集合中有對應值,就顯示1,表示當前使用者點贊;若在反對集合中有值,反對處就顯示1.
1. MAVEN中加入jar包;
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.0</version> </dependency>
2. Jedis底層操作
實現一個具有Dao功能的類,類中可以在Jedis連線池中獲取一個Jedis連線,同時實現與Redis資料庫的get, set , remove等操作。本文的實現用到的都是Redis中的Set資料結構。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * Created by Administrator on 2017/5/1. */ @Service public class JedisAdapter implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(JedisAdapter.class); private Jedis jedis = null; private JedisPool jedisPool = null; @Override public void afterPropertiesSet() throws Exception { //初始化 jedisPool = new JedisPool("localhost", 6379); } //獲取一個Jedis private Jedis getJedis(){ try{ jedis = jedisPool.getResource(); }catch (Exception e){ logger.error("獲取jedis失敗!" + e.getMessage()); }finally { if(jedis != null){ jedis.close(); } } return jedis; } /** * 獲取Redis中集合中某個key值 * @param key * @return */ public String get(String key){ Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.get(key); }catch (Exception e){ logger.error("Jedis get發生異常 " + e.getMessage()); return null; }finally { if(jedis != null){ jedis.close(); } } } /** * 給Redis中Set集合中某個key值設值 * @param key * @param value */ public void set(String key, String value){ Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.set(key, value); }catch (Exception e){ logger.error("Jedis set 異常" + e.getMessage()); }finally { if(jedis != null){ jedis.close(); } } } /** * 向Redis中Set集合新增值:點贊 * @return */ public long sadd(String key, String value){ Jedis jedis = null; try{ jedis = jedisPool.getResource(); return jedis.sadd(key, value); }catch (Exception e){ logger.error("Jedis sadd 異常 :" + e.getMessage()); return 0; }finally { if (jedis != null){ jedis.close(); } } } /** * 移除:取消點贊 * @param key * @param value * @return */ public long srem(String key, String value){ Jedis jedis = null; try{ jedis = jedisPool.getResource(); return jedis.srem(key, value); }catch (Exception e){ logger.error("Jedis srem 異常:" + e.getMessage()); return 0; }finally { if (jedis != null){ jedis.close(); } } } /** *判斷key,value是否是集合中值 * @param key * @param value * @return */ public boolean sismember(String key, String value){ Jedis jedis = null; try{ jedis = jedisPool.getResource(); return jedis.sismember(key, value); }catch (Exception e){ logger.error("Jedis sismember 異常:" + e.getMessage()); return false; }finally { if (jedis != null){ try{ jedis.close(); }catch (Exception e){ logger.error("Jedis關閉異常" + e.getMessage()); } } } } /** * 獲取集合大小 * @param key * @return */ public long scard(String key){ Jedis jedis = null; try{ jedis = jedisPool.getResource(); return jedis.scard(key); }catch (Exception e){ logger.error("Jedis scard 異常:" + e.getMessage()); return 0; }finally { if (jedis != null){ jedis.close(); } } } }
3. 生成點贊與反對鍵值:
點選點贊或者反對的時候,都會根據所在資訊的id生成一個對應的點贊(likeKey) 或者 反對(disLikeKey)的Set集合key值, 將對應點贊或點反對的使用者id當中value值存入Set集合
/** * Created by Administrator on 2017/5/1. */ public class RedisKeyUtil { private static String SPLIT = ":"; private static String BIZ_LIKE = "LIKE"; private static String BIZ_DISLIKE = "DISLIKE"; /** * 產生key:如在newsId為2上的諮詢點贊後會產生key: LIKE:ENTITY_NEWS:2 * @param entityId * @param entityType * @return */ public static String getLikeKey(int entityId, int entityType){ return BIZ_LIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); } /** * 取消贊:如在newsId為2上的資訊取消點贊後會產生key: DISLIKE:ENTITY_NEWS:2 * @param entityId * @param entityType * @return */ public static String getDisLikeKey(int entityId, int entityType){ return BIZ_DISLIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); } }
4. LikeService實現
主要實現的邏輯就是:使用者點贊,就將此使用者id存入對應的點贊集合中,同時從點反對集合中移除;同理點反對就將此使用者id存入對應的點反對集合中,同時從點贊集合中移除此使用者id。
點贊業務操作程式碼:
package com.nowcoder.service; import com.nowcoder.util.JedisAdapter; import com.nowcoder.util.RedisKeyUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * Created by Administrator on 2017/5/1. */ @Service public class LikeService { @Autowired JedisAdapter jedisAdapter; /** * 判斷是點贊還是點反對 * @param userId * @param entityType * @param entityId * @return */ public int getLikeStatus(int userId, int entityType, int entityId) { //根據當前使用者的userid分別生成一個likeKey 和 disLikeKey,再分別判斷這兩個值是否在對應的Like集合中和disLikeKey集合中 //比如如果在likeKey集合中,就返回一個1,否則返回-1 String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType); //判斷值為userId 的使用者是否在key為listKey 的集合中 if(jedisAdapter.sismember(likeKey, String.valueOf(userId))){ return 1; } String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType); return jedisAdapter.sismember(disLikeKey, String.valueOf(userId)) ? -1: 0; } /** * 點贊:即當前使用者點贊後,被點贊使用者的like集合中就會加上一個該點讚的使用者資訊 * @param userId * @param entityType * @param entityId * @return */ public long like(int userId, int entityType, int entityId){ //在當前news上點贊後獲取key: LIKE:ENTITY_NEWS:2 String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType); //在喜歡集合中添加當前操作使用者的userId(即當前使用者點贊後,被點贊使用者的like集合中就會加上一個點讚的使用者資訊) jedisAdapter.sadd(likeKey, String.valueOf(userId)); String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType); jedisAdapter.srem(disLikeKey, String.valueOf(userId)); //返回點贊數量 return jedisAdapter.scard(likeKey); } /** * 反對 :即當前使用者點反對後,被點反對使用者的like集合中就會加上一個該點反對的使用者資訊 * @param userId * @param entityType * @param entityId * @return */ public long disLike(int userId, int entityType, int entityId){ //誰點選反對,誰就出現在key為dislikeKey的Set集合中 String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType); jedisAdapter.sadd(disLikeKey, String.valueOf(userId)); //從贊中刪除 String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType); jedisAdapter.srem(likeKey, String.valueOf(userId)); return jedisAdapter.scard(likeKey); } }
5. 點贊介面的實現:
package com.nowcoder.controller; import com.nowcoder.model.EntityType; import com.nowcoder.model.HostHolder; import com.nowcoder.model.News; import com.nowcoder.service.LikeService; import com.nowcoder.service.NewsService; import com.nowcoder.util.ToutiaoUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; /** * Created by Administrator on 2017/5/1. */ @Controller public class LikeController { @Autowired LikeService likeService; @Autowired NewsService newsService; @Autowired HostHolder hostHolder; @RequestMapping(path = {"/like"}, method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody public String like(@RequestParam("newsId") int newsId){ //在likeKey對應的集合中加入當前使用者的id long likeCount = likeService.like(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId); //資訊上更新點贊數 newsService.updateLikeCount(newsId, (int)likeCount); return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount)); } @RequestMapping(path = {"/dislike"}, method = {RequestMethod.POST, RequestMethod.GET}) @ResponseBody public String disLike(@RequestParam("newsId") int newsId){ //在disLikeKey對應的集合中加入當前使用者 long likeCount = likeService.disLike(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId); if(likeCount <= 0){ likeCount = 0; } //資訊上更新喜歡數 newsService.updateLikeCount(newsId, (int)likeCount); return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount)); } }