前言
日常开发中经常使用redis,但每次项目基本只配置过一次,或者复制粘贴,对于从零搭建redis,其中的原理流程模糊不清,所以在这里做个梳理,日后回顾也很方便。
Redis的官网打开比较慢,而且全英文对英文不好的同学看起来不方便,这块推荐去redis中文网查看官方文档,官网地址:redis中文官方网站。
其实大部分内容官网都介绍的很清楚了,在这里只是对知识做一个梳理,方便自己日后查看。
下载及安装
redis分windows版本和linux版本,这块为了演示方便使用windows版本,在实际开发中,多数基于linux。安装也很简单,将下载的文件解压到一个文件夹即可。然后运行redis-server.exe来启动服务,再运行redis-cli.exe来启动客户端,相当于mysql的客户端一样,用来操作数据库。
基本操作
对于Redis的使用者来说, Redis作为Key-Value型的内存数据库, 其Value有多种类型.
- String(value为字符串)
- Hash(value为多个键值对的数据结构)
- List(value为链表)
- Set(value为没有重复元素的数据结构)
- ZSet(value为有序且没有重复元素的数据结构)
这些Value的类型, 只是"Redis的用户认为的, Value存储数据的方式". 而在具体实现上, 各个Type的Value到底如何存储, 这对于Redis的使用者来说是不公开的。底层的数据结构略显复杂,有兴趣的读者可以参考:此文章正在二次审核中,即将跳转到首页 - 程序员大本营和Redis详解(五)------ redis的五大数据类型实现原理 - YSOcean - 博客园
这两篇文章基本能把底层的数据结构摸透了。
操作String
存储string,通用格式如下,key和value分别代表键值,
EX
seconds – 设置键key的过期时间,单位时秒PX
milliseconds – 设置键key的过期时间,单位时毫秒NX
– 只有键key不存在的时候才会设置key的值XX
– 只有键key存在的时候才会设置key的值
注意: 由于SET
命令加上选项已经可以完全取代SETNX, SETEX, PSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。
set key value [EX seconds] [PX milliseconds] [NX|XX]
举例,代码如下:
set username 123 ex 2 nx
上边代码表示,存储username,值为123,2秒后过期会被删除,nx代表如果username已经存在,则存储失败。
使用get username 来查看存储的值。
使用del username可以删除该值,1代表删除成功,0代表删除失败。
使用type命令可以查看该值的类型,string\hash\list\set\sset
使用expire username 3来设置3秒后过期。
ttl命令用来查看key对应的值剩余存活时间。
使用exist命令返回1或0标识给定key的值是否存在,1代表存在,0代表不存在.
使用mset来实现多值存储,如mset a 1 b 2 c 3,
使用mget来获取多值,如mget a b c
使用incr来实现原子递增,如incr a,代表a++。
使用Incrby来实现执行值增加,如incrby a 100,代表a=a+100.
同理递减有decr和decrby。
注意type、del和exists是通用的命令(string\hash\list\set\sset),其他命令只针对string
操作list
因为list是一个队列,所以对应的操作命令有头添加和尾添加,也可以理解为左添加和右添加,对应的命令是lpush和rpush,
lpush将所有指定的值插入到存于 key 的列表的头部。如果 key 不存在,那么在进行 push 操作前会创建一个空列表。 如果 key 对应的值不是一个 list 的话,那么会返回一个错误,可以使用一个命令把多个元素 push 进入列表,只需在命令末尾加上多个指定的参数。元素是从最左端的到最右端的、一个接一个被插入到 list 的头部。 所以对于这个命令例子 LPUSH mylist a b c
,返回的列表是 c 为第一个元素, b 为第二个元素, a 为第三个元素。
执行命令后会返回list的长度。
rpush同理lpush,这里就不重复介绍。
通过lrange来查看list,
lrange返回存储在 key 的列表里指定范围内的元素。 start 和 end 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推。偏移量也可以是负数,表示偏移量是从list尾部开始计数。 例如, -1 表示列表的最后一个元素,-2 是倒数第二个,以此类推。
所以查看整个队列就可以写命令:lrange mylist 0 -1
pop,它从list中删除元素并同时返回删除的值。可以在左边lpop或rpop右边操作.
使用LTRIM把list从左边截取指定长度。如:ltrim mylist 0 3,执行操作后,mylist变为原来的0到3.
使用llen获取list的长度,
对于lpop和rpop还有阻塞版本,阻塞版本的出现是为了解决生产者消费者的问题,由于需求比较繁琐这里不做介绍参见blpop 命令 -- Redis中国用户组(CRUG)。
List的常用案例
正如你可以从上面的例子中猜到的,list可被用来实现聊天系统。还可以作为不同进程间传递消息的队列。关键是,你可以每次都以原先添加的顺序访问数据。这不需要任何SQL ORDER BY 操作,将会非常快,也会很容易扩展到百万级别元素的规模。
例如在评级系统中,比如社会化新闻网站 reddit.com,你可以把每个新提交的链接添加到一个list,用LRANGE可简单的对结果分页。
在博客引擎实现中,你可为每篇日志设置一个list,在该list中推入博客评论,等等
操作hash
使用hset来存储键值对,如:hset myhash username zhangsan.
使用hget来获取键值对,如:hget myhash username
使用hdel来删除键值对,如:hdel myhash
使用hmset来存储多个键值对,如:hmset myhash username zhangsan password lisi
使用hmget来获取多个键值对,如:hmget myhahs username password
使用hgetall来获取所有键值对,如:hgetall myhash
也可以像string一样,通过hincrby来增加某个键值对的值,如 hincrby myhash username 50.
操作set
我们知道set元素是唯一的,所以不允许存储重复的key。
使用sadd来存储值,如:sadd myset a b c d e
使用smembers来查看所有元素,如:smembers myset;
使用srem来删除值,如:srem myset a;
使用spop来随机获取一个值,并删除set中的该元素。如:mpop myset b。
操作sortedset
要求元素唯一并且有序,排序按照给定的score来排序。
使用zadd key score value 来存储,如:zadd myzset 1 a
使用zrange key start end (增加with scores可以显示出分数)来查看元素,如:zrange myzset 0 -1
使用zrem key value来删除元素,如:zrem myzset a
通用命令
keys * :查看当前redis所有的key;
type:查看key的类型
del:删除key。
flushDB:清空redis数据。
redis的持久化
Redis 提供了不同级别的持久化方式:
- RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
- 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
- 你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
- 最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:
rdb是默认持久化方式,aof需要修改配置文件开启,rdb可以通过命令save、bgsave来触发,正常关闭服务shutdown也会触发,在者就是默认触发方式:
save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存 save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存 save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存
当然如果你只是用Redis的缓存功能,不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。可以直接一个空字符串来实现停用:save ""。但是记住,在主从复制情况中,是无法关闭的。
redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
参考文章:Redis详解(六)------ RDB 持久化 - YSOcean - 博客园
aof持久化的策略,共有三种:
1)always:将aof_buf中的内容写入并同步到AOF文件中。
2)everysec:将aof_buf中内容写如AOF文件中,如果上次同步AOF文件的时间距离现在超过1s,那么再次同步AOF文件。这个操作是由一个线程专门负责执行的。
3)no:将aof_buf中的内容写入到AOF文件中,但并不对AOF同步,何时同步交给OS。
一般选用第二种,
AOF模式的一个问题是AOF文件可能会变得非常大。
通过分析AOF文件,往往发现里面有太多的重复和冗余数据,可以生成一个新的AOF文件来代替旧的AOF文件,这就是AOF重写。这个操作满足一定条件是,Redis会自动触发。一般生产环境一般要求在达到几个g或者几十个g才会重写,因为重写会影响redis性能。
参考文章:Redis持久化:AOF模式 - 简书
更多详细内容参见官网:REDIS persistence -- Redis中国用户组(CRUG)
redis的过期策略及清理算法
过期策略
redis采用的是定期删除+惰性删除策略。
为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.
定期删除+惰性删除是如何工作的呢?
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。本段落引用:【原创】分布式之redis复习精讲 - 孤独烟 - 博客园
内存淘汰机制
当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置。
以下的策略是可用的:
- noeviction(默认):返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
- allkeys-random: 回收随机的键使得新添加的数据有空间存放。
- volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。
一般的经验规则:
- 使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
- 使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
- 使用volatile-ttl:如果你想要通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期。
allkeys-lru 和 volatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。
为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。
以上引自官网,详细内容参见:REDIS lru-cache -- Redis中国用户组(CRUG)
redis的事务
redis也是支持事务的,所以这块可以了解下:REDIS 事务处理 -- Redis中国用户组(CRUG)
redis的哨兵机制
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
- 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
- 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
详细内容参见官网:REDIS sentinel-old -- Redis中国用户组(CRUG)
redis集群
参照官网已经写的很详细了,REDIS cluster-tutorial -- Redis中文资料站 -- Redis中国用户组(CRUG)
基于jedis操作
jedis相当与通过java代码来操作redis,类似通过jdbc操作mysql一样。
通过IDEA搭建简单的测试环境,导入两个依赖,分别是jedis依赖和juint测试依赖:
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13-beta-3</version>
</dependency>
</dependencies>
然后新建一个测试类,通过创建jedis对象就可以调用各种api了。
Jedis jedis=new Jedis();//构造方法中什么都不写,默认为localhost:6379
如果熟悉原生redis调用指令,那么使用jedis也很容易,比如获取string类型的某个key,原生命令是get(key)。jedis中只需要输入jedis.get就可以弹出get相关的方法,由于非常简单,这里就不对各个api做介绍了。
基于redistemplate操作
spring为了操作redis整合的redistemplate框架,
基于springboot整合redis
参考文章:SpringBoot整合Redis及Redis工具类撰写 - zeng1994 - 博客园,感谢该作者
使用IDEA,新建空的maven项目,然后导入pom如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ming</groupId>
<artifactId>redistemplate-test</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
</dependencies>
</project>
在java包下创建自己的包如com.test,然后在该目录下创建springboot启动类,如下:
package com.ming;
import org.springframework.boot.SpringApplication;
@org.springframework.boot.autoconfigure.SpringBootApplication
public class SpringBootApplication {
public static void main(String[] args){
SpringApplication.run(SpringBootApplication.class);
}
}
接着在resource目录下,创建application.yml配置文件:
spring:
redis:
database: 0 # Redis数据库索引(默认为0)
host: localhost #Redis服务器地址
port: 6379 #端口
password: #链接服务器的密码默认为空
jedis:
pool:
max-active: 200 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 #连接池最大阻塞时间(使用负值表示没有限制)
max-idle: 10 #连接池中的最大空闲连接
min-idle: 0 #连接池中的最小空闲连接
timeout: 1000 #连接超时时间
接着创建测试类,在test文件夹下创建跟java文件夹下同样的目录结构,然后创建测试类,如下:
package com.ming;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test(){
System.out.println("test");
redisTemplate.opsForValue().set("zhangsan","123");
}
}
因为在pom中已经导入了redis依赖,我们现在就可以使用redistemplate来测试程序了。比如实现存储一个字符串,如下:
@Test
public void test(){
redisTemplate.opsForValue().set("zhangsan","123");
}
查询一个字符串,如下:
@Test
public void getStringTest(){
Object object=redisTemplate.opsForValue().get("zhangsan");
System.out.println(object.toString());
}
以上代码没什么问题,都可以正常执行,那我们通过命令行查看下redis的数据,结果发现如下:
我们可以清楚看到,正常的key应该是zhangsan,结果显示很长一堆字符串。
其实这个是用于存储进去的是object导致的,这也是spring提供给我们的RedisTemplate的默认方式,默认方式的配置类参见org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,这里就不贴了,然后我们需要自己配置一个Redis的配置类,因为默认的配置类中有这样的设置@ConditionalOnMissingBean,就是说假如你没有自定义配置类时,系统会使用默认的,假如你自定义了配置类,就会使用你自定义的配置类。
配置类如下:
package com.ming.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.omg.PortableInterceptor.NON_EXISTENT;
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 {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<String,Object>();
redisTemplate.setConnectionFactory(factory);
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);
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
// key采用String的序列化方式,默认是object
redisTemplate.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
配置类写好了,为了以后调用方便,再封装一个redis工具类就over了。
工具类如下:
package com.ming.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
redistemplate的api,里边有分布式锁加强版的代码:RedisTemplate Api总结 - 简书
基于redisson实现分布式锁
参考文章:高性能分布式锁-redisson的使用 - webwangbao - 博客园
先用redis实现了简单的分布式锁,然后再用redisson实现分布式锁,并分析了redisson分布式锁实现的原理,参考文章:分布式锁之Redis实现 - 简书
下边这片文章详细的分析了redis分布式锁的常见错误实现方式,最后给了一个正确实现方式,很有参考价值;
参考文章:redis分布式锁,面试官请随便问,我都会_公众号-[程序员之道]-优快云博客_redis分布式锁面试
Redis复习精讲
【原创】分布式之redis复习精讲 - 孤独烟 - 博客园这篇文章写的很好,适合有redis基础,复习面试等使用。涉及到的问题如下:
1、为什么使用redis
2、使用redis有什么缺点
3、单线程的redis为什么这么快
4、redis的数据类型,以及每种数据类型的使用场景
5、redis的过期策略以及内存淘汰机制
6、redis和数据库双写一致性问题
7、如何应对缓存穿透和缓存雪崩问题
8、如何解决redis的并发竞争问题
Redis常见面试题精简版:Redis面试题(2020最新版)_ThinkWon的博客-优快云博客_redis面试题
Redis吊打面试官系列
这个系列,作者写的很不错,可以看他的系列文章:面了BAT,我总结了他们会问的Redis基础知识_敖丙-优快云博客_redis敖丙
面试官:Redis新版本开始引入多线程,谈谈你的看法
总结
本文内容多数摘自互联网,感谢以上文章的作者,在这里我只是对其汇总,做个总结。希望对大家有帮助。