Jason Lee Essay

Only dead fish go with the flow!

Jason Lee Essay

SpringCloud 配置中心优先级

01.02-SpringCloud 配置中心优先级

1. springboot 的配置优先级

命令行参数 > 操作系统环境变量 > 应用外的配置文件 > 应用内的配置文件

这里命令行参数设置的优先级是最高的,方便部署不同环境。

2. 加入 springcloud 配置中心后

加入 springcloud 的配置中心后,优先级并不是想象中的命令行最高优先级。

我实验后得到的优先级有:配置中心 > 命令行参数 > 本地 application.yml > 本地 bootstrap.yml

它的设计者认为,配置中心就是最高优先级的,不允许外部修改。

如果想要覆盖,可在远程配置中加下面配置

1
2
3
4
5
6
spring:  
cloud:
config:
allowOverride: true
overrideNone: true
overrideSystemProperties: false

上面的说法可以在如下链接中得到验证:https://cloud.spring.io/spring-cloud-commons/multi/multi__spring_cloud_context_application_context_services.html#overriding-bootstrap-properties

3. 源码解析

org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration#insertPropertySources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
Binder.get(environment(incoming)).bind("spring.cloud.config",
Bindable.ofInstance(remoteProperties));
// 跟进3个参数的组合,决定远程参数加入environment的开头还是结尾
if (!remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone()
&& remoteProperties.isOverrideSystemProperties())) {
for (PropertySource<?> p : reversedComposite) {
propertySources.addFirst(p);
}
return;
}
if (remoteProperties.isOverrideNone()) {
for (PropertySource<?> p : composite) {
propertySources.addLast(p);
}
return;
}
if (propertySources.contains(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
if (!remoteProperties.isOverrideSystemProperties()) {
for (PropertySource<?> p : reversedComposite) {
propertySources.addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, p);
}
}
else {
for (PropertySource<?> p : composite) {
propertySources.addBefore(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, p);
}
}
}
else {
for (PropertySource<?> p : composite) {
propertySources.addLast(p);
}
}

remoteProperties 默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ConfigurationProperties("spring.cloud.config")
public class PropertySourceBootstrapProperties {

/**
* Flag to indicate that the external properties should override system properties.
* Default true.
*/
private boolean overrideSystemProperties = true;

/**
* Flag to indicate that {@link #isOverrideSystemProperties()
* systemPropertiesOverride} can be used. Set to false to prevent users from changing
* the default accidentally. Default true.
*/
private boolean allowOverride = true;

/**
* Flag to indicate that when {@link #setAllowOverride(boolean) allowOverride} is
* true, external properties should take lowest priority and should not override any
* existing property sources (including local config files). Default false.
*/
private boolean overrideNone = false;
}

org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class, boolean)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}

当应用程序获取参数值(@Value 注解、显示调用 environment.getProperty()时)一样,environment 的查询方式是按照先后顺序查找参数值。

从结果上看,nacos 配置的顺序在命令之前,故 nacos 的优先级更高。

根据 cookie 进行 gateway 路由

01.01-根据 cookie 进行 gateway 路由

1. 设计

参与角色:gateway, service-provider

2. 实现

2.1 LoadBalancer 实现类,参照 RoundRibonLoadBalancer 实现。

功能:跟进前端的 cookie 中的 zone,来匹配 nacos 注册信息中 metadata(元数据)的 version。进行匹配。

其他完全根据 gateway 的路由完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.alibaba.cloud.examples.loadbalancer;

import com.alibaba.cloud.commons.lang.StringUtils;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

/**
* @Title : MyLoadBalancer
* @Description: :
* @author: libo@juzishuke.com
* @date: 2023/9/11 16:51
* @Version:1.0
*/
//@Component
public class MyLoadBalancer implements ReactorServiceInstanceLoadBalancer {

private static final Log log = LogFactory.getLog(MyLoadBalancer.class);

final AtomicInteger position;
final String serviceId;
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

public MyLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String name) {
this(serviceInstanceListSupplierProvider, name, new Random().nextInt(1000));
}

public MyLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String name, int seedPosition) {
this.serviceId = name;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}

@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(
NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(request, supplier,
serviceInstances));
}

private Response<ServiceInstance> processInstanceResponse(Request request,
ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
String zone = ((RequestDataContext) request.getContext()).getClientRequest().getCookies()
.getFirst("zone");
if (StringUtils.isNotBlank(zone)) {
serviceInstances = serviceInstances.stream()
.filter(e -> zone.equalsIgnoreCase(e.getMetadata().get("version"))).collect(Collectors.toList());
}
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(request,
serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(
serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}

private Response<ServiceInstance> getInstanceResponse(Request request,
List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}

// Do not move position when there is only 1 instance, especially some suppliers
// have already filtered instances
if (instances.size() == 1) {
return new DefaultResponse(instances.get(0));
}

// Ignore the sign bit, this allows pos to loop sequentially from 0 to
// Integer.MAX_VALUE
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;

ServiceInstance instance = instances.get(pos % instances.size());

return new DefaultResponse(instance);
}
}

2.2 LoadBalancerConfiguration 配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.alibaba.cloud.examples.config;

import com.alibaba.cloud.examples.loadbalancer.MyLoadBalancer;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/**
* @Title : LoadBalancerConfiguration
* @Description: :
* @author: libo@juzishuke.com
* @date: 2023/9/11 17:16
* @Version:1.0
*/
@Configuration(proxyBeanMethods = false)
public class LoadBalancerConfiguration {


@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new MyLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}

2.3 启动方法

在 service-provider 类上面添加启动参数

1
-Dserver.port=0 -Dspring.cloud.nacos.discovery.metadata.version=v2

-Dserver.port=0 标识随机端口

-Dspring.cloud.nacos.discovery.metadata.version=xxx 标识 snacos 注册的元数据特殊值

2.4 效果

服务 实例端口 meta.version zone=V1 zone=V2 zone=null
gateway 18085
service-provider 18086 V1 Y N N
service-provider 18087 V2 N P N
service-provider 18088 V2 N P N
service-provider 18089 N N Y

2.4.1 zone=V1

2.4.2 zone=V2

第 1 次请求

第 2 次请求

2.4.3 zone=null

联合最左匹配原则

  1. 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

  2. =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。

  3. 尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录。

  4. 索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’)。

  5. 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

  6. 匹配某列的前缀索引,此时可以用到索引,但是如果通配符不是只出现在末尾,
    则无法使用索引。

一组和原则相悖的实验

创建一个表,字段:

1
2
3
4
5
6
7
8
CREATE TABLE `abc` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`a` varchar(64) DEFAULT NULL,
`b` varchar(64) DEFAULT NULL,
`c` varchar(64) DEFAULT NULL
PRIMARY KEY (`id`),
KEY `idx_abc` (`a`,`b`,`c`) USING BTREE
) ENGINE=InnoDB ;

正常的查询

1
2
3
4
5
6
7
explain select * from abc t where t.a='a' and t.b='b' and t.c='c'; // 三列索引
explain select * from abc t where t.a='a' and t.b='b'; // 2
explain select * from abc t where t.a='a' and t.c='c';//1
explain select * from abc t where t.a='a'; //1
explain select * from abc t where t.b='b'; // 0
explain select * from abc t where t.c='c'; // 0
explain select * from abc t where t.a='a' and t.c='c'and t.b>'b'; // 2

以上查询均可按照正常的最左索引进行。

不符合原则的查询

1
explain select * from abc t where  t.b='b' and t.c='c';
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t index idx_abc 201 476 Using where; Using index

结论很奇怪:按最左原则,b和c的查询,由于没有用到a列,所以不应该命中索引。

神奇的地方

