Redis

Redis

1.NoSQL

NoSQL = Not Only SQL

泛指非关系型数据库

2.NoSQL的四大分类

  • KV键值对

    • 新浪:Redis
    • 美团:Redis + Tair
  • 文档型数据库(bson格式和json格式一样)

    • MongoDB(一般必须要掌握),基于分布式文件存储的数据库,c++编写,主要用来处理大量的文档
    • MongoDB世界语关系型数据库和非关系型数据库中间的产品,MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的
    • ConthDB
  • 列存储数据库

    • HBase(需要掌握)
    • 分布式文件系统
  • 图关系数据库

    • 不是存储图形,存放的是关系,比如朋友圈社交网络,广告推荐
    • Neo4j(需要掌握),InfoGrid

3.Redis概述

  • Redis:(Remote Dictionary Server),即远程字典服务

  • Redis做什么:

    • 内存存储,持久化,内存是断电即失,所以说持久化很重要(rdb,aof)
    • 效率高,可用于高速缓存
    • 发布订阅系统(可以进行一些简单的消息队列)
    • 地图信息分析
    • 计时器,计数器(浏览量)
  • Redis的基本命令使用

set name wang //设置键值对
get name //获取key对应的值
keys * //查看所有的key
select 3 //选择3号数据库,redis默认有16个数据库
dbsize //查看数据库的大小
flushdb //清空当前数据库
flushall //清空所有数据库

4.五大数据类型

  • Redis-key
set name wang 

type name //查看当前类的一个类型

keys *

get name

exists name

move name

expire name 10 //设置过期时间为10s

ttl name //可以查看剩余多长时间过期
  • String
127.0.0.1:6379> set key1 v1		#设置值
OK

127.0.0.1:6379> get key1	#获得值
"v1"

127.0.0.1:6379> keys *		#查看值
1) "key1"
2) "name"

127.0.0.1:6379> exists key1		#判断某一个key是否存在
(integer) 1

127.0.0.1:6379> append key1 "hello"		#追加字符串
(integer) 7

127.0.0.1:6379> get key1		#获得值
"v1hello"

127.0.0.1:6379> strlen key1		#获取字符串的长度
(integer) 7

127.0.0.1:6379> getranger key1	0 3	#截取字符串

127.0.0.1:6379> getranger key1	0 -1	#获取全部字符串

127.0.0.1:6379> setranger key1	1 xx	#从字符串下标为1的位置开始,替换下表2和3的字符串
127.0.0.1:6379> set views 0
OK

127.0.0.1:6379> get views #增加1
"0"

127.0.0.1:6379> incr views
(integer) 1

127.0.0.1:6379> get views
"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 #增加10
(integer) 10

127.0.0.1:6379> get views
"10"

127.0.0.1:6379> incrby views 10	
(integer) 20

127.0.0.1:6379> decrby views 5 #减少5
(integer) 15

setex (set with expire) #设置过期时间
setnx (set if not exist) #不存在设置(在分布式锁会常常使用)
#批量设置值
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #批量设置值
OK
127.0.0.1:6379> keys * 
1) "views"
2) "k3"
3) "k1"
4) "k2"
127.0.0.1:6379> mget k1 k2 k3 #批量获取值
1) "v1"
2) "v2"
3) "v3"
#对象
set user:1 {name:zhangsan,age:3} #设置一个user:1对象,值为json字符来保存一个对象
127.0.0.1:6379> set user:1 {name:zhangsan,age:3}
OK
127.0.0.1:6379> get user:1
"{name:zhangsan,age:3}"

#这里的key是一个巧妙的设计: user:{id}:{filed}.如此设计在Redis中是完全OK了
127.0.0.1:6379> mset user:2:name zhangsan user:2:age 18
OK
127.0.0.1:6379> mget user:2:name user:2:age
1) "zhangsan"
2) "18"

#getset 先get再set
127.0.0.1:6379> getset db redis #如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb #如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
  • List
    • 我们可以把list玩成栈、队列、阻塞队列
    • 所有的list命令都是L开头的
# 将一个或者多个值插入到列表头部(左)
127.0.0.1:6379> LPUSH list one
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1
1) "three"
2) "two"

# 从右面队列插入元素
127.0.0.1:6379> RPUSH list right
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379>

#移除元素
Lpop list
Rpop list

#通过下标获取某一个值
lindex list 1
  • set集合,无序,不可重复
sadd myset "hello"
  • Hash哈希类型
