2018年中总结(三)工作中遇到的问题(下)

这部分是另外一家,也就是我目前地公司笔记记录,来这里工作地三个月时间,主要工作是负责调研新技术,目前基本还没接触到业务层面。

最开始是学习spring cloud组件,为公司的服务加上配置中心,这个我之前写过文章,然后尝试加熔断,但是由于涉及代码太多,且Hystrix和代码的耦合度也蛮高的,所以就放弃了。

之后应领导要求,开始熟悉Kubernetes和Istio,应该是为之后做技术储备,但是说实话,公司的业务量很小,完全没必要使用这两种框架,不过有学习的机会,并且领导坚持,我还是非常开心的接下了这个任务。可以看我后续的博客更新记录,基本全部和这两个大方向有关。

这里就简单的记录遇到的一些问题,做些归纳总结。

1.Spring Cloud资料汇总

spring cloud中文网(应该是机翻,但是还是挺感谢有人做贡献的)

稍微说一下我之前接触的部分服务组件:

  • 服务注册发现——Eureka
  • 网络和负载均衡——ribbon和feign
  • 熔断——Hystrix
  • 路由——zuul
  • 配置中心——config
  • 总线——bus
  • 熔断聚合监控——Turbine

当然上面的内容都是跟着demo写了写,大概知道是干什么用的,真正用到的也就是config和bus结合的配置中心了,其他的也没特别的深入挖掘。

下面是当时的一些总结:

首先spring cloud是建立在spring boot的基础上的。而贯穿微服务架构始终的是服务发现Eureka。
对于所有在架构内的为服务,都需要注册到eureka上,方便服务的管理。
然后ribbon和feign起到的作用就是基于restful调用的两种服务调用方式,并且他们可以配置负载均衡。关键注解是@LoadBalanced
熔断是微服务架构中比较重要的一环,在微服务中,服务之间是独立的,每次请求可能会调用多个服务,此时如果出现某个服务挂掉宕机这种极端情况,整个请求可能会卡在这个挂掉的服务这里,熔断的作用就是快速反应,如果这个服务挂了,就果断切除,返回一个fallback,保证请求能够顺利的执行下去。使用turbine可以对熔断的dashboard进行聚合,在一个页面中监控多个服务的熔断信息。
路由zuul可以配置网关,认证,多重路由等等,主要功能是路由转发和过滤器。
日志收集sleuth只是简单的和zipkin组合了一下,没有尝试使用复杂而又强大的elk,以后如果接触到相关内容,可以花时间学习一下。
最后最关键的是config,因为工作要求就是为各个微服务配出config,而config和bus组合可以实现配置的热部署。当然其中还需要使用GitHub的webhook功能。也即在GitHub上更新配置文件后,可以即时对所有相关config client进行更新。

2.整合config配置中心时遇到的一些问题。
开启rabbitmq后,启动config client项目报错:

Caused by: com.rabbitmq.client.ShutdownSignalException: connection error; protocol method: #method<connection.close>(reply-code=400, ...

主要是因为rabbitmq服务器没有给用户分配权限,分配权限命令如下:

rabbitmqctl set_permissions -p "/" username(默认的为guest) "." "." ".*"

参考文档

在使用rabbitmqctl命令的时候遇到的错误:

* Authentication failed (rejected by the remote node), please check the Erlang cookie

解决办法:

C:\WINDOWS\System32\config\systemprofile.erlang.cookie和C:\Users{UserName}.erlang.cookie两个文件内容一致即可。

这个当时浪费了我很长时间,因为很多博客都有问题。参考文章

3.因为公司业务全部依托阿里云,所以熟悉了很多阿里的产品,比如云效(以前听都没听过),应用和流水线这类的东西。

4.Dockfile的一些指令选项

5.java启动命令中加入参数:-Djava.security.egd=file:/dev/./urandom的含义。参考文章

6.配置熔断的时候遇到报错:

java.lang.ClassNotFoundException: com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect

