手把手实战 Redis 教学

2022 年什么会火?什么该学?本文正在参与“聊聊 2022 技术趋势”征文活动

Redis 基础入门

简介:

Redis(Remote Dictionary Server 远程字典服务) 使用 C 语言编写的,开源的 高性能 非关系型的键值对数据库

  • Redis 可以存储键和五种不同类型的值之间的映射 K, V

    值支持五种数据类型:字符串、列表、集合、散列表、有序集合

  • 与传统数据库不同的是 Redis 的数据是存在内存中的,所以:读写速度非常快 因此 redis 被广泛应用于缓存方向

  • Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案;

  • Redis是单线程+多路IO复用技术

Redis 端口:6379,默认16个数据库,类似数组下标从0开始,初始默认使用0号库:所有库同样密码

优点:

  • Redis 是基于内存进行的直接操作,因此读取速度非常快: 读:11w次每秒 写:8w次每秒 有效处理程序:高性能 高并发
  • 虽然Redis 是基于内存的操作,同时为了保证数据安全可靠,会定时对数据进行持久化:RDB AOF 两种持久化方式
  • 支持事务,丰富数据结构,支持多种集群操作

缺点:

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写 Redis适合的场景主要局限在较小数据量的高性能操作和运算上

Redis 概述安装:

Windows 版本:下方网盘连接中提供解压包,解压即用… 可以学习使用.

实际工作中,都是使用linux 的更多,所以这里顺便,记录学习下Redis Linux服务安装:

安装 C语言环境:

因为,Redis 是 C语言开发的,所以,运行需要C语言的开发环境需要安装

C语言环境安装:

# 查看 C语言环境版本: Centos7默认gcc 版本是4.8.3 安装redis6,gcc版本一定要5.3以上
# Red Hat 为了软件的稳定和版本支持,yum 上版本也是4.8.3 所以不建议使用:yum install gcc-c++ 下载/更新
gcc --version

# 安装scl源
# 是为了给 RHEL/CentOS 用户提供一种以方便、安全地安装和使用应用程序和运行时环境的多个版本的方式,同时避免把系统搞乱
# yum 下载过程中需要,确定操作一下,输入 y 回车
yum install centos-release-scl scl-utils-build
# 安装8版本的gcc、gcc-c++、gdb工具链
yum install -y devtoolset-8-toolchain
# 启动...
scl enable devtoolset-8 bash

gcc --version 或 gcc -v 查看 C语言环境的版本

Linux安装 Redis:

① 下载安装包:官方网址🚀 点击页面的,Download 下载最新版本的安装包

② 通过工具:WinSCP等工具将,安装包上传到Linux 上: 随便上传一个路径就可以了 /usr/wsm

③ 解压,配置,启动运行:

# ① 进入上传目录,解压Redis 压缩文件
tar -zxvf redis-6.2.1.tar.gz

# ② 进入解压后的Redis 文件目录: 执行 编译安装...
cd redis-6.2.1
# 	make C语言的命令对文件进行编译,如果没有C语言环境是会出错的,需要执行: make distclean 清理错误
make
# 	正常情况编译后会提示:make test 测试,可以忽略直接进行安装:
make install

# ③ 最开始上传的只是一个安装包,通过make 命令进行了安装,Linux 默认安装程序的目录是:/usr/local/bin
#	进入Linux软件安装目录,查看安装的Redis服务
cd /usr/local/bin
ls
#	Redis服务介绍:
# 	redis-benchmark:	 性能测试工具,可以在自己本子运行,看看自己本子性能如何
# 	redis-check-aof:	修复有问题的AOF文件,rdb和aof后面介绍
# 	redis-check-dump:	修复有问题的dump.rdb文件
# 	redis-sentinel:		Redis集群使用(哨兵模式)
#	redis-server:		Redis服务器启动命令
# 	redis-cli:			客户端,操作入口

# ④ 启动redis服务 /usr/local/bin 目录下执行:
redis-server
# 	可以看到Redis 服务器的:ip 端口6379
16999:C 26 Jan 2022 22:56:23.819 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
16999:C 26 Jan 2022 22:56:23.819 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=16999, just started
16999:C 26 Jan 2022 22:56:23.819 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
16999:M 26 Jan 2022 22:56:23.820 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.2.1 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 16999
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

16999:M 26 Jan 2022 22:56:23.820 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
16999:M 26 Jan 2022 22:56:23.820 # Server initialized
16999:M 26 Jan 2022 22:56:23.820 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
16999:M 26 Jan 2022 22:56:23.820 * Loading RDB produced by version 6.2.1

Redis高级配置:

上面已经将 Redis 服务安装完毕:

  • 我们可以通过 redis-server 可以启动redis服务,但有些效果并不方便,可以通过修改 redis.confRedis配置文件设置Redis服务的配置

  • 为了方便操作,建议将 /usr/wsm 上传的安装包路径中配置文件 拷贝一份在方便操作的目录:

    cp /usr/wsm/redis6.2.1/redis.conf /MyRedis/ 将linux 目录下文件,复制一份到 /MyRedis 目录下方便操作更改;

  • 在Redis 启动服务时候可以指定,启动的配置文件来进行Redis 设置:

    redis-server /MyRedis/redis.conf 通过指定路径下配置文件,来启动加载Redis 服务

Redis 后台启动:

  • redis 默认不是后台启动的,在服务上操作,会一直占着页面,不方便后面的操作… 需要将Redis 设置为:后台启动

  • vim redis.confdaemonize no 更改为:daemonize yes 此时Redis 为后台启动

Redis 远程访问:

  • 在服务器上并不方便操作,Redis命令官方提供一个远程连接的工具:RedisDesktopManager可以在Windows上操作远程Redis

  • redis默认只允许本地访问,要使redis可以远程访问可以修改redis.conf

    注释掉:bind 127.0.0.1可以使所有的ip访问redis

  • 在redis3.2之后,redis增加了protected-mode,在这个模式下,即使注释掉了bind 127.0.0.1,再访问redisd时候还是报错

    修改 redis.conf:protected-mode no 关闭保护模式

Redis 启动:

  • Redis 启动成功: redis-cli 可以在服务器启动客户端操作Redis命令;
  • 开启了远程连接之后: 也可以在 Windows 上直接操作, Linux上的Redis~

a06d17acd399e958c4c09f5b84ee8f5

Redis服务关闭:

服务根路径执行: redis-cli shutdown 或,在客户端: 127.0.0.1:6379> shutdown

Linux查看Redis 服务是否启动:

# 检查Linux 中是否有Redis 线程存活:
ps -ef|grep redis
# 存在:检查到6379...
root     26537     1  0 00:28 ?        00:00:00 redis-server *:6379
root     26576 17279  0 00:28 pts/2    00:00:00 grep --color=auto redis
# 不存在:没有找到运行端口
root     26467 17279  0 00:27 pts/2    00:00:00 grep --color=auto redis

设置密码:

这个非常重要, 我学习的时候使用的是 阿里服务器上面搭建的Redis

开始着只是为了学习,随便搞搞,没想到因为没有密码第二天就被 黑客攻击了,用我的 一核两G 去挖矿? 人都傻了,赶紧上一个密码 要复杂一点哟,还有我的一下文章可能会暴漏服务器ip 大家手下留情😶

Redis 设置密码:

  • 持久密码: 通过设置 redis.conf 配置文件来设置密码,设置之后需要重启更新配置文件… 每次启动都是这个密码

  • 临时密码: 不在 redis.conf 中设置,而是每次启动服务的时候设置一次临时密码,同样,重启服务就没有密码…

持久密码:

  • redis.conf 文件中: requirepass 参数设置Redis 登录密码
  • 示例: requirepass 123 设置Redis持久密码为:123

临时密码:

  • 每次服务启动的时候,如果需要设置密码时使用:
  • redis-cli 建立连接之后执行: config set requirepass 123456 设置临时密码为 123456,临时|持久 密码都存在时候,就近原则 使用临时密码🤯

密码登录:

  • 设置密码之后的控制台登录就需要输入密码了: 不然 Redis 的任何命令都执行不了了.
  • auth <password> 就可以完成登录✔

基本命令:

-------------------------------------------------------------
# 测试连接 返回pong 连接成功
	ping
# 切换数据库,Redis 初始化具有16个数据库,默认使用第 0 个库,可以通过 select index 进行切换
	select index 		# index 	指0-15 16个库下标
# 查看当前下标库大小
	dbsize
# 清空当前数据库所有数据
	flushdb
# 清空Redis所有数据库数据 (16个数据库
	flushall

-------------------------key操作:增删改查...------------------------------
# 查看所有的key
	keys *
# 查看指定key 是否存在
	exsist 	k			# 1存在 0不存在
# 查看当然key 是什么类型
	type 	k
# 设置指定key 的存活时间 单位秒;
	expire	k	s
# 让设置了定时的key 还未死亡,重新设置永久存活
	persist k
# 查询指定key 的存活时间
	ttl		k			# -1表示永不过期,-2表示已过期|不存在,返回剩余秒数
	
# 添加一组 k v 存储数据,对相同的key多次设置值,会直接覆盖 
	set 	k 	v
# 查看指定 k 的 v
	get 	k
# 当前数据库中删除
	del 	k	
# 仅将key 从keyspace元数据中删除,真正的删除会在后续异步操作(非阻塞删除
	unlink	key
# 修改一个 k 的名字	
	rename 	k1	k2		# 更改k1 的名字为k2
						# 如果试图修改一个不存在的key 将会报出错误
						# 要更改的 k2 已经存在,拥有一个v值, 会将 k1 的值赋值给 k2 k1消失
						
# renamenx 安全性修改: 只有要修改的 k名不存在,才可以进行修改 1成功 0失败
	renamenx k1  k2
# 随机返回Redis一个key
	randomkey

以上基础操作非常简单,多多练习即可!

常用的五大基本数据类型:

字符串 String

String是Redis最基本的类型,因为Redis 是k, v 存储的所以可以理解为:是一个HashMap<String,String>

  • String 是二进制存储的:

    意味着Redis的string可以包含任何数据比如:jpg图片或者序列化的对象

    String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M.

常用命令:

<必须> [可选]

  • Redis 下标都是从 0开始计算的
# 新增一个元素 
set 		<k> 	<v> 	[NX|XX|EX|PX]				#添加一对 k v 结构数据,如果相同的k 后面会覆盖前面的数据
														# NX:当数据库中key不存在时,可以将key-value添加数据库
                                            			# XX:必须key存在!不然无效命令,可以将原先 k v 覆盖)与NX参数相反;
			                                            # EX:key的超时秒数
			                                            # PX:key的超时毫秒数,与EX互斥
			                                            ....  省略参数....配置
# 只有key不存在的时候才可以设置值			                                            
sernx		<k>		<过期时间>		<v>
# 设置key同时设置过期时间
setex		<k>		<v>
# 设置更新key的值,并返回旧的数据: k必须是实现存在的,进行修改;
getset		<k>		<v>
# 查询一个key 的值,不存在返回 nil Redis的空标识不存在;
get 		<k>

-------------------------字符功能命令:-----------------------------------------
# 获得值的范围数据,类似java中的substring 前包 后包
getrange	<k>		<起始位置>	<结束位置>				   #参数必须得填,且是数值类型... 第一个位置0 最后一个位置-1(倒数第二位置-2)
# 用 <value>  覆写 <key> 所储存的字符串值.
setrange	<k>		<起始位置>	<value>					  #从字符 起始位置开始,到 value 字符长度的一段都被覆盖...
# 给指定key的字符串值,追加一下字符数据;
append 		<k> 	<v>
# 获取key的字符长度
strlen		<k>	
-------------------------原子性操作:-------------------------------
# 只能对数字值操作将 key 中储存的数字值增1 如果为空,新增值为1 (原子性)
incr 		<k>
# 只能对数字值操作将 key 中储存的数字值减1 如果为空,新增值为-1 (原子性)
decr		<k>
# 将 key 中储存的数字值增减自定义步长,步长要是数值类型,且必须得写!
incrby		<k>		<步长>
decrby		<k>		<步长>
# 同时设置一个或多个 k-v 数据 (原子性:一个失败都失败!)
mset		<k1>	<v1>	<k2><v2><k3><v3>...
# 同时设置一个或多个 k-v 数据,且所有的key都是实现不存在的 (原子性:一个失败都失败!)
msetnx		<k1>	<v1>	<k2><v2><k3><v3>...
# 同时获取多个 k 的值 (原子性:一个失败都失败!)
mget		<k1><k2><k3>...

原子性:

incr | decr 是原子性操作:

  • 原子操作是指不会被线程调度机制打断的操作

  • 这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 切换到另外一线程

    单线程中:能够在单条指令中完成的操作都可以认为是"原子操作"

    多线程中:不能被其它进程(线程)打断的操作就叫原子操作

  • Redis单命令的原子性主要得益于Redis的单线程

java中的i++是否是原子操作:

  • 答:

    i++ 并不是原子性操作 i++ 本质被分为: i=i+1

数据结构:

Redis: String的数据结构为简单动态字符串

  • 类似于 Java 的Arraylist 采用,预分配 动态扩容 方式来减少内存的频繁分配

    当字符串长度小于1M时,扩容都是加倍现有的空间 乘2

    字符长度超过 1M 时,每次扩容只会扩容 1M 需要注意的是字符串最大长度是 512M

列表 List

Redis 的列表类型,类似于Java 的List 集合:有序 不唯一 无限长度 的一个数据集合

  • 同时它的存储方式又像一个 队列:先进先出原则

    你可以添加一个元素到队列 l头部 r尾部 lpush|rpush 往队列中添加数据 lpop|rpop 从队列中取出一个数据

    image-20220129114047308

  • 它底层是一个 双向列表 队列 左右都支持,存取元素,元素一旦被获取就会消失...自动从队列中消息(就像真的被取走了一样) 可以用它实现简单的:发布订阅

  • 当一个队列 的所有值被取走,Redis 的这个k 就会消失… 这样的结构就像是Java的:HashMap<String,List>

常用命令:

# 从左边插入一个或多个值
lpush			<k>		<v>		<v2><v3>...
# 从右边插入一个或多个值
rpush			<k>		<v>		<v2><v3>...
# 从左边获取一个值
lpop 			<k>
# 从右边获取一个值,当一个list 中所有的值获取完了,redis对应的k 也就消失了(值在键在,值亡键亡) 
rpop			<k>
# 从k1(list) 右边获取一个值,往 k2(list) 左边插入~ 只有 rpoplpush 没有 lpoprpush...
rpoplpush		<k1> 	<k2>

-----------------------------功能命令:--------------------------------
# 根据下标显示 k(list) 中多个元素 start stop都是数值类型(0起始 -1倒数第一个下标 -2倒二) 该指令查看并不会移除元素; 
lrange			<k>		<start>		<stop>		
# 根据下标显示 k(list) 中元素,并不会删除元素... (index数值类型)
lindex			<k>		<index>
# 获取 k(list) 中元素数量
llen			<k>
# 在指定的 k(list) 某个元素 'Before之前|After之后' 插入新的值:
linsert			<k>		Before|After	<v1>	<v2>		# 在 v1 的前面或后面插入v2 相同的值,以最左边为准
# 移除 K(list) 中 指定数量count 的 v 匹配的元素, 从左往右移除...
lrem			<k>		<count>			<v>					# 返回移除的数量: count>0从头往下找匹配删除 count<0从下往上匹配 count=0全部匹配的删除
# 将 k(list) 指定下标index 的元素,替换成 v
lset			<k>		<index>			<v>					# 下标长度存在回报错.

数据结构:

Redis 中的 list数据结构为:quickList

quickList 是由多个 zipList 组成的一个链表

  • **因为List 是一个双向列表为了节省空间,普通的链表需要附带 指针 空间消耗太大 **
  • 对于少量的数据都是直接创建一个,连续的存储空间 zipList, 多个zipList 组成了 quickList 就是Redis List的数据结构

无序集合 Set

Redis Set 功能与 List类似 特殊之处:set是可以自动排重

  • 当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择
  • set 还提供了一个:判断某个成员是否在一个set 集合中 的一个重要接口 这个List 很难实现的.

Redis的Set是string类型的无序集合:对应Java HashMap< String,Set<String> >

  • 它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1) 随着数据增加,查找数据时间不变;

常用命令:

Set 不像 List 取值不会删除值

-----------------------------增删改查----------------------------------
# 将一个或多个元素v 加入到集合 k 中,已经存在的元素将被忽略
sadd				<k>		<v1>	<v2><v3>...
# 查看该集合 k(set) 的所有值, set 并不会删除值
smembers 			<k>		
# 随机从集合 k(set) 获取count个值,并不会删除值
srandmember			<k>		<count>
# 随机从集合 k(set) 中获取(吐)一个值, 会移除一个元素
spop 				<k>
# 删除集合k(set) 中的 一个|多个指定 v 返回删除成功的元素个数;
srem				<k>		<v1>	<v2><v3>...

-----------------------------功能命令:--------------------------------
# 判断该 k(set) 是否存在该 value 值:(存在1) (不存在0)
sismember			<k>		<v>
# 返回该集合元素个数 k(set), 不存在的k 回返回0, 类型不是set的k 会报错;
scard				<k>
# 把集合元素从一个集合 k1(set) 中一个值移动到另外一个 k2(set)集合;
smove				<k1>	<k2>	<v>				# 将k1中的v移动到 k2中去,k1中没有v 则0失败
# 返回两个|多个集合中的交集: 多个集合中都有的元素
sinter				<k1>	<k2>	<k3>...
# 返回两个|多个集合中的并集: 多个集合中所有的元素,但相同的值只出现一次
sunion				<k1>	<k2>	<k3>...
# 返回两个|多个集合中的差集: 多个集合互相没有重复的元素
sdiff				<k1>	<k2>	<k3>...

数据结构:

Set数据结构是dict字典,字典是用哈希表实现的 Java中HashSet的内部实现使用的是HashMap

Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值

哈希 Hash

Redis hash 是一个键值对集合: 是一个string类型的field和value的映射表

  • hash特别适合用于存储对象 类似Java里面的 HashMap< String,HashMap<String,Object> >

  • 通过Redis 的Hash 可以很好的表示一个对象:

    对象的id为 Redis k 对应的 value 又是一个 HashMap K属性 V属性值

常用命令:

# 创建一个 k(HashMap<s,o>) 并设置一个|多个 field-value
hset			<k>		<f1>		<v1>	<f2><v2>...		# 返回新增行数,已经存在的 f 会覆盖之前的数据,不会返回新增行数
# 批量设置 k(HashMap<s,o>) 的值
hmset			<k>		<f1>		<v1>	<f2><v2>...		# 返回执行ok|失败	
# 根据 k 和 fieid 获取到集合 并根据k 获取到对应的value
hget			<k>		<f>									# 并不会移除元素,如果k 或 f不对返回 nil
# 根据 k 的 fieid 批量获取 k 集合中对应数据
hmget			<k>		<f1>		<f2>	<f3><f4>...		# 如果f 在k 中没有对应的值,则只是该 fieid列返回nil

# 查看一个 k(HashMap<s,o>) 的所有 fieid
hkeys			<k>
# 查看一个 k(HashMap<s,o>) 的所有 value
hvals			<k>
# 对指定的 k(HashMap<s,o>) 某一个fieid 数值类型进行 + - 增量;
hincrby  		<k>		<f>			<number>				# f number 必须是一个数值类型:number是正数就+ 负数就-
# 设置 k(HashMap<s,o>) 中的 fieid 必须事先不存在,才能创建成功: 以前都是直接覆盖,现在就可以避免值被随便覆盖;
hsetnx			<k>		<f>			<v>

