redis入门一篇就够了!(两万字总结) 上

文章大纲:

image.png

1.官方中文文档

www.redis.cn/

2.linux下安装

  • 官网下载安装包 redis-6.0.6.tar.gz
  • 放到服务器的usr/local/redis目录下
  • 解压:tar -zxvf redis-6.06.tar.gz
  • 在redis目录下进行基本的环境安装:yum install gcc-c++
  • 检查gcc的版本:gcc -v
  • 在redis目录下执行make(若报错可能是gcc的版本问题)

  • 执行make install

  • 进入/usr/local/bin目录,这是redis的默认安装目录。在这个目录创建一个myredisconfig的文件夹。然后将/usr/local/redis/redis-6.0.6下的redis.conf拷贝到这个文件夹下

  • 设置为后台自动启动:修改myredisconfig目录下的redis.conf daemonize no改为daemonize yes,然后:wq!保存退出

  • 通过配置文件启动redis(在/usr/local/bin目录下)

redis-server myredisconfig/redis.conf

  • redis客户端测试连接

redis-cli -p 6379

ping

  • 退出redis

shutdown

exit

  • 查看redis进程

ps -ef|grep redis

3.安装出现的问题

  • 在安装redis6以上的版本时make命令会出现错误,原因是gcc版本太低(centos默认版本为4),可以选择安装低版本redis5或者升级gcc到8版本,通过gcc -v命令查看版本.
  • 升级gcc版本的方法

www.cnblogs.com/jixiaohua/p…

  1. 压力测试工具

redis-benchmark是官方自带的性能测试工具,可以有效的测试redis服务的性能。在bin目录下。

测试demo:

测试100个并发连接,10000个请求,检测host为localhost 端口为6379的redis服务器性能。

redis-benchmark -h localhost -p 6379 -c 100 -n 10000

相关参数说明:

  1. 基本的数据库命令

查看redis.conf可以得知:

  • 进入redis:redis-server myredisconfig/redis.conf redis-cli -p 6379
  • 退出redis:shutdown exit
  • 输入密码:auth 311046
  • 切换数据库:select n n表示第几个数据库
  • 查看当前数据库的key的数量:dbsize
  • 查看具体的key:key *
  • 清空当前库:flushdb
  • 清空全部的库:flushall
  • 设置key值:set name coderchen
  • 获取key值:get name
  • 查看 key是否存在:exists keyname例如 exists name
  • 设置key生存时间当key过期会被自动删除:expire key 秒数
  • 查看还有多少秒过期:ttl name -1表示永不过期,-2表示已经过期。
  • 查看key的类型:type key
  • 删除key: del key

注意:redis命令不区分大小写,但是记录的数据区分大小写

  • 为什么redis默认端口是6379?

www.zhihu.com/question/20…

  1. redis单线程快?

  • 为什么redis是单线程?

redis是基于内存的操作,cpu不是redis的瓶颈,redis的瓶颈是机器内存大小或者网络带宽。既然单线程容易实现而且cpu不会成为瓶颈,那么就顺理成章的采用单线程的方案了。redis是C语言编写的,官方数据可以达到100000+QPS(每秒查询次数)

  • redis为什么这么快?

并不是多线程一定比单线程快,效率高。这与cpu,内存,硬盘的速度有关。

redis单线程快是因为redis是基于内存的操作。单线程效率最高,因为多线程的本质就是cpu模拟多个线程的情况,这其中有一个大的开销就是cpu上下文的切换。而redis单线程对内存中的数据进行读写时,速度比较快。举个例子就是读取1MB的数据时,可能多个进程cpu上下文的切换时间就大于单个进程读取数据的时间

key值加引号和不加的区别?

7.字符串String(单值单value)

  • string是redis 的基本类型,一个key对应一个value。string类型是二进制安全的,即redis的string可以包含任何数据,比如jpg图片或者序列化的对象,string类型redis最基本的数据类型,一个redis中字符串value最多可以是512M

常用命令说明:

官方命令手册:redis.com.cn/commands.ht…

