这里是楼主自学redis总结的一些学习笔记,结合了视频还有很多文档,已经有点记不清redis命令是看的哪位博主的博客做的笔记了,如有冒犯请联系删除。
本文重点不是redis的命令(实际开发中压根用不到,滑稽.png),如果需要了解redis命令的同学请移步到:redis命令大全
桀桀桀,相信大家已经对redis命令有了初步的认识,楼主现在会带领大家再过一遍,一定要跟着写练习代码,楼主已经上班了,实际经验告诉我:不要小看这种简单的代码,实际上他也是没什么luan用的...冲!!
redis的下载和安装博主给你们推荐一个教程:redis下载和安装教程
目录
Redis是什么?
概念
Redis(Remote Dictionary Server ),即远程字典服务 ! 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 免费和开源!是当下最热门的 NoSQL 技术之一!也被人们称之为结构化数据库!
redis的基本操作
-
ping
查看当前连接是否正常,正常返回PONG
-
keys
查看当前库里所有的key
-
flushall
清空所有库的内容
-
expire
给数据设置过期时间(单位:秒)
-
所有操作查询的地方
redis的五大基本类型
String类型
-
添加、查询、追加、获取长度、判断是否存在
set key value
get key
append key value(给key后面追加value的值)
exists key(判断是否存在,存在返回1)
-
自增、自减操作
incr key(指定key的数据自增1,返回结果 相当于java中 i++)
decr key(指定key为的数据自减1,返回结果 相当于java中 i--)
-
截取、替换
getrange key index1 index2 (截取字符串,相当于java中的subString,下标从0开始,不会改变原有数据)
set range key index value(替换字符串,从index的地方开始替换,包括index)
List类型
在Redis的List中,允许从左右两端操作列表(请将栈想像为横着的)


