此文是一个完整的例子, 包含可运行起来的源码.

此例子包含以下部分:

  • 网关层实现自定义LoadBalancer, 根据Header选取实例
  • 服务中的Feign使用拦截器, 读取Header
  • Feign的LoadBalancer也是用网关一样的实现
  • 使用Web Filter来统一设置header变量, 于业务解耦

自定义LoadBalancer, 读取Header

首先创建一个新模块 hello-mybalancerbyheader, pom文件如下

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>com.cnscud.betazone</groupId>
  7. <artifactId>betazone-root</artifactId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <artifactId>hello-mybalancerbyheader</artifactId>
  11. <version>0.0.1-SNAPSHOT</version>
  12. <name>hello-mybalancerbyheader</name>
  13. <description>Demo project for Spring Boot</description>
  14. <dependencies>
  15. <dependency>
  16. <groupId>com.cnscud.betazone</groupId>
  17. <artifactId>hello-pubtool</artifactId>
  18. <version>0.0.1-SNAPSHOT</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework.cloud</groupId>
  22. <artifactId>spring-cloud-starter-gateway</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.cloud</groupId>
  26. <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.cloud</groupId>
  30. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  31. </dependency>
  32. </dependencies>
  33. </project>

创建一个 application.yml,

  1. server:
  2. port: 9200
  3. spring:
  4. application:
  5. name: betazone-hello-mybalancerbyheader
  6. main:
  7. allow-bean-definition-overriding: true
  8. cloud:
  9. gateway:
  10. discovery:
  11. locator:
  12. lowerCaseServiceId: true
  13. enabled: true
  14. routes:
  15. - id: remotename
  16. uri: lb://betazone-hello-remotename
  17. predicates:
  18. - Path=/remoteapi/**
  19. filters:
  20. - StripPrefix=1
  21. loadbalancer:
  22. ribbon:
  23. enabled: false
  24. eureka:
  25. instance:
  26. prefer-ip-address: true
  27. client:
  28. register-with-eureka: true
  29. fetch-registry: true
  30. prefer-same-zone-eureka: true
  31. service-url:
  32. defaultZone: http://localhost:8001/eureka/
  33. logging:
  34. level:
  35. org.springframework.cloud: debug
  36. com.cnscud.betazone: debug

代理了后面的betazone-hello-remotename服务, 启动, 访问 http://localhost:9200/remoteapi/remote/id/2 说明正常, 后面实例是不停轮询的方式来变化的.

那我们来如何根据header访问后面不同的实例哪? 方法有很多, 我们采用最简洁的办法, 抄袭一个Spring自己的 RoundRobinLoadBalancer,

(代码放在hello-pubtool模块, 一会要复用)

  1. package com.cnscud.betazone.pub.zonebyheader;
  2. import org.apache.commons.lang.StringUtils;
  3. import org.apache.commons.logging.Log;
  4. import org.apache.commons.logging.LogFactory;
  5. import org.springframework.beans.factory.ObjectProvider;
  6. import org.springframework.cloud.client.ServiceInstance;
  7. import org.springframework.cloud.client.loadbalancer.DefaultResponse;
  8. import org.springframework.cloud.client.loadbalancer.EmptyResponse;
  9. import org.springframework.cloud.client.loadbalancer.Request;
  10. import org.springframework.cloud.client.loadbalancer.RequestDataContext;
  11. import org.springframework.cloud.client.loadbalancer.Response;
  12. import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
  13. import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
  14. import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
  15. import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
  16. import org.springframework.http.HttpHeaders;
  17. import reactor.core.publisher.Mono;
  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Random;
  24. import java.util.Set;
  25. import java.util.concurrent.atomic.AtomicInteger;
  26. /**
  27. * 根据header里面的workzone选择合适的实例, 如果没有发现, 则返回所有实例.
  28. *
  29. * A Round-Robin-based implementation of {@link ReactorServiceInstanceLoadBalancer}.
  30. *
  31. * @author Spencer Gibb
  32. * @author Olga Maciaszek-Sharma
  33. */
  34. public class MyBetaMainByHeaderLoadBalancer implements ReactorServiceInstanceLoadBalancer {
  35. private static final Log log = LogFactory.getLog(MyBetaMainByHeaderLoadBalancer.class);
  36. final String defaultKey = "default";
  37. final AtomicInteger position;
  38. final Map<String, AtomicInteger> postionMap = new HashMap<>();
  39. final String serviceId;
  40. ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
  41. /**
  42. * @param serviceInstanceListSupplierProvider a provider of
  43. * {@link ServiceInstanceListSupplier} that will be used to get available instances
  44. * @param serviceId id of the service for which to choose an instance
  45. */
  46. public MyBetaMainByHeaderLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
  47. String serviceId) {
  48. this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
  49. }
  50. /**
  51. * @param serviceInstanceListSupplierProvider a provider of
  52. * {@link ServiceInstanceListSupplier} that will be used to get available instances
  53. * @param serviceId id of the service for which to choose an instance
  54. * @param seedPosition Round Robin element position marker
  55. */
  56. public MyBetaMainByHeaderLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
  57. String serviceId, int seedPosition) {
  58. this.serviceId = serviceId;
  59. this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
  60. this.position = new AtomicInteger(seedPosition);
  61. postionMap.put(defaultKey, this.position);
  62. }
  63. @SuppressWarnings("rawtypes")
  64. @Override
  65. // see original
  66. // https://github.com/Netflix/ocelli/blob/master/ocelli-core/
  67. // src/main/java/netflix/ocelli/loadbalancer/RoundRobinLoadBalancer.java
  68. public Mono<Response<ServiceInstance>> choose(Request request) {
  69. //read headers
  70. HttpHeaders headers = ((RequestDataContext) request.getContext()).getClientRequest().getHeaders();
  71. ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
  72. .getIfAvailable(NoopServiceInstanceListSupplier::new);
  73. return supplier.get(request).next()
  74. .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, headers));
  75. }
  76. private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
  77. List<ServiceInstance> serviceInstances,
  78. HttpHeaders headers ) {
  79. Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances, headers);
  80. if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
  81. ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
  82. }
  83. return serviceInstanceResponse;
  84. }
  85. private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,
  86. HttpHeaders headers) {
  87. if (instances.isEmpty()) {
  88. if (log.isWarnEnabled()) {
  89. log.warn("No servers available for service: " + serviceId);
  90. }
  91. return new EmptyResponse();
  92. }
  93. String workzone = headers.getFirst("workzone");
  94. log.info("getInstanceResponse: workzone-> " + workzone);
  95. String positionKey = defaultKey;
  96. Map<String,String> zoneMap = new HashMap<>();
  97. zoneMap.put("zone",workzone);
  98. final Set<Map.Entry<String,String>> attributes =
  99. Collections.unmodifiableSet(zoneMap.entrySet());
  100. List<ServiceInstance> lastInstanceList = instances;
  101. if(StringUtils.isNotBlank(workzone)) {
  102. lastInstanceList = new ArrayList<>();
  103. for (ServiceInstance instance : instances) {
  104. Map<String, String> metadata = instance.getMetadata();
  105. //根据zone头部判断
  106. if (metadata.entrySet().containsAll(attributes)) {
  107. lastInstanceList.add(instance);
  108. }
  109. }
  110. //此处如果没有发现任何一个instance, 返回所有instance: 请根据自己情况定义
  111. if(lastInstanceList.size() <=0){
  112. lastInstanceList = instances;
  113. }
  114. else {
  115. positionKey = workzone;
  116. }
  117. }
  118. AtomicInteger mypos = postionMap.get(positionKey);
  119. if( mypos == null) {
  120. mypos = new AtomicInteger(new Random().nextInt(1000));
  121. postionMap.put(positionKey, mypos);
  122. }
  123. int pos = Math.abs(mypos.incrementAndGet());
  124. ServiceInstance instance = lastInstanceList.get(pos % lastInstanceList.size());
  125. return new DefaultResponse(instance);
  126. }
  127. }