1
2
3
4
ALTER TABLE `test`.`abc` 
ADD COLUMN `d` varchar(255) NULL AFTER `c`;
commit;
explain select * from abc t where t.b='b' and t.c='c';
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t ALL 476 Using where

这轮索引没有命中,就因为多了一个字段吗?

猜想

  1. 当表中所有字段均在索引范围内,无论如何查询,都会试图用索引去提速。
  2. B+Tree在构建的时候,对全部字段有特殊处理。

Java中对集合或者数组排序一般有两个方法,类实现Comparable 接口或者 排序时使用定制化接口 Comparator。
以TreeSet为例,日常写法:

1
2
3
4
5
6
7
8
9
10
11
 Set<Integer> sets = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 == o1 ? 0 : o2 > o1 ? 1 : -1;
}
});
sets.add(1);
sets.add(-1);
sets.add(100);
System.out.println(Arrays.toString(sets.toArray(new Integer[]{})));
//[100, 1, -1]

关于上面的写法,还有一种变种,就是改成o2-o1,似乎没有问题,看源码,使用Comparator的时候,是按照正负进行判断的

坑来了

看例子

1
2
3
4
5
6
7
8
9
10
11
Set<Integer> sets = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
sets.add(1);
sets.add(-1);
sets.add(Integer.MIN_VALUE);
System.out.println(Arrays.toString(sets.toArray(new Integer[]{})));
[-2147483648, 1, -1]

是不是不是预想的结果了,-2147483648成了最大值。

原因:

很简单,做个测试 -2147483648 -1 = ? 答案是 2147483648。
数字在边界处会溢出,比如 2147483648+1=-2147483648

最佳实践:

使用Comparator接口工作时,一定使用比较将结果控制在-1 ,0 1 三个值,保证结果稳定,如果使用数字加减排序,有可能因为有数字溢出导致结果异常。

1. Markdown all in one链接

全功能集,下载人数最多的一款,主要用到的格式化功能。

2. Markdown Preview Enhanced链接

  • 自动编辑器及预览滑动同步
  • 导入外部文件
  • Code Chunk
  • Pandoc
  • Prince
  • 电子书
  • 幻灯片
  • 可扩展性
  • LaTeX 数学
  • 导出 PDF, PNG, 以及 JPEG 凭借 Puppeteer
  • 导出漂亮的 HTML(移动端支持)
  • 编译到 GitHub Flavored Markdown
  • 自定义预览 CSS
  • TOC 生成
  • 流程图 / 时序图 以及各种其他种类的图形
  • 嵌入 LaTeX, 渲染 TikZ, Chemfig 等图形
  • Task List (Github Flavored)
  • 图片助手
  • 脚注
  • Front Matter

3. Markdown Shortcuts链接

快捷键合集,支持右键完成各种格式调整操作:加粗、斜体、表格等等

Name Description Default key binding
md-shortcut.showCommandPalette Display all commands ctrl+M ctrl+M
md-shortcut.toggleBold Make **bold** ctrl+B
md-shortcut.toggleItalic Make _italic_ ctrl+I
md-shortcut.toggleStrikethrough Make ~~strikethrough~~
md-shortcut.toggleLink Make [a hyperlink](www.example.org) ctrl+L
md-shortcut.toggleImage Make an image ![](image_url.png) ctrl+shift+L
md-shortcut.toggleCodeBlock Make ```a code block``` ctrl+M ctrl+C
md-shortcut.toggleInlineCode Make `inline code` ctrl+M ctrl+I
md-shortcut.toggleBullets Make * bullet point ctrl+M ctrl+B
md-shortcut.toggleNumbers Make 1. numbered list ctrl+M ctrl+1
md-shortcut.toggleCheckboxes Make - [ ] check list (Github flavored markdown) ctrl+M ctrl+X
md-shortcut.toggleTitleH1 Toggle # H1 title
md-shortcut.toggleTitleH2 Toggle ## H2 title
md-shortcut.toggleTitleH3 Toggle ### H3 title
md-shortcut.toggleTitleH4 Toggle #### H4 title
md-shortcut.toggleTitleH5 Toggle ##### H5 title
md-shortcut.toggleTitleH6 Toggle ###### H6 title
md-shortcut.addTable Add Tabular values
md-shortcut.addTableWithHeader Add Tabular values with header

