# SA实战 ·《SpringCloud Alibaba实战》第08章-服务治理:实现服务调用的负载均衡

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)

注意:本项目完整源码加入 冰河技术 (opens new window) 知识星球即可获取,文末有优惠券。

大家好,我是冰河~~

在《SpringCloud Alibaba实战 (opens new window)》专栏的《SA实战 ·《SpringCloud Alibaba实战》服务治理:实现服务的自动注册与发现 (opens new window)》一文中,我们实现了服务的自动注册与发现功能。但是还存在一个很明显的问题,那就是如果用户微服务和商品微服务在服务器上部署多份的话,之前的程序无法实现服务调用的负载均衡功能。本文就带着大家一起实现服务调用的负载均衡功能。

# 负载均衡

在正式优化程序代码之前,我们先来看看什么是负载均衡。说的直白点,负载均衡就是将原本由一台服务器处理的请求根据一定的规则分担到多台服务器上进行处理。目前,大部分系统都实现了负载均衡的功能。

负载均衡根据发生的位置,可以分为服务端负载均衡和客户端负载均衡。

# 服务端负载均衡

服务端负载均衡指的是在服务端处理负载均衡的逻辑,如下图所示。

sa-2022-04-27-001

负载均衡在服务端进行处理,当客户端访问服务端的服务A时,首先访问到服务端的负载均衡器,由服务端的负载均衡器将客户端的请求均匀的分发到服务端部署的两个服务A上。

# 客户端负载均衡

客户端负载均衡指的是在客户端处理负载均衡的逻辑,如下图所示。

sa-2022-04-27-002

负载均衡逻辑在客户端一侧,客户端应用调用服务端的应用A时,在向服务端发送请求时,就已经经过负载均衡的逻辑处理,并直接向服务端的某个服务发送请求。

# 启动多服务

为了实现服务调用的负载均衡功能,我们在本地的IDEA环境中分别启动两个用户微服务进程和两个商品微服务进程。

# 启动多个用户微服务

这里,我们在IDEA开发环境中启动多个用户微服务,其实也就是在同一台机器(服务器)上启动多个用户微服务。启动用户微服务时,默认监听的端口为8060,主要由如下配置决定。

server:
  port: 8060
1
2

在同一台机器(服务器)上启动多个用户微服务时,只需要保证启动的多个用户微服务监听的端口号不同即可。

IDEA中可以通过配置不同的端口号来启动多个相同的服务,如下所示,再配置一个用户微服务,使其端口号为8061。

sa-2022-04-27-003

按照上图所示,在Name一栏输入UserStarter2,点击Main class一栏后面的弹出框按钮,选择用户微服务的启动类io.binghe.shop.UserStarter,最重要的就是在VM options一栏后面添加JVM启动参数-Dserver.port=8061,将新添加的用户微服务的监听端口设置为8061。

配置好之后,在IDEA中分别启动端口为8060和8061的两个用户微服务,启动后打开Nacos的服务列表,如下所示。

sa-2022-04-27-004

可以看到,在服务列表中出现了两个用户微服务的实例,说明两个用户微服务都启动成功了。

# 启动多个商品微服务

与用户微服务类似,在IDEA中再次配置一个端口号为8071的商品微服务,如下所示。

sa-2022-04-27-005

接下来,分别启动端口为8070和8071的两个商品微服务,启动后查看Nacos的服务列表,如下所示。

sa-2022-04-27-006

可以看到,端口为8070和8071的两个商品微服务,也成功启动啦。

# 实现自定义负载均衡

这里,我们通过修改订单微服务的代码来实现自定义负载均衡,由于在整个项目中,订单微服务作为客户端存在,由订单微服务调用用户微服务和商品微服务,所以,这里采用的是客户端负载均衡的模式。

# 修改订单微服务代码

此处实现的逻辑在订单微服务的io.binghe.shop.order.service.impl.OrderServiceV3Impl类中,并且在OrderServiceV3Impl类上会标注@Service("orderServiceV3")注解。订单微服务的代码结构如下所示。

