微服务技术栈学习笔记(自用)
文章目录
1.导学
- 微服务技术:
- 持续集成:
自动化编译(Jenkins)+打包(Docker)+自动化部署(kubernetes/rancher)
- 特点:
- 技术对比:
常用:SpringCloud+Feign、SpringCloudAlibaba+Feign(服务接口为Restful风格)
SpringCloudAlibaba+Dubbo、Dubbo原始模式(服务接口为Dubbo方式)
SpringCloud:基于Springboot实现了各种微服务组件的自动装配
2.Eureka
2.1微服务远程调用:
RestTemplate(发送各种http请求,使服务之间可以通信)
OrderApplication.java:
//创建RestTemplate并注入容器
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
OrderService.java:
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.发起http请求,查询用户
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
//3.封装
order.setUser(user);
// 4.返回
return order;
}
2.2搭建Eureka
- 搭建注册中心:
pom.xml:
<!--eureka服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
EurekaApplication.java:
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
application.yml:
server:
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka # 把自己也注册到eureka上(集群用)
2.服务注册:
<!-- eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
spring:
application:
name: orderservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka # 注册
多实例注册:
在某一实例中右击Copy Configuration
并修改相应端口(VM options: -Dserver.port=8082)
3.服务发现:
orderservice.java:
//根据id查询
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
OrderApplication.java:
@Bean
@LoadBalanced //负载均衡设置
public RestTemplate restTemplate(){
return new RestTemplate();
}
3.Ribbon负载均衡
- 底层流程:
- 策略类型:
默认为轮询机制
- 负载均衡策略:
//代码方案(作用于全局:所有服务)
OrderApplication.java:
@Bean
public IRule randomRule(){
return new RandomRule();
}
//配置文件方案
order-service的application.yml:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡策略
- 饥饿加载:
项目启动时就开始创建LoadBalanceClient,降低第一次访问的耗时
(Ribbon默认为懒加载:第一次访问才创建LoadBalanceClient,请求时间很长)
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定对userservice这个服务采用饥饿加载
4.Nacos
- 下载解压:https://github.com/alibaba/nacos/releases
bin:启动脚本
conf:配置文件(默认端口为8848,此处修改端口)
- 单机启动:startup.cmd -m standalone
访问:默认账号和密码都是nacos
4.1服务注册
//父工程
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
//子工程
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
//application.yaml:
cloud:
nacos:
server-addr: localhost:8848
4.2 分级存储
服务-----集群-----实例
服务集群属性:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SH # 集群设置
//然后在nacos服务详情中即可查看
- 根据集群负载均衡:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
//注意将服务权重均设为1
//当跨集群访问发生时,控制台中会有黄色提示
- 实例权重设置:直接在nacos服务中设置即可(0~1)
环境隔离:注意设置的是命名空间id
//新建命名空间:直接在nacos命名空间中新建即可
//配置
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ
namespace: f7f1ce9b-8d30-4250-a656-850a8e17d82b # dev命名空间id
- 临时实例:一旦挂了就消除了相关记录(如果为非临时的话,在主动删除之前一直保有记录)
//配置
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
ephemeral: false # 设置为非临时实例
配置管理服务:配置更改热更新
直接在配置管理
中添加设置即可
[服务名称]-[profile].[后缀名]
- 配置拉取:
<!-- nacos的配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
# 必须是这个名称
bootstrap.yaml:
spring:
application:
name: userservice
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
# 检验日期配置用
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
- 配置热更新:
第一种方式是通过配置文件方式(PatternProperties.java);
第二种方式是通过注解@Value(“${yaml里定义的键值对}”)的方式
优先级:Nacos带环境的配置 > Nacos不带环境的配置 > 本地yaml文件配置
Nacos带环境可以理解为专属化配置(开发环境和生产环境)、肯定优先于Nacos不带环境的全局配置;
- 集群搭建:
cluster.conf:
# 自己的IP和端口号
127.0.0.1:8845
127.0.0.1:8846
127.0.0.1:8847
Nacos的application.properties:
spring.datasource.platform=mysql
db.num=1
db.url=xxx
xxx # 配置自己的数据源
...
server.port=8845
server.port=8846
server.port=8847 # 配置端口
5.Feign
引入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
自定义配置:
# 配置文件方式
feign:
client:
config:
default: # 全局生效
loggerLevel: FULL # 日志级别
feign:
client:
config:
userservice: # 局部生效
loggerLevel: FULL # 日志级别
//代码方式
//先声明一个Bean
public class FeignClientConfiguration{
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC
}
}
//全局
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
//局部
@FeignClients(value = "userservice" , configuration = FeignClientConfiguration.class)
优化:
底层的客户端实现是
URLConnection(默认实现,不支持连接池)
Apache HttpClient(支持连接池,常用)
OKHttp(支持连接池)
第一种方式是默认的,不支持连接池。
所以性能优化指的是:换成第二种方式或者第三种方式。
其中第二种方式 Apache HttpClient 常用于模拟postman的样式,发送一个form-data样式的post请求,也可在这个post请求里上传文件。
步骤:
<!--HttpClient依赖-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.1.0</version>
</dependency>
feign:
httpclient:
enabled: true # 支持HttpClient的开关
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 单个路径的最大连接数
6.Gateway
功能:身份认证和权限校验;服务路由、负载均衡;请求限流
在SpringCloud中网关技术包括两种:gateway和zuul
其中Zuul是基于Servlet的实现,属于阻塞式编程
而Gateway则是基于SPring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
6.1搭建
新建模块
# 配置文件:
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
gateway:
routes:
- id: user-service # 路由标识,必须唯一
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟着是`服务名称`
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合规则
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
流程:
6.2路由断言工厂
如果请求不符合路由断言,那请求就会被拒绝,返回一个404。
因此可以通过配置路由断言工厂的方式来过滤某些请求。
6.3 过滤器
过滤器还可对服务响应进行过滤
全局过滤器GlobalFilter:可以自定义过滤逻辑代码实现
public interface GlobalFilter{
//exchange 请求上下文,可获取请求、响应信息
//chain 把请求委托给下一个过滤器
Mono<Void> filter(ServerWebExchange exchange, GatewayFilter chain);
}
过滤器链执行顺序:
跨域问题处理
域名不一致就是跨域:域名不同;域名相同,端口不同
浏览器禁止请求的发起者和服务端发生跨域ajax请求(只有ajax请求会被浏览器拦截)
解决方案:CORS配置
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
8.ElasticSearch
8.1 倒排索引
类似于查字典,拿到一本字典的目录,不会按页码挨个查找(正向索引),
而是先看目录的关键字,然后找到关键字之后,再去看关键字旁边的页码,最后再根据页码翻到书对应的那一页
概念:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sp679X1m-1668340524144)
架构:
Mysql:擅长事务类型操作,可确保数据安全性与一致性
ES:海量数据搜索、分析、计算
写入数据用Mysql,读取数据(搜索查找)用ES,Mysql与ES保持数据同步
8.2 安装部署
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
解释:
配置堆内存(JVM)。因为ES底层是java实现的,所以要配置jvm内存大小。默认值是1T,对于轻量级服务器太大了,所以适当减少为512M-
单点模式运行(区别于集群模式运行)
数据卷挂载,分别是数据保存目录(data),和插件目录(plugins)
将ES容器加入到创建的docker网络中
9200是用户访问的http协议端口,9300是ES容器节点互联的端口
8.3 分词器配置
选择IK分词器(来源于github,专门适配了中文)
作用:对文档进行分词;对输入内容进行分词
IK的模式:ik_smart(智能切分);ik_max_word(最细切分)
拓展/停用词条:config目录的IkAnalyzer.cfg.xml文件设置
8.4 索引库操作
mapping映射属性
创建索引库PUT
# 创建索引库
PUT /heima
{
"mappings": {
"properties": {
"info": {
"type": "text",
"analyzer": "ik_smart"
},
"email": {
"type": "keyword",
"index": false
},
"name": {
"type": "object",
"properties": {
"firstName": {
"type": "keyword"
},
"lastName": {
"type": "keyword"
}
}
}
}
}
}
查看索引库:GET /索引库名
删除索引库:DELETE /索引库名
修改索引库从设计上被禁止了,索引库和mapping一旦创建无法修改,
但是可以用PUT添加新的字段 (该字段必须是全新的字段)
8.5 文档操作
# 插入文档
POST /heima/_doc/1
{
"info": "黑马程序员java讲师",
"email": "112837@qq.com",
"name":{
"firstName":"云",
"lastName":"赵"
}
}
# 查询
GET /heima/_doc/1
# 删除
DELETE /heima/_doc/1
每次写操作的时候,都会使得文档的
"_version"
字段+1
全量修改:用PUT(用POST的话就是新建了)
# 插入一个文档
PUT /heima/_doc/1
{
"info": "黑马程序员java讲师",
"email": "112837@qq.com",
"name":{
"firstName":"云",
"lastName":"赵"
}
}
//如果id在索引库里面不存在,并不会报错,而是直接新增
//如果索引库存在该记录,就会先删掉该记录,然后增加一个全新的。
增量修改:用POST
# 局部修改文档字段:必须跟一个doc
POST /heima/_update/1
{
"doc": {
"email":"lbwnb@qq.com"
}
}
8.6 用RestClient操作索引库和文档
编写DSL语句
# 酒店的mapping
PUT /hotel
{
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text"
, "analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword"
, "index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all" # 字段参数(用于聚合)
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point" # 地理位置特殊数据类型
},
"pic":{
"type": "keyword"
, "index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
index如果设置成false,则既不参与索引也不参与分词
索引库的id总是被要求成keyword(也就是String)类型,即使数据库的主键id可能是int
DSL查询语法:
# match 和 multi_match 全文检索
GET /hotel/_search
{
"query": {
"match": {
"address": "如家外滩"
}
}
}
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外滩如家",
"fields": ["brand","name","business"]
}
}
}
# 精确查询(term查询)
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
# 精确查询(范围range)
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 300
}
}
}
}
8.7 打分算法
文档结果根据关联度打分返回
eg:使"如家"排名靠前
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"address": "外滩"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "如家"
}
},"weight": 10
}
]
}
}
}
复合查询:
算分条件越多,性能就会越差。所以能使用filter的就别使用must,能不算分就不算分
eg:包含“如家”,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{"match": {
"name": "如家"
}}
],
"must_not": [
{"range": {
"price": {
"gt": 400
}
}}
],
"filter": [
{
"geo_distance": {
"distance": "100km",
"location": {
"lat": 31.21,
"lon": 121.5
}
}
}
]
}
}
}
搜索结果处理:
排序:“FIELD”: “asc” “FIELD”: “desc”
分页:
“from”: 20
, “size”: 5
高亮:“highlight”
深度分页问题
解决方案:
Java RestClient查询语法:
要构建查询条件,只需记住一个类:QueryBuilders。
要构建搜索DSL,只需记住一个API:SearchRequest的source()方法(支持链式编程)
高亮结果解析:
8.8 数据聚合
类似于MySQL的group by(对数据的统计分析和计算)
聚合不能是text类型,不能分词
1.Bucket聚合
# 聚合功能
GET hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 10,
"order": {
"_count": "asc" #结果按照count升序排列
}
}
}
}
}
2.Metric聚合
# 嵌套聚合metric
GET hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 10,
"order": {
"scoreAgg.avg": "asc" # 根据下面的子聚合结果的avg进行升序排序
}
},
"aggs": {
"scoreAgg": {
"stats": {
"field": "score"
}
}
}
}
}
}
Java Restclient对应Json的图例:
在这里插入图片描述
8.9 数据补全
补全分词器(https://github.com/medcl/elasticsearch-analysis-pinyin)
拼音适合创建倒排索引使用,不能在搜索中使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CrkfvlFe-1668340524156)(C:\Users\29851\Desktop\学习笔记\微服务技术栈学习笔记\assets\ES13)]
因此在创建时用my_analyzer,搜索时用ik_smart
8.10 ES与Mysql之间的数据同步
在微服务中,操作MySQL的业务和操作ES的业务可能在不同的微服务上,这种情况应该怎么实现数据同步呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZMpy7cf-1668340524157)(C:\Users\29851\Desktop\学习笔记\微服务技术栈学习笔记\assets\ES14)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UevAmcQN-1668340524158)(C:\Users\29851\Desktop\学习笔记\微服务技术栈学习笔记\assets\ES15)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nOS76oAZ-1668340524159)(C:\Users\29851\Desktop\学习笔记\微服务技术栈学习笔记\assets\ES16)]