javaでsynchronizedブロックにて、
Stringを使うと、
異なるオブジェクトであっても、
文字列が同じであれば排他制御がかかる。
ただし、異なるオブジェクト・同一文字列で排他制御をかけるべきでない。
なぜなら、依存ライブラリ等でもしsyncrozinedブロックを使っていたら、
最悪デッドロック等もありえる。
(そんなライブラリはないと思うが。。)
これはStringの特性によるものなのだが、
いつか詳細を記載したいとは思う。
IT系のめもを蓄積していこうかと
javaでsynchronizedブロックにて、
Stringを使うと、
異なるオブジェクトであっても、
文字列が同じであれば排他制御がかかる。
ただし、異なるオブジェクト・同一文字列で排他制御をかけるべきでない。
なぜなら、依存ライブラリ等でもしsyncrozinedブロックを使っていたら、
最悪デッドロック等もありえる。
(そんなライブラリはないと思うが。。)
これはStringの特性によるものなのだが、
いつか詳細を記載したいとは思う。
持続的接続をredisで行う場合、
接続は1つでよいと書いた記憶があったが、
javaでノンブロッキングでも大丈夫か検証してみた。
javaのredisクライアントはlettuceを使用し、
1つのコネクションを、
マルチスレッドで共有し、SET、GETを試みたところ、
結果にずれは生じなかった。
結論として、
redisはシングルスレッドであるため、
持続的接続の場合は1コネクションでOK。
ただし、javaではマルチスレッドの性能を生かすため、
CPU数 x 2〜3 ぐらいがいいのかなとは思う。
ちなみに、上記でやっても問題なしでした。
以上
JavaMailでSMTP接続をして、
メールを送信しようとした時、
javax.mail.MessagingException: 501 Syntax: HELO hostname
のエラーがでることがある。
これは送信元のサーバのホスト名が
/etc/hostsに掲載されていない場合に起こる。
その他にも色々方法はあるようだが、
/etc/hostsにhostnameをしっかり書いておくことは大事です。
とはいえ、最近クラウドばかり使っていると、
この辺疎かになりがち。。
javaのweb socketで、decoderとpathparamはある条件において不可能なようなだ。
以下の場合はダメ。
@ServerEndpoint(
value = "/ws/{p1}/{p2}/"
decoders = {HogeDecoder.class},
encoders = {HogeEncoder.class})
public class HogeWebsocket {
/**
* open hander.
*/
@OnOpen
public void onOpen(@PathParam("p1") String p1, Session session, EndpointConfig config) {
:
:
}
}
これだと、p1がdecoderの対象になるみたい。
そのため、次のような方法で対処する。
@ServerEndpoint(
value = "/ws/{p1}/{p2}/"
decoders = {HogeDecoder.class},
encoders = {HogeEncoder.class})
public class HogeWebsocket {
/**
* open hander.
*/
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
Map<String, String> pathParameters = session.getPathParameters();
String p1 = pathParameters.get("p1");
:
}
}
本件は、decoderでバイナリからテキストに変換しようとした際に起きた現象。
なので、decoderが何をするかによるとは思うが。。
javaのページを見たが、それらしき記述はなかった。。
redisの冗長化を行うためには、
がある。
clusterはredis3より正式サポートされた機能である。
特徴としては、以下の通り。
master <-> slave構成だと、
masterが倒れたときのフェイルオーバが皆無である。
master <-> slave構成 + sentinelだと、
masterが倒れたときに、slaveが自動昇格できる。
ただ、slave x 2以上、sentinel x 3以上が望ましい感じがする。
cluster構成だと、
分散でデータを保持しているため、
slaveも同時に使い、自動昇格させる必要がある。(slaveがあれば自動昇格する)
master x 3以上、slave x 3以上にする必要がある。
と考えると、clusterが一番な気がするが、
clusterの欠点は以下の通り。
などなど。
とくに、クライアントライブラリが少ないのは気がかりである。
python, rubyではあるらしい。
javaでもlettuceが対応している。
javaのコードは以下の通り。
List<RedisURI> list = new ArrayList<>();
list.add(new RedisURI("192.168.1.45", 16381, 1, TimeUnit.SECONDS));
list.add(new RedisURI("192.168.1.45", 16382, 1, TimeUnit.SECONDS));
list.add(new RedisURI("192.168.1.45", 16383, 1, TimeUnit.SECONDS));
list.add(new RedisURI("192.168.1.45", 16384, 1, TimeUnit.SECONDS));
list.add(new RedisURI("192.168.1.45", 16385, 1, TimeUnit.SECONDS));
list.add(new RedisURI("192.168.1.45", 16386, 1, TimeUnit.SECONDS));
RedisClusterClient client = RedisClusterClient.create(new Iterable<RedisURI>() {
@Override
public Iterator<RedisURI> iterator() {
return list.iterator();
}
});
AsyncExecutions<String> excutions = null;
RedisAdvancedClusterAsyncCommands<String, String> con = client.connect().async();
AsyncNodeSelection<String, String> masters = con.masters();
excutions = masters.commands().set("hoge", "fuga");
excutions.forEach(result -> result.thenAccept(ret -> System.out.println(ret)));
excutions = masters.commands().get("hoge");
excutions.forEach(result -> result.thenAccept(ret -> System.out.println(ret)));
con.close();
client.shutdown();
しかし、multi – execができないのは結構痛い。。。
うまいことやればできるのかな。。
ただ、分散してしまうから、無理なきがする。
このlettuceっていうライブラリは良さげ。
nettyをベースに使っていて、
ノンブロッキングをサポートしているしね。
昨日書いた記事を検証してみた。
以下3つの例を検証。
1.単純ケース
@WebServlet(name = "test01", urlPatterns = {"/test01"})
public class Test01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
try (PrintWriter writer = resp.getWriter();) {
resp.setStatus(200);
resp.setContentType("text/plain");
writer.write("name is " + name);
writer.flush();
writer.close();
}
}
}
2.絶対ダメなケース
@WebServlet(name = "test02", urlPatterns = {"/test02"})
public class Test02 extends HttpServlet {
private String name;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
name = req.getParameter("name");
try (PrintWriter writer = resp.getWriter();) {
resp.setStatus(200);
resp.setContentType("text/plain");
writer.write("name is " + name);
writer.flush();
writer.close();
}
}
}
3.今回検証したかったケース
@WebServlet(name = "test03", urlPatterns = {"/test03"})
public class Test03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
new MyClass(req, resp).execute();
}
class MyClass {
private HttpServletRequest req;
private HttpServletResponse resp;
private String name;
public MyClass(HttpServletRequest req, HttpServletResponse resp) {
this.req = req;
this.resp = resp;
name = req.getParameter("name");
}
public void execute() throws IOException {
try (PrintWriter writer = resp.getWriter();) {
resp.setStatus(200);
resp.setContentType("text/plain");
writer.write("name is " + name);
writer.flush();
writer.close();
}
}
}
}
結論としては、3は大丈夫であった。
jmeterで同時接続100を10回やって、一度も不整合は起きず。
servletはエントリーポイントのインスタンスは1つしか作成しない。
そして、それが複数スレッドで共有される。
そのため、インスタンスフィールドはスレッドセーフにならない。
・スレッドセーフではない例
public class TestServlet extends HttpServlet {
private String name;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
name = req.getParameter("name");
PrintWriter writer = res.getWriter();
writer.print(name);
}
}
で、ここからがわからんところなのだが、
ローカル変数はスレッドセーフになるのだから、
doGetの処理の中で、以下のようなことをしたら、問題ないのか?
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
new MyClass(req, res).execute();
}
}
public class MyClass {
private HttpServletRequest req;
private HttpServletResponse res;
public MyClass(HttpServletRequest req, HttpServletResponse res) {
this.req = req;
this.res = res;
}
public void execute() {
String name = req.getParameter("name");
PrintWriter writer = res.getWriter();
writer.print(name);
}
}
もし、上記でスレッドセーフになるっていうなら、
それでよくね?って思っちゃう。
それとも、MyClassのインスタンスフィールドは実はスレッドセーフではないのか?
メモリ使用量としては、生成するインスタンス分大きいのはわかるが、
もし上記で解決するなら、煩わしいスレッド問題とはオサラバできると思うのだが、
問題あるのだろうか?
servletはフレームワークの世界だから、厳密にわかる人は少ないのだろうけど、
ここは教えて欲しい。。。
・・・後日
上の例は大丈夫だった。
新しく記事を書きました。
ラムダ式が導入されて(java8)結構たちましたが、
あまりやる気がしなかったが、
重い腰を上げて、おれおれフレームワークに少しづつ適用中。
いろいろ例はあるが、やはりコレクションには適用しやすい。
二元ループをするケースで結構はまったので、
メモを残しておく。
(例)URLのBodyパラメータを手作業で分解する場合
Bodyパラメータはこんな感じを想定
hoge=1&fuga=2&piyo[]=1&piyo[]=2
ここではInputStreamから抜かないで、一度分解したのち、文字列に戻すということをやる。
これを従来の処理でやると以下になる。
Map<String, List<String>> paramsBody = new LinkedHashMap<String, List<String>>();
StringBuffer buffer = new StringBuffer();
// サーブレットリクエストからパラメータの情報をとる
Map<String, String[]> params = servletRequest.getParameterMap();
// 回しながら、List型に変換して、さらに文字列を構築する
String sep = "";
for (Iterator<Entry<String, String[]>> iterator = params.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) iterator.next();
String name = entry.getKey();
String[] values = entry.getValue();
if (!paramsBody.containsKey(name)) {
paramsBody.put(name, new ArrayList<String>(Arrays.asList(values)));
}
for (String value : values) {
buffer.append(sep).append(name).append("=").append(WsUrl.decode(value, encoding));
sep = "&";
}
body = buffer.toString();
}
おなじことをラムダ式をつかうと、
Map<String, List<String>> paramsBody = new LinkedHashMap<String, List<String>>();
StringBuffer buffer = new StringBuffer();
// サーブレットリクエストからパラメータの情報をとる
Map<String, String[]> params = servletRequest.getParameterMap();
// 回しながら、List型に変換して、さらに文字列を構築する
String paramString = params.entrySet()
.stream()
.map(entry -> {
String name = entry.getKey();
return Arrays.stream(entry.getValue())
.map(value -> String.format("%s=%s", name, WsUrl.decode(value, encoding)))
.collect(Collectors.joining("&"));
})
.collect(Collectors.joining("&"));
buffer.append(paramString);
body = buffer.toString();
ここまでくるのに結構大変だった。。
javaでwebsocketクライアントをやろうとしたら、
websocket-apiライブラリでは動かなかった。
なんでも、meta-infのserviceのところに何かを配備する必要があるらしい。
そこで、いろいろ調べたら、glassfishのライブラリを導入すれば、
一発で済むことがわかった。
pom.xmlは以下を書くだけ。
<dependency> <groupId>org.glassfish.tyrus.bundles</groupId> <artifactId>tyrus-standalone-client</artifactId> <version>1.11</version> </dependency>
あとは、
WebSocketContainer cont = ContainerProvider.getWebSocketContainer();
URI uri = URI.create("ハンドシェイクのURL");
cont.connectToServer("websocketのインスタンス", uri);
で接続できる。
検証したのは、JDK8だが、JDK7でも大丈夫でしょう。
以上
実行可能warをmavenで作る場合、
warパッケージの直下にmainファイルを配備する必要がある。
しかし、warアプリケーションをpackageすると、
WEB-INF/classes以下にmainファイルも配備されてしまう。
そのため、maven3では、
antrunプラグインを用いて、mainファイルを移動する必要がある。
pom.xmlはこんな感じかな。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>move-main</id>
<phase>prepare-package</phase>
<configuration>
<tasks>
<move todir="target/${project.build.finalName}">
<fileset dir="target/classes">
<include name="app/AppMain.class" />
</fileset>
</move>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
これにより、warファイル直下に実行可能クラスが配備される。
また、jettyなどのembedを組み込む場合、
一緒に依存関係も持って行ってやる必要がある。
こんな感じ。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>jetty-classpath</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty,javax.servlet,javax.websocket</includeGroupIds>
<excludes>META-INF/ECLIPSEF.*</excludes>
<outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
そして、mainファイルはこんな感じ。
package app;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import java.net.URL;
public class AppMain {
public static void main(String[] args) throws Exception {
// 初期設定
int port = 8080;
System.setProperty("prefix", "/");
// サーバ生成
Server server = new Server(port);
// warを読み込むコンテクスト生成
URL warUrl = AppMain.class.getProtectionDomain().getCodeSource().getLocation();
String warLocation = warUrl.toExternalForm();
WebAppContext context = new WebAppContext();
context.setWar(warLocation);
context.setContextPath("/");
// コンテクストにサーバをセットする
// ※重要
context.setServer(server);
// websocketを使えるようにする
WebSocketServerContainerInitializer.configureContext(context);
// 開始
server.setHandler(context);
server.start();
server.join();
}
}
上記が非常に重要。
これで、websocketのendpointをServletContextListener側で指定してやるとうまくいく。
package app;
import websocket.WebsocketIndex;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
public class AppInitializeListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// embed用のwebsocketの追加
try {
ServerContainer wsContainer = (ServerContainer) sce.getServletContext().getAttribute(ServerContainer.class.getName());
System.out.println(String.format("wsContainer => %s", wsContainer));
if (wsContainer != null) {
wsContainer.addEndpoint(WebsocketIndex.class);
}
} catch (DeploymentException e) {
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
実行可能jarでwebsocketを作成する場合、
mainクラス内でendpointを設定してやればよいのだが、
実行可能warの場合、
mainクラスが実際のwebsocketのendpointのクラスの配備位置が異なるため、
クラスのロードが出来ない状態となる。
そのため、上記のような方法で、動的に指定してやることで、
実行可能warにおいて、websocketが利用できる。
【参考】
・実行可能warの生成の詳細がかかれています。
http://qiita.com/k_ui/items/1d3bbbd7993c4c9adf71
・実行可能jarでのwebsocketの設定方法が書かれています。
以上