最近在极客时间上面学习丁雪丰老师的《玩转 Spring 全家桶》,学到了服务注册中心这块,动手实践了一下,老师的视频录制是3年前,现在也有了些变化,自己也动手解决了一下,只有自己写一写才理解的更加细致一些,不然光看一遍视频,有点走马观花。 现将主要过程记录如下

准备工作

首先在本地docker上面安装Consul

docker pull consul
docker run --name my_consul -d -p 8500:8500 -p 8600:8600/udp consul

在8500端口上面启动了consul, 在浏览器上输入http://localhost:8500/ 就可以看到打开如下图的一个界面。

springCloud nacos用户名密码都是对的为什么服务没有注册上去_ci

编写两个服务

我们这里简单的做两个服务,一个bookshop-service, 它提供读写表的服务;另外一个是book-customer-service, 它调用bookshop-service在控制台输出读取到的数据。

父POM文件定义

<properties>
		<java.version>19</java.version>
		<spring-cloud.version>2022.0.0</spring-cloud.version>
		<spring-boot.version>3.0.0</spring-boot.version>
	</properties>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

在dependencyManagement中引入spring-boot-dependencies和spring-cloud-dependencies

编写bookshop-service服务

首先在POM中引入需要用到的包

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

    </dependencies>

spring-cloud-starter-consul-discovery就是用来和consul通信的包
spring-boot-starter-data-jpa 和 h2是我们访问数据库的包,我们使用内存数据库h2
定义一个model

@Entity
@Table(name = "T_BOOK")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String author;

    @Column(updatable = false)
    @CreationTimestamp
    private Date createTime;
    @UpdateTimestamp
    private Date updateTime;
}

在resouces下面放入schema.sql用来建表和插入数据

drop table t_book if exists;


create table t_book (
    id bigint auto_increment,
    create_time timestamp,
    update_time timestamp,
    name varchar(255),
    author varchar(200),
    primary key (id)
);

insert into t_book (name, author, create_time, update_time) values ('python', 'zhangsan', now(), now());
insert into t_book (name, author, create_time, update_time) values ('hadoop', 'lisi', now(), now());
insert into t_book (name, author, create_time, update_time) values ('java', 'wangwu', now(), now());

定义一个JpaRepository

@RepositoryRestResource(collectionResourceRel = "rest-books", path = "rest-books")
public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findByName(@Param("name") String name);
}

我还加了一个hateoas的注解,可以对外提供restful服务,

定义一个controller提供服务

@RestController
@RequestMapping("/book")
@Slf4j
public class BookController {
    @Autowired
    private BookService bookService;


    @GetMapping(path = "/getAll", params = "!name")
    public List<Book> getAll() {
        return bookService.getAllBook();
    }

    @GetMapping("/{id}")
    public Optional<Book> getById(@PathVariable Long id) {
        Optional<Book> book = bookService.getBook(id);
        return book;
    }

    @GetMapping(path = "/", params = "name")
    public Optional<Book> getByName(@RequestParam String name) {
        return bookService.getBook(name);
    }
}

BookService就是调用BookRepository,这里代码省略
在application.properties中加入如下配置

spring.application.name=bookshop-service
server.port=0

spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.prefer-ip-address=true


spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.health.redis.enabled=false

到这里这个服务就编写好了,我们启动它

springCloud nacos用户名密码都是对的为什么服务没有注册上去_ci_02


我们可以看到这个服务就已经上来了。

编写book-customer-service

它是一个控制台程序,我们为了演示它,也把它弄成了一个service
首先依然是pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.7</version>
        </dependency>

    </dependencies>

定义一个ApplicationRunner来取数据和打印数据

@Component
@Slf4j
public class BookCustomerRunner implements ApplicationRunner {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        showServiceInstances();
        readBooks();

        queryBook(1L);
    }

    private void showServiceInstances() {
        log.info("DiscoveryClient: {}", discoveryClient.getClass().getName());
        discoveryClient.getInstances("bookshop-service").forEach(s -> {
            log.info("Host: {}, Port: {}", s.getHost(), s.getPort());
        });
    }

    private void readBooks() {
        ParameterizedTypeReference<List<Book>> ptr =
                new ParameterizedTypeReference<List<Book>>() {};
        ResponseEntity<List<Book>> list = restTemplate
                .exchange("http://bookshop-service/book/getAll", HttpMethod.GET, null, ptr);
        list.getBody().forEach(c -> log.info("Book: {}", c));
    }

    private void queryBook(Long id){
        Book book = restTemplate
                .getForObject("http://bookshop-service/book/{id}", Book.class, id);
        log.info("Book: {}", book);
    }

}

这里我们可以看到我们使用了“http://bookshop-service/book/getAll”, 它就是我们注册到consul的服务,我们在浏览器输入是拿不到数据的,这里spring帮我们做了转换,我们的showServiceInstances,就是打印出真实的host和port。

在这个示例中我们的输出如下

springCloud nacos用户名密码都是对的为什么服务没有注册上去_spring_03

它打印出来对应的Host和Post,然后打印出来了我们从数据库里读出来的几条数据。

我们来看一下现在consul是怎么显示的, 可以看到book-customer-service也已经上来了。

springCloud nacos用户名密码都是对的为什么服务没有注册上去_ci_04

最后

代码我上传到了https://github.com/dengkun39/redisdemo
在学习的时候我还使用了zookeeper来作为服务注册中心, 代码几乎不需要动,只是需要把spring-cloud-starter-consul-discovery 换成spring-cloud-starter-zookeeper-discovery,然后配置文件中加上spring.cloud.zookeeper.connect-string=localhost:2181。 使用zookeeper遇到一段时间后再访问就出现”I/O error on GET request for "http://bookshop-service/book/getAll": Permission denied: no further information“ 应该是有些设置没有弄到位, 刚开始学习,没有办法细究是怎么回事。

服务注册中心学完了,如果需要对外提供服务应该怎么弄呢?总不能像我这样明明是控制台也把它弄成一个服务吧。 这个问题我暂时不知道答案,留在这里,后面学习了再来回答它。