Redis笔记
此笔记部分根据狂神说redis部分视频记录,感谢狂神说!下面为视频路径:https://www.bilibili.com/video/BV1S54y1R7SB?t=951&p=35
一:简介
nosql
no only sql 不仅仅是sql
一般称之为非关系型数据库
web2.0存在一些问题:(3高)
1.高并发
2.大数据
3.高扩展,高可用
分类:
key/value的格式
文档型格式
列类型
图
redis
redis是使用c语言开发的一个高性能键值对的数据库,可以用作数据库、缓存和消息中间件
支持的数据类型(五大基础类型,三种特殊类型)
String(★) 字符串
hash(理解) 散列
list 列表
set 集合
sortedSet(zset) 有序集合
geospatial 地理空间
hyperloglog 范围查询
bitmaps
默认16个数据库,默认0号
select 3 #切换数据库
DBSIZE #查看数据库的数据量
keys * #查看数据库锁有的key
flushall #清空所有数据库
flushdb #清空当前数据库
redis是单线程的(6.0以后支持多线程),官方表示redis是基于内存操作的,redis的瓶颈是根据计算机的内存和网络宽带。
- 多线程(CPU上下文切换:耗时)不一定比单线程快。redis是将所有数据放在内存中的,所以使用单线程去操作效率就是最高的。多次读写都是在一个cpu上的。
二:安装
1.下载redis
2.上传到linux
3.安装redis
mkdir /usr/local/redis
mv /root/redis.tar /usr/local/redis
cd /usr/local/redis
tar -xvf redis.tar
4.编译redis 依赖 gcc
yum install gcc-c++
make
5.安装redis
make PREFIX=/usr/local/redis install
6.配置
复制一个redis.conf 到bin目录下
启动服务器的方式1:
前台启动的方式:
cd /usr/local/redis/bin
./redis-server redis.conf
后台的方式:
配置一下redis.conf
修改:daemonize yes
保存退出
启动客户端
简单的方式:
./redis-cli #连接本地端口号为 6379的服务器
推荐的方式
./redis-cli -h 连接ip -p 端口号
redis的停止
方式1:通过kill -9 进程号(不推荐)
方式2:通过客户端发送命令
./redis-cli -h ip -p port shutdown
性能测试:redis-benchmark.exe
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
三:基本操作方式
-
string
-
赋值:格式: set key value
例如:set username tom -
取值:格式: get key
例如: get username -
先获取再设置:getset key value,如果不存在值,返回nil
例如: getset username jack -
删:del key
例如: del d -
了解
-
对于数字类型 自增和自减
incr key ++
decr key –
-
增加或减少指定的数量
incrby key int
decrby key int
-
拼接字符串,如果当前key不存在,就相当于set key value
append key value
-
字符串范围
getrange key1 0 3 设置key1的值
getrange key1 0 -1 相当于get key1
setrange key2 1 xx 替换指定位置开始的字符串
-
setex(set with expire)设置过期时间
setex key3 30 hello
-
setnx(set if not exist)如果不存在设置,在分布式锁中使用
setnx mykey redis
-
批量设置
mset key1 v1 key2 v2 key3 v3
mget key1 key2
msetnx k4 v4 key1 v1 mset是原子性的操作,要么一起成功,要么一起失败
user:{id}:{filed}
mset user:1:name shangsan user:1:age 2
mget user:1:name user:1:age
-
-
-
list
-
赋值:
左边:lpush key value value2 value3
右边:rpush key value value2 value3 -
取值:
左边:lpop key
右边:rpop key -
获取所有元素
lrange key 0 -1 -
获取元素的个数
llen key -
扩展:
lpushx key value :若有则添加 若没有则不添加
rpushx key value :若有则添加 若没有则不添加lrem key count value:从左边移除count个value 若count>0 :从左边移除count个value 若count<0 :从右边移除count个value 若count=0 :从右边移除所有的value lset key index value 设置链表中指定索引的元素值 0 代表是第一个 -1代表的是最后一个
-
-
hash:
- 了解
- 存入一个map集合
user username tom
age 18
sex 1 - 存值:
存入一个值
hset key subkey subvalue
存入多个值
hmset key subkey1 subvalue1 subkey2 subvalue2 - 获取:
获取一个值
hget key subkey
获取多个值
hmget key subkey1 subkey2 - 移除值:
hdel key subkey subkey
给一个key添加指定的数字
hincrby key subkey int
- 存入一个map集合
- 了解
-
set
- 添加
sadd key value1 valuse2 - 删除
srem key value1 valuse2 - 获取
smembers key - 判断是否是是set中的一员
sismember key value - 运算
差集: sdiff s1 s2
交集: sinter s1 s2
并集: sunion s3 s4 - 获取数量
scard key
srandmember key:随机获取一个
- 添加
-
sortedSet
- 添加元素
zadd key score m1 score m2 - 获取元素
zscore key m:获取指定成员的得分
zcard key:获取key的长度 - 删除元素
zrem
- 添加元素
-
通用的操作
- keys * :查看所有的key
- del key:删除指定的key
- exists key:判断一个key是否存在
- rename oldkey newkey:重命名
- expire key 秒数:
ttl key :查看一个key剩余存活时间
-1:持久存活
-2:不存在 - type 判断一个可以属于什么类型
-
发布订阅
- subscribe channel 订阅给定的频道的信息
- psubscribe pattern 订阅一个或多个符合给定模式的频道
- unsubscribe channel 指退订给定的频道
- unpsubscribe [pattern] 退订所有给定模式的频道。
- publish channel message 将信息发送到指定的频道
-
事务
Redis单条命令是保存原子性的,但是事务不保证原子性。redis事务没有隔离级别的概念,redis事务的本质是一组命令的集合,一个事务中的所有命令都会被序列化,在发起命令后,才会按照顺序执行。一致性、顺序性、排他性
-
简介:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
-
命令:
- multi 开始事务
- exec 执行事务
- discard 回滚
127.0.0.1:6379[3]> multi OK 127.0.0.1:6379[3]> set k1 v1 QUEUED 127.0.0.1:6379[3]> set k2 v2 QUEUED 127.0.0.1:6379[3]> get k2 QUEUED 127.0.0.1:6379[3]> set k3 v3 QUEUED 127.0.0.1:6379[3]> exec 1) OK 2) OK 3) "v2" 4) OK
-
编译型异常(命令出错),所有事务都不会被执行
-
运行时异常(如:0/1),其他的命令可以正常执行,错误命令抛出异常
监控:
- 悲观锁:认为什么时候都会出问题,无论做什么都会加锁
- 乐观锁:认为什么时候都不会出现问题,不会上锁,更新数据时去判断在此期间是否有人修改过这个数据。获取version,更新时比较version
#正常 127.0.0.1:6379[3]> set money 100 OK 127.0.0.1:6379[3]> set out 0 OK 127.0.0.1:6379[3]> watch money OK 127.0.0.1:6379[3]> multi OK 127.0.0.1:6379[3]> decrby money 20 QUEUED 127.0.0.1:6379[3]> incrby out 20 QUEUED 127.0.0.1:6379[3]> exec 1) (integer) 80 2) (integer) 20 #多线程修改时,使用watch可当作乐观锁操作 127.0.0.1:6379[3]> watch money OK 127.0.0.1:6379[3]> multi OK 127.0.0.1:6379[3]> decrby money 10 QUEUED 127.0.0.1:6379[3]> incrby out 10 QUEUED #在执行前,另外一个线程修改了值,会导致执行失败 127.0.0.1:6379[3]> exec (nil) #解决办法 127.0.0.1:6379[3]> unwatch #先解锁,再获取锁 OK 127.0.0.1:6379[3]> watch money OK
-
-
连接命令
- AUTH password 验证密码是否正确
- ECHO message 打印字符串
- PING 查看服务是否运行
- QUIT 关闭当前连接
- SELECT index 切换到指定的数据库
-
geospatial地理位置,可以推算地理位置的信息,底层原理zset
-
geoadd 将指定的地理空间位置(纬度【-180度到180度】、经度【-85.05112878度到85.05112878度】、名称)添加到指定的
key
中,两极无法添加。一般会下载城市数据,直接通过java程序一次性导入。127.0.0.1:6379[3]> geoadd china:city 120.15 30.28 hangzhou (integer) 1 127.0.0.1:6379[3]> geoadd china:city 113.62 34.75 zhengzhou
-
geopos获取当前定位
127.0.0.1:6379[3]> geopos china:city beijing tianjin 1) 1) "116.39999896287918" 2) "39.900000091670925" 2) 1) "117.19999998807907" 2) "39.120000488192183"
-
两个位置之间的直线距离
127.0.0.1:6379[3]> geodist china:city beijing tianjin km "110.6313"
-
georadius:以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
127.0.0.1:6379[3]> georadius china:city 110 30 1000 km 1) "hangzhou" 2) "zhengzhou"
georadiusbymember:指定成员的位置被用作查询的中心。
127.0.0.1:6379[3]> georadiusbymember china:city beijing 1000 km 1) "zhengzhou" 2) "tianjin" 3) "beijing"
geohash:返回一个或多个位置元素的 Geohash (11位)表示。
底层原理:zset
127.0.0.1:6379[3]> zrange china:city 0 -1 1) "hangzhou" 2) "zhengzhou" 3) "tianjin" 4) "beijing" 127.0.0.1:6379[3]> zrem china:city tianjin (integer) 1 127.0.0.1:6379[3]> zrange china:city 0 -1 1) "hangzhou" 2) "zhengzhou" 3) "beijing"
-
-
hyperloglog:数据结构,作基数统计的算法,如网页的UV(一个人访问一个网站多次,还算作一个人)
基数:不重复的元素,允许容错
127.0.0.1:6379[3]> pfadd mykey a d b d d s k #创建 (integer) 1 127.0.0.1:6379[3]> pfcount mykey #统计数量 (integer) 5 127.0.0.1:6379[3]> pfadd mykey2 k i y g p w v (integer) 1 127.0.0.1:6379[3]> pfmerge mykey3 mykey mykey2 #合并,并集 OK 127.0.0.1:6379[3]> pfcount mykey3
-
bitmaps:位图,数据结构,非0即1
位运算,可用作打卡、签到等场景
127.0.0.1:6379[3]> setbit sign 0 0 #设置 (integer) 0 127.0.0.1:6379[3]> setbit sign 1 1 (integer) 0 127.0.0.1:6379[3]> setbit sign 2 1 (integer) 0 127.0.0.1:6379[3]> getbit sign 2 #获取某个index的值 (integer) 1 127.0.0.1:6379[3]> bitcount sign #统计记录 (integer) 2
四:Jedis
Jedis是官方推荐的java连接开发工具,
-
导入对应的依赖
<!--导入jedis--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency>
-
编码测试
-
连接数据库
-
操作命令
-
断开连接
public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); System.out.println(jedis.ping()); }
-
API与命令相似
-
例:事务
public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); jedis.flushDB(); JSONObject jsonObject = new JSONObject(); jsonObject.put("hello", "world"); jsonObject.put("name", "kuangshen"); Transaction multi = jedis.multi(); String s = jsonObject.toJSONString(); try { multi.set("user1", s); multi.set("user2", s); int i = 1 / 0; multi.exec(); } catch (Exception e) { multi.discard(); e.printStackTrace(); } finally { System.out.println(jedis.get("user1")); System.out.println(jedis.get("user2")); jedis.close(); } }
-
五:spring boot整合
springboot操作数据:spring-data, jpa, mongodb, redis
SpringBoot2.x以后,jedis被改成了lettuce
jedis:采用的直连,多个线程操作不安全,若要避免,则要使用jedis pool连接池,更像Bio模式
lettuce:采用netty,可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数量,更像Nio模式
-
源码
@Bean @ConditionalOnMissingBean(name = "redisTemplate")//不存在就生效 public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { //默认的RedisTemplate没有过多的设置,redis对象需要序列化 //两个泛型都是Object,需要强制转换 RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean //由于string类型是最长使用的类型,所以单独提取出来的 public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
配置连接
spring.redis.host=127.0.0.1 spring.redis.port=6379
-
测试及编码
-
@Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { //opsForValue操作string //opsForList list //opsForHyperLogLog HyperLogLog //除了基本的操作,常用的方法都可以直接通过RedisTemplate操作,比如事务 //清空数据库通过connection操作 /*RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); connection.flushDb();*/ redisTemplate.opsForValue().set("mykey", "关注狂神说Java的公众号"); System.out.println(redisTemplate.opsForValue().get("mykey")); }
-
序列化配置,默认为
JdkSerializationRedisSerializer
。自定义序列化配置网上查询。@Nullable private RedisSerializer keySerializer = null; @Nullable private RedisSerializer valueSerializer = null; @Nullable private RedisSerializer hashKeySerializer = null; @Nullable private RedisSerializer hashValueSerializer = null;
若不序列化,会报错
SerializationException
六:Redis config
配置文件redis.windows.conf
-
大小写不敏感
-
包含,INCLUDES,
-
NETWORK
,网络bind 127.0.0.1 #绑定IP、端口号 port 6379 protected-mode yes #以守护线程的方式运行,默认为no
-
GENERAL
,# This can be one of: # debug (a lot of information, useful for development/testing) # verbose (many rarely useful info, but not a mess like the debug level) # notice (moderately verbose, what you want in production probably)生产环境使用 # warning (only very important / critical messages are logged) loglevel notice logfile "" #日志文件位置名 databases 16 #数据库数量
-
SNAPSHOTTING
,快照持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb, .aof
redis是内存数据库,若没有持久化,就会断电即失
#持久化规则,如果xxxs内,至少有xxx个key进行修该,进行持久化操作 save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes #持久化出差后,是否还进行工作 rdbcompression yes #是否压缩rdb文件,需要消耗CPU资源 rdbchecksum yes #保存rdb检查 dir ./ #文件保存目录
-
REPLICATION
配置主从复制,具体查看八
-
SECURITY
,安全,redis密码两种方式,配置文件修该或下述方式。127.0.0.1:6379> config get requirepass #查看密码 1) "requirepass" 2) "" 127.0.0.1:6379> config set requirepass "123456" #设置密码 OK 127.0.0.1:6379> ping (error) NOAUTH Authentication required. 127.0.0.1:6379> auth 123456 #登录 OK 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "123456"
-
LIMITS
,服务端限制# maxclients 10000 # maxmemory <bytes> # maxmemory-policy noeviction 内存达到上限后的策略 # volatile-lru -> remove the key with an expire set using an LRU algorithm # allkeys-lru -> remove any key according to the LRU algorithm # volatile-random -> remove a random key with an expire set # allkeys-random -> remove a random key, any key # volatile-ttl -> remove the key with the nearest expire time (minor TTL) # noeviction -> don't expire at all, just return an error on write operations
-
APPEND ONLY MODE
,aof配置appendonly no #默认不开启,使用rdb方式 appendfilename "appendonly.aof" # appendfsync always #每次修改后同步,消耗性能 appendfsync everysec #每秒执行一次 # appendfsync no #不执行同步
七:持久化
redis是内存数据库,若没有持久化,就会断电即失。在主从复制中,rdb是在从机上备用的,aof几乎不使用
rdb:redis database
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。redis会单独创建(fork)一个子进程来进行持久化,会将数据写入到一个临时rdb文件中,待持久化过程结束后,再用这个临时文件替换上次持久化好的文件,成为正式的rdb文件,子线程退出。缺点是最后一次持久化后的数据可能丢失。
文件默认是dump.rdb
触发机制:
- save的规则满足的条件下
- 执行flushall命令
- 退出redis
恢复rdb文件:只需将rdb文件放在redis启动目录即可。
aof:append only file
将所有数据都记录下来,在恢复时会重新把所有命令都执行一遍。以日志的形式来记录每个写操作。只需要将appendonly no #默认不开启,使用rdb方式
改为yes,然后重启即可生效。
文件默认是appendonly.aof
,若aof文件被破坏,则会启动失败。可使用redis-check-aof
进行修复,会直接将错误命令删除
redis-check-aof --fix appendonly.aof
当同时开启两种持久化方式,重启时会优先载入aof文件来恢复原始数据。因为rdb只用作后备,所以一般只用save 900 1
即可。
八:主从复制
是指将一台redis服务器的数据,复制到其他redis服务器,前者称为master(主节点),后者称为从节点(slave/follower),数据的复制是单向的,只能从主节点到从节点,主节点只能写,从节点只能读。默认情况下,每台主节点都是主节点,且一个主节点可以有多个从节点,一个从节点只能有一个主节点。一个集群至少有三个服务器,一主二从。
作用:
- 数据冗余
- 故障恢复
- 负载均衡
- 高可用(集群)基石
环境配置:
只配置从库,不配置主库。
127.0.0.1:6379> info replication
# Replication 查看当前的信息
role:master
connected_slaves:0 #从机
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
#复制redis的配置文件,对端口号、pidfile、logfile、dbfilename进行更改
cp redis.conf redis79.conf
cp redis.conf redis80.conf
cp redis.conf redis81.conf
#更改完成后用配置文件启动,并用ps -ef|grep redis查看启动线程
redis-server kconfig/redis79.config
redis-server kconfig/redis80.config
redis-server kconfig/redis81.config
一主二从
slaveof 127.0.0.1 6379 #在从机上配置从机的主机地址
info replication #查看主机信息
上述命令行配置的主从复制时暂时的,真实的主从配置应该在配置文件中配置
# slaveof <masterip> <masterport>
# masterauth <master-password>
细节
主机可以写,从机只能读,主机中的所有数据都会被从机保存。若主机shutdown,从机依旧是从机,主机重新启动后,从机会自动连接上主机,能获取到主机写入的新主机。
如果使用命令行配置主从复制,当redis重新启动后,会默认恢复成主机,所以,当从机shutdown后,需要重新配置。
复制原理
slave启动成功连接到master后,会发送sync同步命令,master接到命令,会启动存盘进程,将整个数据文件传送给slave,并完成一次同步。
- 全量复制:slave接到数据库文件后,将其存盘并加载到内存中
- 增量复制:master继续将新的收集到的修改命令依次传送给slave,完成同步
只要重新连接到主机,一次全量复制将被执行。
主机–>从机/主机–>从机方式(层层链路)
配置完成之后中间的redis依旧是从节点,当主节点shutdown后,需要进行如下配置
slaveof no one #让自己变成主机
如果第一个主节点恢复了,就需要重新配置。哨兵模式不需要配置上述步骤。
九:哨兵模式
redis2.8+提供了Sentinel(哨兵)架构。能够后台监控主机是否故障,如果发生故障,根据投票数自动将从库转换为主库
。通过发送命令,等待redis服务器响应,从而监控多个redis实例。使用多个哨兵进行监控,形成多哨兵模式。
如果主服务器宕机,哨兵1先检测到这个结果,系统不会马上进行投票(主观下线),当其他哨兵也检查到并数量达到一定后,哨兵就会发起投票,进行故障转移,切换成功之后,就会通过发布订阅模式,实现票数最多的从机切换主机(客观下线)。
配置sentinel.config
#主机名称 端口号 宕机时,从机投票
sentinel monitor myredis 127.0.0.1 1
#启动哨兵
redis-sentinel kconfig/sentinel-conf
如果主机恢复之后,只能归并到新的主机下,当作从机。
- 哨兵集群,基于主从复制模式
- 主从可以切换,故障可以转移,是主从模式的升级,系统的可用性更好
- 集群数量一旦达到上限,在线扩容十分麻烦。
- 哨兵模式的配置麻烦,可以在网络上查询
十:缓存穿透和雪崩
服务的高可用问题
缓存穿透(查不到)
概念
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
-
布隆过滤器
是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先校验,不符合丢弃,从而避免了底层存储系统的查询压力。
-
缓存空对象
当存储层不命中后,即使返回空对象也会被存储起来,同时设置一个过期时间,之后访问这个数据会直接从缓存中获取,保护后端数据源。
- 如果空值能被缓存起来,这就意味着缓存需要更多的空间存贮更多的键,因为可能会有很多空值的键。
- 即使对空值设置了过期时间,还是会存在存储层和缓存层的数据会有一段时间的不一致,这对于需要保证一致性的业务会有影响
缓存击穿(量太大,缓存过期)
概念
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决方案
-
设置热点数据永不过期
-
加互斥锁
分布式锁,使用分布式锁,保证每个key同时只有一个线程去查询后端服务,其他的线程没有获得分布式锁的权限,因此只需要等待即可。这种方式把高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩
概念
缓存雪崩是指,缓存层出现了错误,不能正常工作了(redis宕机,缓存集中过期失效等)。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
解决方案
-
redis高可用
多设置几台redis,搭建redis集群。(异地多活)
-
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
sh形式存储,在控制层先校验,不符合丢弃,从而避免了底层存储系统的查询压力。
-
缓存空对象
当存储层不命中后,即使返回空对象也会被存储起来,同时设置一个过期时间,之后访问这个数据会直接从缓存中获取,保护后端数据源。
- 如果空值能被缓存起来,这就意味着缓存需要更多的空间存贮更多的键,因为可能会有很多空值的键。
- 即使对空值设置了过期时间,还是会存在存储层和缓存层的数据会有一段时间的不一致,这对于需要保证一致性的业务会有影响