├─shop-order
│  │  pom.xml
│  │  shop-order.iml
│  │
│  └─src
│      └─main
│          ├─java
│          │  └─io
│          │      └─binghe
│          │          └─shop
│          │              │  OrderStarter.java
│          │              │
│          │              └─order
│          │                  ├─config
│          │                  │      LoadBalanceConfig.java
│          │                  │
│          │                  ├─controller
│          │                  │      OrderController.java
│          │                  │
│          │                  ├─mapper
│          │                  │      OrderItemMapper.java
│          │                  │      OrderMapper.java
│          │                  │
│          │                  └─service
│          │                      │  OrderService.java
│          │                      │
│          │                      └─impl
│          │                              OrderServiceV1Impl.java
│          │                              OrderServiceV2Impl.java
│          │                              OrderServiceV3Impl.java
│          │
│          └─resources
│              │  application.yml
│              │
│              └─mapper
│                      OrderItemMapper.xml
│                      OrderMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

首先,在OrderServiceV3Impl类中修改getServiceUrl()方法,使其能够在多个服务地址中随机获取一个服务地址,而不总是获取第一个服务地址。

修改前的代码如下所示。

private String getServiceUrl(String serviceName){
    ServiceInstance serviceInstance = discoveryClient.getInstances(serviceName).get(0);
    return serviceInstance.getHost() + ":" + serviceInstance.getPort();
}
1
2
3
4

修改后的代码如下所示。

private String getServiceUrl(String serviceName){
    List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
    int index = new Random().nextInt(instances.size());
    ServiceInstance serviceInstance = instances.get(index);
    String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
    log.info("负载均衡后的服务地址为:{}", url);
    return url;
}
1
2
3
4
5
6
7
8

在getServiceUrl()方法中,首先通过服务的名称在Nacos中获取到所有的服务实例列表,然后使用随机函数随机生成一个0到服务列表长度减1的整数,而这个随机生成的整数恰好在服务实例列表的下标范围内,然后以这个整数作为下标获取服务列表中的某个服务实例。从而以随机的方式实现了负载均衡,并从获取到的服务实例中获取到服务实例所在服务器的IP地址和端口号,拼接成IP:PORT的形式返回。

接下来,将io.binghe.shop.order.controller.OrderController类中的OrderService修改为注入beanName为orderServiceV3的OrderService对象,如下所示。

@Autowired
@Qualifier(value = "orderServiceV3")
private OrderService orderService;
1
2
3

至此,订单微服务的代码修改完成。

# 测试负载均衡效果

启动订单微服务,并在浏览器或其他测试工具中多次访问链接http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,在订单微服务输出的日志信息中会存在如下所示的日志。

负载均衡后的服务地址为:192.168.0.27:8061
负载均衡后的服务地址为:192.168.0.27:8071
负载均衡后的服务地址为:192.168.0.27:8060
负载均衡后的服务地址为:192.168.0.27:8070
负载均衡后的服务地址为:192.168.0.27:8060
负载均衡后的服务地址为:192.168.0.27:8071
负载均衡后的服务地址为:192.168.0.27:8061
负载均衡后的服务地址为:192.168.0.27:8070
1
2
3
4
5
6
7
8

其中,端口为8060和8061的微服务为用户微服务,端口为8070和8071的微服务为商品微服务。初步实现了订单微服务调用用户微服务和商品微服务的负载均衡。

# 使用Ribbon实现负载均衡

Ribbon是SpringCloud提供的一个能够实现负载均衡的组件,使用Ribbon能够轻松实现微服务之间调用的负载均衡。

# 修改订单微服务代码

在订单微服务中的io.binghe.shop.order.config.LoadBalanceConfig 类的 restTemplate()方法上添加@LoadBalanced注解,如下所示。

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
    return new RestTemplate();
}
1
2
3
4
5

接下来,实现的逻辑在订单微服务的io.binghe.shop.order.service.impl.OrderServiceV4Impl类中,并且在OrderServiceV4Impl类上会标注@Service("orderServiceV4")注解。订单微服务的代码结构如下所示。

