赞
踩
Redis有哪些特点?
适合人群:
技术储备:
授课思路:
课程目标
目录:
Redis是什么?
开源项目
它是一个基于键值的存储服务系统(是一种Map结构,一个key值,一个value值)
支持多种数据结构
性能高,功能丰富
应该问,还有谁没有使用过Redis?它已经深入到我们的方方面面,众多大厂都在使用它。
主从复制是高可用的基础。
支持10W OPS的读写
内存处理
不同种类的存储设备的读写区别:
除了上述的几种数据结构外,Redis还支持BitMaps(位图)、HyperLogLog(超小内存唯一值计数)、GEO(地理信息定位)
通过它的特性可以实现很多的功能,满足各种不同的场景,使用的好的话,它就像一把瑞士军刀一样短小精悍!
安装教程:
wget http://download.redis.io/releases/redis-3.0.7.tar.gz
tar -xzf redis-3.0.7.tar.gz
In -s redis-3.0.7 redis
cd redis
make && make install
Redis安装(windows)
三种启动方法:
最简启动
redis-server
动态参数启动:
redis-server --port 5380
配置文件启动
redis-server configPath
通过配置文件来进行启动,我们将配置都写在这个配置文件中,通过传入配置文件路径来执行配置文件内的内容
比较
验证是否启动的命令:
ps -ef | grep redis
netstat -antpl | grep redis
redis-cli -h ip -p port ping
Redis客户端连接
Redis客户端返回值
Redis 的守护进程默认是关闭的,建议选为Yes,这样能够打印日志。对外端口号默认是6379;工作目录关系到日志文件和持久化文件存在于哪个目录中;
config get *
命令可以显示有多少个配置,图示如下:通用命令:
keys:
keys 命令一般不在生成环境使用,它是一个重命令,数据量大会非常慢,且会阻塞其他命令,如果真的有这样的需求可以使用其他命令替代;
keys* 怎么使用? 1. 热备从节点 2. scan
dbsize
线上可以使用
exists
一般线上可以使用,注意一些特殊场景
del
expire、ttl、persist
ttl可以用来观测过期时间,而persist 命令可以移除掉过期时间限制,它就会没有过期时间,即永不过期。有过期时间且未过期的返回值大于0,没有过期时间永不过期的数据等于-1,如果为-2说明设置了过期时间且数据已经过期。
type
时间复杂度
通过时间复杂度的统计,我们可以清晰地认识到哪些命令可以在生产环境中使用,哪些命令的大致执行耗时等情况
数据结构及编码:
以空间换时间的话,我们可以用hash结构,使用更小的空间达到效果;它内部经过了压缩
redisObject
图示:
redis为什么这么快?
第一条是主要原因,第二条和第三条是辅助。
redis的单线程需要注意什么?
字符串键值结构
字符串的value不能大于512MB
场景:
get、set、del命令
incr、decr、incrby、decrby命令
实战:
incr userid:pageview
(单线程:无竞争) userid:pageview代表key值名称,每个用户+每个页面作为一个唯一的值先查询redis,如果不存在则访问mysql,查询出来后再将数据存入redis中后再返回给前端;
利用redis的单线程特性;
set、setnx、setxx命令
mget、mset命令:
使用mget可以省略大量的网络时间,使用它的时候要注意场景,如果查询的数据量特别大也需要注意慎重。
getset、append、strlen命令
incrbyflot、getrange、setrange命令
字符串总结
哈希键值结构:
它的特点有点类似于一个map内部又嵌套了一个map 结构。
注意:
hget/hset/hdel
hexists/hlen
hmget/hmset
实战:记录网站每个用户个人主页的访问量?
hincrby user:1:info pageview count
实战:缓存视频的基本信息(数据源在mysql中)伪代码
hgetall/hvals/hkeys
注意:小心使用hgetall,它的执行速度比较慢,后面的将等待;
相似API:
左边的是string 里的相关api,右侧是hash相关的api
使用string、hash 对比:
方案一: v1: 采用string结构,直接存取,value为对象的json:
方案二:v2: 采用string结构,将属性值filed 提取出来与key拼接作为一个key,它这个key是key+field的组合:
方案三:采用hash结构,field作为一个大map中的小map的key:
各个方案对比如下:
方案一的节约内存是跟方案二比而得出的;哈希不能对单独的属性数据设置过期时间,只能整体设置过期时间;
hsetnx/hincrby/hincrbyfloat
列表结构:
特点:有序、可以重复、左右两边插入弹出
rpush:
lpush:
linsert
lpop:
执行lpop 后,就会弹出a ,然后list内的结果为: bcd
rpop:
l是从左边处理(left),r是右边处理(right),所以此处是右边弹出最后一个值,则结果为abc,被弹出的是d
lrem:
lrem listkey 0 a
此处count 是0 说明删除全部包含a的,所以执行后图示内容只剩下ccbf
lrem listkey -1 c
因为此处的count 是-1 则只会删除一条符合条件的数据,且从右侧开始,所以最终结果从ccbf => cbf
ltrim:
1 和4 代表起始位置和结束的索引位置,所以bcde会被保留,其他则被舍弃,结果为 bcde
在大数据量下性能相对较好,推荐使用,比删除性能更优
lrange:
源数据不会发生改变,这里只是获取指定范围的数据,不对数据本身进行操作
lindex:
llen:
lset:
lset listkey 2 java
则将图中的c变成了java,最终结果为:abjavadef演示:
实战:
通过时间排序存入,然后获取最新的微博可以拿取0~10 索引的数据;
blpop/brpop
通过List的一些操作可以实现一些数据结构:
zset是一个有序集合。它是一个key-value 集合,但是value中是由两部分组成,一部分是score 一部分是value, 通过score 让整个集合有序:
集合 Vs 有序集合
列表 Vs 有序集合
有序集合都是以Z开头的命令
zadd
zrem:
zscore:
zincrby:
zcard:
演示:
zrange:
zrangebyscore:
zcount:
zremrangebyrank:
zremrangebyscore:
演示:
实战:
有序集合总结:
操作类型 | 基本命令 |
---|---|
基本操作 | zadd、zrem、zcard、zincrby、zscore |
范围操作 | zrange、zrangebyscore、zcount、zremrangebyrank |
集合操作 | zunionstore、zinterstore |
获取Jedis
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
Jedis jedis = new Jedis("127.0.0.1",6379)
jedis.set("hello","world")
String value = jedis.get("hello")
Jedis基本使用:
Jedis连接池使用:
简单使用:
Jedis jedis = null;
try{
// 1. 从连接池获取jedis对象
jedis = jedisPool.getResource();
// 2. 执行操作
jedis.set("hello","world");
}catch(Exception e){
e.printStackTrace();
}finally{
if(jedis!= null)
// 如果使用JedisPool,close操作不是关闭连接,代表归还连接池
jedis.close();
}
官网下载:
简单使用-连接:
简单使用-命令:
做一个简单介绍,具体开发可以参考官方文档
commons-pool 配置(1)- 资源数控制:
参数名 | 含义 | 默认值 | 使用建议 |
---|---|---|---|
maxTotal | 资源池最大连接数 | 8 | 后面讨论 |
maxIdle | 资源池允许最大空闲连接数 | 8 | 后面讨论 |
jmxEnabled | 是否开启jmx监控,可用于监控 | true | 建议开启 |
commons-pool 配置(2)- 借还参数
参数名 | 含义 | 默认值 | 使用建议 |
---|---|---|---|
blockWhenExhausted | 当资源池用尽后,调用者是否要等待。只有当为true时,下面的maxWaitMillis 才会生效 | true | 建议使用默认值 |
maxWaitMillis | 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒) | -1: 表示永不超时 | 不建议使用默认值 |
testOnBorrow | 向资源池借用连接时是否做连接有效性检测(ping),无效连接会被移除 | false | 建议false |
testOnReturn | 向资源池归还连接时是否做连接有效性检测(ping),无效连接会被移除 | false | 建议false |
出现问题的一些地方:
推荐连接池写法:
生命周期:
两个配置:
配置方法:
config get slowlog-max-len = 128
config get slowlog-log-slower-than= 10000
config set slowlog-max-len 1000
config set slowlog-log-slower-than 1000
慢查询命令
运维经验
1次网络命令通信模型:
批量网络命令通信模型:
什么是流水线?
流水线的作用?
使用流水线可以类似管道的作用,同时减少了重复连接和网络耗时,在大量命令同时执行的情况下,可以极大地减少执行耗时,提高性能;但应注意大量命令的条数,如果命令过多,导致慢查询,其他的命令因此而陷入等待,可能会引发其他的问题。
性能对比:
没有pipeline:
使用pipeline:
使用建议:
Redis 不支持消息堆积,故不支持获取历史消息;只能收取到关注的频道,如果没有关注则不会收到。
位图:
什么是位图?
位图定义?
直接去操作位
setbit:
getbit:
bitcount:
bitop:
bitpos:
实战:
hyperloglog是什么?
type hyperloglog_key
后,返回结果为:string说明它的类型本质是String
API:
pfadd key element [element...]
pfcount key [key...]
pfmerge destkey sourcekey [sourcekey ...]
演示:
内存消耗(百万独立用户)
使用经验:
什么是持久化?
持久化的实现方式
MySQL Dump / Redis RDB
MySQL Binlog/kHbase HLog/Redis AOF
什么是RDB?
触发机制-主要三种方式
直接执行save即可,但是有一个问题,它是同步命令,大批量数据下可能会造成redis的阻塞.
它是创建了一个fork子进程去执行,是异步的;
触发机制-不容忽略方式 & 自动触发
RDB持久化的优缺点:
RDB总结:
RDB有什么问题?
AOF 运行原理 - 创建
AOF的三种策略:
三种策略比较:always/everysec/no
命令 | always | everysec | no |
---|---|---|---|
优点 | 不丢失数据 | 每秒一次fsync 丢一秒数据 | 不用管 |
缺点 | IO开销较大,一般的sata盘只有几百TPS | 丢1秒数据 | 不可控 |
AOF重写:
AOF 重写会触发Redis的缓存淘汰策略。
AOF重写的两个方式:
从Redis 2.4开始,AOF重写由Redis自行触发,BGREWRITEAOF仅仅用于手动触发重写操作。
AOF重写配置:
命令 | RDB | AOF |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
轻重 | 重 | 轻 |
RDB是一种快照形式,能够直接将某一时刻的完整数据保存;它能够很快的恢复,但是保存时却很耗费性能;而AOF相反,它是部分数据一直累计写入,恢复时很慢,保存时基本不耗费性能;我们可以根据实际场景来使用,也可以结合使用;
子进程开销和优化
硬盘优化:
两边的数据一样;
两种实现方式:
命令实现:
配置实现:
slaveof ip port
slave-read-only yes
比较:
方式 | 命令 | 配置 |
---|---|---|
优点 | 无须重启 | 统一配置 |
缺点 | 不便于管理 | 需要重启 |
runid: redis在启动的时候,会随机生成一个id来作为这个运行中的redis的标识;主从复制时,可以判断redis是否发生了变化(比如重启后的redis的runid与之前是不一样的,新加的又会多一个runid)
redis-cli -p 6379 info server | grep run
偏移量:
redis-cli -p 6379 info replication
psync ? -1
: 同步命令,可以完成全量复制、部分复制的功能;第一次全量复制:
节点运行ID不匹配:
复制积压缓冲区不足:
作用:
主从复制问题:
一主两从架构问题:
客户端的原理都是类似的,无论什么语言,基本上都是这样子来进行实现;
出现原因:
节点下线:
节点上线:
我们可以使用更强悍的机器,使用超大内存,超牛的CPU,但是单机机器始终有性能上限,且单机成本昂贵性价比不高;使用集群可以解决这些问题。
分布式数据库-数据分布:
分区方式:
顺序分区:
哈希分区:
数据分布对比:
分布方式 | 特点 | 典型产品 |
---|---|---|
哈希分布 | 数据分散度高,键值分布与业务无关,无法顺序访问,支持批量操作 | 一致性哈希Memcache Redis Cluster 其他产品 |
顺序分布 | 数据分散度易倾斜 键值业务相关 可顺序访问 支持批量操作 | BigTable HBase |
哈希分布:
图示:
多倍扩容:
节点取余的优缺点:
它有一个配置叫: cluster-enabled:yes 配置是否以节点方式启动
计算key,计算槽的位置
配置好了就每个服务器运行并加载对应的conf文件
cluster-enabled yes
cluster-node-timeout 15000
cluster-config-file "nodes.conf"
cluster-require-full-coverage yes
创建单个文件的配置:
copy出五个节点需要的配置:
启动7000节点redis,然后查看是否成功启动:
启动五个节点信息
查看所有redis进程,观察是否都启动成功
查看单节点配置信息
查看单节点信息:
第一个命令:redis-cli -p 7000 cluster meet 7001 ,可以将7000节点与7001节点握手。 第二个命令: redis-cli -p 7000 cluster nodes 是查看7000节点所属的集群信息
redis-cli -p 7000 cluster meet 127.0.0.1 7002
redis-cli -p 7000 cluster meet 127.0.0.1 7003
redis-cli -p 7000 cluster meet 127.0.0.1 7004
redis-cli -p 7000 cluster meet 127.0.0.1 7005
分配槽我们可以写一个简单的脚本来分配槽
执行脚本:
sh addslots.sh 0 5461 7000
验证7000 的执行脚本是否执行成功:
看出已经分配了5462个槽,说明执行成功;
在集群中查看此节点信息:
发现7000节点中多了 0-5461 内容,说明它的槽已经被分配
sh addslots.sh 5462 10922 7001
sh addslots.sh 10923 16383 7002
注意:
config get cluster*
命令,查询出来的cluster-require-full-coverage
的值为no ,则不需要所有节点都被分配槽才可以使用。设置三主三从:
里面的4918那串数字可以通过
redis-cli -p 7000 cluster nodes
查到;
完成过程中我们不会使用手动安装,我们可以使用官方工具来进行,但是手动安装可以让我们理解完整的过程;
我们这里的教程是一个服务器中的,多个服务器之间的安装与此类似。
cp ${REDIS_HOME}/src/redis-trib.rb /usr/local/bin
cat ruby.sh
tar -xvf ruby-2.3.1.tar.gz
cd ruby-2.3.1
./configure -prefix=/usr/local/ruby
make && make install
ruby -v
,cd ..
wget http://rubygems.org/downloads/redis-3.3.0.gem
sudo gem install -l redis-3.3.0.gem
sudo gem list -- check redis gem
的相关依赖,如图所示:
cd redis
cd src
./redis-trib.rb
前面的工作做好了,我们需要配置开启redis,如图所示:
将我们之前手动搭建的集群进行杀死,然后替换成我们现在的一键搭建:
cd ../data
rm -rf *
前面准备工作做完,后续核心具体步骤如下:
moved重定向:
槽命中,直接返回结果:
槽不命中,moved重定向:
redis-cli moved 异常:
只有集群模式才能重定向到非本节点的槽,所以没有使用集群模式且命令所属槽不在本地则无法执行命令,就会抛出异常;
ask重定向:
moved和ask的区别?
@Getter
@Setter
@Component
public class JedisClusterFactory{
private JedisCluster jedisCluster;
private List<String> hostPortList;
/**
* 单位是毫秒
*/
private int timeout;
private Logger logger = LoggerFactory.getLogger(JedisClusterFactory.class);
public void init(){
// 这里可以设置相关参数
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
Set<HostAndPort> nodeSet = new HashSet<HostAndPort>();
for(String hostPort: hostPortList){
String[] arr = hostPort.split(":");
if(arr.length != 2){
continue;
}
nodeSet.add(new HostAndPort(arr[0],Integer.parseInt(arr[1])));
}
try{
jedisCluster = new JedisCluster(nodeSet,timeout,jedisPoolConfig);
}catch(Exception e){
logger.error(e.getMessage(),e);
}
}
public void destroy(){
if(jedisCluster != null){
try{
jedisCluster.close();
}catch(IOException e){
logger.error(e.getMessage(),e);
}
}
}
}
// 获取所有节点的JedisPool
Map<String,JedisPool> jedisPoolMap = jedisCluster.getClusterNodes();
for(Entry<String,JedisPool> entry: jedisPoolMap.entrySet()){
//获取每个节点的Jedis连接
Jedis jedis = entry.getValue().getResource();
// 只删除主节点数据
if(!isMaster(jedis)){
continue;
}
// finally close
}
批量操作如何实现呢?mget、mset必须在一个槽,四种优化方法:
四种方案优缺点分析:
谁先到达可选举的时间,则更有可能获得更多的投票;
通过kill某个节点的进程来模拟宕机。
kill -9 进程id
对故障模拟能够提前预知和防范风险;
集群完整性
带宽消耗
PubSub广播
集群倾斜-目录
读写分离
数据迁移
集群vs单机
集群限制:
分布式Redis不一定好,需要考虑实际
很多场景Redis Sentinel已经足够好。
缓存的收益成本:
使用场景:
更新场景:
缓存更新策略
|策略|一致性|维护成本|
|LRU/LIRS算法剔除|最差|低|
|超时剔除|较差|低|
|主动更新|较差|高|
两条建议:
缓存穿透是什么?大量请求不命中,直接访问数据库,导致缓存穿透。如图所示:
原因:
如何发现:
解决办法1- 缓存空对象:
解决办法2-布隆过滤器拦截
提前演练,防患于未然。
问题描述:
问题关键点:
优化IO的集中方法:
四种批量优化的方法
互斥锁(mutex key):
永远不过期:
两种方案对比:
|方案|优点|缺点|
|互斥锁|思路简单,保持一致性|代码复杂度增加、存在死锁风险|
|永远不过期|基本杜绝热点key重建问题|不保证一致性、逻辑过期时间增加维护成本和内存成本|
sudo tar -xvf cachecloud-bin-1.2.tar.gz
cd cachecloud-web
more cachecloud.sql
如图所示:
导入命令为:
source /opt/cachecloud-web/cachecloud.sql
use cache_cloud;
show tables
exit
账号密码根据实际来
sudo sh start.sh
cd logs
tail -f cachecloud-web.log
应用申请:
应用审批:
应用部署:
代码接入:
参考文档进行相关操作即可;
类似问题有很多,比如垃圾邮件过滤(几亿封垃圾邮件)、文字处理软件(例如word)错误单词检测、 网络爬虫重复url检测、Hbase行过滤
布隆过滤器:
布隆过滤器构建:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。