Redis (一)

Redis

写在前面

此文为我学习Redis时的总结,主要是理解Redis的基本信息,以便于温故知新,如有疏漏,请看官谅解。

文章目录

  • Redis
    • 写在前面
    • Redis介绍
      • 什么是Redis
      • 什么是NoSQL
      • NoSQL数据库分类
      • Redis应用场景
    • Redis数据类型
      • String类型
      • Hash类型
      • list类型
        • JAVA中ArrayList与LinkedList的区别
        • 应用场景
      • set类型
        • 应用场景
      • zset类型
        • zset和list类型的比较
        • 应用场景
      • 通用命令
      • KEY命名
    • Redis消息模式
    • Redis事务(弱事务)
      • 介绍
      • 事务命令
          • MULTI
          • EXEC
          • DISCARD
          • WATCH
          • UNWATCH
      • 事务失败处理
      • Redis事务典型应用—Redis乐观锁
      • 使用场景-- Redis乐观锁
    • 扩展点之Spring-data-redis
    • Redis内存模型
      • Redis内存统计
      • Redis内存分配
        • 数据
        • 进程
        • 缓冲内存
        • 内存碎片
    • Redis数据结构
      • 简单动态字符串(SDS)
      • 链表
      • 字典
      • 跳跃表
      • 整数集合
      • 压缩列表
    • 缓存淘汰策略
      • LRU原理
    • Tips 持续更新中...

Redis介绍

什么是Redis

  • Redis是一种Nosql数据库,由C语言编写的高性能键值对(key-value)的内存数据库

  • 它是【单进程单线程】的内存数据库,所以说不存在线程安全问题

  • 它可以支持并发 10w QPS 所以说性能非常优秀。之所以单进程单线程性能这么好,是因为底层采用了【IO多路复用(NIO思想)】

  • 它有优秀的读写性能和丰富的数据类型

  • 它提供了五种数据类型来存储【值】字符串类型(string)、散列类型(hash)、列表类型(list)、集合类型(set)、有序集合类型(sortedset、zset)


什么是NoSQL

  • 泛指非关系型数据库

  • 关系型数据库:有行有列,一张表内它的数据一定是有相同的列,

  • NoSQL数据库是为了解决高并发高可用高扩展大数据存储问题产生的数据库解决方案

  • NoSQL可以作为关系型数据库的良好补充,但是不能替代关系型数据库


NoSQL数据库分类

  • 键值(key-value)存储数据库

    • 相关产品:redis
    • 典型应用:内容缓存,主要用于处理大量数据的高访问负载
    • 优势:快速查询
    • 劣势:存储的数据缺少结构化
  • 列存储数据库

  • 文档型数据库

    • 相关产品MongoDB

Redis应用场景

  • 内存数据库(登录信息、购物车信息、用户浏览记录)不存在DB中
  • 缓存服务器(商品数据、广告数据、访问量统计,排行榜)使用最多 缓存DB信息,减少DB压力
  • Session共享 多个应用服务器时
  • 任务队列(秒杀、抢购、请求限流)利用 list 数据类型
  • 分布式锁的实现 利用 setnx 方法
  • 应用排行,商品评价信息 利用zset 数据类型
  • 支持发布订阅的消息模式
  • 数据过期处理 (精确到毫秒)利用expire 命令

Redis数据类型

Redis中存储数据是通过key-value格式存储数据的,其中value可以定义五种 数据类型:

  • String(字符类型)
  • Hash(散列类型)
  • List(列表类型) 有序可重复
  • Set(集合类型) 无序不可重复
  • SortedSet/Zset(有序集合类型)

除zset外都是插入顺序,zset为自然排序

String类型

  • 赋值
SET KEY VALUE
  • 取值
GET KEY
  • 取值并赋值
GETSET KEY VALUE
  • 仅当不存在时赋值
    • 使用该命令可以实现【分布式锁】的功能
setnx key value

Hash类型

  • String和Hash类型的区别:
  • hash类型适合存储那些对象数据,特别是对象属性经常发生【增删改】操作的数据
  • string类型也可以存储对象数据,一般将java对象转成json字符串进行存储,这种存储适合【查询】操作

list类型

JAVA中ArrayList与LinkedList的区别

ArrayList查询快,新增修改慢,使用数组方式存储数据,而数组在内存中是连续存储,因此根据索引查询数据速度快,而新增或者删除元素时需要涉及到位移操作,所以比较慢。

LinkedList新增修改快,查询两端快使用双向链表方式存储数据,每个元素都记录前后元素的指针,所以插入、删除操作数据时速度非常快,只是更改前后元素的指针指向即可,查询时,通过下标查询,从头开始索引,所以比较慢,但是如果查询前几个或者后几个元素比较快。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbikG75W-1605602149153)(C:\Users\18202\AppData\Roaming\Typora\typora-user-images\image-20200602140330910.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTScPcZ8-1605602149155)(C:\Users\18202\AppData\Roaming\Typora\typora-user-images\image-20200602140421336.png)]

  • redis中的列表类型(list)为一个有序的字符串列表,此处有序指的是插入顺序,是使用双向链表实现的,所以向列表两端添加元素的时间复杂度为0(1) ,获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。

应用场景

某一商品的评论列表 按照时间顺序降序、升序排序

  • 使用list存储商品评论信息,key是商品ID,value是商品评论信息列表

set类型

set类型(集合类型),其中的数据不重复且无序。会去重

sadd set a b c

应用场景

抽奖

zset类型

zset类型(有序集合)和set类型(集合)相比,多关联了一个分数,可以通过分数去快速判断,完成增删改查,查询分数最高或者最低的N个元素。

zset和list类型的比较

相同:

  1. 二者都是有序的
  2. 二者都可以快速获得一定范围内的元素

不同:

  1. list类型通过链表实现,获取靠近两端的数据速度极快,中间的数据获取相对较慢
  2. zset类型使用散列表实现,即使读取中间部分的数据也很快
  3. list不能方便的调整某个数据的位置,zset可以(通过修改分数)
  4. zset要比list更耗内存
  5. zset为自然排序,list为插入排序

应用场景

商品销售排行榜

  • 需求:根据商品销量对商品进行庞航显示
  • 设计:定义商品销售排行榜(zset集合),key为item:sellsort,分数为商品销售量。

写入商品销售量:

  • 商品编号1001的销量9,1002销量10
ZADD items:sellsort 9 1001 10 1002
  • 商品编号1001的销量加1
ZINCRBY items:sellsort 1 1001
  • 获取商品销量前十
ZREVRANGE items:sellsort 0 9 withscores

通用命令

  • keys
redis 127.0.0.1:6379> keys mylist*
1) "mylist"
2) "mylist5"
3) "mylist6"
4) "mylist7"
5) "mylist8"
  • del
del test
  • exists
redis 127.0.0.1:6379> exists HongWan
(integer) 0
redis 127.0.0.1:6379> exists age
(integer) 1
  • expire(重点)

    Redis在实际使用过程中更多的用作缓存,然而缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁

语法:

EXPIRE key seconds 设置key的生存时间(单位:秒)key在多少秒后会自动删除
TTL key 查看key生于的生存时间
PERSIST key 清除生存时间
PEXPIRE key milliseconds 生存时间设置单位为:毫秒

示例:

192.168.101.3:7002> set test 1 设置test的值为1
OK
192.168.101.3:7002> get test 获取test的值
"1"
192.168.101.3:7002> EXPIRE test 5 设置test的生存时间为5秒
(integer) 1
192.168.101.3:7002> TTL test 查看test的生于生成时间还有1秒删除
(integer) 1
192.168.101.3:7002> TTL test
(integer) -2
192.168.101.3:7002> get test 获取test的值,已经删除
(nil) 
  • rename

语法:

rename oldkey newkey
  • type

显示指定key的数据类型

语法:

type k

KEY命名

一般采用 : 表名:ID值

Redis消息模式

由于没有回滚功能,一般不用此功能

Redis事务(弱事务)

介绍

  • Redis 的事务是通过 MULTI 、 EXEC 、 DISCARD 和 WATCH 、UNWATCH这五个命令来完成的
  • Redis 的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合
  • Redis 将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行
  • Redis 不支持回滚操作
    • 大多数事务失败是因为语法错误或者类型错误,这两种错误,在开发阶段都是可以预见的。
    • Redis 为了性能方面就忽略了事务回滚。 (回滚记录历史版本)
  • 在一个事务中,命令不会被干扰,因为Redis是单进程单线程的,所以不会出现线程并发。

事务命令

MULTI
  • 开启事务
EXEC
  • 执行命令队列中的命令,退出事务
DISCARD
  • 清除命令队列退出事务
WATCH
  • watch可以对某一个活多个key进行监控,如果监控的key的数据发生变化,那么事务就不会执行,否则可以正常执行
