JoyLau's Blog

JoyLau 的技术学习与思考

引用本地包并打包部署的问题

背景

  • 在最近的开发中需要对接C++服务提供的ZeroC Ice接口,客户机环境安装了和服务环境相同的Ice,服务端的Ice比较老,是3.4.0的版本
    在maven的中央仓库中没有找到ice-3.4.0的jar包,只能引用安装路径下提供的jar了

那么常用的写法是这样的:(包括但不限于SpringBoot)

1
2
3
4
5
6
7
8
<!--Ice-->
<dependency>
<groupId>Ice</groupId>
<artifactId>Ice</artifactId>
<version>3.4.0</version>
<scope>system</scope>
<systemPath>${basedir}/src/lib/Ice.jar</systemPath>
</dependency>

我是在src下新建的lib目录,在开发编译的是没有问题的。

在进行打包的时候发现Ice.jar没有被打进去

相对于这个应用来说,打成jar包是最合适的做法了

这里说一下,用package打包,不要用SpringBoot插件的jar打包

解决

build里加上这一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<build>
..............
<resources>
<resource>
<directory>src/lib</directory>
<targetPath>BOOT-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<targetPath>BOOT-INF/classes/</targetPath>
</resource>
</resources>
</build>

之后,再打包,再解压一看,果然是打进去了,完美~

然后,遇到了新问题……..

以jar运行时没有主清单属性

之后便很愉快的使用 java -jar xxxxx.jar

提示:没有主清单属性

再解压一看,有Application.java类,但是jar包的大小明显不对,光SpringBoot父项目依赖的jar至少也有10+M了,这个大小明显不对

在结合没有主属性的错误,知道了错误的原因在这:

1
2
3
4
5
6
7
8
9
10
11
   <dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependencyManagement>

我用的项目是多模块依赖

解决的方式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot-dependencies.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</build>

正如我文章截图的那样,解决问题!

父项目依赖,打包成jar

同时加入以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

2017年9月19日更新

SpringBoot 项目打包时修改 MANIFEST.MF 文件

一般情况下我们的 MANIFEST.MF内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Manifest-Version: 1.0
Implementation-Title: joylau-media
Implementation-Version: 1.7-RELEASE
Archiver-Version: Plexus Archiver
Built-By: JoyLau
Implementation-Vendor-Id: cn.joylau.code
Spring-Boot-Version: 1.5.4.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: cn.joylau.code.JoylauMediaApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.5.0
Build-Jdk: 1.8.0_45
Implementation-URL: http://projects.spring.io/spring-boot/joylau-media
/

解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--fork : 如果没有该项配置,肯呢个devtools不会起作用,即应用不会restart -->
<fork>true</fork>
</configuration>
</plugin>

//修改版本号,一般为pom文件的版本
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifestEntries>
<Manifest-Version>${version}</Manifest-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>

SpringBoot 项目中引入缓存

  • 引入依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

@EnableCaching 开启缓存

@CacheConfig(cacheNames = “api_cache”) 配置一个缓存类的公共信息

@Cacheable() 注解到方法上开启缓存

@CachePut() 根据使用的条件来执行具体的方法

@CacheEvict() 根据配置的参数删除缓存

SpringBoot默认支持很多缓存,spring.cache.type就可以知道,默认的是实现的是SimpleCacheManage,这里我记一下怎么设置缓存的超时时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableCaching
@EnableScheduling
public class CachingConfig {
public static final String CACHENAME = "api_cache";
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager(CACHENAME);
}
@CacheEvict(allEntries = true, value = {CACHENAME})
@Scheduled(fixedDelay = 120 * 1000 , initialDelay = 500)
public void reportCacheEvict() {
System.out.println("Flush Cache " + dateFormat.format(new Date()));
}
}

这里巧妙的使用了 定时任务,再其加上注解CacheEvict来清除所有cache name 为 api——cache 的缓存,超时时间是120s