string数据结构是简单的key-value类型,value不仅可以是string,也可以是数字

  • append key1 "hello":对不存在的key进行append,等同于set key1 "hello"
  • append key1 ",world":对已经存在的key append就是在后面追加。
  • strlen key1:获取字符串长度

  • set views 0:要设置为数字才能用incr,decr命令 ,本质上这是一个字符串操作,因为Redis没有专门的整数类型。存储在 key 中的字符串被转换为十进制有符号整数
  • incr views:执行一次就将key中存储 的数字值增一
  • decr views:执行一次就将key中存储的数字值减一
  • incrby views 10:执行一次key中存储的值+10
  • decrby views 10:执行一次key中存储的值-10

  • getrange key2 m n:获取[m,n]区间范围内的字符串。n可以为负数,表示偏移,-1 表示最后一个字符, -2 表示倒数第二个字符,以此类推。
  • setrange key2 n value :将key2的值从第n个开始替换为指定的value
  • setex key2 10 "hello":将key2的值设置为hello,并且key2的生存时间设置为10s。这个命令等价于set key2 "hello" expire key2 10区别是setex 命令是一个原子操作,在同一时间内完成设置值和设置过期时间这两个操作。
  • setnx key3 "redis":在指定的key不存在时,为key设置指定的值,这种情况下等同于set,当key存在时,什么也不做。返回值:0 被设置了 1 没有被设置。

  • mset key1 value1 key2 value2 ......keyn valuen:设置多个key和value,如果这其中已经存在key,则会覆盖掉。
  • mget key1 key2 ...keyn:返回所有给定的key的值
  • msetnx key1 value1 key2 value2 ...keyn valuen:设置多个 key value,当且仅当所有给定的健都不存在时,即使有一个健存在值,msetnx也会拒绝执行操作。

  • getset key value:将key的值修改为value,并返回在修改之前的值。如果key在执行这个命令前不存在,则返回nil。

  • 可以用来缓存对象

mset user:1:name zhangsan user:1:age 2

mget user:1:name user:1:age

应用

  • 微博数,粉丝数等
  1. 列表List(单值多value)

  • redis的列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边)
  • 它的底层就是一个链表

常用命令说明

官方命令手册:redis.com.cn/commands.ht…

  • lpush list "one":将值插入到列表的头部(左边)
  • rpush list "two":将值插入到列表的尾部(右边)
  • lrange list n m:返回指定区间的元素 [n,m] m为-1表示返回n后面所有的元素。

  • lpop list:移除并返回列表的第一个元素,左边的第一个
  • rpop list:移除并返回列表的最后一个元素,右边第一个。

  • lindex list n:按照索引下标n获得元素(-1代表最后一个,0表示第一个,以此类推)


  • llen list:返回列表的长度


  • lrem list n value :移除列表中与value相等的元素,移除n个,顺序是从前往后。n>0 从头到尾删除值为value的元素, n<0,从尾到头删除值为value的元素, n=0,移除所有值为value的元素


  • ltrim list n m:对一个列表进行修剪,只保留区间[n,m]内的元素。其他全部删除。


  • rpoplpush list1 list2:移除列表list1的最后一个元素,并将该元素添加到list2的前边。这个操作是原子性的。从6.2.0起被废除了。


  • lset list n value:将list中第n个元素的值设置为value


  • linsert list before|after value value1:将value1插入到value的前边或者后边。


性能总结

  • 它是一个字符串列表,left,right都可以插入添加
  • 如果键不存在,创建新的链表。
  • 如果已经存在,新增内容
  • 如果值全部移除,对应的键也就消失了
  • 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就比较低。

应用

  • 最新消息排行功能
  • 消息对列
  1. 集合Set(单值多value)

  • redis的集合set是string类型的无序集合,是通过HashTable实现的。

常用命令说明

  • sadd key member[...]:将一个或者多个成员元素加入到集合中。
  • smembers key:返回存储在key中的所有成员
  • sismember key member:判断元素member是否是集合key中的成员。返回1表示是,0表示不是。


  • scrad key:返回集合中元素的数量。


  • srem key value:移除集合key中的一个或者多个元素value


  • srandmember key [count]:返回集合key中的随机元素,count表示返回几个元素。


  • spop key [count]:从集合key中删除一个或者多个元素,并返回删除的元素。(删除是随机的)


  • smove key1 key2 member :将指定的元素从集合key1移动到key2 。这是一个原子操作。


  • sdiff key [key...]:返回第一个集合key与其他key之间的差异,即第一个集合key独有的元素。
  • sinter key [key...]:返回所有给定集合key的成员交集
  • sunion key [key...]:返回所有给定集合key的并集

应用

  • 实现共同关注,共同好友等功能。
  1. 哈希Hash(单值多value,v是一个键值对)

  • Hash是一个键值对集合,类似于java中的map,是一个String类型的field和value的映射表,特别适合存储对象。