数据结构:

Hash类型对应的数据结构是两种:

  • ziplist(压缩列表) field-value长度较短且个数较少时 否则使用:hashtable

有序集合 Zset

Redis有序集合zset与普通集合set非常相似: 都是一个没有重复元素的字符串集合

  • 不同之处在于,Zset 有序集合的每个成员都关联了一个 评分 score

    Redis 通过这个评分 对每个元素进行 从低到高 排序集合中的成员 集合中的 value 是唯一的,但是评分是可以重复的.

  • 通过评分,Zset 集合中的元素是 有序的 可以根据 score评分 position次序 来获取一个评分范围的元素.

  • 访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表

有序集合可以用于实现,排行榜等功能

常用命令:

# 添加一个|多个元素.... 并设置评分 score 简称 s
zadd 			<k>		<s>		<v>		<s2>	<v2>...
# 返回有序集合中指定评分范围的数据, witchscores 简写 ws
zrange			<k>		<start>	<stop>	[ws]					# start stop 都是数值类型,起始|结束评分 [ws] 可选返回数据value同事返回对应的score
# 查看所有的数据,从小到大排序,[limit offset count] 分页展示
zrangebyscore 	<k>		<min><max>	[ws][limit offset count]	# min max 都是数值类型,评分的范围~
# 查看所有的数据,从大到小排序
zrevrangebyscore<k>		<max><min>	[ws][limit offset count]

# 为某个元素的 v 添加评分 score
zincrby			<k>		<number>	<v>							# number 是添加的评分数
# 根据value 删除集合的元素
zrem			<k>		<v>
# 统计该集合,分数区间内的元素个数
zcount			<k>		<min><max>								# min~max 区间的评分数据合
# 返回该值在集合中的排名,从0开始 从小到大排序;
zrank			<k>		<v>

数据结构:

SortedSet(zset)是Redis提供的一个非常特别的数据结构:

zset底层使用了两个数据结构

  • Hash

    hash的作用就是关联元素value和权重score,保障元素value的唯一性; 可以通过元素value找到相应的score值 HashMap<v,s>

  • 跳跃表

    跳跃表的目的在于给元素value排序,根据score的范围获取元素列表. 根据score 生产跳跃表给value 进行排序

跳跃表:

  • 有序集合在生活中比较常见

    例如根据成绩对学生排名,根据得分对玩家排名等

    对于有序集合的底层实现,可以用数组、平衡树、链表等…

    数组不便元素的插入、删除 平衡树或红黑树虽然效率高但结构复杂 链表查询需要遍历所有效率低 Redis采用的是跳跃表

跳跃表 类似于二分发是一种算法: 空间换时间

跳表,是基于链表实现的一种类似“二分”的算法。它可以快速的实现增,删,改,查操作

在这里插入图片描述

正常查询 16 需要依次比较 10次

在这里插入图片描述

而使用 跳跃表:我们每隔一个节点就提取出来一个元素到上一层,把这一层称作索引 其中的down指针指向原始链表

每次查询先比较最上层的索引间隔,通过down 到下一层索引继续比较,知道比较对的结果…

  • 第一次比较 1>16 不成立比较 7>16 不成立13>16 不成立—>往后没有了down 往下 第一级索引
  • 17>16 成立则 从13 down往下 原始链表:16==16 比较了5次大大提高了性能!

但这种,方式是以空间换取时间,随着数据增大 索引也会越来越多,上级的索引是下级索引的索引~

Redis 发布订阅:

Redis 发布订阅 pub/sub 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息 Redis 客户端可以订阅任意数量的频道

  • Redis 客户端可以 发布消息 也可以订阅消息

    发消息客户端,通过往固定的 频道 上发布消息. publish 频道名 发送的消息

    接收消息客户端,可以订阅 一个|多个 频道 subscirbe 频道1 频道2 频道3... 多个客户端可以订阅同一个 频道 类似于MQ的 Fanout交换机

9de367ceef6f6b99d58fc9c1221fe4b

WSM1 对频道 www 发布消息 hello

WSM2 WSM3 订阅 www 评到,可以事实监听到消息订阅…

Redis 新数据类型

Redis6中除了有5中基本的数据类型外,还有另外3中新数据类型: BitMaps HyperLogLog Geospatial

BitMaps

现代计算机用二进制(位) 作为信息的基础单位, 1个字节byte 等于8位bit 每个位标识一个二进制标识 0 1

  • 例如:abc 是由三个字节组成 3byte 等于 24个位bit

    a ASCLL码97——> 01100001

    b ASCLL码98——> 01100010

    b ASCLL码99——> 01100011

Redis提供了Bitmaps这个“数据类型”可以实现对位的操作: 其实本质上还是 k-v 字符串

  • Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同.

  • 把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储 0 和 1 数组的下标在Bitmaps中叫做偏移量

    k					v
    key名				"01100001011000100"  		#本质上就是一个字符串,但Redis可以对每个"位" 进行操作; 
    # 可能这样还是很懵,可以把它当作一个int数组,只允许存储 0|1 而且长度无限...
    # 这就是BitMaps的数据结构,而这个 0|1 通过某些场景定义可以解决很多业务场景... 签到打卡
    
    # 设置一个key zhangsan20220201 张三2022年2月1日打卡报表;
    # v 每一位默认都是0张三第一天打卡就会 setbit 设置数组下标0值1 表示打卡成功!每天如此: 打卡设置对应天数-1 数组下标赋值;
    # 假设一个月后这个key: 0110001011000... 
    # 只需要统计这个 key 中有多少次 1 就知道张三这个月打了多少次卡! 节省内存空间,而且Redis对于BitMaps 还有和很多这种方法;
    

    image-20220201104021992

常用命令:

setbit 根据偏移量设置 v 位上的值,默认每一位的值都是 0

  • 语法:setbit <k> <offset> <v> 设置Bitmaps中某个偏移量offset 的值 0|1 偏移量数组下标从 0开始

getbit 根据偏移量获取该位上的值

  • 语法: getbit <k> <offset> 获取Bitmaps中某个偏移量的值

bitcount 这个和上面很不同✨

  • 正常清空下可以查询指定 key(BigMaps) 下所有的 1 的个数

  • **除此之外还提供两个参数 start 起始下标 end 结束下标 **

    就是这个 start 和 end 正常人都会认为这个是数值下标其实不是, Redis可能为了方便表示的是一个字节单位而且是个范围

    实际上的 start~end 对应数据的下标是 start*8~(end+1)*8-1 例如是0 表示的是:0-7 Redis 对start end 设置的是字节单位所以需要 *8 才是数组真实坐标

  • 语法: bitcount <k> [start,end] [] 可选, 如果不输入 start end 则直接返回改key 所有的1

