Redis学习笔记

Redis学习笔记

一、Nosql出现的背景

单机MYSQL时代出现的问题
  • 数据量急剧增加,单个数据库装不下庞大的数据
  • 数据的索引太大,一个机器的内存也放不下
  • 访问量太大,一台服务器承受不住。(需要大量的读写混合)
如何解决: Memcached(缓存)+mysql+垂直拆分(读写分离)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-djD3BO9d-1666190632545)(images/image-20211015122718484.png)]

读写分离就是: 将一个大的数据库拆分成专门读和写的数据库

优化历程
  • 优化数据库的数据结构和索引(难度大)
  • 文件缓存,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了
  • MemCache,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。
分库分表+水平拆分+Mysql集群

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KgFSjOGe-1666190632546)(images/image-20211015131300452.png)]

​ 虽然利用mysql集群可以解决大量数据存储的问题,但是如今各式各样的数据出现(用户定位数据、图片数据等),大数据时代关系数据库无法满足要求,因此Nosql数据库解决了这些问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTTL1qCX-1666190632546)(images/image-20211015133656560.png)]

为什么要使用Nosql

关系型数据库无法存储个人信息、社交网络、地理位置等各式各样的数据,这时候就需要我们使用非关系型数据库Nosql

什么是Nosql

Nosql = Not Only Sql (不仅仅是 sql)

关系数据库

  • 列+行,同一个表下数据的结构是一样的。
  • 结构化组织
  • SQL
  • 数据和关系都存在单独的表中 row col
  • 操作,数据定义语言
  • 严格的一致性
  • 基础的事务

非关系型数据库

  • 数据存储没有固定的格式、并且可以进行横向扩展
  • 不仅仅是数据
  • 没有固定的查询语言
  • 键值对存储,列存储,文档存储,图形数据库(社交关系)
  • 最终一致性
  • CAP定理和BASE
  • 高性能,高可用,高扩展
大数据的时代的3V
  • 海量 velume
  • 多样 variety
  • 实时 velocity
大数据时代的3高
  • 高并发
  • 高性能
  • 高扩展

实际上的公司实践是:Nosql+RDNMS一起使用才是最合适的。

二、Redis基础

什么是Redis

Redis(Remote Dictionary Server ),即远程字典服务。是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型Key-Value数据库,并提供多种语言的API。与memcached一样,**为了保证效率,数据都是缓存在内存中。**区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件

Redis能干什么
  • 内存储存、持久化、内存断电就失,因此需要持久化(RDB、AOF)
  • 高效率、高速缓存
  • 发布订阅者(观察者模式)系统
  • 地图信息分析
  • 计时器(UV访问量问题)
特性
  • 五种数据类型+3种特殊数据类型
  • 持久化
  • 集群
  • 事务

三、Redis环境搭建和测试

Redis的安装
连接阿里云服务器ECS,创建redis文件夹
cd /usr/local
mkdir redis
下载并安装redis

下载

cd /usr/local/redis
wget http://download.redis.io/releases/redis-3.2.10.tar.gz

解压

tar -xzvf redis-3.2.10.tar.gz

解压后

在这里插入图片描述

安装redis

注意:这里使用gcc对redis进行编译 生成 redis-server等文件。如果没有安装redis需要先在服务器安装gcc,不然是没有redis-server文件的–无法启动和关闭。

yum -y install gcc  gcc-c++

进入redis-3.2.10目录,执行编译

cd redis-3.2.10
sudo make && make install
配置redis.conf

注意:redis.conf是一个非常重要的配置文件

cd redis-3.2.10
vim redis.conf
  1. 在配置文件61行左右(行数在右下角),注释掉172.0.0.1(默认redis是只能内网127.0.0.1访问,如果想外网访问需要修改绑定的地址)

    # bind 127.0.0.1
    
  2. 设置redis可以一直在后台运行,以守护进程方式运行,即关闭SSH工具程序也在运行。
    将 daemonize no 改成 daemonize yes(在128行左右)

    **注意:**守护进程一旦开启,想要关闭redis就相当困难了,使用kill -9 port 依然无法杀死redis进程,因为每次杀死进程后又会重新开启redis

    所以 先不开启 守护进程方式

    如果不小心开启了:修改后,重新编译redis即可

    daemonize no # 关闭
    daemonize yes # 开启
    
  3. 开启远程访问,大概在80行左右

    注意:protected-mode 是3.2 之后加入的新特性,是为了禁止公网访问redis cache,加强redis安全的。

    protected-mode no 
    
  4. 密码设置,将”#requirepass foobared“ 取掉注释改成 requirepass 123456(或者其它你需要的密码)(在480行左右)

    requirepass 123456
    

    最后保存退出

启动redis

进入redis-3.2.10目录,启动redis

cd redis-3.2.10
# 启动 redis
redis-server redis.conf
# 查看是否启动成功
ps aux | grep redis

启动脚本startRedis.sh

############### startRedis.sh 
#!/bin/sh
# start redis
redis-server /usr/local/redis/redis-3.2.10/redis.conf  #  redis.conf 的路径
echo "redis started"


