对redis的浅学

Redis

NoSQl简介

not only sql 不仅仅是SQL

Redis概述

官网概述:
在这里插入图片描述

Redis键的操作

  • set keyName value 设置键和值

在这里插入图片描述

  • keys * 列出当前库所有的键

在这里插入图片描述

  • exists keyName 查看键是否存在(若存在,返回1,否则返回0)

在这里插入图片描述

  • type keyName 查看当前的键对应的值类型

在这里插入图片描述

  • del keyName 删除键

在这里插入图片描述

  • unlink KayName 非阻塞删除键(仅将keys中keyspace的数据删除,真正的删除会在异步操作中)

  • expire keyName seconds 设置键的过期时间
    在这里插入图片描述

  • ttl keyName 查看当前key还有多长时间过期(-1表示永不过期 -2表示已过期)

在这里插入图片描述

  • select index 切换数据库(默认16个数据库,index从0开始)
  • dbsize 查看当前库中有多少个键
  • move key index 移除当前key到index库

常用五大数据类型

Redis字符串

127.0.0.1:6379> set name liusiyuan # 设置键值
OK
127.0.0.1:6379> get name # 获取值
"liusiyuan"
127.0.0.1:6379> exists name # 判断键是否存在
(integer) 1
127.0.0.1:6379> append name hello# 向字符串追加,如果没有键相当于key
(integer) 14
127.0.0.1:6379> get name
"liusiyuanhello"
127.0.0.1:6379> strlen name# 获取键的长度
(integer) 14
127.0.0.1:6379> append name lsy
(integer) 17
127.0.0.1:6379> get name
"liusiyuanhellolsy"
127.0.0.1:6379> set views 0 #初始浏览量为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views#自增1
(integer) 1

127.0.0.1:6379> decr views#自减1
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incrby views 10# 设置指定增量
(integer) 9
127.0.0.1:6379> set key lslsllslslsl
OK
127.0.0.1:6379> get key1
(nil)
127.0.0.1:6379> get key
"lslsllslslsl"
127.0.0.1:6379> getrange key 0 1 #获取指定长度的字符串
"ls"
127.0.0.1:6379> getrange key 0 -1#获取全部字符串
"lslsllslslsl"
127.0.0.1:6379> set key2 12345678
OK
127.0.0.1:6379> get key2
"12345678"
127.0.0.1:6379> setrange key2 2 2
(integer) 8
127.0.0.1:6379> get keys
(nil)
127.0.0.1:6379> get key2
"12245678"
127.0.0.1:6379> setrange key2 3 *******
(integer) 10
127.0.0.1:6379> get key2
"122*******"
#setex(set with expire) 设置过期时间
#setnx(set if not exist) 不存在则设置(在分布式锁中会常常使用)
#msetnx (原子性操作)
127.0.0.1:6379> mset k1 1 k2 2 k3 3
OK
127.0.0.1:6379> keys
(error) ERR wrong number of arguments for 'keys' command
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1
127.0.0.1:6379> mget k1 k2 k3
1) "1"
2) "2"
3) "3"


127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> get db
"redis"
使用场景

value除了是我们的字符串还可以是我们的数字

  • 计数器(浏览量)
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存

List(集合)

在redis里面,可以把list当成栈,队列,阻塞队列 所有的list命令都是由l开头

lpush lrange
127.0.0.1:6379> lpush list 1 # 将一个值或多个值插入到list
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
127.0.0.1:6379> lpush list 2
(integer) 3
127.0.0.1:6379> lpush list 3
(integer) 4
127.0.0.1:6379> lrange lisy 0 -1
(empty array)
127.0.0.1:6379> lrange lisy 0 -1
(empty array)
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "2"
4) "1"
127.0.0.1:6379> lrange list 0 1
1) "3"
2) "2"

127.0.0.1:6379> rpush list 4
(integer) 5
127.0.0.1:6379> lrange lisy 0 -1
(empty array)
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "2"
4) "1"
5) "4"