127.0.0.1:6379> hset myhash field1 wang #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash field1
"wang"
  • Zset(有序集合)

在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1

127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"

5.三种特殊类型

(1)geospatial 地理位置(底层实现原理是Zset),我们可以使用Zset命令来操作geo

  • 可以实现附近的人业务
# 添加地理位置
# 我们一般会下载城市数据,直接通过Java程序一次性导入
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing 

#获取城市的位置,先经度后纬度
127.0.0.1:6379> geopos china:city beijing
1)116.40
2)39.90

#两个城市之间的直线距离
geodist china:city beijing chongqing #单位默认是M
geodist china:city beijing chongqing KM #指定单位为KM mi为英里 ft表示为英尺

#以110 30这个经纬度为中心,寻找方圆1000KM内的城市
127.0.0.1:6379> georadius china:city 110 30 1000 km

#找出以上海为中心周围400km的城市
127.0.0.1:6379> georadiusbymember china:city shanghai 400 km

#底层实现原理是Zset
#查看录入的所有城市
127.0.0.1:6379> zrange china:city 0 -1

#移除某个指定的城市
127.0.0.1:6379> zrem china:city beijing

(2)Hyperloglog

什么是基数?

A{1,3,5,7,8,7}

B{1,3,5,7,8}

基数(不重复的元素)= 5,可以接受误差

  • 网站的UV(一个人访问一个网站多次,但是还算做一个人)
127.0.0.1:6379> PFadd mykey a b c d e f g a b c

#统计基数的个数
127.0.0.1:6379> PFCOUNT mykey
(integer) 9

(3)Bitmaps

统计用户信息,活跃,不活跃!登录,未登录,两个状态的,都可以使用Bitmaps

统计七天打卡信息

127.0.0.1:6379> setbit sign 0 1
127.0.0.1:6379> setbit sign 1 1
127.0.0.1:6379> setbit sign 2 0
127.0.0.1:6379> setbit sign 3 1
127.0.0.1:6379> setbit sign 4 1
127.0.0.1:6379> setbit sign 5 0
127.0.0.1:6379> setbit sign 6 1

127.0.0.1:6379> getbit sign 6
(integer) 1

6.Redis事务

127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #入队
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) "v1"


127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #入队
QUEUED

127.0.0.1:6379> DISCARD #取消事务
OK

# 如果事务编写时中间有编译时的错误入队指令,那么这个事务不会成功
# 运行时有错误,错误的不会执行,其它正常可以执行
# 使用watch可以当作redis的乐观锁实现

7.Jedis

我们要使用Java操作Redis

  • Jedis:是Redis官方推荐的Java连接开发工具,使用Java操作Redis中间件

1.导入对应的依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

2.Jedis连接Redis

Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping()); //PONG
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping()); //PONG
System.out.println("清空数据:" + jedis.flushDB());
System.out.println("判断某个建是否存在" + jedis.exists("username"));
System.out.println("新增<'username','admin'>的键值对" + jedis.set("username","admin"));
System.out.println("新增<'username','password'>的键值对" + jedis.set("password","123546"));
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("删除键password" + jedis.del("password"));
System.out.println("查看键username所存储值的类型" + jedis.type("username"));
System.out.println("重命名key" + jedis.rename("username","name"));
System.out.println("删除当前选择数据库的所有key" + jedis.flushDB());
System.out.println("返回当前数据库中的所有key" + jedis.dbSize());
System.out.println("删除所有数据库中的所有key" + jedis.flushAll());
jdesi.close();//关闭连接

3.Jedis测试Redis事务

	Jedis jedis = new Jedis("127.0.0.1",6379);
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("user1","wyf");
    jsonObject.put("user2","csq");
    System.out.println("jsonObject:"+jsonObject);
    System.out.println(jsonObject.toJSONString());
    //开启事务
    Transaction multi = jedis.multi();
    String result = jsonObject.toJSONString();
    jedis.watch("user1");//通过watch实现乐观锁
    try {
    multi.set("user1",result);
    multi.set("user2",result);
    //成功执行事务
    multi.exec();
    } catch (Exception e) {
    //失败放弃事务
    multi.discard();
    e.printStackTrace();
    } finally {
    System.out.println("user1:" + jedis.get("user1"));
    System.out.println("user2:" + jedis.get("user2"));
    jedis.close();
    }

8.SpringBoot整合Redis

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

jedis:采用的直连,多个线程操作的话,是不安全的,如果想避免不安全,使用jedis pool连接池,更像BIO模式