常用命令

  • hset key field value [field value ....]: 为哈希表key的field字段赋值
  • hget key field:返回哈希表key中的field字段的值
  • hgetall key:返回哈希表key中所有的域和值
  • hdel key field [field...]:删除哈希表key中的一个或者多个指定字段。


  • hlen key:获取哈希表key中的字段fields的数量


  • hexists key field:查看哈希表的指定字段field是否存在。0表示不存在,1表示存在
  • hkeys key:返回哈希表key中所有的域field
  • hvals key:返回哈希表中所有域的值


  • hincrby key field increment: 为哈希表key中的域field的加上增量increment.增量为负数表示进行减法操作。


  • hsetnx key field value:为哈希表中不存在的字段field赋值为value。0表示设置失败,1表示设置成功。

应用

  • Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 存储部分变更的数据,如用户信息等。
  1. 有序集合Zset

  • 在set基础上,加上一个score。之前set是sadd set1 v1 v2 v3…,现在Zset是zadd zset1 score1 v1 score2 v2。

常用命令

  • zadd key score1 member1[score2 member2...] :将一个或者多个member元素及其score值加入到有序集合key中。其中score可以是整数值或者双精度浮点数。可以为正或者负数
  • zrange key n m:返回有序集合key中的[n,m]区间内的元素。下标从0开始。如果m超出范围也不会报错,只会返回包含的。


  • zrangebyscore key n m:返回有序集合key中的成员,按照score的从小到大排序,范围是[n,m],n,m可以为-inf,+inf,这样在不知道有序集合数量的时候获取所有的成员注意[n,m]这个范围是score的范围,不是成员的下标


  • zrem key member1[member2...]:从有序集合key中删除指定的成员member


  • zcard key:返回有序集合key中的成员个数


  • zcount key n m :返回有序集key中,score在[n,m]之间的成员数量。


  • zrank key member:返回有序集key中成员的排名,按照score从低到高排名,从0开始


  • zrevrank key member:返回有序集key中成员的排名,按照score从高到低排名,从0开始

应用

  • 排行榜应用,取top n操作。
  1. 三种特殊的数据类型

12.1 GEO地理位置

简介:

redis的GEO特性在redis3.2版本中推出,这个功能可以将用户给定的地理位置信息存储起来,并对这些信息进行操作。来实现诸如附近位置,摇一摇这类依赖于地理位置信息的功能GEO的数据类型为Zset。

GEO的数据结构总共有六个常用命令:geoadd ,geopos ,geodist , georadius, georadiusbymember , gethash

官方文档:www.redis.net.cn/order/3685.…

命令:

geoadd

将指定的地理空间位置(经度,纬度,名称)添加到指定的key中。这些数据会存储到有序集合Zset中,目的是为了方便使用georadius,或者georadiusbymember命令对数据进行半径查询等操作。

geoadd china:city 121.48 31.40 shanghai 113.88 22.55 shenzhen 120.21 30.20 hangzhou

注意点:该命令采用标准格式的参数xy,所以经度必须在纬度之前。有效的经度 -180~180度,有效的纬度 -85.05~85.05


geopos

从key里面返回所有给定位置元素的位置(经度和纬度)

geopos key member[member...]

geopos china:city shanghai hangzhou


geodist

返回两个给定位置之间的距离,指定的参数unit必须是以下中的一种。m 米 ,km 千米, mi 英里,ft 英尺,默认是m。

geodist key member1 member2 [unit]

geodist china:city shanghai hangzhou km


georadius

以给定的经纬度为中心,找出某一半径内的元素。

范围的单位:m米,km千米 ,mi英里 ,ft英尺

georadius key longitude(经度) latitude(纬度) radius m|km|ft|mi [withcoord] [withdist][withhash][asc|desc][count count]

withdist:返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位与用户给定范围单位保持一致。

withcoord:将位置元素的经纬度也返回。

withhash:以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大

asc: 根据中心的位置, 按照从近到远的方式返回位置元素。

desc:根据中心的位置, 按照从远到近的方式返回位置元素。

count:获取前n个匹配的元素,可以减少带宽当数据量大时。

举例:

georadius china:city 120 30 1500 km withdist


georadiusbymember

找出位于指定范围内的元素,中心点是由给定的位置元素决定的

georadiusbymember key member radius m|km|ft|mi [withcoord][withdist][withhash][asc|desc][count count]

举例:

georadiusbymember china:city shanghai 1500 km


geohash

返回一个或者多个位置元素的geohash表示。

redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似表示距离越近。

geohash key member[member...]

举例:

geohash china:city shanghai hangzhou


zrem

geo没有提供删除成员的方法,但是因为geo的底层是zset,所有可以用zrem命令实现对地理位置信息的删除。

zrem china:city shanghai:移除元素

zrange china:city 0 -1:查看所有元素