############### end

vim startRedis.sh
chmod +x ./startRedis.sh
./startRedis.sh

出现 redis-server *: 6379 即启动成功

在这里插入图片描述

如果出现了redis-server 127.0.0.1: 6379需要查看redis.conf是否配置正确

开启阿里云服务器端口

首先登陆阿里云控制台; 控制台–>云服务器ECS–>安全与网络–>安全组–>配置规则

出方向和入方向都要配置

img

点击快速添加

img

测试连接

服务器测试连接

进入redis-3.2.10目录,测试连接

redis-cli  -a 123456  # 如果没有设置密码: redis-cli 
or
redis-cli
# 查看密码
config get requirepass
# 退出 
exit

退出redis

service redis stop

显示结果,服务器本地连接成功

在这里插入图片描述

本地远程测试连接

下载RDM管理工具:https://redisdesktop.com/

设置连接信息

​ 名字: 任意取

​ 密码: redis密码

​ 地址: 服务器地址

在这里插入图片描述

点击测试连接

在这里插入图片描述

如果测试失败

请排查以下原因

  1. 配置文件redis.conf配置错误,是否开启远程连接

  2. 6379端口是否开发

  3. 密码配置错误

    # 查看redis密码
    redis-cli 
    config get requirepass
    

    要是显示为空,重新配置redis.conf文件中密码:requirepass 12345

    或者临时性配置密码

    redis-cli 
    config set requirepass 123456
    
关闭redis
redis shell中关闭redis
redis-cli -a 123456  # 如果设置了密码,要带上密码,否者会出现没有权限的警告
127.0.0.1:6379> shutdown  
127.0.0.1:6379> exit

如果出现问题,应该是直接使用redis-cli进入的shell

127.0.0.1:6379> shutdown
NOAUTH Authentication required.

解决方法

auth 123456
直接关闭redis
redis-cli -a 123456 shutdown  # 设计密码直接关闭redis
redis-cli shutdown # 没有设置密码,关闭redis
强制关闭redis
ps aux | grep redis  # 查看 进程 ID
root     19935  0.1  0.2  37252  4176 ?        Sl   14:49   0:00 redis-server *:6379
root     20050  0.0  0.0  14436  1004 pts/0    S+   15:01   0:00 grep --color=auto redis
kill -9 19935  # 杀死 redis 进程
Redis使用自带的测试工具测试

使用redis-benchmark自带工具测试,redis的工具都放在/usr/local/bin

root@iZ2ze3e3cxev6ehgdt3qpdZ:/usr/local/bin# ls
chardetect  cloud-init-per  jsonpatch    redis-benchmark  redis-cli
cloud-id    docker-compose  jsonpointer  redis-check-aof  redis-sentinel
cloud-init  jsondiff        jsonschema   redis-check-rdb  redis-server
root@iZ2ze3e3cxev6ehgdt3qpdZ:/usr/local/bin# redis-benchmark -h localhost -p 6379 -c 100 -n 10000
====== PING_INLINE ======
  10000 requests completed in 0.18 seconds - 10000条请求
  100 parallel clients - 100个并发数据
  3 bytes payload  - 所需空间 3 btyes
  keep alive: 1 - 单机

76.71% <= 1 milliseconds - 76.71%用了1毫秒
99.96% <= 2 milliseconds
100.00% <= 2 milliseconds
56497.18 requests per second - 每秒可以处理56497.18条数据
Redis基本知识

Redis是单线程的,Redis是基于内存操作,CPU不是Redis的瓶颈,而是机器内存和网络带宽。

Redis单线程为什么这么快

Redis将所有数据全部放在了内存操作中,省略了CPU上下文切换的耗时,对于系统而言没有上下文切换效率是最好的。,在内存存储数据情况下,单线程就是最佳的方案。

误区
  • 高性能的服务器一定是多线程
  • 多线程服务器要比单线程快

多线程存在CPU的上下文切换

Redis中的16个数据库

Redis中有16个数据库,默认使用第0个,可以使用select n切换数据库,dbsize可以查看当前数据库的大小,与key数量相关。

127.0.0.1:6379> config get databases # 查看redis中的数据库信息
1) "databases"
2) "16"
127.0.0.1:6379> dbsize # 默认是0号数据库, 查看0号数据库的大小
(integer) 0
127.0.0.1:6379> select 4 # 切换到4号数据库
OK
127.0.0.1:6379[4]> set name test # 设置  name 键值对
OK
127.0.0.1:6379[4]> select 8
OK
127.0.0.1:6379[8]> get name # 不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
(nil)
127.0.0.1:6379[8]> select 4
OK
127.0.0.1:6379[4]> get name
"test"
127.0.0.1:6379[4]> keys *  # # 查看当前数据库中所有的key。
1) "name"

config get databases : 查看redis中的数据库信息

dbsize : 默认是0号数据库, 查看0号数据库的大小

select 4 :切换到4号数据库

keys * :查看当前数据库中所有的key。

flushdb:清空当前数据库中的键值对。

flushall:清空所有数据库的键值对。

set name test : 设置键值对

get name : 得到键值对