代码首先定义了一个postionMap, 用来存放不同区域的上次访问的位置, 避免每次都访问第一个实例. 然后获取Header
HttpHeaders headers = ((RequestDataContext) request.getContext()).getClientRequest().getHeaders();

然后读取 "workzone" 字段, 如果存在, 则遍历instances, 发现实例的metadata的zone如果和当前字段一样, 则加到列表里.

检查完成, 使用命中的列表进行轮询...如果没有实例, 则使用所有实例进行轮询.

实例化LoadBalancer(为了复用, 放在hello-pubtool模块里)

  1. /**
  2. * 定义配置, 引入LoadBalancer.
  3. *
  4. * @author Felix Zhang 2021-06-09 16:46
  5. * @version 1.0.0
  6. */
  7. public class MyBetaMainByHeaderLoadBalancerConfiguration {
  8. @Bean
  9. public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
  10. LoadBalancerClientFactory loadBalancerClientFactory) {
  11. String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
  12. return new MyBetaMainByHeaderLoadBalancer(
  13. loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
  14. }
  15. }
声明配置(在hello-mybalancerbyheader模块里使用):

  1. /**
  2. * 声明配置类.
  3. */
  4. @Configuration(proxyBeanMethods = false)
  5. @LoadBalancerClients(defaultConfiguration = MyBetaMainByHeaderLoadBalancerConfiguration.class)
  6. public class LoadBalancerAutoConfiguration {
  7. }