├─shop-order
│  │  pom.xml
│  │  shop-order.iml
│  │
│  └─src
│      └─main
│          ├─java
│          │  └─io
│          │      └─binghe
│          │          └─shop
│          │              │  OrderStarter.java
│          │              │
│          │              └─order
│          │                  ├─config
│          │                  │      LoadBalanceConfig.java
│          │                  │
│          │                  ├─controller
│          │                  │      OrderController.java
│          │                  │
│          │                  ├─mapper
│          │                  │      OrderItemMapper.java
│          │                  │      OrderMapper.java
│          │                  │
│          │                  └─service
│          │                      │  OrderService.java
│          │                      │
│          │                      └─impl
│          │                              OrderServiceV1Impl.java
│          │                              OrderServiceV2Impl.java
│          │                              OrderServiceV3Impl.java
│          │                              OrderServiceV4Impl.java
│          │
│          └─resources
│              │  application.yml
│              │
│              └─mapper
│                      OrderItemMapper.xml
│                      OrderMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

在OrderServiceV4Impl类中删除如下代码。

@Autowired
private DiscoveryClient discoveryClient;

private String getServiceUrl(String serviceName){
    List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
    int index = new Random().nextInt(instances.size());
    ServiceInstance serviceInstance = instances.get(index);
    String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
    log.info("负载均衡后的服务地址为:{}", url);
    return url;
}
1
2
3
4
5
6
7
8
9
10
11

在saveOrder()方法中删除如下两行代码。

//从Nacos服务中获取用户服务与商品服务的地址
String userUrl = this.getServiceUrl(userServer);
String productUrl = this.getServiceUrl(productServer);
1
2
3

修改通过restTemplate调用用户微服务和商品微服务的方法。修改前的代码如下所示。