四、Redis五大数据类型

Redis-key

在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。

  • set key value :创建键值对

  • get key : 得到键值对

  • exists key:判断键是否存在

  • del key:删除键值对

  • move key db:将键值对移动到指定数据库

  • expire key second:设置键值对的过期时间

  • type key:查看value的数据类型

  • ttl age : 查看剩余过期时间

    ttl key
    	 - 如果返回 -1 , 表示key没有设置过过期时间
    	 - 如果返回 -2 , 表示key设置过过期时间,并且已经过期
    	 - 如果返回 剩余时间 , 表示key还没有过期
    
key * redis:4>set age 20 # 创建键值对 age
"OK"
redis:4>get age # 得到键值对 age
"20"
redis:1>exists name # 判断键name是否存在
"0"
redis:1>exists age  # 判断键age是否存在
"1"
redis:1>del age   # 删除键值对
"1"
redis:1>type name # 查询将键值对的类型
string
redis:1>expire age 15 # 删除键值对
"1"
redis:1>ttl age # 查看剩余过期时间
"5"
redis:1>ttl age
"2"
redis:1>ttl age
"1"
redis:1>ttl age
"-2"
redis:1>get age
null
redis:1>keys * # 查询该数据所有的键值对

String
向指定的key的value后追加字符串

语法: APPEND key value

redis:0>set msg hello
"OK"
redis:0>get msg
"hello"
redis:0>append msg ",world"  # 向msg后追加 信息
"11"
redis:0>get msg
"hello,world"
将指定key的value数值++和–操作

语法: DECR/INCR key

redis:0> set age 20
OK 
127.0.0.1:6379> incr age   # age++
(integer) 21 
127.0.0.1:6379> decr age  # age--
(integer) 20

按指定的步长对数值进行加减

语法: INCRBY/DECRBY key

redis:0> set age 20
OK 
redis:0>incrby age 2 # age+2
"22" 
redis:0>decrby age 10  # age-10
" 12"
为数值加上浮点型数值

语法: INCRBYFLOAT key

redis:0>set age 20
"OK"
redis:0>incrbyfloat age 1.1
"21.1"
获取字符串的长度

语法: strlen key

redis:0>strlen age
"4"
redis:0>
按起止位置获取字符串(闭区间,起止位置都取)

语法: GETRANGE key start end

redis:0>set name asdfghj
"OK"
redis:0>getrange name 1 4
"sdfg"
用指定的value 替换key中 offset开始的值

语法: SETRAGNE key offset value

redis:0>get name
"asdfghj"
redis:0>setrange name 2 *
"7"
redis:0>get name
"as*fghj"
redis:0>
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

语法:getset key value

redis:0>get name
"as*fghj"
redis:0>getset name san
"as*fghj"
redis:0>get name
"san"
redis:0>getset msg 123  # 如果 getset 的是不存在的key , 返回null
null
仅当key不存在时进行set

语法:setnx key value

redis:0>flushall
"OK"
redis:0>setnx msg 123 # 仅对不存在的键值对进行 set
"1"
redis:0>setnx msg 123 # 如果键值对存在, 返回失败
"0"
批量设置键值对

语法:msetnx key1 value1 key2 value2..

redis:0>msetnx k1 v1 k2 v2
"1"
redis:0>keys *
 1)  "k2"
 2)  "k1"
 3)  "msg"
批量获取多个key保存的值

语法: MSET key1 key2 ...

redis:0>mget k1 k2
 1)  "v1"
 2)  "v2"
redis:0>
对象操作

语法: MSET key:{id1}:{filed1} value1 key:{id2}:{filed2} value2

redis:0>mset user:1:name san user:2:age 20
"OK"
redis:0>mget user:1:name user:2:age
 1)  "san"
 2)  "20"
redis:0>
List列表

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

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ymG0fkbY-1666190632547)(images/image-20211015202052785.png)]

Redis中的list是可以双端操作的,相当于双端队列,操作左边Lxxx,操作右边Rxxx,

从左边或者右边添加元素(push)

语法: lpush/rpush key value1 value2...

redis:0>lpush mylist v1 v2
"2"
redis:0>rpush mylist v3
"3"
redis:0>lrange mylist 0 -1
 1)  "v2"
 2)  "v1"
 3)  "v3"
从左、从右获取list元素

语法: lrange key start end

redis:0>lrange mylist 1 2 # 获取 从1到2位数据
 1)  "v1"
 2)  "t2"
redis:0>lrange mylist 0 -1 # 获取整个list
 1)  "t1"
 2)  "v1"
 3)  "t2"
redis:0>
在指定位置插入数据

语法: linsert key before/after pivot value

redis:0>linsert mylist before t1 m1 # 在t1前插入 m1
"4"
redis:0>linsert mylist after t1 m2 # 在 t1 后插入 m2
"5"
redis:0>lrange mylist 0 -1
 1)  "m1"
 2)  "t1"
 3)  "m2"
 4)  "v1"
 5)  "t2"
redis:0>
查看列表长度

语法:llen key

redis:0>llen mylist
"5"
redis:0>
通过索引获取列表元素