BIO: 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即用户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情就会造成不必要的线程开销

lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据,更像NIO模式

NIO: 同步非阻塞,服务器实现模式为一个线程处理多个请求连接,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询连接有I/O请求就进行处理

1.导入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.配置依赖

redis:
    host: 127.0.0.1
    port: 6379

3.测试

@Autowired
private StringRedisTemplate stringRedisTemplate;
调用StringRedisTemplate中的API

9.Redis持久化

Redis是内存数据库,一旦服务器进程退出,服务器中的数据库状态也会消失,所以Redis提供了持久化功能!

1.RDB(Redis DataBase)

即内存快照,也是全量快照,当服务器宕机时,Redis中存储的数据就会丢失。这个时候就需要内存快照来恢复Redis中的数据了。

Redis提供了两个命令来生产全量的RDB文件,一个是 save ,另一个是 bgsave。

save:在主线程中执行,会导致阻塞;

bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。

不用想也知道,我们应该使用哪种方式来生成RDB文件了。

增量快照,就是指,做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。在第一次做完全量快照后,T1 和 T2 时刻如果再做快照,我们只需要将被修改的数据写入快照文件就行。但是,这么做的前提是,我们需要记住哪些数据被修改了。

2.AOF(Append Only File)

将我们所有的命令都记录下来,保存的是appendonly.aof文件,aof文件随着指令变多,文件大小越来越大,修复的速度也比rdb慢,所以默认的配置是rdb持久化,恢复的时候重新写入aof文件,不适合大数据量。

10.Redis订阅发布

//订阅一个频道,等待推送的信息
127.0.0.1:6379> subscribe wyf
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "wyf" 
3) (integer) 1
1) "message" #消息
2) "wyf" #频道名字
3) "hello" #消息的具体内容
1) "message"
2) "wyf"
3) "hello"
1) "message"
2) "wyf"
3) "hello11"
//发布者像频道发送消息
127.0.0.1:6379> publish wyf "hello"
(integer) 1
127.0.0.1:6379> publish wyf "hello"
(integer) 1
127.0.0.1:6379> publish wyf "hello11"
(integer) 1

应用场景:

1.实时聊天系统

2.订阅、关注

稍微复杂的情景我们使用消息中间件MQ

11.Redis主从复制

主机只能写,从机只能读

主机断开连接,从机依旧能够连接到主机,但是读取不到主机内容,如果主机返回,从机依旧可以直接获取到主机写的信息

全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中(只要重新连到master,全量复制将自动被执行)

增量复制:master继续将新的收集到的命令一次传给slave,完成同步

M—> S/M—>S,中间的既可以当前一个的slave,也可以当下一个的master(层层链路)

如果没有老大(master)了,能不能选择出一个老大(master)呢,手动(哨兵模式未出之前)

如果主机断开了连接,使用 slave of no one让自己变为master

12.Redis哨兵模式(自动选取master)

测试:

目前场景一主二从

1.配置哨兵模式配置文件sentinel.conf

#sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1 #这个1代表如果主机挂了,slave投票选举新的主机

2.启动哨兵

redis-sentinel sentinel.conf

13.Redis的缓存穿透和雪崩(面试高频)

  • 缓存穿透(查不到)

定义:用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是此次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库,这给持久层数据库造成很大压力,这时候就相当于出现了缓存穿透。

  • 缓存穿透的解决方案

    • 布隆过滤器
    • 缓存空对象(缺点是存储了很多空值的键,即使对空值设置了国企时间,还是会存在缓存层和存储层的数据有一点时间窗口的不一致)
  • 缓存击穿(量太大,缓存过期,空档期请求全部砸到数据库)

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key瞬间失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开一个洞。

  • 缓存击穿解决方案

    • 设置热点数据永不过期

      从缓存面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。

    • 加互斥锁

      分布式锁:使用分布式锁,保证每一个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转到了分布式锁,因此对分布式锁的考验很大。

  • 缓存雪崩

定义:在某一个时间段,缓存集中过期失效,Redis宕机

  • 缓存雪崩解决方案

    • redis高可用

      redis有可能挂掉,多设几台redis,一台挂掉之后其它的还可以继续工作,其实就是搭建集群(异地多活)

    • 限流降级(SpringCould中提到)

      缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查询和写缓存,其它线程等待。

    • 数据预热

      在正式部署之前,把数据先预先访问一遍,这样大部分访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间尽量均匀。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值