UNWATCH
  • 清除所有先前为一个事务监控的键

事务失败处理

弱事务,不支持回滚。

语法错误

有语法错误,在事务内的所有命令不会执行;运行时的错误,之前执行成功的不会回滚。

运行错误

在事务中所有正确的命令可以执行,只有运行错误那条失败。具有弱事务性。

Redis事务典型应用—Redis乐观锁

在生产环境里,利用redis乐观锁来实现秒杀,Redis乐观锁是Redis事务的经典应用。

适用场景:大部分人参加的活动,只有小部分人能成功,如秒杀

秒杀场景描述:
秒杀活动对稀缺或者特价的商品进行定时,定量售卖,吸引成大量的消费者进行抢购,但又只有少部分消费者可以下单成功。因此,秒杀活动将在较短时间内产生比平时大数十倍,上百倍的页面访问流量和下单请求流量。由于秒杀只有少部分请求能够成功,而大量的请求是并发产生的,所以如何确定哪个请求成功了,就是由redis乐观锁来实现。

具体思路如下:
监控锁定量,如果该值被修改成功则表示该请求被通过,反之表示该请求未通过。
从监控到修改到执行都需要在redis里操作,这样就需要用到Redis事务。

使用场景-- Redis乐观锁

悲观锁:你用的时候别人不能用(排他性),互斥的,先加锁再执行,十分影响性能。

乐观锁:谁都可以干,但是只有小部分人能干成。

乐观锁基于CAS(Compare And Swap)思想(比较并替换),是不具有互斥性,不会产生锁等待而消耗资源,但是需要反复的重试,但也是因为重试的机制,能比较快的响应。因此我们可以利用redis来实现乐观锁。具体思路如下:
1、利用redis的watch功能,监控这个redisKey的状态值

2、获取redisKey的值

3、创建redis事务

4、给这个key的值+1

5、然后去执行这个事务,如果key的值被修改过则回滚,key不加1

public void watch() {
try {
String watchKeys = "watchKeys";
//初始值 value=1
jedis.set(watchKeys, 1);
//监听key为watchKeys的值
jedis.watch(watchkeys);
//开启事务
Transaction tx = jedis.multi();
//watchKeys自增加一
tx.incr(watchKeys);
//执行事务,如果其他线程对watchKeys中的value进行修改,则该事务将不会执行
//通过redis事务以及watch命令实现乐观锁
List<Object> exec = tx.exec();
if (exec == null) {
System.out.println("事务未执行");
} else {
System.out.println("事务成功执行,watchKeys的value成功修改");
}
} catch (Exception e) {
e.printStackTrace();
Redis乐观锁实现秒杀
} finally {
jedis.close();
}
}

Redis乐观锁实现秒杀