语法:lindex mylist index

redis:0>lindex mylist 2
"m2"
redis:0>
通过索引为元素设值

语法:lset key index value

redis:0>lset mylist 2 u1
"OK"
redis:0>lrange mylist 0 -1
 1)  "m1"
 2)  "t1"
 3)  "u1"
 4)  "v1"
 5)  "t2"
redis:0>
移除第一个或最后一个元素

语法:lpop/rpop mylist

redis:0>lpop mylist
"v4"
redis:0>lrange mylist 0 -1
 1)  "v2"
 2)  "t1"
 3)  "v1"
 4)  "t2"
 5)  "v3"
截取指定范围的值

语法:ltrim key start end

redis:0>lrange mylist 0 -1
 1)  "v2"
 2)  "t1"
 3)  "v1"
 4)  "t2"
 5)  "v3"
redis:0>ltrim mylist 1 3
"OK"
redis:0>lrange mylist 0 -1
 1)  "t1"
 2)  "v1"
 3)  "t2" 
从头部或尾部开始删除指定元素

语法:lrem key count value

redis:0>lrange mylist 0 -1
 1)  "t3"
 2)  "t2"
 3)  "m1"
 4)  "t1"
 5)  "u1"
 6)  "v1"
 7)  "t2"
redis:0>lrem mylist 1 t2  # 删除mylist 中的一个 t2 , 从头部开始
"1"
redis:0>lrange mylist 0 -1
 1)  "t3"
 2)  "m1"
 3)  "t1"
 4)  "u1"
 5)  "v1"
 6)  "t2"
redis:0>
redis:0>lrange mylist 0 -1
 1)  "u1"
 2)  "t3"
 3)  "m1"
 4)  "t1"
 5)  "u1"
 6)  "v1"
 7)  "t2"
redis:0>lrem mylist -1 u1  # 删除mylist 中的一个 t2 , 从尾部部开始
"1"
redis:0>lrange mylist 0 -1
 1)  "u1"
 2)  "t3"
 3)  "m1"
 4)  "t1"
 5)  "v1"
 6)  "t2"
redis:0>

注意:

  • cout > 0 , 从头部开始数
  • cout < 0 , 从尾部开始数
List总结
  • list实际上是一个链表before Node after , left, right 都可以插入值
  • 如果key不存在,则创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在
  • 在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
List应用

消息排队!消息队列(Lpush Rpop),栈(Lpush Lpop)

set集合

RedisSetstring类型的无序集合。集合成员是唯一的,这就意味着set集合中不能出现重复的数据。

Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)

向set集合中添加值

语法: sdd key member member2...

redis:0>sadd myset v1
"1"
查询set集合

语法: smembers key

redis:0>smembers myset
 1)  "v3"
 2)  "v2"
 3)  "v1"
redis:0>
返回集合中所有的成员

语法: scrad key

redis:0>scard myset
"3"
查询member元素是否是集合的成员,结果是无序的

语法: sismember key member

redis:0>sismember myset v4
"0"
redis:0>sismember myset v1
"1"
redis:0>
将一个set中的一个元素移动到另一个set集合中

语法: smove key newkey value

redis:0>smove myset newset v2
"1"
redis:0>smembers myset
 1)  "v3"
 2)  "v1"
redis:0>smembers newset
 1)  "v2"
移除集合中的成员

语法: srem key member1 member2...

redis:0>smembers myset
 1)  "v5"
 2)  "v1"
 3)  "v3"
 4)  "v4"
 5)  "v7"
 6)  "v6"
redis:0>srem myset v7 v6
"2"
redis:0>smembers myset
 1)  "v3"
 2)  "v4"
 3)  "v1"
 4)  "v5"
redis:0>
返回所有集合的差集 key1- key2 …

语法: sdiff key1 key2 ...

redis:0>smembers myset
 1)  "v3"
 2)  "t1"
 3)  "v4"
 4)  "v1"
 5)  "v5"
 6)  "t3"
 7)  "t2"
redis:0>smembers newset
 1)  "t5"
 2)  "t4"
 3)  "v2"
 4)  "t1"
 5)  "t6"
redis:0>sdiff myset newset
 1)  "v5"
 2)  "v1"
 3)  "v3"
 4)  "v4"
 5)  "t2"
 6)  "t3"
返回所有集合的交集

语法: sinter key1 key2 ...

redis:0>smembers myset
 1)  "v3"
 2)  "t1"
 3)  "v4"
 4)  "v1"
 5)  "v5"
 6)  "t3"
 7)  "t2"
redis:0>smembers newset
 1)  "t5"
 2)  "t4"
 3)  "v2"
 4)  "t1"
 5)  "t6"
redis:0>sinter myset newset
 1)  "t1"
返回所有集合的并集

语法: sunion key1 key2 ...

redis:0>smembers myset
 1)  "v3"
 2)  "t1"
 3)  "v4"
 4)  "v1"
 5)  "v5"
 6)  "t3"
 7)  "t2"
redis:0>smembers newset
 1)  "t5"
 2)  "t4"
 3)  "v2"
 4)  "t1"
 5)  "t6"
