1. Spring Boot 中使用 Dubbo
高并发下Redis会出现的问题:
缓存穿透、缓存雪崩、热点缓存
本节还会演示一下双重检查锁机制
1.1 定义 commons 工程 11-dubboCommons
(1) 创建工程
这里就创建一个普通的 Maven 的 Java 工程,并命名为 11-dubboCommons。
(2) 定义 pom 文件
<groupId>com.abc</groupId>
<artifactId>11-dubboCommons</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>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
(3) 定义实体类
@Data
public class Employee implements Serializable {
private Integer id;
private String name;
private int age;
}
(4) 定义业务接口
public interface EmployeeService {
void addEmployee(Employee employee);
Employee findEmployeeById(int id);
Integer findEmployeeCount();
}
(5) 将工程安装到本地库
运行Maven 的 install 命令,将工程安装到本地版本库,以备其它工程使用。
1.2 定义提供者 11-provider-springboot
(1) 创建工程
创建一个 Spring Boot 工程,并重命名为 11-provider-springboot。
(2) 定义 pom 文件
- A、添加 dubbo 与 spring boot 整合依赖
<!--dubbo与spring boot整合依赖-->
<dependency>
<groupId>com.alibaba.spring.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
- B、 添加 zkClient 依赖
因为dubbo-spring-boot-starter用的dubbo是2.6.0,所以ZK客户端要用zkclient
<!-- zk客户端依赖:zkclient -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
-
C、 其它依赖
-
dubboCommons 依赖
-
spring boot 与 redis 整合依赖
-
mybatis 与 spring boot 整合依赖
-
数据源 Druid 依赖
-
mysql 驱动依赖
-
slf4j-log4j12 依赖
-
spring-boot-starter-web 依赖
(3) 定义 Service 实现类
@Service // Dubbo的注解 <dubbo:service/>
@Component
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeDao dao;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
// 当有对象插入时会清空realTimeCache缓存空间
@CacheEvict(value="realTimeCache", allEntries = true)
@Transactional(rollbackFor = Exception.class)
@Override
public void addEmployee(Employee employee) {
dao.insertEmployee(employee);
}
// 一旦有了查询结果,则会将此结果写入到realTimeCache缓存
// key是employee_加上方法参数
@Cacheable(value="realTimeCache", key = "'employee_'+#id")
@Override
public Employee findEmployeeById(int id) {
// 从DB查询
System.out.println("从DB查询id = " + id);
return dao.selectEmployeeById(id);
}
private volatile Object count;
// 双重检测锁机制解决Reids的热点缓存问题
// 非实时缓存数据
@Override
public Integer findEmployeeCount() {
// 获取Redis操作对象
BoundValueOperations<Object, Object> ops = redisTemplate.boundValueOps("count");
// 从缓存获取数据
count = ops.get();
if(count == null) {
synchronized (this) {
count = ops.get();
if(count == null) {
System.out.println("从DB中查询");
// 从DB中查询
count = dao.selectEmployeeCount();
// 将查询结果存放到Redis
ops.set(count, 10, TimeUnit.SECONDS);
}
}
}
return (Integer) count;
}
}
(4) 定义 Dao 接口
@Mapper // 自动Mapper的动态代理对象
public interface EmployeeDao {
void insertEmployee(Employee employee);
Integer selectEmployeeCount();
Employee selectEmployeeById(int id);
}
(5) 定义映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.abc.dao.EmployeeDao">
<insert id="insertEmployee">
insert into employee(name, age) values(#{name},{age})
</insert>
<select id="selectEmployeeCount" resultType="int">
select count(id) from employee
</select>
<select id="selectEmployeeById" resultType="Employee">
select id, name, age from employee where id=#{xxx}
</select>
</mapper>
数据库已经建好了
(6) 修改启动类
在启动类上必须要添加@EnableDubboConfiguration 注解,开启 Dubbo 的自动配置功能。
@EnableTransactionManagement
@SpringBootApplication
@EnableCaching
@EnableDubboConfiguration // 开启Dubbo自动配置功能
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
(7) 修改主配置文件
server:
port: 8888
mybatis:
注册mybatis中实体类的别名
type-aliases-package: com.abc.bean
注册映射文件
mapper-locations: classpath:com/abc/dao/*.xml
spring:
注册数据源
datasource:
指定数据源类型为Druid
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf8
username: root
password: 111
连接Redis服务器
redis:
host: redis5OS
port: 6379
password: 111
连接Redis高可有集群
redis:
sentinel:
master: mymaster
nodes:
- sentinelOS1:26379
- sentinelOS2:26379
- sentinelOS3:26379
配置缓存
cache:
type: redis 指定缓存类型
cache-names: realTimeCache 指定缓存区域名称
功能等价于spring-boot配置文件中的<dubbo:application/>
application:
name: 11-provider-springboot
指定zk注册中心
dubbo:
registry: zookeeper://zkOS:2181
zk集群作注册中心
registry: zookeeper://zkOS1:2181?backup=zkOS2:2181,zkOS3:2181
1.3 定义消费者 11-consumer-springboot
(1) 创建工程
创建一个 Spring Boot 工程,并重命名为 11-consumer-springboot。
(2) 定义 pom 文件
- dubbo 与 spring boot 整合依赖
- zkClient 依赖
- dubboCommons 依赖
- JSP 引擎 jasper 依赖
- slf4j-log4j12 依赖
- spring-boot-starter-web 依赖
比较特殊的:
<build>
<resources>
<!--注册webapp目录为资源目录-->
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
(3) 修改主配置文件
spring:
功能等价于spring-dubbo配置文件中的<dubbo:application/>
application:
name: 11-consumer-springboot
指定zk注册中心
dubbo:
registry: zookeeper://zkOS:2181
zk集群作注册中心
registry: zookeeper://zkOS1:2181?backup=zkOS2:2181,zkOS3:2181
(4) 创建 index.jsp 页面
在src/main/webapp 目录下定义 index.jsp 文件。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index page</title>
</head>
<body>
<form action="consumer/employee/register" method="post">
姓名:<input type="text" name="name"/> <br>
年龄:<input type="text" name="age"/> <br>
<input type="submit" value="注册"/>
</form>
</body>
</html>
(5) 定义处理器
@Controller
@RequestMapping("/consumer/employee")
public class SomeController {
// @Autowired
@Reference // Dubbo的注解 <dubbo:reference />
private EmployeeService employeeService;
@PostMapping("/register")
public String someHandle(Employee employee, Model model) {
employeeService.addEmployee(employee);
model.addAttribute("employee", employee);
return "/welcome.jsp";
}
@RequestMapping("/find/{id}")
@ResponseBody
public Employee findHandle(@PathVariable("id") int id) {
return employeeService.findEmployeeById(id);
}
@RequestMapping("/count")
@ResponseBody
public Integer countHandle() {
return employeeService.findEmployeeCount();
}
}
@Reference注解:
(6) 定义 welcome.jsp 页面
src/main/webapp 目录下定义 welcome.jsp 文件。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>welcome page</title>
</head>
<body>
welcome you 【 ${employee} 】<br>
</body>
</html>
(7) 修改入口类
@SpringBootApplication
@EnableDubboConfiguration
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
(8) 演示
演示实时缓存(高并发情况下会出现缓存一致性问题):
- 直接查询,发现没有从数据库查询,因为缓存已经有数据了
- 进行插入操作,完成后再次查询,发现从数据库查了,因为插入操作会清空缓存:
非实时缓存演示:
2. generic 泛化服务/泛化引用
在dubbo:service/与dubbo:reference/中都有一个 generic 属性,其在dubbo:service/标签中可以提供泛化服务,在dubbo:reference/中可以提供泛化引用。泛化服务与泛化引用无需同时使用。其主要是针对某一方没有具体业务接口的.class 情况的
。这又是 Dubbo 扩展性的一个体现。
2.1 提供者工程 14-generic-provider
(1) 创建工程
复制02-provider-zk 工程,在其基础之上修改。
(2) 修改 pom
将api 依赖删除,不再需要该业务接口了。
(3) 定义 GerericServiceImpl 类
将原有的 SomeServiceImpl 方法删除,定义如下方法。
public class GenericServiceImpl implements GenericService {
@Override
public Object $invoke(String method, String[] parameterTypes,
Object[] args) throws GenericException {
if ("hello".equals(method)) {
return "Generic hello," + args[0];
}
return null;
}
}
泛化服务接口:
/**
* Generic service interface
*
* @export
*/
public interface GenericService {
/**
* Generic invocation
* 方法名
* @param method Method name, e.g. findPerson. If there are overridden methods, parameter info is
* required, e.g. findPerson(java.lang.String)
* 方法参数类型列表
* @param parameterTypes Parameter types
* 参数具体值
* @param args Arguments
* @return invocation return value
* @throws GenericException potential exception thrown from the invocation
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
default CompletableFuture<Object> $invokeAsync(String method, String[] parameterTypes, Object[] args) throws GenericException {
Object object = $invoke(method, parameterTypes, args);
if (object instanceof CompletableFuture) {
return (CompletableFuture<Object>) object;
}
return CompletableFuture.completedFuture(object);
}
}
(4) 修改 xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="02-provider-zk"/>
<!--声明注册中心:单机版zk-->
<dubbo:registry address="zookeeper://zkOS:2181" />
<dubbo:protocol name="dubbo" port="20880"/>
<!--注册GenericServiceImpl-->
<bean id="someService" class="com.abc.provider.GenericServiceImpl"/>
<!--此时的报红是Idea检测的问题,不影响运行-->
<dubbo:service interface="com.abc.service.SomeService" ref="someService"
generic="true"/>
</beans>
2.2 消费者工程 14-generic-consumer
(1) 创建工程
复制02-provider-zk 工程,在其基础之上修改。
(2) 修改 pom
将api 依赖删除,不再需要该业务接口了。
(3) 修改 xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="02-consumer-zk">
<dubbo:parameter key="qos.port" value="33333"/>
</dubbo:application>
<!--指定服务注册中心:zk单机-->
<dubbo:registry address="zookeeper://zkOS:2181" />
<dubbo:reference id="someService" interface="com.abc.service.SomeService"
check="false" generic="true" />
</beans>
(4) 修改 ConsumerRun
public class ConsumerRun {
public static void main(String[] args) throws IOException {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-consumer.xml");
// 这里的类型为GenericService
GenericService service = (GenericService) ac.getBean("someService");
//调用业务方法
Object hello = service.$invoke("hello", //指定要调用的方法名
new String[]{
String.class.getName()},//方法参数类型列表
new Object[]{
"Tom"});//指定方法参数列表
System.out.println("================ " + hello);
}
}
运行结果:
3. 属性配置优先级
Dubbo 配置文件中各个标签属性配置的优先级总原则是:
- 方法级优先,接口级(服务级)次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
另外,还有两个标签需要说明一下:
<dubbo:consumer/>
设置在消费者端,用于设置消费者端的默认配置,即消费者端的全局设置。<dubbo:provider/>
设置在提供者端,用于设置提供者端的默认配置,即提供者端的默认配置。
4. 配置建议
- Provider 端要配置合理的 Provider 端属性
- Provider 端上尽量多配置 Consumer 端属性