在说说我比较喜欢的使用方式

单独写了篇文章,戳下面:

持续更新中…

问题

  • 用jackson 作为json转换器的时候,如果传入的json的key 比接收对象多的话,就会报错

解决

先看下SpringMVC原来的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>

这里的json转换器配置的是:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

我们进入到这个类中发现,这个类是继承的 AbstractJackson2HttpMessageConverter

AbstractJackson2HttpMessageConverter 继承的是 AbstractHttpMessageConverter<Object>
找到这个包下面 有一个类 GsonHttpMessageConverter 同样继承的 AbstractHttpMessageConverter<Object>
OK,就是他了

1
2
3
4
5
6
7
8
9
10
11
12
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.json.GsonHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>

这样,参数就随便你整吧,多点少点杜无所谓,完全匹配不上就返回个{}给你

来看下fastjson

fastjson下面有这个一个 package : com.alibaba.fastjson.support.spring

根据字面意思可知,这里是对spring的支持

找到下面这个class FastJsonHttpMessageConverter

1
public class FastJsonHttpMessageConverter extends AbstractHttpMessageConverter<Object>

OK,这个类同样也是继承了 AbstractHttpMessageConverter

只要把这个类注入进去就可以了

SpringBoot使用FastJSON解析数据

  • 第一种继承WebMvcConfigurerAdapter,重写configureMessageConverters方法:
1
2
3
4
5
6
7
8
9
10
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
FastJsonHttpMessageConverter converter=new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig= new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
converter.setFastJsonConfig(fastJsonConfig);
converters.add(converter);

}
  • 第二种方式bean注入HttpMessageConverters:
1
2
3
4
5
6
7
8
9
@Bean  
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastConverter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> converter = fastConverter;
return new HttpMessageConverters(converter);
}

2017年普通高等学校招生全国统一考试

程序员的高考试卷(A卷) `考生类别:码农`

1、程序员A:借我1000元吧。程序员B:给你凑个整数。程序员B借给程序员A多少钱?()

   A. 1000元
   B. 1024元
   C. 1111元

2、程序员A:嘿 //是什么意思啊?程序员B:嘿.程序员A:呃 我问你//是什么意思?程序员B:问吧.程序员A:我刚才不是问了么?程序员B:啊?程序员A到底问了程序员B什么问题?()

   A. 嘿
   B. 呃 我问你
   C. //是什么意思

3、为什么程序员分不清万圣节和圣诞节?()

   A. 因为 31 OCT == 25 DEC
   B. 程序员只有加班/不加班,不过节
   C. 程序员没有女朋友,不过节

4、程序员最怕弹出的窗口是()

   A.   选项A

   B.   选项B

   C.   选项C

5、程序员:哎,太累了日子没法过了,怎么才能换行啊?()

   A. 打回车
   B. 不换行,日子不过了
   C. 除了敲代码,都不会,换行还是敲代码啊

6、程序员会给自己的孩子起什么名字?()

   A. 依依、灵灵、依灵、灵依、依初
   B. Ctrl、Alt 、Delete
   C. 程序员怎么会有女朋友?

7、如何快速挣到一百万?()

   A. while
   B. 买彩票
   C. 当乞丐

8、程序员下班前给老婆打电话:老婆,晚饭我带回来吃,你说买些啥?老婆:买1斤包子吧,如果遇到卖西瓜的,就买一个。程序员买包子时,看到旁边有人在卖西瓜。那么,程序员带了什么晚饭回家?()

   A. 1斤包子
   B. 1个包子
   C. 1个西瓜

9、我GET不到你的笑点,怎么办?()

   A. 智商不在一条线
   B. 太矮了,踩凳子上
   C. 用Post试试

10、为什么吸烟的程序员不在乎香烟盒上的那个警告?()

   A. 字太小
   B. 程序员眼中只有程序
   C. 不关心Warning,只关心Error

