此文为我学习Redis时的总结,主要是理解Redis的基本信息,以便于温故知新,如有疏漏,请看官谅解。
Redis是一种Nosql数据库,由C语言编写的高性能键值对(key-value)的内存数据库
它是【单进程单线程】的内存数据库,所以说不存在线程安全问题
它可以支持并发 10w QPS 所以说性能非常优秀。之所以单进程单线程性能这么好,是因为底层采用了【IO多路复用(NIO思想)】
它有优秀的读写性能和丰富的数据类型
它提供了五种数据类型来存储【值】字符串类型(string)、散列类型(hash)、列表类型(list)、集合类型(set)、有序集合类型(sortedset、zset)
泛指非关系型数据库
关系型数据库:有行有列,一张表内它的数据一定是有相同的列,
NoSQL数据库是为了解决高并发、高可用、高扩展、大数据存储问题产生的数据库解决方案
NoSQL可以作为关系型数据库的良好补充,但是不能替代关系型数据库
键值(key-value)存储数据库
列存储数据库
文档型数据库
Redis中存储数据是通过key-value格式存储数据的,其中value可以定义五种 数据类型:
除zset外都是插入顺序,zset为自然排序
SET KEY VALUE
GET KEY
GETSET KEY VALUE
setnx key value
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)]
某一商品的评论列表 按照时间顺序降序、升序排序
set类型(集合类型),其中的数据不重复且无序。会去重
sadd set a b c
抽奖
zset类型(有序集合)和set类型(集合)相比,多关联了一个分数,可以通过分数去快速判断,完成增删改查,查询分数最高或者最低的N个元素。
相同:
不同:
商品销售排行榜
写入商品销售量:
ZADD items:sellsort 9 1001 10 1002
ZINCRBY items:sellsort 1 1001
ZREVRANGE items:sellsort 0 9 withscores
redis 127.0.0.1:6379> keys mylist*
1) "mylist"
2) "mylist5"
3) "mylist6"
4) "mylist7"
5) "mylist8"
del test
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 oldkey newkey
显示指定key的数据类型
语法:
type k
一般采用 : 表名:ID值
由于没有回滚功能,一般不用此功能
弱事务,不支持回滚。
语法错误
有语法错误,在事务内的所有命令不会执行;运行时的错误,之前执行成功的不会回滚。
运行错误
在事务中所有正确的命令可以执行,只有运行错误那条失败。具有弱事务性。
在生产环境里,利用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();
}
}
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
作为数据库,数据是最主要的部分;这部分占用的内存会统计在 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的五种数据类型string,hash,list,set,zset。
没有直接使用 C 字符串(即以空字符’\0’结尾的字符数组),是一个结构体。
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
使用:
所有的KEY
数据里的字符串
AOF缓冲区和用户输入缓冲
最大缓存
淘汰策略
Redis淘汰策略配置:maxmemory-policy voltile-lru,支持热配置
Redis 提供 6种数据淘汰策略:
一般选择前两种。
“如果数据最近被访问过,那么将来被访问的几率也更高”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mkX2X2dR-1605602149156)(C:\Users\18202\AppData\Roaming\Typora\typora-user-images\image-20200602163053104.png)]
Redis是什么?
是由C语言编写的高性能key-value键值对的非关系型数据库,
Redis的主要应用场景有哪些?
主要应用场景:频繁读取的数据,例如商品信息、购物车 、用户信息等等,还有秒杀,
Redis数据类型有哪些?
五种 string hash list set zset
Redis的数据类型和各自的使用场景及注意事项是什么?
string 一般数据例如 库存信息,一些配置信息,频繁的【查询】的时候用string,也可以存对象,一般存的是json的字符串
hash 存放的是一个对象,当这个对象频繁【增删改】时用
list 存放的是一个list,有序可重复,是一个双向链表结构,链表两端的数据【增删查】速度很快,存放商品的评价列表
set 无序不重复,场景主要用于随机抽奖
zet 有序不重复,场景主要用于各类排行榜
2.什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?
3.Redis 有哪些架构模式?讲讲各自的特点
4.使用过Redis分布式锁么,它是怎么实现的?
5.使用过Redis做异步队列么,你是怎么用的?有什么缺点?
6.什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?
7.Redis常用命令
8.为什么Redis 单线程却能支撑高并发?
9.说说Redis的内存淘汰策略
10.Redis的并发竞争问题如何解决?