About SLF4J

SLF4J 是什么?

Java 的世界里有很多优秀的 log 库,比如 logback,log4j2。SLF4J(Simple Logging Facade)作为一个日志框架的抽象层,以 Facade 模式帮助我们“无缝”地选择、切换这些 log 库。

如何使用 SLF4J?

在项目中要使用 SLF4J 需要做的就是引入 slf4j-api-2.0.7.jar,再根据你选择的底层 log 框架选择对应的 provider。比如,
如果底层使用 log4j2,就要导入 log4j-slf4j-impl(provider/bridge)、log4j-api.jar(log4j 接口)、log4j-core(log4j 实现)。
如果底层使用 logback,就要导入”ch.qos.logback:logback-classic”,这个会自动导入 slf4j-api-2.0.7.jar(provider/bridge)、logback-core-1.3.6.jar(logback 实现)。

SpringBoot 底层默认支持 logbak,如果使用 log4j2 则需要显示的 exclude logback。

<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
      </exclusions>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j2</artifactId>
  </dependency>
<dpendencies>

关于 SLF4J 的 MDC

SLF4J, 在底层库支持 MDC(Mapped Diagnostic Context)的情况下也是支持 MDC 的。MDC data can also be highly helpful in filtering messages or triggering certain actions, 目前也就 log4j2, logback 支持 MDC。在当前微服务流行的情况下让各个微服务的日志格式遵循同一格式是困难的,而且 MDC 的跨服务传递也是不现实的,这个特性在实际工程中用的比较少。

关于 log4j2 自己的 log。

  1. 如果我们希望看到 log4j2 自己的 log,可以设置-Dlog4j2.debug=true,让它自己的 log 显示在 console。
  2. Composite Configuration, 因为可以通过 log4j2.configurationFile 设置多个逗号分隔的配置文件,log4j 有一套自己的逻辑对多个配置文件进行 merge。所以除非项目中有特定的需求,显示的通过 log4j2.configurationFile 显示的指定配置文件可以减少不必要的困惑。



References:
[1]: https://www.slf4j.org/manual.html
[2]: https://logging.apache.org/log4j/2.x/manual/configuration.html#CompositeConfiguration
[3]: https://howtodoinjava.com/logback/setting-up-slf4j/

为本地Mac开发环境设置mongo服务

1)安装

Mac 上通过 brew 安装 Mongo 参考:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/#std-label-install

brew tap mongodb/brew
brew install mongodb-community@5.0

brew services start/restart/stop mongodb-community@5.0
brew services list

通过 brew 安装后,MongoDB 的配置文件在 /usr/local/etc/mongod.conf,可根据实际情况设置端口、认证、复制集等。

net:
  port: 27117
  bindIp: 0.0.0.0
replication:
  replSetName: rs0

2) 设置密码登录

创建超级用户、普通用户:
mongosh

use admin
db.createUser(
  {
    user: "root",
    pwd: "password",
    roles: [
      { role: "root", db: "admin" }
    ]
  }
)

// 创建用户test,对数据库testDB1、testDB2读写的权限,其authorizaiton的DB是testDB1。
use testDB1
db.createUser(
  {
    user: "testuser",
    pwd: "123",
    roles: [
      { role: "readWrite", db: "testDB" },
      { role: "readWrite", db: "testDB2" }
    ]
  }
)

给 root 用户设置 clusterManager 角色,这样才可以使用 rs.initiate()、rs.conf()与复制集相关的命令。

use admin
db.grantRolesToUser(
  "root",
  [ "clusterManager" ]
)

这个 dbAdminAnyDatabase 角色似乎很‘方便’。

db.grantRolesToUser("yourUser", [{"role": "dbAdminAnyDatabase", "db": "admin"}])

3)设置复制集。

复制集需要安全通行。首先生成 mongodb 私钥
Reference:https://docs.mongodb.com/manual/tutorial/enforce-keyfile-access-control-in-existing-replica-set/

openssl rand -base64 756 > KEY_PATH/mongo_server.key

修改配置设置 keyFile:

security:
  keyFile: KEY_PATH/mongo_server.key
  authorization: enabled

强制设置本地节点复制集名称。

replication:
  replSetName: rs0

重启 mongo

brew services restart mongodb-community

进入 mongosh,执行 rs.initiate()初始化数据集:

rs.initiate()
rs.conf()

4)强制设置 master 复制集。

遇到的问题及解决:开始在端口为 27017 的情况下按照 https://mongoing.com/docs/tutorial/convert-standalone-to-replica-set.html 介绍的方式把单节点的 mongo 服务转变为单复制集的方式运行。

接着又更改了 mongo 服务的端口为 27117,这样之前的复制集节点就不对了。
先加上当前 27117 端口的 mongo 服务作为一个复制集。

