手把手教你用Spring Cloud + Redis 是实现点赞功能,包教包会

花名提莫 2021-02-25 10:15:59 ⋅ 1150 阅读

原文:https://www.toutiao.com/i6791466166662464007

经常刷微博或者刷某条的人都会看见在页面的各个地方都有点赞的功能,like this:@

手把手教你用Spring Cloud + Redis 是实现点赞功能,包教包会

 

这玩意是怎么实现的呢?我来手把手教你实现,其实很简单(但是考虑的东西还是挺多的,麻雀虽小,五脏俱全)。

先给出代码地址吧:

代码出自开源项目CodeRiver;已经fork到我自己的github了,地址:

https://github.com/shezhanjun/coderiver

下面开始我们的实现分析;

点赞的动作是比较高频的写操作,那么我们我们不能来一次就往数据库中写一次,所以我们肯定需要将数据写到redis里面,然后通过定时任务将redis的数据同步到数据库中,减轻数据库的压力;

有了这个思路我们开始我们的实现,

先安装redis:

说下Docker 安装运行redis:

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

然后启动redis:

redis-server

首先想一下redis应该如何选数据存储格式和类型呢?

我们看一下:

Redis 可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。

下面来对这5种数据结构类型作简单的介绍

手把手教你用Spring Cloud + Redis 是实现点赞功能,包教包会

 

用 Redis 存储两种数据,一种是记录点赞人、被点赞人、点赞状态的数据,另一种是每个用户被点赞了多少次,做个简单的计数。

由于需要记录点赞人和被点赞人,还有点赞状态(点赞、取消点赞),还要固定时间间隔取出 Redis 中所有点赞数据,分析了下 Redis 数据格式中 Hash 最合适。

因为 Hash 里的数据都是存在一个键里,可以通过这个键很方便的把所有的点赞数据都取出。这个键里面的数据还可以存成键值对的形式,方便存入点赞人、被点赞人和点赞状态。

设点赞人的 id 为 likedPostId,被点赞人的 id 为 likedUserId ,点赞时状态为 1,取消点赞状态为 0。将点赞人 id 和被点赞人 id 作为键,两个 id 中间用 :: 隔开,点赞状态作为值。

所以如果用户点赞,存储的键为:likedUserId::likedPostId,对应的值为 1 。取消点赞,存储的键为:likedUserId::likedPostId,对应的值为 0 。取数据时把键用 :: 切开就得到了两个id,也很方便。

在可视化工具 RDM 中看到的是这样子

手把手教你用Spring Cloud + Redis 是实现点赞功能,包教包会

 

好了,现在可以开始使用spring boot和redis进行整合了:

1.在 pom.xml 中引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</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

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
 
import java.net.UnknownHostException;
 
 
@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 项目中的配置已经完成,可以愉快的使用了。

下面开始写操作redis的类:

将具体操作方法封装到了 RedisService 接口里

RedisService.java

import com.solo.coderiver.user.dataobject.UserLike;
import com.solo.coderiver.user.dto.LikedCountDTO;
 
import java.util.List;
 
public interface RedisService {
 
    /**
     * 点赞。状态为1
     * @param likedUserId
     * @param likedPostId
     */
    void saveLiked2Redis(String likedUserId, String likedPostId);
 
    /**
     * 取消点赞。将状态改变为0
     * @param likedUserId
     * @param likedPostId
     */
    void unlikeFromRedis(String likedUserId, String likedPostId);
 
    /**
     * 从Redis中删除一条点赞数据
     * @param likedUserId
     * @param likedPostId
     */
    void deleteLikedFromRedis(String likedUserId, String likedPostId);
 
    /**
     * 该用户的点赞数加1
     * @param likedUserId
     */
    void incrementLikedCount(String likedUserId);
 
    /**
     * 该用户的点赞数减1
     * @param likedUserId
     */
    void decrementLikedCount(String likedUserId);
 
    /**
     * 获取Redis中存储的所有点赞数据
     * @return
     */
    List<UserLike> getLikedDataFromRedis();
 
    /**
     * 获取Redis中存储的所有点赞数量
     * @return
     */
    List<LikedCountDTO> getLikedCountFromRedis();
 
}

实现类 RedisServiceImpl.java

import com.solo.coderiver.user.dataobject.UserLike;
import com.solo.coderiver.user.dto.LikedCountDTO;
import com.solo.coderiver.user.enums.LikedStatusEnum;
import com.solo.coderiver.user.service.LikedService;
import com.solo.coderiver.user.service.RedisService;
import com.solo.coderiver.user.utils.RedisKeyUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
 
