# SA实战 ·《SpringCloud Alibaba实战》第07章-服务治理:实现服务的自动注册与发现

作者:冰河
星球: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)》一文中,我们初步实现了用户微服务、商品微服务和订单微服务之间的交互,但是在实现的过程中,存在一个很明显的问题:那就是将用户微服务所在的IP和端口,以及商品微服务所在的IP和端口硬编码到订单微服务的代码中了。这样的做法存在着非常多的问题。

# 硬编码的问题

如果将用户微服务和商品微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题,其中,最明显的问题有三个,如下所示。

(1)如果用户微服务和商品微服务的IP地址或者端口号发生了变化,则订单微服务将变得不可用,需要对同步修改订单微服务中调用用户微服务和商品微服务的IP地址和端口号。

(2)如果系统中提供了多个用户微服务和商品微服务,则无法实现微服务的负载均衡功能。

(3)如果系统需要支持更高的并发,需要部署更多的用户微服务和商品微服务以及订单微服务,如果将用户微服务和商品微服务的IP地址和端口硬编码到订单微服务,则后续的维护会变得异常复杂。

所以,在微服务开发的过程中,需要引入服务治理功能,实现微服务之间的动态注册与发现。

# 服务治理

如果系统采用了微服务的架构模式,随着微服务数量的不断增多,服务之间的调用关系会变得纵横交错,以纯人工手动的方式来管理这些微服务以及微服务之间的调用关系是及其复杂的,也是极度不可取的。所以,需要引入服务治理的功能。服务治理也是在微服务架构模式下的一种最核心和最基本的模块,主要用来实现各个微服务的自动注册与发现。

引入服务治理后,微服务项目总体上可以分为三个大的模块:服务提供者、服务消费者和注册中心,三者的关系如下图所示。

sa-2022-04-25-001

(1)服务提供者会将自身提供的服务注册到注册中心,并向注册中心发送心跳信息来证明自己还存活,其中,心跳信息中就会包含服务提供者自身提供的服务信息。

(2)注册中心会存储服务提供者上报的信息,并通过服务提供者发送的心跳来更新服务提供者最后的存活时间,如果超过一段时间没有收到服务提供者上报的心跳信息,则注册中心会认为服务提供者不可用,会将对应的服务提供者从服务列表中剔除。

(3)服务消费者会向注册中心订阅自身监听的服务,注册中心会保存服务消费者的信息,也会向服务消费者推送服务提供者的信息。

(4)服务消费者从注册中心获取到服务提供者的信息时,会直接调用服务提供者的接口来实现远程调用。

这里需要注意的是:服务消费者一般会从注册中心中获取到所有服务提供者的信息,根据具体情况实现对具体服务提供者的实例进行访问。

# 注册中心

从上面的分析可以看出,微服务实现服务治理的关键就是引入了注册中心,它是微服务架构模式下一个非常重要的组件,主要实现了服务注册与发现,服务配置和服务的健康检测等功能。

# 服务注册与发现

(1)服务注册:注册中心提供保存服务提供者和服务消费者的相关信息。

(2)服务发现:也可以理解为服务订阅,服务调用者也就是服务消费者,向注册中心订阅服务提供者的信息,注册中心会向服务消费者推送服务提供者的信息。

# 服务配置

(1)配置订阅:服务的提供者和消费者都可以向注册中心订阅微服务相关的配置信息。

(2)配置下发:注册中心能够将微服务相关的配置信息主动推送给服务的提供者和消费者。

# 服务健康检测

注册中心会定期检测存储的服务列表中服务提供者的健康状况,例如服务提供者超过一定的时间没有上报心跳信息,则注册中心会认为对应的服务提供者不可用,就会将服务提供者踢出服务列表。

# 常见的注册中心

能够实现注册中心功能的组件有很多,但是常用的组件大概包含:Zookeeper、Eureka、Consul、Etcd、Nacos等。这里,就给大家简单介绍下这些能够实现注册中心功能的框架或组件。

(1)Zookeeper

接触过分布式或者大数据开发的小伙伴应该都知道,Zookeeper是Apache Hadoop的一个子项目,它是一个分布式服务治理框架,主要用来解决应用开发中遇到的一些数据管理问题,例如:分布式集群管理、元数据管理、分布式配置管理、状态同步和统一命名管理等。在高并发环境下,也可以通过Zookeeper实现分布式锁功能。