bitop and 是一个复合操作: 它可以和多个 BitMaps 进行统计 交集 并集 非 异或

  • 语法: bitop and|or|not|xor <返回的新集合k名> <k1> <k2> <k3>... and|or|not|xor选其一即可

    and交集 交集就是求两个|多个集合之间共同的元素

    or并集 差集是求两个|多个集合之间元素的差异 集合A差集合B:集合A中排除掉和集合B中共同元素 后剩下的元素

    not非 对比一个多个集合每个下标 0|1 是否一样一样取 0 不一样取 1 然后全部取反~ 是一种二进制算法

    xor异或 异或集,有的又叫交补集,是求两个集合互为不一样的元素 A|B集合 中互相没有的元素.

实际场景:

模拟场景:

  • 统计一个网站,每天的用户浏览量:

  • 定义一个k(BitMaps) unique:users:20220201 代表2022-02-01这天的独立访问用户的Bitmaps

    用户的 id 就是,BitMaps 的下标: 只需要通过用户 id getbit unique:users:20220201 用户id 就可以得到改用户在这一天有没有登录系统;

# 用户id: 1 3 5 7 2022年2月1日登录系统设置 1
127.0.0.1:6379> setbit unique:users:20220201 1 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20220201 3 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20220201 5 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20220201 7 1
(integer) 0
# 查看2022年2月1日 id1 id2 用户有没有登录系统 
127.0.0.1:6379> getbit unique:users:20220201 1			# id1登录系统
(integer) 1
127.0.0.1:6379> getbit unique:users:20220201 2			# id2没有登录系统
(integer) 0

# 统计当天一共有多少个用户登录系统 2022年2月1日
127.0.0.1:6379> bitcount unique:users:20220201
(integer) 4												# 一共有四个用户访问系统

# 计算出两天都访问过网站的用户数量,使用 and交集:A B 集合中共同的元素;
# 用户id: 1 2 3 4 2022年2月2日登录系统设置 1
127.0.0.1:6379> setbit unique:users:20220202 1 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20220202 2 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20220202 3 1 
(integer) 0
127.0.0.1:6379> setbit unique:users:20220202 4 1 
# 查看这两天的集合的交集,得知这两天都登录的用户放到 20220201_02 集合中;
127.0.0.1:6379> bitop and  20220201_02 unique:users:20220201 unique:users:20220202
(integer) 1
127.0.0.1:6379> bitcount 20220201_02					# bitcount 查看集合中元素和,得知这两天一共有两个人登录系统;
(integer) 2
# 而且还可以查看具体是拿些用户进行了系统登录;
127.0.0.1:6379> getbit 20220201_02 0
(integer) 0
127.0.0.1:6379> getbit 20220201_02 1
(integer) 1
127.0.0.1:6379> getbit 20220201_02 2
(integer) 0
127.0.0.1:6379> getbit 20220201_02 3
(integer) 1												# 得用户id 1 3 这两天都登录了系统;

很多应用的用户id以一个指定数字例如10000开头 10001 10002

  • 直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费

    在第一次初始化Bitmaps时, 假如偏移量非常大, 那么整个初始化过程执行会比较慢, 可能会造成Redis的阻塞

    Redis 是单线程多路复用,这样的操作会造成其它请求的堵塞执行...

  • 10001这样的id Bitmaps的偏移量对应势必会造成一定的浪费, 通常的做法是每次做setbit操作时将用户id减去这个指定数字 或设计某种算法

Bitmaps与set对比

假设网站有1亿用户, 每天独立访问的用户有5千万,如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表

set和Bitmaps存储一天活跃用户对比
数据 类型每个用户id占用空间需要存储的用户量全部内存量
集合 类型64位5000000064位*50000000 = 400MB
Bitmaps1位1000000001位*100000000 = 12.5MB

很明显, 这种情况下使用Bitmaps能节省很多的内存空间, 尤其是随着时间推移节省的内存还是非常可观的

  • 但Bitmaps并不是万金油, 假如该网站每天的独立访问用户很少
  • 只有10万 大量的僵尸用户 , 那么两者的对比如下表所示, 很显然, 这时候使用Bitmaps就不太合适了, 因为基本上大部分位都是0
set和Bitmaps存储一天活跃用户对比(独立用户比较少)
数据类型每个userid占用空间需要存储的用户量全部内存量
集合类型64位10000064位*100000 = 800KB
Bitmaps1位1000000001位*100000000 = 12.5MB

HyperLogLog

简介:

在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV PageView页面访问量

可以使用Redis的incrincrby轻松实现

  • 但像UV 独立IP数、搜索记录数等需要去重和计数的问题如何解决? 对每个浏览的ip进行统计, 统计该网站每天被多少个ip 访问…

    这要求对每个不同的 ip 进行存储,并统计集合中 不同ip值的个数...

  • 解决基数问题有很多种方案:

    数据存储在MySQL表中,使用distinct count计算不重复个数

    Redis提供的hash、set、bitmaps等数据结构来处理 但他们都比较消耗内存空间...

什么是基数:就是一组不重复的数据~

  • 数据集 {1, 3, 5, 7, 5, 7, 8} 那么这个基数数据集就是:{1,3,5,7,8}

Hyperloglog

  • 是用来做基数统计的算法,HyperLogLog 的优点是,
  • 在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的. 只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数
  • 可以理解HypeLogLog就是高级的 Set

常用命令:

pfadd 将指定的元素添加到 HypeLogLog中

  • 语法: pfadd <k> <v1> <v2>... 插入成功返回1 失败0

pfcount 统计 k(HypeLogLog) 中的元素个数.

  • 语法: pfcount <k>

pfmerge 将一个|多个 k(HypeLogLog) 合并并存储到另外一共 k(HypeLogLog)

  • 语法: pfmerge <新k> <旧k1> <旧k2> 会自动过滤 k1 k2重复的值…

Geospatial

简介:

Redis 3.2 中增加了对GEO类型的支持 Geographic地理信息

Redis 可以通过记录地图上的坐标 经纬度° 来设置地理信息:提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作

常用命令:

geoadd 添加一个经纬度坐标

  • 语法: geoadd <k> <经度> <维度> <地址名称>

  • 实例: geoadd china:city 121.43 31.23 shanghai 创建一个 中国城市集合 上海的坐标是 121.43 31.23

  • 注意: 两极无法直接添加,一般会下载城市数据,直接通过 Java 程序一次性导入

    有效的经度从 -180 度到 180 度

    有效的纬度从 -85.05112878 度到 85.05112878 度 当坐标位置超出指定范围时,该命令将会返回一个错误 已经添加的数据,是无法再次往里面添加的

    南北极,无法暂时直接添加…