可以看出缺少aop的相关类,需要加依赖。参考文章

7.Lombok插件
IDEA手动安装插件

8.分布式id生成算法SnowFlake

9.高频词汇 DDD —— 领域驱动设计。

10.12-factor

11.k8s集群拉取私有镜像仓库失败,需要进行相关配置才可使用(配置 pull secret)。参考

忘记在配置文件中加imagePullSecrets: 标签,导致deployment创建失败。

12.使用命令kubectl get pods报错:

The connection to the server localhost:8080 was refused - did you specify the right host or port?

原因是认证问题,当前node节点没有对应的admin.conf文件。将master节点的admin.conf文件copy到node节点/etc/kubernetes/admin.conf文件上。然后使用命令:

export KUBECONFIG=/etc/kubernetes/admin.conf

13.关于网段子网掩码的计算,例如网段192.168.17.0/24

其子网掩码的前24位为1,所以对应的子网掩码表示为255.255.255.0;此时能够使用的主机共254台,因为0和255不允许作为主机地址。(网络广播地址192.168.17.255 和网络标示192.168.17.0)

14.在改项目的时候发现,如果存在spring cloud的部分组件,restTemplate就不能按照localhost/ip+port访问服务,主要就是和ribbon的@LoadBalance注解有关,去掉这个注解就可以正常工作。

15.没有volatile关键字的double check不安全。因为:使用volatile变量禁止指令重排序相关文章

其他内容没什么干货,或者就是和工作内容直接相关的小问题,没有写的价值。

不过我下面需要重点介绍两个问题。

Spring Security

这块是我在修改服务代码的时候偶然碰上的问题,为此,我花了点时间了解了一下Spring Security,当然,依然还是处于简单了解的层面,依然没有进行应用,也没有进行深耕,希望以后有机会可以深度发掘。

一切的起源来自一个报错:

java.lang.ClassCastException: org.springframework.security.authentication.AnonymousAuthenticationToken cannot be cast to org.springframework.security.oauth2.provider.OAuth2Authentication

可以看出这是一个类型转换的问题,但是为什么会这样?

定位到了问题代码:

(OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication()

在这里出现了问题,但是为什么会出现这个问题?

从这里开始,就拉开了我一点点了解Spring Security的序幕。

当然首先就是不明所以的读博客去了解了,因为我的时间有限,所以没有选择阅读官方文档式的深耕,而是选择了最快接受消息,解决问题的读博客方式(弊端就是一知半解,为了解决问题,会导致很快遗忘)。这里推荐一个系列,写的很好,比较清晰。(能找到好的博客也是需要能力,能写好的博客是能力出众的体现!我还是需要继续努力,增加自己的硬实力,写出好的博客造福各位,而不是当毒瘤!)

看过这个系列文章后,大致总结了一下:

Spring Security通过继承WebSecurityConfigurerAdapter 并重写其中的configure()方法来进行相应的配置(主要是HttpSecurityAuthenticationManagerBuilder )。自定义的配置类WebSecurityConfig加上了@EnableWebSecurity注解,同时继承了WebSecurityConfigurerAdapter

使用configure(AuthenticationManagerBuilder auth)暴露一个AuthenticationManager(最核心的身份认证管理器)的建造器:AuthenticationManagerBuilder

HttpSecurity的典型配置,其中http作为根开始配置,每一个and()对应了一个模块的配置(等同于xml配置中的结束标签),并且and()返回了HttpSecurity本身,于是可以连续进行配置。通过authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息。通过formLogin()对应表单认证相关的配置。通过logout()对应了注销相关的配置。通过httpBasic()可以配置basic登录。以及antMatchers()进行角色权限和路径的匹配。

上面只是简述了一下在spring boot中对于spring security的简单配置,下面再简单总结一下spring security的核心过滤器链路调用(详情可参阅这篇文章):只介绍几个重要的过滤器

SecurityContextPersistenceFilter :它有两个主要职责;请求来临时,创建SecurityContext安全上下文信息,请求结束时清空SecurityContextHolder

UsernamePasswordAuthenticationFilter :主要是表单验证的时候非常重要的一个过滤器,表单提交username和password,被封装成token进行一系列的认证,主要通过这个过滤器完成。
偷一张时序图:
这里写图片描述

AnonymousAuthenticationFilter:匿名身份过滤器,需要将它与UsernamePasswordAuthenticationFilter 放在一起比较理解,spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

剩下不一一介绍,不过看到这,应该就能明白我之前遇到的报错是什么问题了,因为匿名过滤器是载表单验证过滤器后的,所以说明我的报错是因为表单验证出现了问题,然后走了匿名流程,最后导致的类型转换错误。

之后又简单的了解了一下OAuth2.0。

看了阮一峰的关于OAuth2.0的文章,总结了一下:

OAuth就是为了第三方认证场景才出现的。它在“客户端”和“服务提供商”之间设置了一个授权层。“客户端”不能直接登陆“服务提供商”,只能登陆授权层,以此将用户与客户端区分开来。“客户端”登陆授权层所用的令牌(token),与与用户的密码不同。用户可以在登陆的时候指定授权层令牌的权限范围和有效期。
“客户端”登陆授权层以后,“服务提供商”根据令牌的权限范围和有效期,向“客户端”开放用户存储的资料。

然后偷一张图。。。
这里写图片描述

(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。

而 OAuth 2 授权服务器必须的端点:

AuthorizationEndpoint 是用于授权服务请求的。默认的URL是:/oauth/authrize
TokenEndpoint 是用于获取访问令牌(Access Tokens)的请求。默认的URL是:/oauth/token

这两个url在spring cloud中整合spring security及OAuth 2.0的时候,经常能看到的配置。

到这里,我的问题解决了,学习也就暂时告一段落,写的很乱,因为当时的思路比这个更乱,所以这块还是不太好归纳。

发送post请求遇到的问题

下一个问题是我在测试的时候,遇到的spring boot中,使用restTemplate发送post请求遇到的问题。

因为之前一直使用的spring mvc,所以在我写这个测试项目的时候,思想还是停留在那个阶段的。而且spring boot有很多之前没用过,见过的注解,所以感觉自己落伍了。。。

首先是第一次发送请求的时候,接收端无法获取传递参数。当时的想法是从request中获取参数,写出了下面不伦不类的代码:

@RequestMapping("/login")
public String getLogin(User user){
    logger.info("get in ============> RouteController.getLogin()");
    System.out.println(user);
    HttpHeaders headers = new HttpHeaders();
    MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
    headers.setContentType(type);
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());

    Gson gson = new Gson();
    String entity = gson.toJson(user);
    HttpEntity<String> formEntity = new HttpEntity<>(entity,headers);
    String ss = gson.toJson(formEntity);
    System.out.println(ss);

    String result = this.restTemplate.postForObject(remoteURL + "/login",formEntity,String.class);
    System.out.println(result);
    return result;
}

// 然后我觉得应该从request中取值,所以接收端是这样的
@RequestMapping(value = "/login")
public String login(HttpServletRequest request, User user){
    logger.info("get in ============> LoginController.login()");

    String username;
    String password;

    if (user.getUsername() == null && user.getPassword() == null){
        String[] usernames = request.getParameterValues("username");
        String[] passwords = request.getParameterValues("password");
        System.out.println(usernames + " " + passwords);
        username = usernames[0];
        password = passwords[0];
    }else {
        username = user.getUsername();
        password = user.getPassword();
    }

    if (username.equals("german") && password.equals("123456")){
        return "go";
    }

    return "fail";
}

当时的想法很简单,我发送了请求,request中肯定是有参数的对吧,但是就是无法从request中取到值,所以应该是哪个地方出现了问题,当然为什么request中没有取到值,我现在也没想通,网络学的比较差,唉。。。

之后就去网上搜了一下,然后照猫画虎,写了第二个示例:

@PostMapping("/login")
    public String getLogin(User user){
        logger.info("get in ============> RouteController.getLogin()");
        System.out.println(user);
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());

        Gson gson = new Gson();
//        String entity = gson.toJson(user);
        MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
        params.add("username", user.getUsername());
        params.add("password", user.getPassword());
        HttpEntity httpEntity  = new HttpEntity(params,headers);
        String ss = gson.toJson(httpEntity );
        System.out.println(ss);

        ResponseEntity<String> request = this.restTemplate.postForEntity(remoteURL + "/login",httpEntity ,String.class);
        System.out.println(request.getBody());
        return request.getBody();
    }