11、一对程序员恋人面对面坐着,他们在做什么?()

   A. 面向对象编程
   B. 喝咖啡
   C. 抱怨产品经理

12、老板:小程,下班前新版本一定要上线!小程:好的。第二天,老板上班,问小程:新版本怎么还没上线? 小程怎么回答的?()

   A. 版本出问题了
   B. 版本上线前需求又改了
   C. 我还没下班呢

![Title](//s3.joylau.cn:9000/blog/gaokao-title.jpg)

2017年普通高等学校招生全国统一考试

程序员的高考试卷(B卷) `考生类别:码神`

1、以下哪个概念和公孙龙的《指物论》中的“指”字含义相近?()

   A. 变量
   B. 数组
   C. 对象
   D. 指针

2、蔺相如,司马相如;魏无忌,长孙无忌。下列哪一组对应关系与此类似( )

   A. PHP,Python
   B. JSP,servlet
   C. java,java script
   D. C,C++

3、秦始皇吞并六国采用了以下哪种算法思想?( )

   A. 递归
   B. 分治
   C. 迭代
   D. 模拟

4、雅典王子忒修斯勇闯克里特岛斩杀米诺牛的时候采用了以下哪种算法?( )

   A. 动态规划
   B. 穷举
   C. 记忆化搜索
   D. Dijkstra算法

5、众里寻他千百度,蓦然回首,那人却在灯火阑珊处(辛弃疾《青玉案》)。所体现的算法是:( )

   A. 贪心
   B. 回溯
   C. 穷举
   D. 分治

6、《公孙龙子》记载:“齐王之谓尹文曰:‘寡人甚好士,以齐国无士,何也?’尹文曰:‘愿闻大王之所谓士者。’齐王无以应。”这说明了齐王:( )

   A. 昏庸无道
   B. 是个结巴
   C. 不会下定义
   D. 不会定义自己的需求

7、惠施曾提出过“卵有毛”的命题,以下哪一项是导致这个错误命题的原因:( )

   A. 混淆了命名空间
   B. 引入了错误的包
   C. 衍生类未重载
   D. 调用了危险的指针

8、下面哪种面向对象的方法可以让你变得富有?( )

   A. 继承
   B. 封装
   C. 多态
   D. 抽象

那么你能答对几题呢? 下期发布标准答案 滑稽

先来一张集合的

java-skill-tree1

Java核心技术总结

java-skill-tree2

J2EE技术总结

java-skill-tree3

工作学习总结

java-skill-tree4

大数据相关技术总结

java-skill-tree5

来看看Java工程师技能表

java-skill-tree6
java-skill-tree8
java-skill-tree9

恐怖的Linux大法

java-skill-tree7

Im Back

今天被问了一道题,是这样的:

求解:一筐鸡蛋:
1个1个拿,正好拿完
2个2个拿,还剩1个
3个3个拿,正好拿完
4个4个拿,还剩1个
5个5个拿,还差1个
6个6个拿,还剩3个
7个7个拿,正好拿完
8个8个拿,还剩1个
9个9个拿,正好拿完
问筐里最少有多少鸡蛋

能算出这道题的智商不一般!求答案?有高手没,算算吧!

”5个5个拿,是还差1个“,也就是还剩下4个,这是这个题目的一个小陷阱…

我第一反应想到的是这个数一定是63的倍数,但是后来就没有什么想法了。

再后来,我想到了一个残暴的方法,穷举法

1
2
3
4
5
6
7
8
9
10
int i = 1;
while (true) {
System.out.println(i);
if (i % 2 == 1 && i % 3 == 0 && i % 4 == 1 && i % 5 == 4 && i % 6 == 3 && i % 7 == 0
&& i % 8 == 1 && i % 9 == 0) {
System.out.println("鸡蛋数=" + i);
break;
}
i++;
}

执行后正确答案是1449;

能被7整除,能被9整除,所以肯定是63的倍数
如果利用63的倍数来做写的话:

1
2
3
4
5
6
7
8
9
int i = 1;
while (true) {
int num = 63 *i;
if (num%5==4&&num%6==3&&num%8==1) {
System.out.println("鸡蛋数=" + num);
break;
}
i++;
}

答案依旧是1449,稍微显得动了点头脑,但还是穷举法,有什么高大上的解法么???在下默默献上膝盖!

IntelliJIDEA-Plugins

说明

我现在用的这个插件时ECTranslation,是用于做中英文翻译的,可以在看文档和注释的是方便的使用,然而近期变得不好用了

  • 翻译的内容有时能出来,有时出不来,有时甚至没有反应
  • 查看了该款插件的源代码,发现是调用的有道翻译的API接口,而且在代码里写死了APIkey和KeyFrom
  • 调用了有道的API,加上上面作者提供的Key,再传入翻译的文本内容,发现返回值居然是请求次数过多,被封禁了…..
  • 明白了,很多使用这个插件的开发者都是用的作者提供的默认Key,默认情况下1小时请求的限制次数是1000次
  • 肯定是次数超了
  • 但是他的配置信息是写在代码里的,能配置到IDEA的面板上供使用者自己配置就好了
  • 于是我有了自己动手的想法

开始项目

第一步创建IDEA插件项目:
IntelliJIDEA-Build
第二步目录结构如下图所示:
IntelliJIDEA-Folder

项目配置

plugin.xml

看代码,相信能看懂的:

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
<idea-plugin>
<id>cn.joylau.plugins.translation</id>
<name>joylau-translation</name>
<version>1.0</version>
<vendor email="2587038142.liu@gmail" url="http://www.joylau.cn">JoyLau</vendor>

<description><![CDATA[
Plugin for translate English to Chinese.<br>
<li>1. Choose the word you want translate.</li>
<li>2. Press Ctrl + NUMPAD0.</li>
<li>3. Fork ECTranslation Change ApiKey and KeyFrom</li>

]]></description>

<change-notes><![CDATA[
<li>Change ApiKey and KeyFrom for myself</li>
<li>Change KeyMap to Ctrl + NumPad 0</li>
]]>
</change-notes>

<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->
<idea-version since-build="141.0"/>

<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
on how to target different products -->
<!-- uncomment to enable plugin in all products
<depends>com.intellij.modules.lang</depends>
-->

<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
</extensions>

<actions>
<!-- Add your actions here -->
<action id="ECTranslation" class="cn.joylau.plugins.translation.ECTranslation" text="Translate">
<add-to-group group-id="EditMenu" anchor="first"/>
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl NUMPAD0"/>
</action>
</actions>

</idea-plugin>

只有一个action ,调用的类是ECTranslation,快捷键设置的ctrl + NumPad 0

最后

代码都是人家的,我就没好意思往IDEA的仓库里上传了…

如果你想使用这个插件: 点击查看点击下载

更新(2023-08-02)

最近发现有道 API 的接口不能用了,无法返回翻译数据,于是重新修改了下插件,发布最新版本

地址: https://github.com/JoyLau/joylau-translation/releases/tag/V2

目前有个问题就是,现在的有道 API 是收费的,刚开始注册送 50 元,不过很快会用完的, 到时再换其他的翻译 API 吧

Redis-Master&Slave

配置

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
################################# REPLICATION #################################

# Master-Slave replication. Use slaveof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
# 1) Redis replication is asynchronous, but you can configure a master to
# stop accepting writes if it appears to be not connected with at least
# a given number of slaves.
# 2) Redis slaves are able to perform a partial resynchronization with the
# master if the replication link is lost for a relatively small amount of
# time. You may want to configure the replication backlog size (see the next
# sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
# network partition slaves automatically try to reconnect to masters
# and resynchronize with them.
#
slaveof xx.xx.xx.xx 6379

# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the slave to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the slave request.
#
masterauth xx

# When a slave loses its connection with the master, or when the replication
# is still in progress, the slave can act in two different ways:
#
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
# still reply to client requests, possibly with out of date data, or the
# data set may just be empty if this is the first synchronization.
#
# 2) if slave-serve-stale-data is set to 'no' the slave will reply with
# an error "SYNC with master in progress" to all the kind of commands
# but to INFO and SLAVEOF.
#
slave-serve-stale-data yes

# You can configure a slave instance to accept writes or not. Writing against
# a slave instance may be useful to store some ephemeral data (because data
# written on a slave will be easily deleted after resync with the master) but
# may also cause problems if clients are writing to it because of a
# misconfiguration.
#
# Since Redis 2.6 by default slaves are read-only.
#
# Note: read only slaves are not designed to be exposed to untrusted clients
# on the internet. It's just a protection layer against misuse of the instance.
# Still a read only slave exports by default all the administrative commands
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
# security of read only slaves using 'rename-command' to shadow all the
# administrative / dangerous commands.
slave-read-only no

参数解释

  • slaveof : Slave库配置Master的ip地址和端口号
  • masterauth :如果Master配置了密码,那么这里设置密码
  • slave-serve-stale-data : 如果Master宕机了,Salve是否继续提供服务
  • slave-read-only : Slave 是否是只读模式,默认为是

部分配置项解释

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
daemonize yes #是否以后台进程运行,默认为no 
pidfile /var/run/redis.pid #如以后台进程运行,则需指定一个pid,默认为/var/run/redis.pid
bind 127.0.0.1 #绑定主机IP,默认值为127.0.0.1(注释)
port 6379 #监听端口,默认为6379
timeout 300 #超时时间,默认为300(秒)
loglevel notice #日志记slave-serve-stale-data yes:在master服务器挂掉或者同步失败时,从服务器是否继续提供服务。录等级,有4个可选值,debug,verbose(默认值),notice,warning
logfile /var/log/redis.log #日志记录方式,默认值为stdout
databases 16 #可用数据库数,默认值为16,默认数据库为0
save 900 1 #900秒(15分钟)内至少有1个key被改变
save 300 10 #300秒(5分钟)内至少有300个key被改变
save 60 10000 #60秒内至少有10000个key被改变
rdbcompression yes #存储至本地数据库时是否压缩数据,默认为yes
dbfilename dump.rdb #本地数据库文件名,默认值为dump.rdb
dir ./ #本地数据库存放路径,默认值为 ./

slaveof 10.0.0.12 6379 #当本机为从服务时,设置主服务的IP及端口(注释)
masterauth elain #当本机为从服务时,设置主服务的连接密码(注释)
slave-serve-stale-data yes #在master服务器挂掉或者同步失败时,从服务器是否继续提供服务。
requirepass elain #连接密码(注释)

maxclients 128 #最大客户端连接数,默认不限制(注释)
maxmemory #设置最大内存,达到最大内存设置后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理后,任到达最大内存设置,将无法再进行写入操作。(注释)
appendonly no #是否在每次更新操作后进行日志记录,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认值为no
appendfilename appendonly.aof #更新日志文件名,默认值为appendonly.aof(注释)
appendfsync everysec #更新日志条件,共有3个可选值。no表示等操作系统进行数据缓存同步到磁盘,always表示每次更新操作后手动调用fsync()将数据写到磁盘,everysec表示每秒同步一次(默认值)。

really-use-vm yes
vm-enabled yes #是否使用虚拟内存,默认值为no
vm-swap-file /tmp/redis.swap #虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-max-memory 0 #vm大小限制。0:不限制,建议60-80% 可用内存大小。
vm-page-size 32 #根据缓存内容大小调整,默认32字节。
vm-pages 134217728 #page数。每 8 page,会占用1字节内存。
vm-page-size #vm-pages 等于 swap 文件大小
vm-max-threads 4 #vm 最大io线程数。注意: 0 标志禁止使用vm
hash-max-zipmap-entries 512
hash-max-zipmap-value 64

list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
activerehashing yes

原理

  • 如果设置了一个Slave,无论是第一次连接还是重连到Master,它都会发出一个SYNC命令;
  • 当Master收到SYNC命令之后,会做两件事:
    a) Master执行BGSAVE,即在后台保存数据到磁盘(rdb快照文件);
    b) Master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);
  • 当Master在后台把数据保存到快照文件完成之后,Master会把这个快照文件传送给Slave,而Slave则把内存清空后,加载该文件到内存中;
  • 而Master也会把此前收集到缓冲区中的命令,通过Reids命令协议形式转发给Slave,Slave执行这些命令,实现和Master的同步;
  • Master/Slave此后会不断通过异步方式进行命令的同步,达到最终数据的同步一致;
  • 需要注意的是Master和Slave之间一旦发生重连都会引发全量同步操作。但在2.8之后版本,也可能是部分同步操作。

