使用redis+lettuce实现消息队列和生产消费

准备

安装redis

使用Docker部署redis

docker run --name redis-mq -d -p 6379:6379 redis redis-server --appendonly yes

创建stream和group

安装redis-cli

apt install -y redis-cli

进入redis-cli

redis-cli -h localhost -p 6379

创建空的stream和对应的group

xgroup create testTask testGroup $ mkstream

代码实现

配置依赖

使用lettuce作为redis连接库

dependencies {
    implementation(kotlin("stdlib-jdk8"))
    implementation("io.lettuce:lettuce-core:5.2.1.RELEASE")
}

生产者

fun consume() {
    val client = RedisClient.create("redis://192.168.75.120:6379/0")
    val connection = client.connect()
    val commands = connection.sync()
    val consumer = io.lettuce.core.Consumer.from("testGroup", "testTask")
    val content = commands.xreadgroup(
        consumer, XReadArgs.StreamOffset.lastConsumed("testTask")
    )
    println(content.size)
    for (c in content) {
        for (k in c.body.keys) {
            println("$k: ${c.body[k]}")
        }
    }
}

消费者

fun main() {
    val client = RedisClient.create("redis://192.168.75.120:6379/0")
    val connection = client.connect()
    val commands = connection.sync()
    val body = HashMap<String, String>()
    for (i in 1..10) {
        body[i.toString()] = "test"
    }
    commands.xadd("testTask", body)
}

使用Gradle打包Kotlin代码以及所有依赖

使用插件Shadow进行打包。build.gradle如下:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.61'
    id 'com.github.johnrengelman.shadow' version '5.2.0'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "org.apache.kafka:kafka-clients:2.4.0"
    implementation "org.apache.kafka:kafka-streams:2.4.0"
    implementation "org.apache.logging.log4j:log4j-api:2.13.0"
    implementation "org.apache.logging.log4j:log4j-core:2.13.0"
    implementation "org.slf4j:slf4j-log4j12:1.7.30"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
jar {
    manifest {
        attributes "Main-Class": "org.example.testK8s.MainKt"
    }
}

之后执行下面的指令(或者在IDEA右侧的Gradle面板可以找到)进行打包:

gradle shadowJar

使用Java/Kotlin等外部消费者无法消费Kafka消息

问题

有一Kafka集群,在集群内机器上生产消费消息均正常,但是使用外部机器(未部署Kafka)无法消费。

解决

在Kafka配置server.properties中加入字段:

port=9092
advertised.host.name=192.168.226.10

使用K8s和Kafka实现工作队列的并行处理

使用K3s搭建Kubernetes集群

使用K3s搭建Kubernetes集群

主要参考你的第一次轻量级K8S体验 —— 记一次Rancher 2.2 + K3S集成部署过程

需要至少2台机器。

当前各软件版本为:

软件名 版本
Docker 18.09.9
K3s v1.17.0+k3s.1
rancher v2.3.3

Docker安装

直接参考官方文档

安装依赖使apt可以使用HTTPS的仓库

apt install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

添加Docker官方的GPG密钥

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

添加Docker稳定版仓库

add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

更新apt索引

apt update

安装Docker

apt-get install -y docker-ce docker-ce-cli containerd.io

配置registry

docker pull registry:2
docker run -d -p 5000:5000 --restart=always --name registry \
    -v /var/data:/var/lib/registry registry:2

修改Docker配置/etc/docker/daemon.json

{
      "insecure-registries" : ["192.168.226.11:5000"]
}

rancher安装

选择一台安装rancher server,这台不安装k3s,好像会有冲突。

docker run -d -v /data/docker/rancher-server/var/lib/rancher/:/var/lib/rancher/ --restart=unless-stopped --name rancher-server -p 80:80 -p 443:443 rancher/rancher:stable

之后访问http://192.168.226.10/(改成自己的ip)进行初始化部署。设定密码和地址后,可选切换语言。

选择添加集群 -> 导入,输入集群名,然后点击创建。保存最后一条指令,记得在kubectl前添加k3s

k3s安装

k3s - Releases下载k3s文件(约50MB的那个),上传至/usr/local/bin/,并执行下面指令

chmod +x /usr/local/bin/k3s

下载pause镜像

docker pull kubernetes/pause:latest
docker tag kubernetes/pause:latest k8s.gcr.io/pause

执行官方安装脚本

curl -sfL https://get.k3s.io | sh -

配置容器引擎使用Docker

vim /etc/systemd/system/multi-user.target.wants/k3s.service

在文件中ExecStart字段最后一个\后添加一行,并填入--docker,类似下面

ExecStart=/usr/local/bin/k3s \
    server \
    --docker

保存配置并重启k3s

systemctl daemon-reload
systemctl restart k3s

执行之前保存的指令,注意不要直接使用下面的样例,主机地址和yaml文件名要使用刚才生成的。