public class Second {
public static void main(String[] arg) {
String redisKey = "second";
ExecutorService executorService = Executors.newFixedThreadPool(20);
try {
Jedis jedis = new Jedis("127.0.0.1", 6378);
// 初始值
jedis.set(redisKey, "0");
jedis.close();
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
executorService.execute(() -> {
Jedis jedis1 = new Jedis("127.0.0.1", 6378);
try {
jedis1.watch(redisKey);
String redisValue = jedis1.get(redisKey);
int valInteger = Integer.valueOf(redisValue);
String userInfo = UUID.randomUUID().toString();
// 没有秒完
if (valInteger < 20) {
Transaction tx = jedis1.multi();
tx.incr(redisKey);
List list = tx.exec();
// 秒成功 失败返回空list而不是空
if (list != null && list.size() > 0) {
System.out.println("用户:" + userInfo + ",秒杀成功!
当前成功人数:" + (valInteger + 1));
}
// 版本变化,被别人抢了。
else {
System.out.println("用户:" + userInfo + ",秒杀失败");
}
}
// 秒完了
else {
System.out.println("已经有20人秒杀成功,秒杀结束");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis1.close();
}
});
}
executorService.shutdown();
}
}

扩展点之Spring-data-redis

Redis内存模型

Redis内存统计

127.0.0.1:6379> info memory
# Memory
#Redis分配的内存总量,包括虚拟内存(字节)
used_memory:853464
#占操作系统的内存,不包括虚拟内存(字节)
used_memory_rss:12247040
*****#内存碎片比例 如果小于0说明使用了虚拟内存,碎片比较多,需要重启进行整理
mem_fragmentation_ratio:15.07
#Redis使用的内存分配器
mem_allocator:jemalloc-5.1.0

Redis内存分配

数据

作为数据库,数据是最主要的部分;这部分占用的内存会统计在 used_memory 中。
Redis 使用键值对存储数据,其中的值(对象)包括 5 种类型,即字符串、哈希、列表、集合、有序集合。
这 5 种类型是 Redis 对外提供的,实际上,在 Redis 内部,每种类型可能有 2 种或更多的内部编码实
现。

进程

Redis 主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几M,在大多数生产
环境中与 Redis 数据占用的内存相比可以忽略。
这部分内存不是由 jemalloc 分配,因此不会统计在 used_memory 中。
补充说明:除了主进程外,Redis 创建的子进程运行也会占用内存,如 Redis 执行 AOF、RDB 重写时创建的子进程。
当然,这部分内存不属于 Redis 进程,也不会统计在 used_memory 和 used_memory_rss 中。

缓冲内存

缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF 缓冲区等;其中,客户端缓冲区存储客户端连接的
输入输出缓冲;复制积压缓冲区用于部分复制功能;AOF 缓冲区用于在进行 AOF 重写时,保存最近的
写入命令。
在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由 jemalloc 分配,因此会统计在
used_memory 中。

内存碎片

内存碎片是 Redis 在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致 Redis 释放的空间在物理内存中并没有释放。

但 Redis 又无法有效利用,这就形成了内存碎片,内存碎片不会统计在 used_memory 中

内存碎片的产生与对数据进行的操作、数据的特点等都有关;此外,与使用的内存分配器也有关系:如果内存分配器设计合理,可以尽可能的减少内存碎片的产生。如果 Redis 服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为重启之后,Redis 重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。

Redis数据结构

Redis基于以下数据结构创建了一个对象系统。

最终组成了Redis的五种数据类型string,hash,list,set,zset。

简单动态字符串(SDS)

没有直接使用 C 字符串(即以空字符’\0’结尾的字符数组),是一个结构体。

struct sdshdr{
//记录buf数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}

使用:

  • 所有的KEY

  • 数据里的字符串

  • AOF缓冲区和用户输入缓冲

链表

字典

跳跃表

整数集合

压缩列表

缓存淘汰策略

最大缓存

  • 在Redis中,允许用户设置最大使用内存大小maxmemory,默认为0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。
  • redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。

淘汰策略

Redis淘汰策略配置:maxmemory-policy voltile-lru,支持热配置

Redis 提供 6种数据淘汰策略:

一般选择前两种。

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-enviction(驱逐):禁止驱逐数据。默认,不推荐

LRU原理

“如果数据最近被访问过,那么将来被访问的几率也更高”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mkX2X2dR-1605602149156)(C:\Users\18202\AppData\Roaming\Typora\typora-user-images\image-20200602163053104.png)]

  1. 新数据插入到链表头部
  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部
  3. 当链表满的时候,将链表尾部的数据丢弃。在Java中可以使用LinkHashMap去实现LRU利用哈希链表。

Tips 持续更新中…

  1. Redis是什么?

    是由C语言编写的高性能key-value键值对的非关系型数据库,

  2. Redis的主要应用场景有哪些?

    主要应用场景:频繁读取的数据,例如商品信息、购物车 、用户信息等等,还有秒杀,

  3. Redis数据类型有哪些?

    五种 string hash list set zset

  4. Redis的数据类型和各自的使用场景及注意事项是什么?

string 一般数据例如 库存信息,一些配置信息,频繁的【查询】的时候用string,也可以存对象,一般存的是json的字符串

hash 存放的是一个对象,当这个对象频繁【增删改】时用

list 存放的是一个list,有序可重复,是一个双向链表结构,链表两端的数据【增删查】速度很快,存放商品的评价列表

set 无序不重复,场景主要用于随机抽奖

zet 有序不重复,场景主要用于各类排行榜

  1. Redis的消息模式是如何实现的?
  2. Redis持久化方式
  3. RDB和AOF
  4. Redis缓存穿透

2.什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?

3.Redis 有哪些架构模式?讲讲各自的特点

4.使用过Redis分布式锁么,它是怎么实现的?

5.使用过Redis做异步队列么,你是怎么用的?有什么缺点?

6.什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?

7.Redis常用命令

8.为什么Redis 单线程却能支撑高并发?

9.说说Redis的内存淘汰策略

10.Redis的并发竞争问题如何解决?

你可能感兴趣的:(JAVA技术,redis)