1. 程式人生 > 其它 >Redis應用---Redis實現點贊點踩功能

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));
    }


}