redis:0>sunion myset newset
 1)  "v1"
 2)  "v5"
 3)  "t2"
 4)  "t3"
 5)  "t6"
 6)  "v3"
 7)  "t5"
 8)  "t4"
 9)  "v2"
 10)  "t1"
 11)  "v4"
Hash(哈希)

Redis hash 是一个string类型的fieldvalue的映射表,hash特别适合用于存储对象

Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。

设置hash表中的对象field的值。重复设置同一个field会覆盖,返回0

语法: hset key field value

redis:0>hset myhash name san
"1"
redis:0>hset myhash name dan
"0"
redis:0>
同时设置多个对象到hash表中

语法: hmset key field1 value1 field2 value2

redis:0>hmset myhash age 18 sex 1
"OK"
redis:0>hgetall myhash
 1)  "name"
 2)  "dan"
 3)  "age"
 4)  "18"
 5)  "sex"
 6)  "1"
redis:0>
查看哈希表中所有字段和值

语法: hgetall key

redis:0>hgetall myhash
 1)  "name"
 2)  "dan"
 3)  "age"
 4)  "18"
 5)  "sex"
 6)  "1"
redis:0>
查看哈希表中某个字段和值

语法: hget key field

redis:0>hget myhash name
"dan"
redis:0>
查看哈希表中所有字段(没有值)

语法:hkeys key

redis:0>hkeys myhash
 1)  "name"
 2)  "age"
 3)  "sex"
redis:0>
查看哈希表 key 中,指定的字段是否存在。

语法: hexists key field

redis:0>hexists myhash name
"1"
redis:0>hexists myhash name1
"0"
redis:0>
查看哈希表的所有值(没有字段)

语法: hvals key

redis:0>hvals myhash
 1)  "dan"
 2)  "18"
 3)  "1"
redis:0>
查看哈希表的长度

语法: hlen key

redis:0>hlen myhash
"3"
redis:0>
删除哈希表指定字段

语法: hdel key field1 field2...

redis:0>hdel myhash age sex
"2"
redis:0>hkeys myhash
 1)  "name"
redis:0>
为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段

语法: hincrby key field n

redis:0>hincrby myhash age 4
"16"
redis:0>hget myhash age
"16"
redis:0>
为哈希表 key 中的指定字段的浮点数值加上增量 n。

语法: hincrbyfloat key field n

redis:0>hincrbyfloat myhash age 2.5
"18.5"
redis:0>

Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于对象的存储,Sring更加适合字符串存储!

Zset(有序集合)

不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。

score相同:按字典顺序排序

向有序集合添加一个或多个成员,或者更新已存在成员的分数

语法: zadd key score member1 score2 member2...

redis:0>zadd myzset 68 java 98 c # 向 zset中添加 java 68分,c98 分
"2"
redis:0>
获取有效集合的成员数

语法: zcard key

redis:0>zcard myzset
"2"
redis:0>
计算有序集合中的指定区间的score成员数

语法: zcount key min max

127.0.0.1:6379> ZCOUNT myzset 0 1 # 获取score在 [0,1]区间的成员数量
(integer) 1
有序集合中对指定成员的分数加上增量 n

语法: zincrby key n member

redis:0>zincrby myzset  10 java
"78"
redis:0>
查询有序集合中指定成员的分数值

语法: zscore key member

redis:0>zscore myzset java
"78"
redis:0>
通过索引区间,获取有序集合全部的成员

语法: zrange key 0 -1

redis:0>zrange myzset 0 2 # 获得 1 - 3 区间的成员
 1)  "java"
 2)  "c++"
 3)  "c"
redis:0>zrange myzset 0 -1 # 获得所有 成员
 1)  "java"
 2)  "c++"
 3)  "c"
 4)  "php"
redis:0>
通过字典区间,间返回有序集合的成员

语法: zrangebylex key min max

redis:0>zrangebylex myzset - +
 1)  "java"
 2)  "c++"
 3)  "c"
 4)  "php"
redis:0>
通过字典区间,分页 按索引显示查询记录

语法: zrangebylex key - + limit n m

redis:0>zrangebylex myzset - + limit 0 2  # 查询第一条到第二条数据
 1)  "java"
 2)  "c++"
redis:0>zrangebylex myzset - + limit 2 3  # 查询第2条到第3条数据
 1)  "c"
 2)  "php"
redis:0>
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 显示 3,4,5条记录

"apple"

"back"

"java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员

"abc"

"add"

"amaze"

"apple"

127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员

"apple"

"back"

"java"

通过分数返回指定区间内的成员

语法: zrangebyscore key min max

127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之间的的成员
"m1"
"m3"
"m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
"m1"
"m3"
统计有序集合指定区间的成员数量

语法: zlexcount key - +

redis:0>zlexcount myzset - +
"4"
redis:0>
127.0.0.1:6379> ZLEXCOUNT testset [apple [java

(integer) 3
移除指定范围的成员

语法: zrem key field min max

redis:0>zrem myzset c
"1"
redis:0>
27.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典区间[apple,java]中的所有成员

(integer) 3

127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成员

(integer) 2

127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成员

(integer) 2
按score降序排序,然后按索引,返回结果的

语法:zrevrange key min max

redis:0>zrevrange myzset 0 -1
 1)  "php"
 2)  "c++"
 3)  "java"