User user = restTemplate.getForObject("http://" + userUrl + "/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = restTemplate.getForObject("http://" + productUrl + "/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}

//#####################此处省略N行代码#########################

Result<Integer> result = restTemplate.getForObject("http://" + productUrl + "/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

修改后的代码如下所示。

User user = restTemplate.getForObject("http://" + userServer + "/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = restTemplate.getForObject("http://" + productServer + "/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}

//#####################此处省略N行代码#########################

Result<Integer> result = restTemplate.getForObject("http://" + productServer + "/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

接下来,将io.binghe.shop.order.controller.OrderController类中的OrderService修改为注入beanName为orderServiceV4的OrderService对象,如下所示。

@Autowired
@Qualifier(value = "orderServiceV4")
private OrderService orderService;
1
2
3

至此,订单微服务的代码修改完成。

# 测试负载均衡效果

启动订单微服务,并在浏览器或其他测试工具中多次访问链接http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,启动的每个用户微服务和商品微服务都会打印相关的日志,说明使用Ribbon实现了负载均衡功能。

注意:这里就不粘贴测试时每个启动的微服务打印的日志了,小伙伴们可自行测试并演示效果。

# 使用Fegin实现负载均衡

Fegin是SpringCloud提供的一个HTTP客户端,但只是一个声明式的伪客户端,它能够使远程调用和本地调用一样简单。阿里巴巴开源的Nacos能够兼容Ribbon,而Fegin又集成了Ribbon,所以,使用Fegin也能够实现负载均衡。

# 修改订单微服务代码

(1)在订单微服务的pom.xml文件中添加Fegin相关的依赖,如下所示。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1
2
3
4

(2)在订单微服务的启动类io.binghe.shop.OrderStarter 上添加 @EnableFeignClients 注解,如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 订单服务启动类
 */
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(value = { "io.binghe.shop.order.mapper" })
@EnableDiscoveryClient
@EnableFeignClients
public class OrderStarter {
    public static void main(String[] args){
        SpringApplication.run(OrderStarter.class, args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

(3)在订单微服务工程shop-order下新建io.binghe.shop.order.fegin包,并在io.binghe.shop.order.fegin下新建UserService接口,并在UserService接口上标注@FeignClient("server-user")注解,其中注解的value属性为用户微服务的服务名称。UserService接口用来远程调用用户微服务的接口,源码如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 调用用户微服务的接口
 */
@FeignClient("server-user")
public interface UserService {
    @GetMapping(value = "/user/get/{uid}")
    User getUser(@PathVariable("uid") Long uid);
}
1
2
3
4
5
6
7
8
9
10

(4)在io.binghe.shop.order.fegin下新建ProductService接口,并在ProductService接口上标注@FeignClient("server-product")注解,其中注解的value属性为商品微服务的服务名称。ProductService接口用来远程调用商品微服务的接口,源码如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 调用商品微服务的接口
 */
@FeignClient("server-product")
public interface ProductService {

    /**
     * 获取商品信息
     */
    @GetMapping(value = "/product/get/{pid}")
    Product getProduct(@PathVariable("pid") Long pid);

    /**
     * 更新库存数量
     */
    @GetMapping(value = "/product/update_count/{pid}/{count}")
    Result<Integer> updateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(5)接下来,实现的逻辑在订单微服务的io.binghe.shop.order.service.impl.OrderServiceV5Impl类中,并且在OrderServiceV5Impl类上会标注@Service("orderServiceV5")注解。订单微服务的代码结构如下所示。

├─shop-order
│  │  pom.xml
│  │  shop-order.iml
│  │
│  ├─src
│  │  └─main
│  │      ├─java
│  │      │  └─io
│  │      │      └─binghe
│  │      │          └─shop
│  │      │              │  OrderStarter.java
│  │      │              │
│  │      │              └─order
│  │      │                  ├─config
│  │      │                  │      LoadBalanceConfig.java
│  │      │                  │
│  │      │                  ├─controller
│  │      │                  │      OrderController.java
│  │      │                  │
│  │      │                  ├─fegin
│  │      │                  │      ProductService.java
│  │      │                  │      UserService.java
│  │      │                  │
│  │      │                  ├─mapper
│  │      │                  │      OrderItemMapper.java
│  │      │                  │      OrderMapper.java
│  │      │                  │
│  │      │                  └─service
│  │      │                      │  OrderService.java
│  │      │                      │
│  │      │                      └─impl
│  │      │                              OrderServiceV1Impl.java
│  │      │                              OrderServiceV2Impl.java
│  │      │                              OrderServiceV3Impl.java
│  │      │                              OrderServiceV4Impl.java
│  │      │                              OrderServiceV5Impl.java
│  │      │
│  │      └─resources
│  │          │  application.yml
│  │          │
│  │          └─mapper
│  │                  OrderItemMapper.xml
│  │                  OrderMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

修改OrderServiceV5Impl类的代码,修改前的代码如下所示。

@Autowired
private RestTemplate restTemplate;

private String userServer = "server-user";
private String productServer = "server-product";
1
2
3
4
5

修改后的代码如下所示。

@Autowired
private UserService userService;
@Autowired
private ProductService productService;
1
2
3
4

修改OrderServiceV5Impl类中saveOrder()的代码,修改前的代码如下所示。

User user = restTemplate.getForObject("http://" + userServer + "/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = restTemplate.getForObject("http://" + productServer + "/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}

//##################此处省略N行代码########################

Result<Integer> result = restTemplate.getForObject("http://" + productServer + "/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

修改后的代码如下所示。

User user = userService.getUser(orderParams.getUserId());
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = productService.getProduct(orderParams.getProductId());
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}

//##################此处省略N行代码########################

Result<Integer> result = productService.updateCount(orderParams.getProductId(), orderParams.getCount());
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以看到,修改后的代码使用Fegin调用远程的用户微服务和商品微服务,已经完全没有了拼接URL路径的痕迹。

接下来,将io.binghe.shop.order.controller.OrderController类中的OrderService修改为注入beanName为orderServiceV5的OrderService对象,如下所示。

@Autowired
@Qualifier(value = "orderServiceV5")
private OrderService orderService;
1
2
3

至此,订单微服务的代码修改完成。

# 测试负载均衡效果

启动订单微服务,并在浏览器或其他测试工具中多次访问链接http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,启动的每个用户微服务和商品微服务都会打印相关的日志,说明使用Ribbon实现了负载均衡功能。

注意:这里就不粘贴测试时每个启动的微服务打印的日志了,小伙伴们可自行测试并演示效果。

好了,至此,我们就用三种方式实现了微服务之间调用的负载均衡,限于篇幅,文中并未给出完整的源代码,想要完整源代码的小伙伴可加入【冰河技术】知识星球获取源码。

另外,一不小心就写了8章了,小伙伴们你们再不上车就跟不上了!!!

# 星球服务

加入星球,你将获得:

1.项目学习:微服务入门必备的SpringCloud Alibaba实战项目、手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】、Seckill秒杀系统项目—进大厂必备高并发、高性能和高可用技能。

2.框架源码:手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】。

3.硬核技术:深入理解高并发系列(全册)、深入理解JVM系列(全册)、深入浅出Java设计模式(全册)、MySQL核心知识(全册)。

4.技术小册:深入理解高并发编程(第1版)、深入理解高并发编程(第2版)、从零开始手写RPC框架、SpringCloud Alibaba实战、冰河的渗透实战笔记、MySQL核心知识手册、Spring IOC核心技术、Nginx核心技术、面经手册等。

5.技术与就业指导:提供相关就业辅导和未来发展指引,冰河从初级程序员不断沉淀,成长,突破,一路成长为互联网资深技术专家,相信我的经历和经验对你有所帮助。

冰河的知识星球是一个简单、干净、纯粹交流技术的星球,不吹水,目前加入享5折优惠,价值远超门票。加入星球的用户,记得添加冰河微信:hacker_binghe,冰河拉你进星球专属VIP交流群。

# 星球重磅福利

跟冰河一起从根本上提升自己的技术能力,架构思维和设计思路,以及突破自身职场瓶颈,冰河特推出重大优惠活动,扫码领券进行星球,直接立减149元,相当于5折, 这已经是星球最大优惠力度!


领券加入星球,跟冰河一起学习《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》,更有已经上新的《大规模分布式Seckill秒杀系统》,从零开始介绍原理、设计架构、手撸代码。后续更有硬核中间件项目和业务项目,而这些都是你升职加薪必备的基础技能。

100多元就能学这么多硬核技术、中间件项目和大厂秒杀系统,如果是我,我会买他个终身会员!

# 其他方式加入星球

特别提醒: 苹果用户进圈或续费,请加微信 hacker_binghe 扫二维码,或者去公众号 冰河技术 回复 星球 扫二维码加入星球。

# 星球规划

后续冰河还会在星球更新大规模中间件项目和深度剖析核心技术的专栏,目前已经规划的专栏如下所示。

# 中间件项目

  • 《大规模分布式定时调度中间件项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式IM(即时通讯)项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式网关项目实战(非Demo)》:全程手撸代码。
  • 《手写Redis》:全程手撸代码。
  • 《手写JVM》全程手撸代码。

# 超硬核项目

  • 《从零落地秒杀系统项目》:全程手撸代码,在阿里云实现压测(已上新)。
  • 《大规模电商系统商品详情页项目》:全程手撸代码,在阿里云实现压测。
  • 其他待规划的实战项目,小伙伴们也可以提一些自己想学的,想一起手撸的实战项目。。。

既然星球规划了这么多内容,那么肯定就会有小伙伴们提出疑问:这么多内容,能更新完吗?我的回答就是:一个个攻破呗,咱这星球干就干真实中间件项目,剖析硬核技术和项目,不做Demo。初衷就是能够让小伙伴们学到真正的核心技术,不再只是简单的做CRUD开发。所以,每个专栏都会是硬核内容,像《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》就是很好的示例。后续的专栏只会比这些更加硬核,杜绝Demo开发。

小伙伴们跟着冰河认真学习,多动手,多思考,多分析,多总结,有问题及时在星球提问,相信在技术层面,都会有所提高。将学到的知识和技术及时运用到实际的工作当中,学以致用。星球中不少小伙伴都成为了公司的核心技术骨干,实现了升职加薪的目标。

# 联系冰河

# 加群交流

本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “冰河” 微信(hacker_binghe),备注:星球编号

冰河微信

# 公众号

分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。内容在 冰河技术 微信公众号首发,强烈建议大家关注。

公众号:冰河技术

# 视频号

定期分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。

视频号:冰河技术

# 星球

加入星球 冰河技术 (opens new window),可以获得本站点所有学习内容的指导与帮助。如果你遇到不能独立解决的问题,也可以添加冰河的微信:hacker_binghe, 我们一起沟通交流。另外,在星球中不只能学到实用的硬核技术,还能学习实战项目

关注 冰河技术 (opens new window)公众号,回复 星球 可以获取入场优惠券。

知识星球:冰河技术