入门Redis概述
1 )选择Redis是因为其高性能
- 因为 Redis 它数据存储的机制是存在内存中的,减少了传统关系数据库的磁盘IO
- 它是单线程的保证了原子性,它还提供了事务,锁等相关的机制
2 )Redis 环境安装配置
- linux 或 docker 环境
3 )Redis 的并发下的模式演进
- 并发在十万以下的时候,Redis提供了单机单节点模式
- 官方测试的数据: 读取每秒11万次,写入每秒8万1, 所以性能是非常高的
- 当并发上来了,10万 到 20万左右的时候,使用 Redis 提供到读写分离, 主从模式
- 在这种模式下,主节点肯定要保持高可用的,Redis 提供了哨兵监控机制
- 为了降低各节点之间存储数据的压力,Redis 提供了集群模式
4 )了解微服项目的构建以及springboot应用的开发
- 我们围绕着项目来打通Redis任督二脉的,以项目作为驱动
- 在项目中实现 Redis 各种应用场景:实现缓存,保证缓存中的数据一致性等等
5 )理论准备
- 理论作为基石,需要了解缓存相关中间件,(Redis, Memcache 和 Ehcache) 的比较
- 对 Redis 的基本理解,Redis 它的定位是什么?解决了什么问题?
- 微服务相关的概念,最终是以微服务项目作为驱动开发的
5 )实际操作
- 在linux环境中安装 Redis,包括了解它的一些配置信息,每一个配置项都代表什么意思
- 以及把 Redis 作为开机自启项加入到系统服务当中,它的启动, 停止该如何去操作
- Redis 的基本命令
- 数据库表结构的设计
- Redis 作为一款 nosql 数据库, 肯定不能百分百替代关系数据库的
- 在一般项目中都是 Redis 和 mysql 组合,而 Redis 都是作为缓存层而存在
- 它最终缓存的是关系数据库中的数据
- SpringBoot & SpringCloud项目搭建
- 通过一个微服务的项目,也可以使用 go-micro 或 其他
- 这里了解 SpringBoot & SpringCloud 类似的微服务项目环境如何搭建
Redis 版本演进
- 目前Redis有8个版本,生产环境最新版是 7,我目前常用的还是 6
- 我们看下之前的一些版本更新的变化
1 )Redis 2.6
- 键的过期时间支持毫秒
- 从节点支持只读功能
2 ) Redis2.8
- 可以用bind命令绑定多个IP地址
- 发布订阅添加了pub/sub
- Redis Sentinel第二版,相比于Redis2.6的 Redis Sentinel, 此版本已经变成生产可用
3 )Redis3.0 (里程碑)
- Redis最大的改动就是添加Redis的分布式实现Redis Cluster
4 )Redis3.2
- Redis3.2在2016年5月6日正式发布,相比于Redis3.0主要特征如下:
- 添加GEO相关功能
- 新的List编码类型:quicklist
5 )Redis4.0(重大改版)
- 提供了模块系统,方便第三方开发者拓展Redis的功能
- 提供了新的缓存剔除算法:LFU(Last Frequently Used),并对已有算法进行了优化
- 提供了非阻塞del和flushall/flushdb功能,有效解决删除了bigkey可能造成的Redis阻塞
- 提供了RDB-AOF混合持久化格式,充分利用了AOF和RDB各自优势。
6 )Redis5.0
- 新的Stream数据类型
- 客户经常连接和断开连接时性能更好
7 )Redis6.0
- 多线程IO。多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。
Redis 安装可能出现的问题
1 )gcc的版本的问题
- 先安装c语言依赖
- yum install -y gcc-c++ autoconf automake
- 一般系统会自带 autoconf 和 automake
- redis 在 centos 7 和 8 里面需要不同gcc版本
- centos 8 中 gcc 版本高,安装redis比较简单
- centos 7 中 安装最新版redis, 必须升级gcc
- 对 gcc 做升级
- $
yum install -y centos-release-scl scl-utils-build
安装 scl 源 - $
yum install -y devtoolset-9-toolchain
安装 9 版本的 gcc、gcc++、gdb 工具链 (toolchain) - $
scl enable devtoolset-9 bash
临时覆盖系统原有的 gcc 引用 - $
gcc -v
查看 gcc 当前版本
- $
2 )编译安装的注意事项
- 下载解压后,进行预编译
- 之后编译 make (检查要安装的软件所依赖的环境是否缺失)
- 创建一个redis 目录 $
mkdir -p /usr/local/redis
- 安装 $
make PREFIX=/usr/local/redis install
- 前台启动:$
./redis-server
默认加载的配置是解压后的那个目录中的 redis.conf - 可以将该文件拷贝到 /usr/local/redis 目录中,之后可以进行修改
daemonize yes # 这里默认是 no, 修改成yes后守护进程打开, 这样就可以后台运行了
- 再次启动 $
./redis-server ./redis.conf
,这样就可以后台运行了 - 检查 $
ps -ef | grep redis
Redis 启动方式
-
配置开机启动(centos7以上)
-
在系统服务目录里创建redis.service文件, $
vim /etc/systemd/system/redis.service
[Unit] Description=redis-server After=network.target[Service] Type=forking ExecStart=/usr/local/redis/bin/redis-server/usr/local/redis/bin/redis.conf PrivateTmp=true[Install] WantedBy=multi-user. target
-
重载系统服务:$
systemctl daemon-reload
-
启动 $
systemctl start redis.service
-
停止 $
systemctl stop redis.service
-
重启 $
systemctl restart redis.service
-
开启成功,将服务加入开机自启: $
systemctl enable redis.service
Redis 配置
- Redis 支持很多的参数,但都有默认值
- daemonize 默认情况下,redis不是在后台运行的,如果需要在后台运行,把该项的值更改为 yes
- bind 指定 Redis 只接收来自于该IP地址的请求,可以通过空格来支持多项
- port 监听端口,默认为6379
- databases 设置数据库的个数,默认使用的数据库是0,一般翻倍修改
- save 设置 Redis 进行数据库镜像的频率
- dbfilename 镜像备份文件的文件名
- dir 数据库镜像备份的文件放置的路径
- requirepass 设置客户端连接后进行任何其他指定前需要使用的密码,不推荐,这种方式被ACL取代
- maxclients 限制同时连接的客户数量
- maxmemory 设置 redis 能够使用的最大内存
Redis 基本数据类型
- Redis作为内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
- 它的value支持多种类型的数据结构,基本数据结构包含:字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)五种。
- 这五种数据结构在我们工作中经常使用到,也是最重要的
1 )字符串(strings)
1.1.类型介绍
- 字符串是Redis最简单的储存类型,它存储的值可以是字符串、整数或者浮点数,对整个字符串或者字符串的其中一部分执行操作;
- 对整数或者浮点数执行自增(increment) 或者 自减(decrement)操作
- Redis的字符串是一个由字节组成的序列,跟java里面的ArrayList有点类似,采用预分配冗余空间的方式来减少内存的频繁分配,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len
- 当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间
- 需要注意的是字符串最大长度为512M
- 扩充方式是扩充一次,不足则继续尝试扩充一次,直到满足空间大小
1.2.应用场景
- 字符串类型在工作中使用广泛,主要用于缓存数据,提高查询性能
- 比如存储登录用户信息、电商中存储商品信息、可以做计数器(想知道什么时候封锁一个IP地址(访问超过几次))等等
1.3.操作指令
- set key value: 添加一条String类型数据
- get key: 获取一条String类型数据
- mset key1 value1 key2 value2: 添加多条String类型数据
- mget key1 key2:获取多条String类型数据
- incr key: 白增(+1)
- decr key: 自减(-1)
- incrby key: 指定步长自增
- decrby key: 指定步长自减
- del key: 删除数据,适用于任何数据类型
2 )散列(hashes)
2.1.类型介绍
- 散列相当于Java中的HashMap, 内部是无序字典
- 实现原理跟HashMap一致,一个哈希表有多个节点,每个节点保存一个键值对
- 与Java中的HashMap不同的是,rehash的方式不一样
- 因为Java的 HashMap 在字典很大时,rehash是个耗时的操作,需要一次性全部rehash
- Redis为了高性能,不能堵塞服务,所以采用了渐进式 rehash策略
- 渐进式 rehash 会在 rehash的同时,保留新旧两个 hash结构,查询时会同时查询两个 hash结构
- 然后在后续的定时任务中以及 hash 操作指令中,循序渐进地将旧 hash的内容一点点迁移到新的 hash 结构中,当搬迁完成了,就会使用新的hash结构取而代之
- 当hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收
2.2.应用场景
- Hash也可以同于对象存储,比如存储用户信息,与字符串不一样的是,字符串是需要将对象进行序列化(比如json序列化)之后才能保存,而Hash则可以讲用户对象的每个字段单独存储,这样就能节省序列化和反序列的时间
- 如下:此外还可以保存用户的购买记录,比如key为用户id,field为商品id,value为商品数量
- 同样还可以用于购物车数据的存储,比如 key 为用户id, field为商品id, value 为购买数量等等
2.3.操作指令
- 注意这里 在 4.0 之前有细微的改动,4.0之前使用 hmset 后面修改为 hset
- 这里直接使用示例,例如:
- 设置哈希对象 hset userInfo username wang age 18 addr beijing # 返回 field 的数量
- 获取一个属性 hget userInfo username # wang
- 获取多个属性 hmget userInfo username age # wang 18
- 获取所有属性 hgetall userInfo # 这里会输出所有 fields 和 value
- 获取属性长度 hlen userInfo # 2
- 指定属性步长 hincrby userInfo age 2 # 20
- 指定属性步长 hincrby userInfo age 2 # 22 可以看到每次加2
- 删除指定属性 hdel userInfo age
- 删除哈希 del userInfo
3 )列表(lists)
3.1. 类型介绍
- Redis中的lists相当于Java中的LinkedList,实现原理是一个双向链表(其底层是一个快速列表),即可以支持反向查找和遍历,更方便操作
- 插入和删除操作非常快,时间复杂度为0(1),但是索引定位很慢,时间复杂度为O(n)
3.2. 应用场景
- lists的应用场景非常多,可以利用它轻松实现热销榜;
- 可以实现工作队列(利用lists的push操作,将任务存在lists中,然后工作线程再用pop操作将任务取出进行执行);
- 可以实现最新列表,比如最新评论等
3.3. 操作指令
- 左边插入数据 lpush animals dog cat mouse # 在动物列表中左侧插入3个动物,返回插入的个数,内部排列为:mouse cat dog
- 右边插入数据 rpush animals fish # 这时候动物列表内部排列为:mouse cat dog fish, 内部索引为: 0 1 2 3
- 最左边弹出 lpop animals 最左边弹出 返回弹出元素 mouse 动物列表还有:cat dog fish
- 最右边弹出 rpop animals 最右边弹出 返回弹出元素 fish 动物列表还有:cat dog
- 查看数据,从左到右 lrange animals 0 1 返回 cat dog 这里的 0 1 是从左到右的索引,左右索引缺一不可
4 )集合(sets)
4.1.类型介绍
- 集合类似Java中的HashSet,内部实现是一个value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因
4.2.应用场景
- redis的sets类型是使用哈希表构造的,因此复杂度是0(1),它支持集合内的增删改查,并且支持多个集合间的交集、并集、差集操作
- 可以利用这些集合操作,解决程序开发过程当中很多数据集合间的问题。比如计算网站独立ip, 用户画像中的用户标签,共同好友等功能
4.3.操作指令
- 在一个集合里添加元素 sadd nums 1 2 3 # 在 nums 这个集合里添加元素,返回增加成功元素的个数 3
- 再次在nums中测试添加 sadd nums 1 1 2 2 3 3 # 返回 0 这里看到没有添加进入一个,符合 set 的特征
- 查看集合所有元素 smembers nums # 1 2 3
- 删除指定的元素 srem nums 2 # 返回1 表示移除了 1 条
- spop nums 随机移除一个 # 返回被移除的元素
- 两个集合的交集并集和差集
- sadd nums1 1 2 3
- sadd nums2 2 3 4
- 这里看两个集合的交集 sinter nums1 nums2 # 输出 2 3
- 两个集合的差集(基于第一个集合左比较) sdiff nums1 nums2 # 1
- 两个集合的交集 sunion nums1 nums2 # 1 2 3 4
5 )有序集合(sorted sets)
5.1.类型介绍
- sorted sets是Redis类似于 SortedSet 和 HashMap的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个value赋予一个score,代表这个value的排序权重。
- 内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
- sorted sets中最后一个value被移除后,数据结构自动删除,内存被回收
5.2.应用场景
- 主要应用于根据某个权重进行排序的队列的场景,比如游戏积分排行榜,设置优先级的任务列表,学生成绩表等
5.3.操作指令
- 这里直接使用示例,例如:
- zadd rank 66 zhang 88 li 77 wang # 在 rank 集合中插入3条数据,每条数据有自己对应的 score, 默认顺序按score值从低到高,重复插入操作不生效
- zrange rank 0 3 # 这里会从小到大输出 zhang wang li
- zrangebyscore rank 77 88 这里会输出score 从 77 到 88 的数据
- zrem rank li # 这里删除元素,返回删除成功的个数
- zcard rank # 返回获取到的元素个数
- zcount rank 77 88 # 统计在一个score的范围内有多少个元素
- zrank rank wang # 返回某个元素在有序集合中所在的位置
- zrevrank rank wang # 返回某个元素在有序集合反转后的位置
Redis 如何支撑 10w+ QPS
1 ) Redis 特点
- 内存数据库,速度快,也支持数据的持久化。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供Lists、Hashes、Sets、Sorted Sets 等多种数据结构的存储
- Redis支持数据的备份(master-slave)与集群(分片存储),以及拥有哨兵监控机制
- 支持事务
2 ) 优势
- 性能极高-Redis能读的速度是110000次/s,写的速度是81000次/s
- 丰富的数据类型-Redis支持 Strings、Lists、Hashes、Sets、Sorted Sets 等数据类型操作
- 原子操作-Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行(事务)
- 丰富的特性-Redis还支持 publish/subscribe,通知,key 过期等特性
3 ) Redis、Memcached、Ehcache 的区别
Ehcache | Memcached | Redis | |
---|---|---|---|
最新版本 | 2017年2月 3.3.0 | 2018年7月 1.5.9 | 2018年7月 5.0 Beta |
许可证 | 开源免费 | 开源免费 | 开源免费 |
实现语言 | Java | C | C |
服务器系统 | Java | FreeBSD、Linux、OSX、Unix、Windows | FreeBSD、Linux、OSX、Unix、Windows |
数据类型 | 支持 | 不支持 | 支持多种:string、hash、list、set、sorted set等 |
客户端语言 | Java | .Net、C、C++、ColdFusion、Erlang、Java、Lisp、Lua、Ocaml、Perl、PHP、Python、Ruby | C、C#、C++、D、Erlang、Go、Haxe、Java、Node.js、Lisp、Lua、MatLab、Objective-C、Pascal、Perl、PHP、R、Ruby、Scala、Swift、Visual Basic 等 30十多种 |
- 在分布式的情况下, 用 Ehcache,就不能很好的去实现分布式项目之间缓存的同步共享的问题
- Memcached 对于数据类型比较单一,存在一些瓶颈
- 最终通过比较,还是选择 Redis
- 这三个中间件都可以应用于缓存,但目前市面上使用Redis的场景会更多,更广泛,其原因是:Redis性能高、原子操作、支持多种数据类型,主从复制与哨兵监控,持久化操作等
4 ) Redis的高并发
- 官方的bench-mark数据:测试完成了50个并发执行100000个请求, 设置和获取的值是一个256字节字符串
- 结果:读的速度是110000次/s,写的速度是81000次/s。redis尽量少写多读,符合缓存的适用要求
- 单机redis支撑万级,如果10万+可以采用主从复制的模式
4.1.原理
- Redis是纯内存数据库,所以读取速度快。
- Redis使用的是非阻塞10,10多路复用,减少了线程切换时上下文的切换和竞争。
- Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
- Redis存储结构多样化,不同的数据结构对数据存储进行了优化加快读取的速度。
- Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
4.2. Redis的单线程
选择单线程原因
- 不需要各种锁的性能消耗
- 单线程多进程集群方案
- CPU消耗
优劣对比
单进程单线程优势
- 代码更清晰,处理逻辑更简单
- 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
- 不存在多进程或者多线程导致的切换而消耗CPU
单进程单线程弊端
- 无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善
4.3 IO多路复用技术
redis 采用网络10多路复用技术来保证在多连接的时候,系统的高吞吐量
- 举个现实生活的例子,从左边水龙头流向右边,传统的模式下,需要建立5个管子,如下
- 而在 IO 多路复用模式下,只需要建立一根管子让左侧水龙头分别与这根管子相连即可,如下
- 最终目的,都是要提高系统的吞吐量,而提高吞吐量就是为了快速的写入和读取
- 它拥有这些特性才能保证 Redis 在高并发的场景下,仍然是拥有非常高的性能的