rs.add("127.0.0.1:27117")

查看复制集配置

rs0 [direct: primary] test> cfg = rs.conf()
{
  _id: 'rs0',
  version: 4,
  term: 30,
  members: [
    {
      _id: 0,
      host: '127.0.0.1:27017',
      arbiterOnly: false,
      buildIndexes: true,
      hidden: false,
      priority: 1,
      tags: {},
      secondaryDelaySecs: Long("0"),
      votes: 1
    },
    {
      _id: 1,
      host: '127.0.0.1:27117',
      arbiterOnly: false,
      buildIndexes: true,
      hidden: false,
      priority: 1,
      tags: {},
      secondaryDelaySecs: Long("0"),
      votes: 1
    }
  ],
  protocolVersion: Long("1"),
  writeConcernMajorityJournalDefault: true,
  settings: {
    chainingAllowed: true,
    heartbeatIntervalMillis: 2000,
    heartbeatTimeoutSecs: 10,
    electionTimeoutMillis: 10000,
    catchUpTimeoutMillis: -1,
    catchUpTakeoverDelayMillis: 30000,
    getLastErrorModes: {},
    getLastErrorDefaults: { w: 1, wtimeout: 0 },
    replicaSetId: ObjectId("616d08c744db747bb7e580b8")
  }
}

删除第一节点,

cfg.members.splice(0,1)

强制执行重新配置

rs.reconfig(cfg, {force : true})

5)设置复制集。

通过用户名密码登录

mongosh --port 27117 -u testuser -p 123 --authenticationDatabase  test

6)Mac 环境设置 maxfile limit

查看当前设置:

launchctl limit maxfiles

如果 soft limit 是 256,则执行下面命令扩大。否则 mongo server 可用的线程将受限。

sudo launchctl limit maxfiles 65536 200000

因为机器重启后以上更改会失效,所以还需要添加以上命令到开机脚本中。因为使用到了 sudo,可使用 visudo 修改设置免密码。

%admin          ALL = (ALL) NOPASSWD: ALL

7)mongo.conf

mongo.conf 大致如此:
如果需要设置 MongoDB 无需认证登录的话,需要 authrozation、keyFile 这两个字段同时注释掉。·

systemLog:
  destination: file
  path: /usr/local/var/log/mongodb/mongo.log
  logAppend: true
storage:
  dbPath: /usr/local/var/mongodb
net:
  bindIp: 127.0.0.1
  port: 27017
  maxIncomingConnections: 4096
security:
  keyFile: /YOURPATH/mongo_server.key
  authorization: enabled
replication:
  replSetName: rs0

Monoid

Monoid

Type signature

public interface Monoid<T> : SemiGroup<T> {
    T Empty
}

class (Semigroup a) <= Monoid a where
    empty :: a

Monoid Laws:

The empty value doesn't change the meaning of a monoidal value.
a <> empty == a
empty <> a == a

Usage/Feature of semigroup:

Semigroup/Monoid are domain modeling tools.

Semigroup

SemiGroup 幺半群

Type signature:

// C#定义SemiGroup:
public interface SemiGroup<T> {
    T Append(T thingToAppend);
}

// 用Haskell定义:
class SemiGroup a where
    append :: a-> a -> a

// Haskell实现的String with append:
instance Semigroup String where
    append a b = a ++ b

__Law__s are additional properties that need to be satisfied beyond what’s expressed in the type signature.

Laws of Semigroup:

Append is associative.

Append(Append(a, b), c) == Append(a, Append(b, c))

(a <> b) <>> c == a <> (b <> c)

Usage/Feature of semigroup:

1)Partition friendly

2)Incremental

使用OpenSSL创建CA、Service证书的步骤

OpenSSL是SSL、TLS协议的一个实现、开源工具集。OpenSSL可用于密钥、证书管理以及相关测试。

准备目录

mkdir root-ca
cd root-ca
mkdir certs db private
chmod 700 private
touch db/index
openssl rand -hex 16 > db/serial
echo 1001 > db/crlnumber

创建RootCA

root-ca.conf
1
2
3
4
5
# Create CA Private Key
openssl req -new -config root-ca.conf -out root- ca.csr -keyout private/root-ca.key

# CA self-sign
openssl ca -selfsign -config root-ca.conf -in root-ca.csr -out root-ca.crt -extensions ca_ext

二级CA

sub-ca.conf
# 这一步同时生产私钥和csr
openssl req -config sub-ca.conf -new -out sub-ca.csr -keyout private/sub-ca.key

# 用root ca签署sub ca
openssl ca -config root-ca.conf -in sub-ca.csr -out sub-ca.crt -extensions sub_ca_ext    

Service Certificate