(2)Eureka

Eureka是Netflix开源的SpringCloud中支持服务注册与发现的组件,但是后来闭源了。

(3)Consul

Consul 是 HashiCorp 公司推出的开源产品,用于实现分布式系统的服务发现、服务隔离、服务配置,这些功能中的每一个都可以根据需要单独使用,也可以同时使用所有功能。

(4)Etcd

etcd 是一个高度一致的分布式键值存储,它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据。它可以优雅地处理网络分区期间的领导者选举,即使在领导者节点中也可以容忍机器故障。

(5)Nacos

这里,我们重点说下Nacos。Nacos是阿里巴巴开源的一款更易于构建云原生应用的支持动态服务发现、配置管理和服务管理的平台,其提供了一组简单易用的特性集,能够快速实现动态服务发现、服务配置、服务元数据及流量管理,主要如下所示。

  • 服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如IP地址、端口等信 息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
  • 服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
  • 服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)
  • 服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清 单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地存。
  • 服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。

这里,我们选用的注册中心就是阿里巴巴开源的Nacos。

# 搭建Nacos环境

(1)到 https://github.com/alibaba/nacos/releases (opens new window) 链接下载Nacos的安装包,我这里下载的安装包为:nacos-server-1.4.3.zip。

(2)解压Nacos安装包,并在命令行进入到Nacos的bin目录下执行如下命令以单机的方式启动Nacos。

startup.cmd -m standalone
1

注意:如果需要以单机的方式启动Nacos,则需要添加 -m standalone 参数,否则,Nacos会以集群的方式启动。

(3)启动Nacos之后,在浏览器中输入链接http://localhost:8848/nacos 来访问Nacos的管理界面,默认的用户名和密码都是Nacos,如下所示。

sa-2022-04-25-002

输入用户名和密码进入Nacos的管理界面,如下所示。

sa-2022-04-25-003

这里,我们进入到Nacos的服务管理-服务列表菜单下,如下所示。

sa-2022-04-25-004

可以看到,在Nacos的服务管理-服务列表菜单下还没有任何服务,接下来,我们就对项目的代码进行改造。

# 集成Nacos注册中心

引入Nacos注册中心时,我们需要对项目的代码进行一定的改造,以便利用Nacos实现服务的注册与发现功能。

# 改造用户微服务

(1)在用户微服务的pom.xml文件中添加nacos的服务注册与发现依赖,如下所示。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
1
2
3
4

(2)在用户微服务的resources目录下的application.yml文件中添加Nacos注册中心的服务地址配置,如下所示。

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
1
2
3
4
5

(3)在用户微服务的启动类io.binghe.shop#UserStarter上标注@EnableDiscoveryClient注解,如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 启动用户服的类
 */
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(value = { "io.binghe.shop.user.mapper" })
@EnableDiscoveryClient
public class UserStarter {