@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<UserLike> getLikedDataFromRedis() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);
        List<UserLike> 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<LikedCountDTO> getLikedCountFromRedis() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, ScanOptions.NONE);
        List<LikedCountDTO> list = new ArrayList<>();
        while (cursor.hasNext()){
            Map.Entry<Object, Object> map = cursor.next();
            //将点赞数量存储在 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, 用于根据一定规则生成 key

public class RedisKeyUtils {
 
    //保存用户点赞数据的key
    public static final String MAP_KEY_USER_LIKED = "MAP_USER_LIKED";
    //保存用户被点赞数量的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 用户点赞状态的枚举类

package com.solo.coderiver.user.enums;
 
import lombok.Getter;
 
/**
 * 用户点赞的状态
 */
@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;
    }
}

下面我们开始做数据库那块了:

我们先设计数据库表:分析了一下数据库表中至少要包含三个字段:被点赞用户id,点赞用户id,点赞状态。再加上主键id,创建时间,修改时间就行了。

建表语句

数据库表中至少要包含三个字段:被点赞用户id,点赞用户id,点赞状态。再加上主键id,创建时间,修改时间就行了。

建表语句

对应的对象 UserLike


import com.solo.coderiver.user.enums.LikedStatusEnum;
import lombok.Data;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
 
/**
 * 用户点赞表
 */
@Entity
@Data
public class UserLike {
 
    //主键id
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
 
    //被点赞的用户的id
    private String likedUserId;
 
    //点赞的用户的id
    private String likedPostId;
 
    //点赞的状态.默认未点赞
    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;
    }
}

操作数据库同样封装在接口中

LikedService


import com.solo.coderiver.user.dataobject.UserLike;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
 
import java.util.List;
 
public interface LikedService {
 
    /**
     * 保存点赞记录
     * @param userLike
     * @return
     */
    UserLike save(UserLike userLike);
 
    /**
     * 批量保存或修改
     * @param list
     */
    List<UserLike> saveAll(List<UserLike> list);
 
 
    /**
     * 根据被点赞人的id查询点赞列表(即查询都谁给这个人点赞过)
     * @param likedUserId 被点赞人的id
     * @param pageable
     * @return
     */
    Page<UserLike> getLikedListByLikedUserId(String likedUserId, Pageable pageable);
 
    /**
     * 根据点赞人的id查询点赞列表(即查询这个人都给谁点赞过)
     * @param likedPostId
     * @param pageable
     * @return
     */
    Page<UserLike> getLikedListByLikedPostId(String likedPostId, Pageable pageable);
 
    /**
     * 通过被点赞人和点赞人id查询是否存在点赞记录
     * @param likedUserId
     * @param likedPostId
     * @return
     */
    UserLike getByLikedUserIdAndLikedPostId(String likedUserId, String likedPostId);
 
    /**
     * 将Redis里的点赞数据存入数据库中
     */
    void transLikedFromRedis2DB();
 
    /**
     * 将Redis中的点赞数量数据存入数据库
     */
    void transLikedCountFromRedis2DB();
 

}

LikedServiceImpl 实现类

import com.solo.coderiver.user.dataobject.UserInfo;
import com.solo.coderiver.user.dataobject.UserLike;
import com.solo.coderiver.user.dto.LikedCountDTO;
import com.solo.coderiver.user.enums.LikedStatusEnum;
import com.solo.coderiver.user.repository.UserLikeRepository;
import com.solo.coderiver.user.service.LikedService;
import com.solo.coderiver.user.service.RedisService;
import com.solo.coderiver.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.util.List;
 
@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<UserLike> saveAll(List<UserLike> list) {
        return likeRepository.saveAll(list);
    }
 
    @Override
    public Page<UserLike> getLikedListByLikedUserId(String likedUserId, Pageable pageable) {
        return likeRepository.findByLikedUserIdAndStatus(likedUserId, LikedStatusEnum.LIKE.getCode(), pageable);
    }
 
    @Override
    public Page<UserLike> 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<UserLike> 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<LikedCountDTO> list = redisService.getLikedCountFromRedis();
        for (LikedCountDTO dto : list) {
            UserInfo user = userService.findById(dto.getId());
            //点赞数量属于无关紧要的操作,出错无需抛异常
            if (user != null){
                Integer likeNum = user.getLikeNum() + dto.getCount();
                user.setLikeNum(likeNum);
                //更新点赞数量
                userService.updateInfo(user);
            }
        }
    }
}

数据库的也搞完了我们看看我们还差什么?是不是就差定时任务了,好的,加上

定时任务 Quartz 很强大,就用它了。

1.添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2.编写配置文件

package com.solo.coderiver.user.config;
 
import com.solo.coderiver.user.task.LikeTask;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@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)  //两个小时执行一次
                .repeatForever();
        return TriggerBuilder.newTrigger().forJob(quartzDetail())
                .withIdentity(LIKE_TASK_IDENTITY)
                .withSchedule(scheduleBuilder)
                .build();
    }

}

3.编写执行任务的类继承自 QuartzJobBean

package com.solo.coderiver.user.task;
 
import com.solo.coderiver.user.service.LikedService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateUtils;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
 
