目录

客户端 Jedis、Redisson、Lettuce 对比

RedisTemplate 常用方法

RedisTemplate 序列化方式

分布式锁需求分析 与 主流实现方式

基于 Redis 实现分布式锁

其它常用操作与方法


客户端 Jedis、Redisson、Lettuce 对比

1、三个都提供了基于 Redis 操作的 Java API,只是封装程度,具体实现稍有不同。

Jedis

Java 实现的客户端。支持基本的数据类型如:String、Hash、List、Set、Sorted Set。

使用阻塞的 I/O,方法调用同步,程序流需要等到 socket 处理完 I/O 才能执行,不支持异步操作。Jedis 客户端实例不是线程安全的,需要通过连接池来使用 Jedis。

Redisson 分布式锁,分布式集合,可通过 Redis 支持延迟队列。
Lettuce

用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

基于 Netty 框架的事件驱动的通信层,其方法调用是异步的。Lettuce 的 API 是线程安全的,所以可以操作单个 Lettuce 连接来完成各种操作。

RedisTemplate 常用方法

org.springframework.data.redis.core.RedisTemplate 常用方法(本文环境 Spring Boot 2.1.3):

方法 描述
Boolean expire(K key, final long timeout, final TimeUnit unit)

为指定的 key 指定缓存失效时间。时间一到 key 会被移除。key 不存在时,不影响。

注意:如果 key 后续被重新设置值,比如 set key value,则 key 过期时间失效,需要重新设置。

Boolean expireAt(K key, final Date date) 设置 key 失效日期。注意:如果 key 后续被重新设置值,比如 set key value,则 key 过期时间失效,需要重新设置。
Long getExpire(K key) 获取 key 的剩余过期时间。 -1 表示永久有效。-2 表示 key 不存在。
Long getExpire(K key, final TimeUnit timeUnit) 获取 key 的剩余过期时间,并换算成指定的时间单位 
Boolean hasKey(K key) 判断 key 是否存在
Boolean delete(K key) 删除指定的 key
Long delete(Collection keys) 删除多个 key
RedisSerializer<?> getDefaultSerializer() 获取默认的序列化方式。RedisTemplate 是 JdkSerializationRedisSerializer;StringRedisTemplate 是 StringRedisSerializer
Set keys(K pattern) 获取整个库下符合指定正则的所有 key,如 keys(*) 获取所有 key
Boolean move(K key, final int dbIndex) 将 key 从当前库移动目标库 dbIndex
ClusterOperations<K, V> opsForCluster() 获取 ClusterOperations 用于操作集群
GeoOperations<K, V> opsForGeo() 获取 GeoOperations 用于操作地图
<HK, HV> HashOperations<K, HK, HV> opsForHash() 获取 HashOperations 用于操作 hsha 数据类型
ListOperations<K, V> opsForList() 获取 ListOperations 用于操作 list 类型
SetOperations<K, V> opsForSet() 获取 SetOperations 用于操作无序集合
ValueOperations<K, V> opsForValue() 获取 ValueOperations 用于操作 String类型
ZSetOperations<K, V> opsForZSet() 获取 ValueOperations 用于操作有序集合
rename(K oldKey, K newKey) 为 oldKey 进行重命名
DataType type(K key) 查询缓存 key 的类型,DataType 是一个枚举,code(name) 可选值如下:none, string, list, set, zset hash
none 表示 key 不存在,或者类型不确定
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisStuWebApplicationTests {//注入 RedisTemplate 或者 StringRedisTemplate 其中一个即可,前者是后者的父类。它们默认已经全部在容器种了.//org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration 中已经自动将 RedisTemplate 添加到了容器中,直接获取使用即可.@Resourceprivate RedisTemplate redisTemplate;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Testpublic void test1() throws InterruptedException {ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();opsForValue.set("wmx_name", "Zhang San");//设置字符串值System.out.println(opsForValue.get("wmx_name"));//Zhang SanstringRedisTemplate.expire("wmx_name", 60, TimeUnit.SECONDS);//设置 key 失效时间
//        for (int i = 0; i < 15; i++) {
//            System.out.println(stringRedisTemplate.getExpire("wmx_name"));
//            System.out.println(stringRedisTemplate.getExpire("wmx_name", TimeUnit.SECONDS));
//            Thread.sleep(1000);
//        }//判断是否含有指定的 keySystem.out.println(stringRedisTemplate.hasKey("wmx_name") + ", " + stringRedisTemplate.hasKey("wmx_name_2"));//true, falseopsForValue.set("age", "33");opsForValue.set("address", "长沙");stringRedisTemplate.delete("age");//删除 keyDate stopDate = new Date();stopDate.setTime(System.currentTimeMillis() + (60 * 1000));stringRedisTemplate.expireAt("address", stopDate);//设置 key 1 分钟后失效List<RedisClientInfo> clientList = stringRedisTemplate.getClientList();System.out.println(clientList);System.out.println(redisTemplate.getDefaultSerializer());//获取默认序列化方式Set<String> keys = stringRedisTemplate.keys("*");//获取当前库下所有的 keySystem.out.println("keys=" + keys);Boolean move = stringRedisTemplate.move("address", 2);//将 address 移动 2 号数据库System.out.println("move=" + move);opsForValue.set("info", "描述");System.out.println(opsForValue.get("info"));stringRedisTemplate.rename("info", "summary");//对 key 进行重命名}
}