127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "2"
4) "1"
5) "4"
127.0.0.1:6379> lpop list
"3"
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "2"
3) "1"
4) "4"
127.0.0.1:6379> rpop list
"4"
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "2"
3) "1"

127.0.0.1:6379> lindex list 1 #获取下标的元素
"2"
127.0.0.1:6379> lindex list 0
"2"
127.0.0.1:6379> llen list# 获取列表的长度
(integer) 3
127.0.0.1:6379> ltrim key1 1 2 #截断操作
OK
127.0.0.1:6379> lrange key1 0 -1
1) "5"
2) "4"
127.0.0.1:6379>
127.0.0.1:6379> lrange key1 0 -1
1) "1"
2) "1"
3) "1"
4) "5"
5) "4"
6) "3"
7) "2"
127.0.0.1:6379> lrem key1 2 1#去除两个value为1的
(integer) 2
127.0.0.1:6379> lrange key1 0 -1
1) "1"
2) "5"
3) "4"
4) "3"
5) "2"

127.0.0.1:6379> lpush ksy1 1
(integer) 1
127.0.0.1:6379> lpush ksy1 2
(integer) 2
127.0.0.1:6379> lpush ksy1 3
(integer) 3
127.0.0.1:6379> lpoplpush ksy1 ksy2
(error) ERR unknown command `lpoplpush`, with args beginning with: `ksy1`, `ksy2`,
127.0.0.1:6379> rpoplpush ksy1 ksy2
"1"
127.0.0.1:6379> keys *
1) "ksy1"
2) "ksy2"
127.0.0.1:6379> lrange ksy1 0 -1
1) "3"
2) "2"
127.0.0.1:6379> lrange ksy2 0 -1
1) "1"
127.0.0.1:6379>
lset 如果不存在列表去更新就会报错
如果存在,更新当前下标的值
linsert将具体的value插入到某个value的前面或后面

127.0.0.1:6379> linsert key before 3 2.5
(integer) 4

实际上是一个双链表

如果不存在,创建新的链表

如果存在,新增内容

如果移除key ,空链表,也代表不存在

在两边插入或改动值效率最高

使用场景

消息排队,消息队列

Set集合

无序不重复集合

127.0.0.1:6379> sadd set hello#set中添加元素
(integer) 1
127.0.0.1:6379> sadd set lsy
(integer) 1
127.0.0.1:6379> sadd set css
(integer) 1
127.0.0.1:6379> smembers set#查看指令set的所有值
1) "lsy"
2) "css"
3) "hello"

127.0.0.1:6379> sismember set css#判断set中是否存在值
(integer) 1
127.0.0.1:6379> sismember set csss
(integer) 0
127.0.0.1:6379> scard set#获取set中的个数
(integer) 3
127.0.0.1:6379>

127.0.0.1:6379> srem set css #移除set中的元素
(integer) 1
127.0.0.1:6379> scard set
(integer) 2
127.0.0.1:6379> smembers set
1) "lsy"
2) "hello"
#抽奖随机
127.0.0.1:6379> srandmember set #随机获取其中的一个值
"hello"
127.0.0.1:6379> srandmember set
"lsy"
127.0.0.1:6379> srandmember set
"lsy"

127.0.0.1:6379> spop set#随机移除元素
"css"
#将一个指定的值移到另一个集合中
127.0.0.1:6379> smove set set2 hello
(integer) 1
127.0.0.1:6379> smembers set2
1) "world"
2) "hello"
#共同关注(交集) 
127.0.0.1:6379> sdiff set set2 #差集
1) "1"
2) "5"

127.0.0.1:6379> sinter set set2 #交集
1) "2"
2) "3"
3) "4"

127.0.0.1:6379> sunion set set2#并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
微博,A用户将所有关注的人放在一个set集合中,将它的粉丝也放在一个集合中 共同关注求交集

hash(Map集合)

