栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

云原生应用的四大思考点和代码上云改造详细流程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

云原生应用的四大思考点和代码上云改造详细流程

 Java学习:Java从入门到精通总结

 深入浅出RocketMQ设计思想:深入浅出RocketMQ设计思想

 绝对不一样的职场干货:大厂最佳实践经验指南


 最近更新:2022年6月25日

 个人简介:通信工程本硕、Java程序员。做过科研paper,发过专利,优秀的程序员不应该只是CRUD

 点赞  收藏 ⭐留言  都是我最大的动力!


文章目录
  • CICD思考
  • 弹性思考
  • 解耦思考
  • 中台思考
  • 案例实战-云原生应用改造记

CICD思考

一份代码多次发布

关键点:一个应用 = 一份代码库 = 多个版本

建议使用单链发布,一个链的不同版本发布在不同的库里

编译发布和运行分离

关键点:一次发布 = 一次编译 = 多配置多环境发布

编译完成后要保证不变,改变的只是配置文件

环境一致性

开发、准生产、生产三个环境的技术栈、CICD、运行时环境、后台数据库、中间件全部一致


弹性思考

无状态进程

  • 所有结构化数据不放在本地,而是放在MySQL数据库里
  • 所有非结构化数据不放在本地文件系统,尤其是css、js等放在OSS上
  • 缓存数据放在Redis里

关键点:不共享磁盘 + 缓存外置


同步并发

  • 横轴表示不同的负载应用:web应用、工作节点、时钟应用
  • 纵轴表示他们可以做弹性伸缩,可以使用多线程 + 多进程(容器)+ 多沙盒(隔离性最前)

快速启动优雅关闭

关键点:无状态 + 无依赖 + 秒级启停

  • 启动时不和数据库、中间件打交道
  • 启动和关闭速度一定要是秒级且优雅的(清除缓存等)

解耦思考

依赖明示

除了对第三方包会有一些依赖之外,对系统也可能有一些依赖,比如:

  • apt-get
  • rpm
  • yum
  • brew

需要用Gradle / Maven来明确指出需要依赖第三方什么库和什么包,在操作系统层面可以尝试采用Dockerfile,提前打包好所有运行时需要的库、包、第三方依赖等


配置和密码分离

  • 环境变量:和系统环境相关的内容。可以使用不同的环境参数来注入,也可以使用外部的SpringCloud Config等向系统注入
  • 密钥:使用密码再次加密,防止公司内部人员看到

开源精神 + 环境变量 + 集中式管理

环境变量 和 密钥的配置管理可以统一,动态对一些内容加解密,当做环境变量传输给应用

后台服务

和外部系统之间的访问全部剥离出去

关键点:无本地盘依赖 + 无启动依赖 + 可熔断降级


端口绑定

代码里面可以正常写成80端口,但实际上会给冲突的应用绑定一个其他的端口,实现一台物理机可以跑不同的应用,当应用访问另一个应用时,只需要知道应用名称即可,ip和端口号的转换全部在底层实现

关键点:全自动端口映射 + 服务注册和发现


中台思考

日志流

  • 多节点汇聚
  • 多维度分析
    ○ 系统、应用、web调用、业务拔高分析
  • 大流量
  • 处理、归档、备份
    ○ 减少不必要的日志,减少需要管理的在线日志数量

关键点:标准输出 + 不代理、不保存、不处理

所有日志往标准输出里进行输出,不能输出到本地硬盘

管理作业

关键点:同一代码库 + 管理服务 + 定时器/事件驱动

任何的cloud native的代码都在一个代码库里,可以将作业变成一个普通的服务,所有任务都是偏API或消息处理的


认证授权

认证服务尽量简化,通过外部服务或服务网格来实现

监控遥测
关键点:健康 + 性能 + 追踪


API经济

  • 中台思维
  • 移动端为先
    ○ 开发一个业务时要思考在移动端能否发布
    ○ 后端接口既要能被移动端调用,又要能被web前端调用
  • 服务合同SLA
逐步搭建出云原生的最高境界:IaaS+PaaS->SaaS

案例实战-云原生应用改造记