部分复制

  • 2.8开始,当Master和Slave之间的连接断开之后,他们之间可以采用持续复制处理方式代替采用全量同步。
    Master端为复制流维护一个内存缓冲区(in-memory backlog),记录最近发送的复制流命令;同时,Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID(Master run id)。当网络断开,Slave尝试重连时:
    a. 如果MasterID相同(即仍是断网前的Master服务器),并且从断开时到当前时刻的历史命令依然在Master的内存缓冲区中存在,则Master会将缺失的这段时间的所有命令发送给Slave执行,然后复制工作就可以继续执行了;
    b. 否则,依然需要全量复制操作;
  • Redis 2.8 的这个部分重同步特性会用到一个新增的 PSYNC 内部命令, 而 Redis 2.8 以前的旧版本只有 SYNC 命令, 不过, 只要从服务器是 Redis 2.8 或以上的版本, 它就会根据主服务器的版本来决定到底是使用 PSYNC 还是 SYNC :
    如果主服务器是 Redis 2.8 或以上版本,那么从服务器使用 PSYNC 命令来进行同步。
    如果主服务器是 Redis 2.8 之前的版本,那么从服务器使用 SYNC 命令来进行同步。

同步机制

全量同步

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
  1)从服务器连接主服务器,发送SYNC命令;
  2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
  3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
  4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