127.0.0.1:6379> hset myhash  name css#向hash中添加值
(integer) 1
[hmset 命令] [key 表名] [field 名称] [value值] [field 名称] [value值] ...
127.0.0.1:6379> hset myhash  age 21
(integer) 1
127.0.0.1:6379> hget myhash age#获取hash中的值
"21"
#获取全部值
127.0.0.1:6379> hgetall myhash
1) "name"
2) "lsy"
3) "age"
4) "21"
#删除元素
127.0.0.1:6379> hdel myhash name
(integer) 1
#查看是否存在
127.0.0.1:6379> hexists myhash name
(integer) 0
127.0.0.1:6379> hexists myhash age
(integer) 1
#列出所有的值

127.0.0.1:6379> hvals myhash
1) "21"
#自增值
127.0.0.1:6379> hincrby myhash age 1
(integer) 22
#如果不存在则添加
127.0.0.1:6379> hsetnx myhash name 1
(integer) 1
127.0.0.1:6379> hsetnx myhash age 1
(integer) 0


数据结构

hash类型对应的数据结构是两种:ziplist压缩列表,hashtable哈希表,当field-value长度较短且个数较少时使用ziplist 否则hashtable

zset有序集合

数据结构

hash+跳跃表

跳跃表:和二分查找思想有点类似,建立多级索引结构,实现跳跃式查找,时间复杂度为logn

127.0.0.1:6379> zadd topn 200 java 300 c++ 400 mysql 500 redis
(integer) 4
127.0.0.1:6379> zcard topn
(integer) 4
127.0.0.1:6379> zrange topn 0 -1
1) "java"
2) "c++"
3) "mysql"
4) "redis"
127.0.0.1:6379> zrange topn 0 -1 withscores
1) "java"
2) "200"
3) "c++"
4) "300"
5) "mysql"
6) "400"
7) "redis"
8) "500"

新数据类型

bitmaps

位图,bitmaps想象成一个以位为单位的数组,数组中每个单元只能存0和1

应用场景:商品是否存在,用户是否存在 0和1

127.0.0.1:6379> publish channel1 hello
(integer) 1
127.0.0.1:6379> setbit users:20220101 1 1
(integer) 0
127.0.0.1:6379> setbit users:20220101 6 1
(integer) 0
127.0.0.1:6379> setbit users:20220101 11 1
(integer) 0
127.0.0.1:6379> setbit users:20220101 15 1
(integer) 0
127.0.0.1:6379> setbit users:20220101 19 1
(integer) 0
127.0.0.1:6379> getbit users:20220101 1
(integer) 1
127.0.0.1:6379> getbit users:20220101 8
(integer) 0
127.0.0.1:6379> getbit users:20220101 19
(integer) 1
#统计
127.0.0.1:6379> bitcount users:20220101
(integer) 5

Hyperloglog

是一种用于统计基数的数据集合类型,主要用于网页的UV(不重复访客,一个人多次访问网站,只记录一次)

GeoSpatial

主要用于地理位置的存储,适合朋友的定位,附近的人,打车距离计算

发布与订阅

消息通信方式

127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "hello"

127.0.0.1:6379> publish channel1 hello
(integer) 1

各数据类型使用场景总结

String

最常规的操作,set和get,一般做计数缓存功能,

  • 计数器(浏览量)
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存

hash

  • 购物车 用户id作为key 商品id作为field 商品数量作为value
  • 存储对象

list

  • 简单的消息队列 lpush和rpop

set(无序不重复)

  • 共同关注 A用户将所有关注的人放在一个set集合中,将它的粉丝也放在一个集合中 共同关注求交集 sinter set set2 #交集
  • 全局去重
  • 抽奖随机 srandmember set

zset(有序不重复)

比set多了一个权重参数scope

  • 排行榜应用(比如所熟悉的语言排行榜,销售排行榜,点赞量排序)

Redis为什么这么快?

  • redis底层为C语言编写,有一个全局hash表,查询时间为O(1)
  • 在内存中操作,读取效率高
  • 单线程数据操作,避免了多线程下的上下文切换
  • 采用IO多路复用技术和非阻塞式IO
  • 丰富的数据结构,string,hash,set,zset,list

Redis删除策略