接受端一度改为这种状态:
@RequestMapping(value = "/login")
public String login(@RequestBody String username, @RequestBody String password){
    logger.info("get in ============> LoginController.login()");
    System.out.println(usernames + " " + passwords);

    if (username.equals("german") && password.equals("123456")){
        return "go";
    }

    return "fail";
}

这个时候发送端一直报错400,是传递的数据格式问题。

然后又修改了一版,得到最终版本,也发现了上面的出现400的原因。

发送端:
@PostMapping("login")
public String getLogin(@RequestParam String username, String password){
    logger.info("get in ============> RouteController.getLogin()");
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());

    MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
    params.add("username", username);
    params.add("password", password);
    HttpEntity httpEntity = new HttpEntity(params,headers);
    ResponseEntity<String> request = this.restTemplate.exchange(remoteURL + "/login", HttpMethod.POST, httpEntity,String.class);
    System.out.println(request.getBody());
    return request.getBody();
}

接收端:
@PostMapping(value = "login", produces = MediaType.APPLICATION_JSON_VALUE)
public String login(@RequestParam String username, String password){
    logger.info("get in ============> LoginController.login()");

    if (username.equals("german") && password.equals("123456")){
        return "go";
    }

    return "fail";
}

问题的根源在于接收端的接受参数注解!使用@RequestParam和使用@RequestBody完全是两个概念!就是这里导致的400问题。

然后就搜了一下两者的区别

简单来说,我的失误在于:

因为我在请求头中设置的ContentType类型为multipart/form-data,此类型多用来上传文件类型—即使用@RequestBody不能处理这种格式的数据,@RequestParam这个却是可以处理的。
而使用其他格式包括application/json, application/xml等。这些格式的数据,必须使用@RequestBody来处理。(亲测,确实如此,不过这样的话,传递的json全部在一个变量中,需要再次处理,和之前使用@RequestParam不同。)

也是比较初级的错误,很久没有遇到,有些遗忘了。

最后再附RestTemplate的几种请求调用

到此为止,我的年中总结也就结束了。可以看到,学习的东西也没有太多,遇到的问题可能也都不是太难,甚至还有一些很低级的错误。究其原因就是,复盘太少,导致低级的错误,时间长遗忘,重复犯错;学习新东西的时候,还是有些急躁,所以会导致学完不久就忘,掌握的既不扎实,也没有内化到自己的知识体系中,纯粹是为了解决问题而短暂学习,是无法得到长足的技术进步的。以上都是前半年的不足之处,需要改正。

不过学到的东西就是:感觉上比较困难的问题,在多次拆解后,也是一个一个比较小的问题,将问题逐渐细化,最后总能找到解决方案

最后看到雷军的一段话,送给自己,有则改之无则加勉。

“如果浪费了半小时时间,我就觉得很惭愧。后来我看到很多人不珍惜时间的时候, 我就觉得这样的人真没出息。时间是自己的,你到一个公司打工的时候,偷懒,老板没有看见,就觉得自己又蒙了一下,玩猫和老鼠的游戏,真是没有必要。公司所付的那么一点钱, 就买下了你一个月的青春?学会的东西首先是自己的,其次才是公司的。没有多少人真正计算过自己一个小时值多少钱。

猜你喜欢

转载自blog.csdn.net/ybt_c_index/article/details/80925158