启动应用, 我们来测试一下,

  1. localhost:cnscud.github.io felixzhang$ curl -H 'workzone:beta' "http://localhost:9200/remoteapi/remote/id/2"
  2. World [remotename: 127.0.0.1:9002]
  3. localhost:cnscud.github.io felixzhang$ curl -H 'workzone:beta' "http://localhost:9200/remoteapi/remote/id/2"
  4. World [remotename: 127.0.0.1:9002]

发现返回的都是beta区域的实例, 说明代码正确.

Feign实现根据Header访问不同区域的实例

第一步都是简单的, 我们看看Feign如何也能继承上一层的效果哪? 启用同样的LoadBalancer, 但是从哪读Header哪, 因为不是同一个Request, 显然是读不到的.

为了把代码区分出来, 不破坏原来的例子, 我们复制一个hello-nameservice模块到 hello-nameservicebyheader.

先打开上面一样的配置:

  1. /**
  2. * 配置声明类: .
  3. */
  4. @Configuration(proxyBeanMethods = false)
  5. @LoadBalancerClients(defaultConfiguration = MyBetaMainByHeaderLoadBalancerConfiguration.class)
  6. public class LoadBalancerByHeaderAutoConfiguration {
  7. }

但是这没什么用, 因为读不到Header.

然后我去网上搜了搜, 读了5*5=25篇文章之后.....中间过程省略50000字......

实现个Feign的拦截器

我们先看看原来的Controller代码,

  1. @RequestMapping("/id/{userid}")
  2. public String helloById(@PathVariable("userid") String userid, HttpServletRequest request) {
  3. logger.debug("call helloById with " + userid);
  4. logger.info("[nameservice] workzone header:" + request.getHeader("workzone"));
  5. if (StringUtils.isNotBlank(userid) && StringUtils.isNumeric(userid)) {
  6. return "hello " + feignRemoteNameService.readName(Integer.parseInt(userid)) + getServerName();
  7. }
  8. return "hello guest" + getServerName();
  9. }

里面是可以读取到request.getHeader("workzone")的, 但是进入到feign的服务里面, 显然就读不到了, 而Feign的拦截器可以帮我们把Header放进去.
所以我们首先找个地方把这个header存起来,

因为不知道服务什么情况下会被调用, 有可能是异步, 有可能是线程池, 有可能....总之, 此处省略10000字, 我们实现一个:

