前言
在当今互联网高速发展的时代,微服务架构已经成为企业开发的主流选择,如何高效地进行服务的注册与发现、配置管理、消息队列、监控和链路追踪等方面的操作,成为了一个亟待解决的问题。
nacos、redis、elasticsearch、sentinel、skywalking、rabbitmq和springcloud等工具和框架作为微服务架构中不可或缺的一部分,其安装、配置和使用的正确与否,直接关系到整个系统的稳定性和性能。本文将为读者提供详细的指导,帮助他们快速掌握这些工具和框架,并正确应用于自己的项目中。
Spring Cloud Alibaba-Version、Spring Cloud Version、Spring Boot Version的版本关系
环境准备:
- 64 bit JDK 1.8+;
- Maven 3.2.x+;
一、Nacos
Nacos 在阿里巴巴起源于 2008 年五彩石项目(完成微服务拆分和业务中台建设),成长于十年双 十一的洪峰考验,沉淀了简单易用、稳定可靠、性能卓越的核心竞争力。受之于开源,反哺于社区,阿里在2018将Nacos开源。
功能:发现、配置和管理微服务(支持微服务路由权重、多种负载均衡策略、心跳检测,服务路由DNS解析等)
当前官网推荐的稳定版本为2.1.1,最新版本为2.2.3
下载zip解压后双击bin下的startup.cmd
启动
默认启动端口:8848,端口等配置可修改于/conf/application.properties
默认是以集群的方式启动,直接启动如果报错。可以在启动时指定模式:
startup.cmd -m standalone
访问可视化web站点:http://localhost:8848/nacos/index.html
1. 服务注册中心
pom
引入服务发现依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${latest.version}</version>
</dependency>
启动类
配置注解
@EnableDiscoveryClient // 开启服务注册发现
@SpringBootApplication
application.yaml
配置
spring:
cloud:
nacos:
# 服务注册名默认为spring.application.name
discovery:
# 命名空间(分割不同开发环境或者不同项目都行),用户名密码和注册中心地址
namespace: 1e8be043-f7fc-45fe-83b9-69dd32641df7
password: nacos
username: nacos
server-addr: localhost:8848
# Nacos的服务组,用于区分不同的服务组
group: DEFAULT_GROUP
# 权重,默认为1
weight: 1
# Nacos的集群名称,用于区分不同的集群
# cluster-name: DEFAULT
# 服务实例的元数据,可用于自定义标签和属性
# metadata: xxx
# 服务实例的状态,默认为UP
# instance-state: xxx
# 设置为永久实例,默认为临时实例
# ephemeral: false
SpringBoot中,如果不配置@EnableDiscovery如何动态将一个内部服务注入到服务中心中呢,很简单
try {
// 创建Nacos服务注册的配置项
Properties properties = new Properties();
properties.setProperty("serverAddr", "127.0.0.1:8848");
properties.setProperty("namespace", "NAMESPACE");
// 创建Nacos服务注册的实例
NamingService namingService = NamingFactory.createNamingService(properties);
// 将服务注册到注册中心
namingService.registerInstance("service-name", "127.0.0.1", 7003);
} catch (NacosException e) {
LOGGER.error("服务注册到失败", e);
}
这种方式会增加一定的手动配置和管理的工作量,但能很灵活控制服务的注册。
还有很多java、open-api的方式代码管理Nacos的方法,如需用到翻阅官网
2. 服务配置中心
pom.xml
引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${latest.version}</version>
</dependency>
启动类
加上注解
@RefreshScope // 开启配置自动更新,实时从配置中心同步配置
微服务与配置中心的配置需要编写在较高优先级的bootstrap.properties
或bootstrap.yaml
中
# 端口号可以写这里也可以写在application.yaml或application.properties中
server.port: 7070
# 配置 Nacos server 的地址和应用名
spring:
cloud:
nacos:
config:
# 上下文路径,默认为'',选填,就url上会多出的前缀资源路径
contextPath: /nacos
server-addr: {Nacos服务器地址} # Nacos服务器的地址,通常为IP地址和端口号,例如:localhost:8848
# Nacos的命名空间,用于隔离不同的环境或项目
namespace:
# 配置组,默认DEFAULT_GROUP,即命名空间下再分割为组(配置文件不同组的可同DATA-ID)
group: DEFAULT
# 允许动态刷新的配置ID列表,多个ID用逗号分隔
refreshable-dataids: xxx.yaml, xx.yaml
# 这个服务会取配置中心找的配置名字满足下面命名规则都会被加载
# ${prefix || ${spring.application.name}}-${spring.profiles.active || ''}.${file-extension || 'properties'}
# 会匹配的配置文件的前缀,默认为spring.application.name
# prefix:
# 会匹配的配置文件的扩展名,默认为properties
# file-extension: yaml
# 共享配置,具名查找
shared-configs:
- data-id: global-common.yaml
group: DEFAULT_GROUP
refresh: true
- data-id: global-redis.yaml
group: DEFAULT_GROUP
refresh: true
# 扩展配置,具名查找
extend-configs:
- data-id: global-common.yaml
group: DEFAULT_GROUP
refresh: true
profiles:
active: dev
application:
# 必填
name: product-service
3. 思考
Nacos中的保护阈值的作用是什么?
Nacos中的负载均衡是怎么样的?
Nacos的就近访问是什么意思?
你是怎么理解CAP理论的?
Nacos中保证的是CP还是AP?
如何理解Nacos中的命名空间?
你觉得注册中心应该是CP还是AP?
二、Redis
介绍
Redis 是一种开源内存中数据结构存储,用作数据库、缓存、消息代理和流引擎。 Redis 提供数据结构,例如 字符串、散列、列表、集合、带范围查询的排序集、位图、超级日志、地理空间索引和流
使用场景:
- 缓存,加快数据访问速率
- 共享Session
- 分布式锁:在分布式服务中。尽管它可能并不常用,但利用Redis的setnx功能可以编写分布式锁
- 排行榜、点赞功能
1. 安装与配置
官网下载地址(最新7.2):https://github.com/MicrosoftArchive/redis/releases
Window下载地址(最新支持到5.0.14):https://github.com/tporadowski/redis/releases
安装后:计算机的服务管理多了个redis的服务,可通过计算机服务管理。或者通过sc命令管理redis服务,sc命令在redis安装后便自动配置到全局变量中了,它是用来与服务控制管理器和服务进行通信的命令行程序
# 查询redis状态、启动停止
sc query Redis
sc start Redis
sc stop Redis
# 创建服务(并将其添加到注册表中)
sc create Redis binPath= "redis-server.exe所在详细路径 --service-run redis-server.exe所在详细路径" start = auto
# 可通过键入以下命令获取有关命令的更多帮助: "sc [command]"
配置文件为redis.windows.conf ,配置可以自行修改
redis常见配置
port
: Redis服务器监听的端口号,默认为6379。bind
: 绑定的IP地址,默认为127.0.0.1,表示只允许本地连接。timeout
: 客户端连接的超时时间,单位为秒,默认为0,表示无限制。loglevel
: 日志记录的级别,默认为notice
,可选的值有debug
、verbose
、notice
、warning
。logfile
: 日志文件的路径,默认为空,表示不记录日志。databases
: Redis支持的数据库数量,默认为16。save
: 数据持久化的策略,默认为禁用持久化。rdbcompression
: 是否使用RDB压缩,默认为yes
。dbfilename
: RDB持久化文件的名称,默认为dump.rdb
。requirepass
: Redis连接密码,默认为空,表示无密码。maxclients
: 最大客户端连接数,默认为10000。maxmemory
: 最大可用内存量,用于设置内存淘汰策略。appendonly
: 是否开启AOF持久化,默认为no
。appendfilename
: AOF持久化文件的名称,默认为appendonly.aof
。appendfsync
: AOF持久化的fsync策略,默认为everysec
。daemonize
: 是否以守护进程方式运行Redis,默认为no
。
redis手动启动需要指定配置文件
redis-server.exe redis.windows.conf
2. Redis命令
redis-cli.exe
密码认证:如果你在配置文件中为redis设置了密码(默认没有密码),我们需要使用密码来进行验证之后再来对redis客户端进行操作,否则我们没有操作redis缓存数据库的权限。
auth password
简单语法
SET key value # 设置键值对
GET key # 获取键对应的值
DEL key # 删除键值对
EXISTS key # 检查键是否存在
EXPIRE key seconds # 设置键的过期时间
TTL key # 获取键的剩余过期时间
MOVE key db # 将键移动到另一个数据库
KEYS pattern # 获取所有键
TYPE key # 获取键的类
LPUSH key value1 [value2 …] # 在列表头部插入元素
RPUSH key value1 [value2 …] # 在列表尾部插入元素
LRANGE key start stop # 获取列表指定范围的元素
LLEN key # 获取列表长度
LPOP key # 移除并返回列表的第一个元素
RPOP key # 移除并返回列表的最后一个元素
…………更多查看官网命令大全
3. 整合SpringBoot
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.2</version>
</dependency>
- spring-data-redis 是Spring大家族中的一个成员,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现
- lettuce-core 一个高效的java redis客户端,用于与redis通信和交互,提供了异步、非阻塞、连接池、redis哨兵和redis集群的功能。底层使用netty进行网络通信并丰富了redis的相关命令、编码解码器等高级特性。
application.yaml
spring:
redis:
database: 0
# Redis服务器地址
host: localhost
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password: 123456
# 为了提高Redis的性能,可以配置连接池来管理Redis连接
lettuce:
pool:
#连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
# 连接超时时间(毫秒)
time-between-eviction-runs: 300
config
@Configuration
public class RedisConfig {
/**
* 默认是jdk序列化器,改为jackson的序列化器。解决redis存入时乱码的问题
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
//首先解决key的序列化问题
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
//解决value的序列化问题
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
}
}
在需要的地方注入RedisTemplate即可使用
原生注解:
共同参数(value/cacheNames: string命名空间 , key: string可以适应spel表达式的缓存键)
-
@Cacheable 有缓存直接返回,没有执行完方法后将结果缓存并返回,一般用在查询方法上
-
@CachePut 每次都会执行,并将结果存入指定的缓存中,其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库,一般用在新增的方法上
-
@CacheEvict 会清空指定的缓存,一般用在更新或者删除方法上。
有两个额外参数
allEntries:boolean方法调用后立即清空所有缓存;默认false
beforeInvocation: boolean是否在方法执行前就清空;默认false
4. Redis锁
Redis分布式锁的实现是利用 Redis 的原子性操作实现锁的获取和释放,从而达到共享资源的独占性。(键锁)
4.1 setIfAbsent
private boolean locked = false;
public boolean lock(String lockKey,String lockValue) {
if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue)) {
// 加锁成功,锁延期
redisTemplate.expire(lockKey, LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS);
locked = true;
return true;
} else {
return false;
}
}
public void unlock(String lockKey) {
if (locked) {
redisTemplate.delete(lockKey);
}
}
4.2 使用 Lua 脚本
lua脚本是在Redis服务器上运行的脚本,脚本包含了多个命令的组合操作,一般用于执行复杂的操作,从而达到减少通信的次数
简单语法
-- 定义变量关键字(弱类型,类型会根据值自动确定) local
-- 条件语句:if-then、if-then-else、if-elseif-else。
-- 循环语句:while循环、repeat-until循环、for循环。
-- 跳出循环:break语句。
-- 定义函数关键字: function,参数列表用括号括起来。函数可以返回一个或多个值
function add(a, b)
return a + b
end
local result = add(2, 3) -- 调用函数
-- 定义数组:{} 索引默认从1开始
-- 定义字典: {}
-- 调用Redis的命令: redis.call()或者redis.pcall(),call或抛出异常,pcall会捕获异常
redis.call("SET", "key", "value")
常见lua脚本
-- 设置键的过期时间
if redis.call("EXISTS", KEYS[1]) == 1 then
return redis.call("EXPIRE", KEYS[1], ARGV[1])
else
return 0
end
-- 原子增加计数器
return redis.call("INCRBY", KEYS[1], ARGV[1])
-- 获取集合两个成员的交集
return redis.call("SINTER", KEYS[1], KEYS[2])
-- 执行事务
redis.call("MULTI")
redis.call("SET", "key1", "value1")
redis.call("SET", "key2", "value2")
local result = redis.call("EXEC")
return result
-- 游戏的排行榜
redis.call("ZADD", "leaderboard", ARGV[1], ARGV[2])
local rank = redis.call("ZREVRANK", "leaderboard", ARGV[2])
return redis.call("ZCOUNT", "leaderboard", "-inf", "+inf"), rank + 1
-- 解析 :ZADD命令,将指定成员及其分值添加到有序集合(sorted set)"leaderboard"中。ARGV[1]表示脚本的第一个参数,用于指定成员的分值,ARGV[2]表示脚本的第二个参数,用于指定成员的名称。
-- local rank = redis.call("ZREVRANK", "leaderboard", ARGV[2]) ZREVRANK命令,返回有序集合"leaderboard"中指定成员的排名(从高到低排序)。ARGV[2]表示脚本的第二个参数,即成员的名称。使用local关键字声明了一个本地变量rank来接收返回值。
-- return redis.call("ZCOUNT", "leaderboard", "-inf", "+inf"), rank + 1 ZCOUNT命令,返回有序集合"leaderboard"中成员的数量。"-inf"和"+inf"表示负无穷和正无穷,表示统计整个有序集合的范围。rank + 1表示排名加一,即在排行榜上的实际名次。
-- 最终,这个脚本会返回两个值,一个是有序集合"leaderboard"中成员的数量,另一个是指定成员在排行榜上的实际名次(排名加一)。
-- 这个脚本可以用于实时更新游戏排行榜,并返回给客户端有关成员数量和排名的信息。
Redis 的早期版本只能通过EVAL
命令来编写脚本,发送编写号的 Lua 脚本以供服务器原子性执行,但是有以下不足:
- 发送整个脚本,网络开销和编译开销。(但是Redis提供了命令形式的优化
EVALSHA
。通过首先调用SCRIPT LOAD
获取脚本的 SHA1,应用程序可以随后仅使用其摘要重复调用它) - SHA1 摘要毫无意义,使得调试系统变得极其困难(例如,在
MONITOR
会话中) - 仅缓存脚本不持久化,SCRIPT FLUSH、重启和故障转移会消失
- 因为它们是短暂的,不可相互调用,脚本之间共享和重用代码几乎不可能
为了满足这些需求,同时避免对已建立且广受欢迎的临时脚本进行重大更改,Redis v7.0 引入了 Redis 函数。
函数:
- 数据持久化,属于数据库的一部分不需要运行时加载和承担事务终止的风险
- 原子性、注册后全局共享
- 一次开发,启动时加载重复使用
5. RedisClient
redisclient-windows 为redis提供了一套ui工具,具有友好的交互界面。轻松连接redis客户端,可以更加直观的查看数据并进行数据的操作,其它一些功能
- 发布和订阅:可以订阅指定主题,并在收到消息时进行相应处理。
- 数据库数据的导入和导出
Download the runable jar file redisclient-win32.x86_64.2.0.jar
java环境下启动jar包
java -jar redisclient-win32.x86_64.2.0.jar
6. 思考
Redis与其他缓存工具(如Memcached)有什么区别?
Redis的数据持久化方式有哪些?它们之间的区别是什么?
如何防止Redis的缓存穿透和缓存击穿?
Redis的内存淘汰策略有哪些?
Redis的过期策略是什么?
Redis在什么情况下可能会出现性能问题,并如何解决?
Redis的主从复制是什么?如何配置主从复制?
三、ElasticSearch
1. 安装与配置
ElasticSearch是一个分布式的 RESTful 风格的搜索和数据分析引擎
功能:全文检索、结构化搜索、分析,大量数据、快速处理
- 主要概念: 类似于数据库
- Database == Index
- Table == Type
- Row + Column == Document + Field
- Schema 定义表的字段、字段类型及其字段关系、约束等 == Mapping下定义Type(表)的字段结构处理规则(也就是如何告诉ES如何处理这个字段的搜索规则)
当前最新版本为8.9.1 Download Elasticsearch
下载完成后进入修改配置 /conf/elasticsaerch.yml
否则第一次访问elasticsearch会自动进行安全配置(集群相关)
测试是否启动成功: http://localhost:9200/
2. kibana
Kibana是一个开源的数据可视化工具,主要用于分析和可视化大规模数据集。Kibana提供了一系列交互式的可视化组件,包括图表、表格、地图、仪表盘等,用户可以通过简单的拖拽和点击操作,将数据转化为易于理解和分析的图形化展示
官网下载:Download Kibana,需要和elasticsearch的版本匹配
下载后启动kibana.bat
访问地址:localhost:5601?code=663646
在Dev Tools中的控制台与es交互
这里简单介绍,参考Elasticsearch官方文档查询DSL以获取更全面的语法
// Mapping是用于定义索引中的文档结构和字段类型的一种方式,类似于数据库表结构。它
// 描述了索引中的每个字段的数据类型、索引方式以及其他属性。
// 合理定义Mapping非常重要良好的Mapping定义可以提高搜索的准确性和性能。
// 默认索引是自动识别字段类型并生成
// 创建索引时可以显示指定Mapping结构
PUT /index_name
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "standard"
},
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd"
},
"description": {
"type": "text",
"index": false,
"store": true
}
}
}
}
/**
"type":指定字段的数据类型,例如"text"、"keyword"、"integer"、"date"、"geo"等
"index":指定字段是否需要被索引(默认true)
字段的存储方式:指定字段是否需要存储原始值。
"format": 指定日期类型字段的日期格式。
"analyzer":指定字段在进行索引和查询时使用的分析器
"store": 指定字段是否需要存储原始值。可以设置为"true"或"false"(默认值)。
"fielddata": 指定是否为该字段启用fielddata,用于聚合和排序操作。
"fields": 定义字段的多个子字段,可以为同一个字段指定不同的分析器。
"search_analyzer": 指定字段在进行查询时使用的分析器。
"normalizer": 指定用于对字段进行规范化的正规化器。
"copy_to": 指定将该字段的内容复制到其他字段。
"ignore_above": 指定将超过指定长度的内容截断或忽略。
*/
// PUT创建或更新
// 创建或更新一个文档(记录),并指定文档的ID
PUT /index_name/_doc/document_id
{
"field_name": "field_value"
}
// 创建或更新一个文档,并自动生成的文档ID
PUT /index_name/_doc
{
"field_name": "field_value"
}
// 创建或更新一个文档,并在指定索引和类型中
PUT /index_name/type_name/document_id
{
"field_name": "field_value"
}
// 使用外部版本号创建或更新文档
PUT /index_name/_doc/document_id?version=version_number
{
"field_name": "field_value"
}
// DSL查询
// 匹配查询(Match Query):在指定的字段中搜索包含指定词项的文档。
{
"query": {
"match": {
"field_name": "dijia"
}
}
}
// 多词项查询(Multi-Match Query):在多个字段中搜索包含指定词项的文档。
{
"query": {
"multi_match": {
"query": "dijia",
"fields": ["field1", "field2"]
}
}
}
// 范围查询(Range Query):根据指定的范围条件查询文档。
{
"query": {
"range": {
"field_name": {
"gte": 10,
"lte": 20
}
}
}
}
// 前缀查询(Prefix Query):根据指定前缀匹配文档。
{
"query": {
"prefix": {
"field_name": "prefix"
}
}
}
// 通配符查询(Wildcard Query):使用通配符进行模糊匹配。
{
"query": {
"wildcard": {
"field_name": "wildcard*"
}
}
}
// 正则表达式查询(Regexp Query):使用正则表达式进行匹配。
{
"query": {
"regexp": {
"field_name": "regex.*"
}
}
}
// 布尔查询(Bool Query):使用逻辑运算符组合多个查询条件。
{
"query": {
"bool": {
"must": [
{ "match": { "field1": "term1" } },
{ "match": { "field2": "term2" } }
],
"filter": {
"range": {
"field3": { "gt": 10 }
}
},
"must_not": [
{ "match": { "field4": "term3" } }
]
}
}
}
// 聚合查询(Aggregation Query):对搜索结果进行分组、统计和计算。
{
"aggs": {
"group_by_field": {
"terms": {
"field": "field_name"
},
"aggs": {
"avg_value": {
"avg": {
"field": "numeric_field"
}
}
}
}
}
}
3. 整合springboot
版本对应
引入的依赖和下载的elasticsearch的版本要一致,我的是8.5.3
当我们只引入java的elasticsearch客户端依赖。运行时会发生版本不兼容报错
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.5.3</version>
</dependency>
Elasticsearch Java High Level REST Client是官方提供的用于与Elasticsearch进行通信的Java客户端,基于RESTful API,提供了高级别的API
我们需要排除后自己引入版本一致的elasticsearch-rest-client
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<exclusions>
<exclusion>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</exclusion>
</exclusions>
<version>8.5.3</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>8.5.3</version>
</dependency>
JAVA 客户端API操作ES
CreateIndexRequest createIndexRequest = Requests.createIndexRequest(index).settings(settings).mapping(type,mapping);
CreateIndexResponse response = transportClient.admin().indices().create(createIndexRequest).get();
DeleteIndexRequest deleteIndexRequest = Requests.deleteIndexRequest(index);
DeleteIndexResponse response = transportClient.admin().indices().delete(deleteIndexRequest).get();
public EsModel getQueryResult{
/* 构建查询请求 */
SearchRequest.Builder searchRequestBuilder=new SearchRequest.Builder().index(EsConstant.PRODUCT_INDEX);
// 1.bool查询 利用逻辑关系组合多个其它的查询,实现复杂搜索。布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询,子查询的组合方式有
// must: 必须匹配每个子查询,类似“与”
// should:选择性匹配子查询,类似“或”
// must_not:必须不匹配,不参与算分,类似“非”
// filter:必须匹配,不参与算分
/* 分页*/
searchRequestBuilder.from((searchParam.getPageNum()-1) * EsConstant.PRODUCT_PAGESIZE);
searchRequestBuilder.size(EsConstant.PRODUCT_PAGESIZE);
/* 高亮*/
if(!StringUtils.isEmpty(searchParam.getKeyword())){
Highlight.Builder highlightBuilder=new Highlight.Builder();
// highlightBuilder.requireFieldMatch(false) //多字段时,需要设置为false
highlightBuilder.fields("saleTitle", highlightFieldBuilder->highlightFieldBuilder)
.preTags("<b style='color:red'>")
.postTags("</b>");
searchRequestBuilder.highlight(highlightBuilder.build());
}
/*聚合
* 桶(Bucket)聚合:用来对文档做分组
– TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
– Date Histogram:按照日期阶梯
* 分组,例如一周为一组,或者一月为一组
* 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
– Avg:求平均值
– Max:求最大值
– Min:求最小值
– Stats:同时求max、min、avg、sum等*/
// 分类、品牌聚合和及其各自的子聚合
// 按照attrId聚合之后再按照attrName和attrValue聚合
searchRequestBuilder
.aggregations("brandAgg",aggregationBuilder->aggregationBuilder
.terms(t->t.field("brandId"))
.aggregations("brandNameAgg",subAggregationBuilder->subAggregationBuilder
.terms(t->t.field("brandName")))
.aggregations("brandImgAgg",subAggregationBuilder->subAggregationBuilder
.terms(t->t.field("brandImg")))
)
.aggregations("catalogAgg",aggregationBuilder->aggregationBuilder
.terms(t->t.field("catalogId"))
.aggregations("catalogNameAgg",subAggregationBuilder->subAggregationBuilder
.terms(t->t.field("catalogName")))
)
.aggregations("attrsAgg",aggregationBuilder->aggregationBuilder
.nested(n->n.path("attrs"))
.aggregations("attrIdAgg",subAggregationBuilder->subAggregationBuilder
.terms(t->t.field("attrs.attrId"))
.aggregations("attrNameAgg",sub1AggregationBuilder->sub1AggregationBuilder
.terms(t->t.field("attrs.attrName")))
.aggregations("attrValueAgg",sub1AggregationBuilder->sub1AggregationBuilder
.terms(t->t.field("attrs.attrValue"))))
);
SearchRequest searchRequest = searchRequestBuilder.build();
try {
SearchResponse<ProSaleInfoEsModel> results=elasticsearchClient.search(searchRequest,EsModel.class);
return results
} catch (IOException e) {
throw new ServiceException(e.getMessage());
}
}
4. 一些常见插件
- Analysis插件:这些插件增加了对不同语言和文本处理的支持,例如中文分词、英文词干提取、同义词扩展等。
- Search查询插件,扩展Elasticsearch的查询功能,es 的search功能功能十分强大,有了SearchPlugin我们可以在search中增加更多查询方法,我们后续可能会在此基础上增加很多令人兴奋的查询。例如根据用户购买的书籍查询与用户相似的其他用户,例如结合模型对搜索词进行expanding。
- Scripting插件:Scripting插件允许在搜索和聚合过程中使用自定义脚本。可以使用Groovy、Painless等脚本语言来编写脚本,实现更高级的查询和聚合逻辑,例如用在function_score查询中,使用自定义方法进行打分,我们熟知的painless脚本就是ScriptPlugin脚本
- Ingest插件:Ingest插件提供了数据预处理功能,可以在索引文档之前对数据进行处理。例如,可以使用JSON处理插件对数据进行分割、合并、重命名等操作。
- Mapping插件:这些插件增强了Elasticsearch的索引映射功能。比如增加一个attachment类型 , 里面可以放PDF或者WORD数据
- Monitoring插件:这些插件提供了对Elasticsearch集群的监控和诊断能力。可以监控集群的健康状况、性能指标等,并提供可视化的仪表盘和报告。
- Security插件:Security插件提供了对Elasticsearch集群的安全性管理。可以实现身份验证、授权、TLS加密等安全功能,保护敏感数据和集群免受未授权访问。
- SQL插件:这个插件使得可以使用SQL语句来查询Elasticsearch中的数据。它提供了对常见SQL语法的支持,并可以在数据仓库和ELK(Elasticsearch、Logstash、Kibana)堆栈中进行集成。
- ClusterPlugin 集群管理插件,用于加强自定义对集群的管理功能,该插件可以用来扩展allocation机制,例如在进行分片选择的时候如果我们可能倾向于一些机器
5. 思考
Elasticsearch与传统数据库(如MySQL)有哪些区别?
Elasticsearch的倒排索引是什么?它的原理和作用是什么?
Elasticsearch的数据模型是怎样的?如何进行数据的存储和检索?
Elasticsearch如何处理中文分词和搜索?
Elasticsearch如何集群下保证数据的一致性和可靠性?
四、Seata分布式事务
1. 基础介绍
目前有两种分布式事务的实现思路:XA协议的强一致规范以及柔性事务的最终一致性规范
XID: 全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中。
1.1 3个核心组件
核心组件可分为服务端TC和客户端TM、RM两类
-
TC事务协调器,全局事务的协调者,负责将RM的反馈结果响应给TM,并听从TM的最终决议,将具体决议(提交或回滚)发送给RM执行,相当于中间人,协调RM和TM维护全局事务和分支事务的状态
-
TM事务管理器,它是事务的发起者(具体的微服务)。根据RM第一阶段的执行结果,进行决议。并将决议反馈给TC。相当于发号施令的
-
RM资源管理器,其实就是事务的参与者。获取TC的执行命令具去执行分支事务的第一阶段以及第二阶段,并将执行结果反馈给TC,相当于具体做事的
1.2 工作流程
-
TM向TC开启事务,TC生成并返回全局事务标识XID
-
XID跟随调用链路传播到其它服务
-
RM(参与者)向TC注册到该XID对应的全局事务的管辖下(分支事务的或回滚会回馈TC)【AT第一个阶段】
-
TM根据TC获取各个RM分支事务的执行结果并向TC发起全局提交或者回滚决议【AT第二阶段】
-
TC 调度 XID 下管辖的所有分支事务完成提交或回滚操作
1.3 写隔离
-
一阶段本地事务提交前,需要确保先拿到 全局锁 。
-
拿不到 全局锁 ,不能提交本地事务。
-
拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。`
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚,tx2尝试获取全局锁超出一定范围也发生回滚
一阶段事务的提交已经完成提交了, 而二阶段的回滚操作是通过回滚日志完成,通过一阶段的回滚日志进行反向补偿,并不是依赖于数据库的事务机制。
1.4 读隔离
在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
读未提交:一个事务未提交的数据可以被其它事务读到
读已提交:一个事务提交后数据才被其它事务读到
可重复读:一个事务执行过程中看到的数据总是和这个事务开启时看到的数据是一致的,未提交的事务对其它事务是不可见的
串行化:读写都加锁,后来的事务需要等待前一个事务执行完毕
2. 安装与配置
下载安装包
链接: https://github.com/seata/seata/releases
新版一般有script脚本,如果没有下载后放到seata安装目录(注意脚本的版本需要对应)
seata脚本: https://github.com/seata/seata/tree/master/script
创建数据库db_seata
导入\scriptserver\db\mysql.sql
:
- global_table:存储全局事务的信息
- branch_table:存储事务参与者的信息
- lock_table:存储锁信息(锁的表信息,行信息)
使用nacos时也要注意group要和seata server中的group一致,默认group是"DEFAULT_GROUP"
我的是1.7版本的seata,配置文件名为conf/application.yml , 格式参照同目录下的application.example.yml
数据存储模式: seata.store.mode
- file(默认):将事务日志存储在文件系统中,不支持高可用,持久化至本地文件 root.data (bin\sessionStore\root.data) 中,性能较高。
- db:将事务日志存储在关系数据库中,可以实现高可用,性能差些。
- redis:将事务日志以及全局锁信息存储在 Redis 中,性能较高,事务信息存在丢失风险,需要进行持久化
注册中心: seata.registry.type
- nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa
配置中心: seata.config.type
- nacos 、 consul 、 apollo 、 zk 、 etcd3
本文使用常用配置:db + nacos
, 需要修改的配置 seata.registry.nacos 、 seata.config.nacos 、seata.store.db
配置文件修改好后,找到 \script\config-center\config.txt
文件,config.tx
文件中有seata上传到配置中心的所有参数信息,想知道每个配置都是什么意思,可以 查看官网的 配置文档
可以根据自己的需要的配置上传到配置中心,这里只需要如下信息
#Transaction routing rules configuration, only for the client
service.vgroupMapping.order_tx_group=default
service.vgroupMapping.product_tx_group=default
service.vgroupMapping.user_tx_group=default
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
# 事务分组:seata的资源逻辑,可以按微服务的需要,在应用程序(客户端)对自行定义事务分组,每组取一个名字。
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/db_seata?useUnicode=true&rewriteBatchedStatements=true&useSSL=false&characterEncoding=utf-8&serverTimezone=UTC
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
事务分组:seata的资源逻辑,可以按微服务的需要,在应用程序(客户端)对自行定义事务分组,每组取一个名字。
事务分组如何找到后端集群:客户端会去配置中心找service.vgroupMapping .[事务分组配置项],取得配置项的值,通过一定的前后缀+服务名去构造服务名以实现不同的服务名,最后拿到服务名去相应的注册中心去拉取相应服务名的服务列表,获得后端真实的集群服务列表
作用:集群的崩溃通过只切换对应分组,可以把故障缩减到服务级别,但前提也是你有足够server集群。
config.txt改好后:有手动和脚本两种方式上传nacos
-
新建seataServer.properties手动copy
-
如果有sh环境
sh nacos‐config.sh ‐h localhost ‐p 8848 -u nacos -w nacos ‐g SEATA_GROUP ‐t 5a3c7d6c‐f497‐4d68‐a71a‐2e5e3340b3ca # -h主机 -p端口 -u用户名 -w密码 -g分组 -t命名空间(默认空)
-
如果有py环境
python nacos‐config.py ‐h localhost ‐p 8848 -u nacos -w nacos ‐g SEATA_GROUP ‐t 5a3c7d6c‐f497‐4d68‐a71a‐2e5e3340b3ca # -h主机 -p端口 -u用户名 -w密码 -g分组 -t命名空间(默认空)
单体环境:双击 seata-server.bat
即可,默认端口8091
3. 整合SpringBoot
引入seaeta依赖
<dependencies>
<!--nacos 服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--导入openfeign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!-- skywalking 工具类 可自定义链路追踪,跟服务版本一致-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.12.0</version>
</dependency>
</dependencies>
Seata的微服务客户端数据库增加以下该表
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
application.yaml配置
spring:
application:
name: order-service
datasource:
url: jdbc:mysql://localhost:3306/db_order?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
cloud:
nacos:
server-addr: localhost:8847
discovery:
username: nacos
password: nacos
alibaba:
seata:
# 配置事务分组,在 "seata\seata\script\config-center\config.txt" 中设置
tx-service-group: order_tx_group
seata:
registry:
# 配置 seata 的注册中心, 告诉 seata client 怎么去访问 seata server 事务协调者进行通信
type: nacos
nacos:
# seata-server 所在的注册中心地址
server-addr: localhost:8847
# 指定 seata-server 在注册中心的 服务名, (默认 seata-server)
application: seata-server
username: nacos
password: nacos
# 默认 SEATA_GROUP
group: SEATA_GROUP
config:
# 配置 seata 的配置中心,可以读取关于 seata client 的一些配置,即 "seata\seata\script\config-center\config.txt" 中的配置
type: nacos
nacos:
server-addr: localhost:8847
username: nacos
password: nacos
group: SEATA_GROUP
- 在启动类或方法上添加
@GlobalTransactional
即可
五、RabbitMq消息队列
1. 概念介绍
交换机、队列、路由键、生产者消费者间关系
- 交换机和队列通过路由键绑定关系
- 生产者提供交换机名称和路由键找到要发送到的队列,发送消息时可携带参数
- 消费者监听队列等待消息到来,根据不同参数有不同监听的方法
交换机的类型
-
Direct 直连交换机
交换机和队列是一对一关系,通过路由键绑定
-
Fanout 广播交换机
忽视路由键,将消息发送给绑定的所有队列
-
Topic 主题交换机
交换机对应一个或多个队列,路由键有匹配规则的
#代表任意个,*表示一个(必须出现)
如
2. 安装与配置
2.1 Erlang安装
RabbitMq依赖于Erlang环境,因此首先需要安装Erlang环境
官网安装:https://erlang.org/download/
RabbitMQ version | Minimum required Erlang/OTP | Maximum supported Erlang/OTP |
---|---|---|
3.12.0 ~ 3.12.4 | 25.0 | 26.0 |
3.11.0 ~ 3.11.22 | 25.0 | 25.3.x |
3.10.19 ~ 3.10.25 | 24.3.4.8 | 25.3.x |
3.10.14 ~ 3.10.18 | 24.3 | 25.2 |
3.10.8 ~ 3.10.13 | 24.2 | 25.2 |
3.10.5 ~ 3.10.7 | 23.2 | 25.2 |
3.10.0 ~ 3.10.4 | 23.2 | 24.3 |
安装后配置Erlang的环境变量
在控制台测试
erl
2.2 RabbitMq下载
下载链接:https://github.com/rabbitmq/rabbitmq-server/releases
如果未启动,手动运行
rabbitmq-server.bat start
启动成功!
如果要访问浏览器上的管理后台,启用RabbitMq自带的后台插件
rabbitmq-plugins.bat enable rabbitmq_management
重启服务后访问管理后台:http://localhost:15672
使用默认管理员用户:账号 guest 密码 guest
3. 整合SpringBoot
毕竟是同一个rabbitmq,微服务项目可以跨服务生产或者消费消息。
这里简便只在同一个服务中演示
引入Maven
依赖
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
application.yaml
server:
port: 7001
spring:
application:
name: rabbitmq-test
# 配置rabbitMq 服务器
rabbitmq:
host: 127.0.0.1
port: 5672
username: root
password: root
#虚拟host 可以不设置,使用默认host(/)
virtual-host: testHost
如果想为单独一个服务或系统配置了一个virtual-host,在后台页面手动新增一个虚拟主机
创建一个用户
角色
Admin 能做一切,管理用户、vhosts和权限,关闭其他用户的连接,并管理所有vhosts的策略和参数
Monitoring 访问管理插件,查看所有连接和通道以及节点相关信息
Policymaker 访问管理插件,并管理他们可以访问的vhosts的策略和参数
Management 访问管理插件
分配新用户虚拟主机testHost的权限
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置直连交换机、队列和他俩的路由键绑定
**/
@Configuration
public class DirectRabbitConfig {
@Bean
public Queue directQueue() {
// Queue("TestDirectQueue",true,true,false);
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// args: 其它参数,设置队列的属性
//一般设置一下队列的持久化就好,其余两个就是默认false
return new Queue("directQueue",true);
}
//Direct交换机
@Bean
DirectExchange directExchange() {
return new DirectExchange("directExchange",true,false);
}
//绑定 将队列和交换机绑定, 并设置用于匹配键:abc
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(directQueue()).to(directExchange()).with("abc");
}
}
消费者监听队列
@Component
@RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue
public class DirectReceiver {
@RabbitHandler
public void process(String testMessage) {
System.out.println("DirectReceiver消费者收到消息 : " + testMessage.toString());
// TODO 业务处理
}
// 编写处理不同参数的方法
}
消息生产者(任何服务)发送消息,就会触发消费者监听处理方法的执行
@RestController
public class Controller{
// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/hello")
public void hello(){
// 调用RabbitTemplate的方法发送消息
rabbitTemplate.convertAndSend("testDirectExchange","abc","hello world");
}
}
Topic主题交换机和Fanout广播交换机
配置和上面都差不多
Topic,注意路由键规则即可
Fanout,不需要路由键(如果有会忽略)
3.1 死信队列
概念:消息无法被消费者正常消费时,将这些无法消费的消息发送到专门的死信队列中,进行死信处理
死信队列出现场景
- 消息被拒绝
- 消息过期
- 队列达到最大长度
public class RabbitMqConfig{
// 死信队列和死信交换机和普通的创建方式一致(同上)
@Bean
Queue directQueue(){
// 正常的队列需要的额外参数配置如下
HashMap<String,String> map = new HashMap<>();
map.put("x-dead-letter-exchange", "死信交换机名");
map.put("x-dead-letter-routing-key", "消息进入死信交换机时携带的路由键");
args.put("x-max-length", 5);
args.put("x-message-ttl", 60 * 1000); // 存活时间
return new Queue("directQueue",true,false,false,map);
}
}
队列的Args其它参数
参数名 | 作用 |
---|---|
x-message-ttl | 发送到队列的消息在丢弃之前可以存活的时间 |
x-max-length | 限制队列最大长度 |
x-expires | 队列没有访问超时时自动删除的时间 |
x-max-length-bytes | 限制队列最大容量 |
x-dead-letter-exchange | 将删除/过期的数据放入指定交换机 |
x-dead-letter-routing-key | 将删除/过期的数据放入指定路由键 |
x-max-priority | 队列的优先级 |
x-queue-mode | 队列模式 |
x-queue-master-locator | 镜像队列 |
4. 分布式事务
延时队列可实现分布式事务的最终一致性
商场下单业务
- 用户下单
- 扣减库存
订单服务:用户下单,生成订单id发送下单消息到下单队列(存活30分钟,超时会进入下单延时队列),调用库存服务锁库存,保存订单信息
- 如果
库存服务异常回滚
,调用方订单服务肯定知道该异常的发生因此也发生回滚,满足全局回滚 - 如果库存服务成功调用后,调用方订单服务在接下来的执行发生异常回滚,
此时订单不存在,但是库存修改了
,30分钟后库存服务会根据订单是否存在来决定解锁库存,存在不解锁,不存在解锁,实现最终一致性
库存服务:监听下单延时队列,30分钟后会收到下单队列的消息进入下单延时队列(携带订单id),此时判断订单如果不存在或者未付款,解锁库存并且修改订单状态
5. 消息延迟插件
RabbitMq可以使用死信队列实现延时队列(每个队列内消息统一指定存活时长),但是默认并不支持消息的延迟,我们需要下载插件 rabbitmq_delayed_message_exchange,可以实现在发送消息在交换机时指定延时到达队列的时间。
下载完成后将其放在RabbitMq安装目录下的plugins文件夹中,在bin目录下中运行命令
rabbitmq-plugins.bat enable rabbitmq_delayed_message_exchange
重启服务后访问,交换机类型上多了x-delayed-message的延时类型
该交换机类型可设置的延时粒度小至消息级别,使用它是没问题的,但是要注意以下几点
-
监听延迟交换机的延迟消息量,是其尽量维持在一个可控的量级(比如10W)
-
持久化延迟时间比较久的消息,然后只延迟最近几小时的消息,然后去扫持久化延迟消息表或者Redis其它数据源,需要注意的就是有些消息可能比较集中的某个时刻发生
-
需要进行一定的压测
编码实现,不一样的两个地方
创建交换机
**/
@Configuration
public class RabbitMqConfig {
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, args);
}
}
发送时指定参数
@RestController
public class Controller{
// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/hello")
public void hello(){
// 调用RabbitTemplate的方法发送消息
rabbitTemplate.convertAndSend("delayExchange","delayRouteKey","hello world",
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 消息延迟5秒
message.getMessageProperties().setHeader("x-delay", 5000);
return message;
}
});
}
}
6. 思考
RabbitMQ与其他消息队列中间件(如Kafka)有哪些区别?
RabbitMQ支持哪些消息模型?
RabbitMQ如何保证消息的可靠性投递?
RabbitMQ如何处理消息的持久化?它的持久化机制是什么?
RabbitMQ消息的序列化方式有哪些?如何选择合适的序列化方式?
RabbitMQ的消费者限流是什么?
六、Sentinel流量监控
1. 介绍
官网:http://sentinelguard.io/zh-cn/
Sentinel是一个分布式微服务治理组件,主要以流量为切入点。从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel存在以下概念
1.1 资源
-
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
-
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
1.2 规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
1.3 流量控制
流量控制有以下几个角度:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程池、系统负载等;
- 控制的效果,例如直接限流、冷启动、排队等;
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
1.4 熔断降级
当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源
2. 安装与配置
官网下载:Download Sentinel
下载为一个jar包,Sentinel是一个java项目,直接使用java命令运行即可启动
# 可以通过运行java -jar sentinel-dashboard-1.8.0.jar --help获取更多关于可用参数的帮助
java -jar sentinel-dashboard-1.8.0.jar --server.port=8888 --sentinel.dashboard.auth.username=sentinel --sentinel.dashboard.auth.password=sentinel
# --server.port=<port>:指定Sentinel Dashboard的监听端口,默认为8080。
# --server.context-path=<path>:指定Sentinel Dashboard的上下文路径,默认为空。
# --sentinel.dashboard.auth.username=<username>:指定需要登录Sentinel Dashboard的用户名。
# --sentinel.dashboard.auth.password=<password>:指定需要登录Sentinel Dashboard的密码。
# --sentinel.dashboard.sentinel.api.cors.allowed-origins=<origins>:指定允许访问Sentinel Dashboard的跨域源。
启动成功后
访问路径:localhost:8888
3. 整合SpringBoot
引入Maven
依赖
<!-- 必须引入,包含了Sentinel的核心功能,如流量控制、熔断降级、系统保护等。它提供了Sentinel的基本功能类和API -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
<!-- Sentinel本地应用接入控制台,连接dashboard需要。不连接网页控制台可以不引入 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.0</version>
</dependency>
application.yaml
spring:
application:
name: db-order
cloud:
sentinel:
transport:
post: 8722 # 跟控制台交流的端口,随意指定一个未使用的端口即可,如果被占用会自动++直到不被占用
client-ip: localhost
dashboard: localhost:8888 # 控制台地址
在idea设置JVM启动参数(VM OPTIONS)
-Dcsp.sentinel.dashboard.server=127.0.0.1:9000 # Sentinel控制台的地址和端口号
-Dproject.name=Sentinel-Quick-Start # 本地应用在控制台中的名称
4. 定义资源规则
-
手动定义的资源规则
在后台的页面手动定义的资源规则是非持久化的,重启java服务后会消失
-
硬编码定义
定义资源 => 配置资源规则
private static void initFlowRules(){ List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("HelloWorld"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // Set limit QPS to 20. rule.setCount(20); rules.add(rule); FlowRuleManager.loadRules(rules); } public static void main(String[] args) { // 配置规则. initFlowRules(); while (true) { // 1.5.0 版本开始可以直接利用 try-with-resources 特性 try (Entry entry = SphU.entry("HelloWorld")) { // 被保护的逻辑 System.out.println("hello world"); } catch (BlockException ex) { // 处理被流控的逻辑 System.out.println("blocked!"); } } }
-
使用注解
<!-- @SentinelResource等注解jar包 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>1.8.0</version> </dependency>
在需要定义的资源(接口/方法)上添加注解@SentinelResource
@SentinelResource有以下参数
- value:资源名称,必需项(不能为空)
- entryType:entry 类型,可选项(默认为 EntryType.OUT , 系统规则只对 IN 生效)
- blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称
blockHandler 需要是public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore指定忽略的异常)
返回值类型和原方法相匹配,可以额外多一个 Throwable 类型的参数用于接收对应的异常。fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
- exceptionsToIgnore: 指定忽略的异常类,原样抛出或进入全局捕获
5. 降级规则持久化
5.1 NACOS
若项目使用NACOS作为配置中心,则Sentinel的降级规则可以持久化入NACOS中,推送由控制台统一进行管理
引入Maven依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.3</version>
</dependency>
application.yaml
spring:
application:
name: user-service
cloud:
sentinel:
transport:
#配置 Sentinel dashboard 地址
dashboard: localhost:8888
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
datasource:
# 配置多个数据源用于获取规则配置信息,例如流控规则、降级规则等。ds1和ds2就是两个不同的数据源,可以配置不同的属性值来指定不同的数据源。
ds1:
nacos:
server-addr: localhost:8848 #Nacos地址
dataId: ${spring.application.name}-flow-sentinel #dataID
groupId: DEFAULT_GROUP
data-type: json # 配置文件的格式
# 注意网关流控规则对应 gw-flow
# 表示流控规则,可配置规则:flow(默认),degrade,authority,system,param-flow,gw-flow,gw-api-group
rule-type: flow
ds2:
nacos:
#Nacos地址
server-addr: localhost:8848
dataId: ${spring.application.name}-degrade-sentinel
groupId: DEFAULT_GROUP
data-type: json
#表示降级规则,可配置规则:flow(默认),degrade,authority,system,param-flow,gw-flow,gw-api-group
rule-type: degrade
流控规则的配置文件格式 ${spring.application.name}-flow-sentinel
[
{
"resource": "test",
"limitApp": "default",
"grade": 1,
"count": 2,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
降级规则的配制文件格式 ${spring.application.name}-degrade-sentinel
[
{
"resource": "getOrderList",
"count": 20.0,
"grade": 0,
"passCount": 0,
"timeWindow": 10
}
]
5.2 MYSQL
: 》// TODO
6. 规则的种类
Sentinel 支持以下几种规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则 和 热点参数规
1. 来源访问控制规则(AuthorityRule)
通过调用来源判断是否放行
黑名单模式,不属于黑都放行;白名单模式,属于白名单内才放行。
来源访问控制规则非常简单,主要有以下配置项:
- resource:资源名,即限流规则的作用对象。
- limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB。
- strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模
2. 热点规则(ParamFlowRule)
热点参数规则类似于流量控制规则(FlowRule
),经常访问的数据,访问频次最高的 Top K 数据,可以通过限制访问参数如商品ID限制购买流量
配置项
属性 | 说明 | 默认值 |
---|---|---|
resource | 资源名,必填 | |
count | 限流阈值,必填 | |
grade | 限流模式 | QPS 模式 |
durationInSec | 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 | 1s |
controlBehavior | 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 | 快速失败 |
maxQueueingTimeMs | 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 | 0ms |
paramIdx | 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置 | |
paramFlowItemList | 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型 | |
clusterMode | 是否是集群参数流控规则 | false |
clusterConfig | 集群流控相关配置 |
… 更多查看官网
七、SkyWalking链路追踪
1. 介绍
官网:https://skywalking.apache.org/
SkyWalking是一款分布式系统的应用程序性能监控工具,专为 微服务 、 云原生 和基于 容器 的 (Kubernetes) 架构而设计。
1.1 强大的功能
- 分布式追踪:清晰地记录请求调用链的调用关系、精确的路径和性能信息,有助快速定位问题
- 性能监控:记录各个节点的性能指标,响应时间吞吐量、错误率等
- 应用拓扑图:自动绘制拓扑图,更直观的展示组件依赖关系
- 数据可视化,提供了直观、易理解的图文监控数据可视化界面
- 多语言支持,支持多种编程语言的探针,包括Java、.Net Core、PHP、NodeJS、Golang、LUA、Rust、C++ 等,使得可以对不同语言编写的分布式系统进行监控和跟踪。
- 告警与报警:内置 webhook 支持通过 HTTP、gRPC、Slack 等自动发送事件通知,自定义告警规则在系统指标达到条件是触发告警
1.2 架构
Skywalking架构可分为四个模块
- Agent:在应用中,收集 Trace、Log、Metrics 等监控数据,使用 RPC、RESTful API、Kafka 等 Transport 传输方式,发送给 OAP 服务,包括各种格式的指标、跟踪、日志和事件(SkyWalking、Zipkin、OpenTelemetry、Prometheus、Zabbix 等)
- 平台后端:支持数据聚合、分析和流式处理,涵盖跟踪、指标、日志和事件。作为聚合者角色、接收者角色或两者兼而有之。
- Storage:通过开放/可插入接口存储 SkyWalking 数据。您可以选择现有的实现,例如 ElasticSearch、H2、MySQL、TiDB、BanyanDB,也可以自己实现。
- Web UI:是一个高度可定制的基于 Web 的界面,允许 SkyWalking 最终用户可视化和管理 SkyWalking 数据。
2. 安装与配置
官网下载Skywalking:Download skywalking
新建一个数据库 db_skywalking
供使用mysql存储模式的保存监控数据
解压后进入skywalking目录修改配置\config\application.yml
官网下载JAVA的探针 :Download java-agent.jar ,,用于微服务的启动跟随运行
下载完成解压目录下有一个启动jar包,copy它的完整路径
在idea增加启动参数
-javaagent:D:\mytool\apache-skywalking-java-agent-8.14.0\skywalking-agent\skywalking-agent.jar -Dskywalking.agent.service_name=order-service -Dskywalking.collector.backend_service=127.0.0.1:11800
如果是较新版的idea,界面可能不太一样,如下:
修改探针jar包路径和当前服务名
多个微服务启动只需要启动探针jar时更改为不同的服务名字即可
相信这篇文章能令你回忆起坑坑洼洼的来路,并让你对技术有了更深的思考与追寻