openssl genrsa -aes128 -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl ca -config sub-ca.conf -in server.csr -out server.crt -extensions server_ext

Reference:

《OpenSSL攻略》 这篇文档详细介绍了OpenSSL的使用。

更多背景知识可参考:《HTTPS权威指南》

修改Android模拟器中的文件

本地开发机运行着server向跑在模拟器中的app提供rest api服务。

1)模拟器中的127.0.0.1是指模拟器自己本机。

2)模拟器中使用10.0.2.2来访问开发机。

3)如果在本地tomcat启用了虚拟主机,可以修改模拟器中的/etc/hosts来指向域名。

// 列出安装的模拟器
emulator -list-avds

// 以可写模式启动模拟器
emulator -avd Your_Emulator_Name -writable-system
adb root
adb remount

// 把模拟器上的文件pull到本地
adb pull /etc/hosts newHosts

// 添加 "10.0.2.2    your_domain"
vi newHosts             

adb push newHosts /etc/hosts

// 进入模拟器的shell
adb shell

Reactive Scala Driver for MongoDB

其实最好的学习文档就是官方提供的http://reactivemongo.org/releases/0.12/documentation/index.html

关于Driver和Connection

MongoDriver的实例管理者共享的资源,比如用于异步处理的actor system。
一个连接管理着网络连接池。通常一个driver和connection在一个应用中只需要一个实例即可。

MongoConnection是一个逻辑连接的表示,实际是有一个连接池构成,默认有10个连接。可以通过connection option参数rm.nbChannelsPerNode进行设置。
Driver和connection的创建是有代价的,所以在应用中可以创建之后共享使用。而DefaultDB, Collection这样的对象是及其轻量的,随用随建即可。而且我们也不应创建出DB,Collection之后把这些引用就保存起来一直使用,因为如果底层网络出现问题,你一直使用的这个DB活着Collection对象就会出现问题。随用随取这种方式可以避免获得有问题的对象。

Connection options

import reactivemongo.api.MongoConnectionOptions

val conOpts = MongoConnectionOptions(/* connection options */)
val connection4 = driver1.connection(List("localhost"), options = conOpts)

通过设置连接选项参数可以指定网络连接、mongo写关注等方面的行为。
详情参见

Write-concern文档
Read-preferences文档

在遗留项目中使用 Spring Boot and Cloud

几年前还没有Spring Booot的时候,公司Maven项目里一般都定义了自己的parent pom,规范了可用的第三方类库。在这样的系统里如何引进Spring Boot,Spring Clould这样的工具呢?这篇官方文档介绍了在已有parent pom的情况下如何使用boot。

下面算是几个学习摘要。

1) 如果遗留项目中没有使用自己parent pom,那么在项目模块中就可以直接设置parent为spring-boot-starter-parent

<!-- Inherit defaults from Spring Boot -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.6.RELEASE</version>
</parent>

如果想更改某个子项目版本,通过下面设置属性的方式进行更改。

<properties>
    <spring-data-releasetrain.version>Fowler-SR2</spring-data-releasetrain.version>
</properties>

查看有哪些可更改的properties,参考这里

2)如果已有的maven项目中已经存在parent pom,那么可以通过 denpendencyManagemengt的方式指定boot的BOM。

<dependencyManagement>
     <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>1.3.6.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

这种情况下,如果需要指定某个版本的库,就需要在定义spring-boot-dependencies的前面定义dependence了。就像下面这样,spring-data-releasetrain使用了不同于1.3.6.RELEASE的另一个版本Fowler-SR2。

<dependencyManagement>
    <dependencies>
        <!-- Override Spring Data release train provided by Spring Boot -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-releasetrain</artifactId>
            <version>Fowler-SR2</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>1.3.6.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

3)关于Maven中BOM[Bill Of Materials]的概念。
这篇文章讲得不错:http://howtodoinjava.com/maven/maven-bom-bill-of-materials-dependency/

A BOM dependency keep track of version numbers and ensure that all dependencies (both direct and transitive) are at the same version.

4) 如何添加BOM依赖?

maven通过dependencyManagement来支持BOM。比如像下面这样加入Spring BOM。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>4.0.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

那么,以后再使用那个这个BOM包含的库时,就不必制定version了。比如:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
<dependencies>

在__dependencyManagement下的声明不会引入实际的依赖__,它仅仅__用来约束dependencies__下面的依赖使用。
虽然这个BOM概念很赞,但是不要幻想有什么通用的BOM。每个项目都有它自己的BOM文件用来管理用到的库依赖。

5) 图例

下图用来说明一下从start.spring.io网站,生产的模版项目中,pom.xml对SpringBoot,SpringCloud作为BOM的使用。一般地,我们应该把这这些BOM定义在作为parent使用的pom里进行集中地、统一地管理。
一个pom的例子