import java.text.SimpleDateFormat;
import java.util.Date;
 
/**
 * 点赞的定时任务
 */
@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 里的点赞信息同步到数据库里
        likedService.transLikedFromRedis2DB();
        likedService.transLikedCountFromRedis2DB();
    }
}

在定时任务中直接调用 LikedService 封装的方法完成数据同步。

以上就是点赞功能的设计与实现,不足之处还请各位大佬多多指教。

另外,点赞/取消点赞 跟 点赞数 +1/ -1 应该保证是原子操作 , 不然出现并发问题就会有两条重复的点赞记录 , 所以要给整个原子操作加锁 . 同时需要在Spring Boot 的系统关闭钩子函数中补充同步redis中点赞数据到mysql中的过程 . 不然有可能出现距离上一次同步1小时59分的时候服务器更新 , 把整整两小时的点赞数据都给清空了 . 如果点赞设计到比较重要活动业务的话这就很尴尬了 .

如有更好的实现方案欢迎在评论区交流,看到这里了,你是不是自己也会实现了!


全部评论: 0

    我有话说:

    Spring Boot 缓存之内嵌H2数据库

    本文手把手 怎么 H2 数据库当做缓存来使用增强系统性能

    Spring Cloud(Greenwich版)-03-编写高可Eureka Server(集群)

    前言 上一章Spring Cloud(Greenwich版)-02-服务注册与服务发现-Eureka入门,我们实现了将User和Goods微服务都注册到了Eureka上,那么在生产环境中为了达到高

    构建数据集成和实时数据处理流水线工具Spring Cloud Data Flow

    Spring Cloud Data Flow 基于原生云对 Spring XD 的重新设计

    Spring Cloud(Greenwich版)-06-Spring Cloud集成Feign

    Spring Cloud系列教程第6节-Spring Cloud集成Feign

    Spring Cloud快速搭建开放平台解决方案

    基于Spring Cloud开放平台解决方案 SOP

    mongoHelper 0.3.9 发布,spring-data-mongodb 增强工具,简化 CRUD 操作

    mongoHelper 基于 spring-data-mongodb 的增强工具,简化 CRUD 操作,提供类 jpa 的数据库操作。 传统关系型数据库及围绕它们构建的 orm 在项目开发中有很

    前端实战篇:通过JS抓取城市所有站点与线路

    做公交线路定位,木有数据怎么办?网上抓去~ 手把手通过JS实现站点线路数据抓取

    深入理解 Spring Cloud 核心组件 底层原理

    搜云库技术团队:https://mp.weixin.qq.com/s/_KCBWTPvFQ2QsM4P8-gqTQ 之前一直在看 Spring Cloud 及微服务架构 对 Spring Cloud

    Spring Cloud Alibaba 发布毕业后的首个版本

    方剑,花名洛夜,GitHub ID @fangjian0423,开源爱好者,阿里巴巴高级开发工程师,阿里云产品 EDAS 开发,Spring Cloud Alibaba 开源项目负责人。

    码云推荐:一个优秀的分布式spring boot/Spring Cloud API限流框架,特别适合微服务架构

    一个优秀的分布式spring boot/Spring Cloud API限流框架,特别适合微服务架构.

    今日头条 iOS 安装大小优化 - 新阶段、新实践

    前言 今日头条 iOS 端从 2016 年起就关注到了安装大小的问题,并启动了大小优化。2017 年,我们将当时的经验发表为技术文章 《干货|今日头条iOS端安装大小优化—思路与实践

    Redis系列四 锁

      本文目标 1. 熟悉乐观锁ABA概念 2. 理解掌握redis事务以及watch回滚; 3. 实战redis锁 乐观锁 乐观锁一种不阻塞其他线程并发的机制,它不使用数据库的

    「转载」Redis 到底怎么实现“附近的人”这个功能的?

    Redis,结合其有序队列zset以及geohash编码,实现了空间搜索功能,且拥有极高的运行效率。

    Spring Boot 开发利器

    工欲善其事,必先利其器每次创建新项目都要找各种依赖的很烦?今天给大家介绍下 Spring 官方提供的 Online 项目脚手架

    Redis系列六 Lua

      本文目标 学习lua基本语法 能够采用redis+lua lua 基本语法 Lua 一种轻量小巧的脚本语言,标准C语言编写并以源代码形式开放, 其设计目的为了嵌入应用

    Redis系列七 Debug Lua

      调试redis+lua 学了lua的基本语法,了解了redis+lua的配套法,但是却不知道怎么断点调试。学就学全面, 官网中有dubug相关说明。地址:Redis Lua

    Jedis 3.5.0 发布,Redis 的 Java 客户端开发

    Jedis 3.5.0 发布了。Jedis Redis 官方推荐的面向 Java 的操作 Redis 客户端,与新版本的 Redis 完全兼容。 本次更新内容包括: 增强功能 通过 COUNT