源码链接:https://pan.baidu.com/s/14kmiMmkIDnL4s1P1Kj456g?pwd=99ir
提取码:99ir

  1. 做一个预定小应用。保证一份代码原则,并且应用非常小巧,可以快速启动优雅关闭
  2. 生产可发布,环境一致性
  3. 配置管理、编译发布和运行环境分离、依赖Spring Cloud config
  4. 服务注册和发现、后台服务
  5. API网关和熔断,实现API经济、监控遥测
  6. 容器云,满足三个原则:端口绑定、编译发布和运行分离、同步并发
  7. 引入消息队列、API经济、管理作业
  8. 链路追踪、监控遥测
  9. 安全加固、认证授权

阶段1:秒级启动/关闭的小程序

预定商品类:

@Entity
public class Reservation {
    
    public Reservation() {
    }
    
    public Reservation(String reservationName) {
        this.reservationName = reservationName;
    }
    
    @Id
    @GeneratedValue
    private Long id;
    private String reservationName;
    
    public Long getId() {
        return id;
    }
    
    public String getReservationName() {
        return reservationName;
    }
}

应用启动类:

配置文件application.yaml为空,pom文件里引入了SpringBoot的starter等


阶段2:生产可发布,环境一致性
在pom中引入spring-boot-starter-actuator做一些健康检查和其他简单的操作


    org.springframework.boot
    spring-boot-starter-actuator

还要支持安装包可以打包成jar包


    org.springframework.boot
    spring-boot-maven-plugin
    
        true
    

整个项目的属性要保持一致,所以在配置文件中指定:

info.build.artifact=${project.artifactId}
info.build.version=${project.version}

阶段3:配置管理、编译发布和运行环境分离、依赖Spring Cloud config

此时除了reservation-service放应用代码之外,还额外有一个config-service来做spring cloud config部署,其中config-service很简洁:

@EnableConfigServer
@SpringBootApplication
public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

配置文件要指定服务端口和配置信息的uri

spring.cloud.config.server.git.uri=https://github.com/joshlong/bootiful-microservices-config.git
server.port=8888

reservation-service会用到config-service,所以在其pom里引入config服务


    org.springframework.cloud
    spring-cloud-starter-config

同时在配置文件中指明config服务的uri:

spring.application.name=reservation-service
spring.cloud.config.uri=http://localhost:8888

阶段4:引入Eureka

eureka-service的启动类:

@EnableEurekaServer
@SpringBootApplication
public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

eureka-service的配置文件:

spring.cloud.config.uri=http://localhost:8888
spring.application.name=eureka-service

给reservation-service的启动类添加一个注解:

并在配置文件里指定自己的应用名:

spring.application.name=reservation-service
spring.cloud.config.uri=http://localhost:8888

此外还独立出来一个reservation-client,配置文件为:

spring.application.name=reservation-client
spring.cloud.config.uri=http://localhost:8888

启动类为:

阶段5:API网关和熔断

reservation-client引入zuul和hystrix组件:


    org.springframework.cloud
    spring-cloud-starter-zuul


    org.springframework.cloud
    spring-cloud-starter-hystrix

reservation-client的启动类添加一个注解:

reservation-client定义controller:

@RestController
@RequestMapping("/reservations")
class ReservationApiGatewayRestController {
    
    @Autowired
    @LoadBalanced
    private RestTemplate rt;
    
    public Collection getReservationNamesFallback() {
        return Collections.emptyList();
    }
    
    @RequestMapping("/names")
    @HystrixCommand(fallbackMethod = "getReservationNamesFallback")
    public Collection getReservationNames() {
        
        ParameterizedTypeReference> parameterizedTypeReference =
            new ParameterizedTypeReference>() {
        };
        
        ResponseEntity> exchange = rt.exchange(
            "http://reservation-service/reservations", HttpMethod.GET, null, parameterizedTypeReference);
        
        return exchange
            .getBody()
            .getContent()
            .stream()
            .map(Reservation::getReservationName)
            .collect(Collectors.toList());
    }
    
}

/reservations/names如果出错,就会访问getReservationNamesFallback


Hystrix会记录加了@HystrixCommand注解的API的调用情况,通过自定义的hystrix-dashboard项目对外展示,启动类如下:

@EnableHystrixDashboard
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

配置文件:

spring.application.name=hystrix-dashboard
spring.cloud.config.uri=http://localhost:8888

阶段6:容器云
在上一步的基础上加了几个脚本

cd config-service
cf push
# run cf apps to get the URL of config-service,
# or note it from the "urls" output of the `cf push`
# and place it in the next line where it says _URL_
cf cups config-service -p '{"uri":"http://config-service-unfiercely-alkalimetry.cfapps.io"}'
cd ..