命令演示

12.2 HyperLogLog

简介

redis在2.8.9版本添加了HyperLogLog结构。

redis HyperLogLog是用来做基数统计的算法。HyperLogLog的优点是在输入元素的数量和体积非常大时,计算基数所需的空间总是固定的,并且很小

基数:比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

在redis里面,每个HyperLogLog键只需要花费12kb内存,就可以计算接近2^64个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

HyperLogLog则是一种算法,提供了不精确的去重基数方案。

举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的 解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量 用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存 用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要12k就可以统计大量的用户 数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的

基本命令

pfadd key element[element...]:添加指定元素到HyperLogLog

pfcount key[key...]:返回给定HyperLogLog的基数估计值。

pfmerge destkey sourcekey [sourcekey...]:将多个HyperLogLog合并为一个HyperLogLog,并集计算。

12.3 BitMap

简介

​ 在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录 365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这种数据结构, Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap 表示的形式大概如下:0101000111000111………………………,这样有什么好处呢?当然就是节约内存 了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。

​ BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上 底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个 bitmap 相关命令。

基本命令
  • setbit key n value:设置key的第n位的值value是0或1,n是从0开始的。
  • getbit key n:获取第n位的值。如果没有设置返回0
  • bitcount key[start,end]:统计key上值为1的个数。

  1. redis事务

理论


redis事务的概念:

redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有的命令都会被序列化。在事务执行过程中,会按照顺序串行化执行对列中 的命令,其他客户端提交的命令请求不会插入到事务执行的命令序列中。

总结的说:redis事务就是一次性,顺序性,排他性的执行一个对列中的一系列命令。


redis事务没有隔离级别的概念:

批量操作在发送exec命令前被放入对列缓存,并不会被实际执行。


redis不保证原子性:

redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。


redis事务的三个阶段:

开始事务

命令入队

执行事务


redis事务的相关命令

  • watch key1 key2...:监视一个或者多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断(类似乐观锁)
  • unwatch:取消watch对所有key的监控。
  • multi:标记一个事务块的开始(queued)
  • exec:执行所有事务块的命令(一旦执行exec后,之前加的监控锁都会被取消掉)
  • discard:取消事务,放弃事务块中所有 的命令

乐观锁和悲观锁?

  • 悲观锁(Pessimistic Lock),顾名思义就是很悲观,每次拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿到这个数据就会block知道它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等,读锁写锁等,都是在操作之前先上锁。
  • 乐观锁(Optimistic Lock)顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新。

实践

  • 正常执行

  • 放弃事务

  • 如果事务对列中存在命令性错误(类似java编译性错误),执行exec命令时,所有命令都不会执行。

  • 如果在事务对列中存在语法性错误(类似于java的1/0运行时异常),则执行exec命令时,其他正确命令会被执行,错误命令抛出异常。

  • watch命令的演示(watch 用来监控key在事务执行的前后是否变化)–事务成功执行

  • 使用watch事务执行失败

由于在事务执行过程中,watch监视的值出现了变化,因此导致了错误,这时要放弃监视,然后重来。

**注意:**一旦exec执行事务后,无论事务执行成功还是失败,watch对变量的监控都被取消。因此当事务执行失败后,需要重新执行watch对变量进行监控,并开启新的事务进行操作。

watch指令类似于乐观锁,事务提交时,如果watch监控的多个key中任何key的值被其他客户端更改,则使用exec执行事务时,事务对列不会被执行,同时返回Nullmulti-bulk 通知调用者事务执行失败。

  1. springboot整合redis

步骤:

  • 导入依赖
1
2
3
4
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 在application.properties中配置
1
2
3
4
properties复制代码spring.redis.host=115.29.232.195
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=50000
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() {
/*
* opsForList:操作list,类似string
* opsForGeo 操作geo
* opsForSet
* .......
* 和redis命令行一样。
* */
/*获取redis的连接对象*/
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();

redisTemplate.opsForValue().set("mykey","kuangshen");
System.out.println(redisTemplate.opsForValue().get("mykey"));
redisTemplate.opsForSet().add("k5","v9");
Set k5 = redisTemplate.opsForSet().members("k5");
System.out.println(k5);

}
}

redis要做的配置

  • 阿里云设置redis的安全组:端口6379


  • 修改redis.conf

(在/usr/local/bin/myredisconfig/redis.conf)