curl --insecure -sfL https://192.168.226.11/v3/import/nb4hcqpzsvggwhcsfgpj5vjss8s2wsqbhv82d72s68hx8cf6gfzhsj.yaml | k3s kubectl apply -f -

稍等一会界面就会出现节点状态。

容器执行测试

参考Kubernetes官方文档

先准备一个镜像

docker pull busybox
docker tag busybox:latest 192.168.226.11:5000/busybox:latest
docker push 192.168.226.11:5000/busybox:latest

假设现在我们从私有仓库拉取一个镜像并执行。

创建job.yaml文件

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: 192.168.226.11:5000/busybox
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

创建Job

k3s kubectl create -f job.yaml

查看Job状态

kubectl get cronjob hello

查看Job创建的Pod

kubectl get jobs

选择上面的一个已完成的pod的id,例如hello-1578545280。获取这个pod的输出

pods=$(kubectl get pods --selector=job-name=hello-1578545280 --output=jsonpath={.items[*].metadata.name})
kubectl logs $pods

添加节点

新节点同样先安装Docker。安装k3s按如下步骤。

查看主节点的node-token

cat /var/lib/rancher/k3s/server/node-token

在新节点上安装并启动为普通节点,注意ip地址和token改成自己的。

curl -sfL https://get.k3s.io | K3S_URL=https://192.168.226.11:6443 K3S_TOKEN=K105933dce21eca704fb3913c26976e0c13c36878fc0c846a0780915c12fccdd78e::server:1a8e2a73e1247868ccb5b3ce0b3cbc7e sh -

同样需要配置容器引擎使用Docker

vim /etc/systemd/system/multi-user.target.wants/k3s-agent.service 

修改方式和主节点一样,然后重启k3s-agent,在rancher界面即可看到新加入的节点。


microk8s部署多节点k8s集群

microk8s部署多节点Kubernetes集群

为了增加工作量,想上一个k8s。microk8s安装简单还支持多节点,就选它了。

准备和安装

snap没有换源一说,只能设置代理。

先设置systemd editor的默认编辑器为vim

vim /etc/profile

加入以下内容

export SYSTEMD_EDITOR="/usr/bin/vim"

使设置生效

source /etc/profile

配置snapd

systemctl edit snapd

加入以下内容

[Service]
Environment="http_proxy=http://127.0.0.1:1080"
Environment="https_proxy=http://127.0.0.1:1080"

配置生效

systemctl daemon-reload
systemctl restart snapd

安装microk8s

snap install microk8s --classic

查看组件状态

> microk8s.status
microk8s is running
addons:
cilium: disabled
dashboard: disabled
dns: disabled
fluentd: disabled
gpu: disabled
helm: disabled
ingress: disabled
istio: disabled
jaeger: disabled
juju: disabled
knative: disabled
kubeflow: disabled
linkerd: disabled
metallb: disabled
metrics-server: disabled
prometheus: disabled
rbac: disabled
registry: disabled
storage: disabled

构建多节点集群

在一台机器上执行

microk8s.add-node

会出现以下内容

Join node with: microk8s.join ip-172-31-20-243:25000/DDOkUupkmaBezNnMheTBqFYHLWINGDbf

复制join指令到其它已经安装了microk8s的机器上执行。

查看集群内节点

> microk8s.kubectl get no
10.22.254.79       Ready       27s   v1.15.3
ip-172-31-20-243   Ready       53s   v1.15.3

kotlin+puppeteer写爬虫

kotlin + puppeteer写爬虫

环境搞的很郁闷,这个代码倒是简单解决了,多亏了一位日本老哥。

主要参考:バックグラウンドで使うpuppeteer with Kotlin

环境准备

build.gradle中添加:

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.1.1'
}

让kotlin使用async/await

接口有变动,参考中代码部分失效。

import kotlin.coroutines.*
import kotlin.js.Promise

suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
    then({ cont.resume(it) }, { cont.resumeWithException(it) })
}

fun <T> async(x: suspend () -> T): Promise<T> {
    return Promise { resolve, reject ->
        x.startCoroutine(object : Continuation<T> {
            override val context = EmptyCoroutineContext

            override fun resumeWith(result: Result<T>) {
                if (result.isSuccess)
                    resolve(result.getOrNull()!!)
                else
                    reject(result.exceptionOrNull()!!)
            }
        })
    }
}

封装puppeteer接口

import kotlin.js.Promise

@Suppress("FunctionName")
@JsModule("puppeteer")
external object Puppeteer {

    class Page {

        fun goto(url: String, options: dynamic): Promise<dynamic>

        fun waitFor(element: String, options: dynamic): Promise<dynamic>

        fun waitFor(num: Int): Promise<dynamic>

        fun content(): Promise<dynamic>

        fun click(selector: dynamic): Promise<dynamic>

        fun close(): Promise<dynamic>

        fun evaluate(pageFunction: Function<dynamic>): Promise<dynamic>

    }

    class Browser {

        fun newPage(): Promise<Page>

        fun close(): Promise<dynamic>

        fun wsEndpoint(): String

    }

    fun launch(options: dynamic): Promise<Browser>
}