geopos 根据 k(Geospatial) 地址名称 获取该位置的 坐标

  • 语法: geopos <k> <地址名>
  • 实例: geopos china:city shanghai 获取上海的坐标.

geodist 获取两个坐标之间的直线距离

  • 语法: geodist <k> <地址1> <地址2> [m|km|ft|mi] 两个地址首先要在同一个 k(Geo)

    m表示米 km表示千米 mi表示英里 ft表示英尺 如果没有指定则,默认使用 m 做单位;

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

  • 语法: georadius <k> <经度> <纬度> <半径范围> [m|km|ft|mi]

    以指定 坐标 为中心,查找附近的设备… 这个功能太好用了!

Redis 配置文件:

请结合配置文件:redis.conf 配置文件进行管理;

开头说明:

image-20220131225702533

  • Units 单位:

    Redis 开头定义了一些基本度量单位,指支持Bytes 不支持 bit单位 配置文件:敏感大小写…

  • 这里无需更改了解,Redis的规则单位说明:

INCLUDES

引入外部配置:

类似jsp中的include,多实例的情况可以把公用的配置文件提取出来

  • include /路径/路径/文件.conf 引入外部的配置文件,就像 jsp 的 < include > 可以引入外部的小脚本~

MODULES

自定义模块配置:

  • redis3.0的爆炸功能是新增了集群

  • redis4.0就是在3.0的基础上新增了许多功能 自定义模块配置就是其中之一

    通过 loadmodule 配置将引入自定义模块来新增一些功能.

NETWORK

网络配置:

  • bind

    绑定redis服务器网卡IP,默认为127.0.0.1,即本地回环地址 这样访问 Redis服务只能通过本机的客户端连接,而无法通过远程连接

    如果bind选项为空的话,那会接受所有来自于可用网络接口的连接… Redis3.2之后新增了保护模式记得关闭:"protected-mode no"

  • port

    指定redis运行的端口,默认是6379 据说是Redis开发者,唾弃的一个电影女主名字的9键拼写

    由于Redis是单线程模型,因此单机开多个Redis进程的时候会修改端口 本地搭建集群~

  • timeout

    设置客户端连接的超时时间

    当客户端在这段时间内没有发出任何指令,那么Redis服务自动关闭该连接。默认值为0,表示不关闭

  • tcp-keepalive

    对访问客户端的一种心跳检测,每个n秒检测一次 单位是 秒

    将周期性的使用SO_KEEPALIVE检测客户端是否还处于健康状态,避免服务器一直阻塞.

  • tcp-backlog

    设置tcp的backlog backlog其实是一个连接队列:里面记录着 未完成三次握手队列 + 已经完成三次握手 网络连接的合

    在高并发环境下你需要一个高backlog值来避免慢客户端连接问题.

GENERAL

