引用依赖
发送者
消息的转换器作用
配置jackson序列化jackson-databind
在配置类中配置对应的对象即可覆盖默认的MessageConverter
//发送者用的是什么序列化机制
// 消费者也需要使用对应的序列化机制
@Bean
MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
声明交换器、队列及绑定关系
如何保证消息的可靠性
Spring整合MQ的自动补偿机制
底层使用Aop拦截,如果程序(消费者)没有抛出异常,自动提交事务
如果Aop使用异常通知拦截获取到异常后,自动实现补偿机制
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual #手动确认
# acknowledge-mode: auto #自动确认 manual #手动确认
# 重试策略相关配置
retry:
enabled: true # 是否开启重试功能
max-attempts: 5 # 最大重试次数
# 时间策略乘数因子 0 1 2 4 8
# 时间策略乘数因子
multiplier: 2.0
initial-interval: 1000ms # 第一次调用后的等待时间
max-interval: 20000ms # 最大等待的时间值
如何避免消息重复消费
如何避免消息堆积
与MySQL类比
索引库(indices) | 类型(type) | 映射(mappings) | 文档(document) | 字段(field) |
---|---|---|---|---|
数据库(Database) | 表(table) | 表的字段约束 | 行(Row) | 列(Column) |
分词器
ik_max_word
和ik_smart
创建索引库: PUT /库名称
# 定义一个索引库,并且设置动态模板
PUT heima3
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
}
},
"dynamic_templates": [ # 模板名称,随便起
{
"strings": { #匹配条件
"match_mapping_type": "string", # 凡是string类型的
"mapping": { #映射规则 ||
"type": "keyword" #统一按照 keyword来处理
}
}
}
]
}
}
查询索引库: GET /索引库名称
删除索引库: DELETE /索引库名称
比较常见的数据类型:
string类型,又分两种:
Numerical:数值类型,分两类
Date:日期类型
Object:对象,对象不便于搜索。因此ES会把对象数据扁平化处理再存储。
可以给一个已经存在的索引库添加映射关系,也可以创建索引库的同时直接指定映射关系。
索引库已经存在
PUT /索引库名/_mapping
{
"properties": {
"字段名1": {
"type": "类型",
"index": true,
"analyzer": "分词器"
},
"字段名2": {
"type": "类型",
"index": true,
"analyzer": "分词器"
},
...
}
}
索引库不存在
PUT /索引库名
{
"mappings":{
"properties": {
"字段名1": {
"type": "类型",
"index": true,
"analyzer": "分词器"
},
"字段名2": {
"type": "类型",
"index": true,
"analyzer": "分词器"
},
...
}
}
}
查看映射关系
GET /索引库名/_mapping
新增文档
通过POST请求,可以向一个已经存在的索引库中添加文档数据
ES中,文档都是以JSON格式提交的。
POST /{索引库名}/_doc 随机生成id
POST /{索引库名}/_doc/{id} 指定id
{
"key":"value"
}
查看文档
get加上条件进行查询
GET /{索引库名称}/_doc/{id}
修改文档
id对应文档存在,则修改
id对应文档不存在,则新增
PUT /{索引库名称}/_doc/{id}
删除文档
DELETE加上条件进行查询
DELETE /{索引库名称}/类型名/id值
批量导入数据
基本语法
GET /索引库名/_search
{
"query":{ # query代表一个查询对象,里面可以有不同的查询属性
"查询类型":{
"查询条件":"查询条件值"
}
}
}
查询类型
match_all:查询所有
match:分词查询
GET /heima/_search
{
"query":{
"match":{
"title":{"query":"三星手机","operator":"and"}#
"title":"三星手机"# 默认是 or
}
}
}
term:词条查询
fuzzy:模糊查询
GET /heima/_search
{
"query":{
"fuzzy": {
"title":{
"value": "飞毛腿",
"fuzziness": 2 #取值的范围 0 1 2
} #0 不允许出现偏差
} #1 允许错一次词
} #2 最大是2
}
range:范围查询
GET /heima/_search
{
"query":{
"range":{
"price":{
"gte":1000,
"lte":2000
}
}
}
}
bool:布尔查询
GET /heima/_search
{
"query":{
"bool":{
"should":[ # 或的条件
{第一个条件},
{第二个条件},
{第三个条件},
],
"must":[ # 必须满足的条件,会对文档进行得分的计算
],
"must_not":[ # 必须不满足的条件
]
}
}
}
查询条件
查询结果
{
"took" : 3, # 查询花费时间,单位是毫秒
"timed_out" : false, # 是否超时
"_shards" : { # 分片信息
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : { # 搜索结果总览对象
"total" : { # 搜索到的总条数
"value" : 10,
"relation" : "eq" # eq 代表结果是准确 gt
},
"max_score" : 1.0, # 所有结果中文档得分的最高分
"hits" : [ # 搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
{
"_index" : "heima", # 索引库
"_type" : "_doc", # 文档类型
"_id" : "2", # 文档id
"_score" : 1.0, # 文档得分,按照文档与查询的相关度倒序返回结果
"_source" : { # 文档的源数据
"title" : "三星 Note II (N7100) 云石白 联通3G手机",
"images" : "http://image.leyou.com/12479122.jpg",
"price" : 2599
}
},
。。。。。。
}
]
}
}
使用filter查询
GET /heima/_search
{
"query":{
"bool":{
.....
"filter":{ #对满足条件的结果进行一个过滤, 不会对文档进行得分的计算
"range":{
"price":{
"gte":1000,
"lte":5000
}
}
}
}
}
}
目标 sort_排序设置
GET /heima/_search
{
"query":{},
"sort":[
{
"排序字段":{
"order":"asc 和 desc"
}
}
]
}
分词字段不能参与排序
目标 from+size_分页设置
GET /heima/_search
{
"query":{},
"sort":[
{
"排序字段":{
"order":"asc 和 desc"
}
}
],
"from": # 从第几条记录开始
"size": # 查询的条数
}
目标 highlight_高亮设置
GET /heima/_search
{
"query": {
"match": {
"title": "联通手机"
}
},
"highlight": { #处理的结果放到了highlight字段中 , source字段的内容不受影响
"pre_tags": "",
"post_tags": "",
"fields": {
"title": {}
}
}
}
目标 aggs_聚合设置
GET /car/_search
{
"size":0,
"aggregations":{ # 聚合设置
"group_by_color":{ # 自定义名称
"terms":{ # 分桶类型 按照词条分桶
"field":"color" # 根据color字段分桶
},
"aggs":{
"avg_price":{
"avg":{
"field":"price"
}
},
"group_by_make":{
"terms":{
"field":"make"
}
}
}
}
}
}
引入es客户端依赖
elasticsearch-rest-high-level-client
准备实体类
创建es的java客户端对象
public class EsDemo01 {
RestHighLevelClient restHighLevelClient;
@Before
public void init(){
restHighLevelClient = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost",9200)
)
);
}
@After
public void close() throws IOException {
restHighLevelClient.close();
}
}
创建请求对象 :new CreateIndexRequest对象
调用CreateIndexRequest的settings方法,设置settings
调用CreateIndexRequest的mapping方法,设置mappings
通过client执行请求
CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
//这里的request是CreateIndexRequest对象。
解析返回结果
createIndexResponse.isAcknowledged()
创建文档请求
IndexRequest request = new XxxxRequest("user",...);//Get,Update,Delete
设置参数
request.xxx(...)
客户端执行请求
Xxxx xxxx = restHighLevelClient.Xxxx(request, RequestOptions.DEFAULT);
解析返回结果
xxxx.xxxx();
搜索功能API说明
1. new 一个SearchRequest 创建搜索的请求(request)
new 一个SearchSourceBuilder 对象来构建所有的查询参数JSON(builder)
builder.query(查询条件) : 设置查询条件
QueryBuilders : 构建查询条件的工具类
builder.sort: 排序设置
builder.from: 分页设置
builder.size:
builder.highlighter 高亮设置
builder.aggregations 聚合设置
request.source(builder) 设置查询参数
2. 客户端执行 . search方法,得到SearchResponse对象
3. 解析响应结果
SearchResponse.getHits 查询总结果
getTotalHits.value : 总记录数
getHits : 文档列表
遍历文档列表
getSource ==> user对象
getSourceAsString ==> 字符串格式
getHighlight结果
getAggregations : 获取聚合结果
部分查询代码
//查询全部
MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
//分词查询
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("note","唱歌javaee");
//词条查询
TermQueryBuilder gender = QueryBuilders.termQuery("gender", "0");
//范围查询
builder.query(QueryBuilders.rangeQuery("age").gte(25).lte(30));
//布尔查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("note","同学唱歌"));
boolQueryBuilder.must(QueryBuilders.rangeQuery("age").gt(20));
索引库查询-高亮处理
// 高亮条件的构建
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 哪个字段高亮?
highlightBuilder.field("note");
// 前置标签
highlightBuilder.preTags("");
// 后置标签
highlightBuilder.postTags("");
builder.highlighter(highlightBuilder);
索引库查询-聚合处理
// es 提供了 AggregationBuilders 可以快捷的创建聚合条件
// 参数: 自定义的聚合处理名称 参数: 分桶的字段
TermsAggregationBuilder termsBuilder = AggregationBuilders.terms("group_by_gender").field("gender");
MaxAggregationBuilder maxBuilder = AggregationBuilders.max("age_max").field("age");
// 设置分桶后 求最大值
termsBuilder.subAggregation(maxBuilder);
builder.aggregation(termsBuilder);
searchRequest.source(builder);
...
// 整个的聚合处理结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据自定义的聚合处理名称 得到处理结果
Terms termsResult = aggregations.get("group_by_gender");
// 得到该分桶下的 分桶处理结果
List<? extends Terms.Bucket> buckets = termsResult.getBuckets();
for (Terms.Bucket bucket : buckets) {
// 获取该分桶的 key
String keyAsString = bucket.getKeyAsString();
// 获取该分桶文档的数量
long docCount = bucket.getDocCount();
// 获取子聚合 整个的处理结果
Aggregations subAggregations = bucket.getAggregations();
Max maxResult = subAggregations.get("age_max");
double value = maxResult.getValue();
}
ES单机部署的缺陷
ES集群的概念
搭建Windows下集群
进入到ES的安装目录的bin目录下,打开3个CMD命令窗口
分别运行下面指令:
# node0指令:
elasticsearch.bat -E node.name=node0 -E cluster.name=elastic -E path.data=node0_data
# node1指令:
elasticsearch.bat -E node.name=node1 -E cluster.name=elastic -E path.data=node1_data
# node2指令:
elasticsearch.bat -E node.name=node2 -E cluster.name=elastic -E path.data=node2_data
-E代表环境参数
node.name代表节点的名称
cluster.name代表集群的名称
path.data代表索引库数据的存储目录
没有设置端口信息 es默认选用 9200 9300端口
如果端口被占用es会默认重试端口+1 直到端口未占用
Kibana连接集群
修改kibana安装目录下config目录中的kibana.yml
# elasticsearch.hosts: ["http://localhost:9200"]
改为
elasticsearch.hosts: ["http://localhost:9200","http://localhost:9201","http://localhost:9202"]
配置分片和副本信息
集群动态伸缩
实现微服务的一种技术架构,主要涉及的组件包括
创建父工程
服务提供者
依赖
application
server:
port: 9001
spring:
datasource:
url: jdbc:mysql://localhost:3306/javaee109?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
#注册
application:
name: user-service
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka/
instance:
ip-address: 127.0.0.1 # 指定一个ip地址
prefer-ip-address: true # 优先使用IP地址
lease-renewal-interval-in-seconds: 30 # 服务注册成功 每隔30s向eureka发送一次心跳
lease-expiration-duration-in-seconds: 90 # 服务没有发送心跳,将在90s后失效
启动类
@MapperScan("com.itcast.user.mapper")
创建实体类
创建映射类
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
User findById(@Param("id") Long id);
}
新建一个Service
新建一个Controller(对外查询的接口)
服务调用者
依赖
新建一个启动类
// 注册RestTemplate的对象到Spring的容器中
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
application
server:
port: 8080
#注册
spring:
application:
name: consumer # 应用名称
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
registry-fetch-interval-seconds: 30 # 每隔多长时间拉取一次服务
复制一个实体类
controller中服务拉取,再调用RestTemplate,远程访问user-service的服务接口
// 根据服务id(spring.application.name),获取服务实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
// 取出一个服务实例
ServiceInstance instance = instances.get(0);
// 从实例中获取host和port,组成url
String url = String.format("http://%s:%s/user/%s", instance.getHost(), instance.getPort(), id);
// 查询
User user = restTemplate.getForObject(url, User.class);
return user;
编写EurekaServer
依赖
启动类
@EnableEurekaServer // 声明这个应用是一个EurekaServer
编写配置
server:
port: 10086
spring:
application:
name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
client:
service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
defaultZone: http://127.0.0.1:10086/eureka
register-with-eureka: false # 不注册自己,集群时删掉
fetch-registry: false #不拉取服务,集群时删掉
步骤
-Dserver.port=10087
基于负载均衡的算法,从服务列表中选择指定的服务进行调用
默认: 轮询的算法
在consumer-service的启动类中,RestTemplate的配置方法上添加@LoadBalanced注解:
restTemplate.getForObject("http://user-service/user/1",User.class);
拦截器会获取 user-service
从服务列表中 根据负载均衡算法,选择对应的服务去执行
概念
作用
手段
步骤
引入依赖
spring-cloud-starter-netflix-hystrix
开启熔断器注解
@SpringCloudApplication
controller方法开启熔断保护
@HystrixCommand(fallbackMethod = "xxxFallback")
熔断配置
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 2000 # 超时时间
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 触发熔断的最小请求次数,默认20
熔断触发的工作原理
概念
步骤
引入feign依赖spring-cloud-starter-openfeign
开启feign的注解@EnableFeignClients
,在启动类上
定义feign的接口
@FeignClient(value = "user-service",// 要调用的服务名称
fallback = UserClientFallBack.class,//熔断
configuration = FeignConfig.class) //日志
public interface UserClient {
// fei会根据我们的配置 生成要调用的路径
// GET http://user-service/user/{id}
// 根据拼接的地址进行调用
// feign 内部整合了Ribbon
// GET http://127.0.0.1:9001/user/{id}
@GetMapping("/user/{id}")
String findById(@PathVariable Long id);
}
具体调用
userClient.findById(id)
相关配置
#feign内置了ribbon, 直接提供了负载均衡的效果
ribbon:
ConnectTimeout: 1000 # 连接超时时长
ReadTimeout: 2000 # 数据通信超时时长
MaxAutoRetries: 0 # 当前服务器的重试次数
MaxAutoRetriesNextServer: 0 # 重试多少次服务
OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
#feign的内部,内置了Hystrix的功能,需要手动开启
feign:
hystrix:
enabled: true # 开启了熔断功能
#更改日志的打印级别
logging:
level:
com.itcast.consume: debug
服务熔断
日志控制
Configuration
public class FeignConfig {
@Bean
Logger.Level level(){
// - NONE:不记录任何日志信息,这是默认值。
// - BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
// - HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
// - FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
return Logger.Level.FULL;
}
}
概念
核心概念
AbstractGatewayFilterFactory
步骤
引入网关依赖spring-cloud-starter-gateway
引入熔断器依赖spring-cloud-starter-netflix-hystrix
网关的配置
server:
port: 10010
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: user-service # 当前路由的唯一标识
uri: lb://user-service # 路由的目标微服务,lb:代表负载均衡,user-service:代表服务id
predicates: # 断言
- Path=/user/** # 按照路径匹配的规则
filters:
- AddRequestHeader=info, java is best!
default-filters: # 默认过滤项
- StripPrefix=1 # 配置后 忽略第一个路径 /user-service/user/1 ==> /user/1
- name: Hystrix # 指定过滤工厂名称(可以是任意过滤工厂类型)
args: # 指定过滤的参数
name: fallbackcmd # hystrix的指令名
fallbackUri: forward:/fallbackTest # 失败后的跳转路径
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 1000 # 失败超时时长
自定义全局过滤器
全局过滤器Global Filter 与局部的GatewayFilter会在运行时合并到一个过滤器链中
根据org.springframework.core.Ordered
来排序后执行,顺序可以通过getOrder()
方法或者@Order
注解来指定。
一个过滤器的执行包括"pre"
和"post"
两个过程,
/**
* 全局过滤器
* @作者 itcast
* @创建日期 2020/9/17 14:18
**/
@Component // 加上组件注解,不加不生效
public class LoginFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// exchange: 封装了 请求对象 和 响应对象的信息
ServerHttpRequest request = exchange.getRequest(); // 获取请求对象
// 凭证信息
String token = request.getQueryParams().toSingleValueMap().get("access-token");//
// chain : 过滤器链
if(StringUtils.equals(token,"admin")){
// 放行
return chain.filter(exchange);
}else {
// 终止请求
ServerHttpResponse response = exchange.getResponse();
// 设置没有权限的状态
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 设置请求终止
return response.setComplete();
}
}
}
@Configuration
@Slf4j
public class GlobalConfig {
@Bean
@Order(-1)
GlobalFilter filter1(){
return (exchange,chain)->{
// 写前置的处理代码
log.info("filter1 的pre方法");
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("filter1 的post方法");
}));
};
}
}
概念
创建配置中心步骤
在gitee中创建仓库
创建config-server 工程 并引入依赖
spring-cloud-starter-netflix-eureka-client
spring-cloud-config-server
配置中心启动类
@EnableConfigServer
配置文件内容
server:
port: 12580
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/sh_itheima/cloud_config.git
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
微服务获取配置步骤
引config依赖spring-cloud-starter-config
创建一个boostrap.yml配置文件,将在服务启动时被加载
在这个配置文件中需要配置 eureka的地址 和 config配置中心的配置
server:
port: 9101
spring:
application:
name: consume-service
cloud:
config:
discovery:
service-id: config-server # 配置中心的名称
enabled: true # 开启从注册中心 获取 配置中心的地址
label: master # 分支的名称
name: consume # application: 应用名称
profile: dev # 环境名称
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
#开启 暴露 refresh的方法
management:
endpoints:
web:
exposure:
include: "*" # 暴露refresh接口
提供配置内容读取类,用于读取配置
提供方法,访问配置
微服务动态刷新最新配置
spring-boot-starter-actuato
概念
config配置
引入bus依赖spring-cloud-starter-bus-amqp
端点spring-boot-starter-actuator
config配置中添加 Rabbitmq依赖 及 暴露 刷新接口
server:
port: 12580
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/taft31/config-test.git # 需要拉取配置的Git仓库地址
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/ # eureka地址
management:
endpoints:
web:
exposure:
include: "*"
消费者服务
引入bus依赖spring-cloud-starter-bus-amqp
消费者配置添加 mq信息,在spring下
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
配置webhooks实现配置更新动态自动刷新
spring-cloud-config-monitor
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-klz9lOcn-1605669045472)(assets/微服务相关.png)]
容器和虚拟机
docker
安装
# 查看linux内核版本
uname -r
# 查看是否已经安装过
yum list installed | grep docker
# 删除之前安装的docker
yum remove -y docker-ce docker-ce-cli containerd.io
# 删除之前生成的docker数据
rm -rf /var/lib/docker
# 安装依赖工具
yum install -y yum-utils device-mapper-persistent-data lvm2
# 设置阿里云镜像
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 必须先更新yum源,否则找不到docker-ce,只能找到docker
yum makecache fast
# 查看仓库的可安装版本
yum list docker-ce --showduplicates|sort -r
# 安装64位的版本
yum install -y docker-ce.x86_64
# 检查是否安装成功
yum list installed | grep docker
# 或 查看版本信息
docker -v
docker version
# 查询docker 详细信息
docker info
# 查看命令
docker --help
# 查看指定命令相关选项信息
docker 命令 --help
启动
# 启动docker服务
systemctl start docker
# 检查docker服务状态
systemctl status docker
# 重启
systemctl restart docker
# 停止
systemctl stop docker
# 开启自动启动
systemctl enable docker
配置docker的镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://efiewyyy.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
#查看镜像
docker images
docker images -q
#搜索镜像
#在官网网站上查找镜像: hub.docker.com
docker search 仓库名称
#拉取镜像
docker pull [注册中心的地址/]仓库名称[:版本号]
#[强制]删除镜像
docker rmi [-f] 镜像仓库名称or镜像ID[:版本号]
docker rmi -f `docker images -q`
# 查看正在运行的容器
docker ps
docker ps -a
#创建并运行交互式容器,会直接进入到容器,退出后容器会停止
docker run --name=mycentos -it centos:latest [/bin/]bash
#创建并运行守护式的容器,退出容器后容器依旧在后台运行【常用】
docker run --name=mycentos -id centos
docker exec -it mycentos [/bin/]bash
# 停止正在运行容器
docker stop 容器名称(容器ID)
# 启动
docker start 容器名称(容器ID)
docker restart 容器名称(容器ID)
# 暂停
docker pause 容器名称(容器ID)
# 恢复
docker unpause 容器名称(容器ID)
# 查看信息
docker logs -f 容器名称
#文件拷贝
docker cp ./test.txt mycentos:/
docker cp mycentos:/test.txt ./
#目录挂载,宿主机目录:容器目录,宿主机目录必须是以/或~开头,设置容器只读
docker run -id --name=mycentos -v /root/myvolumn:/myvolumn[:ro] centos
#删除容器,需要先停止运行,或强制删除
docker rm 容器的名称or容器的id
#docker伴随容器的启动自动启动
docker run -id --name=mycentos centos --restart always
docker update ly-mysql --restart=always
mysql的部署
docker run -d --name=mysql -p 3306:3006 -e MYSQL_ROOT_PASSWORD=root mysql:5.7
redis部署
docker run -d --name=myredis -p 6379:6379 redis
tomcat的部署
docker run -d --name=mytomcat -p 9001:8080 -v /root/mytomcat:/usr/local/tomcat/webapps tomcat
nginx部署
docker run -d --name=mynginx -p 9002:80 nginx
elasticsearch部署
# -e中的内容的设置是单节点启动
docker run -d --name=myes -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.4.2
# 安装IK分词器:
docker exec -it myes /bin/bash
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
# 下载完毕后重启
kibana部署
# Kibana和ES安装到同一个docker服务下
# --link后面的myes 是es容器的名称
docker run -d --name=my_kibana -p 5601:5601 --link myes:elasticsearch kibana:7.4.2
rabbitmq部署
docker pull rabbitmq:3.7.7-management
docker run -d --name myrabbit -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.7.7-management
RABBITMQ_DEFAULT_USER: 设置默认的用户名
RABBITMQ_DEFAULT_PASS: 设置默认的密码
管理后台: http://ip:15672 通过 admin admin登录
// 在容器上做更改, 可以将容器提交成新的镜像
docker commit 容器的名称 镜像的名称:版本
// 将镜像打包成压缩包
docker save -o 压缩包的名称.tar 镜像的名称:版本
// 将压缩包加载到docker中
docker load -i 压缩包的名称.tar
Dockerfile是一个脚本文件,在里面定义构建镜像的命令和参数通过这个文件可以快速构建镜像
使用Dockerfile 在centos基础镜像上安装JDK在打包成新镜像
#1. mkdir /root/centosjdk8
cd /root/centosjdk8
#2. 将jdk1.8上传到docker服务器上的/root/centosjdk8文件夹下
#3. 创建Dockerfile脚本文件 用于构建镜像
vi Dockerfile
#4. 编写脚本文件内容
# 基于centos镜像
FROM centos
# 声明作者
MAINTAINER mrchen
# 创建一个文件夹
RUN mkdir /usr/local/java
# 拷贝压缩包
ADD ./jdk-8u161-linux-x64.tar.gz /usr/local/java/
# 设置工作目录
WORKDIR /usr
# 配置环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_161
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
# 5. 根据脚本构建镜像
构建 新镜像名称 dockerfile脚本所在目录(就是那个点)
docker build -t centosjdk8 .
搭建私人注册中心
#拉取 注册中心 镜像
docker pull registry
#启动私人的注册中心
docker run -d --name=myregistry -p 5000:5000 registry
#访问注册中心
http://192.168.12.134:5000/v2/_catalog
上传镜像到私有注册中心
#设置信任注册中心的地址
#修改daemon.json文件添加信任列表
vim /etc/docker/daemon.json
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
,"insecure-registries":["192.168.12.134:5000"]
}
#重启docker
systemctl daemon-reload
systemctl restart docker
#注册中心地址/仓库名称:版本
docker tag centosjdk8 192.168.12.134:5000/centosjdk8
docker push 192.168.12.134:5000/centosjdk8
基于docker部署微服务项目
# 编辑该文件
vi /lib/systemd/system/docker.service
# 将原来的 ExecStart注释掉 加#号注释
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock -H tcp://0.0.0.0:7654
# 保存后重启docker
systemctl daemon-reload
systemctl restart docker
# 检查能否远程访问 如果访问不通注意防火墙
http://自己的IP:2375/version
父工程中引入docker-maven-plugin插件
在要部署的工程上打开终端窗口
mvn clean package -DskipTests docker:build -DpushImage
命令依次作用:
清除target 打包 忽略测试 docker构建镜像 推送到注册中心
概念
所涉及相关工具
分类
高并发对技术的要求
架构方式
业务架构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a3u6GOD5-1605669045474)(assets/image-20200107161255077.png)]
技术架构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDECzlAr-1605669045475)(assets/image-20200414113210392.png)]
主要技术
前端技术包括:
服务端技术包括:
概念
资源导入
npm run serve
单文件组件
.vue
文件是vue组件的特殊形式
把html、css、js代码做了分离
页面菜单
组件路由
配置了一些路由的path路径和组件的映射关系
组件默认的路径前缀是src/views
route("/item/category",'/item/Category',"Category")
//代表的意思是:
- path:/item/category
- 组件:./src/views下的/item/Category文件
访问path:/item/category,会被路由到:./src/views/item/Category组件
统一环境
项目结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0d6RCzl9-1605669045477)(assets/image-20200108103538061.png)]
父工程
注册中心
网关
ResponseEntity是一个Spring提供的,用于封装响应结果的实体类。可以自定义响应状态码、响应头、响应头。
ResponseEntity.status(HttpStatus.CREATED)
:用于指定这次响应的状态码,HttpStatus枚举中定义了常见的返回状态码。另外,ResponseEntity提供了几个便捷方法,代表常用状态码:
ResponseEntity.ok()
:代表200ResponseEntity.noContent()
:代表204.body(result)
:用于指定这次响应的返回值结果,就是响应体
步骤
定义异常处理器
创建一个类@ControllerAdvice
@Controller
的类创建一个方法@ExceptionHandler(RuntimeException.class)
@ControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler(RuntimeException.class)//如果有,写自定义的异常类
public ResponseEntity<String> handleLyException(RuntimeException e) {
return ResponseEntity.status(e.getStatus()).body(e.getMessage());
}
}
需要使用异常处理器的模块引入依赖
在启动类的注解上添加扫描包信息,当前的和新加的都要扫描
@SpringBootApplication(scanBasePackages = {"com.leyou.item", "com.leyou.common.advice"})
@Slf4j
@Aspect //把当前类标识为一个切面供容器读取
@Component
public class CommonLogAdvice {
@Around("within(@org.springframework.stereotype.Service *)")//可以用||
public Object handleExceptionLog(ProceedingJoinPoint jp) throws Throwable {
try {
// 记录方法进入日志
log.debug("{}方法准备调用,参数: {}", jp.getSignature(), Arrays.toString(jp.getArgs()));
long a = System.currentTimeMillis();
// 调用切点方法
Object result = jp.proceed();
// 记录方法结束日志
log.debug("{}方法调用成功,执行耗时{}", jp.getSignature(), System.currentTimeMillis() - a);
return result;
} catch (Throwable throwable) {
log.error("{}方法执行失败,原因:{}", jp.getSignature(), throwable.getMessage(), throwable);
// 判断异常是否是LyException
if(throwable instanceof LyException){
// 如果是,不处理,直接抛
throw throwable;
}else{
// 如果不是,转为LyException
throw new LyException(500, throwable);
}
}
}
}
CMD命令
#启动:
start nginx.exe
#停止:
nginx.exe -s stop
#重新加载:
nginx.exe -s reload
配置nginx反向代理
打开nginx.conf
文件,然后在文件的最后一行的}
上面引入配置:
include vhost/*.conf;
vhost/leyou.conf
# 负载均衡配置,默认是轮询
upstream leyou-manage{
server 127.0.0.1:9001; # 节点信息
#server 127.0.0.1:9002; # 节点信息
#server 127.0.0.1:9003; # 节点信息
#server 127.0.0.1:9004; # 节点信息
}
# 加权轮询
upstream leyou-manage{
server 127.0.0.1:9001 weight=1; # 节点信息
server 127.0.0.1:9002 weight=2; # 节点信息
}
#IP哈希
upstream leyou-manage {
ip_hash;
server 127.0.0.1:9001; # 节点信息
server 127.0.0.1:9002; # 节点信息
}
#最少连接
upstream leyou-manage{
least_conn;
server 127.0.0.1:9001; # 节点信息
server 127.0.0.1:9002; # 节点信息
}
server { #定义一个监听服务配置
listen 80; #监听的端口
server_name manage.leyou.com; #监听的域名,端口一致,域名不同也不会处理
location / { #匹配当前域名下的哪个路径。/ 代表的是一切路径
proxy_pass http://leyou-manage; #监听并匹配成功后,反向代理的目的地,可以指向某个ip和port
proxy_connect_timeout 600; #反向代理后的连接超时时间
proxy_read_timeout 5000; #反向代理后的读取超时时间
}
}
...
介绍
注解
官方文档:https://mp.baomidou.com/guide/annotation.html
@TableName
声明当前类关联的表名称@TableId
主键注解@TableField
字段注解(非主键)新增
// 插入一条记录
int insert(T entity);
删除
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
修改
// 根据 whereEntity 条件,更新记录
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);
查询单个
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
查询集合
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
分页查询
@Configuration
public class MybatisConfig {
/**
* 注册mybatis plus的分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
public void testPageQuery(){
// 分页条件
Page<User> page = new Page<>();
// 当前页
page.setCurrent(1);
// 每页大小
page.setSize(3);
// 分页查询,结果会放到Page中,因此无需返回
userMapper.selectPage(page, null);
}
新增
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
保存更新
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
删除
// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
修改
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereEntity 条件,更新记录
boolean update(T entity, Wrapper<T> updateWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
查询单个
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
查询多个
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
分页查询
// 无条件翻页查询
IPage<T> page(IPage<T> page);
// 翻页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件翻页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 翻页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
查询数量
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
链式查询
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();
环境搭建
引入依赖ly-item-service
mybatis-plus-boot-starter
全局配置ly-item-service
mybatis-plus:
type-aliases-package: com.leyou.item.entity # 别名扫描包
mapper-locations: classpath*:/mappers/*.xml # mapper的xml文件地址
global-config:
db-config:
id-type: auto # 全局主键策略,默认为自增长
update-strategy: not_null # 更新时,只更新非null字段
insert-strategy: not_null # 新增时,只新增非null字段
启动类
@MapperScan
注解@SpringBootApplication
注解开启分页插件ly-item-service/config
@Configuration
public class MybatisConfig {
/**
* 注册mybatis plus的分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(500);
return paginationInterceptor;
}
修改日志advice的切入点语法,注意引入依赖
@Around("within(@org.springframework.stereotype.Service *) || within(com.baomidou.mybatisplus.extension.service.IService+)")
基本代码
ly-item-service
中添加CategoryMapper
接口BaseMapper<实体类>
ly-item-service
中添加CategoryService
接口IService<实体类>
@service
注解ServiceImpl
ly-item-service
中添加CategoryController
CategoryService
在开发中,根据对象作用的领域不同,会把实体类划分成这样:
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class CategoryDTO extends BaseDTO {
private Long id;
private String name;
private Long parentId;
private Boolean isParent;
private Integer sort;
public CategoryDTO(BaseEntity entity) {
super(entity);
}
/**
* 为了方便实现DTO与PO转换,在DTO中定义了工具方法
* BaseDTO中定义了通用的单个对象的转换方法
* CategoryDTO中定义了从PO集合转为DTO集合的功能
*/
public static <T extends BaseEntity> List<CategoryDTO> convertEntityList(Collection<T> list){
if(list == null){
return Collections.emptyList();
}
return list.stream().map(CategoryDTO::new).collect(Collectors.toList());
}
}
XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。SpringCloudGateway的CORS
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 是否将当前cors配置加入到SimpleUrlHandlerMapping中,解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://manage.leyou.com"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
搭建工程
@TableId(type = IdType.INPUT)
代表主键采用自己填写而不是自增长。根据品牌id查询品牌
根据品牌id集合查询品牌集合
分页查询品牌
分页的DTO类,其中包含一个list列表
@Override
public PageDTO<BrandDTO> queryBrandOfPages(Integer page, Integer rows, String key) {
Page<Brand> brandPage = new Page<>(page, rows);
//select * from tb_brand [where name lick %key% or letter = key] limit 123,456
this.page(brandPage, new QueryWrapper<Brand>()
.like(null!=key,"name",key)
.or()
.eq(null!=key,"letter",key)
);
return new PageDTO(brandPage.getTotal(),brandPage.getPages(),BrandDTO.convertEntityList(brandPage.getRecords()));
}
根据分类id查询品牌
利用中间表查询
List<Long> brandIds =categoryBrandService.query()
.eq("category_id", cid)
.list()
.stream()
.map(CategoryBrand::getBrandId)
.collect(Collectors.toList());
通过注解关联查询
@Select("SELECT b.id, b.name, b.letter, b.image FROM tb_category_brand cb INNER JOIN tb_brand b ON b.id = cb.brand_id WHERE cb.category_id = #{cid}")
List<Brand> queryByCategoryId(@Param("cid") Long cid);
======================================================
@Override
public List<BrandDTO> queryBrandByCategory(Long id) {
List<Brand> list = getBaseMapper().queryByCategoryId(id);
return BrandDTO.convertEntityList(list);
}
新增品牌
@Transactional
开启事务更新品牌
中间表先删除,再新增
@RequestMapping(method = {RequestMethod.POST,RequestMethod.PUT})
根据ID删除品牌
基本概念
存储空间(Bucket)
导入依赖
aliyun-sdk-oss
配置
网关配置
routes:
- id: auth-service # 授权服务
uri: lb://auth-service
predicates:
- Path=/auth/**
微服务配置
ly:
oss:
accessKeyId: LTAI4FhtSrGpB2mq4N36XbGb
accessKeySecret: OEavFEiAyGm7OsGYff5TClHx88KJ28
host: http://ly-images.oss-cn-shanghai.aliyuncs.com # 访问oss的bucket的域名
endpoint: oss-cn-shanghai.aliyuncs.com # 你选择的oss服务器的地址
dir: "heima01" # 保存到bucket的某个子目录
expireTime: 1200000 # 过期时间,单位是ms
maxFileSize: 5242880 #文件大小限制,这里是5M
实体类
@ConfigurationProperties("ly.oss")
@Component
配置类
controller
service
serviceImpl
SPU和SKU
特殊数据类型
自动补全和提示
ES的推荐功能(Suggester)包含三种不同方式
为了提高suggester的速度,数据类型是completion类型。
查询时要指定要在哪个completion类型的字段上进行查询
POST articles/_search
{
"suggest": { #代表接下来的查询是一个suggest类型的查询
"article-suggester": { #这次查询的名称,自定义
"prefix": "el ", #用来补全的词语前缀
"completion": { #代表是completion类型的suggest,其它类型还有:Term、Phrase
"field": "suggestion", #要查询的字段
"size": 10
}
}
}
}
拼音搜索
引入依赖
elasticsearch-rest-high-level-client
log4j2.xml配置
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
Console>
Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
Root>
Loggers>
Configuration>
准备实体类
创建库和映射
创建索引库
创建ES的客户端
client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.206.99", 9200, "http")
)
);
删除以前的Goods索引库
创建CreateIndexRequest对象,并指定索引库名称
指定settings配置和mapping配置
发起请求,得到响应
导入文档数据
基本查询
Suggest查询
ES也提供异步调用的API,利用回调函数来处理执行结果。
其底层是异步的Http请求,并且将执行结果用Future
来封装。
public void testAsyncAddDocument() throws InterruptedException {
// 准备文档
Goods goods = new Goods(5L, "松下电吹风", "松下电吹风 网红电吹风", 1599L);
// 创建请求(创建请求)
IndexRequest request = new IndexRequest("goods")
.id(goods.getId().toString())
.source(JSON.toJSONString(goods), XContentType.JSON);
// 创建请求(删除请求)
DeleteRequest request = new DeleteRequest("goods", "5");
// 创建请求(查询请求)
GetRequest request = new GetRequest("goods", "1");
// 执行请求,第三个参数是回调处理
client.xxxAsync(request, RequestOptions.DEFAULT, new ActionListener<IndexResponse>() {
/**
* 执行成功时的回调,参数是IndexResponse结果
* @param indexResponse 执行结果
*/
@Override
public void onResponse(IndexResponse indexResponse) {
System.out.println("我是成功的回调!" + indexResponse);
}
/**
* 执行失败时的回调,参数是异常信息
* @param e 异常信息
*/
@Override
public void onFailure(Exception e) {
System.out.println("我是失败的回调!");
e.printStackTrace();
}
});
System.out.println("我的异步方法调用完成~~");
// 因为我们的程序结束会立即停止,接收不到回调结果,这里我们休眠一下,等待下回调结果
Thread.sleep(2000L);
}
WebFlux
响应式
Reactor Project
Flux 和 Mono
使用spring的initialize
选择依赖
Mono
Mono
Flux
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> getUserStream() {
log.info("stream 开始执行");
Flux<User> flux = getUserWithFlux();
log.info("stream 执行完毕");
return flux;
}
静态方法
generate方法
generate()
方法通过同步和逐一的方式来产生 Flux 序列。create方法
与Flux类似的API
独特的方法
SpringBoot的starter结构一般如下:
MyRepository
的实现类。Repository
接口的接口,例如MyRepository
BeanDefinition
BeanDefinition
注册到Spring容器RepositoryScanner
安装到本地仓库
IDEA通过maven窗口中的install命令
命令行导入
mvn source:jar install -Dmaven.test.skip=true
引入依赖
配置地址
编写实体类
@Index("goods")
:声明实体类相关的索引库名称,如果没指定,会以类名首字母小写后做索引库名称@Id
:实体类中的id字段的类型,名称可以不叫id,只要加注解就可以准备客户端Repository
文档的CRUD
//新增数据
boolean save(T t);
boolean save(Iterable<T> iterable);
//文档删除
boolean deleteById(ID id);
//根据Id查询
Mono<T> queryById(ID id);
//根据条件查询
@Test
public void testQuery() throws InterruptedException {
// 搜索条件的构建器
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 1.查询条件
sourceBuilder.query(QueryBuilders.matchQuery("title", "红米手机"));
// 2.分页条件
sourceBuilder.from(0);
sourceBuilder.size(20);
// 3.高亮条件
sourceBuilder.highlighter(new HighlightBuilder().field("title"));
System.out.println("开始查询。。。");
Mono<PageInfo<Goods>> mono = repository.queryBySourceBuilderForPageHighlight(sourceBuilder);
mono.subscribe(info -> {
long total = info.getTotal();
System.out.println("total = " + total);
List<Goods> list = info.getContent();
list.forEach(System.out::println);
});
System.out.println("结束查询。。。");
Thread.sleep(2000);
}
//自动补全
@Test
public void testSuggest() throws InterruptedException {
log.info("开始查询");
Mono<List<String>> mono = repository.suggestBySingleField("name", "s");
mono.subscribe(list -> list.forEach(System.out::println));
log.info("结束查询");
Thread.sleep(2000);
}
@FeignClient("item-service")
@EnableFeignClients(basePackages = "...")
@SpringBootApplication(scanBasePackages = {...})
流程分析
商品微服务发送消息
引入依赖
spring-boot-starter-amqp
配置文件
spring:
rabbitmq:
host: ly-mq
username: leyou
password: 123321
virtual-host: /leyou
template: #有关AmqpTemplate的配置
retry: #失败重试
enabled: true #开启失败重试
initial-interval: 10000ms #第一次重试的间隔时长
max-interval: 80000ms #最长重试间隔,超过这个间隔将不再重试
multiplier: 2 #下次重试间隔的倍数
publisher-confirms: true #缺省的交换机名称,此处配置后,发送消息如果不指定交换机就会使用这个
搜索服务接收消息
传统模式
静态化页面
优点:
缺陷:
动态模板,静态化数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XjrhF3qv-1605669045478)(assets/image-20200310181707858.png)]
需要的服务
语法入门Lua菜鸟教程
变量声明
-- 定义数字
local a = 123
-- 定义字符串
local b = "hello world"
– 定义数组
local c = {“hello”, “world”, “lua”}
– 定义table
local d = {
name = “jack”,
age = 21
}
2. 打印结果
```lua
print('hello world')
条件控制
if( 布尔表达式 1)
then
--[ 在布尔表达式 1 为 true 时执行该语句块 --]
elseif( 布尔表达式 2)
then
--[ 在布尔表达式 2 为 true 时执行该语句块 --]
else
--[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end
循环语句
for i=0, 10, 1 do
print(i)
end
--打印数组a的所有值
local a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
-- 遍历时,i是角标,v是元素。Lua中数组角标从1开始
-- 定义table
local b = {
name = "jack",
age = 21
}
for k, v in pairs(b) do
print(k, v)
end
-- 遍历时,k是key,v是值。
介绍
安装OpenResty
基本配置
为了不影响OpenResty安装目录的结构,我们在新的目录中来启动和配置
nginx -p `pwd` -c conf/nginx.conf #使用当前目录下的配置启动
nginx -p `pwd` -c conf/nginx.conf -s reload #重新加载配置
nginx -p `pwd` -c conf/nginx.conf -s stop #停止
配置文件:nginx.conf
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
lua_package_path "/usr/local/openresty/lualib/?.lua;;"; #lua 模块
lua_package_cpath "/usr/local/openresty/lualib/?.so;;"; #c模块
lua_shared_dict item_local_cache 50m; #共享全局变量,在所有worker间共享
default_type text/html; # 默认响应类型是html
include lua.conf; # 引入一个lua.conf文件
}
配置文件:lua.conf
server {
listen 80;
location / {
# 响应数据由 lua/test.lua这个文件来指定
content_by_lua_file lua/test.lua;
}
# 采用正则表达式映射路径,有两个(\d+),分别是第1组、第2组正则
location ~ /lua_request/(\d+)/(\d+) {
# $1代表获取第1组正则捕获的内容,set $a $1代表把$1的值赋值给$a这个变量
set $a $1;
# $2代表获取第2组正则捕获的内容,set $b $2代表把$2的值赋值给$b这个变量
set $b $2;
#nginx内容处理
content_by_lua_file lua/test_request.lua;
}
}
lua脚本:test.lua
ngx.say("hello, world
")
lua脚本:test_request.lua
-- 定义一个函数,打印table数据,,-- 获取路径占位符中通过正则得到的参数,,-- 获取请求url参数
function sayTables(val)
for k,v in pairs(val) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ", "), "
")
else
ngx.say(k, " : ", v, "
")
end
end
end
ngx.say('')
ngx.say('')
ngx.say(' ')
ngx.say(" -----请求路径占位符参数-------
");
ngx.say("")
ngx.say("ngx.var.a : ", ngx.var.a, "
")
ngx.say("ngx.var.b : ", ngx.var.b, "
")
ngx.say("ngx.var[1] : ", ngx.var[1], "
")
ngx.say("ngx.var[2] : ", ngx.var[2], "
")
ngx.say("
")
ngx.say(" -----请求url参数-------
");
ngx.say("")
local params = ngx.req.get_uri_args()
sayTables(params)
ngx.say("
")
return ngx.exit(200)
lua.conf
中定义一个location
映射lua
文件,编写脚本
xxx.html
location
#用来处理内部请求,然后反向代理到百度
location ~ /baidu/(.*) {
# 重写路径,去掉路径前面的 /baidu
rewrite /baidu(/.*) $1 break;
# 禁止响应体压缩
proxy_set_header Accept-Encoding '';
# 反向代理到百度
proxy_pass https://www.baidu.com;
}
#测试接口
location ~ /lua_http/(.*) {
# 关闭lua代码缓存
lua_code_cache off;
# 指定请求交给lua/test_http.lua脚本来处理
content_by_lua_file lua/test_http.lua;
}
test_http.lua
-- 向 /baidu 这个location发请求,并且携带请求参数
local resp = ngx.location.capture("/baidu/s?wd="..ngx.var[1], {
method = ngx.HTTP_GET
})
-- 查询失败的处理
if not resp then
ngx.say("request error");
end
-- 查询成功的处理,这里是打印响应体
ngx.say(resp.body)
流程
ly-page
微服务读取数据
步骤
ly-page
的http工具优化
介绍
设置主从同步
修改mysql配置文件,设置binary log
设置账号权限
安装canal
docker inspect 容器id
查看容器地址docker run -p 11111:11111 --name canal \
-e canal.destinations=test \
-e canal.instance.master.address=172.17.0.2:3306 \ #数据库地址和端口
-e canal.instance.dbUsername=canal \
-e canal.instance.dbPassword=canal \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false \
-e canal.instance.filter.regex=heima.tb_spu,heima.tb_sku,heima.tb_spu_detail,heima.tb_category,heima.tb_brand,heima.tb_spec_param \
--network host \
-d canal/canal-server
编写canal客户端
引入依赖
canal-spring-boot-starter
配置文件
canal:
destination: test
server: ly-canal:11111 # canal地址
添加Redis操作方法
编写监听器
EntryHandler
implements EntryHandler
数据,-- 获取路径占位符中通过正则得到的参数,-- 获取请求url参数
function sayTables(val)
for k,v in pairs(val) do
if type(v) == “table” then
ngx.say(k, " : ", table.concat(v, ", "), “
”)
else
ngx.say(k, " : ", v, “
”)
end
end
end
ngx.say(’
ngx.say("
ngx.say("
return ngx.exit(200)
## 14.4OpenResty模板渲染模块
- 安装模板渲染模块
- 定义模板位置
- 创建上面定义的目录
- 在`lua.conf`中定义一个`location`映射
- 新建`lua`文件,编写脚本
- template模块
- Redis模块
- 其他模块
- 新建模板文件:`xxx.html`
## 14.5OpenResty内部请求代理capture
- location
```nginx
#用来处理内部请求,然后反向代理到百度
location ~ /baidu/(.*) {
# 重写路径,去掉路径前面的 /baidu
rewrite /baidu(/.*) $1 break;
# 禁止响应体压缩
proxy_set_header Accept-Encoding '';
# 反向代理到百度
proxy_pass https://www.baidu.com;
}
#测试接口
location ~ /lua_http/(.*) {
# 关闭lua代码缓存
lua_code_cache off;
# 指定请求交给lua/test_http.lua脚本来处理
content_by_lua_file lua/test_http.lua;
}
test_http.lua
-- 向 /baidu 这个location发请求,并且携带请求参数
local resp = ngx.location.capture("/baidu/s?wd="..ngx.var[1], {
method = ngx.HTTP_GET
})
-- 查询失败的处理
if not resp then
ngx.say("request error");
end
-- 查询成功的处理,这里是打印响应体
ngx.say(resp.body)
流程
ly-page
微服务读取数据
步骤
ly-page
的http工具优化
介绍
设置主从同步
修改mysql配置文件,设置binary log
设置账号权限
安装canal
docker inspect 容器id
查看容器地址docker run -p 11111:11111 --name canal \
-e canal.destinations=test \
-e canal.instance.master.address=172.17.0.2:3306 \ #数据库地址和端口
-e canal.instance.dbUsername=canal \
-e canal.instance.dbPassword=canal \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false \
-e canal.instance.filter.regex=heima.tb_spu,heima.tb_sku,heima.tb_spu_detail,heima.tb_category,heima.tb_brand,heima.tb_spec_param \
--network host \
-d canal/canal-server
编写canal客户端
引入依赖
canal-spring-boot-starter
配置文件
canal:
destination: test
server: ly-canal:11111 # canal地址
添加Redis操作方法
编写监听器
EntryHandler
implements EntryHandler