摘要:本文我們就結合案常式序來說明Java記憶體模型中的Happens-Before原則。 本文分享自華為雲社區《【高併發】一文秒懂Happens-Before原則》,作者: 冰 河。 在正式介紹Happens-Before原則之前,我們先來看一段代碼。 【示例一】 class VolatileExa ...
最近在極客時間上面學習丁雪豐老師的《玩轉 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/ 就可以看到打開如下圖的一個界面。
編寫兩個服務
我們這裡簡單的做兩個服務,一個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
到這裡這個服務就編寫好了,我們啟動它
我們可以看到這個服務就已經上來了。
編寫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。
在這個示例中我們的輸出如下
它列印出來對應的Host和Post,然後列印出來了我們從資料庫里讀出來的幾條數據。
我們來看一下現在consul是怎麼顯示的, 可以看到book-customer-service也已經上來了。
最後
代碼我上傳到了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“ 應該是有些設置沒有弄到位, 剛開始學習,沒有辦法細究是怎麼回事。
服務註冊中心學完了,如果需要對外提供服務應該怎麼弄呢?總不能像我這樣明明是控制台也把它弄成一個服務吧。 這個問題我暫時不知道答案,留在這裡,後面學習了再來回答它。