演示源码:src/test/java/com/wmx/wmxredis/WmxRedisApplicationTests.java · 汪少棠/wmx-redis - Gitee.com

RedisTemplate 序列化方式

1、StringRedisTemplate 继承 RedisTemplate,主要区别就是前者默认使用 StringRedisSerializer 序列化 String,后者默认使用 JdkSerializationRedisSerializer 序列化对象。

2、RedisTemplate<K, V> 可以用来存储对象,如 Map、List 、Set、POJO 等,但对象需要实现 Serializable 序列化接口。此种方式序列化时,以二进制数组方式存储,内容没有可读性。

Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", 1001);
dataMap.put("name", "张三");
HashOperations opsForHash = redisTemplate.opsForHash();
opsForHash.putAll("map_1",dataMap);
redisTemplate.expire("map_1",60,TimeUnit.SECONDS);
Map map_11 = opsForHash.entries("map_1");
System.out.println(map_11);//{name=张三, id=1001}

3、StringRedisTemplate 专门用来存储字符串,StringRedisTemplate extends RedisTemplate<String, String>。序列化接口 org.springframework.data.redis.serializer.RedisSerializer 的实现类如下:

序列化方式 描述
FastJsonRedisSerializer 采用 com.alibaba.fastjson 进行序列化与反序列化 ,提供了 (Class type) 参数的构造器,需要传入对象类型。
GenericFastJsonRedisSerializer 提供了基本的 GenericFastJsonRedisSerializer() 构造器。
GenericJackson2JsonRedisSerializer 与 Jackson2JsonRedisSerializer 功能差不多,构造函数可以指定 Class,默认为 String.
GenericToStringSerializer 需要调用者传一个对象到字符串互转的 Converter(相当于转换为字符串的操作交给转换器去做)
Jackson2JsonRedisSerializer 采用 com.fasterxml.jackson 进行序列化与反序列化。优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。构造函数必须传入 Class 类型。
JdkSerializationRedisSerializer RedisTemplate 默认的序列化方式。要求存储的对象必须实现java.io.Serializable接口。序列化后的结果非常庞大。存储的为二进制数据,对开发者不友好。
OxmSerializer 以 xml 格式存储,解析起来比较复杂,效率也比较低
StringRedisSerializer StringRedisTemplate 默认的字符串序列化方式。key 和 value 都会采用此方式进行序列化,是被推荐使用的,对开发者友好,轻量级,效率也比较高。此时只能是 String,不能是其它对象,否则报错。