bind 127.0.0.1 改为 bind 0.0.0.0(注释掉也行

protected-mode yes 改为 protected-mode no: (即该配置项表示是否开启保护模式,默认是开启,开启后Redis只会本地进行访问,拒绝外部访问)

daemonize no 改为 daemonize yes

注意:修改后要重新登录redis


  • 关于防火墙的设置

rpm -qa|grep firewalld;rpm -qa|grep firewall-config :检查系统是否安装了firewalld和firewall-config.CentOS中系统默认安装firewalld, firewall-config要自己安装

yum -y update firewalld:将firewalld更新为最新版

yum -y install firewall-config:安装firewall-config

systemctl start firewalld:启动firewalld服务

systemctl status firewalld:查看firewalld的状态

systemctl stop firewalld:停止firewalld服务

systemctl enable firewalld:恢复开机自动启动firewalld服务

推荐参考文章:CentOS7中firewalld的安装与使用详解 blog.csdn.net/solaraceboy…


  • 检查阿里云的防火墙是否开放端口号

firewall-cmd --query-port=6379/tcp 如果是 yes,则就是开放的。

如果是no

则永久开放6379端口:firewall-cmd --zone=public --add-port=6379/tcp --permanent

重新加载:firewall-cmd --reload

然后再查看端口是否开启:firewall-cmd --query-port=6379/tcp

  • 查看redis服务进程
1
perl复制代码 ps -ef | grep redis
  • 如果上面的方法都试过了但还是不行,试试重启服务器

推荐一个好用的redis客户端工具

​ Another Redis Desktop Manager

下载地址:github.com/qishibo/Ano…

可以用它来测试和管理redis

redis的序列化配置

  • 通过源码可知,springboot自动帮我们在容器中生成了一个redisTemplate和一个StringRedisTemplate,但是这个redisTemplate的泛型是<ooject,object>,写代码不方便,需要类型转换的代码,并且没有设置数据存在redis时,key及value的序列化方式。因此要自定义一个配置类。
  • 为什么要序列化:www.jianshu.com/p/cc5a29b06…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
java复制代码package com.kuang.config;

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;

import java.net.UnknownHostException;

/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-06-06
* Time: 14:05
*/
@Configuration

public class redisConfig {
/*编写自己的redisTemplate----固定模板*/
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
/*为了自己开发方便,一般直接使用<String,object>*/
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
/*json序列化配置*/
Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Jackson2JsonRedisSerializer.setObjectMapper(om);
/*string序列化配置*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();


/*配置具体的序列化方式*/
/*key采用string的序列化方式*/
template.setKeySerializer(stringRedisSerializer);
/*hash的key采用string的序列化方式*/
template.setHashKeySerializer(stringRedisSerializer);
/*value序列化方式采用jackson*/
template.setValueSerializer(Jackson2JsonRedisSerializer);
/*hash的value序列化方式采用jackson*/
template.setHashValueSerializer(Jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

redis的工具类

  • 直接使用redisTemplate操作redis时,需要很多代码,因此直接封装一个redisUtils,这样写代码方便一点。这个redisUtils交给spring容器实例化,使用时直接注解注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
java复制代码package com.kuang.utils;

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.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-06-06
* Time: 14:52
*/
/*在真实开发中,经常使用*/
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================

/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
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((Collection<String>) 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)
*/
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)
*/
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
*/
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 对应多个键值
*/
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)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}

/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================

/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
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 键
*/
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代表所有值
*/
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 键
*/
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倒数第二个元素,依次类推
*/
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 值
*/
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 时间(秒)
*/
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;
}
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
kotlin复制代码package com.kuang;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kuang.pojo.User;
import com.kuang.utils.RedisUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Set;

@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
@Qualifier("redisTemplate")/*避免与源码重合,跳转到自定义redisTempalte*/
private RedisTemplate redisTemplate;

@Autowired
private RedisUtil redisUtil;


@Test
void contextLoads() {
/*
* opsForList:操作list,类似string
* opsForGeo 操作geo
* opsForSet
* .......
* 和redis命令行一样。
* */
/*获取redis的连接对象*/
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();

redisTemplate.opsForValue().set("mykey","kuangshen");
System.out.println(redisTemplate.opsForValue().get("mykey"));
redisTemplate.opsForSet().add("k5","v9");
Set k5 = redisTemplate.opsForSet().members("k5");
System.out.println(k5);



}

@Test
public void test() throws JsonProcessingException {
/*真实开发一般使用json传递数据*/
User user = new User("kuangshen", 3);
// String jsonUser = new ObjectMapper().writeValueAsString(user);
// redisTemplate.opsForValue().set("user", jsonUser);
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));

}

@Test
public void test1() {
redisUtil.set("username", "coderchen");
System.out.println(redisUtil.get("username"));
}

}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%