4. markdown-toc链接

非常好用的目录生成工具,

  • Insert header number sections. 插入目录段落序号
  • Auto active plugin on markdown。
  • Insert anchor for header <a id="markdown-header" name="header"></a> 插入锚到文件头
  • Linking via anchor tags # A 1 → #a-1 自动重拍段落编号
  • Depth control[1-6] with depthFrom:1 and depthTo:6 段落编号支持6级
  • Enable or disable links with withLinks:true
  • Refresh list on save with updateOnSave:true
  • Use ordered list (1. …, 2. …) with orderedList:true

1. 领域-Domain

领域是一个组织所做的事情以及其中所包含的一切。是基于业务来确定的,一个项目或一个产品面对的是什么行业、什么样的组织即项目领域。
领域是具有相对性的概念,一个项目的领域可能是另一个项目的通用子域,因为业务对象是不同的。
在一个项目中,领域需要被划分为多个子域,子域有核心子域,关联子域,通用子域。

1.1. 如何定义领域

  1. 战略设计
  2. 战术设计

2. 子域

子域即对领域中某一类的业务的问题集合,是具体的问题实现。

3. 限界上下文

限界上下文(Bounded Context)定义了每个模型的应用范围,在每个Bounded Context中确保领域模型的一致性。不同的限界上下文中,领域模型可以不用保证一致性。通常我们根据团队的组织、软件系统的每个部分的用法及物理表现(如组件划分,数据库模式)来设置模型的边界。

4. 架构

5. 实体

6. 值对象

7. 聚合根

Swagger-ui Spring-boot集成方法

现在做的Java项目基本上都是前后端分离的,后端只提供api接口即可,和前端交互的时候都是用json,程序员都讨厌写文档,但是不写接口文档肯定要被前端开发喷死.

Swagger-ui介绍

Swagger是个前后端协作的利器,解析代码里的注解生成JSON文件,通过Swagger UI生成网页版的接口文档,可以在上面做简单的接口调试 也可以自动生成文档给前端人员调试

页面:

使用方式

引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
<springfox.swagger2.version>2.8.0</pringfox.swagger2.version>


<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox.swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.swagger.ui.version}</version>
</dependency>

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//包路径
.apis(RequestHandlerSelectors.basePackage("cn.infisa.swagger"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("测试系统")
.description("测试系统")
.version("1.0")
.build();
}
}

标记RestController

主要用到两个注解 @API@ApiOperation
@API
用于注解类
@ApiOperation
用于注解方法

将上述注解加到需要生成接口文档的RestController和RequestMapping上,之后启动服务即可生成接口文档。
输入:http://localhost:8080/swagger-ui.html即可访问API列表。

使用

  1. get接口

  2. shiro get接口

Q&A

1. shiro的集成

  1. 在shiro中放行swagger-ui的页面
1
2
3
4
5
//开放swagger
chains.put("/swagger-resources/**", "anon");
chains.put("/webjars/**", "anon");
chains.put("/v2/**", "anon");
chains.put("/swagger-ui.html/**", "anon");
  1. 请求中加入ctoken参数,方法上增加注解
1
2
3
@ApiImplicitParams({
@ApiImplicitParam(name = "ctoken", required = true, value = "a", paramType = "header"),
})

Requirements

启动一个Kafka集群实例,启动方式参考官网教程。地址为:192.168.1.173:9092

Project

1
2
3
4
5
6
kafka客户端依赖,利用Java-ProducerAPI生成数据发送给kafka。
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.3.0</version>
</dependency>