Redis 总体常用的配置:

  • daemonize

    设置为yes 表示指定Redis以守护进程的方式启动 后台启动 默认值为 no

  • pidfile

    配置PID文件路径,当redis作为守护进程运行的时候,会将服务 pid 默认写到:/var/redis/run/redis_6379.pid Linux系统可以通过查看该文件获取 redis服务pid

  • loglevel

    定义日志级别:默认值为notice,有如下4种取值

    debug(记录大量日志信息,适用于开发、测试阶段

    verbose(较多日志信息

    notice(适量日志信息,使用于生产环境

    warning(仅有部分重要、关键信息才会被记录

  • logfile

    配置log文件地址, 默认打印在命令行终端的窗口上, 默认不输出配置文件;

  • databases

    设置数据库的索引。默认的数据库是 0 Redis一共有16个库

SNAPSHOTTING

SNAPSHOTTING 快照:

  • save 配置触发 Redis的持久化条件, 也就是什么时候将内存中的数据保存到硬盘

    save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存

    save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存

    save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存

    如果你只是用Redis的缓存功能,不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能: save ""

  • stop-writes-on-bgsave-error 默认值为yes

    当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据

    这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了 Redis重启 又可以重新开始接收数据了

  • rdbcompression 默认值是yes

    对于存储到磁盘中的快照,可以设置是否进行压缩存储 redis会采用LZF算法进行压缩

    如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大

  • rdbchecksum 默认值是yes

    在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验 这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能

  • dbfilename

    设置快照的文件名,默认是 dump.rdb

  • dir

    设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名 使用上面的 dbfilename 作为保存的文件名

SECURITY

SECURITY 安全保护: Redis 的密码设置 一些命令的禁用

  • rename-command 命令重命名,对于一些危险命令例如:flushdb flushall config keys 一些黑客不法分子,输入命令执行…

    服务端redis-server,常常需要禁用以上命令来使得服务器更加安全:禁用语法: rename-command flushdb "" 禁用 flushdb命令

    也可以保留命令但是不能轻易使用,重命名这个命令即可:重命名语法:rename-command flushdb "fdb" 重命名,之后更简单了😀

  • requirepass

    置redis连接密码…

CLIENTS

CLIENTS 客户端设置:

  • maxclients 默认 0 无限制

    设置客户端最大并发连接数,默认无限制 Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件

    当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

MEMORY MANAGEMENT

MEMORY MANAGEMENT 内存管理:

  • maxmemory

    设置Redis的最大内存,如果设置为0 表示不作限制

    建议设置,否则, 内存满了造成服务器宕机 maxmemory-policy可以对内存满载进行设置…

  • maxmemory-policy 当内存使用达到maxmemory设置的最大值时,redis使用的内存清除策略

    volatile-lru 利用LRU算法移除设置过过期时间的key 从设置定时key,的数据集中移除最近很少使用的数据淘汰

    volatile-ttl 从设置定时key,数据集中挑选最近快要过期的数据淘汰

    volatile-random 从设置定时key,数据集中挑选随机挑选数据淘汰

    allkeys-lru 从全部数据集中挑选,使用较少的淘汰

    allkeys-random 从全部数据集中,随机挑选数据淘汰

    no-enviction 禁止数据淘汰,Redis内存慢了,则不在允许新增数据 默认值

  • maxmemory-samples lru ttl 样本数量

    LRU算法和最小TTL算法都并非是精确的算法,而是估算值

    所以:你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小

    设置:maxmemory-samples 5

APPEND ONLY MODE

APPEND ONLY MODE 追加模式:

appendonly 默认值为no 关闭

  • 默认redis使用的是rdb方式持久化
  • Append Only File是另一种持久化方式 AOF 设置 yes 开启 aof

appendfilename

  • aof文件名,默认是"appendonly.aof"

appendfsync aof持久化策略的配置

  • no:表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快 Linux 默认30s 一次保存
  • always:表示每次写入都执行fsync,以保证数据同步到磁盘
  • everysec:表示每秒执行一次fsync,可能会导致丢失这1s数据

no-appendfsync-on-rewrite 默认值为no

  • 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysecalways的aof模式来说:

  • 执行fsync会造成阻塞过长时间,如果对延迟要求很高的应用,这个字段可以设置为yes:

    rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入

    默认为no,建议yes Linux的默认fsync策略是30秒。可能丢失30秒数据

auto-aof-rewrite-min-size 64mb

  • 设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写

aof-load-truncated 默认值为 yes

  • aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存,会执行异常
  • 如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load重启
  • 如果是no,用户必须手动redis-check-aof修复AOF文件才可以

LUA SCRIPTING

LUA SCRIPTING lua脚本设置:

  • lua-time-limit:一个lua脚本执行的最大时间,单位为ms。默认值为5000

REPLICATION

REPLICATION 复制:

Redis 集群的配置, 下面介绍…

REDIS CLUSTER

Redis 集群的配置, 下面介绍…

Redis 整合 Java Jedis

Redis 为了方便提供了一共依赖,Jedis 就想Java 操作数据库一样需要引入一个 JDBC驱动

里面定义了大量操作 Redis 的接口方法… 这个不需要太细致精通,了解即可,毕竟现在Java 都是SpringBoot 的工程了. 下面整合SpringBoot 才是关键!

测试连接 入门

① 创建一共Maven工程:RedisDemo 本地Git 进行管理…

② 添加Maven依赖:

pom.xml

<!-- 引入环境依赖 -->
<dependencies>
    <!-- redis的操作依赖: 类似于数据库的JDBC -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>

    <!-- Junit 测试依赖 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

③ 编写测试类:

  • 为了方便管理创建一个包:com.wsm.jedis

  • JedisTest.Java

    public class JedisTest {
        /** 测试连接 */
        public static void main(String[] args) {
            // 创建一共Jedis Redis的连接对象...
            Jedis jedis = new Jedis("47.243.109.199",6379);		// 这里的ip 是个人的服务器,本人学习完毕可能会关闭建议切换自己的ip...
            // 设置密码的Redis 第一个命令是需要登录,不然什么操作都做不了...
            jedis.auth("540707!@#wsm");
            // 测试连接
            String pong = jedis.ping();
            System.out.println("测试连接"+pong+"返回PONG连接成功~");
            // 处理完毕之后关闭连接.
            jedis.close();
        }
    }
    

    执行测试连接,返回PONG 连接成功!

  • 通过 new Jedis() 获取的 Jedis对象 进行操作Redis 的各种方法… 下面进行逐一简单测试~

  • 使用完毕之后,记得 Jedis.close(); 回收资源

注意:使用远程连接操作时候:

Redis 配置文件要打开允许网络远程调用:bind xxxx 关闭保护模式

如果是Linux 操作系统,要关闭 防火墙

常用方法使用:

JedisTest.Java

/** Redis常用方法使用: */
@Test
public void test1(){
    // 创建一共Jedis Redis的连接对象...
    Jedis jedis = new Jedis("47.243.109.199",6379);
    jedis.auth("540707!@#wsm");

    /**常用命令使用**/
    jedis.flushAll();       //清空所有数据库;
    jedis.set("k1", "v1");
    jedis.set("k2", "v2");
    jedis.set("k3", "v3");
    // 查看所有的 keys
    Set<String> keys = jedis.keys("*");
    System.out.println(keys.size());
    for (String key : keys) {
        System.out.println(key);
    }
    // 判断数据库中是否存在k1
    System.out.println(jedis.exists("k1"));
    // 判断数据库中k1的过期时间: -1永久存在
    System.out.println(jedis.ttl("k1"));
    // 获取k1的值
    System.out.println(jedis.get("k1"));

    //切换数据库...
    //jedis.select(1);
    // 处理完毕之后关闭连接.
    jedis.close();
}

五大基本数据类型使用:

/** 五大基本数据类型,基本使用: */
@Test
public void test2(){
    // 创建一共Jedis Redis的连接对象...
    Jedis jedis = new Jedis("47.243.109.199",6379);
    jedis.auth("540707!@#wsm");
    jedis.flushAll();
    /** String类型 **/
    // mset 批量新增k-v 数据
    jedis.mset("k1","v1","k2","v2","k3","v3");
    // mget 批量获取结果集
    List<String> mget = jedis.mget("k1", "k2", "k3");
    System.out.println("String类型:"+mget);

    /** List类型 **/
    // 创建 k(list) 并从右添加三个数据
    jedis.rpush("Klist", "list1","list2","list3");
    List<String> klist = jedis.lrange("Klist", 0, -1);
    System.out.println("List类型:"+klist);

    /** set类型 **/
    jedis.sadd("kset", "set1");
    jedis.sadd("kset", "set2");
    jedis.sadd("kset", "set3");
    Set<String> kset = jedis.smembers("kset");
    System.out.println("set类型:"+kset);

    /** zset类型 **/
    jedis.zadd("kzset", 1, "1");
    jedis.zadd("kzset", 3, "3");
    jedis.zadd("kzset", 2, "2");
    Set<String> zrange = jedis.zrange("kzset", 0, -1);  // 会默认从小往大排序
    System.out.println("zset类型:"+zrange);

    /** hash类型 **/
    System.out.println("Hash类型:");
    jedis.hset("khash", "name","zhangsan");
    jedis.hset("khash", "age","18");
    System.out.println("张三的age"+jedis.hget("khash", "age"));
    // Jedis 支持参数传递 Map
    HashMap<String,String> lisimap = new HashMap<>();
    lisimap.put("name","lisi");
    lisimap.put("age","18");
    lisimap.put("weight","120");
    jedis.hmset("Klisi", lisimap);
    // 输出李四个人信息
    System.out.println("李四的个人信息: "+jedis.hmget("Klisi", "name","age","weight"));

    // 处理完毕之后关闭连接.
    jedis.close();
}

Redis 的大部分方法都和 Jedis 中API 一致不详细一一介绍了

个人代码,需要的可以下方地址下载…点个赞呀♥

image-20220201201816466

Redis 整合 SpringBoot

Spring Boot整合Redis非常简单: 创建一个SpringBoot工程~

添加依赖:

pom.xml

<!-- redis依赖配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置Redis 属性:

application.yaml .properties

#Redis服务器地址
spring.redis.host=47.243.109.199
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务连接密码
spring.redis.password=540707!@#wsm
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0

配置Controller 并测试请求:

创建 com.wsm.controller包下: Redistest.Java测试类

@RestController
@RequestMapping("/redisTest")
public class Redistest {
    // SpringBoot 提供的redisTemplate 配置类;
    @Autowired
    private RedisTemplate redisTemplate;

    // 发送请求REST 风格要 {param} 要发送redis 消息
    @GetMapping("/demo1/{param}")
    public void demo1(@PathVariable("param")String param){
        // 向Redis 存一共k-v 字符类型
        String key = "wsm";
        redisTemplate.opsForValue().set(key,param);
        // 根据key 获取该值
        // redisTemplate是一个<Object,Object> 返回数据需要强转;
        String  strval = (String) redisTemplate.opsForValue().get(key);
        System.out.println("获取Redis消息:"+strval);
    }
}

发送一请求 http://localhost:8080/redisTest/demo1/你好

  • 请求发送成功,后台成功接收到请求,并发送Redis 同时进行读取
  • 但,Redis存储的k 是乱码 这是因为RedisTemplate 默认序列化接口的问题...

41c81896321e77112616116c1bb5445

RedisTemplate 使用:

之前通过,Redis 整合Java 使用的是 Jedis

  • 可以直接通过 jedis 建立连接,然后:直接使用Redis 命令对应的方法... 我个人感觉良好,挺好用的但是 Springboot2 移除了…Jedis——替换为 lettuce
  • Jedis 采用直连方式,多个线程操作是不安全的,A B线程同时操作Redis共享数据… 为了避免需要使用 jedis pool 连接池
  • Lettuce 实例可以在多线程中共享,不存在线程安全问题,可以减少线程的数据.
# RedisTemplate 对五种数据结构分别定义了操作
RedisTemplate.opsForValue();			# 操作字符串 BitMaps的方法也在这里面...
RedisTemplate.opsForHash();				# 操作hash
RedisTemplate.opsForList();				# 操作list
RedisTemplate.opsForSet();				# 操作set
RedisTemplate.opsForZSet();				# 操作有序set
RedisTemplate.opsForGeo();				# 操作Geo
RedisTemplate.opsForHyperLogLog();		# 操作HyperLogLog

# 除了数据类型的操作,我们对于Redis 常用的基本操作方法也可以通过获取连接对象来操作:
# 获取到连接对象之后,就可以像Jedis 一样直接操作Redis... 大量的命令方法也可以直接调用; 
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();					# 清空数据库

Redis 整合 k 乱码问题:

查看SpringBoot 自动注入的 RedisTemplate

spring-boot-autoconfigure-2.6.3.jar 自动配置文件下:RedisProperties

  • SpringBoot 自动集成的配置类,不可更改,是 RedisTemplate.Java源码 默认实现
  • @ConditionalOnMissingBean 注解,当程序中没有 RedisTemplate 则使用,注入改配置类对象到 Spring容器

RedisTemplate.Java源码

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }
   	/** 内部定义了两个 RedisTemplate StringRedisTemplate用于操作Redis **/
    
    @Bean
    // 当Spring容器中没有 redisTemplate 就将该配置注入到Spring中.也就是说:只要自定义 RedisTemplate 注入这个方法就会失效;
    @ConditionalOnMissingBean(name = {"redisTemplate"})				
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)		
    // 参数RedisConnectionFactory连接工厂SpringBoot2 使用了Lettuce
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);			// 默认采用 JDk的序列化方式,所以会产生 乱码;
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);			// 默认采用 StringRedisSerializer 类序列化
    }
}