实现一个Holder, 存放变量
  1. /**
  2. * Holder: store a String.
  3. * !!!! 仅供参考, 没有全面测试过.
  4. *
  5. * 注意: 测试的几种情况要考虑: 父子线程, 线程池, 高QPS等等: InheritableThreadLocal, TransmittableThreadLocal, HystrixRequestVariableDefault
  6. *
  7. * @author Felix Zhang 2021-06-10 10:37
  8. * @version 1.0.0
  9. */
  10. public class RequestHeaderHolder {
  11. //如果存Map, 好像有问题, 此处用String
  12. private static final ThreadLocal<String> MYLOCAL;
  13. static {
  14. MYLOCAL = new InheritableThreadLocal();
  15. //new TransmittableThreadLocal();
  16. }
  17. public static String get() {
  18. return MYLOCAL.get();
  19. }
  20. public static void set(String value) {
  21. MYLOCAL.set(value);
  22. }
  23. public static void remove() {
  24. MYLOCAL.remove();
  25. }
  26. }

至于有没有问题, 因为不太好验证各种各种极限情况, 所以在实战中迎接战火吧, 总之也就InheritableThreadLocal/TransmittableThreadLocal/HystrixRequestVariableDefault这几个东西了.

然后我们在Controller把header存下来:

  1. RequestHeaderHolder.set(request.getHeader("workzone"));
此处实现一个拦截器:

  1. /**
  2. * Feign Interceptor for transfer header.
  3. *
  4. * @author Felix Zhang 2021-06-10 10:04
  5. * @version 1.0.0
  6. */
  7. public class FeignRequest4ZoneHeaderInterceptor implements RequestInterceptor {
  8. @Override
  9. public void apply(RequestTemplate template) {
  10. //获取之前设置的header
  11. String workzone = RequestHeaderHolder.get();
  12. if(workzone !=null){
  13. template.header("workzone", workzone);
  14. }
  15. }
  16. }

超级简单的拦截器, 把workzone这个header放到RequestTemplate的header里面去.

然后实例化拦截器

  1. /**
  2. * Interceptor configuration 声明拦截器配置.
  3. *
  4. * @author Felix Zhang 2021-06-10 11:09
  5. * @version 1.0.0
  6. */
  7. public class FeignByZoneHeaderConfig {
  8. @Bean("myInterceptor")
  9. public RequestInterceptor getRequestInterceptor() {
  10. return new FeignRequest4ZoneHeaderInterceptor();
  11. }
  12. }

启用配置(全局声明):

  1. @EnableFeignClients(basePackages = "com.cnscud.betazone.hellonameservicebyheader", defaultConfiguration = FeignByZoneHeaderConfig.class)

或者单独声明

  1. @FeignClient(value = "betazone-hello-remotename", configuration = FeignByZoneHeaderConfig.class)

hello-mybalancerbyheader模块里添加路由:

  1. - id: default
  2. uri: lb://betazone-hello-nameservicebyheader
  3. predicates:
  4. - Path=/api/**
  5. filters:
  6. - StripPrefix=1

重新启动应用, 访问 , 发现生效了

  1. localhost:cnscud.github.io felixzhang$ curl -H 'workzone:beta' "http://localhost:9200/api/remote/id/2"
  2. hello World [remotename: 127.0.0.1:9002] [nameservice:127.0.0.1:8203]
  3. localhost:cnscud.github.io felixzhang$ curl -H 'workzone:beta' "http://localhost:9200/api/remote/id/2"
  4. hello World [remotename: 127.0.0.1:9002] [nameservice:127.0.0.1:8203]

返回的一直是beta区域的实例.

来个Web Filter

我们回想一下, 刚才

  1. RequestHeaderHolder.set(request.getHeader("workzone"));

是写在Controller里面的, 这显然不合适, 这个谁也看不懂, 和业务也没啥关系, 那我们可以放到Filter里面去, Filter就很容易了, 就是个普通的Web Filter就行.

在hello-pubtool模块里创建ZoneHeaderFilter, 为了别处公用:


  1. /**
  2. * Web Filter.
  3. *
  4. * @author Felix Zhang 2021-06-10 16:45
  5. * @version 1.0.0
  6. */
  7. public class ZoneHeaderFilter extends OncePerRequestFilter {
  8. private final Log logger = LogFactory.getLog(this.getClass());
  9. @Override
  10. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
  11. throws ServletException, IOException {
  12. logger.info("[ZoneHeaderFilter] workzone header:" + request.getHeader("workzone"));
  13. //给Feign Client用的, 使用位置: FeignRequest4ZoneHeaderInterceptor
  14. RequestHeaderHolder.set(request.getHeader("workzone"));
  15. filterChain.doFilter(request, response);
  16. }
  17. }

