springboot微服务
新建项目mallproduct
pom.xml中依赖添加:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.edu.mall.product</groupId>
<artifactId>mall-product</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>LATEST</version>
</dependency>
</dependencies>
</project>
在resources下新建product.sql,内容如下:
CREATE database db_products default charset utf8;
create table products (pid int not null primary key auto_increment, pname varchar (200), type varchar (50), price double, createTime timestamp )
在mysql中执行上面的sql语句。
然后在resources下新建application.properties,内容如下:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.152.45:3306/db_products?useSSL=false&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss 可以将返回的时间格式化,否则会返回类似2019-06-04T22:06:56.000+0000的时间格式
spring.jackson.time-zone=GMT-5 数据库的时间与springboot返回的时间不一致,需要修改时区
新建包com.edu.mall.product,新建bean包,并且建立product.java,内容如下:
package com.edu.mall.product.bean;
import java.sql.Timestamp;
public class Product {
private Integer pid;
private String pname;
private String type;
private Double price;
private Timestamp createTime;
@Override
public String toString() {
return "Product{" +
"pid=" + pid +
", pname='" + pname + '\'' +
", type='" + type + '\'' +
", price=" + price +
", createTime=" + createTime +
'}';
}
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Timestamp getCreateTime() {
return createTime;
}
public void setCreateTime(Timestamp createTime) {
this.createTime = createTime;
}
}
新建mapper包,在mapper包中,新建ProductMapper.java,内容如下:
package com.edu.mall.product.mapper;
import com.edu.mall.product.bean.Product;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface ProductMapper {
@Insert("insert into products (pname,type,price) values (#{pname}, #{type}, #{price})")
public Integer add(Product product);
@Delete("delete from products where pid=#{arg1}")
public Integer deleteById(Integer pid);
@Update("update products set pname=#{pname}, type=#{type}, price=#{price} where pid=#{pid}")
public Integer update(Product product);
@Select("select * from products where pid=#{arg1}")
public Product getById(Integer pid);
@Select("select * from products order by pid desc")
public List<Product> queryByList();
}
测试mybatis是否配置成功,App.java中的内容如下:
package com.edu.mall.product;
import com.edu.mall.product.bean.Product;
import com.edu.mall.product.mapper.ProductMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
ProductMapper productMapper = run.getBean(ProductMapper.class);
Product product = new Product();
product.setPname("java入门到精通");
product.setPrice(10.0);
product.setType("计算机");
productMapper.add(product);
run.close();
}
}
点击运行App.java 可以成功插入到mysql数据库中。说明mybatis已经集成成功了。
将上面的App.java中的测试关闭。
新建web包,然后新建Respose.java 内容如下:
package com.edu.mall.product.web;
public class Response {
/**
* 200 表示成功
* 500 表示失败
*/
private String code;
private String msg;
private Object data;
@Override
public String toString() {
return "Response{" +
"code='" + code + '\'' +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
public Response(String code, String msg) {
this.code = code;
this.msg = msg;
}
public Response(String code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
新建controller包,然后新建ProductController.java,内容如下
package com.edu.mall.product.controller;
import com.edu.mall.product.bean.Product;
import com.edu.mall.product.mapper.ProductMapper;
import com.edu.mall.product.web.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* product rest 服务
*/
@RestController
public class ProductController {
@Autowired
private ProductMapper productMapper;
@PostMapping("/soa/product/add")
public Object add(Product product) {
Integer res = productMapper.add(product);
return res == 1 ? new Response("200", "OK") : new Response("500", "Fail");
}
@PutMapping("/soa/product/update")
public Object update(Product product) {
Integer res = productMapper.update(product);
return res == 1 ? new Response("200", "OK") : new Response("500", "Fail");
}
@PostMapping("/soa/product/{id}")
public Object get(@PathVariable("id") Integer id) {
Product product = productMapper.getById(id);
return new Response("200", "OK", product);
}
@DeleteMapping("/soa/product/{id}")
public Object delete(@PathVariable("id") Integer id) {
Integer res = productMapper.deleteById(id);
return res == 1 ? new Response("200", "OK") : new Response("500", "Fail");
}
@GetMapping("/soa/products")
public Object list(Integer id) {
List<Product> products = productMapper.queryByList();
return new Response("200", "OK", products);
}
}
运行App.java,使用postman测试运行成功。
新建项目mallweb
pom.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.edu.mall.product</groupId>
<artifactId>mall-web</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
</dependencies>
</project>
同时将mallproduct中的Product.java和Response.java拷贝到这个项目中,新建App.java
package com.edu.mall.web;
import com.google.gson.Gson;
import org.springframework.web.client.RestTemplate;
import web.Response;
public class App {
static String BASE_URL = "http://127.0.0.1:8080";
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
String body = restTemplate.getForObject(BASE_URL + "/soa/product/1", String.class);
System.out.println(body);
Response response = new Gson().fromJson(body, Response.class);
System.out.println(response);
System.out.println(response.getCode());
System.out.println(response.getMsg());
System.out.println(response.getData());
}
}
把原先的一个大系统,拆分成小的系统
每个小系统分别开发,测试,维护。
调用方式:
服务提供的是什么服务?rest(http),web service,rpc
rest方式可以使用RestTemplate, httpclient
springboot 服务注册与发现
web端调用服务的方式常用的有两种:
1. Nginx,将服务地址配置在Nginx,由web端连接Nginx做代理,Nginx做负载均衡,但是这种方式是静态方式,每部署一台就需要在Nginx配置上。
2. 注册中心,服务方(微服务),调用方。首先服务提供方将服务提供到注册中心,然后调用方从注册中心拿到地址,通常服务提供方把自己的地址(ip:port)提交到注册中心,调用方从注册中心获取ip和端口号,获取之后,就可以直接与服务方连接调用。好处就是服务是可以动态添加和删除。如果获取到多个ip和端口,可以使用负载均衡算法选择其中的一个。还有一个好处就是,调用方只需要知道注册中心的地址,不需要服务端的地址。只需要维护一个ip地址就可以了。
如何实现注册中心?
本节依然使用上面的项目mallproduct和mallweb
zookeeper,consul,etcd,redis,通常使用这几种来作为注册中心。
使用zookeeper来作为注册中心。使用curator框架使用zookeeper,curator对zookeeper进行了封装。
首先加载curator依赖:
服务注册方添加依赖:在mall-Product/pom.xml中:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery-server</artifactId>
<version>4.2.0</version>
</dependency>
服务发现方添加依赖:在mall-web/pom.xml中
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>4.2.0</version>
</dependency>
服务的注册
常见的注册中心:zookeeper, consul, etcd, redis
服务提供方,需要在服务启动的时候,把服务的信息(ip,端口)注册到注册中心(zookeeper)
在mall-product中新建ServiceRegister.java
package com.edu.mall.product;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryOneTime;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class ServiceRegister implements ApplicationRunner {
@Value("${zookeeper.address}")
private String zkAddress;
@Override
public void run(ApplicationArguments args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient(zkAddress, new RetryOneTime(1000));
client.start();
client.blockUntilConnected();
ServiceInstance<Object> instance = ServiceInstance.builder().name("product").address("192.168.170.132").port(8080).build();
ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client).basePath("/soa").build();
serviceDiscovery.registerService(instance);
serviceDiscovery.start();
System.out.println("service register success");
}
}
在application.properties中添加配置:
zookeeper.address=192.168.152.45:2181
运行App.java,控制台显示service register success,说明服务注册成功。
查看zookeeper,使用命令
iie4bu@hostdocker:~/apache-zookeeper-3.5.5-bin/bin$ ./zkCli.sh
[zk: localhost:2181(CONNECTED) 0] ls /
[soa, zookeeper]
[zk: localhost:2181(CONNECTED) 1]
可以看到有了soa节点,查看soa下面的服务:
[zk: localhost:2181(CONNECTED) 2] ls /soa
[product]
[zk: localhost:2181(CONNECTED) 3
可以看到product服务的名字。继续查看:
[zk: localhost:2181(CONNECTED) 3] ls /soa/product
[f63b4b19-e313-419a-acba-5aab6e08fc25]
[zk: localhost:2181(CONNECTED) 4]
查看详细信息:
[zk: localhost:2181(CONNECTED) 4] get /soa/product/f63b4b19-e313-419a-acba-5aab6e08fc25
{"name":"product","id":"f63b4b19-e313-419a-acba-5aab6e08fc25","address":"192.168.170.132","port":8080,"sslPort":null,"payload":null,"registrationTimeUTC":1560151924584,"serviceType":"DYNAMIC","uriSpec":null}
[zk: localhost:2181(CONNECTED) 5]
服务的发现
在项目mallweb中新建Client.java
package com.edu.mall.web;
import com.google.gson.Gson;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryOneTime;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.web.client.RestTemplate;
import web.Response;
import java.util.Collection;
public class Client {
public static void main(String[] args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.152.45:2181", new RetryOneTime(1000));
client.start();
client.blockUntilConnected();
RestTemplate restTemplate = new RestTemplate();
ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client).basePath("/soa").build();
Collection<ServiceInstance<Object>> products = serviceDiscovery.queryForInstances("product");
products.forEach((instance) -> {
System.out.println(instance.getAddress());
System.out.println(instance.getPort());
});
}
}
输出如下:
192.168.170.132
8080
说明可以发现服务。
调用服务,修改Client.java
package com.edu.mall.web;
import com.google.gson.Gson;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryOneTime;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.web.client.RestTemplate;
import web.Response;
import java.util.Collection;
public class Client {
public static void main(String[] args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.152.45:2181", new RetryOneTime(1000));
client.start();
client.blockUntilConnected();
ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client).basePath("/soa").build();
Collection<ServiceInstance<Object>> products = serviceDiscovery.queryForInstances("product");
products.forEach((instance) -> {
RestTemplate restTemplate = new RestTemplate();
String body = restTemplate.getForObject("http://" + instance.getAddress() + ":" + instance.getPort() +"/soa/product/1", String.class);
System.out.println(body);
Response response = new Gson().fromJson(body, Response.class);
});
}
}
输出结果如下:
{"code":"200","msg":"OK","data":{"pid":1,"pname":"python入门","type":"计算机类","price":133.0,"createTime":"2019-06-04 17:06:56"}}
说明调用成功。
这种情况下,每调用一次就往注册中心查一次,一般在正式的环境中不这样做,一般会从缓存中拿取。
服务发现
在进行服务调用的时候,需要先从注册中心获取到服务的地址,然后根据获取到的服务地址进行调用
目前我们只获取到一个服务,如果我们要获取到多个服务如何做?
将mall-product复制一份,改名为mall-product2
修改mall-product2的端口号为9090。同时在ServiceRegister.java中修改端口号为9090:
ServiceInstance<Object> instance = ServiceInstance.builder().name("product").address("192.168.170.132").port(9090).build();
运行mall-product和mall-product2,然后可以看到有两个服务启动:
[zk: localhost:2181(CONNECTED) 22] ls /soa/product
[19568e15-9a90-4174-953f-f00124bc9595, 59eae9c2-525c-455e-839a-d3a2284bcf19]
然后运行mall-web项目的Client.java,输出结果如下:
{"code":"200","msg":"OK","data":{"pid":1,"pname":"python入门","type":"计算机类","price":133.0,"createTime":"2019-06-04 17:06:56"}}
{"code":"200","msg":"OK","data":{"pid":1,"pname":"python入门","type":"计算机类","price":133.0,"createTime":"2019-06-04 17:06:56"}}
输出了两遍,我们需要根据算法做一些选择。随机或者轮训。新建LoadBalance.java
package com.edu.mall.web;
import java.util.List;
public class LoadBalance {
private List<String> services;
private int index = 0;
public LoadBalance(List<String> services) {
this.services = services;
}
public String choose() {
String service = services.get(index);
index++;
if (index >= services.size()) {
index = 0;
}
return service;
}
}
然后修改Client.java
package com.edu.mall.web;
import com.google.gson.Gson;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryOneTime;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.web.client.RestTemplate;
import web.Response;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 服务发现
* 在进行服务调用的时候,需要先从注册中心获取到服务的地址,然后根据获取到的服务地址进行调用
*/
public class Client {
public static void main(String[] args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.152.45:2181", new RetryOneTime(1000));
client.start();
client.blockUntilConnected();
ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client).basePath("/soa").build();
Collection<ServiceInstance<Object>> products = serviceDiscovery.queryForInstances("product");
final List<String> services = new ArrayList<>();
products.forEach((instance) -> {
services.add(instance.getAddress() + ":" + instance.getPort());
});
LoadBalance loadBalance = new LoadBalance(services);
RestTemplate restTemplate = new RestTemplate();
String body = restTemplate.getForObject("http://" + loadBalance.choose() + "/soa/product/1", String.class);
System.out.println(body);
Response response = new Gson().fromJson(body, Response.class);
}
}
这样当多个服务端提供服务时,可以轮训进行服务提供,当某个服务down掉后不影响。
springboot打包运行
方法一
在项目路径下执行:
mvn clean package
可以将项目打包成jar包,但是依赖没有加进去。
使用命令
mvn clean package dependency:copy-dependencies
可以将依赖包拷贝到target/dependency路径下,然后将mall-product-1.0-SNAPSHOT.jar也拷贝到target/dependency下,在target路径下执行:
java -Djava.ext.dirs=dependency com.edu.mall.product.App
执行成功