    public static void main(String[] args){
        SpringApplication.run(UserStarter.class, args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

此时,就完成了对用户微服务的代码改造。

(4)启动用户微服务,并刷新Nacos页面,如下所示。

sa-2022-04-25-005

可以看到,用户微服务已经成功注册到Nacos中。

# 改造其他微服务

我们可以用同样的方式来改造商品微服务和订单微服务的代码,改造好之后,分别启动商品微服务和订单微服务,并再次刷新Nacos的页面,如下所示。

sa-2022-04-25-006

可以看到,用户微服务、商品微服务和订单微服务都已成功注册到Nacos。

# 实现服务发现

按照整个项目的执行流程,用户执行下单操作时,订单微服务会调用用户微服务的接口获取用户的基本信息,会调用商品微服务的接口获取商品的基本信息。在订单微服务中校验用户的合法性和校验商品库存是否充足,如果用户合法并且商品库存充足,就会向订单数据表中记录订单信息并调用商品微服务的接口来扣减商品的库存。

用户微服务和商品微服务作为服务的提供者,而订单微服务作为服务的消费者,如果要实现服务的发现功能,我们还需要对订单微服务的代码进行改造。将订单微服务中硬编码的用户微服务和商品微服务的IP地址和端口号修改成从Nacos中获取。

为了让小伙伴们能够更好的对比修改前和修改后的代码,这里,并没有在订单微服务的 io.binghe.shop.order.service.impl#OrderServiceImpl 类上直接修改,还是将其重命名为 io.binghe.shop.order.service.impl.OrderServiceV1Impl 类,同时,再次将其复制一份并命名为io.binghe.shop.order.service.impl.OrderServiceV2Impl类,在后续的开发过程中,如果涉及到大的代码变动,都会以这种方式进行更新。

# 注入服务发现类

(1)在io.binghe.shop.order.service.impl.OrderServiceV2Impl 类中首先注入DiscoveryClient类的对象,如下所示。

@Autowired
private DiscoveryClient discoveryClient;
1
2

# 创建动态服务地址方法

io.binghe.shop.order.service.impl.OrderServiceV2Impl 类中创建一个从Nacos中通过服务名称获取IP和端口号的方法getServiceUrl(),并在getServiceUrl()方法中将IP和端口号拼接成IP:PORT的形式,如下所示。

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

具体的实现方式就是调用DiscoveryClient对象的getInstances()方法,并传入服务的名称,从Nacos注册中心中获取一个ServiceInstance类型的List集合,从List集合中获取第1个元素,也就是从List集合中获取到一个ServiceInstance对象,从ServiceInstance对象中获取到IP地址和端口号,并将其拼接成IP:PORT的形式。

# 定义服务提供者名称

io.binghe.shop.order.service.impl.OrderServiceV2Impl 类中定义两个成员变量userServer和productServer,表示用户微服务和商品微服务的服务名称,并将其分别复制为server-userserver-product

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

注意:userServer的值需要与用户微服务下的application.yml文件中的如下配置的值相同。

spring:
  application:
    name: server-user
1
2
3

productServer的值需要与商品微服务下的application.yml文件中的如下配置的值相同。

spring:
  application:
    name: server-product
1
2
3

# 修改提交订单逻辑

io.binghe.shop.order.service.impl.OrderServiceV2Impl 类的saveOrder()方法中,将硬编码的用户微服务和商品微服务的IP和端口修改成从Nacos注册中心中获取,涉及改动的代码片段如下所示。

(1)添加获取用户微服务与商品微服务的IP和端口号的代码片段,如下所示。

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

(2)修改使用restTemplate获取用户信息的代码片段,修改前的代码片段如下所示。

User user = restTemplate.getForObject("http://localhost:8060/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
1
2
3
4

修改后的代码片段如下所示。

User user = restTemplate.getForObject("http://" + userUrl + "/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
1
2
3
4

可以看到,订单微服务获取用户微服务信息时,不再是硬编码用户微服务的IP地址和端口号了。

(3)修改使用restTemplate获取商品信息的代码片段,修改前的代码片段如下所示。

Product product = restTemplate.getForObject("http://localhost:8070/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
1
2
3
4

修改后的代码片段如下所示。

Product product = restTemplate.getForObject("http://" + productUrl + "/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
1
2
3
4

可以看到,订单微服务获取商品微服务信息时,不再是硬编码商品微服务的IP地址和端口号了。

(4)修改使用restTemplate扣减商品库存的代码片段,修改前的代码片段如下所示。

Result<Integer> result = restTemplate.getForObject("http://localhost:8070/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
1
2
3
4

修改后的代码片段如下所示。

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

可以看到,订单微服务调用商品微服务的扣减商品库存接口时,不再是硬编码商品微服务的IP地址和端口号了。

注意:修改后的io.binghe.shop.order.service.impl.OrderServiceV2Impl 类的完整源码,小伙伴们可自行查看项目代码,冰河在这里不再赘述。

至此,整个项目就改造完成了。接下来,我们进行测试。

# 测试项目

开发完成后,我们对快速搭建并开发完成的三大微服务进行简单的测试,在测试之前我们需要先在数据表中添加一些测试数据。

# 添加测试数据

(1)在用户表中添加一条id为1001的记录,如下所示。

INSERT INTO `shop`.`t_user`(`id`, `t_username`, `t_password`, `t_phone`, `t_address`) VALUES (1001, 'binghe', 'c26be8aaf53b15054896983b43eb6a65', '13212345678', '北京');
1

(2)在商品数据表中添加几条商品记录,如下所示。

INSERT INTO `shop`.`t_product`(`id`, `t_pro_name`, `t_pro_price`, `t_pro_stock`) VALUES (1001, '华为', 2399.00, 100);
INSERT INTO `shop`.`t_product`(`id`, `t_pro_name`, `t_pro_price`, `t_pro_stock`) VALUES (1002, '小米', 1999.00, 100);
INSERT INTO `shop`.`t_product`(`id`, `t_pro_name`, `t_pro_price`, `t_pro_stock`) VALUES (1003, 'iphone', 4999.00, 100);
1
2
3

# 测试库存不足的情况

(1)分别启动用户微服务、商品微服务和订单微服务。

(2)查询id为1001的商品信息,如下所示。

mysql> select * from t_product where id = 1001;
+------+------------+-------------+-------------+
| id   | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 华为       |     2399.00 |         100 |
+------+------------+-------------+-------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,id为1001的商品的库存为100。

(3)查询订单表和订单条目表中的数据,如下所示。

  • 查询订单表
mysql> select * from t_order;
Empty set (0.00 sec)
1
2

可以看到,订单数据表的数据为空。

  • 查询订单条目表
mysql> select * from t_order_item;
Empty set (0.00 sec)
1
2

可以看到,订单条目数据表的数据为空。

(4)在浏览器中调用订单微服务的下单接口,传入的商品数量为1001,如下所示。

sa-2022-04-21-005

可以看到,返回的信息中,code为500,codeMsg输出的信息为执行失败,data返回的结果为商品库存不足,并且输出了提交的参数信息。

(5)再次查询id为1001的商品信息,如下所示。

mysql> select * from t_product where id = 1001;
+------+------------+-------------+-------------+
| id   | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 华为       |     2399.00 |         100 |
+------+------------+-------------+-------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,商品id为1001的商品库存仍为100,并没有减少。

(6)再次查询订单表和订单条目表中的数据,如下所示。

  • 查询订单表
mysql> select * from t_order;
Empty set (0.00 sec)
1
2

可以看到,订单数据表的数据为空。

  • 查询订单条目表
mysql> select * from t_order_item;
Empty set (0.00 sec)
1
2

可以看到,订单条目数据表的数据为空。

综上,当提交订单时传入的商品数量大于商品的库存数量时,系统会抛出异常,并不会执行提交订单和扣减库存的操作。

# 测试正常下单的情况

(1)在测试库存不足的情况的基础上,我们将调用提交订单的接口时传入的商品数量修改为10,如下所示。

sa-2022-04-21-006

可以看到,当商品库存充足时,调用订单微服务的下单接口,返回的数据为success表示下单成功。

(2)再次查询id为1001的商品信息,如下所示。

mysql> select * from t_product where id = 1001;
+------+------------+-------------+-------------+
| id   | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 华为       |     2399.00 |          90 |
+------+------------+-------------+-------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,id为1001的商品库存由原来的100变更为90,减少了10个库存。

(3)再次查询订单表和订单条目表中的数据,如下所示。

  • 查询订单表
mysql> select * from t_order;
+------------------+-----------+-------------+-------------+-----------+---------------+
| id               | t_user_id | t_user_name | t_phone     | t_address | t_total_price |
+------------------+-----------+-------------+-------------+-----------+---------------+
| 3270016896208896 |      1001 | binghe      | 13212345678 | 北京      |      23990.00 |
+------------------+-----------+-------------+-------------+-----------+---------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,订单数据表中成功记录了订单的信息

  • 查询订单条目表
mysql> select * from t_order_item;
+------------------+------------------+----------+------------+-------------+----------+
| id               | t_order_id       | t_pro_id | t_pro_name | t_pro_price | t_number |
+------------------+------------------+----------+------------+-------------+----------+
| 3270017277890560 | 3270016896208896 |     1001 | 华为       |     2399.00 |       10 |
+------------------+------------------+----------+------------+-------------+----------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,订单条目数据表中成功记录了订单条目的信息。

至此,项目的测试完毕。

另外,小伙伴们在【冰河技术】知识星球获取到源码后,可以自行测试其他异常情况,比如商品不存在的异常和用户不存在的异常,或者自行验证几个其他的异常情况,看提交是否提交,商品库存是否扣减了。

这次,我们只是使用SpringBoot快速搭建了三个微服务项目,从下一篇开始,我们就要逐渐接入SpringCloud Alibaba技术了,小伙伴们,你们准备好了吗?加入【冰河技术】知识星球,一起搞定《SpringCloud Alibaba实战 (opens new window)》吧,加油!!

# 星球服务

加入星球,你将获得:

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)公众号,回复 星球 可以获取入场优惠券。

知识星球:冰河技术