这也太简单了....
在Feign的模块里声明 WebZoneHeaderFilterConfig:

  1. /**
  2. * 注入Filter.
  3. *
  4. * @author Felix Zhang 2021-06-10 17:05
  5. * @version 1.0.0
  6. */
  7. @Configuration
  8. public class WebZoneHeaderFilterConfig {
  9. @Bean
  10. public FilterRegistrationBean<ZoneHeaderFilter> zoneheaderFilter() {
  11. FilterRegistrationBean<ZoneHeaderFilter> registrationBean
  12. = new FilterRegistrationBean<>();
  13. registrationBean.setFilter(new ZoneHeaderFilter());
  14. registrationBean.addUrlPatterns("/*");
  15. return registrationBean;
  16. }
  17. }

收工了....

删除掉Controller里面的代码, 重新启动应用, 访问, 发现依然生效了

  1. localhost:cnscud.github.io felixzhang$ curl -H 'workzone:beta' "http://localhost:9200/api/remote/id/2"
  2. hello World [remotename: 127.0.0.1:9002] [nameservice:127.0.0.1:8203]
  3. localhost:cnscud.github.io felixzhang$ curl -H 'workzone:beta' "http://localhost:9200/api/remote/id/2"
  4. hello World [remotename: 127.0.0.1:9002] [nameservice:127.0.0.1:8203]

返回的一直是beta区域的实例.

收工.

感想:

网上的方法非常多, 概念非常多...可能是因为Spring Cloud的可定制性太强了, 组件太多了, 拦截器/Filter/各种配置/Rule/AOP/LoadBalancer各种都可以定制....
反观我的实现, 大部分是抄袭Spring自己的实现, 自己的代码没有几行, 这样我就放心多了...

注意

每个人的情况都不一样, 不同的组件, 不同的实现方式, 都可能造成不一样的效果, 所以仅供参考, 学到手才算真的好!

所有教程里的项目源码: https://github.com/cnscud/javaroom/tree/main/betazone2

感谢网上的各种文章, 太多了, 就不一一贴出来了. (很多写的不全, 过时, 所以读的时候也很费劲, 知识爆炸的时代学习成本也很高, 选择多了也不见得都是好事, 当然也是好事)

感谢

感谢感谢, 感谢帮助我的朋友, 家人. 感谢你们. 2021.6.10