在redis中,如果键设置了过期时间,可能不会立即删除,redis将该键带上过期时间开辟了一块内存用作于expires字典,采用惰性删除和定期删除

  • 立即删除:创建一个定时器,当key设置了过期时间,时间过期后,有定时器任务立即执行对数据的删除. 用处理器性能换取内存空间

    • 优点:节省内存,到时就立即删除,快速释放内存空间
    • 缺点:不考虑CPU当前性能,无论CPU过载多高,都会执行删除任务
  • 惰性删除:数据到期后不会立即删除,当下一次get数据时,会将数据删除

    • 优点:节省了cpu当前性能
    • 缺点:内存压力大,出现长期不使用的数据不会删除
  • 定期删除:周期性的对redis数据进行检查,采用随机抽取策略,利用过期数据占比的方式控制删除频度,redis启动时会读取server.hz值,默认为10

    • 每秒会对服务器进行定时轮询,在轮询中,依次对16个数据库进行轮询,每次轮询一个数据库时,会进行依次数据库检测.检测时间为250ms/server.hz,在expires空间内挑选w个数据检测是否超时,超时在删除 如果检测数据>W*25%时,则删除后再次检测次数据库
    • Cpu占用设置峰值,检测频度可以自定义
    • 内存压力不是很大,长期占用内存的冷数据会持续清理

Redis是多线程还是单线程

在Redis6.x前,redis是真正意义上的单线程操作,包括网络请求和数据命令操作,都是以单线程进行操作

在Redis6.x之后,处理客户端连接请求由专门线程完成,执行命令还是说单线程,保证了多线程环境下的数据安全

Redis持久化机制

RDB:在redis中默认采用RDB持久化,一段时间间隔,将数据快照写入dump.rdb文件中,写入机制在配置文件中有save m n 在m时间里数据改变n次写入文件

优点:

  • 只有一个dump.rdb文件,方便持久化
  • 适合大规模的数据恢复,比AOF启动效率高
  • 性能最大化,fork子进程来完成写操作,让主进程继续处理命令

缺点

  • 数据安全性地,最后一次数据持久化容易丢失

AOF:类似于日志文件,将操作命令写入文件,保存到AOF文件

优点

  • 数据安全 不会存在数据丢失问题
  • 如果AOF文件存在问题,也可以使用redis - check -aof检测文件

缺点

  • AOF文件比RDB文件大,恢复速度慢
  • 启动效率低

怎么选择持久化方式

同时开启两种机制,用AOF保证数据安全,作为恢复数据的第一选择,RDB作为不同程度的冷备

Redis事务

redis事务不支持事务回滚(原子性操作),但是保证事务隔离性问题

在redis事务中,一组命令是最小的单位,首先先将这些命令入队操作,exec执行命令,保证一次性多个命令,服务端在执行事务的过程中,不会被其他客户端发来的请求打扰

操作:开启事务–>multi

​ 执行事务—>exec

Redis和MySQL数据一致性问题

总有四种方案:

  • 先更新数据库,后更新redis
  • 先更新redis,后更新数据库
  • 先删除缓存,后更新数据库
  • 先更新数据库,后删缓存

前两种方式没人使用

第一种存在问题:并发情况下先更新数据库,会将脏数据刷到缓存中

同时有请求A和请求B进行更新操作,那么会出现

(1)线程A更新了数据库

(2)线程B更新了数据库

(3)线程B更新了缓存

(4)线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

第二种存在问题:如果先更新缓存成功,但是更新数据库失败,会造成数据不一致,和第一种情况类似

第三种存在问题:

  1. 线程A删除缓存
  2. 线程B访问缓存不存在
  3. 线程B读取数据库
  4. 线程B将旧值写入缓存
  5. 线程A新值写入数据库

解决方式:

①延迟双删

  1. 先淘汰数据缓存
  2. 写入数据库
  3. 休眠一秒,再次淘汰缓存,这么做,可以将一秒内产生的缓存脏数据再次删除

②更新和读取操作进行异步串行化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值