-
左插入,查询集合,右插入
lpush key(list的名字) value...(可以同时插入多个value,进去的元素在头部)
lrange key start end (查询列表,从start下标开始,end结束)
rpush key value ... (右插入,跟lpush相反,这里添加进去元素是在尾部!)
-
左移除,右移除操作
lpop
rpop
-
查询指定下标元素,获取集合长度,更新操作,插入操作
lindex key index
llen key(获取长度)
lset list index value(更新list集合中下标为index的元素为value)
linsert list before item value(在集合中的‘item’元素之前’ 加上一个元素)
有序集合zset
-
zadd(添加)、zrange(查询)、zrangebyscore(排序小-大)、zrevrange(排序大-小)、zrangebyscore withscores(查询所有值包含key)操作
127.0.0.1:6379> zadd myzset 1 one 2 two 3 three #添加zset值,可多个 (integer) 3 127.0.0.1:6379> ZRANGE myzset 0 -1 #查询所有的值 1) "one" 2) "two" 3) "three" #-inf 负无穷 +inf 正无穷 127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf #将zset的值根据key来从小到大排序并输出 1) "one" 2) "two" 3) "three" 127.0.0.1:6379> ZRANGEBYSCORE myzset 0 1 #只查询key<=1的值并且排序从小到大 1) "one" 127.0.0.1:6379> ZREVRANGE myzset 1 -1 #从大到小排序输出 1) "two" 2) "one" 127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf withscores #查询指定zset的所有值,包含序号的值 1) "one" 2) "1" 3) "two" 4) "2" 5) "three" 6) "3" -
zrem(移除元素)、zcard(查看元素个数)、zcount(查询指定区间内的元素个数)操作
127.0.0.1:6379> zadd myset 1 v1 2 v2 3 v3 4 v4 (integer) 4 127.0.0.1:6379> ZRANGE myset 0 -1 1) "v1" 2) "v2" 3) "v3" 4) "v4" 127.0.0.1:6379> zrem myset v3 #移除指定的元素,可多个 (integer) 1 127.0.0.1:6379> ZRANGE myset 0 -1 1) "v1" 2) "v2" 3) "v4" 127.0.0.1:6379> zcard myset #查看zset的元素个数,相当于长度,size。 (integer) 3 127.0.0.1:6379> zcount myset 0 100 #查询指定区间内的元素个数 (integer) 3 127.0.0.1:6379> zcount myset 0 2 #查询指定区间内的元素个数 (integer) 2
Set(集合)元素唯一不重复
-
sadd(添加)、smembers(查看所有元素)、sismember(判断是否存在)、scard(查看长度)、srem(移除指定元素)操作
#set中所有的元素都是唯一的不重复的! 127.0.0.1:6379> sadd set1 ding da mian tiao #添加set集合(可批量可单个,写法一致,不再赘述) (integer) 4 127.0.0.1:6379> SMEMBERS set1 #查看set中所有元素 1) "mian" 2) "da" 3) "tiao" 4) "ding" 127.0.0.1:6379> SISMEMBER set1 da #判断某个值在不在set中,在返回1 (integer) 1 127.0.0.1:6379> SISMEMBER set1 da1 #不在返回0 (integer) 0 127.0.0.1:6379> SCARD set1 #查看集合的长度,相当于size、length (integer) 4 127.0.0.1:6379> srem set1 da #移除set中指定的元素 (integer) 1 127.0.0.1:6379> SMEMBERS set1 #移除成功 1) "mian" 2) "tiao" 3) "ding" -
srandmember(抽随机)操作
127.0.0.1:6379> sadd myset 1 2 3 4 5 6 7 #在set中添加7个元素 (integer) 7 127.0.0.1:6379> SMEMBERS myset 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 6) "6" 7) "7" 127.0.0.1:6379> SRANDMEMBER myset 1 #随机抽取myset中1个元素返回 1) "4" 127.0.0.1:6379> SRANDMEMBER myset 1 #随机抽取myset中1个元素返回 1) "1" 127.0.0.1:6379> SRANDMEMBER myset 1 #随机抽取myset中1个元素返回 1) "5" 127.0.0.1:6379> SRANDMEMBER myset #不填后参数,默认抽1个值,但是下面返回不会带序号值 "3" 127.0.0.1:6379> SRANDMEMBER myset 3 #随机抽取myset中3个元素返回 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> SRANDMEMBER myset 3 #随机抽取myset中3个元素返回 1) "6" 2) "3" 3) "5" -
sdiff(差集)、sinter(交集)、sunion(并集)操作
127.0.0.1:6379> sadd myset1 1 2 3 4 5 (integer) 5 127.0.0.1:6379> sadd myset2 3 4 5 6 7 (integer) 5 127.0.0.1:6379> SMEMBERS myset1 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 127.0.0.1:6379> SMEMBERS myset2 1) "3" 2) "4" 3) "5" 4) "6" 5) "7" 127.0.0.1:6379> SDIFF myset1 myset2 #查询指定的set之间的差集,可以是多个set 1) "1" 2) "2" 127.0.0.1:6379> SINTER myset1 myset2 #查询指定的set之间的交集,可以是多个set 1) "3" 2) "4" 3) "5" 127.0.0.1:6379> sunion myset1 myset2 #查询指定的set之间的并集,可以是多个set 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 6) "6" 7) "7"
Hash
-
hset(添加hash)、hget(查询)、hgetall(查询所有)、hdel(删除hash中指定的值)、hlen(获取hash的长度)、hexists(判断key是否存在)操作
127.0.0.1:6379> hset myhash name dingdada age 23 #添加hash,可多个 (integer) 2 127.0.0.1:6379> hget myhash name #获取hash中key是name的值 "dingdada" 127.0.0.1:6379> hget myhash age #获取hash中key是age的值 "23" 127.0.0.1:6379> hgetall myhash #获取hash中所有的值,包含key 1) "name" 2) "dingdada" 3) "age" 4) "23" 127.0.0.1:6379> hset myhash del test #添加 (integer) 1 127.0.0.1:6379> hgetall myhash 1) "name" 2) "dingdada" 3) "age" 4) "23" 5) "del" 6) "test" 127.0.0.1:6379> hdel myhash del age #删除指定hash中的key(可多个),key删除后对应的value也会被删除 (integer) 2 127.0.0.1:6379> hgetall myhash 1) "name" 2) "dingdada" 127.0.0.1:6379> hlen myhash #获取指定hash的长度,相当于length、size (integer) 1 127.0.0.1:6379> HEXISTS myhash name #判断key是否存在于指定的hash,存在返回1 (integer) 1 127.0.0.1:6379> HEXISTS myhash age #判断key是否存在于指定的hash,不存在返回0 (integer) 0 -
hkeys(获取所有key)、hvals(获取所有value)、hincrby(给值加增量)、hsetnx(存在不添加)操作
127.0.0.1:6379> hset myhash age 23 high 173 (integer) 2 127.0.0.1:6379> hgetall myhash 1) "name" 2) "dingdada" 3) "age" 4) "23" 5) "high" 6) "173" 127.0.0.1:6379> hkeys myhash #获取指定hash中的所有key 1) "name" 2) "age" 3) "high" 127.0.0.1:6379> hvals myhash #获取指定hash中的所有value 1) "dingdada" 2) "23" 3) "173" 127.0.0.1:6379> hincrby myhash age 2 #让hash中age的value指定+2(自增) (integer) 25 127.0.0.1:6379> hincrby myhash age -1 #让hash中age的value指定-1(自减) (integer) 24 127.0.0.1:6379> hsetnx myhash nokey novalue #添加不存在就新增返回新增成功的数量(只能单个增加哦) (integer) 1 127.0.0.1:6379> hsetnx myhash name miaotiao #添加存在则失败返回0 (integer) 0 127.0.0.1:6379> hgetall myhash 1) "name" 2) "dingdada" 3) "age" 4) "24" 5) "high" 6) "173" 7) "nokey" 8) "novalue"
Springboot整合redis
1)最低级别的整合
只需要导入依赖包,然后在使用的地方自动装配RedisTemplate就行了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@Autowired
RedisTemplate redisTemplate;
@Test
public void test(){
ValueOperations ops = redisTemplate.opsForValue();
ops.set("hehe","hhh");
String hehe = (String) ops.get("hehe");
System.out.println(hehe);
}
因为Spring boot的自动装配原理,即使什么都不干,Spring也会帮忙自动装配RedisTemplate,实现最基本的使用
运行结果:

注意:可以看到,我们成功的在redis中存入和取出了数据,但是在终端中查看数据的时候是这个样子的
![]()
这个样子是不利于我们查找和分别数据的,所以需要对上面进行升级
2)自定义配置类的方式整合redis
目的:不使用Spring boot的自动装配提供的RedisTemplate,而是自己采用@Bean的方式自己配置,达到自定义序列化的效果。
新建配置类:RedisConfiguration
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String,String>redisTemplate(
RedisConnectionFactory factory){
RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.string());
return redisTemplate;
}
}
运行结果:

查询结果:可以看到已经是比较好分辨的情况

有以下注意点:
-
RedisTemplate<String,String>
里面的第一个String代表key的类型,第二个String代表value的类型
-
自己配置必须给redisTemplate指定连接工厂
redisTemplate.setConnectionFactory(factory),这个连接工厂是Springboot自动创建好的,作用是根据配置文件指定端口号等配置,如果没有配置文件则使用默认的规则,如:port=6379
-
指定key序列化器和value序列化器
这个就是让将java对象按照指定的序列化规则存入,这里我们使用的是通过String的方式进行序列化
编程实现redis的各种操作
-
新增数据
@Test void setValue() { String key = "test"; String value = "this is test"; // 只要是对字符串类型的Value进行操作,必须调用opsForValue()方法得到相应的操作器 ValueOperations<String, String> ops = redisTemplate.opsForValue(); ops.set(key, value); log.debug("已经向Redis中写入Key为【{}】的数据:{}", key, value); }执行结果:

-
查询数据
执行结果:

-
查询没有的key
@Test void getNull() { String key = "hahahaha"; ValueOperations<String, String> ops = redisTemplate.opsForValue(); Serializable value = ops.get(key); log.debug("已经从Redis中取出Key为【{}】的数据:{}", key, value); }执行结果:

-
查找所有的key
@Test void keys() { String keyPattern = "*"; Set<String> keys = redisTemplate.keys(keyPattern); log.debug("查询当前Redis中所有的Key,Key的数量:{}", keys.size()); for (String key : keys) { log.debug("key = {}", key); } }执行结果:

-
删除
@Test void delete() { String key = "test"; Boolean result = redisTemplate.delete(key); log.debug("删除Key为【{}】的数据,结果:{}", key, result); }执行结果:

-
新增list
@Test void setList() { String[] str = new String[5]; for (int i = 0; i < 5; i++) { str[i] = "item"+i; } String key = "list"; // 得到List集合的操作器 ListOperations<String, String> ops = redisTemplate.opsForList(); for (String s : str) { ops.rightPush(key,s); } log.debug("向Redis中写入列表数据完成,Key为【{}】,写入的列表为:{}", key, str); }执行结果:

-
取list的值
@Test void listRange() { String key = "list"; long start = 0; long end = -1; ListOperations<String, String> ops = redisTemplate.opsForList(); List<String> list = ops.range(key, start, end); log.debug("从Redis中读取Key为【{}】的列表,start={},end={},获取到的列表长度为:{}", key, start, end, list.size()); for (String str : list) { log.debug("{}", str); } }执行结果:

-
设置过期时间
redisTemplate.opsForValue().set("2","张无忌",2, TimeUnit.SECONDS);//过期时间2秒 redisTemplate.opsForValue().set("2","张无忌",2, TimeUnit.MINUTES);//过期时间2分钟 redisTemplate.opsForValue().set("2","张无忌",2, TimeUnit.HOURS);//过期时间2小时 redisTemplate.opsForValue().set("2","张无忌",2, TimeUnit.DAYS);//过期时间2天 /* TimeUnit.SECONDS:秒 TimeUnit.MINUTES:分 TimeUnit.HOURS:时 TimeUnit.DAYS:日 TimeUnit.MILLISECONDS:毫秒 TimeUnit.MILLISECONDS:微秒 TimeUnit.NANOSECONDS:纳秒 */这里以两分钟为例,测试设置key为k的数据过期时间为两分钟,结果是:

redis实现序列化对象
这里我们使用的序列化方式是json格式的(实际上还是操作的String)
@Bean
public RedisTemplate<String,? extends Object>redisTemplate(
RedisConnectionFactory factory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
return redisTemplate;
}
然后编写测试类,测试是否成功
@Test
public void addObject(){
User user = new User();
user.setUserName("wp");
user.setPassword("123456");
user.setUserId("id1");
user.setUserAge(18);
user.setUserSex(1);
user.setUserEmail("123@qq.com");
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
ops.set("user:"+user.getUserId(), JSON.toJSON(user));
log.debug("成功将{}存入了redis中",user);
}
@Test
public void getObject(){
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
JSONObject json = (JSONObject) ops.get("user:id1");
User user = json.toJavaObject(User.class);
log.debug("从redis中取到的数据是:{}",user);
}
![]()

redis事务和乐观锁概念
redis事务定义:
redis的事务中,一次执行多条命令,本质是一组命令的集合,一个事务中所有的命令将被序列化,即按顺序执行而不会被其他命令插入。
在redis中,事务的作用就是在一个队列中一次性、顺序性、排他性的执行一系列的命令。
redis事务的生命周期:
-
事务的创建:
使用MULTI开启一个事务
-
加入队列:
在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真的执行
-
EXEC命令进行提交事务
提交事务之后,将队列的命令依次执行
首先先说结论:
在redis事务中,对于一个存在问题(错误)的命令,如果在入队的时候就已经出错,整个事务内的命令将都不会被执行(其后续的命令依然可以入队);
但是如果这个错误命令在入队的时候并没有报错,而是在执行的时候出错了,那么redis默认跳过这个命令执行后续命令。
也就是说,redis只实现了部分事务。
总结redis事务的三条性质:
-
单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
-
没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行
-
不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
redis事务和MySql的区别:
-
redis没有隔离级别这个说法,redis把指令变成一个队列,最后一次性执行。MySQL是利用MVCC机制实现了隔离级别,而MySql是先执行,然后将指令存入undolog里面。
-
redis不可以回滚,只有执行或者不执行,如果队列里面的命令没错,就会执行,但是如果执行出错也不会回滚;MySql是可以回滚的,在事务的中途遇到错误,会利用undolog回滚之前所有的操作。
-
redis不能保证原子性,原因如上所说;MySql很大程度上是能保证原子性的。
redis的乐观锁:
redis的CAS(check and set)机制是配合事务实现的,redis的实现原理是使用watch进行监视一个(或多个)数据,如果在事务提交之前数据发生了变化,那么整个事务将提交失败。也就是说,事务的命令队列在提交之前,会先检查watch监视的数据,如果发生改变,这个命令队列将不会执行。
事务和乐观锁的实现:
常见命令:
-
multi:使用该命令,标记一个事务块的开始,通常在执行之后会回复OK,(但不一定真的OK),这个时候用户可以输入多个操作来代替逐条操作,redis会将这些操作放入队列中。
-
exec:执行这个事务内的所有命令
-
discard:放弃事务,即该事务内的所有命令都将取消
-
watch:监控一个或者多个key,如果这些key在提交事务(EXEC)之前被其他用户修改过,那么事务将执行失败,需要重新获取最新数据重头操作(类似于乐观锁)。
-
unwatch:取消WATCH命令对多有key的监控,所有监控锁将会被取消。
使用multi命令开启事务,exec执行事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
4) OK
使用multi开启事务,discard取消事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> keys *
(empty list or set)
使用watch没有被修改
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> watch num
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr num
QUEUED
127.0.0.1:6379> exec
OK
使用watch被修改
首先设置num的值为10,然后监控他,最后执行失败
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> watch num
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr num
QUEUED
127.0.0.1:6379> exec
(nil)
原因是:在事务提交前,用另外一个session去修改num的值,即使改变后的值是一样的,也不会执行成功(ABA问题)
127.0.0.1:6379> get num
"10"
127.0.0.1:6379> set num 20
OK
Java实现redis事务(watch)
还是利用redisTemplate执行操作,但是有注意点!!不能简单的使用redisTemplate.multi().....然后再使用redisTemplate.exec(),这样会抛出一个异常,No ongoing transaction. Did you forget to call multi,意思大概是没有开启事务;
根据源码,我们找到错误原因,因为在Configuration里面没有开启事务,所以每次都是一个新的连接,导致失败
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(action, "Callback object must not be null");
RedisConnectionFactory factory = getRequiredConnectionFactory();
RedisConnection conn = null;
try {
// RedisTemplate 的 enableTransactionSupport 属性标识是否开启了事务支持,默认是 false
if (enableTransactionSupport) {
// only bind resources in case of potential transaction synchronization
conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
} else {
conn = RedisConnectionUtils.getConnection(factory);
}
boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
使用SessionCallback的方式实现事务:
我们注意到,redistemplate有个execute()方法,内部的参数是一个SessionCallback类型的,而SessionCallback里面我们可以自定义很多的命令操作,然后一次性交给redisTemplate执行,达到事务的效果。

@Autowired
RedisTemplate<String,String> redisTemplate;
@Test
public void testMulti() {
SessionCallback sessionCallback = new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
redisOperations.multi();
redisOperations.opsForValue().set("wp","汪鹏");
redisOperations.opsForValue().set("test","test");
return redisOperations.exec();
}
};
//将SessionCallback交给redisTemplate执行
redisTemplate.execute(sessionCallback);
}
执行前后结果的对比:

使用SessionCallback的方式实现CAS:
@Test
public void testWatch(){
SessionCallback session = new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.watch("num");
operations.multi();
operations.opsForValue().set("num","15");
return operations.exec();
}
};
System.out.println(redisTemplate.execute(session));
}
本文详细介绍了Redis的基础知识,包括命令操作如String、List、有序集合Zset、Set和Hash类型,以及Redis事务和乐观锁的概念。还展示了如何在SpringBoot中整合Redis,包括基本的整合和自定义配置类的方式,以及编程实现Redis的各种操作。此外,还探讨了Java中使用Redis事务和CAS机制的实现。
10万+

被折叠的 条评论
为什么被折叠?