爬虫代码

fun main() {
    async {
        val browser = Puppeteer.launch(object {}.also { it: dynamic ->
            it.devtools = true
            it.args = arrayOf("--no-sandbox", "--disable-setuid-sandbox")
            it.headless = true
        }).await()
        try {
            val page = browser.newPage().await()
            page.goto("http://www.baidu.com", object {}.also { it: dynamic -> it.timeout = 10 * 1000 }).await()
            page.waitFor(1000).await()
            val content = page.content().await()
            println(content.toString())
        } finally {
            browser.close().await()
        }
    }
}

注意it.headless = true为开启Chrome的Headless模式,需要显示界面调试置为false即可。


kotlin+nodejs+idea+gradle项目构建

kotlin + nodejs + idea + gradle项目构建

kotlin转nodejs的插件只能用gradle,maven虽然也有但是不会用。这玩意我居然搞了一天才看到”Hello, JavaScript!”,人老了。

主要参考:基于 Node.js 环境的 KotlinJs 工程的完美搭建

创建项目

现在nodejs已经是IDEA中自带插件了,不再需要手动安装。直接创建项目,选gradle -> kotlin(JavaScript)。

之后需要等IDEA将项目配置完毕。

配置build.gradle

group 'org.ndp'
version '1'

buildscript {
    ext.kotlin_version = '1.3.60'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin2js'

buildscript {
    repositories {
        maven {
            url "https://dl.bintray.com/kotlin/kotlin-eap"
        }
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45"
    }
}

apply plugin: 'org.jetbrains.kotlin.frontend'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}

compileKotlin2Js {
    kotlinOptions.moduleKind = "commonjs"
    kotlinOptions.sourceMap = true
    kotlinOptions.metaInfo = true
}

kotlinFrontend {
    npm {
        devDependency "karma"     // development dependency
    }
}

之后需要静待gradle配置好,时间比较长。

不要在compileKotlin2Js中设置输出目录,会导致Error: Cannot find module 'kotlin'

编写Kotlin

src/main/kotlin/下新建kt文件:

fun main(args: Array<String>) {
    println("Hello JavaScript!")
}

点击main旁边的运行即可看到结果。


maven的kotlin项目打包以及添加kotlin依赖

maven的kotlin项目打包以及添加kotlin依赖

准备了一个纯jre的docker镜像,想运行kotlin的jar包,结果找了一圈还是在官网找到了解决办法。

不包含kotlin依赖

pom.xmlbuild -> plugins中添加

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <archive>
            <manifest>
                <addClasspath>true</addClasspath>
                <mainClass>${main.class}</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>

包含kotlin依赖

pom.xmlbuild -> plugins中添加

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.6</version>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals> <goal>single</goal> </goals>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>${main.class}</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
        </execution>
    </executions>
</plugin>

React0x00

Hello, world!

创建项目直接使用Webstorm,选择React App。然后执行:

react-scripts eject

清空src中的文件,重新建立文件index.js,即React入口, 输入:

import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('root')
);

render的第一个参数为jsx,可以理解为React的元素,可以作为js变量的值。

import React from 'react'
import ReactDOM from 'react-dom'

const ele = <h1>Hello, world!</h1>;
ReactDOM.render(
    ele,
    document.getElementById('root')
);

创建组件

箭头函数

import React from 'react'
import ReactDOM from 'react-dom'

const Ele = (props) => {
    return <h1>Hello, {props.title}!</h1>
};
ReactDOM.render(
    <Ele title="React" />,
    document.querySelector('#root')
);

import React from 'react'
import ReactDOM from 'react-dom'

class Ele extends React.Component {
    render() {
        return (
            <div>
                <h1>class app</h1>
                <p>{this.props.title}</p>
            </div>
        )
    }
}

ReactDOM.render(
    <Ele title="arg"/>,
    document.querySelector('#root')
);

组件嵌套

import React from 'react'
import ReactDOM from 'react-dom'

const Title = () => <h1>app title</h1>;

class Ele extends React.Component {
    render() {
        return (
            <div>
                <Title />
                <p>{this.props.title}</p>
            </div>
        )
    }
}

ReactDOM.render(
    <Ele title="test arg"/>,
    document.querySelector('#root')
);

组件样式

使用js

const Title = () => <h1 style={{color: '#0099ff'}}>app title</h1>;

使用class

.blue-title {
    color: #0099ff;
}
import './index.css'

const Title = () => <h1 className="blue-title">app title</h1>;

使用第三方库classnames

安装。

npm i -D classnames
.blue-title {
    color: #0099ff;
}
.red-title {
    color: #ff6666;
}
.bg {
    background-color: antiquewhite;
}
import './index.css'
import classNames from 'classnames'

const Title = () => <h1 className={classNames('bg', {'blue-title': true, 'red-title': false})}>app title</h1>;