特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我
视频地址: https://space.bilibili.com/31137138/favlist?fid=326428938
课件说明: 本次提供的课件是 Spring Cloud Netflix 版微服务架构指南,如果有兴趣想要学习 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相关课程资源
案例代码: https://github.com/topsale/hello-spring-cloud-netflix
什么是 Redis
Redis 是用 C 语言开发的一个开源的高性能键值对(key-value)数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求
Redis 的特点
- Redis 支持数据持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
- Redis 不仅仅支持简单的 KV 类型的数据,同时还提供
list
,set
,zset
,hash
等数据结构的存储 - Redis 支持数据的备份,即 Master - Slave 模式的数据备份
Redis 的优势
- 性能极高: Redis 读的速度是 110000 次/s, 写的速度是 81000 次/s
-
丰富的数据类型: Redis 支持
Strings
,Lists
,Hashes
,Sets
及Ordered Sets
数据类型操作 -
原子性: Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过
MULTI
和EXEC
指令包起来 - 其它特性:Redis 还支持 发布/订阅,Key 过期设置等
Redis 的应用场景
- 缓存(数据查询、短连接、新闻内容、商品内容等等)
- 分布式集群架构中的 Session 分离
- 聊天室的在线好友列表
- 任务队列(秒杀、抢购、12306 等等)
- 应用排行榜
- 网站访问统计
- 数据过期处理(可以精确到毫秒)
- 分布式协调(分布式锁)
Redis 高可用集群
HA(High Available,高可用性群集)机集群系统简称,是保证业务连续性的有效解决方案,一般有两个或两个以上的节点,且分为活动节点及备用节点。通常把正在执 行业务的称为活动节点,而作为活动节点的一个备份的则称为备用节点。当活动节点出现问题,导致正在运行的业务(任务)不能正常运行时,备用节点此时就会侦测到,并立即接续活动节点来执行业务。从而实现业务的不中断或短暂中断。
Redis 一般以主/从方式部署(这里讨论的应用从实例主要用于备份,主实例提供读写)该方式要实现 HA 主要有如下几种方案:
- keepalived: 通过 keepalived 的虚拟 IP,提供主从的统一访问,在主出现问题时, 通过 keepalived 运行脚本将从提升为主,待主恢复后先同步后自动变为主,该方案的好处是主从切换后,应用程序不需要知道(因为访问的虚拟 IP 不变),坏处是引入 keepalived 增加部署复杂性,在有些情况下会导致数据丢失
- zookeeper: 通过 zookeeper 来监控主从实例, 维护最新有效的 IP, 应用通过 zookeeper 取得 IP,对 Redis 进行访问,该方案需要编写大量的监控代码
-
sentinel: 通过 Sentinel 监控主从实例,自动进行故障恢复,该方案有个缺陷:因为主从实例地址( IP & PORT )是不同的,当故障发生进行主从切换后,应用程序无法知道新地址,故在 Jedis2.2.2 中新增了对 Sentinel 的支持,应用通过
redis.clients.jedis.JedisSentinelPool.getResource()
取得的 Jedis 实例会及时更新到新的主实例地址
注意: sentinel 是解决 HA 问题的,cluster 是解决主从复制问题的,不重复,并且经常一起用
Redis Sentinel
Redis 集群可以在一组 redis 节点之间实现高可用性和 sharding。在集群中会有 1 个 master 和多个 slave 节点。当 master 节点失效时,应选举出一个 slave 节点作为新的 master。然而 Redis 本身 (包括它的很多客户端) 没有实现自动故障发现并进行主备切换的能力,需要外部的监控方案来实现自动故障恢复。
Redis Sentinel 是官方推荐的高可用性解决方案。它是 Redis 集群的监控管理工具,可以提供节点监控、通知、自动故障恢复和客户端配置发现服务。
基于 Docker 安装 Redis 集群
2018 年 10 月 Redis 发布了稳定版本的 5.0 版本,推出了各种新特性,其中一点是放弃 Ruby 的集群方式,改为 使用 C 语言编写的 redis-cli
的方式,使集群的构建方式复杂度大大降低
- 下载所需镜像
docker pull redis
docker pull zvelo/redis-trib
- 创建配置文件
官方配置文件地址:http://download.redis.io/redis-stable/redis.conf ,我们部署 3 个节点,需要分别创建 3 个配置文件,路径如下
renode1:`cluster/node1/redis.conf`
renode2:`cluster/node2/redis.conf`
renode3:`cluster/node3/redis.conf`
配置文件内容如下 (端口号不要重复)
bind 0.0.0.0
# 关闭保护模式
protected-mode no
# 绑定自定义端口
port 6379
# 禁止 Redis 后台运行
# daemonize yes
pidfile /var/run/redis_6379.pid
# 开启集群
cluster-enabled yes
# 集群的配置,配置文件首次启动自动生成
cluster-config-file nodes_6379.conf
# 开启 AOF
appendonly yes
# 集群的 IP
cluster-announce-ip 192.168.x.x
# 集群的端口
cluster-announce-port 6379
# 集群的总线端口
cluster-announce-bus-port 16379
- 创建资源配置
docker-compose.yml
配置文件如下
version: '3.1'
services:
renode1:
image: redis
container_name: renode1
restart: always
ports:
- 6379:6379
- 16379:16379
volumes:
- ./cluster/node1:/usr/local/etc/redis
command:
redis-server /usr/local/etc/redis/redis.conf
renode2:
image: redis
container_name: renode2
restart: always
ports:
- 6380:6380
- 16380:16380
volumes:
- ./cluster/node2:/usr/local/etc/redis
command:
redis-server /usr/local/etc/redis/redis.conf
renode3:
image: redis
container_name: renode3
restart: always
ports:
- 6381:6381
- 16381:16381
volumes:
- ./cluster/node3:/usr/local/etc/redis
command:
redis-server /usr/local/etc/redis/redis.conf
docker-compose up -d
- 创建集群
docker run --rm -it zvelo/redis-trib create 192.168.141.220:6379 192.168.141.220:6380 192.168.141.220:6381
# 输出如下
>>> Creating cluster
>>> Performing hash slots allocation on 3 nodes...
Using 3 masters:
192.168.141.220:6379
192.168.141.220:6380
192.168.141.220:6381
M: 9250c85592b7bb8a19636b90e4cf22590bd3334f 192.168.141.220:6379
slots:0-5460 (5461 slots) master
M: 7373de7b44ee50a1e4f653bfba1bb808138e7e3a 192.168.141.220:6380
slots:5461-10922 (5462 slots) master
M: ec7e6e4cdd142249e3aa83541044932655d75b66 192.168.141.220:6381
slots:10923-16383 (5461 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join..
>>> Performing Cluster Check (using node 192.168.141.220:6379)
M: 9250c85592b7bb8a19636b90e4cf22590bd3334f 192.168.141.220:6379
slots:0-5460 (5461 slots) master
M: 7373de7b44ee50a1e4f653bfba1bb808138e7e3a 192.168.141.220:6380
slots:5461-10922 (5462 slots) master
M: ec7e6e4cdd142249e3aa83541044932655d75b66 192.168.141.220:6381
slots:10923-16383 (5461 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
验证 Redis 集群是否成功
- 交互式进入容器
docker exec -it renode1 /bin/bash
- 登录 Redis
redis-cli -p 6379
- 查看集群信息
127.0.0.1:6379> cluster info
# 输出如下
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:3
cluster_current_epoch:3
cluster_my_epoch:1
cluster_stats_messages_ping_sent:860
cluster_stats_messages_pong_sent:870
cluster_stats_messages_sent:1730
cluster_stats_messages_ping_received:868
cluster_stats_messages_pong_received:860
cluster_stats_messages_meet_received:2
cluster_stats_messages_received:1730
- 查看集群节点
127.0.0.1:6379> cluster nodes
# 输出如下
ec7e6e4cdd142249e3aa83541044932655d75b66 192.168.141.220:6381@16381 master - 0 1569665070102 3 connected 10923-16383
7373de7b44ee50a1e4f653bfba1bb808138e7e3a 192.168.141.220:6380@16380 master - 0 1569665069094 2 connected 5461-10922
9250c85592b7bb8a19636b90e4cf22590bd3334f 192.168.141.220:6379@16379 myself,master - 0 1569665069000 1 connected 0-5460
- 查看集群插槽
127.0.0.1:6379> cluster slots
# 输出如下
1) 1) (integer) 10923
2) (integer) 16383
3) 1) "192.168.141.220"
2) (integer) 6381
3) "ec7e6e4cdd142249e3aa83541044932655d75b66"
2) 1) (integer) 5461
2) (integer) 10922
3) 1) "192.168.141.220"
2) (integer) 6380
3) "7373de7b44ee50a1e4f653bfba1bb808138e7e3a"
3) 1) (integer) 0
2) (integer) 5460
3) 1) "192.168.141.220"
2) (integer) 6379
3) "9250c85592b7bb8a19636b90e4cf22590bd3334f"
基于 Docker 安装 Redis Sentinel
- 创建配置文件
我们部署 3 个 Sentinel 节点,需要分别创建 3 个配置文件,路径如下
resentinel1:`cluster/node1/sentinel.conf`
resentinel2:`cluster/node2/sentinel.conf`
resentinel3:`cluster/node3/sentinel.conf`
配置文件内容如下 (端口号不要重复)
bind 0.0.0.0
port 26379
dir /tmp
# 自定义集群名,其中 192.168.141.220 为 Redis Master 的 IP,6379 为 Redis Master 的端口,2 为最小投票数(因为有 3 台 Sentinel 所以可以设置成 2)
sentinel monitor rmaster 192.168.141.220 6379 2
sentinel down-after-milliseconds rmaster 30000
sentinel parallel-syncs rmaster 1
sentinel failover-timeout rmaster 180000
sentinel deny-scripts-reconfig yes
- 创建资源配置
docker-compose.yml
配置文件如下
version: '3.1'
services:
resentinel1:
image: redis
container_name: resentinel1
ports:
- 26379:26379
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- ./cluster/node1/sentinel.conf:/usr/local/etc/redis/sentinel.conf
resentinel2:
image: redis
container_name: resentinel2
ports:
- 26380:26380
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- ./cluster/node2/sentinel.conf:/usr/local/etc/redis/sentinel.conf
resentinel3:
image: redis
container_name: resentinel3
ports:
- 26381:26381
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- ./cluster/node3/sentinel.conf:/usr/local/etc/redis/sentinel.conf
验证 Sentinel 集群是否成功
- 交互式进入容器
docker exec -it resentinel1 /bin/bash
- 登录 Redis Sentinel
redis-cli -p 26379
- 查看集群 Master 信息
127.0.0.1:26379> sentinel master rmaster
# 输出如下
1) "name"
2) "rmaster"
3) "ip"
4) "192.168.141.220"
5) "port"
6) "6379"
7) "runid"
8) "30055483aeb9d75f35c0046aaec03440731e3e88"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "413"
19) "last-ping-reply"
20) "413"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "2878"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "143537"
29) "config-epoch"
30) "0"
31) "num-slaves"
32) "0"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"
Redis 数据类型
Redis 支持 5 种数据类型
- string(字符串)
- hash(哈希)
- list(列表)
- set(集合)
- zset(sorted set:有序集合)
字符串
-
string
是 Redis 最基本的数据类型。一个key
对应一个value
-
string
是二进制安全的。也就是说 Redis 的string
可以包含任何数据。比如图片或者序列化的对象 -
string
类型的值最大能存储 512MB -
string
类似于 Java 中的Map
,一个key
对应一个value
哈希
- Redis hash 是一个键值对(key - value)集合
- Redis hash 是一个
string
类型的key
和value
的映射表,hash
特别适合用于存储对象 - 可以将 Redis hash 看成一个 KV 的集合。也可以将其想成一个
hash
对应着多个string
列表
Redis 列表是简单的字符串列表,按照插入顺序排序。我们可以往列表的左边或者右边添加元素, list
就是一个简单的字符串集合,和 Java 中的 list 相差不大,区别就是这里的 list 存放的是字符串 (list 内的元素是可重复的)
集合
Redis 的 set
是字符串类型的无序集合。集合是通过哈希表实现的,因此添加、删除、查找的复杂度都是 o(1)
,Redis 的 set 是一个 key 对应着多个字符串类型的 value,也是一个字符串类型的集合,但是和 Redis 的 list 不同的是 set 中的字符串集合元素不能重复,但是 list 可以
有序集合
Redis zset 和 set 一样都是字符串类型元素的集合,并且集合内的元素不能重复;不同的是,zset 每个元素都会关联一个 double 类型的分数。redis 通过分数来为集合中的成员进行从小到大的排序
zset 的元素是唯一的,但是分数(score)却可以重复
Spring Boot 整合 Redis
添加依赖
- 主要依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- 因为要与数据库结合使用,以
provider-admin-service
项目为例,一并提供完整依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.funtl</groupId>
<artifactId>provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>provider-admin-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Spring Boot Data -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<!-- Commons -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.funtl.hello.spring.cloud.provider.ProviderAdminApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
添加配置
- 主要配置
spring:
redis:
cluster:
nodes: 192.168.141.206:6379,192.168.141.206:6380,192.168.141.206:6381
lettuce:
# 连接池配置
pool:
# 连接池中的最小空闲连接,默认 0
min-idle: 0
# 连接池中的最大空闲连接,默认 8
max-idle: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制),默认 -1ms
max-wait: -1ms
# 连接池最大连接数(使用负值表示没有限制),默认 8
max-active: 8
- 完整配置
spring:
main:
allow-bean-definition-overriding: true
application:
name: provider-admin
zipkin:
base-url: http://192.168.141.204:9411
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.141.206:8066/myshop?useUnicode=true&characterEncoding=utf-8&serverTimezone=Hongkong&useSSL=false
username: root
password: 123456
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
redis:
cluster:
nodes: 192.168.141.206:6379,192.168.141.206:6380,192.168.141.206:6381
lettuce:
pool:
min-idle: 0
max-idle: 8
max-wait: -1ms
max-active: 8
server:
port: 11000
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8761/eureka/
mybatis:
type-aliases-package: com.funtl.hello.spring.cloud.provider.domain
mapper-locations: classpath:mapper/*.xml
客户端工具类
注意: 我们使用 Spring 官方推荐的 Lettuce 客户端工具不再采用 Jedis,原因是
- 效率优于 Jedis
- 目前 Jedis 只支持单机
- Jedis setNx 和设置过期时间是不同步的,在某些极端的情况下会发生死锁,导致程序崩溃(如果没有设置 value, 线程 1 可能会释放线程 2 的锁)
注意: 由于代码量较大会导致《简书》页面无法正常显示,请移步 LettuceUtils.java
- 通过 Spring 容器注入即可使用
package com.funtl.hello.spring.cloud.provider.cache;
import com.funtl.hello.spring.cloud.commons.cache.LettuceUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class LettuceUtilsTests {
@Resource
private LettuceUtils lettuceUtils;
@Test
public void testSet() {
lettuceUtils.set("name", "李小红");
}
@Test
public void testGet() {
System.out.println(lettuceUtils.get("name"));
}
@Test
public void testDelete() {
lettuceUtils.delete("name");
}
}
特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我
视频地址: https://space.bilibili.com/31137138/favlist?fid=326428938
课件说明: 本次提供的课件是 Spring Cloud Netflix 版微服务架构指南,如果有兴趣想要学习 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相关课程资源
案例代码: https://github.com/topsale/hello-spring-cloud-netflix