Spring Cloud分区发布实践(6)--灰度服务-根据Header选择实例区域的更多相关文章

  1. Spring Cloud分区发布实践(2) 微服务

    我们准备一下用于查询姓名的微服务. 首先定义一下服务的接口, 新建一个空的Maven模块hello-remotename-core, 里面新建一个类: public interface RemoteN ...

  2. Spring Cloud分区发布实践(1) 环境准备

    最近研究了一下Spring Cloud里面的灰度发布, 看到各种各样的使用方式, 真是纷繁复杂, 眼花缭乱, 不同的场景需要不同的解决思路. 那我们也来实践一下最简单的场景: 区域划分: 服务分为be ...

  3. Spring Cloud分区发布实践(4) FeignClient

    上面看到直接通过网关访问微服务是可以实现按区域调用的, 那么微服务之间调用是否也能按区域划分哪? 下面我们使用FeignClient来调用微服务, 就可以配合LoadBalancer实现按区域调用. ...

  4. Spring Cloud分区发布实践(3) 网关和负载均衡

    注意: 因为涉及到配置测试切换, 中间环节需按此文章操作体验, 代码仓库里面的只有最后一步的代码 准备好了微服务, 那我们就来看看网关+负载均衡如何一起工作 新建一个模块hello-gateway, ...

  5. Spring Cloud分区发布实践(5)--定制ServiceInstanceListSupplier

    现在我们简单地来定制二个 ServiceInstanceListSupplier, 都是zone-preference的变种. 为了方便, 我重新调整了一下项目的结构, 把一些公用的类移动到hello ...

  6. spring boot 2.0.3+spring cloud (Finchley)7、服务链路追踪Spring Cloud Sleuth

    参考:Spring Cloud(十二):分布式链路跟踪 Sleuth 与 Zipkin[Finchley 版] Spring Cloud Sleuth 是Spring Cloud的一个组件,主要功能是 ...

  7. 厉害了,Spring Cloud Alibaba 发布 GA 版本!

    ? 小马哥 & Josh Long ? 喜欢写一首诗一般的代码,更喜欢和你共同 code review,英雄的相惜,犹如时间沉淀下来的对话,历久方弥新. 相见如故,@杭州. 4 月 18 日, ...

  8. Spring Cloud Alibaba发布第二个版本,Spring 发来贺电

    还是熟悉的面孔,还是熟悉的味道,不同的是,这次的配方升级了. 今年10月底,Spring Cloud联合创始人Spencer Gibb在Spring官网的博客页面宣布:阿里巴巴开源 Spring Cl ...

  9. 基于Spring Cloud和Netflix OSS构建微服务,Part 2

    在上一篇文章中,我们已使用Spring Cloud和Netflix OSS中的核心组件,如Eureka.Ribbon和Zuul,部分实现了操作模型(operations model),允许单独部署的微 ...

随机推荐

  1. Message、Handler、Message Queue、Looper 之间的关系

    单线程模型中Message.Handler.Message Queue.Looper之间的关系 1.Message Message即为消息,可以理解为线程间交流的信息.处理数据后台线程需要更新UI,你 ...

  2. Java新一代单元测试框架JUnit5速览

    为什么学JUnit5 Java技术栈的单元测试框架有两个:JUnit和TestNG,有种说法是TestNG比JUnit更强大,学TestNG就够了,但是当我打开GitHub看到star的时候,犹豫了: ...

  3. 熬夜总结vue3中setUp函数的2个参数详解

    1.setUp函数的第1个参数props setup(props,context){} 第一个参数props: props是一个对象,包含父组件传递给子组件的所有数据. 在子组件中使用props进行接 ...

  4. python随机漫步

  5. [HTML]图像标签<img>的用法、属性及路径问题

    图像标签:<img>        用法:<img src = "图像地址"> 图像标签的属性 属性 说明 src 指明图像的地址(分为相对路径和绝对路径两 ...

  6. 模拟windows10计算器的实现

    用户界面部分: import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.HashMap; impo ...

  7. 4、oracle表操作

    4.1.dml操作: 1.查看当前用户下所有的表: select * from user_tables; 2.查看某表的大小: select sum(bytes)/(1024*1024) as &qu ...

  8. 2、mysql编译安装

    2.1前言: 此文档介绍的是cmake编译安装的方式: 二进制的安装方式在linux运维_集群_01中有详细的安装说明(已经编译完成,进行初始操作即可) 初始化操作时需要对编译好的mysql进行一下备 ...

  9. QQ邮箱获取授权码方法

    1.登录QQ邮箱,点击"设置" 2.点击"账户" 3.开启POP3/SMTP服务 4.点击"生成授权码" 5.完成验证后,即可生成授权码 P ...

  10. 【译】在运行时编辑代码的 .NET 热重载

    今天,我们很高兴向你介绍 Visual Studio 2019 中 16.11(预览版1)中的 .NET 热重载(通过 .NET 6(预览版4)中的 dotnet watch 命令行工具).在这篇文章 ...