Java学习:Java从入门到精通总结
深入浅出RocketMQ设计思想:深入浅出RocketMQ设计思想
绝对不一样的职场干货:大厂最佳实践经验指南
最近更新:2022年6月25日
个人简介:通信工程本硕、Java程序员。做过科研paper,发过专利,优秀的程序员不应该只是CRUD
点赞 收藏 ⭐留言 都是我最大的动力!
文章目录
- 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
- 做一个预定小应用。保证一份代码原则,并且应用非常小巧,可以快速启动优雅关闭
- 生产可发布,环境一致性
- 配置管理、编译发布和运行环境分离、依赖Spring Cloud config
- 服务注册和发现、后台服务
- API网关和熔断,实现API经济、监控遥测
- 容器云,满足三个原则:端口绑定、编译发布和运行分离、同步并发
- 引入消息队列、API经济、管理作业
- 链路追踪、监控遥测
- 安全加固、认证授权
阶段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的启动类里增加注解:



