<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redis 如何高效實現(xiàn)點贊、取消點贊功能

          共 14312字,需瀏覽 29分鐘

           ·

          2021-11-08 21:13

          點擊“藍字”,關注,置頂公眾號

          每日技術干貨,第一時間送達!


          1

          前言


          基于 SpringCloud, 用戶發(fā)起點贊、取消點贊后先存入 Redis 中,再每隔兩小時從 Redis 讀取點贊數(shù)據(jù)寫入數(shù)據(jù)庫中做持久化存儲。


          點贊功能在很多系統(tǒng)中都有,但別看功能小,想要做好需要考慮的東西還挺多的。


          點贊、取消點贊是高頻次的操作,若每次都讀寫數(shù)據(jù)庫,大量的操作會影響數(shù)據(jù)庫性能,所以需要做緩存。


          至于多久從 Redis 取一次數(shù)據(jù)存到數(shù)據(jù)庫中,根據(jù)項目的實際情況定吧,我是暫時設了兩個小時。


          項目需求需要查看都誰點贊了,所以要存儲每個點贊的點贊人、被點贊人,不能簡單的做計數(shù)。


          文章分四部分介紹:


          • Redis 緩存設計及實現(xiàn)

          • 數(shù)據(jù)庫設計

          • 數(shù)據(jù)庫操作

          • 開啟定時任務持久化存儲到數(shù)據(jù)庫



          2

          Redis 緩存設計及實現(xiàn)


          1.1 Redis 安裝及運行


          Redis 安裝請自行查閱相關教程。


          說下Docker 安裝運行 Redis


          docker run -d -p?6379:6379?redis:4.0.8


          如果已經安裝了 Redis,打開命令行,輸入啟動 Redis 的命令


          redis-server


          1.2 Redis 與 SpringBoot 項目的整合


          1.在 pom.xml 中引入依賴


          <dependency>??
          ????<groupId>org.springframework.bootgroupId>
          ??
          ????<artifactId>spring-boot-starter-data-redisartifactId>??
          dependency>


          2.在啟動類上添加注釋 @EnableCaching


          @SpringBootApplication??
          @EnableDiscoveryClient??
          @EnableSwagger2??
          @EnableFeignClients(basePackages =?"com.solo.coderiver.project.client")
          @EnableCaching??
          public class UserApplication {
          ????public?static?void?main(String[] args) {
          ????????SpringApplication.run(UserApplication.class, args);
          ????}
          }


          3.編寫 Redis 配置類 RedisConfig


          @Configuration??
          public?class?RedisConfig {

          ????@Bean??
          ????@ConditionalOnMissingBean(name =?"redisTemplate")
          ????public?RedisTemplate<String,?Object> redisTemplate(
          ????????????RedisConnectionFactory redisConnectionFactory)
          ????????????throws UnknownHostException {
          ????????Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =?new?Jackson2JsonRedisSerializer<Object>(Object.class);
          ????????ObjectMapper om =?new?ObjectMapper();
          ????????om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
          ????????om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
          ????????jackson2JsonRedisSerializer.setObjectMapper(om);

          ????????RedisTemplate<String,?Object> template =?new?RedisTemplate<String,?Object>();
          ????????template.setConnectionFactory(redisConnectionFactory);
          ????????template.setKeySerializer(jackson2JsonRedisSerializer);
          ????????template.setValueSerializer(jackson2JsonRedisSerializer);
          ????????template.setHashKeySerializer(jackson2JsonRedisSerializer);
          ????????template.setHashValueSerializer(jackson2JsonRedisSerializer);
          ????????template.afterPropertiesSet();
          ????????return?template;
          ????}

          ????@Bean??
          ????@ConditionalOnMissingBean(StringRedisTemplate.class)
          ????public?StringRedisTemplate stringRedisTemplate(
          ????????????RedisConnectionFactory redisConnectionFactory)
          ????????????throws UnknownHostException {
          ????????StringRedisTemplate template =?new?StringRedisTemplate();
          ????????template.setConnectionFactory(redisConnectionFactory);
          ????????return?template;
          ????}

          }


          至此 Redis 在 SpringBoot 項目中的配置已經完成,可以愉快的使用了。


          1.3 Redis 的數(shù)據(jù)結構類型


          Redis 可以存儲鍵與5種不同數(shù)據(jù)結構類型之間的映射,這5種數(shù)據(jù)結構類型分別為String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。


          下面來對這5種數(shù)據(jù)結構類型作簡單的介紹:



          1.4 點贊數(shù)據(jù)在 Redis 中的存儲格式


          用 Redis 存儲兩種數(shù)據(jù),一種是記錄點贊人、被點贊人、點贊狀態(tài)的數(shù)據(jù),另一種是每個用戶被點贊了多少次,做個簡單的計數(shù)。


          由于需要記錄點贊人和被點贊人,還有點贊狀態(tài)(點贊、取消點贊),還要固定時間間隔取出 Redis 中所有點贊數(shù)據(jù),分析了下 Redis 數(shù)據(jù)格式中 Hash 最合適。


          因為 Hash 里的數(shù)據(jù)都是存在一個鍵里,可以通過這個鍵很方便的把所有的點贊數(shù)據(jù)都取出。這個鍵里面的數(shù)據(jù)還可以存成鍵值對的形式,方便存入點贊人、被點贊人和點贊狀態(tài)。


          設點贊人的 id 為 likedPostId,被點贊人的 id 為 likedUserId ,點贊時狀態(tài)為 1,取消點贊狀態(tài)為 0。將點贊人 id 和被點贊人 id 作為鍵,兩個 id 中間用 :: 隔開,點贊狀態(tài)作為值。


          所以如果用戶點贊,存儲的鍵為:likedUserId::likedPostId,對應的值為 1 。取消點贊,存儲的鍵為:likedUserId::likedPostId,對應的值為 0 。取數(shù)據(jù)時把鍵用 :: 切開就得到了兩個id,也很方便。


          在可視化工具 RDM 中看到的是這樣子



          1.5 操作 Redis


          將具體操作方法封裝到了 RedisService 接口里


          RedisService.java


          public?interface?RedisService?{?

          ????/**
          ?????* 點贊。狀態(tài)為1
          ?????*?@param?likedUserId
          ?????*?@param?likedPostId
          ?????*/
          ??
          ????void?saveLiked2Redis(String likedUserId, String likedPostId);

          ????/**
          ?????* 取消點贊。將狀態(tài)改變?yōu)?
          ?????*?@param?likedUserId
          ?????*?@param?likedPostId
          ?????*/
          ??
          ????void?unlikeFromRedis(String likedUserId, String likedPostId);

          ????/**
          ?????* 從Redis中刪除一條點贊數(shù)據(jù)
          ?????*?@param?likedUserId
          ?????*?@param?likedPostId
          ?????*/
          ??
          ????void?deleteLikedFromRedis(String likedUserId, String likedPostId);

          ????/**
          ?????* 該用戶的點贊數(shù)加1
          ?????*?@param?likedUserId
          ?????*/
          ??
          ????void?incrementLikedCount(String likedUserId);

          ????/**
          ?????* 該用戶的點贊數(shù)減1
          ?????*?@param?likedUserId
          ?????*/
          ??
          ????void?decrementLikedCount(String likedUserId);
          ??
          ????/**
          ?????* 獲取Redis中存儲的所有點贊數(shù)據(jù)
          ?????*?@return??
          ?????*/
          ??
          ????List?getLikedDataFromRedis();

          ????/**
          ?????* 獲取Redis中存儲的所有點贊數(shù)量
          ?????*?@return??
          ?????*/
          ??
          ????List?getLikedCountFromRedis();

          }


          實現(xiàn)類 RedisServiceImpl.java


          @Service
          @Slf4j
          public?class?RedisServiceImpl?implements?RedisService {

          ????@Autowired
          ????RedisTemplate redisTemplate;

          ????@Autowired
          ????LikedService likedService;

          ????@Override
          ????public?void?saveLiked2Redis(String?likedUserId,?String?likedPostId) {
          ????????String?key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
          ????????redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.LIKE.getCode());
          ????}

          ????@Override
          ????public?void?unlikeFromRedis(String?likedUserId,?String?likedPostId) {
          ????????String?key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
          ????????redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.UNLIKE.getCode());
          ????}

          ????@Override
          ????public?void?deleteLikedFromRedis(String?likedUserId,?String?likedPostId) {
          ????????String?key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
          ????????redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);
          ????}

          ????@Override
          ????public?void?incrementLikedCount(String?likedUserId) {
          ????????redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, likedUserId,?1);
          ????}

          ????@Override
          ????public?void?decrementLikedCount(String?likedUserId) {
          ????????redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, likedUserId,?-1);
          ????}

          ????@Override
          ????public?List getLikedDataFromRedis() {
          ????????CursorObject
          ,?Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);
          ????????List list =?new?ArrayList<>();
          ????????while?(cursor.hasNext()){
          ????????????Map.Entry<Object,?Object> entry = cursor.next();
          ????????????String?key = (String) entry.getKey();
          ????????????//分離出 likedUserId,likedPostId
          ????????????String[] split = key.split("::");
          ????????????String?likedUserId = split[0];
          ????????????String?likedPostId = split[1];
          ????????????Integer value = (Integer) entry.getValue();

          ????????????//組裝成 UserLike 對象
          ????????????UserLike userLike =?new?UserLike(likedUserId, likedPostId, value);
          ????????????list.add(userLike);

          ????????????//存到 list 后從 Redis 中刪除
          ????????????redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);
          ????????}

          ????????return?list;
          ????}

          ????@Override
          ????public?List getLikedCountFromRedis() {
          ????????CursorObject,?Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, ScanOptions.NONE);
          ????????List list =?new?ArrayList<>();
          ????????while?(cursor.hasNext()){
          ????????????Map.Entry<Object,?Object> map = cursor.next();
          ????????????//將點贊數(shù)量存儲在 LikedCountDT
          ????????????String?key = (String)map.getKey();
          ????????????LikedCountDTO dto =?new?LikedCountDTO(key, (Integer) map.getValue());
          ????????????list.add(dto);
          ????????????//從Redis中刪除這條記錄
          ????????????redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, key);
          ????????}
          ????????return?list;
          ????}
          }


          用到的工具類和枚舉類


          RedisKeyUtils, 用于根據(jù)一定規(guī)則生成 key


          public?class?RedisKeyUtils?{

          ????//保存用戶點贊數(shù)據(jù)的key
          ????public?static?final?String MAP_KEY_USER_LIKED =?"MAP_USER_LIKED";
          ????//保存用戶被點贊數(shù)量的key
          ????public?static?final?String MAP_KEY_USER_LIKED_COUNT =?"MAP_USER_LIKED_COUNT";

          ????/**
          ?????* 拼接被點贊的用戶id和點贊的人的id作為key。格式 222222::333333
          ?????*?@param?likedUserId 被點贊的人id
          ?????*?@param?likedPostId 點贊的人的id
          ?????*?@return
          ?????*/

          ????public?static?String getLikedKey(String likedUserId, String likedPostId){
          ????????StringBuilder builder =?new?StringBuilder();
          ????????builder.append(likedUserId);
          ????????builder.append("::");
          ????????builder.append(likedPostId);
          ????????return?builder.toString();
          ????}
          }


          LikedStatusEnum 用戶點贊狀態(tài)的枚舉類


          /**
          ?* 用戶點贊的狀態(tài)
          ?*/

          @Getter
          public?enum?LikedStatusEnum {
          ????LIKE(1,?"點贊"),
          ????UNLIKE(0,?"取消點贊/未點贊"),
          ????;

          ????private?Integer code;

          ????private?String?msg;

          ????LikedStatusEnum(Integer code,?String?msg) {
          ????????this.code = code;
          ????????this.msg = msg;
          ????}
          }



          3

          數(shù)據(jù)庫設計


          數(shù)據(jù)庫表中至少要包含三個字段:被點贊用戶id,點贊用戶id,點贊狀態(tài)。再加上主鍵id,創(chuàng)建時間,修改時間就行了。


          建表語句


          create?table?`user_like`(
          ????`id`?int?not?null?auto_increment,
          ????`liked_user_id`?varchar(32)?not?null?comment?'被點贊的用戶id',
          ????`liked_post_id`?varchar(32)?not?null?comment?'點贊的用戶id',
          ????`status`?tinyint(1)?default?'1'?comment?'點贊狀態(tài),0取消,1點贊',
          ????`create_time`?timestamp?not?null?default?current_timestamp?comment?'創(chuàng)建時間',
          ??`update_time`?timestamp?not?null?default?current_timestamp?on?update?current_timestamp?comment?'修改時間',
          ????primary?key(`id`),
          ????INDEX?`liked_user_id`(`liked_user_id`),
          ????INDEX?`liked_post_id`(`liked_post_id`)
          )?comment?'用戶點贊表';


          對應的對象 UserLike


          /**
          ?* 用戶點贊表
          ?*/

          @Entity
          @Data
          public?class?UserLike {

          ????//主鍵id
          ????@Id
          ????@GeneratedValue(strategy = GenerationType.IDENTITY)
          ????private?Integer id;

          ????//被點贊的用戶的id
          ????private?String?likedUserId;

          ????//點贊的用戶的id
          ????private?String?likedPostId;

          ????//點贊的狀態(tài).默認未點贊
          ????private?Integer status = LikedStatusEnum.UNLIKE.getCode();

          ????public?UserLike() {
          ????}

          ????public?UserLike(String?likedUserId,?String?likedPostId, Integer status) {
          ????????this.likedUserId = likedUserId;
          ????????this.likedPostId = likedPostId;
          ????????this.status = status;
          ????}
          }



          4

          數(shù)據(jù)庫操作


          操作數(shù)據(jù)庫同樣封裝在接口中


          LikedService


          public?interface?LikedService?{
          ?
          ????/**
          ????* 保存點贊記錄
          ????*?@param?userLike
          ????*?@return
          ????*/

          ???UserLike save(UserLike userLike);
          ?
          ????/**
          ????* 批量保存或修改
          ????*?@param?list
          ????*/

          ???List saveAll(List?list);
          ?

          ???/**
          ????* 根據(jù)被點贊人的id查詢點贊列表(即查詢都誰給這個人點贊過)
          ????*?@param?likedUserId 被點贊人的id
          ????*?@param?pageable
          ????*?@return
          ????*/

          ???Page getLikedListByLikedUserId(String likedUserId, Pageable pageable);


          ????/**
          ????* 根據(jù)點贊人的id查詢點贊列表(即查詢這個人都給誰點贊過)
          ????*?@param?likedPostId
          ????*?@param?pageable
          ????*?@return
          ????*/

          ???Page getLikedListByLikedPostId(String likedPostId, Pageable pageable);
          ?
          ????/**
          ????* 通過被點贊人和點贊人id查詢是否存在點贊記錄
          ????*?@param?likedUserId
          ????*?@param?likedPostId
          ????*?@return
          ????*/

          ???UserLike getByLikedUserIdAndLikedPostId(String likedUserId, String likedPostId);
          ?
          ????/**
          ????* 將Redis里的點贊數(shù)據(jù)存入數(shù)據(jù)庫中
          ????*/

          ???void transLikedFromRedis2DB();
          ?
          ????/**
          ????* 將Redis中的點贊數(shù)量數(shù)據(jù)存入數(shù)據(jù)庫
          ????*/

          ???void transLikedCountFromRedis2DB();
          ?
          }


          LikedServiceImpl 實現(xiàn)類


          @Service
          @Slf4j
          public?class?LikedServiceImpl?implements?LikedService?{

          ????@Autowired
          ????UserLikeRepository likeRepository;

          ????@Autowired
          ????RedisService redisService;

          ????@Autowired
          ????UserService userService;

          ????@Override
          ????@Transactional
          ????public?UserLike?save(UserLike userLike)?{
          ????????return?likeRepository.save(userLike);
          ????}

          ????@Override
          ????@Transactional
          ????public?List?saveAll(List list)?{
          ????????return?likeRepository.saveAll(list);
          ????}

          ????@Override
          ????public?Page?getLikedListByLikedUserId(String likedUserId, Pageable pageable)?{
          ????????return?likeRepository.findByLikedUserIdAndStatus(likedUserId, LikedStatusEnum.LIKE.getCode(), pageable);
          ????}

          ????@Override
          ????public?Page?getLikedListByLikedPostId(String likedPostId, Pageable pageable)?{
          ????????return?likeRepository.findByLikedPostIdAndStatus(likedPostId, LikedStatusEnum.LIKE.getCode(), pageable);
          ????}

          ????@Override
          ????public?UserLike?getByLikedUserIdAndLikedPostId(String likedUserId, String likedPostId)?{
          ????????return?likeRepository.findByLikedUserIdAndLikedPostId(likedUserId, likedPostId);
          ????}

          ????@Override
          ????@Transactional
          ????public?void?transLikedFromRedis2DB()?{
          ????????List list = redisService.getLikedDataFromRedis();
          ????????for?(UserLike like : list) {
          ????????????UserLike ul = getByLikedUserIdAndLikedPostId(like.getLikedUserId(), like.getLikedPostId());
          ????????????if?(ul ==?null){
          ????????????????//沒有記錄,直接存入
          ????????????????save(like);
          ????????????}else{
          ????????????????//有記錄,需要更新
          ????????????????ul.setStatus(like.getStatus());
          ????????????????save(ul);
          ????????????}
          ????????}
          ????}

          ????@Override
          ????@Transactional
          ????public?void?transLikedCountFromRedis2DB()?{
          ????????List list = redisService.getLikedCountFromRedis();
          ????????for?(LikedCountDTO dto : list) {
          ????????????UserInfo user = userService.findById(dto.getId());
          ????????????//點贊數(shù)量屬于無關緊要的操作,出錯無需拋異常
          ????????????if?(user !=?null){
          ????????????????Integer likeNum = user.getLikeNum() + dto.getCount();
          ????????????????user.setLikeNum(likeNum);
          ????????????????//更新點贊數(shù)量
          ????????????????userService.updateInfo(user);
          ????????????}
          ????????}
          ????}
          }


          數(shù)據(jù)庫的操作就這些,主要還是增刪改查。


          ?

          5

          開啟定時任務持久化存儲到數(shù)據(jù)庫


          定時任務 Quartz 很強大,就用它了。


          Quartz 使用步驟:


          1.添加依賴


          <dependency>??
          ????<groupId>org.springframework.bootgroupId>
          ??
          ????<artifactId>spring-boot-starter-quartzartifactId>??
          dependency>


          2.編寫配置文件


          @Configuration
          public?class?QuartzConfig?{

          ????private?static?final?String LIKE_TASK_IDENTITY =?"LikeTaskQuartz";

          ????@Bean
          ????public?JobDetail?quartzDetail(){
          ????????return?JobBuilder.newJob(LikeTask.class).withIdentity(LIKE_TASK_IDENTITY).storeDurably().build();
          ????}

          ????@Bean
          ????public?Trigger?quartzTrigger(){
          ????????SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
          // .withIntervalInSeconds(10) //設置時間周期單位秒
          ????????????????.withIntervalInHours(2)?//兩個小時執(zhí)行一次
          ????????????????.repeatForever();
          ????????return?TriggerBuilder.newTrigger().forJob(quartzDetail())
          ????????????????.withIdentity(LIKE_TASK_IDENTITY)
          ????????????????.withSchedule(scheduleBuilder)
          ????????????????.build();
          ????}
          }


          3.編寫執(zhí)行任務的類繼承自 QuartzJobBean


          /**
          ?* 點贊的定時任務
          ?*/

          @Slf4j
          public?class?LikeTask?extends?QuartzJobBean?{

          ????@Autowired
          ????LikedService likedService;

          ????private?SimpleDateFormat sdf =?new?SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

          ????@Override
          ????protected?void?executeInternal(JobExecutionContext jobExecutionContext)?throws?JobExecutionException?{

          ????????log.info("LikeTask-------- {}", sdf.format(new?Date()));

          ????????//將 Redis 里的點贊信息同步到數(shù)據(jù)庫里
          ????????likedService.transLikedFromRedis2DB();
          ????????likedService.transLikedCountFromRedis2DB();
          ????}
          }


          ?在定時任務中直接調用 LikedService 封裝的方法完成數(shù)據(jù)同步。


          以上就是點贊功能的設計與實現(xiàn),不足之處還請各位大佬多多指教。


          另外,點贊/取消點贊 跟 點贊數(shù) +1/ -1 應該保證是原子操作,不然出現(xiàn)并發(fā)問題就會有兩條重復的點贊記錄,所以要給整個原子操作加鎖。


          同時需要在Spring Boot 的系統(tǒng)關閉鉤子函數(shù)中補充同步redis中點贊數(shù)據(jù)到mysql中的過程 . 不然有可能出現(xiàn)距離上一次同步1小時59分的時候服務器更新,把整整兩小時的點贊數(shù)據(jù)都給清空了。如果點贊設計到比較重要活動業(yè)務的話這就很尷尬了 。


          來源:blog.csdn.net/lsy0903/article/details/103949459



          往期推薦



          MongoDB和MySQL效率性能對比

          扔掉 Postman ,來試試神器ApiPost!

          ElasticSearch 讓人嘆為觀止的分布式系統(tǒng)架構設計

          代碼生成利器:IDEA 強大的 Live Templates

          裝上這個插件,讓自己的代碼更規(guī)范!

          MySQL 架構總覽、從查詢執(zhí)行流程到SQL 解析順序





          瀏覽 61
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  97人妻精品一区二区三区香蕉 | 欧美18禁| 看国产毛片 | 国产亚洲欧美精品久久久久久 | 我要看黄色特黄大片 |