启动项
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication{
public static void main(String[] args){
SpringApplication.run(OrderApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
service 中的 queryOrderById
@Service
public class OrderService{
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId){
Order order=orderMapper.findById(orderId);
String url ="http://localhost:8081/user/"+order.getUserId();
User user=restTemplate.getForObject(url,User.classs);
order.setUser(user);
return order;
}
}
Feign
@LoadBalanced 是 Spring Cloud 中的一个注解,用于在使用 RestTemplate 进行 HTTP 请求时实现负载均衡。 RestTemplate 服务远程调用 发送http请求
@Bean
public RestTemplate xxrestTemplate(){
return new RestTemplate();
}
@Autowired
private RestTemplate kkrestTemplate;
String xxurl = "http://localhost:8081/user/"+order.getUserId();
User gguser = kkrestTemplate.getForObject(xxurl, User.class);//第一个参数是路径,第二个参数是你想要拿到什么类型的数据
order.setUser(gguser);//把向用户项目拿到的数据封装到这个订单项目
Feign声明式客户端 RestTemplate 发起远程调用的代码
String url="http://userservice/user/"+order.getuserId();
User user=restTemplate.getForObject(url,User.class);
声明式 编写Feign客户端
类似操作数据库的mapper 例子
@FeignClient("userservice")
public interface UserClient{
GetMapping("/user/{id}")
User findById(@PathVariablke("id") Long id);
}
引入使用
引入依赖
<dependency>
<grouId>org.springframework.cloud<grouId>
<artifactId>spring-cloud-start-openfeign<artifactId>
</dependency>
添加注解
在启动类上标注@EnableFeignClient
配置
yml配置
feign:
client:
config:
default:
loggerLevel: FULL
java代码配置 新建一个类
public class FeignClientConfiguration{
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}
如果是全局配置,放到@EnableFeignClients
注解中
@EnableFeignClients(defaultConfigutation=FeignClietnConfiguration.class)
如果是局部配置
注解添加到public interface UserClient{}
上
@FeignClient(value="userservice",configuration=FeignClientConfiguration.class)
Feign的性能优化 Feign的底层实现
Feign 添加Http Client的支持
引入依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
配置连接池
feign:
client:
config:
default:
loggerLevel: BASIC
httpclient:
enable: true
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 每个路径的最大连接数
企业中Feign的两种最佳实践
给消费者FeignClient和提供者的controller定义统一的父接口作为标准
对springmvc不起效(父接口参数列表中的映射不会被继承)
public interface UserAPI{
@GetMapping("/user/{id}")
User findById(@PathVariable("id"))
}
客户端
@FeignClient(value="userservice")
public interface UserClient extends UerAPI{
}
服务端
@RestController
public class UserController implements UserAPI{
}
远程调用user-service
实践
新建项目,引入feign的统一api,引入自己写的包
<dependency>
<groupID>cn.itcast.demo<groupID>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
修改原来代码中的类的引入位置
发现无法自动注入成功
两种方式
@EnableFeignClients(basePackages="cn.itcast.feign.clents") 适合批量
@EnableFeignClients(clients={UserClient.class}) 适合单独
Eureka Nacos
服务分级存储
服务搭建 windows平台下载后用命令行直接运行
服务注册到nacos 在clouud-demo父工程中添加spring-cloud-alibaba的管理;依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibab-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-discover</artifactId>
</dependency>
修改user-service&order-service中的application.yml文件
spring:
cloud:
nacos:
server-addr: localhost:8848
配置集群属性
spring:
cloud:
nacos:
discovery:
cluster-name: HZ #设置集群名称,例如杭州
优先修改相同集群的,要修改负载均衡规则
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
相同集群中随机挑选
权重 Nacos控制台中编辑,即可修改权重
0.1
1 频率越高
0 不被访问
环境隔离namespace 不同namespace下的服务不可见 树状图
namespace
group
service/data
spring:
cloud:
nacos:
discovery:
namespace: 492a7d5d-237b-46a1-a99a-fa8e # 此处填id
临时实例和非临时实例 临时实例 采用心跳检测,服务挂掉之后,会直接删除 非临时实例,采用nacos主动询问,挂掉后会报不健康, 不会删除
Eureka 采用pull nacos 采用pull + push, 主动推送变更消息
spring:
cloud:
nacos:
discovery:
ephemeral: false
nacos 配置管理 放入热更新的配置
新建配置 DataID: userservice-dev.yaml Group: DEFAULT_GROUP 配置文件的内容
pattern:
dateformat: yyyy-MM-dd HH:mm:ss
nacos 中的配置文件会和application.yml中合并
顺序: bootstrap.yml->nacos配置文件->application.yml
引入nacos配置管理依赖
<dedpendency>
<groupId>com.alibaba.cloud</groupId>
<artifcatId>spring-cloud-starter-alibab-nacos-config</artifcatId>
</dedpendency>
在userserives 中的resource目录中添加一个bootstrap.yml
文件,这个文件是引导文件,优先级高于application.yml
spring:
applicatioin:
name: userservice # 服务名字
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
测试配置是否正常 在controller中测试
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public claass UserController{
@Atuowired
private UserService userService;
@Value("${pattern.dataformat}")
private String dataformat;
@GetMapping("now")
public String now(){
return LocalDataTime.now().format(DataTimeFormatter.ofPattern(dataformat));
}
}
在@Value
注入的变量所在类上添加注解,添加@RefreshScope
注解
用ConfigurationProperties
会自动刷新
@Data
@Component //注册为bean 都可以使用
@ConfigConfigurationProperties(prefix="pattern")
public calss PatternProperties{
private String dataformate;
}
多环境共享配置
在nacos中新建配置,为userservice.yaml
pattern:
envSharedValue: 环境共享属性
在Config / PatternProperties 中
@Data
@Component //注册为bean 都可以使用
@ConfigConfigurationProperties(prefix="pattern")
public calss PatternProperties{
private String envSharedValue;
}
优先级
profile.yaml -> 服务名称.yaml -> 本地配置
nacos集群搭建
进入nacos的config目录,修改配置文件cluster.conf.example,重命名为cluster.conf 添加节点信息
127.0.0.1:8844
127.0.0.1:8845
127.0.0.1:8846
打开mysql的数据源(去掉#) 密码账号还有url
db.num=1
分别更改三个server.port
启动
startup.cmd
在最后追加 config/nginx.conf
upstream nacos-cluster{
server 127.0.0.1:8845
server 127.0.0.1:8846
server 127.0.0.1:8847
}
server{
listen: 80;
server_name localhost;
location /nacos{ # 访问/nacos这个路径
proxy_pass http://nacos-cluster;
}
}
eurekaApplication order 访问两个user服务 user user
服务端
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
启动类加入@EnableEurekaSServer
注解
server:
port: 10086
spring:
application:
name: eurekaserver # 服务名称
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/ # 地址信息
客户端
引入eurekaclient依赖变化
在application.yml添加名称和地址不变
仍为defaultZone: http://127.0.0.1:10086/eureka/ # 地址信息
使用和服务配置 修改路径
String url="http://localhost:8081/user"
改为
String url="http://userservice/user"
启动项目中加入@LoadBalance
不是给启动项加,给RestTemplate 类加
Ribbon
IRule 自定义
在application类中定义一个新的rule
@Bean
public IRule randomRule(){
return new RandomRule();
}
对所有微服务都随机(这个类中的)
在配置文件中修改
userservice:
ribbon:
NFLoadBalancecrRuleClassName: com.netflix.loadbalancer.RandomRule
对某个指定的微服务而言的(这个类中的)
饥饿加载 启动时候就创建类
ribbon:
eager-load:
enable: true
clients: userservice # 针对这个类
Gateway SpringGateWay
网关
请求转发 安全认证 流量控制 负载均衡 降级熔断 参数校验
请求转发+请求过滤
先nginx 再 网关
SpringGateWay
实现原理 通过过滤器来处理请求
流程 路由判断 请求过滤 服务处理 后端处理 响应过滤 后端处理后再次进行过滤 响应返回
SpringCloud GateWay 断言 如果客户端发送的请求满足了断言的条件,则映射到指定的路由器 可以在配置文件中写明 可以对Cookies 时间 Header Method Path Query 配置
一个路由规则匹配多个断言 如果一个请求可以匹配多个路由,则映射第一个匹配成功的路由
如何实现动态路由
结合Nacos or Eureka
注册中心,将配置写到注册中心,进而避免重启
SpringCloud GateWay 过滤器 分类
第一种分类方法 Pre 请求到微服务之前,可以进行拦截和修改 Post 请求完之后,可以修改内容或者响应头
第二种分类方法 GatewayFilter 局部过滤器 应用在单个路由 GlobalFilter 全局 应用在所有路由器上的过滤器
SpringCloud GateWay 支持限流吗 支持 基于Redis
配置全局异常处理 实现 ErrorWebExceptionHandler 并重写 handler 方法
springcloud中网关的实现包括两种
zuul 阻塞式编程,基于servlet
<dependency>
<groupId>org.springframework.cloud</grouupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</grouupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
创建主类
public class GatewayApplication{
public static void main(String[] args){
SpringApplication.run(GatewayApplication.class,args);
}
}
编写路由配置以及nacos地址
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 #nacos地址
gateway:
routes:
- id: user-service # 路由id,唯一即可
uri: lb://userservice # lb 就是负载均衡 后面跟上服务名称 loadbalance
# uri: http://127.0.0.1:8081 # 固定地址
predicates: # 断言
- Path=/user/**
AddRequstHeader GatewayFilter Factory
spring:
cloud:
gateway:
routes:
filters:
- AddRequestHeader=Truth,Itcast is freaking awsome!
@FeignClient("userservice")
public interface UserClient{
GetMapping("/user/{id}")
User findById(@PathVariablke("id") Long id,RequestHeader(value="truth",required=false)String truth);
}
or 对于所有的都过滤
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=Truth,Itcast is freaking awsome!
全局过滤器 GlobalFilter 通过重写,进而自定义
自定义全局顾虑器,判断参数中是否有authorization,有的话判断是不是admin,同时满足,则放行
新建一个类AuthorizeFilter
@Component
public class AuthorizeFilter implements GlobalFilter,Ordered{
@Override
public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
ServerHttpRequest request=exchange.getRequest();
MultiValueMap<String,String> params=request.getQueryParams();
String auth=params.getFirst("authorization");
if("admin".equal(auth)){
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttoStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
@Override
public int getOrder(){
return -1;
}
}
order 值越小,优先级越高,执行顺序越往前
GlobalFilter 通过实现Ordered接口,或者添加@Order注解来指定Order
路由过滤器和dedfaultFilter的order由Spring指定,默认是按照从1递增
当过滤器的order值一样时,会按照defaultFilter>路由过滤器>GlobalFilter
的顺序执行
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true #解决询问的options请求被拦截
crosssConfigurations:
'[/**]':
allowedOrigins: # 允许那些跨域请求
-"http://localhost:8090"
-"http://www.leyou.com"
allowedMethods: # 允许的ajax请求方式
-"GET"
-"POST"
-"DELETE"
-"PUT"
-"OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 #跨域有效期检测
ES
lucene 开源java语言的搜索引擎类库
高性能
只限于java开发
学习曲线陡峭
不支持水平拓展
elasticseacrch
install
docker network create es-net
docker load -i es.tar
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-plugs:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net\
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
-e "cluster.name=es-docker-cluster"
设置集群
使用
GET / #测试es是否连接
POST /_analyze
{
"text": "黑马学习java太棒了",
"analyzer":"ik_max_word" //ik_min_word
}
kibana 给elasticsearch提供可视化界面
安装
docker run -d \
--name kibana
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--netwotk=es-net \
-p 5601:5601 \
libana:7.12.1
libana版本要和es版本一样
ik分词器 默认的对中文支持很差
分词器 在线安装
docker exec -it elasticsearch /bin/bash
./bin/elasticsearch-plug install
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
exit
docker restart elasticsearch
离线安装
docker volume inspect es-plugins
之后将安装包放到指定路径
重启容器
词库
用户可以自己拓展词典,可以配置停用的词典
在ik分词器的config目录的analyzer.cfg.xml
文件中
rabbit MQ SpringAMQP Kafka
SpringAMQP是基于RabbitMQ封装的一套模板 message queue 消息队列,也就是broker
异步通信 right: 提高吞吐量 解耦
wrong: 依赖broker 架构复杂,不利于追踪
安装 官网 docker
使用
<dependency>
<groupId>org.springframework.boot<groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
rabbitmq:
host: 192.168.150.101
port: 5672
virtual-host: / # 虚拟主机
usrname: itcast
password: 123321
新建测试类,进行测试
@RunWith(SpringRunner.class)
@SpingBootTest
public class SpringAmpqTest{
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue(){
String queueName ="simple..queue";
String message="hello,Spring amqp!";
rabbitTemplate.converAndSend(queueName,message);
}
}
接收消息
@Component
public class SpringRabbitListener{
@RabbitListener(queues="simple.queue")
public void listenSimpleQueue(String msg){
System.out.println("消费者收到消息:"+msg);
}
}
修改application.yml
设置preFetch的值,可以控制预取消息的上限
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 默认好像是无穷大
workqueue
@Component
public class SpringRabbitListener{
@RabiitListener(queues="simple.queue");
public void ListenWorkQueue1(String msg) throw EXception{
Systemctl.out.println("消费者1接收消息:"+msg);
Thread.sleep(20);
}
@RabiitListener(queues="simple.queue");
public void ListenWorkQueue2(String msg) throw EXception{
Systemctl.out.println("消费者2接收消息:"+msg);
Thread.sleep(200);
}
exchange 引入交换机
@Configuration
public class FanoutConfig{
@Bean
public FanoutExchange fanoutExchange()}{
return new FanoutExchange("itcast.fanout");
}
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1,FanoutExchange fanoutExchane){
return BindingBuilder
.bind(fanoutQueue1)
.to(fanoutExchane)
}
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1,FanoutExchange fanoutExchane){
return BindingBuilder
.bind(fanoutQueue2)
.to(fanoutExchane)
}
@RabbitListener(queues="fanout.queue1")
public void listenFanoutQueue1(String msg){
System.out.println(msg);
}
@RabbitListener(queue="fanout.queue2")
public void listenFanoutQueue2(String msg){
System.out.println(msg);
}
}
Test
@Test
public void testSendFanoutExchange(){
String exchangeName="itcast.fanout";
String message="hello";
rabbitTemplate.convertAndSend(exchangeName,"".message);
}
简单声明 && 路由
@RabbitListener(bindings=@QueueBinding(
value=@Queue(name="direct.queue1");
exchange=@Exchange(name="itcast.direct",type=ExchangeTypes.DIRECT).
key={"red","blue"}
))
public void listenDirectQueue1(String msg){
System.out.println(msg);
}
@RabbitListener(bindings=@QueueBinding(
value=@Queue(name="direct.queue2");
exchange=@Exchange(name="itcast.direct",type=ExchangeTypes.DIRECT).
key={"yellow","blue"}
))
public void listenDirectQueue1(String msg){
System.out.println(msg);
}
testSendFanoutExchange
Test
@Test
public void testSendFanoutExchange(){
String exchangeName="itcast.direct";
String message="hello";
rabbitTemplate.convertAndSend(exchangeName,"blue".message);
}
topicExchange
多个单词,以 .
分开
# 0个或者多个
* 代指一个
ex:
#.news
@RabbitListener(bindings=@QueueBinding(
value=@Queue(name="direct.queue1");
exchange=@Exchange(name="itcast.topic",type=ExchangeTypes.DIRECT).
key="china.#"
))
public void listenDirectQueue1(String msg){
System.out.println(msg);
}
@RabbitListener(bindings=@QueueBinding(
value=@Queue(name="direct.queue2");
exchange=@Exchange(name="itcast.topic",type=ExchangeTypes.DIRECT).
key="#.news"
))
public void listenDirectQueue1(String msg){
System.out.println(msg);
}
Test
@Test
public void testSendFanoutExchange(){
String exchangeName="itcast.direct";
String message="hello";
rabbitTemplate.convertAndSend(exchangeName,"china.news".message);
}
可以让发送的数据为json类型,而不是java序列化的 jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
启动类
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
消费者,监听object.queue队列并消费信息
@RabbitListener(queues="object.queue")
public void listenObjectQueue(Map<String,Object> msg){
System.out.println(msg);
}