<span>Redis应用</span>
1.1 特点
- 内存数据库,速度快,也支持数据持久化
- Redis不仅仅支持简单的key-value类型的数据,同时还提供List、Hash、Set、Sorted Set等多种数据类型
- Redis支持数据的备份(master-slave)与集群(分片存储),以及拥有哨兵模式
- 支持事务
1.2 优势
- 性能极高- Redis能读的速度是110000次/s,写的速度81000次/s
- 丰富的数据类型-Redis支持String、List、Hash、Set、Sorted Set等数据结构
- 原子操作,Redis所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行(事务)
- 丰富的特性,Redis还支持push/subscribe、通知key过期等特性
3. Redis的高并发
3.1 原理
- Redis是纯内存数据库,所以读取速度快
- Redis使用的是非阻塞IO、IO多路复用,减少了线程切换时上下文的切换和竞争
- Redis采用单线程模式,保证了每个操作的原子性,也减少了线程的上线文切换和竞争
- Redis存储结构多样化,不同的数据结构对数据存储进行了优化,加快读取速度
- Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
3.2 Redis单线程
3.2.1 原因
- 不需要各种锁的性能消耗
- 但鲜橙多进程集群方案
- CPU消耗
3.2.2 优劣
单进程单线程优势
- 代码更清晰,处理逻辑更简单。
- 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能死锁而导致的性能消耗
- 不存在多进程或者多线程导致的切换而消耗CPU
单进程单线程弊端
- 无法发挥多核CPU性能,不过可以通过单机开多个实例进行完善。
3.2.2 IO 多路复用
4 Redis安装
4.1 单机版安装
安装wget工具
yun -y install wget
下载安装包(本次安装6.0.9) 安装包地址(https://download.redis.io/releases/)
wget https://download.redis.io/releases/redis-6.0.9.tar.gz
安装gcc+c环境
yum install -y gcc-c++ autoconf automake
4.3.1 升级gcc
在编译Redis 6之前需要升级gcc的版本,默认情况下yum安装的gcc版本是4.8.5,由于版本过低,在编译期间会报错。因此在编译前需要先升级gcc
# 安装scl源
yum install -y centos-release-scl scl-utils-build
# 安装9版本的gcc、gcc-c++、gbd工具链(toolchain)
yum install -y devtoolset-9-toolchain
# 临时覆盖系统原有的gcc 引用
scl enable devtoolset-9 bash
# 检查gcc当前版本
gcc -v
成功升级到gcc 9版本
[root@localhost redis-6.0.9]# gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/lto-wrapper Target: x86_64-redhat-linux Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,lto --prefix=/opt/rh/devtoolset-9/root/usr --mandir=/opt/rh/devtoolset-9/root/usr/share/man --infodir=/opt/rh/devtoolset-9/root/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --with-linker-hash-style=gnu --with-default-libstdcxx-abi=gcc4-compatible --enable-plugin --enable-initfini-array --with-isl=/builddir/build/BUILD/gcc-9.3.1-20200408/obj-x86_64-redhat-linux/isl-install --disable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux Thread model: posix gcc version 9.3.1 20200408 (Red Hat 9.3.1-2) (GCC)
4.3.2 预编译
tar -zxvf redis-6.0.9.tar.gz -C /usr/local/ cd /usr/local/redis-6.0.9/ make
4.3.3 创建redis安装目录
mkdir -p /usr/local/redis make PREFIX=/usr/local/redis/ install
4.3.4 启动redis
cd /usr/local/redis/bin
./redis-server
启动后可以看到redis显示为单节点、Port
4.3.5 配置以守护进程的方式启动(后台启动)
- 拷贝 6.0.9下的redis.conf
bash cd redis-6.0.9/ cp redis.conf /usr/local/redis/bin/
- 修改redis.conf
# 修改第224行
daemonize yes
再次启动
./redis-server ./redis.conf
4.3.6 配置开机启动(CentOS 7以上)
- 在系统服务目录中创建redis.service文件
bash vi /etc/systemd/system/redis.service
写入以下内容 ``` bash [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
*重载系统服务*
``` bash
systemctl daemon-reload
- 测试并加入开机自启
- 关闭redis-server
systemctl stop redis.service
- 开启redis-server
systemctl start redis.service
- 重启redis-server
systemctl restart redis.service
- 查看redis-server状态
systemctl status redis.service
- 将服务加入开机自启
bash systemctl enable redis.service
- daemonize默认情况下,redis不是在后台运行的,如果需要在后台运行,把该项的值更改为yes
- bind 指定redis只接受来自于该ip的请求
- port 监听端口,默认为6379
- databases 设置数据库的个数,默认使用的数据库是0
- save 设置Redis进行数据库镜像的频率
- dbfilename 镜像备份文件的文件名
- dir 数据库镜像备份的文件放置的路径
- requirepass 设置客户端连接后进行其他指令前需要使用密码
- maxclients 限制同时连接的客户数量
- maxmemory 设置redis能够使用的最大内存
5. 缓存异常及解决方案
5.1 缓存雪崩
雪崩:就是指缓存中大批量热点数据过期后系统涌入大量查询的请求,引发数据库压力。
解决方案:
- 将缓存失效时间分散开,比如每个key的过期时间都是随机的
- Redis数据永不失效(如果业务准许,比如不用更新的名单类)
5.2 缓存击穿
击穿:指一个热点key被穿透,同一个key会有成千上百次请求,比如微博热点排行榜。如果这个
解决方案
- 业界比较常用的做法,是使用mutex.具体来说就是在缓存失效的时候(判断拿出来的值为空),不是直接去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如redis的setnx或者memcache的Add)去set一个mutex key,当操作返回成功时,在进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
5.3 缓存穿透
穿透是指绕过redis,调用者发起的请求参数(key)在缓存和数据库中都不出在,通过不存在的key,成功穿透到系统底层,大规模不断发起不存在的key检索请求导致系统压力过大最后故障。
解决方法
- 分布式布隆过滤器
- 返回空值,遇到数据库和Redis都查询不到的值,在Redis中set一个null value,过期时间很短,目的在于同一个key再次请求时直接返回null,避免穿透。
5.4 缓存预热
缓存预热就是在系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存。
5.5 缓存淘汰
6. Jedis客户端
6.1 Jedis连接
-
添加POM依赖
<dependencies> <!-- jedis 客户端--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.3.0</version> </dependency> <!--junit test--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
public class jedisPoolConnectRedis { private static JedisPool jedisPool; static { // 创建连接池配置对象 JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); //设置最大连接数,默认是8 jedisPoolConfig.setMaxTotal(5); //设置最大空闲数, 默认是8 jedisPoolConfig.setMaxIdle(5); //设置最小空闲数量,默认是0 jedisPoolConfig.setMinIdle(0); //设置等待时间 ms jedisPoolConfig.setMaxWaitMillis(100); //初始化jedisPool对象 GenericObjectPoolConfig poolConfig; jedisPool = new JedisPool(jedisPoolConfig, "192.168.21.105", 6379, 100, "123456"); } /** * 获取Jedis对象 * @return */ public static Jedis getJedis(){ return jedisPool.getResource(); }
-
测试类
public class JedisPoolTest { Jedis jedis = null; //建立连接 @Before public void init(){ //初始化Jedis客户端 jedis = jedisPoolConnectRedis.getJedis(); //身份认证,设置连接密码 //ping pang心跳机制,监测是否连接成功 String pong = jedis.ping(); System.out.println("pong = " + pong); } @Test public void testJedisPool(){ Set<String> keys = jedis.keys("*"); Assert.assertNotNull(keys); } @After public void close(){ if(null!=jedis){ jedis.close(); } } }
2. application.yml配置
3. 测试类
7.Redis主从环境
7.1节点资源规划
ip | 角色 |
---|---|
192.168.147.102 | Master |
192.168.147.103 | slave1 |
192.168.147.104 | slave2 |
7.2创建文件目录
mkdir -p /usr/local/redis/conf mkdir -p /usr/local/redis/data mkdir -p /usr/local/redis/log
7.3 创建配置文件
- 主节点(master)
# 放行访问ip限制 bind 0.0.0.0 # 后台启动 daemonize yes # 日志存储目录及日志文件名 logfile "/usr/local/redis/log/redis.log" # rdb数据文件名 dbfilename dump.rdb # aof模式开启和aof数据文件名 appendonly yes appendfilename "appendonly.aof" # rdb数据文件和aof数据文件的存储目录 dir /usr/local/redis/data # 设置密码 requirepass 123456 # 从节点访问主节点密码(必须与requirepass一致) masterauth 123456 # 从节点只读模式 replica-read-only yes
- 从节点(salve)
在maste配置文件的基础上,最后一行增加slaveof
# 从节点属于哪个主节点 slaveof 192.168.147.102 6379
7.4 启动并验证
分别启动主节点和从节点
# 启动redis 服务
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
查看主从副本信息
# redis-cli连接 /usr/local/redis/bin/redis-cli -a 123456 # 进入redis-cli后,输入 info replication 查看slave状态 info replication
主节点redis-cli连接后,查看主节点信息
127.0.0.1:6379> info replication # Replication role:master connected_slaves:2 slave0:ip=192.168.147.104,port=6379,state=online,offset=784,lag=1 slave1:ip=192.168.147.103,port=6379,state=online,offset=784,lag=0 master_replid:e8101cf8b0d87dfd39a9ac78f9c4b4530fa051fd master_replid2:0000000000000000000000000000000000000000 master_repl_offset:784 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:784 127.0.0.1:6379>
从节点redis-cli连接后,查看从节点信息
127.0.0.1:6379> info replication # Replication role:slave master_host:192.168.147.102 master_port:6379 master_link_status:up master_last_io_seconds_ago:4 master_sync_in_progress:0 slave_repl_offset:140 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:e8101cf8b0d87dfd39a9ac78f9c4b4530fa051fd master_replid2:0000000000000000000000000000000000000000 master_repl_offset:140 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:15 repl_backlog_histlen:126 127.0.0.1:6379>
- 从节点反馈的主节点信息, 表明目前主从节点通讯正常。
8. Redis 哨兵模式
8.1 节点资源规划
IP | 角色 |
---|---|
192.168.147.102 | master |
192.168.147.103 | slave1 |
192.168.147.104 | slave2 |
8.2 编写配置文件
三个节点分别创建sentinel.conf
配置文件
# 放行所有IP限制 bind 0.0.0.0 # 进程端口号 port 26379 # 后台启动 daemonize yes # 日志记录文件 logfile "/usr/local/redis/log/sentinel.log" # 进程编号记录文件 pidfile /var/run/sentinel.pid # 指示Sentinel 去监视一个名为mymaster的主服务器 sentinel monitor mymaster 192.168.147.102 6379 2 # 访问主节点的密码 sentinel auth-pass mymaster 123456 # Sentinel 认为服务器已经断线所需的毫秒数 sentinel down-after-millseconds mymaster 10000 # 若Sentinel 在该配置值内未完成failover操作,则认为本次failover 失败 sentinel failover-timeout master 180000
8.3 启动
先启动3个redis服务
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
再启动3个Sentinel服务
/usr/local/redis/bin/redis-server /usr/local/redis/conf/sentinel.conf --sentinel
或使用
/usr/local/redis/bin/redis-sentinel /usr/local/redis/conf/sentinel.conf
8.4 查看启动日志
tail -f /usr/local/redis/log/sentinel.log
12951:X 18 May 2021 04:08:07.841 # Configuration loaded
12951:X 18 May 2021 04:08:07.843 * Increased maximum number of open files to 10032 (it was originally set to 1024).
12951:X 18 May 2021 04:08:07.845 * Running mode=sentinel, port=26379.
12951:X 18 May 2021 04:08:07.846 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
12951:X 18 May 2021 04:08:07.860 # Sentinel ID is 5ceca968240397ce918bd16d1a44dbd4d8350e59
12951:X 18 May 2021 04:08:07.861 # +monitor master mymaster 192.168.147.102 6379 quorum 2
12951:X 18 May 2021 04:08:07.865 * +slave slave 192.168.147.103:6379 192.168.147.103 6379 @ mymaster 192.168.147.102 6379
12951:X 18 May 2021 04:08:07.867 * +slave slave 192.168.147.104:6379 192.168.147.104 6379 @ mymaster 192.168.147.102 6379
12951:X 18 May 2021 04:08:44.093 * +sentinel sentinel 8115d2d6bb8a7d9d360bbc569a9ca20be36a29ac 192.168.147.103 26379 @ mymaster 192.168.147.102 6379
12951:X 18 May 2021 04:08:46.702 * +sentinel sentinel d77d049e307f7704bb3e98b5068985eafaa95a04 192.168.147.104 26379 @ mymaster 192.168.147.102 6379
9. 哨兵工作原理
9.1 定时任务
Sentinel内部有3个定时任务,分别是:
- 每1秒每个Sentinel对其他Sentinel和Redis节点执行
ping
操作(监控) - 每2秒每个Sentinel通过Master节点的channel交换信息(publish/Subscribe)
- 每10秒每个Sentinel会对Master和Slave执行
info
命令
9.2 主观下线
所谓主管下线(Subjectively down,简称SDOWN)指的是单个Sentinel实例对服务器做出的下线判断,即单位Sentinel任务某个服务下线(有可能是接收不到订阅,之间的网络不通等原因)。
9.3 客观下线
客观下线(Objectively Down,简称ODOWN)指的是多个Sentinel实例在对同一个服务器做出SDOWN判断,并且通过命令相互交流之后,得出的服务器下线判断,然后开启failover。
9.4 仲裁
仲裁指的是配置文件中的quorum
选项
quorum
的值一般设置为Sentinel个数的二分之一加1,例如3个Sentinel就设置为2。
9.5哨兵的工作原理
- 每秒
ping
- 有效回复PING命令的时间超时配置文件
down-after-milliseconds
选项所指定的值,被认定为主观下线 - 确认主观下线状态
- 满足条件,客观下线
- 投票选举主节点,从节点复制数据
- 当主节点标记为客观下线时,INFO命令触发由10s一次改为1s一次
- 若没有足够数量的Sentinel同意Master已经下线,Master的客观下线状态就会被移除。若Master重新向Sentinel的Ping命令返回有效回复,Master的主观下线状态就会被移除。
9.7 SpringBoot中配置Redis哨兵模式
- 配置文件接入redis哨兵(推荐使用)
timeout: 3000
database: 1
password: 123456
# 改为sentinel模式
sentinel:
master: mymaster
nodes: 192.168.21.107:26379,192.168.21.108:26379,192.168.21.109:26379
- @Bean方式(使用RedisConnectionFactory方式)
@Bean public RedisConnectionFactory lettuceConnectionFactory(){ RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration() .master("mymaster") .sentinel("192.168.21.107",26379) .sentinel("192.168.21.108",26379) .sentinel("192.168.21.109",26379); sentinelConfiguration.setDatabase(1); sentinelConfiguration.setPassword("123456"); return new LettuceConnectionFactory(sentinelConfiguration); }
10 Redis Cluster模式
10.1 优势
- 去中心化
- 可扩展性
- 高可用性
- 自动故障转移
10.2 缺点
- 数据通过异步复制,无法保证数据强一致性
- 集群环境搭建略微复杂
10.3 常见分区算法
- 范围分区
- 节点分区
hash(object) % N 优点:实现简单 缺点:当扩容或收缩节点时,需要迁移的数据量大(翻倍扩容可以相对减少迁移量)
- 一致性hash分区
优点:相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。 缺点:当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此这种方式不适用少量数据节点的分布式方案 应用场景:Memecached
- 虚拟槽分区
优点:每个node均匀的分配了slot,缩小增减节点影响额范围 缺点:需要存储node和slot的对应信息 应用场景:Redus Cluster
10.4 Redis Cluster集群搭建
10.5 Redis集群原理
- 哈希槽 Redis集群(Cluster)并没有选用一致性hash,而是采用了哈希槽(slot)的这种概念,主要的原因就是一致性hash算法对于数据分部、节点位置的控制并不是很友好。 首先哈希槽其实是两个概念
- hash算法。Redis Cluster的hash算法不是简单地hash(),而是crc16算法,一种校验算法。
- 槽位的概念,空间分配的规则。
一定要注意,对于槽位的转移和分配,Redis集群是不会自动进行的,而是需要人工配置的。所以Redis集群的高可用是依赖于节点的主从复制和主从间的自动故障转移
10.6 16384个slot(槽位)
Redis Cluster没有单机的那种16个数据库(0-15)的概念,而是分成了16384个Slot(槽位),每个节点负责其中一部分槽位,槽位的信息存储在每个节点中。
10.7 槽位定位算法
Redis Cluster 默认会对key值使用CRC16算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体的槽位。
槽位计算公式:HASH_SLOT = CRC16(Key) mod 16384
为什么是16384个槽?
- 如果槽位65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
- Redis的集群主节点数量基本不可能超过1000个
- 槽位越小,节点越少的情况下,压缩率高。
10.8 SpringBoot整合Redis集群
如果项目中没有引用连接池,可以不用添加commmons-pool2
的依赖
<dependency> <groupId>org.apache.commons</groupId> <artifacted>commons-pool2</artifactId> </dependency>
-
appllication.yml
redis: # port: 6379 # host: 192.168.21.105 timeout: 3000 password: 123456 # database: 5 # lettuce 连接池 lettuce: pool: max-active: 8 # 最大连接数,默认8 max-idle: 8 # 最大空闲连接,默认8 min-idle: 0 #最小空闲连接,默认0 max-wait: 1000 #最大连接阻塞等待时间,单位毫秒,默认-1 cluster: max-redirects: 5 # Redis命令执行时最多转发次数 nodes: 192.168.21.107:6372, ....
-
@Bean方式
1 @Bean 2 public RedisConnectionFactory lettuceConnectionFactory(){ 3 RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(); 4 //节点 5 List<RedisNode> redisNodeList = new ArrayList<>(); 6 redisNodeList.add(new RedisNode("192.168.10.101",6371)); 7 8 clusterConfiguration.setClusterNodes(redisNodeList); 9 //Redis 命令执行时最多转发次数 10 clusterConfiguration.setMaxRedirects(5); 11 // 密码 12 clusterConfiguration.setPassword("123456"); 13 return new LettuceConnectionFactory(clusterConfiguration); 14 }