cd eureka-service
cf push
# run cf apps to get the URL of eureka-service,
# or note it from the "urls" output of the `cf push`,
# and place it in the next line where it says _URL_
cf cups eureka-service -p '{"uri":"http://eureka-service-rooted-subquarter.cfapps.io"}'
cd ..

cd reservation-service
cf push
cd ..

cd reservation-client
cf push
cd ..

cf scale reservation-service 10000

先进入到config-service里执行cf push,实现了类似于docker的build,把一个应用jar变成一个容器镜像,发布完成之后,所用容器汇聚成一个uri:http://config-service-unfiercely-alkalimetry.cfapps.io,只用访问zhegeuri即可访问config-service


cf cups config-service -p '{"uri":"http://config-service-unfiercely-alkalimetry.cfapps.io"}'将后面一长串uri变成一个短名config-service


reservation-service要绑定eureka-service,config-service,只需要写一个yml文件:

---
applications:
- name: reservation-service
  path: target/reservation-service.jar
  host: reservation-service-${random-word}
  memory: 512M
  services:
    - config-service
    - eureka-service
  env:
    DEBUG: "true"
    debug: "true"

启动的时候是单实例,并且内存消耗只有512M,如果想要多实例,可以在脚本里添加一个cf scale reservation-service 10000命令


阶段7:引入消息队列
reservation-client中引入消息队列


    org.springframework.cloud
    spring-cloud-starter-stream-rabbit

在reservation-client的启动类上指定绑定的消息队列:

并提供一个API来生产消息:

@RequestMapping(method = RequestMethod.POST)
public void accept(@RequestBody Reservation reservation) {
    Message build =
        MessageBuilder.withPayload(reservation.getReservationName()) .build();
    this.out.send( build );
}

reservation-service中引入消息队列


    org.springframework.cloud
    spring-cloud-starter-stream-rabbit

在reservation-service的启动类上指定绑定的消息队列:


阶段8:使用zipkin做链路追踪

zipkin-service启动类:

@EnableDiscoveryClient
@EnableZipkinServer
@SpringBootApplication
public class ZipkinServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(ZipkinServiceApplication.class, args);
	}
}

配置文件,注册到eureka上:

spring.application.name=zipkin-service

其它服务要使用时,只需在pom里引入:


			org.springframework.cloud
			spring-cloud-starter-zipkin


阶段8:安全认证

启动类:

使用的就是前述的OAuth2.0

package com.example.authservice;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.security.Principal;
import java.util.Optional;
import java.util.stream.Stream;

@EnableResourceServer
@EnableDiscoveryClient
@SpringBootApplication
public class AuthServiceApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(AuthServiceApplication.class, args);
    }
}

@RestController
class PrincipalRestController {
    
    @RequestMapping("/user")
    Principal p(Principal p) {
        return p;
    }
}

@Configuration
@EnableAuthorizationServer
class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
    
    private final AuthenticationManager authenticationManager;
    
    OAuthConfiguration(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
            .inMemory()
            .withClient("html5").authorizedGrantTypes("password").scopes("openid").secret("password")
            .and()
            .withClient("batch-job-client").authorizedGrantTypes("client_credentials").secret("password").scopes("openid");
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(this.authenticationManager);
    }
}

@Service
class AccountUserDetailsService implements UserDetailsService {
    
    private final AccountRepository accountRepository;
    
    AccountUserDetailsService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return this.accountRepository.findByUsername(username)
            .map(account ->
                 User.withUsername(account.getUsername())
                 .password(account.getPassword()).roles("USER")
                 .build())
            
            .orElseThrow(() -> new UsernameNotFoundException("couldn't find " + username + "!"));
    }
}

@Component
class AccountInitializer implements ApplicationRunner {
    
    private final AccountRepository accountRepository;
    
    AccountInitializer(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }
    
    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        Stream.of("jlong,spring", "dsyer,cloud", "pwebb,boot")
            .map(x -> x.split(","))
            .forEach(tuple -> accountRepository.save(new Account(null, tuple[0], tuple[1], true)));
        
    }
}

interface AccountRepository extends JpaRepository {
    
    Optional findByUsername(String username);
}

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Data
class Account {
    
    @Id
    @GeneratedValue
    private Long id;
    
    private String username, password;
    private boolean active;
}

在代码里激活OAuth2.0,例如reservation-client的启动类里增加注解:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1007008.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号