微服务系列三:微服务核心——网关路由
目录
前言
一、登录存在的问题归纳
二、*微服务网关整体方案
三、认识微服务网关
四、网关鉴权实现
五、OpenFeign微服务间用户标识信息传递实现
六、微服务网关知识追问巩固
前言
本篇文章具体讲解微服务中网关的实现逻辑、用于解决什么样的问题。其中标题中标注* 涉及到非常巧妙的设计或者核心配置。需要仔细阅读并理解,相信对你理解微服务、学习微服务会有很大的帮助!
通过这篇文章学习后,希望你能回答第六节中所有的问题。
当前微服务项目大致是被我们拆分出来了,分别是以下几个模块:
用户服务、商品服务、购物车服务、交易服务、支付服务
但是拆分模块后,还是会出现不少问题,主要的问题归纳如下:
一、登录存在的问题归纳
1. 请求不同数据时要访问不同的入口,需要维护多个入口地址。
原先单体项目的时候,前端只需要将请求转发到8080端口,就可以访问单体项目中所有的方法了。但是现在多入口模式下,前端要如何维护请求的转发呢?
2. 前端无法调用nacos,无法实时更新服务列表
前端nginx无法直接和nacos交互,怎么知道这个入口地址还可用,如果宕机了还怎么发送?
3.每个微服务都需要编写登录校验、用户信息获取的功能
最简单的做法是每一个需要校验的微服务模块都配置一个JWT校验。但是这样明显不太优雅,本节是为了提供一种统一校验的方法。利用网关解决这个问题。
4. 微服务模块如何获取用户标识信息?
只有通过JWT校验才会有用户标识信息。也就是说通过网关校验后,微服务模块又要如何获取到这些用户标识信息呢?
5. 微服务内如何使用用户标识信息?
可以使用TreadLocal保存用户信息供本模块方法使用。
6. 微服务间进行调用,OpenFeign如何发送用户标识信息?
我们需要想办法将用户标识信息写到请求头中,随着OpenFeign请求一同发送。
想要解决上述问题,我们需要引入“网关”的概念。
二、*微服务网关整体方案
提前将整体方案总结,如果能看得明白,接下来的具体实现想必也不是问题:
2.1 后端黑盒
解决问题一。我们在前端和后端微服务项目之中添加了一层网关微服务。并且设置网关微服务的端口为8080。这样一来,前端代码无需修改,只需将所有请求发送到8080,接着由我们的网关微服务进行请求转发和负载均衡。实现了黑盒效果
2.2 网关模块的作用
1. 实现前端请求的统一处理转发、微服务列表维护、负载均衡
网关直接和Nacos注册中心进行交互,获取实时的服务列表。自动完成服务的转发和负载均衡。
2. 网关过滤器实现鉴权统一认证(JWT令牌)
既然所有的前端请求都会访问到网关模块,那我们完全可以在网关模块添加一个过滤器用于JWT令牌的统一校验。只有通过了JWT令牌校验的请求才会被转发。否则网关会返回401权限不足的异常。
2.3 微服务拦截器作用
实现用户标识信息的保存
具体的。当网关通过了JWT令牌校验后,我们需要通过拦截器将网关传递的用户标识信息保存到ThreadLocal中,供当前微服务模块使用。
然而,如果在每一个微服务都编写一个拦截器还是太不优雅了。对此,我们可以将MVC的拦截器编写到公共模块 common-service中,其他模块引用即可。
2.4 OpenFeign拦截器作用
实现微服务间调用的用户标识信息传递
前面微服务拦截器解决了如何保存用户标识信息的问题。但是这种拦截器能够保存用户标识信息的前提是——请求必须通过网关转发而来。
然而微服务间的调用是通过OpenFeign工具完成的。因此无法使用MVC拦截器获取到用户标识信息。这就要求我们在微服务请求发送前,要想办法给请求添加上用户标识信息。
如何呢?一样是利用拦截器将OpenFeign请求先拦截下来,将用户标识信息添加到请求头。
三、认识微服务网关
3.1 网关模式的演示
前端:我到底该发给谁?
网关:你发给我就行了,接下来的事你不用管!
网关:我要怎么知道我管理的微服务的列表呢?
注册中心:别担心,你找我就行了!
3.2 *编写网关模块
【实现底层】
在SpringCloud中网关的实现包括两种:
使用GateWay更好喔!
【使用步骤】
- 创建网关模块
- 引入网关依赖——SpringCloudGateway、NacosDiscovery
- 编写网关启动类
- 配置网关转发规则——路由规则
1. 创建网关模块
2. 引入网关依赖
<dependencies><!--common--><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--nacos discovery--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--负载均衡--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
3. 编写启动类
package com.hmall.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class GateWayApplication {public static void main(String[] args) {SpringApplication.run(GateWayApplication.class, args);}
}
4. 编写路由规则
server:port: 8080 # 网关端口 前端请求统一处理spring:application:name: hm-gateway # 微服务名称cloud:nacos:server-addr: 192.168.186.140:8848 # nacos地址gateway:routes:- id: cart # 路由id,自定义uri: lb://cart-service # 目标服务地址 lb://服务名称 lb表示负载均衡predicates:- Path=/carts/** # 断言,路径匹配 路径以/api/cart/开头都会被路由到cart-service- id: itemuri: lb://item-servicepredicates:- Path=/items/**,/search/** # 多路径匹配以逗号分割- id: payuri: lb://pay-servicepredicates:- Path=/pay-orders/**- id: tradeuri: lb://trade-servicepredicates:- Path=/orders/**- id: useruri: lb://user-servicepredicates:- Path=/users/**,/addresses/**
3.3 测试网关转发功能
3.4 *网关路由属性
前面我们初步配置了网关路由规则。实际上,Gateway路由还有很多功能,我们窥探一下源码了解一下吧!
网关路由对应的Java类型是RouteDefinition,其中常见的属性有:
- id:路由唯一标示
- uri:路由目标地址
- predicates:路由断言,判断请求是否符合当前路由。
- filters:路由过滤器,对请求或响应做特殊处理。
【路由断言】
在Java中以工厂模式实现:Spring提供了12种基本的RoutePredicateFactory实现:
Spring Cloud Gatewayhttps://docs.spring.io/spring-cloud-gateway/docs/3.1.7/reference/html/#gateway-request-predicates-factories
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org,**.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
weight | 权重处理 | - Weight=group1, 2 |
XForwarded Remote Addr | 基于请求的来源IP做判断 | - XForwardedRemoteAddr=192.168.1.1/24 |
【过滤器】
网关中提供了33种路由过滤器,每种过滤器都有独特的作用Spring Cloud Gatewayhttps://docs.spring.io/spring-cloud-gateway/docs/3.1.7/reference/html/#gateway-request-predicates-factories
四、网关鉴权实现
我们的登录是基于JWT来实现的,校验JWT的算法复杂,而且需要用到秘钥。如果每个微服务都去做登录校验,这就存在着两大问题:
-
每个微服务都需要知道JWT的秘钥,不安全
-
每个微服务重复编写登录校验代码、权限校验代码,麻烦
所以我们把JWT校验提前到网关模块来做,成功则请求到对应的模块,失败则返回401报错信息。
4.1 网关请求处理流程分析
我们采取的网关gateway底层是如何进行处理的呢?
- 客户端请求进入网关后由
HandlerMapping
对请求做判断,找到与当前请求匹配的路由规则(Route
),然后将请求交给WebHandler
去处理。 -
WebHandler
则会加载当前路由下需要执行的过滤器链(Filter chain
),然后按照顺序逐一执行过滤器(后面称为Filter
)。 -
图中
Filter
被虚线分为左右两部分,是因为Filter
内部的逻辑分为pre
和post
两部分,分别会在请求路由到微服务之前和之后被执行。 -
只有所有
Filter
的pre
逻辑都依次顺序执行通过后,请求才会被路由到微服务。 -
微服务返回结果后,再倒序执行
Filter
的post
逻辑。 -
最终把响应结果返回。
这里我们主要关注过滤器。有以下特点:
- 第一:过滤器的PRE部分在发送请求到微服务之前执行,因此时候在这部分实现JWT校验
- 第二:过滤器有先后顺序之分,默认Netty路由过滤器是优先级最低的。用于请求转发。
- 第三:我们编写的JWT鉴权过滤器要确保优先级比Netty路由器高。
4.2 自定义过滤器实践
自定义过滤器分为两种:
- GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
- GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。
其中全局过滤器的实现更加简单,两者的过滤方法同名同参数
/*** 处理请求并将其传递给下一个过滤器* @param exchange 当前请求的上下文,其中包含request、response等各种数据* @param chain 过滤器链,基于它向下传递请求* @return 根据返回值标记当前请求是否被完成或拦截,chain.filter(exchange)就放行了。*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
4.2.1 GlobalFilter实现
1. 创建过滤器,继承GlobalFilter 和 Ordered类,编写filter方法
2. 测试执行情况
4.2.2 GatewayFilter实现
【过滤器模板】、【简单无参过滤器】
【装饰模式】、【配置过滤器优先级】
【过滤器模板】、【有参过滤器的实现】
【生效范围配置】
4.3 *实现网关登录校验
使用简单的GlodalFilter实现登录校验过滤器,基本步骤如下:
- 配置JWT校验工具
- 编写登录校验过滤器
- 测试网关拦截效果
1. 导入JWT相关工具类
-
AuthProperties
:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问 -
JwtProperties
:定义与JWT工具有关的属性,比如秘钥文件位置 -
SecurityConfig
:工具的自动装配 -
JwtTool
:JWT工具,其中包含了校验和解析token
的功能 -
hmall.jks
:秘钥文件
2. 编写登录校验过滤器
- 使用构造器注入配置
- 获取请求头
- 判断请求路径是否在放行白名单里
- 获取请求头中的token
- 解析并校验token
- 如果token无效,只需拦截,设置响应状态码401
- TODO: token有效,传递用户标识信息
- 放行
package com.hmall.gateway.fliters;import cn.hutool.core.collection.CollUtil;
import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component // 将过滤器交给Spring管理
@RequiredArgsConstructor // 采用构造器注入(只会注入 final)
@EnableConfigurationProperties(AuthProperties.class) // 开启属性配置
public class AuthGlobalFilter implements GlobalFilter, Ordered {// 注入Jwt工具类private final JwtTool jwtTool;// 注入配置文件中的属性private final AuthProperties authProperties;// **(重要) 路径匹配器,用于匹配路径是否在白名单中private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 登录校验开始//1. 获取requestServerHttpRequest request = exchange.getRequest();//2. 判断路径是否在白名单中if(isExclude(request.getURI().getPath())){// 无需拦截 直接放行return chain.filter(exchange);}//3. 获取请求头中的tokenString token = null;List<String> headers = request.getHeaders().get(HttpHeaders.AUTHORIZATION);if(!CollUtil.isEmpty(headers)){ // 请求头中存在tokentoken = headers.get(0); // 获取token}//4. 解析并校验tokenLong userId = null;try{userId = jwtTool.parseToken(token);}catch (UnauthorizedException e){// 如果token无效,拦截返回401即可ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete(); // 直接返回响应,不会再往下走了}//TODO 5. 如果有效,传递用户标识信息System.out.println("userId = " + userId);//6. 放行return chain.filter(exchange);}/*** 判断路径是否在白名单中* @param path* @return*/private boolean isExclude(String path) {for(String pathPattern : authProperties.getExcludePaths()){ // 遍历白名单if(antPathMatcher.match(pathPattern,path)) { // 如果路径匹配上了,则返回truereturn true;}}return false;}// 过滤器优先级,值越小优先级越高@Overridepublic int getOrder() {return 0;}
}
3. 测试网关拦截效果
4.4 通过网关传递用户标识信息
上一节中,我们还有一步没有完成:
网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?
由于网关发送请求到微服务依然采用的是
Http
请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。
4.4.1 *改造网关过滤器
在获取用户信息后保存到请求头,转发到下游微服务
@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 登录校验开始//1. 获取requestServerHttpRequest request = exchange.getRequest();//2. 判断路径是否在白名单中if(isExclude(request.getURI().getPath())){// 无需拦截 直接放行return chain.filter(exchange);}//3. 获取请求头中的tokenString token = null;List<String> headers = request.getHeaders().get(HttpHeaders.AUTHORIZATION);if(!CollUtil.isEmpty(headers)){ // 请求头中存在tokentoken = headers.get(0); // 获取token}//4. 解析并校验tokenLong userId = null;try{userId = jwtTool.parseToken(token);}catch (UnauthorizedException e){// 如果token无效,拦截返回401即可ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete(); // 直接返回响应,不会再往下走了}//5. 如果有效,将userId放到请求头中,继续往下走String userInfo = userId.toString();ServerWebExchange ex = exchange.mutate().request(b -> b.header("user-info",userInfo)).build();//6. 放行return chain.filter(ex);}
4.4.2 *编写微服务拦截器
拦截请求获取用户信息,保存到ThreadLocal后放行。
为了提高代码复用,我们SpringMVC的拦截器最好写在通用模块中:hm-common。
在hm-common中已经有一个用于保存登录用户的ThreadLocal工具——UserContext,我们只需要编写拦截器将用户信息保存到UserContext即可。
【基本步骤】
- 定义SpringMVC拦截器
- 编写拦截器代码
- 注册拦截器
注意啦!!!
拦截器中记得一定要加上这么两个注解:
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
其中第二个注解不能少,少了马上报错,原因如下:
第二个配置表示:
当DispatcherServlet类存在时这个配置类才会生效* 为什么要这么做呢? 因为网关模块也依赖了common模块,而网关模块底层gateway是没有走spring mvc的, * 所以这个配置类在网关模块中是不需要的,所以需要加上这个注解 * 不加这个注解的话,在网关模块启动的时候会报错,因为配置不生效
- 配置自动装配,使配置类生效
这个配置类默认是不会生效的,因为它所在的包是
com.hmall.common.config
,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。基于SpringBoot的自动装配原理,我们要将其添加到
resources
目录下的META-INF/spring.factories
文件让其自动装配
编写拦截器代码,只做判断不拦截
package com.hmall.common.interceptor;import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class UserInfoInterceptor implements HandlerInterceptor {/*** 在请求处理之前进行调用(Controller方法调用之前)* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1. 获取请求头中的用户信息String userInfo = request.getHeader("userInfo");//2. 判断用户信息是否为空if(StrUtil.isNotBlank(userInfo)){// 不为空,将用户信息存入到ThreadLocal中UserContext.setUser(Long.valueOf(userInfo));}// 3. 放行return true;}/*** 在请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清除ThreadLocal中的用户信息UserContext.removeUser();}
}
注册拦截器
package com.hmall.common.config;import com.hmall.common.interceptor.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.text.DecimalFormat;@Configuration // 配置类/*** mvc配置类 当DispatcherServlet类存在时这个配置类才会生效* 为什么要这么做呢? 因为网关模块也依赖了common模块,而网关模块底层gateway是没有走spring mvc的,* 所以这个配置类在网关模块中是不需要的,所以需要加上这个注解* 不加这个注解的话,在网关模块启动的时候会报错,因为配置不生效*/
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {/*** 添加拦截器* @param registry*/public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}
配置自动装配,使配置类生效
测试网关作用
五、OpenFeign微服务间用户标识信息传递实现
5.1 网关请求 和 OpenFeign请求的区别
前端发起的请求都会经过网关再到微服务,由于我们之前编写的过滤器和拦截器功能,微服务可以轻松获取登录用户信息。
但是这也是一种局限。只有通过网关转发的请求才有用户信息的传递。而在微服务项目中,有很多稍微复杂点的业务需要多个微服务模块协调,此时请求是通过OpenFeign发送的,而不是网关转发的。自然而然地无法传递用户标识信息。
但是,如果不传递用户标识信息,业务就无法正常进行。例如购物车微服务在下单结算后需要调用商品微服务清除购物车信息,但是如果不传递当前购物车用户的ID信息,如何正确清除商品信息呢?
因此,我们本节就是为了解决微服务间传递用户标识信息的。
5.2 如何使OpenFeign请求携带用户信息?
将UserContext中的用户表示信息在发送OpenFeign请求前,存入请求头一并发出给目标微服务。
5.3 如何使所有OpenFeign请求的请求头携带用户信息?
使用Feign中提供的一个拦截器接口:feign.RequestInterceptor,
确保请求在发出之前都能携带上用户标识信息。
5.4 *编写OpenGeign拦截器
- 创建拦截器
- 该写在哪呢?为了让所有微服务调用OpenFeign请求都能添加上请求用户信息,我们需要将拦截器写在hm-api 这个ClientAPI模块中
- 编写拦截器代码
- 这个拦截器需要像前一节写的拦截器一样先注册再使用么?不需要的哦!因为这个拦截器的底层并不是走SpringMvc那套的,直接使用即可!!
- 测试
/*** 拦截器: 将登录用户信息放入请求头中,传递给下游微服务* @return*/@Beanpublic RequestInterceptor userInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 获取登录用户Long userId = UserContext.getUser();if(userId == null) {// 如果为空则直接跳过return;}// 如果不为空则放入请求头中,传递给下游微服务template.header("user-info", userId.toString());}};}
测试,清除选中的购物车信息:
好了,现在微服务之间通过OpenFeign调用时也会传递登录用户信息了。
六、微服务网关知识追问巩固
1. 拆分微服务模块后,前端如何知道要请求到哪个模块?如何解决这个问题?
2. 请你概述一下网关模块的基本方案是什么?
3. 微服务为什么添加网关层?谈谈网关层的作用是什么?
4. 微服务网关方案动用了“一个过滤器、两个拦截器”,它们分别的用途是什么?底层的实现有有什么不同?
5. 微服务中是如何更好地解决登录校验问题的?应该在哪里实现登录校验功能?
6. 谈谈gateway网关过滤器有几种实现形式?实现的步骤分别是什么?
6. 微服务模块如何获取用户标识信息?
7. 微服务中是怎么解决跨微服务请求中用户标识信息传递问题的?应该在哪里实现该功能?
8. 请你谈谈一下SpringMvc拦截器的使用步骤?
9. 假设配置的拦截器不在包扫描范围下,你要如何配置从而确保拦截器生效呢?
相关文章:

微服务系列三:微服务核心——网关路由
目录 前言 一、登录存在的问题归纳 二、*微服务网关整体方案 三、认识微服务网关 四、网关鉴权实现 五、OpenFeign微服务间用户标识信息传递实现 六、微服务网关知识追问巩固 前言 本篇文章具体讲解微服务中网关的实现逻辑、用于解决什么样的问题。其中标题中标注* 涉…...
【系统架构设计师】2023年真题论文: 论边云协同的设计与实现(包括解题思路和素材)
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 真题题目(2023年 试题4)解题思路边云协同概念和架构边云协同的关键技术边云协同的设计与实现案例分析论文素材参考真题题目(2023年 试题4) 边云协同是指将边缘计算和云计算相结合,实现边缘设备与云端资源之间…...

vue3记录(第一版)
vue2与vue3的区别 vue2属于选项式API,vue3属于组合式API setup概述 setup是vue3中一个新的配置项,值是一个函数,组件中所用到的数据,方法,计算属性,监视等等,均配置在setup中 vue3中的setup和vue2的data,methods之间有什么关系呢? 因为setup比data解析的早,所以在data中可以…...
R 语言数据导入与导出
R 语言数据导入与导出 数据的导入和导出是数据处理中的重要步骤。R 语言提供了多种方法来读取和写入不同格式的数据文件,包括 CSV、Excel、JSON、SQL 数据库等。本文将介绍如何在 R 语言中进行数据的导入和导出。 1. 导入数据 1.1 读取 CSV 文件 CSV(C…...
kubectl常用命令简介
在之前浅谈Kubernetes集群架构 中介绍了kube-apiserver是提供k8s对内或对外的api请求的唯一入口,本文介绍的 kubectl 是官方的CLI命令行工具,用于与 apiserver 进行通信,将用户在命令行输入的命令,组织并转化为 apiserver 能识别的…...

【小白学机器学习31】 大数定律,中心极限定理,标准正态分布与概率的使用
目录 1 正态分布相关的2个相关定理 1.1 大数定律:(证明了)分布的稳定性 1.2 中心极限定理:(证明了)分布的收敛性 2 使用标准差和概率的2种思路 2.1 标准正态分布的曲线 2.2 两种使用方式 2.3 第1种:按整数倍标准差δ 作为标准使用 2.…...

Go语言基础语法
一、创建工程 说明: (1)go.mod文件是go项目依赖管理文件,相当于前端的package.json,也就是Java项目中的Maven的pom.xml。 二、打印数据到控制台 (1)引入fmt (2)使用fmt…...
CSS层叠/CSS变量和!important的使用
layer components {:root {--theme: red;font-family: serif !important;} } CSS Layers CSS Layers 是一种用于管理和组织样式规则的新机制。它允许开发者定义不同的样式层,以便更好地控制样式的优先级和覆盖关系。通过使用 layer 规则,开发者可以将样…...

提升工作效率的小众神器
🤖宝子们,今天我要给大家分享五款超实用的小众工作软件,让你的工作事半功倍!😎 🌟亿可达 - 自动化办公神器 亿可达是一款自动化办公工具,无需编程知识就能搭建出各种自动化工作流程。界面清新…...

【Python+Pycharm】2024-Python安装配置教程
【PythonPycharm】2024-Python安装配置教程 一、下载装 Python 1、进入Python官网首页,下载最新的Python版本 Download Python | Python.org 选择对应版本下载 安装 测试安装情况 python如果安装失败 在系统环境变量添加安装路径 where pythonwin7安装路径添加…...

systemverilog中clocking的用法
文章目录 1.clocking简介2.clocking实例分析3.重点分析(1)bus定义是不是随便取名的(2)输入输出的定义原则是什么(3)到底如何消除了竞争和冒险(4)没用到的信号如何处理(5&…...
【Python开发】大模型应用开发项目整理
不知不觉已经入职3个月了,同事很好,工作充实,学到了很多东西,大大小小的需求也实现了接近20个。负责2个主要component,数据抓取和利用GenAI做数据提取。 1 背景 提取新闻中事件关键信息,比如人名ÿ…...

Redis 的使⽤和原理
第一章:初识 Redis 1.1盛赞 Redis Redis 是⼀种基于键值对(key-value)的 NoSQL 数据库,与很多键值对数据库不同的是,Redis 中的值可以是由 string(字符串)、hash(哈希)、list&…...
前端学Java
一:语法 1、注解 注解(Annotation)是Java中的一种特殊类型的语法,它可以被用来为代码提供元数据。元数据是关于数据的数据,注解可以用于类、方法、变量等的描述与标记。 理解注解可以从以下几个方面入手:…...
VR游戏:多人社交将是VR的下一个风口
第一部分:创业笔记 1. 市场趋势 从单机游戏转向多人互动体验:随着技术的进步,VR游戏正从单机模式向多人互动体验转变。代表作品如Rec Room、Phasmophobia、Among Us和Breachers等,这些游戏的成功证明了多人互动模式的巨大潜力。…...
Docker与虚拟机(VM)的不同
Docker与虚拟机(VM)在实现的原理上存在显著的不同,主要体现在以下几个方面: 一、基础原理 Docker 利用Linux内核的特性,如容器(containers)、命名空间(namespaces)和控制…...

Pr 视频效果:透视
效果面板/视频效果/透视 Video Effects/Perspective Adobe Premiere Pro 的视频效果中,透视 Perspective效果组主要用于在二维平面的视频剪辑中模拟三维空间的透视效果。 通过调整这些效果,可以改变图像的视角、添加阴影、创造立体感,增强画面…...
C 语言标准库 - <limit.h>
简介 <limits.h> 是 C 标准库中的一个头文件,定义了各种数据类型的限制。这些宏提供了有关整数类型(char、short、int、long 和 long long 等)和其他数据类型的最大值和最小值的信息。 这些限制指定了变量不能存储任何超出这些限制的…...

Python | Leetcode Python题解之第519题随机翻转矩阵
题目: 题解: class Solution:def __init__(self, m: int, n: int):self.m mself.n nself.total m * nself.map {}def flip(self) -> List[int]:x random.randint(0, self.total - 1)self.total - 1# 查找位置 x 对应的映射idx self.map.get(x,…...

大数据新视界 -- 大数据大厂之提升 Impala 查询效率:索引优化的秘籍大揭秘(上)(3/30)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

DBLP数据库是什么?
DBLP(Digital Bibliography & Library Project)Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高,数据库文献更新速度很快,很好地反映了国际计算机科学学术研…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...