Main

1
2
3
4
5
6
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.1.173:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer producer = new KafkaProducer<String, String>(props);
ProducerRecord record = new ProducerRecord<String, String>(topic, null, null, JacksonUtils.nonDefaultMapper().toJson(student));

上面这段类似与shell启动一个Producer

1
bin/kafka-console-producer.sh --broker-list 192.168.1.173:9092 --topic student

不过shell启动的方式只能通过控制台输入信息,Java部分通过代码生成数据发送。

运行:

发送端:

1
2
3
4
5
6
7
8
9
10
11
12
发送数据: {"id":77064,"name":"bhdX","password":"xfZUDcVG","age":16}
发送数据: {"id":77065,"name":"HgkO","password":"JkJXpgOp","age":57}
发送数据: {"id":77066,"name":"fIMG","password":"VSivsZtI","age":26}
发送数据: {"id":77067,"name":"nzFk","password":"jupUFQDN","age":42}
发送数据: {"id":77068,"name":"czDo","password":"rhcvkLHO","age":52}
发送数据: {"id":77069,"name":"UzEo","password":"VhBYLAph","age":22}
发送数据: {"id":77070,"name":"Wult","password":"xhXxxwxg","age":22}
发送数据: {"id":77071,"name":"yvAv","password":"eoYLmEqD","age":15}
发送数据: {"id":77072,"name":"nqoF","password":"uvIDROzp","age":15}
发送数据: {"id":77073,"name":"LrXX","password":"JdvTFdEj","age":93}
发送数据: {"id":77074,"name":"HIZA","password":"xKKZBhbY","age":44}
发送数据: {"id":77075,"name":"UuNe","password":"XELywaGT","age":82}

另外启动一个kafka-consumer,查看输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ bin/kafka-console-consumer.sh --bootstrap-server 192.168.1.173:9092 \
--topic student \
--from-beginning
{"id":77064,"name":"bhdX","password":"xfZUDcVG","age":16}
{"id":77065,"name":"HgkO","password":"JkJXpgOp","age":57}
{"id":77066,"name":"fIMG","password":"VSivsZtI","age":26}
{"id":77067,"name":"nzFk","password":"jupUFQDN","age":42}
{"id":77068,"name":"czDo","password":"rhcvkLHO","age":52}
{"id":77069,"name":"UzEo","password":"VhBYLAph","age":22}
{"id":77070,"name":"Wult","password":"xhXxxwxg","age":22}
{"id":77071,"name":"yvAv","password":"eoYLmEqD","age":15}
{"id":77072,"name":"nqoF","password":"uvIDROzp","age":15}
{"id":77073,"name":"LrXX","password":"JdvTFdEj","age":93}
{"id":77074,"name":"HIZA","password":"xKKZBhbY","age":44}
{"id":77075,"name":"UuNe","password":"XELywaGT","age":82}

代码地址:https://gitee.com/jasonlee0529/bigdata-all/tree/master/kafka/kafka-provider

yum安装

过程在官方文档都有:https://www.postgresql.org/download/linux/redhat/

安装完成后修改登录密码:

1
2
3
4
5
[root@localhost ~]# su - postgres
-bash-4.2$ psql -U postgres
postgres=# alter user postgres with password '123456';
ALTER ROLE
postgres=#\q

开启远程访问

修改配置文件__/var/lib/pgsql/11/data/postgresql.conf__ 。将listen_addresses的值改为*。

1
2
3
4
5
#listen_addresses = 'localhost'     # what IP address(es) to listen on;
# comma-separated list of addresses;
# defaults to 'localhost'; use '*' for all
# (change requires restart)
listen_addresses='*'

信任远程连接

1
2
3
4
5
6
7
8
9
[root@localhost ~]# vim /var/lib/pgsql/11/data/pg_hba.conf
# IPv4 local connections:
host all all 127.0.0.1/32 ident