按score降序排序,返回分数集中在【min,max】之间的成员

语法:zrevrangebyscore key max min

redis:0>zrevrangebyscore myzset 100 1

 redis:0>zrevrangebyscore myzset 100 1
 1)  "php"
 2)  "c++"
 3)  "java"
按字典倒序 返回集合中(add,java]字典区间的成员
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员
"java"

"back"

"apple"

"amaze
zset应用案例:
  1. set排序 存储班级成绩表 工资表排序!
  2. 普通消息,1.重要消息 2.带权重进行判断
  3. 排行榜应用实现,取Top N测试

五、三种特殊数据类型

Geospatial(地理位置)

使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用

  • geoadd key longitud(经度) latitude(纬度) member [..] 将具体经纬度的坐标存入一个有序集合
  • geopos key member [member..] 获取集合中的一个/多个成员坐标
  • geodist key member1 member2 [unit] 返回两个给定位置之间的距离。默认以米作为单位。
  • georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count] 以给定的经纬度为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
  • GEORADIUSBYMEMBER key member radius... 功能与GEORADIUS相同,只是中心位置不是具体的经纬度,而是使用结合中已有的成员作为中心点。
  • geohash key member1 [member2..] 返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。
有效经纬度
  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。
Hyperloglog(基数统计)

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

因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

Hyperloglog其底层使用string数据类型。

什么是基数?

基数就是数据集中不重复的元素的个数,

(还可以使用set集合来统计不重复元素的个数,因为set集合元素是不重复的)

应用场景:

网页的访问量(UV):一个用户多次访问,也只能算作一个人。

传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,()

Hyperloglog就能帮助我们利用最小的空间完成。

  • PFADD key element1 [elememt2..] 添加指定元素到 HyperLogLog中
  • PFCOUNT key [key] 返回给定 HyperLogLog 的基数估算值。
  • PFMERGE destkey sourcekey [sourcekey..] 将多个 HyperLogLog 合并为一个 HyperLogLog

代码示例

redis:0>pfadd test  a d f g h j k l  # 添加元素到hyperloglog中
"1"
redis:0>type test # 查看类型
"string"
redis:0>pfcount test # 计算基数
"8"
redis:0>pfadd test2 q s f r y h y u i 
"1"
redis:0>pfcount test2
"8"
redis:0>pfmerge test3 test test2 #  合并 test 和 test2 为 test3
"OK"
redis:0>pfcount test3
"14"
redis:0>
BitMaps(位图)

使用位存储,信息状态只有 0 和 1

Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。

应用场景: 签到统计、状态统计

  • setbit key offset value 为指定key的offset位设置值
  • getbit key offset 获取offset位的值
  • bitcount key [start end] 统计字符串被设置为1的bit数,也可以指定统计范围按字节
  • bitop operration destkey key[key..] 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
  • BITPOS key bit [start] [end] 返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位

代码示例


redis:0>setbit sign 0 1	# 星期一 正常

"0"
redis:0>setbit sign 1 1 # 星期二 正常

"0"
redis:0>setbit sign 2 0 # 星期三 缺勤

"0"
redis:0>setbit sign 3 1 # 星期四 正常

"0"
redis:0>setbit sign 4 0 # 星期五 缺勤

"0"
redis:0>setbit sign 5 1 # 星期六 正常

"0"
redis:0>setbit sign 6 0 # 星期天 缺勤

"0"
redis:0>type sign # bitMap 的数据类型为 String
"string"
redis:0>getbit sign 5 # 得到某一天的情况

"1"
redis:0>bitcount sign # 统计一个星期之内的考勤情况 

"4"
redis:0>

六、redis事务

事务本质: 一组命令的集合,事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。

事务具有: 一次性 、顺序性、排他性

redis事务特点:

  • Redis的单条命令是保证原子性的,但是redis事务不能保证原子性
  • Redis事务没有隔离级别的概念
Redis事务操作过程
  • multi开启事务
  • 命令入队
  • exec执行事务

注: 所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec)一次性完成。

创建事务
redis:0>multi # 开启事务
"OK"
redis:0>set k1 v1 # 入队列
"QUEUED"
redis:0>set k2 v2 # 入队列
"QUEUED"
redis:0>get k2 # 入队列
"QUEUED"
redis:0>keys * # 
"QUEUED"
redis:0>exec # 执行事务
 1)  "OK"
 2)  "OK"
 3)  "OK"
 4)  "OK"
 5)  "OK"
 6)  "v2"
 7)  "OK"
 8)    1)   "k2"
  2)   "k1"

 9)  "OK"
取消事务
redis:0>multi 
"OK"
redis:0>set h1 v1
"QUEUED"
redis:0>discard  # discard 取消事务
"OK"
redis:0>exec
"ERR EXEC without MULTI"
redis:0>get h1
null
事务错误