4、本文环境 Spring Boot 2.1.3 + Java JDK 1.8,下面以指定 Jackson2JsonRedisSerializer 序列化方式为例:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {//自定义 RedisTemplate 序列化方式@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();//创建 RedisTemplate,key 和 value 都采用了 Object 类型redisTemplate.setConnectionFactory(redisConnectionFactory);//绑定 RedisConnectionFactory//创建 Jackson2JsonRedisSerializer 序列方式,对象类型使用 Object 类型,Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);//设置一下 jackJson 的 ObjectMapper 对象参数// 设置 RedisTemplate 序列化规则。因为 key 通常是普通的字符串,所以使用 StringRedisSerializer 即可。// 而 value 是对象时,才需要使用序列化与反序列化redisTemplate.setKeySerializer(new StringRedisSerializer());// key 序列化规则redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value 序列化规则redisTemplate.setHashKeySerializer(new StringRedisSerializer());// hash key 序列化规则redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// hash value 序列化规则redisTemplate.afterPropertiesSet();//属性设置后操作return redisTemplate;//返回设置好的 RedisTemplate}
}

在线演示源码:src/main/java/com/wmx/wmxredis/config/RedisConfig.java · 汪少棠/wmx-redis - Gitee.com

5、RedisTemplate 使用就很简单了,保存数据与读取数据时,直接操作对象即可,会自动进行序列化与反序列化:

import com.wmx.wmxredis.beans.Person;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** RedisTemplate 操作 redis** @author wangMaoXiong*/
@RestController
public class RedisController {/*** 从容器中获取 RedisTemplate 实例*/@Resourceprivate RedisTemplate redisTemplate;/*** 保存数据,设置缓存:http://localhost:8080/redis/save?id=1000&name=张三** @param person* @return*/@GetMapping("redis/save")public String redisCache(Person person) {ValueOperations opsForValue = redisTemplate.opsForValue();ListOperations opsForList = redisTemplate.opsForList();HashOperations opsForHash = redisTemplate.opsForHash();person.setBirthday(new Date());//设置缓存。演示三种数据类型:字符串、列表、hashopsForValue.set(RedisController.class.getName() + "_string" + person.getId(), person);opsForList.rightPushAll(RedisController.class.getName() + "_list" + person.getId(), person, person);opsForHash.put(RedisController.class.getName() + "_map", "person" + person.getId(), person);//设置 key 失效时间redisTemplate.expire(RedisController.class.getName() + "_string" + person.getId(), 60, TimeUnit.SECONDS);redisTemplate.expire(RedisController.class.getName() + "_list" + person.getId(), 60, TimeUnit.SECONDS);redisTemplate.expire(RedisController.class.getName() + "_map", 60, TimeUnit.SECONDS);return "缓存成功.";}/*** 查询缓存:http://localhost:8080/redis/get?personId=1000** @param personId* @return*/@GetMapping("redis/get")public List<Person> getRedisCache(@RequestParam Integer personId) {//1、演示三种数据类型:字符串、列表、hashValueOperations opsForValue = redisTemplate.opsForValue();ListOperations opsForList = redisTemplate.opsForList();HashOperations opsForHash = redisTemplate.opsForHash();//2、读取缓存,如果 key 不存在,则返回为 null.Person person = (Person) opsForValue.get(RedisController.class.getName() + "_string" + personId);List<Person> personList = opsForList.range(RedisController.class.getName() + "_list" + personId, 0, -1);Person person1 = (Person) opsForHash.get(RedisController.class.getName() + "_map", "person" + personId);System.out.println("person=" + person);System.out.println("personList=" + personList);System.out.println("person1=" + person1);return personList;}
}

在线演示源码:

src/main/java/com/wmx/wmxredis/controller/RedisController.java · 汪少棠/wmx-redis - Gitee.com

src/main/java/com/wmx/wmxredis/beans/Person.java · 汪少棠/wmx-redis - Gitee.com

温馨提示!

关于 RedisTemplate 的序列化,实际生产中遇到一次需要将数据库查出来的一个 List 对象放到 Redis 中缓存,当时数据量达到 20 万行,结果 put 到缓存的时间长达 6、7 秒,然后当使用 alibaba 的 fastjson 手动先将 List 对象序列化为字符串,然后作为普通的字符串使用 ValueOperations set 到缓存,此时 set 的时间降到了 100 毫秒内,而 fastjson 序列化对象同样非常之快。取值时,同样将取出的字符串使用 fastjosn 手动反序列化成 List 即可。

分布式锁需求分析 与 主流实现方式

分布式锁需求分析 与 主流实现方式

基于 Redis 实现分布式锁

1、分布式索最常见的一种方案就是使用 Redis 做分布式锁,使用 Redis 做分布式锁的思路是:在 redis 中设置一个值表示加了锁,然后释放锁的时候就把这个 key 删除。

// 获取锁
// NX是指如果key不存在就成功,key存在返回false,PX可以指定过期时间
SET anyLock unique_value NX PX 30000// 释放锁:通过执行一段lua脚本
// 释放锁涉及到两条指令,这两条指令不是原子性的
// 需要用到redis的lua脚本支持特性,redis执行lua脚本是原子性的
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

2、一定要用 "SET key value NX PX milliseconds" 命令:否则先设置了值,再设置过期时间,这个不是原子性操作,有可能在设置过期时间之
前宕机,会造成死锁(key永久存在)

3、value 要具有唯一性:在解锁的时候,需要验证 value 是和加锁的一致才删除 key。
这是避免了一种情况:假设A获取了锁,过期时间30s,此时35s之后,锁已经自动释放了,A
去释放锁,但是此时可能B获取了锁。A客户端就不能删除B的锁了。

4、下面通过 RedisTemplate 进行实现:

    /*** http://localhost:8080/redis/execute?key=wwww&value=1rui* <p>* Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit)* Boolean setIfAbsent(K key, V value, Duration timeout)* * 1、key 不存在时进行设值,返回 true; 否则 key 存在时,不进行设值,返回 false.* * 2、此方法相当于先设置 key,然后设置 key 的过期时间,它的操作是原子性的,是事务安全的。* * 3、相当于:SET anyLock unique_value NX PX 30000,NX是指如果key不存在就成功,key存在返回false,PX可以指定过期时间* T execute(RedisScript<T> script, List<K> keys, Object... args):执行给定的脚本。* * 1、多个操作使用 lau 脚本统一执行是事务安全的,具有原子性* * 2、脚本中 KEYS[x] 是对 keys 进去取值,ARGV[x] 是对 args 进行取值,索引从1开始.* * 3、返回脚本执行的结果,类型与 RedisScript 的类型一致。** @param key* @param value* @return* @throws InterruptedException*/@GetMapping("redis/execute")public Map<String, Object> execute(@RequestParam String key, @RequestParam String value) throws InterruptedException {Map<String, Object> returnMap = new HashMap<>();Boolean ifAbsent = false;try {ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(60));if (!ifAbsent) {returnMap.put("code", 500);returnMap.put("msg", "程序正在处理中,请稍后再试!");return returnMap;}TimeUnit.SECONDS.sleep(10);//休眠 10 秒,模拟执行业务代码System.out.println("执行业务代码.");} catch (Exception e) {e.printStackTrace();} finally {if (ifAbsent){
//接口执行完毕后删除 key,key 不存在时 execute 方法返回 0
//此种脚本删除的方式在 redis 集群部署时会报错,实际上直接使用  redisTemplate.delete(cacheKey) 也是可以的String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";RedisScript<Long> redisScript = RedisScript.of(script, Long.class);
// 返回删除key的个数,未删除成功时,返回 0Object execute = redisTemplate.execute(redisScript, Arrays.asList(key), value);returnMap.put("data", execute);}}returnMap.put("code", 200);returnMap.put("msg", "seccess");return returnMap;}

在线演示源码:src/main/java/com/wmx/wmxredis/controller/RedisController.java · 汪少棠/wmx-redis - Gitee.com

并发压测:src/main/java/com/wmx/hb/controller/FlowConfigController.java · 汪少棠/hb - Gitee.com

其它常用操作与方法

redisTemplate.getConnectionFactory().getConnection().flushAll(); //清空 redis 所有数据库(all databases)中的所有数据(all keys)
redisTemplate.getConnectionFactory().getConnection().flushDb(); //清空 redis 当前连接的数据库(selected database)中的所有数据(all keys)

RedisTemplate 常用方法、序列化方式、基于 Redis 实现分布式锁相关推荐

  1. redis使用sysc超时_基于redis的分布式锁实现

    随着业务越来越复杂,应用服务都会朝着分布式.集群方向部署,而分布式CAP原则告诉我们,Consistency(一致性). Availability(可用性).Partition tolerance(分 ...

  2. Java基于redis实现分布式锁(SpringBoot)

    前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...

  3. 基于 Redis 实现分布式锁思考

    以下文章来源方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/xuan_lu/article/details/111600302 分布式锁 基于redis实 ...

  4. nx set 怎么实现的原子性_基于Redis的分布式锁实现

    前言 本篇文章主要介绍基于Redis的分布式锁实现到底是怎么一回事,其中参考了许多大佬写的文章,算是对分布式锁做一个总结 分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问 ...

  5. 基于Redis的分布式锁和Redlock算法

    来自:后端技术指南针 1 前言 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手 ...

  6. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  7. 基于 Redis 的分布式锁到底安全吗?

    [完整版] 网上有关Redis分布式锁的文章可谓多如牛毛了,不信的话你可以拿关键词"Redis 分布式锁"随便到哪个搜索引擎上去搜索一下就知道了.这些文章的思路大体相近,给出的实现 ...

  8. js 拉勾网效果_Node.js 中实践基于 Redis 的分布式锁实现

    在一些分布式环境下.多线程并发编程中,如果对同一资源进行读写操作,避免不了的一个就是资源竞争问题,通过引入分布式锁这一概念,可以解决数据一致性问题. 作者简介:五月君,Nodejs Developer ...

  9. 基于Redis的分布式锁到底安全吗(上)?

    网上有关Redis分布式锁的文章可谓多如牛毛了,不信的话你可以拿关键词"Redis 分布式锁"随便到哪个搜索引擎上去搜索一下就知道了.这些文章的思路大体相近,给出的实现算法也看似合 ...

  10. 基于Redis的分布式锁实现

    本文转自 一.分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的 ...

最新文章

  1. Configuring the Java Virtual Manager (JVM)
  2. oracle转mysql数据库
  3. 数据结构-Hash总结(一):理论学习篇
  4. 面试官:什么是JDK什么是JRE?服务器可以只安装JRE吗?
  5. linux如何运行sh监控文件夹,如何使用Shell进行文件监控?
  6. 桩筏有限元中的弹性板计算_PKPM2010年11月结构技术问题汇总
  7. 转js 将json字符串转换为json对象的方法解析
  8. JAVA后端开发的一些工作经验
  9. 怎么查看当前系统jdk版本
  10. 必备效率:一个让你不再加班的小白编程课
  11. java contains 大小写_用.contains方法忽略大小写的选项?
  12. Control Egress TCP Traffic
  13. 上传智能车竞赛比赛成绩
  14. 备忘_命令行查看电池损耗
  15. 2018年python薪资_最好的Python:2017和2018年至今我最喜欢的文章集
  16. MTK平台Metadata的加载(4)—Q版本后
  17. 现在为什么很多企业都在使用终端安全管理系统?有什么好处...
  18. 语音识别基本原理学习
  19. 常用软件性能测试工具
  20. Java中String接受的最大字符串的长度

热门文章

  1. 总结htmlfile:未知的运行时错误
  2. 终结VC2005分发包版本问题
  3. TSP(旅行者问题)——动态规划详解
  4. 拓端tecdat|用R语言模拟随机服务排队系统
  5. Android入门笔记06
  6. 【leetcode】栈(python)
  7. oracle怎么查导入导出记录,Oracle实验记录——数据的导入和导出
  8. 大疆DJI Thermal SDK Linux编译
  9. pyqtSignal信号和槽
  10. 浅析JDK,JRE,JVM的区别