增量同步

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis主从同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

最后

emoji

来个简单的小例子

2个项目先来测试一下:

  • eureka-server
  • eureka-service

eureka-server

pom 配置

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
<?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>cn.joylau.cloud.eureka.server</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>eureka-server</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.properties

1
2
3
4
5
server.port=8080
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

EurekaServerApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.joylau.cloud.eureka.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

eureka-service

pom 配置

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
<?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>cn.joylau.cloud.eureka.service</groupId>
<artifactId>eureka-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>eureka-service</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.properties

1
2
3
4
spring.application.name=eureka-service
server.port=8888
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/

EurekaServerApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.joylau.cloud.eureka.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaServiceApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class, args);
}
}

ComputeController

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
package cn.joylau.cloud.eureka.service;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* Created by JoyLau on 4/14/2017.
* cn.joylau.cloud.eureka.service
*/
@RestController
public class ComputeController {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private DiscoveryClient client;
@RequestMapping(value = "/add" ,method = RequestMethod.GET)
public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
ServiceInstance instance = client.getLocalServiceInstance();
Integer r = a + b;
logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
return r;
}
}

配置说明

  • @EnableEurekaServer : 开启服务发现
  • eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下eureka server也是一个eureka client ,必须要指定一个 server
  • 当client向server注册时,它会提供一些元数据,例如主机和端口,URL,主页等。Eureka server 从每个client实例接收心跳消息。 如果心跳超时,则通常将该实例从注册server中删除
  • @EnableDiscoveryClient : 注册一个微服务
  • spring.application.name :应用名
0%