事务可能遇到错误

  • 代码语法错误(编译时异常)所有的命令都不执行,如:入队命令为错误命令
  • 代码逻辑错误 (运行时异常)**其他命令可以正常执行 ** , 因此redis不保证原子性
事务错误 - 代码语法错误,所有的命令都不执行
redis:0>multi 
"OK"
redis:0>set h1 v1
"QUEUED"
redis:0>e r  #### 错误语法
"ERR unknown command 'e'"
redis:0>exec ### 执行失败
"EXECABORT Transaction discarded because of previous errors."

代码语法错误,所有的命令都不执行

事务错误 - 代码逻辑错误,其他命令可以正常执行
redis:0>multi
"OK"
redis:0>incr k1   # 给String ++ 逻辑错误
"QUEUED"
redis:0>set h1 v1 
"QUEUED"
redis:0>exec
 1)  "OK"
 2)  "ERR value is not an integer or out of range"
 3)  "OK"
 4)  "OK"
 5)  "OK"
redis:0>

代码逻辑错误,其他命令可以正常执行, 说明Redis事务不保证原子性

七、乐观锁、悲观锁

锁的目的:

实现并发控制: 保证在并发情况下数据的准确性

如何实现并发控制

实现并发控制的主要手段分为乐观并发控制悲观并发控制两种。

悲观锁
悲观锁概念

​ 在修改数据时,为了避免其他人修改,直接对数据进行加锁,以防止数据被其他人修改。这种先锁定,再修改数据的方式就是悲观锁。

悲观锁特点

悲观锁特点

​ 悲观锁,具有强烈的独占排他特性。

为什么要称之为悲观锁

​ 之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。

悲观锁实现
  • 传统的关系型数据库使用这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。
  • Java 里面的同步 synchronized 关键字的实现。synchronized
两种悲观锁

共享锁(S锁、读锁),加了共享锁只能读,不能写,只能加共享锁

排他锁(X锁、写锁), 加了排他锁不能在加其他锁,不能读,不能写

悲观锁缺点

悲观并行控制,即悲观锁,采用先上锁再访问 的原则。为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

  • 加悲观锁产生额外开销
  • 加悲观锁,死锁几率变大
  • 加悲观锁,降低并行度
乐观锁
乐观锁概念

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RhuRl4d4-1666190632547)(images/image-20211018145039189.png)]

乐观锁的实现

​ 乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。(乐观锁没有使用数据库本身的一些锁,去实现乐观锁

  • CAS 实现:Javajava.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
  • **版本号控制:**一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功
乐观锁问题

· 乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

八、redis 中的watch key实现乐观锁

使用watch key监控指定数据,相当于乐观锁加锁。

实现方法

​ 对需要上锁的key添加watch key即可

> watch key
实例,对支付过程进行加锁,无插队情况
redis:0>set money 100  # 账户的钱
"OK"
redis:0>set out 0   # 开销的钱
"OK"
redis:0>watch money  # 对账户key加锁
"OK"
redis:0>multi # 创建事务
"OK"
redis:0>decrby money 20
"QUEUED"
redis:0>incrby out 20
"QUEUED"
redis:0>exec  # 执行
 1)  "OK"
 2)  "80"
 3)  "OK"
 4)  "20"
 5)  "OK"
redis:0>
模拟线程插队情况

线程1,事务先不执行

redis:0>get money
"170"
redis:0>watch money
"OK"
redis:0>multi 
"OK"
redis:0>decrby money 50
"QUEUED"
redis:0>incrby out 50
"QUEUED"
redis:0>  #### ===> 先不执行事务 

线程2,事务修改money的值

redis:0>incrby money 100  # 充值
"270"
redis:0>

线程1, 执行失败

redis:0>exec

redis:0>get money  # 执行失败, money还是 线程2修改的值
"270"
redis:0>
解除watch锁
unwatch key

九、Jedis

Jedis是以前java操作redis的方法,使用Java来操作RedisJedisRedis官方推荐使用的Java连接redis的客户端。已经过时,现在使用springboot来连接redis

Jedis连接redis

导入依赖

 <dependencies>
        
        <dependency>
            <groupId>redis.clientsgroupId>
            <artifactId>jedisartifactId>
            <version>3.2.0version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.70version>
        dependency>

    dependencies>

编写测试连接类

public class JedisTest {
    public static void main(String[] args) {
        // 创建 jedis对象 连接
        Jedis jedis = new Jedis("8.140.106.149",6379);
        // 认证密码, 如果没有设置密码,则不需要认证
        jedis.auth("123456");
        System.out.println(jedis.ping());
    }
}

注:

连接成功得到 jedis对象后,java操作 redis和在终端上是一模一样的

jedis测试事务
public class JedisTest {

    public static void main(String[] args) {
        // 创建 jedis对象 连接
        Jedis jedis = new Jedis("8.140.106.149",6379);
        // 认证密码
        jedis.auth("123456");
        //测试连接
        System.out.println(jedis.ping());
        //清空默认redis默认数据库
        jedis.flushDB();
        // 创建需要添加的内容, 需要转化为json
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","word");
        jsonObject.put("name","san");
        String result = jsonObject.toJSONString();
        //开启事务
        Transaction multi = jedis.multi();
        try{
            multi.set("user1",result);
            multi.set("user2",result);
            multi.exec();
        }catch (Exception e){ //如果出现异常,放弃事务
            multi.discard();
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close(); // 关闭连接
        }

    }
}