StringRedisTemplate 与 RedisTemplate 的区别

StringRedisTemplate 只能对 key=String 因为Redis 经常使用 k-v 字符类型,为了提高效率而特定义的 StringRedisTemplate

  • StringRedisTemplate 继承了 RedisTemplate

  • RedisTemplate 是一个泛型类,而 StringRedisTemplate 则不是 它是专门为了String类型

  • 他们各自序列化的方式不同,但最终都是得到了一个字节数组

    StringRedisTemplate 使用的是 StringRedisSerializer 类

    RedisTemplate 使用的是 JdkSerializationRedisSerializer 类 因此获取数据,反序列化时候会需要 强至转换

  • 两者的数据是不共通的:

    StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据

    RedisTemplate 只能管理 RedisTemplate中 的数据

自定义 RedisTemplate 解决k乱码

定义 com.wsm.config 配置包 RedisTemplate.Java

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")		// 为了方便使用 泛型定义为 <S,O>
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {
		// JSON序列化类配置对象
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(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();
        // RedisTemplate配置
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        // 将RedisTemplate 的传输方式都更改为 JSON传输方式
        // key 采用String序列化
        template.setKeySerializer(stringRedisSerializer);
        // 值采用JSON序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // Hashkey 采用String序列化
        template.setHashKeySerializer(stringRedisSerializer);
        // 值采用JSON序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);       // 因为,很多时候公司都是使用对象转JSON 来进行存储... 很少直接发送对象,所以使用JSON序列化配置
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

重启配置执行:

image-20220202004410968

Redis 存储的key 不在乱码…

注意:

正常情况下,公司存储对象都是JSON 类型所以,发送的value 设置JSON序列化

当然 Redis 也支持发送存储 对象,实体类要进行序列化配置,不然不可以进行网络传输… Redis会报错. SpringBoot2.6之后程序会默认对实体类进行序列化,所以很多人会忽略!

Redis 常见错误:

(error) ERR Errors trying to SHUTDOWN. Check logs.错误

image-20220127104517687

Redis 面试题:

为什么Redis这么快:

  • 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速

  • 数据存储结构,类似于HashMap,HashMap的优势就是查找和操作的 时间复杂度都是O(1)

  • 采用单线程,避免了不必要的上下文切换和竞争条件

  • 多路IO复用,非阻塞IO

    这里不好解释多路复用,可以理解为:网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量

    多路-指的是多个socket网络连接

    复用-指的是复用一个线程

    多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术.

    复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗,Redis在内存中操作数据的速度非常快,内存内的操作不会成为这里的性能瓶颈

多路复用:

多路复用主要有三种技术:select,poll,epoll

select | poll

  • 两者很类似,大概是一种轮询的形式进行多路执行,每个请求与服务建立连接,对多个连接进行轮询操作处理,未处理的会进入堵塞

  • 缺点:会有空操作情况,大量的连接如果有的连接没有操作,也会进行轮询…浪费上下文切换时间

  • select | poll 区别: select 最大支持1024个连接 poll 支持无限个连接

epoll

  • 每个连接,的操作会进行统计,如果没有操作的连接,不会进行操作 避免了空操作情况

为什么Redis是单线程的

官方回答:

  • Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽. 单线程容易实现
  • 多线程还要考虑,线程之间上下文切换反而影响性能
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java.慈祥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值