原服务是基于springcloud的微服务架构,这套服务从2018年使用至今。核心组件是spring-cloud-gateway、oauth2、eureka(nacos)、spring-cloud-config。经过多年的发展,k8s➕servicemesh已经逐步成熟,已经成为研发解放生产力的核心技能和工具。
更多细节参阅:https://xie.infoq.cn/article/2baee95d42ed7f8dd83cec170

一、迁移场景

image.png

servicemesh和springcloud对比

二、servicemesh对比springcloud的优势

  • 微服务基础设施下沉 —— 微服务架构支撑、网络通信、治理等相关能力下沉到基础设施层,业务部门无需投入专人开发与维护,可以有效降低微服务架构下研发与维护成本;
  • 降低升级成本 —— Sidecar支持热升级,降低中间件和技术框架客户端、SDK升级成本;
  • 语言无关 —— 提供多语言服务治理能力;
  • 降低复杂测试、演练成本 —— 降低全链路压测、故障演练成本和业务侵入性。

Feign:
image.png
服务网格:
image.png

spirngcloud依赖客户端sdk集成,需要将业务服务注册到注册中心,调用时feignClient从注册中心获取服务实例进行调用。Java的客户端sdk十分成熟,但是我们在实际开发过程中nodejs服务接入集群十分费劲,没有官方提供的eurekaClient和feignClient可以集成。服务心跳机制、下线、健康检查功能都不健全。所以受语言限制十分严重,同时每个服务接入springcloud都需要集成SDK,并进行重复的配置。
servicemesh使用istio组件,sidercar帮我们解决了服务注册发现和服务调用的负载均衡问题。无需在客户端实现这部分逻辑,使用hostname+port调用即可。

三、在kubesphere中开启servicemesh

参考官方文档:https://www.kubesphere.io/zh/docs/v3.3/pluggable-components/service-mesh/

四、服务间调用

创建2个springboot服务:file-system(调用方) user(被调用方)

方式1:使用resttemplate进行服务间调用。

@Slf4j
@RestController
@RequestMapping("/api")
public class FileController2 {
    
    @Autowired
    private RestTemplate restTemplate;
    
    /**
     * 查询用户信息
     *
     * @return
     */
    @GetMapping("/file/{fid}")
    public JSONObject getLicense(@PathVariable String fid, HttpServletRequest request) {
        //查询数据库获取fid对应file信息
        JSONObject fileInfo = new JSONObject();
        fileInfo.put("fid", fid);
        fileInfo.put("filename", "test.docx");
        //调用用户服务查询uid为1的用户信息
        HttpHeaders headers = getHeadersFromHttpServletRequest(request);
        log.info("Request headers:{}", JSON.toJSONString(headers));
        HttpEntity<String> entity = new HttpEntity<>(headers);
        String url = "http://istio-demo-user:8082/user/1";
        ResponseEntity<JSONObject> userObj = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                JSONObject.class
        );
        fileInfo.putAll(userObj.getBody());
        return fileInfo;
    }

    private static HttpHeaders getHeadersFromHttpServletRequest(HttpServletRequest request) {
        HttpHeaders headers = new HttpHeaders();
        // 获取所有请求头的名称
        Enumeration<String> headerNames = request.getHeaderNames();
        // 遍历请求头,将其添加到 HttpHeaders 中
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            if (headerName.equals("content-length") || headerName.toLowerCase(Locale.ROOT).equals("content-type")) {
                continue;
            }
            headers.add(headerName, headerValue);
        }
        return headers;
    }
}

方式2:模拟FeignClient进行服务调用,替换相关注解

源自开源项目改造:https://github.com/ring1012/istio-fake
添加依赖:

<dependency>
    <groupId>istio.fake</groupId>
    <artifactId>istio-fake</artifactId>
    <version>1.1</version>
    <exclusions>
        <exclusion>
            <artifactId>lombok</artifactId>
            <groupId>org.projectlombok</groupId>
        </exclusion>
    </exclusions>
</dependency>

启用注解

@EnableFakeClients(basePackages = "com.iflytek.filesystem.feign")

改造FeignClient为FakeClient,name为被调用方应用名称➕端口。

@FakeClient(name = "istio-demo-user:8082")
public interface UserFeignClient {
//    http:istio-demo-user:8082/user/1

    /**
     * 根据用户Uid列表获取用户基础信息
     * @param uid
     * @return
     */
    @GetMapping(value = "/user/{uid}")
	JSONObject getUserBaseInfoByUid(@PathVariable Long uid);

}

配置请求头转发,增加业务使用请求头和链路追踪请求头。

链路追踪所需转发的请求头参考:https://istio.io/latest/zh/docs/tasks/observability/distributed-tracing/overview/

@Component
public class FakeConfiguration {

    /**
     * 注入默认透传的请求头
     * @return
     */
    @Bean
    public List<String> tracingHeaderList() {
        return Arrays.asList("x-request-id", "x-b3-traceid",
                "x-b3-spanid",
                "x-b3-parentspanid",
                "x-b3-sampled",
                "x-b3-flags", "uid", "enterprise_id");
    }
}

服务调用

    @Autowired
    private UserFeignClient userFeignClient;

   JSONObject userJsonObject = userFeignClient.getUserBaseInfoByUid(1L);

使用第二种方式可以轻松将feignClient切换到FakeClient,降低服务改造成本。

四、在kubesphere中展示请求链路

流量监控
image.png

链路追踪
image.png