十、SpringBoot整合redis

采用jedis的问题
  • jedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式**(BIO,同步阻塞IO模式,数据的读写必须在一个线程完成之后才能执行**)
  • lettuce : 采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式**(NIO,同步非阻塞IO模式,NIO使用单线程或者只是用少量的多线程,多链接使共用一个线程。)**

说明: 在 SpringBoot2.x 之后,原来使用的jedis 被替换为了 lettuce?

两种通信模式BIO和NIO

https://www.cnblogs.com/xycq/articles/13885013.html

阅读源码

我们在学习SpringBoot自动配置的原理时,整合一个组件并进行配置一定会有一个自动配置类xxxAutoConfiguration,并且在spring.factories中也一定能找到这个类的完全限定名。Redis也不例外。

找到Redis的自动配置类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-euBcPiAD-1666190632547)(images/image-20211019101340388.png)]

ctrl+f搜索Redis,再按ctrl+鼠标右键即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUjEqT98-1666190632548)(images/image-20211019101434730.png)]

分析RedisAutoConfiguration自动配置类
// proxyBeanMethods = true 或不写,是Full模式
// proxyBeanMethods = false 是lite模式
// 不带@Configuration的类叫Lite配置类
// 1. Full模式下,通过方法调用指向的仍旧是原来的Bean
// 2. lite模式下,Spring 5.2.0+的版本,建议你的配置类均采用Lite模式去做,即显示设置proxyBeanMethods = false。Spring Boot在2.2.0版本(依赖于Spring 5.2.0)起就把它的所有的自动配置类的此属性改为了false,即@Configuration(proxyBeanMethods = false),提高Spring启动速度
@Configuration(
    proxyBeanMethods = false
)
// 某个class位于类路径上,才会实例化一个Bean
@ConditionalOnClass({RedisOperations.class})
// @EnableConfigurationProperties注解的作用是:使使用 @ConfigurationProperties 注解的类生效。
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    // ConditionalOnMissingBean : 当这个bean(RedisTemplate)不存在的时候,下面的RedisTemplate就会生效,也就是如果你自己写了一个  redisTemplate 那么这个 redisTemplate 就会失效。
    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    // String 类型比较常用,所以单独定义一个StringRedisTemplate 
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

引入了redis的配置文件RedisProperties,可以发现大对数配置参数都在其中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r7YDPNQe-1666190632548)(images/image-20211019101923144.png)]

RedisConnectionFactory接口解析
public interface RedisConnectionFactory extends PersistenceExceptionTranslator {
    RedisConnection getConnection();

    RedisClusterConnection getClusterConnection();

    boolean getConvertPipelineAndTxResults();

    RedisSentinelConnection getSentinelConnection();
}

实现该接口有jedis方法和lettuce方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RsZeK0l0-1666190632548)(images/image-20211019105301158.png)]

整合Redis测试连接
引入依赖
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
配置连接参数
spring.application.name=jedis-springboot
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123456
编写测试连接代码
@SpringBootTest
class JedisSpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        // redisTemplate 操作不同的数据类型,api和我们的指令是一样的
        // opsForValue 操作字符串 类似String
        // opsForList 操作List 类似List
        // opsForSet
        // opsForHash
        // opsForZSet
        // opsForGeo
        // opsForHyperLog

        // 除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD

        // 获取连接对象
        //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //connection.flushDb();
        //connection.flushAll();
        redisTemplate.opsForValue().set("name","san");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

}

// 控制台输出
san
redis中查看数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njsGTTeO-1666190632548)(images/image-20211019121225744.png)]

​ 此时我们回到Redis查看数据时候,惊奇发现全是乱码,可是程序中可以正常输出。这时候就关系到存储对象的序列化问题,在网络中传输的对象也是一样需要序列化,否者就全是乱码。

RedisTemplate内部的序列化配置是这样的 ,默认的序列化器是采用JDK序列化器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56qjInUU-1666190632549)(images/image-20211019121515257.png)]

后续我们定制RedisTemplate就可以对其进行修改。

RedisSerializer提供了多种序列化方案:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DJa3Oxmn-1666190632549)(images/image-20211019121625467.png)]

编写自己的 RedisTemplete
@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 我们为了自己开发方便,一般直接使用 
        RedisTemplate<String, Object> template = new RedisTemplate<String,
                Object>();
        template.setConnectionFactory(factory);
        // 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;
    }
}
需要导入依赖
<dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
            <version>2.10.0version>
        dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-annotationsartifactId>
            <version>2.10.0version>
        dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-coreartifactId>
            <version>2.10.0version>
        dependency>
测试连接redis
@SpringBootTest
class JedisSpringbootApplicationTests {

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("name","san");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

}

你可能感兴趣的:(Redis,笔记,redis,学习,数据库)