改为:

# IPv4 local connections:
host all all 127.0.0.1/32 ident
host all all 192.168.1.1/24 trust

致命错误: 没有用于主机 “xxx”, 用户 “postgres”, 数据库 “postgres”, SSL 关闭 的 pg_hba.conf 记录

注意pa_hba.conf文件里面的主机地址(address)配置是否正确,链接地址与主机地址是否在同一子网中,注意子网掩码的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 导入包
var gulp = require('gulp'),
concat = require('gulp-concat'),
merge = require('merge-stream'),
preprocess = require('gulp-preprocess'),
cssmin = require('gulp-clean-css'), // css压缩
sass = require('gulp-sass'),
sassImport = require('gulp-sass-import'),
rename = require('gulp-rename'), // 重命名
del = require('del'), // 文件清理
runSequence = require('run-sequence'); // 多任务

gulp.task('clean', function(cb) {
return del("dist", cb);
})
gulp.task('bower', function(cb) {
return gulp.src('bower_components/**')
.pipe(gulp.dest('dist/bower_components'));
})
gulp.task('css', function(cb) {
return gulp.src(['**/**/*.css', '!bower_components/**', '!node_modules/**', '!src/plugin/**'])
.pipe(cssmin({
advanced: true, //类型:Boolean 默认:true [是否开启高级优化(合并选择器等)]
compatibility: '', //保留ie7及以下兼容写法 类型:String 默认:''or'*' [启用兼容模式; 'ie7':IE7兼容模式,'ie8':IE8兼容模式,'*':IE9+兼容模式]
keepBreaks: false, //类型:Boolean 默认:false [是否保留换行]
keepSpecialComments: '*' //保留所有特殊前缀 当你用autoprefixer生成的浏览器前缀,如果不加这个参数,有可能将会删除你的部分前缀
}))
.pipe(gulp.dest('dist/'));
})
gulp.task('sass', function(cb) {
return gulp.src(['**/**/*.scss', '!bower_components/**', '!node_modules/**', '!src/plugin/**'])
.pipe(sassImport({
filename: '_file',
}))
.pipe(sass().on('error', sass.logError))
.pipe(cssmin({
advanced: true, //类型:Boolean 默认:true [是否开启高级优化(合并选择器等)]
compatibility: '', //保留ie7及以下兼容写法 类型:String 默认:''or'*' [启用兼容模式; 'ie7':IE7兼容模式,'ie8':IE8兼容模式,'*':IE9+兼容模式]
keepBreaks: false, //类型:Boolean 默认:false [是否保留换行]
keepSpecialComments: '*' //保留所有特殊前缀 当你用autoprefixer生成的浏览器前缀,如果不加这个参数,有可能将会删除你的部分前缀
}))
.pipe(gulp.dest('dist/'));
})
gulp.task('html', function(cb) {
return gulp.src(['**/**/*.html', '!src/plugin'])
.pipe(gulp.dest('dist/'));
})
gulp.task('image', function(cb) {
return gulp.src(['**/**/*.png', '**/**/*.jpg', '!bower_components/**', '!node_modules/**', '!src/plugin/**'])
.pipe(gulp.dest('dist/'))
})
var folders=[];
gulp.task('js', function(cb) {
var tasks = folders.map(function(element) {
return gulp.src(element + '/**/*.js')
.pipe(concat('all.js'))
.pipe(gulp.dest("dist/" + element))
});

return merge(tasks);
});
gulp.task('env', function(cb) {
var env1 = process.env.NODE_ENV || 'dev';
console.log(process.argv);
return gulp.src('env/env.' + env1 + '.js')
.pipe(concat('env.js'))
.pipe(gulp.dest('dist/env'));
});

const { series, parallel } = require('gulp');
exports.default = series('clean', parallel('bower', 'image', 'css', 'sass', 'html', 'env', 'js'));
var logger = function(msg) {
console.log(msg);
};
0%