Week 9
week 9
去了一趟保定,生病休息了两三周,继续学习
进阶知识
框架
Spring Cloud
- 服务发现与注册
Eureka 注册中心搭建
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
spring:
application:
name: eureka-server
security:
user:
name: peng
password: 8098
server:
port: 8761
servlet:
encoding:
force: true
charset: utf-8
enabled: true
eureka:
instance:
hostname: 127.0.0.1
server:
# 不从自身拉取注册信息,是一个单独的注册中心,不向注册中心注册自己
enable-self-preservation: false
client:
# 是否把自己注册到 eureka
register-with-eureka: false
# false 表示自己就是注册中心,不需要从 eureka server获取注册信息
fetch-registry: false
# 如果是集群,要配置所有其他server的地址
# 如果不是集群,那没必要向自己注册吧,所以可以注释掉
# service-url:
# default: http://127.0.0.1:8762/eureka/, http://127.0.0.1:8763/eureka/
@SpringBootApplication
@EnableEurekaServer // 添加注解
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class);
}
}
客户端和服务端对于 Eureka 来说都是客户端,都需要向其注册
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka:
client:
service-url:
# 默认服务器
defaultZone: http://127.0.0.1:8761/eureka/
# 拉取注册信息间隔时间
registry-fetch-interval-seconds: 30
instance:
# 心跳间隔时间,证明自身存货
lease-renewal-interval-in-seconds: 2
# 10s 没发心跳就证明我没有存货,请将我踢出
lease-expiration-duration-in-seconds: 10
# 以 IP 作为链接,而不是机器名
prefer-ip-address: true
// 测试
// ok() 注册到 eureka,ok2() 通过 eureka 调 ok()
@RestController
public class TestController {
@Resource
private DiscoveryClient discoveryClient;
@Resource
private RestTemplate restTemplate;
@GetMapping("/ok")
public String ok() {
System.out.println("hello-hello-hello-hello-hello-hello-hello-hello-hello-hello");
return "ok " + new Date();
}
@GetMapping("/ok2")
public String ok2() {
String object = restTemplate.getForObject("http://user-manage/ok", String.class);
return "ok2 " + new Date() + " " + object;
}
@GetMapping("/test")
public Object test() {
List<String> services = discoveryClient.getServices();
for (String service : services) {
System.out.println("service: " + service);
}
List<ServiceInstance> instances = discoveryClient.getInstances("user-manage");
for (ServiceInstance instance : instances) {
System.out.println(new Gson().toJson(instance));
}
return discoveryClient;
}
}
- 负载均衡
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- 服务配置 spring cloud config
Spring Cloud Config的原理是将应用程序的配置存储在远程仓库中,并将其作为一个REST API来访问。它采用了基于Spring Boot的分布式配置中心的思想,将配置文件存放在远程仓库(如GitHub)中,并使用Git来管理配置文件。Spring Cloud Config服务器端连接远程配置文件,客户端连接配置中心服务端获取配置文件信息。当远程仓库有更新时会通知配置中心服务端,客户端会自动获取最新的配置信息。通过这种方式,开发人员可以集中管理配置文件,不同环境不同配置,动态化的配置更新,分环境部署等等。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
spring:
application:
name: config-server
cloud:
uri: http://localhost:8888
username: peng
password: 8098
// 在客户端中,可以通过 @Value 注解来获取配置信息
@Value("${my.property}")
private String myProperty;
在Spring Cloud Config中,可以通过REST接口来更新配置信息。具体来说,可以使用PUT
请求来更新配置文件中的属性值。例如:
curl -X PUT -d "my.property=newValue" http://localhost:8888/myapp/development/my.property
其中,myapp
为应用程序的名称(spring.application.name=myapp),development
为环境名称(spring.profiles.active=development),my.property
为需要更新的属性名,newValue
为新的属性值。更新成功后,客户端会立即获取到新的配置信息。
- 服务限流与熔断 Hystrix
Hystrix是Netflix开源的一个延迟和容错库,用于处理分布式系统的延迟和容错。它通过断路器机制,在分布式系统中某个服务出现故障时,避免整体服务的失败,从而提高系统的弹性。
断路器(Circuit Breaker)是Hystrix中的一个核心概念,它是一个开关装置,当某个服务单元发生故障时,断路器会通过故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常。这样保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延。
在Hystrix中,断路器的状态会根据其打开或关闭的状态来决定行为。当断路器处于关闭状态时,请求会正常调用;而当断路器处于打开状态时,请求会被短路到FallBack响应,不会调用主逻辑。
服务限流是为了防止系统在流量激增时因过载而导致的服务故障。Hystrix并不直接提供限流功能,但可以通过设置断路器的请求总数阀值和错误百分比阀值来实现一定程度的限流效果。当请求总数在快照时间窗内超过了阀值,或者错误百分比超过设定阀值时,断路器会打开,从而防止了更多的请求被调用。
总的来说,Hystrix通过断路器机制实现了服务降级、熔断及限流功能,以提高分布式系统的弹性,避免级联故障的发生。
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
</dependency>
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
semaphore:
maxConcurrentRequests: 10
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import org.springframework.stereotype.Component;
@Component
public class HelloWorldService {
@HystrixCommand(fallbackMethod = "fallbackMethod")
public String sayHello(String name) {
// 在这里执行远程服务的调用
return "Hello " + name + "!";
}
public String fallbackMethod(String name) {
return "Hello Failure " + name + "!";
}
}
@HystrixCommand注解的作用:基于Spring AOP,通过使用代理对象来对被注解的方法进行隔离和监控。当一个被 @HystrixCommand 注解的方法被调用时,Hystrix会将其封装成一个独立的线程池,以确保该方法的执行不会影响其他线程的执行。同时,Hystrix会对该方法的执行进行监控,如果该方法的执行出现异常或超时,Hystrix会触发断路器,防止该方法的调用继续向下传播,从而避免故障的扩散。在实现上,@HystrixCommand 注解会基于AOP框架来工作,通过增强被注解的方法来达到隔离、监控和故障处理的目的。
- 服务链路追踪 Dapper
Dapper是Google开源的一款分布式系统的跟踪系统,它可以帮助开发者更好地监控和解决分布式系统中的性能问题。
在服务链路追踪方面,Dapper可以帮助我们实现以下功能:
- 跟踪请求的完整路径:Dapper可以跟踪每个请求在分布式系统中的完整路径,从入口点到出口点,帮助开发者找出系统中的瓶颈和延迟。
- 监控延迟和错误:Dapper可以监控系统中每个服务的延迟和错误情况,帮助开发者及时发现和解决性能问题。
- 分布式追踪:Dapper可以在分布式系统中实现分布式追踪,跟踪跨越多个服务的请求路径,帮助开发者快速定位跨服务的问题。
- 日志聚合:Dapper可以将系统中的日志信息聚合在一起,方便开发者分析和查询。
- 监控系统状态:Dapper可以监控系统的状态和性能指标,帮助开发者了解系统的实时状况。
总之,Dapper可以帮助开发者更好地了解分布式系统中的请求路径、延迟和错误情况,从而更好地优化系统性能和解决性能问题。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.dapper</groupId>
<artifactId>dapper-core</artifactId>
</dependency>
spring:
cloud:
dapper:
enabled: true
# 服务唯一ID
id: 10001
sampling:
# 采样率 每10个请求生成一条追踪数据,如果设置为0,则关闭采样功能
percentage: 10
log-level: INFO
app-name: myapp
app-version: 1.0.0
# 配置UI界面
ui:
enabled: true
# 界面地址:http://localhost:8074/dapper/hi?name=myApp
path: /dapper/hi
// 服务提供者
@FeignClient(value = "service-provider", path = "/api")
public interface ServiceClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String hello();
}
// 服务消费者
@Service
public class ServiceConsumer {
@Autowired
private ServiceClient serviceClient;
public String callServiceProvider() {
return serviceClient.hello();
}
}
- 服务网关
Spring Cloud Gateway是一个基于Spring Boot的微服务网关,它提供了一种简单且有效的服务路由和治理方式。它与Spring Cloud生态圈中的其他组件集成,例如Eureka、Zuul和Ribbon等,以实现微服务的动态路由、负载均衡、安全等功能。
Spring Cloud Gateway的特性包括:
- 动态路由:基于注解和规则配置,可以实现服务之间的动态路由。
- 负载均衡:通过内置的负载均衡算法,实现对多个服务实例的均衡调用。
- 安全:支持对请求进行身份验证、授权等安全处理。
- 监控:提供对网关性能的监控和日志输出,方便进行故障排查和性能优化。
- 路由策略:支持多种路由策略,如优先级路由、轮询等。
- 扩展性:通过编写自定义过滤器,可以扩展网关的功能。
在使用Spring Cloud Gateway时,需要先引入相关依赖,并在配置文件中进行相关配置。例如,可以通过配置server:port来指定网关的端口号,通过配置spring☁️gateway:discovery:locator:enabled=true来启用从注册中心发现服务的功能。同时,可以将网关注册到注册中心,以便被其他服务发现和使用。
总之,Spring Cloud Gateway作为微服务架构中的重要组件之一,可以帮助开发者实现服务的动态路由、负载均衡、安全等功能,提高系统的可维护性和可扩展性。
- 服务安全
Spring Cloud Security是对Spring Boot应用程序中基于令牌的安全性相关的功能的模块,它使基于OAuth2的SSO更容易。在Spring Cloud中,使用Spring Security可以非常方便地实现应用的安全性保护,同时与微服务架构中的其他组件(如Eureka、Zuul等)集成,实现服务的动态路由和负载均衡等功能。
在实现Spring Cloud Security时,首先需要在每个模块工程中增加spring-boot-starter-security的引用,然后在需要使用安全保护的工程中增加spring-session和Redis的引用。
具体来说,Spring Cloud Security提供了与OAuth2相关的功能,支持在资源服务器之间中继令牌,以及使用嵌入式Zuul代理配置下游身份验证。在授权服务器中,可以使用JWT(Json Web Token)令牌来实现安全性相关的功能。
总之,Spring Cloud Security提供了一套完整的解决方案来实现Spring Boot应用程序的安全性保护,同时与其他微服务架构中的组件集成,实现服务的动态路由、负载均衡和安全等功能。
应用服务器
- JBoss:
- 优点:免费、开放源代码,符合J2EE规范;内存和硬盘空间占用较小;安装简单,只需解压缩并配置环境变量;支持“热部署”;与Web服务器在同一个Java虚拟机中运行,提高运行效率和安全性;支持J2EE-EAR的实施,方便集成和管理。
- 缺点:不直接支持jsp和servlet,需要绑定Tomcat或其他容器;相比其他服务器,其市场份额较小。
- 应用场景:适合开发、集成、部署和管理大型的分布式web应用、网络应用和数据库应用。
- Tomcat:
- 优点:轻量级,适合学习和开发;性能稳定;文档齐全,社区活跃。
- 缺点:相比其他服务器,功能较为简单;在高负载情况下,性能表现可能不如其他服务器。
- 应用场景:适合开发和测试环境,以及轻量级的应用部署。
- Jetty:
- 优点:轻量级,快速且内存占用小;支持http/2, WebSocket等现代协议;可以与多种框架集成,如Spring Boot。
- 缺点:相比其他服务器,功能较为简单;在高负载情况下,性能表现可能不如其他服务器。
- 应用场景:适合需要快速部署和轻量级解决方案的场景。
- Weblogic:
- 优点:属于应用级服务器,支持jsp和servlet,以及更多的java规范;用于开发、集成、部署和管理大型的分布式web应用、网络应用和数据库应用;具有强大的管理和监控功能。
- 缺点:相对其他服务器来说,安装和配置较为复杂;对系统资源要求较高。
- 应用场景:适合需要高性能、大规模的应用部署和管理。
这些服务器的特点和优缺点主要根据其设计目标、实现方式和使用体验等方面来评判的。在选择使用哪种服务器时,需要根据实际的应用需求、系统环境和资源状况来做出决策。
高级篇
性能优化
使用单例
只进行一次实例化,重复利用,比如 spring 的 bean 默认就是单例模式
Future
使用 Future 进行性能优化主要基于异步编程的思想。Future 代表一个异步计算的结果,可以避免阻塞当前线程,提高程序的响应性能。
以下是使用 Future 进行性能优化的主要方式:
- 异步计算:将耗时的计算任务异步执行,不会阻塞当前线程。在计算完成后,可以通过回调函数或者 Future 的 thenApply 方法来处理计算结果。
- 并行计算:使用 Future 可以将多个计算任务并行执行,提高计算效率。可以使用线程池来管理这些并行任务,例如使用 Java 中的 ExecutorService 或者 Python 中的 multiprocessing 模块。
- 异常处理:Future 可以更好地处理异常。在异步计算过程中,如果发生异常,Future 会将异常封装成 ExecutionException,并在计算完成时抛出。这样可以避免传统回调函数中复杂的异常处理逻辑。
- 组合多个异步操作:Future 可以方便地组合多个异步操作。例如,可以使用 Future.thenCompose 方法将多个异步操作串联起来,依次执行。这样可以避免手动管理复杂的控制流程。
需要注意的是,使用 Future 进行性能优化需要注意一些问题,例如并发访问共享变量可能会引起的竞态条件和死锁等问题。因此,在使用 Future 进行性能优化时,需要仔细考虑并发控制和线程安全等问题。
线程池
使用线程池进行性能优化是一种常见的手段,它可以提高程序的并发性能和响应速度。
- 避免频繁创建和销毁线程:在处理大量并发任务时,频繁地创建和销毁线程会带来较大的性能开销。使用线程池可以避免这种情况,线程池会预先创建一定数量的线程,并重复利用这些线程,避免频繁地创建和销毁线程。
- 合理设置线程池的参数:线程池的参数包括核心线程数、最大线程数、队列大小等。这些参数需要根据实际业务场景进行合理设置,以达到最佳的性能表现。例如,如果任务量较大,可以将核心线程数和最大线程数设置得较高,以充分利用系统资源。
- 任务调度策略:线程池提供了不同的任务调度策略,例如直接提交、有界队列、无界队列等。根据实际业务场景选择合适的任务调度策略可以更好地满足性能需求。例如,对于 CPU 密集型任务,可以选择无界队列,以避免任务被阻塞。
- 异步执行任务:线程池可以异步执行任务,即在执行任务的同时,不会阻塞主线程。这样可以提高程序的响应性能,特别是在处理大量并发请求时,可以避免主线程被阻塞,提高系统的吞吐量。
- 线程池监控:可以使用监控工具对线程池进行监控,以便及时发现性能瓶颈和问题。例如,可以使用 JMX 或其他监控工具来监控线程池的运行状态、任务执行情况等。
需要注意的是,使用线程池进行性能优化需要注意一些问题,例如死锁、任务执行超时、线程泄漏等问题。因此,在使用线程池进行性能优化时,需要仔细考虑并发控制和线程安全等问题,并进行适当的调优和监控。
选择就绪
一种在多线程编程中提高程序响应性能的策略。
以下是使用选择就绪进行性能优化的主要方式:
- 减少线程阻塞:在多线程编程中,线程阻塞会带来较大的性能开销。使用选择就绪策略可以减少线程阻塞的情况。具体来说,程序可以根据当前线程的运行状态和其他条件,选择一个就绪的线程来执行,以避免等待其他线程释放资源。这样可以提高程序的响应性能和并发性能。
- 优化任务调度:使用选择就绪策略可以优化任务调度,以更好地利用系统资源。具体来说,程序可以根据当前就绪线程的数量、任务的优先级、CPU 占用情况等条件,选择一个合适的线程来执行任务,以实现更高效的资源利用和任务执行。
- 动态调整线程数:使用选择就绪策略可以根据系统负载情况动态调整线程数。具体来说,程序可以根据当前任务的负载情况、系统资源的使用情况等条件,动态地创建或销毁线程,以实现更好的负载均衡和资源利用。
需要注意的是,使用选择就绪进行性能优化需要注意一些问题,例如线程安全和并发控制等问题。因此,在使用选择就绪进行性能优化时,需要仔细考虑并发控制和线程安全等问题,并进行适当的调优和监控。同时,还需要注意避免过度创建线程导致系统资源耗尽的问题。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SelectiveReady {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
final int taskId = i;
executor.submit(() -> {
try {
// 模拟耗时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " is completed.");
});
}
executor.shutdown();
try {
// 阻塞,直到👇
// 等待所有任务完成 或者 等到上限时间1h
executor.awaitTermination(1, TimeUnit.HOURS);
// 才能继续做其它操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
减少上下文切换
在多任务环境下,频繁的上下文切换会增加系统的开销,因为每次上下文切换都需要保存和恢复任务的执行环境,这会占用大量的处理器时间。
以下是使用减少上下文切换进行性能优化的主要方式:
- 减少任务数量:减少上下文切换的一个方法是减少同时运行的任务数量。如果一个系统同时运行的任务过多,会导致频繁的上下文切换,从而增加了系统的开销。因此,可以将任务按照优先级、执行时间等因素进行分类,并按照一定的策略调度任务,以减少上下文切换的次数。
- 避免不必要的上下文切换:有些上下文切换是可以避免的。例如,有些任务在执行过程中可能需要等待某些条件满足才能继续执行,这时就可以使用阻塞等待的方式,避免上下文切换。另外,还可以使用异步操作、多线程等技术,将等待时间利用起来执行其他任务,从而减少上下文切换的次数。
- 使用线程池:线程池可以重复利用线程资源,避免频繁地创建和销毁线程。这样可以减少系统开销,提高程序的响应性能。在线程池中,可以根据实际业务场景选择合适的线程数量,并合理地管理线程的生命周期,以实现更高效的上下文切换。
- 使用协程:协程是一种轻量级的线程,它可以在单线程中实现多任务的执行。协程不需要像线程那样创建和销毁,也不需要进行复杂的同步和通信操作。因此,使用协程可以减少上下文切换的次数,提高程序的响应性能。
需要注意的是,减少上下文切换需要根据实际业务场景进行适当的优化。如果过度地减少任务数量或降低并发度,可能会导致系统吞吐量下降,反而会影响程序的性能。因此,在进行性能优化时,需要进行适当的调优和监控,以找到最佳的平衡点。